git: c7bcccbb669e - stable/14 - MFC: MFV: less v710.

From: Xin LI <delphij_at_FreeBSD.org>
Date: Sat, 26 Sep 2026 01:49:24 UTC
The branch stable/14 has been updated by delphij:

URL: https://cgit.FreeBSD.org/src/commit/?id=c7bcccbb669ee9900bf13c6ecbf42daa9d5d1f82

commit c7bcccbb669ee9900bf13c6ecbf42daa9d5d1f82
Author:     Xin LI <delphij@FreeBSD.org>
AuthorDate: 2026-09-19 18:20:29 +0000
Commit:     Xin LI <delphij@FreeBSD.org>
CommitDate: 2026-09-26 01:48:17 +0000

    MFC: MFV: less v710.
    
    (cherry picked from commit 4523eebc6c1828d4fd41d7f1ab943cf079043bc8)
---
 ObsoleteFiles.inc                       |   4 +
 UPDATING                                |  21 ++
 contrib/less/NEWS                       |  81 +++++
 contrib/less/brac.c                     |  17 +-
 contrib/less/ch.c                       |  46 ++-
 contrib/less/charset.c                  |  47 +--
 contrib/less/cmd.h                      |  13 +-
 contrib/less/cmdbuf.c                   |  33 +-
 contrib/less/command.c                  | 228 ++++++++----
 contrib/less/decode.c                   | 371 ++++++++++++-------
 contrib/less/edit.c                     |  48 +--
 contrib/less/filename.c                 | 133 +++++--
 contrib/less/forwback.c                 |  11 +-
 contrib/less/funcs.h                    |  43 +--
 contrib/less/help.c                     |   6 +-
 contrib/less/ifile.c                    |   9 +-
 contrib/less/input.c                    |  14 +-
 contrib/less/jump.c                     |  23 +-
 contrib/less/less.h                     |  16 +-
 contrib/less/less.hlp                   |   4 +
 contrib/less/less.nro                   | 238 ++++++++----
 contrib/less/lessecho.nro               |  67 +++-
 contrib/less/lesskey.c                  | 364 -------------------
 contrib/less/lesskey.h                  |  22 --
 contrib/less/lesskey.nro                | 120 +++---
 contrib/less/lesskey_parse.c            |  83 +++--
 contrib/less/lessmsg                    | 372 +++++++++++++++++++
 contrib/less/lessmsg.inc                | 331 +++++++++++++++++
 contrib/less/lessmsg_int                |   5 +
 contrib/less/line.c                     | 106 ++++--
 contrib/less/linenum.c                  |   6 +-
 contrib/less/lmsg.c                     | 218 +++++++++++
 contrib/less/lmsg.h                     |  24 ++
 contrib/less/lsystem.c                  | 124 ++++---
 contrib/less/main.c                     |  66 ++--
 contrib/less/mark.c                     |  40 +-
 contrib/less/optfunc.c                  | 157 ++++----
 contrib/less/option.c                   |  59 +--
 contrib/less/option.h                   |   1 +
 contrib/less/opttbl.c                   | 622 ++++++++++++++++++--------------
 contrib/less/os.c                       |   2 +-
 contrib/less/output.c                   |  67 ++--
 contrib/less/pattern.c                  |  41 ++-
 contrib/less/pattern.h                  |  15 +-
 contrib/less/pckeys.h                   |  11 +-
 contrib/less/prompt.c                   |  39 +-
 contrib/less/screen.c                   | 510 ++++++++++++++------------
 contrib/less/search.c                   | 104 +++---
 contrib/less/signal.c                   |  10 +-
 contrib/less/tags.c                     |  20 +-
 contrib/less/ttyin.c                    |   8 +
 contrib/less/version.c                  |  20 +-
 targets/pseudo/userland/Makefile.depend |   1 -
 usr.bin/Makefile                        |   1 -
 usr.bin/less/Makefile                   |   5 +-
 usr.bin/less/Makefile.common            |   5 +-
 usr.bin/lesskey/Makefile                |   7 -
 usr.bin/lesskey/Makefile.depend         |  15 -
 58 files changed, 3274 insertions(+), 1800 deletions(-)

diff --git a/ObsoleteFiles.inc b/ObsoleteFiles.inc
index d4c05005f5a2..5b7648b78e6d 100644
--- a/ObsoleteFiles.inc
+++ b/ObsoleteFiles.inc
@@ -51,6 +51,10 @@
 #   xargs -n1 | sort | uniq -d;
 # done
 
+# 20260925: lesskey(1) removed, lesskey(5) man page added
+OLD_FILES+=usr/bin/lesskey
+OLD_FILES+=usr/share/man/man1/lesskey.1.gz
+
 # 20260602: Removed by 0a36787e4c1f on 20210723
 OLD_FILES+=usr/share/locale/hr_HR.ISO8859-2/LC_MESSAGES
 OLD_FILES+=usr/share/locale/nl_BE.ISO8859-1/LC_MESSAGES
diff --git a/UPDATING b/UPDATING
index 0e3fd5317bb0..443af136201c 100644
--- a/UPDATING
+++ b/UPDATING
@@ -12,6 +12,27 @@ Items affecting the ports and packages system can be found in
 /usr/ports/UPDATING.  Please read that file before updating system packages
 and/or ports.
 
+20260925:
+	less(1) has been updated to version 710, which removes the obsolete
+	lesskey(1) utility.  It is no longer built or installed, and the
+	manual page describing the lesskey file format has moved from
+	lesskey(1) to lesskey(5).
+
+	No separate compilation step is needed any more: less(1) has been
+	able to read plain-text lesskey files directly since version 590,
+	imported into FreeBSD in 2021.  Keep your key bindings in the
+	plain-text ~/.lesskey (or the file named by $LESSKEYIN) rather than
+	compiling them into the binary ~/.less; system-wide bindings belong
+	in /etc/syslesskey (or $LESSKEYIN_SYSTEM) rather than /etc/sysless.
+	If both forms are present, the plain-text one is used and the binary
+	one is ignored.
+
+	Existing binary lesskey files are still read, so nothing stops
+	working immediately, but they can no longer be generated or
+	regenerated.  Binary files produced by lesskey from less 240 or
+	older are no longer supported at all and must be rewritten in the
+	plain-text format.
+
 20260727:
 	The wait_for_pids() function in rc.subr, used by rc scripts
 	when stopping or restarting a service, has been modified to
diff --git a/contrib/less/NEWS b/contrib/less/NEWS
index a031353acf46..49c4a5c0f317 100644
--- a/contrib/less/NEWS
+++ b/contrib/less/NEWS
@@ -9,6 +9,87 @@
   Report bugs, suggestions or comments at 
   https://github.com/gwsw/less/issues.
 
+======================================================================
+
+	Major changes between "less" versions 704 and 710
+
+* Add || command.
+
+* In | and || commands, ^N allows entering line number instead of mark letter.
+
+* Add "ESC-]" / "ESC-RIGHTARROW" (right-limit-scroll) command (github #783).
+
+* In lesskey files, add \k^x syntax for control chars and \k+x syntax
+  for shifted chars.
+
+* Underline OSC 8 links in text.
+
+* Add -DO for custom coloring of OSC 8 links.
+
+* Add LESSMSG, which allows translation of messages into other languages.
+
+* Add LESSSECURE_DISALLOW.
+
+* Add LESS_SHELL_COPTION.
+
+* Add %G, %y, and %% in prompt strings.
+
+* Add lesskey support for shift-uparrow, shift-downarrow, shift-pageup,
+  shift-pagedown, ctl-uparrow, ctl-downarrow, ctl-pageup, ctl-pagedown,
+  shift-delete.
+
+* Change "make check" to use nonstandard object and binary file names,
+  to avoid accidentally installing test binaries (github #791).
+
+* Fix issues on systems with volatile getenv return values.
+
+* Improve error handling for malformed binary lesskey file.
+
+* Improve messaging when using __mouse after --emouse (github #808).
+
+* Remove support for obsolete pre-241 lesskey binary format.
+
+* Remove obsolete lesskey program.
+
+* Move lesskey man page to section 5.
+
+* Update display when resize window while in F mode on Windows.
+
+* Fix missing "END" message at end of &-filtered output (github #774).
+
+* Fix missing bell with G command at end of &-filtered output (github #774).
+
+* Don't reset signal handlers in a subprocess on Windows.
+
+* Fix ' command to not modify the mark when given a numeric argument
+  (github #799).
+
+* Fix erroneous message from --rscroll command when rscroll char is non-ASCII.
+
+* Fix unexpected exit when TERM is invalid (github #809).
+
+* Fix possible crash when use ":e #" after ":d" (github #810).
+
+* Fix infinite loop if invalid option letter in LESS_UNSUPPORT.
+
+* Fix crash if run !! command with no previous ! command (github #812).
+
+* Fix inability to run a shell command starting with "!" character.
+
+* Fix bugs using ^S search modifier (github #814).
+
+* Fix display bugs when a status message contains non-ASCII chars
+  or is displayed on a very narrow screen (github #816).
+
+* Fix bug defining command which has a built-in command as a prefix
+  (github #795).
+
+* Fix string overrrun if input contains "\e\0".
+
+* Fix memory leak when using ${...} in lesskey file.
+
+* Fix status column display when long line is marked (github #820).
+
 ======================================================================
 
 	Major changes between "less" versions 702 and 704
diff --git a/contrib/less/brac.c b/contrib/less/brac.c
index b3b74e07d18a..deaedb187b3c 100644
--- a/contrib/less/brac.c
+++ b/contrib/less/brac.c
@@ -23,12 +23,12 @@
  * The characters which serve as "open bracket" and 
  * "close bracket" are given.
  */
-public void match_brac(char obrac, char cbrac, int forwdir, int n)
+public void match_brac(char obrac, char cbrac, lbool forwdir, int n)
 {
 	int c;
 	int nest;
 	POSITION pos;
-	int (*chget)();
+	int (*chget)(void);
 
 	extern int ch_forw_get(), ch_back_get();
 
@@ -41,9 +41,9 @@ public void match_brac(char obrac, char cbrac, int forwdir, int n)
 	if (pos == NULL_POSITION || ch_seek(pos))
 	{
 		if (forwdir)
-			error("Nothing in top line", NULL_PARG);
+			error(LM(Nothing_in_top_line), NULL_PARG);
 		else
-			error("Nothing in bottom line", NULL_PARG);
+			error(LM(Nothing_in_bottom_line), NULL_PARG);
 		return;
 	}
 
@@ -55,9 +55,9 @@ public void match_brac(char obrac, char cbrac, int forwdir, int n)
 		if ((c = ch_forw_get()) == '\n' || c == EOI)
 		{
 			if (forwdir)
-				error("No bracket in top line", NULL_PARG);
+				error(LM(No_bracket_in_top_line), NULL_PARG);
 			else
-				error("No bracket in bottom line", NULL_PARG);
+				error(LM(No_bracket_in_bottom_line), NULL_PARG);
 			return;
 		}
 	} while (c != obrac || --n > 0);
@@ -83,8 +83,7 @@ public void match_brac(char obrac, char cbrac, int forwdir, int n)
 			if (nest == INT_MAX)
 				break;
 			nest++;
-		}
-		else if (c == cbrac && --nest < 0)
+		} else if (c == cbrac && --nest < 0)
 		{
 			/*
 			 * Found the matching bracket.
@@ -95,5 +94,5 @@ public void match_brac(char obrac, char cbrac, int forwdir, int n)
 			return;
 		}
 	}
-	error("No matching bracket", NULL_PARG);
+	error(LM(No_matching_bracket), NULL_PARG);
 }
diff --git a/contrib/less/ch.c b/contrib/less/ch.c
index dfe1e7169f72..beda45895c52 100644
--- a/contrib/less/ch.c
+++ b/contrib/less/ch.c
@@ -124,8 +124,8 @@ extern int autobuf;
 extern int sigs;
 extern int follow_mode;
 extern lbool waiting_for_data;
-extern constant char helpdata[];
-extern constant int size_helpdata;
+extern constant char *help_data;
+extern constant int size_help_data;
 extern IFILE curr_ifile;
 #if LOGFILE
 extern int logfile;
@@ -256,7 +256,7 @@ static int ch_get(void)
 				return ('?');
 			if (less_lseek(ch_file, (less_off_t)pos, SEEK_SET) == BAD_LSEEK)
 			{
-				error("seek error", NULL_PARG);
+				error(LM(seek_error), NULL_PARG);
 				clear_eol();
 				return (EOI);
 			}
@@ -275,10 +275,12 @@ static int ch_get(void)
 			ch_have_ungotchar = FALSE;
 		} else if (ch_flags & CH_HELPFILE)
 		{
-			bp->data[bp->datasize] = (unsigned char) helpdata[ch_fpos];
+			bp->data[bp->datasize] = (unsigned char) help_data[ch_fpos];
 			n = 1;
 		} else
 		{
+			if (ch_file < 0)
+				return (EOI);
 			n = iread(ch_file, &bp->data[bp->datasize], LBUFSIZE - bp->datasize);
 		}
 
@@ -324,7 +326,22 @@ static int ch_get(void)
 			/* Either end of file or no data available.
 			 * read_again indicates the latter. */
 			if (!read_again)
+			{
 				ch_fsize = pos;
+				if (ch_flags & CH_POPENED)
+				{
+					/*
+					 * ch_file is a pipe from a LESSOPEN child program.
+					 * Since we've read to EOF, the child should have exited.
+					 * Close the pipe now so the child does not remain a
+					 * zombie. This also reports any error status from the
+					 * child if -show-preproc-errors is enabled.
+					 */
+					close_pipe(get_altpipe(curr_ifile));
+					set_altpipe(curr_ifile, NULL);
+					ch_file = -1;
+				}
+			}
 			if (ignore_eoi || read_again)
 			{
 				/* Wait a while, then try again. */
@@ -388,7 +405,7 @@ public void ch_ungetchar(int c)
 	else
 	{
 		if (ch_have_ungotchar)
-			error("ch_ungetchar overrun", NULL_PARG);
+			error(LM(ch_ungetchar_overrun), NULL_PARG);
 		ch_ungotchar = (unsigned char) c;
 		ch_have_ungotchar = TRUE;
 	}
@@ -408,7 +425,7 @@ public void end_logfile(void)
 	if (!tried && ch_fsize == NULL_POSITION)
 	{
 		tried = TRUE;
-		ierror("Finishing logfile", NULL_PARG);
+		ierror(LM(Finishing_logfile), NULL_PARG);
 		while (ch_forw_get() != EOI)
 			if (ABORT_SIGS())
 				break;
@@ -454,8 +471,7 @@ public void sync_logfile(void)
 		}
 		if (!wrote && !warned)
 		{
-			error("Warning: log file is incomplete",
-				NULL_PARG);
+			error(LM(log_file_is_incomplete), NULL_PARG);
 			warned = TRUE;
 		}
 	}
@@ -614,7 +630,7 @@ public POSITION ch_length(void)
 	if (ignore_eoi)
 		return (NULL_POSITION);
 	if (ch_flags & CH_HELPFILE)
-		return (size_helpdata);
+		return (size_help_data);
 	if (ch_flags & CH_NODATA)
 		return (0);
 	return (ch_fsize);
@@ -745,7 +761,7 @@ public void ch_flush(void)
 		ch_flags &= ~CH_CANSEEK;
 	} else
 	{
-		ch_fsize = (ch_flags & CH_HELPFILE) ? size_helpdata : filesize(ch_file);
+		ch_fsize = (ch_flags & CH_HELPFILE) ? size_help_data : filesize(ch_file);
 	}
 
 	if (less_lseek(ch_file, (less_off_t)0, SEEK_SET) == BAD_LSEEK)
@@ -755,7 +771,7 @@ public void ch_flush(void)
 		 * there's a good chance we're at the beginning anyway.
 		 * {{ I think this is bogus reasoning. }}
 		 */
-		error("seek error to 0", NULL_PARG);
+		error(LM(seek_error_to_0), NULL_PARG);
 	}
 }
 
@@ -818,7 +834,7 @@ static void ch_delbufs(void)
 /*
  * Is it possible to seek on a file descriptor?
  */
-public int seekable(int f)
+public lbool seekable(int f)
 {
 #if MSDOS_COMPILER
 	extern int fd0;
@@ -828,7 +844,7 @@ public int seekable(int f)
 		 * In MS-DOS, pipes are seekable.  Check for
 		 * standard input, and pretend it is not seekable.
 		 */
-		return (0);
+		return (FALSE);
 	}
 #endif
 	return (less_lseek(f, (less_off_t)1, SEEK_SET) != BAD_LSEEK);
@@ -883,7 +899,7 @@ public void ch_init(int f, int flags, ssize_t nread)
 	/*
 	 * Figure out the size of the file, if we can.
 	 */
-	ch_fsize = (flags & CH_HELPFILE) ? size_helpdata : filesize(ch_file);
+	ch_fsize = (flags & CH_HELPFILE) ? size_help_data : filesize(ch_file);
 
 	if (ch_fsize == 0 && nread > 0)
 	{
@@ -924,7 +940,7 @@ public void ch_close(void)
 		 * But don't really close it if it was opened via popen(),
 		 * because pclose() wants to close it.
 		 */
-		if (!(ch_flags & (CH_POPENED|CH_HELPFILE)))
+		if (ch_file >= 0 && !(ch_flags & (CH_POPENED|CH_HELPFILE)))
 			close(ch_file);
 		ch_file = -1;
 	} else
diff --git a/contrib/less/charset.c b/contrib/less/charset.c
index f2020e4ef5c5..9447a8dcff24 100644
--- a/contrib/less/charset.c
+++ b/contrib/less/charset.c
@@ -191,12 +191,12 @@ static void ichardef_utf(constant char *s)
 			wchar_range_get(&s, &range);
 			if (range.last == 0)
 			{
-				error("invalid hex number(s) in LESSUTFCHARDEF", NULL_PARG);
+				error(LM(invalid_hex_number_in_LESSUTFCHARDEF), NULL_PARG);
 				quit(QUIT_ERROR);
 			}
 			if (*s++ != ':')
 			{
-				error("missing colon in LESSUTFCHARDEF", NULL_PARG);
+				error(LM(missing_colon_in_LESSUTFCHARDEF), NULL_PARG);
 				quit(QUIT_ERROR);
 			}
 			switch (*s++)
@@ -279,7 +279,7 @@ static void ichardef(constant char *s)
 
 		default:
 		invalid_chardef:
-			error("invalid chardef", NULL_PARG);
+			error(LM(invalid_chardef), NULL_PARG);
 			quit(QUIT_ERROR);
 			/*NOTREACHED*/
 		}
@@ -288,7 +288,7 @@ static void ichardef(constant char *s)
 		{
 			if (cp >= chardef + sizeof(chardef))
 			{
-				error("chardef longer than 256", NULL_PARG);
+				error(LM(chardef_longer_than_256), NULL_PARG);
 				quit(QUIT_ERROR);
 				/*NOTREACHED*/
 			}
@@ -305,7 +305,7 @@ static void ichardef(constant char *s)
  * Define a charset, given a charset name.
  * The valid charset names are listed in the "charsets" array.
  */
-static int icharset(constant char *name, int no_error)
+static int icharset(constant char *name, lbool no_error)
 {
 	struct charset *p;
 	struct cs_alias *a;
@@ -341,7 +341,7 @@ static int icharset(constant char *name, int no_error)
 	}
 
 	if (!no_error) {
-		error("invalid charset name", NULL_PARG);
+		error(LM(invalid_charset_name), NULL_PARG);
 		quit(QUIT_ERROR);
 	}
 	return (0);
@@ -353,9 +353,9 @@ static int icharset(constant char *name, int no_error)
  */
 static void ilocale(void)
 {
-	int c;
+	size_t c;
 
-	for (c = 0;  c < (int) sizeof(chardef);  c++)
+	for (c = 0;  c < sizeof(chardef);  c++)
 	{
 		if (isprint(c))
 			chardef[c] = 0;
@@ -411,7 +411,7 @@ static void set_charset(void)
 	 * See if environment variable LESSCHARSET is defined.
 	 */
 	s = lgetenv("LESSCHARSET");
-	if (icharset(s, 0))
+	if (icharset(s, FALSE))
 		return;
 
 	/*
@@ -438,7 +438,7 @@ static void set_charset(void)
 #endif
 	{
 		s = nl_langinfo(CODESET);
-		if (icharset(s, 1))
+		if (icharset(s, TRUE))
 			return;
 	}
 #endif
@@ -453,7 +453,7 @@ static void set_charset(void)
 	{
 		if (   strstr(s, "UTF-8") != NULL || strstr(s, "utf-8") != NULL
 		    || strstr(s, "UTF8")  != NULL || strstr(s, "utf8")  != NULL)
-			if (icharset("utf-8", 1))
+			if (icharset("utf-8", TRUE))
 				return;
 	}
 
@@ -466,12 +466,12 @@ static void set_charset(void)
 #else
 #if MSDOS_COMPILER
 #if MSDOS_COMPILER==WIN32C
-	(void) icharset("utf-8", 1);
+	(void) icharset("utf-8", TRUE);
 #else
-	(void) icharset("dos", 1);
+	(void) icharset("dos", TRUE);
 #endif
 #else
-	(void) icharset("utf-8", 1);
+	(void) icharset("utf-8", TRUE);
 #endif
 #endif
 }
@@ -527,7 +527,7 @@ public constant char * prchar(LWCHAR c)
 	/* {{ Fixed buffer size means LESSBINFMT etc can be truncated. }} */
 	static char buf[MAX_PRCHAR_LEN+1];
 
-	c &= 0377; /*{{type-issue}}*/
+	c = (unsigned char) c; /* {{ parameter is interpreted as a byte }} */
 	if ((c < 128 || !utf_mode) && !control_char(c))
 		SNPRINTF1(buf, sizeof(buf), "%c", (int) c);
 	else if (c == ESC)
@@ -617,27 +617,28 @@ public lbool is_utf8_well_formed(constant char *ssa, int slen)
 	unsigned char s0 = ss[0];
 
 	if (IS_UTF8_LEAD5(s0) || IS_UTF8_LEAD6(s0) || IS_UTF8_INVALID(s0))
-		return (FALSE);
+		return (FALSE); /* invalid lead byte */
 
 	len = utf_len(s0);
 	if (len > slen)
-		return (FALSE);
+		return (FALSE); /* UTF-8 sequence doesn't fit in buffer */
 	if (len == 1)
 		return (TRUE);
 	if (len == 2)
 	{
 		if (s0 < 0xC2)
-			return (FALSE);
+			return (FALSE); /* overlong (fits in 1 byte) */
 	} else
 	{
+		/* mask = byte with len high-order bits set to 1, others 0 */
 		unsigned char mask = (unsigned char) (~((1 << (8-len)) - 1));
 		if (s0 == mask && (ss[1] & mask) == 0x80)
-			return (FALSE);
+			return (FALSE); /* overlong (fits in fewer bytes) */
 	}
 
 	for (i = 1;  i < len;  i++)
 		if (!IS_UTF8_TRAIL(ss[i]))
-			return (FALSE);
+			return (FALSE); /* not a continuation byte */
 	return (TRUE);
 }
 
@@ -651,7 +652,6 @@ public void utf_skip_to_lead(constant char **pp, constant char *limit)
 	} while (*pp < limit && !IS_UTF8_LEAD((*pp)[0] & 0377) && !IS_ASCII_OCTET((*pp)[0]));
 }
 
-
 /*
  * Get the value of a UTF-8 character.
  */
@@ -755,7 +755,8 @@ public void put_wchar(mutable char **pp, LWCHAR ch)
 }
 
 /*
- * Step forward or backward one character in a string.
+ * Step forward (dir > 0) or backward (dir < 0) one character in a string.
+ * If dir > 0, limit is end of string; if dir < 0, limit is start of string.
  */
 public LWCHAR step_charc(constant char **pp, signed int dir, constant char *limit)
 {
@@ -779,6 +780,7 @@ public LWCHAR step_charc(constant char **pp, signed int dir, constant char *limi
 			len = utf_len(*p);
 			if (p + len > limit || !is_utf8_well_formed(p, len))
 			{
+				/* Invalid UTF-8 sequence; just take the next byte as a character. */
 				ch = (LWCHAR) (unsigned char) *p++;
 			} else
 			{
@@ -797,6 +799,7 @@ public LWCHAR step_charc(constant char **pp, signed int dir, constant char *limi
 			len = utf_len(*--p);
 			if (p + len != *pp || !is_utf8_well_formed(p, len))
 			{
+				/* Invalid UTF-8 sequence; just take the next byte as a character. */
 				p = *pp - 1;
 				ch = (LWCHAR) (unsigned char) *p;
 			} else
diff --git a/contrib/less/cmd.h b/contrib/less/cmd.h
index fb66a6adb99d..97d51ab9b7f9 100644
--- a/contrib/less/cmd.h
+++ b/contrib/less/cmd.h
@@ -89,6 +89,7 @@
 #define A_F_FOREVER_BELL       77
 #define A_L_MOUSE              78
 #define A_R_MOUSE              79
+#define A_RSHIFT_LIMIT         80
 
 /* These values must not conflict with any A_* or EC_* value. */
 #define A_INVALID              100
@@ -180,6 +181,14 @@
 #define SK_CTL_END             37
 #define SK_SHIFT_LEFT_ARROW    38
 #define SK_SHIFT_RIGHT_ARROW   39
-#define SK_PAD_ENTER           41
-
 #define SK_CONTROL_K           40
+#define SK_PAD_ENTER           41
+#define SK_SHIFT_UP_ARROW      42
+#define SK_SHIFT_DOWN_ARROW    43
+#define SK_CTL_UP_ARROW        44
+#define SK_CTL_DOWN_ARROW      45
+#define SK_SHIFT_DELETE        46
+#define SK_SHIFT_PAGE_UP       47
+#define SK_SHIFT_PAGE_DOWN     48
+#define SK_CTL_PAGE_UP         49
+#define SK_CTL_PAGE_DOWN       50
diff --git a/contrib/less/cmdbuf.c b/contrib/less/cmdbuf.c
index 9f13473eba03..5c6600f9c49c 100644
--- a/contrib/less/cmdbuf.c
+++ b/contrib/less/cmdbuf.c
@@ -358,7 +358,7 @@ static void cmd_lshift(void)
 		s = ns;
 	}
 
-	cmd_offset = (int) (s - cmdbuf);
+	cmd_offset = (int) ptr_diff(s, cmdbuf);
 	save_cp = cp;
 	cmd_home();
 	cmd_repaint(save_cp);
@@ -387,7 +387,7 @@ static void cmd_rshift(void)
 		cols += width;
 	}
 
-	cmd_offset = (int) (s - cmdbuf);
+	cmd_offset = (int) ptr_diff(s, cmdbuf);
 	save_cp = cp;
 	cmd_home();
 	cmd_repaint(save_cp);
@@ -479,7 +479,7 @@ static int cmd_erase(void)
 	 */
 	s = cp;
 	cmd_left();
-	clen = (int) (s - cp);
+	clen = (int) ptr_diff(s, cp);
 
 	/*
 	 * Remove the char from the buffer (shift the buffer left).
@@ -993,10 +993,12 @@ static int cmd_istr(constant char *str)
  */
 static void set_tk_original(constant char *word)
 {
+	size_t len = ptr_diff(cp, word);
 	if (tk_original != NULL)
 		free(tk_original);
-	tk_original = (char *) ecalloc(ptr_diff(cp,word)+1, sizeof(char));
-	strncpy(tk_original, word, ptr_diff(cp,word));
+	tk_original = (char *) ecalloc(len+1, sizeof(char));
+	strncpy(tk_original, word, len);
+	tk_original[len] = '\0';
 }
 
 #if TAB_COMPLETE_FILENAME
@@ -1418,15 +1420,18 @@ public LINENUM cmd_int(mutable long *frac)
 	{
 		if (ckd_mul(&n, n, 10) || ckd_add(&n, n, *p - '0'))
 		{
-			error("Integer is too big", NULL_PARG);
+			error(LM(Integer_is_too_big), NULL_PARG);
 			return (0);
 		}
 	}
-	*frac = 0;
-	if (*p++ == '.')
+	if (frac != NULL)
 	{
-		/* {{ Just ignore error in fractional part. }} */
-		(void) getfraction(&p, frac);
+		*frac = 0;
+		if (*p++ == '.')
+		{
+			/* {{ Just ignore error in fractional part. }} */
+			(void) getfraction(&p, frac);
+		}
 	}
 	return (n);
 }
@@ -1481,7 +1486,7 @@ static char * histfile_find(lbool must_exist)
 	name = dirfile(lgetenv("XDG_STATE_HOME"), &LESSHISTFILE[1], must_exist);
 	if (name == NULL)
 	{
-		char *dir = dirfile(home, ".local/state", 1);
+		char *dir = dirfile(home, ".local/state", TRUE);
 		if (dir != NULL)
 		{
 			name = dirfile(dir, &LESSHISTFILE[1], must_exist);
@@ -1606,7 +1611,7 @@ static void addhist_init(void *uparam, struct mlist *ml, constant char *string)
 {
 	(void) uparam;
 	if (ml != NULL)
-		cmd_addhist(ml, string, 0);
+		cmd_addhist(ml, string, FALSE);
 	else if (string != NULL)
 		restore_mark(string);
 }
@@ -1721,8 +1726,8 @@ static void make_file_private(FILE *f)
 #if HAVE_FCHMOD
 	lbool do_chmod = TRUE;
 #if HAVE_STAT
-	struct stat statbuf;
-	int r = fstat(fileno(f), &statbuf);
+	less_stat_t statbuf;
+	int r = less_fstat(fileno(f), &statbuf);
 	if (r < 0 || !S_ISREG(statbuf.st_mode))
 		/* Don't chmod if not a regular file. */
 		do_chmod = FALSE;
diff --git a/contrib/less/command.c b/contrib/less/command.c
index 09eb1614c76d..cba6117c4f6f 100644
--- a/contrib/less/command.c
+++ b/contrib/less/command.c
@@ -38,7 +38,7 @@ extern int hshift;
 extern int bs_mode;
 extern int proc_backspace;
 extern int show_attn;
-extern int less_is_more;
+extern lbool less_is_more;
 extern int chopline;
 extern POSITION highest_hilite;
 extern char *every_first_cmd;
@@ -103,7 +103,8 @@ static int search_incr_hshift;
 static time_type ignoring_input_time;
 #endif
 #if PIPEC
-static char pipec;
+static POSITION pipe_pos1;
+static POSITION pipe_pos2;
 #endif
 
 /* Stack of ungotten chars (via ungetcc) */
@@ -181,28 +182,34 @@ static void mca_search1(void)
 		set_mca(A_B_SEARCH);
 
 	if (search_type & SRCH_NO_MATCH)
-		cmd_putstr("Non-match ");
+		cmd_putstr(LM(Non_match));
 	if (search_type & SRCH_FIRST_FILE)
-		cmd_putstr("First-file ");
+		cmd_putstr(LM(First_file));
 	if (search_type & SRCH_PAST_EOF)
-		cmd_putstr("EOF-ignore ");
+		cmd_putstr(LM(EOF_ignore));
 	if (search_type & SRCH_NO_MOVE)
-		cmd_putstr("Keep-pos ");
+		cmd_putstr(LM(Keep_pos));
 	if (search_type & SRCH_NO_REGEX)
-		cmd_putstr("Regex-off ");
+		cmd_putstr(LM(Regex_off));
 	if (search_type & SRCH_WRAP)
-		cmd_putstr("Wrap ");
+		cmd_putstr(LM(Wrap));
 	for (i = 1; i <= NUM_SEARCH_COLORS; i++)
 	{
 		if (search_type & SRCH_SUBSEARCH(i))
 		{
-			char buf[INT_STRLEN_BOUND(int)+8];
-			SNPRINTF1(buf, sizeof(buf), "Sub-%d ", i);
+			static char *buf = NULL;
+			static size_t buflen;
+			if (buf == NULL)
+			{
+				buflen = INT_STRLEN_BOUND(int)+strlen(LM(Sub_X)+1);
+				buf = ecalloc(buflen, sizeof(char));
+			}
+			SNPRINTF1(buf, buflen, LM(Sub_X), i);
 			cmd_putstr(buf);
 		}
 	}
 	if (literal_char)
-		cmd_putstr("Lit ");
+		cmd_putstr(LM(Lit));
 
 #if HILITE_SEARCH
 	if (search_type & SRCH_FILTER)
@@ -300,10 +307,10 @@ static void exec_mca(void)
 		curropt = NULL;
 		break;
 	case A_F_BRACKET:
-		match_brac(cbuf[0], cbuf[1], 1, (int) number);
+		match_brac(cbuf[0], cbuf[1], TRUE, (int) number);
 		break;
 	case A_B_BRACKET:
-		match_brac(cbuf[1], cbuf[0], 0, (int) number);
+		match_brac(cbuf[1], cbuf[0], FALSE, (int) number);
 		break;
 #if EXAMINE
 	case A_EXAMINE: {
@@ -329,7 +336,7 @@ static void exec_mca(void)
 		constant char *done_msg = (*cbuf == CONTROL('P')) ? NULL : "!done";
 		if (done_msg == NULL)
 			++cbuf;
-		if (*cbuf != '!')
+		if (strcmp(cbuf, "!") != 0)
 		{
 			if (shellcmd != NULL)
 				free(shellcmd);
@@ -337,9 +344,7 @@ static void exec_mca(void)
 		}
 		if (!secure_allow(SF_SHELL))
 			break;
-		if (shellcmd == NULL)
-			shellcmd = "";
-		lsystem(shellcmd, done_msg);
+		lsystem(shellcmd == NULL ? "" : shellcmd, done_msg);
 		break; }
 	case A_PSHELL: {
 		constant char *done_msg = (*cbuf == CONTROL('P')) ? NULL : "#done";
@@ -357,7 +362,7 @@ static void exec_mca(void)
 			++cbuf;
 		if (!secure_allow(SF_PIPE))
 			break;
-		(void) pipe_mark(pipec, cbuf);
+		(void) pipe_pos(cbuf, pipe_pos1, pipe_pos2);
 		if (done_msg != NULL)
 			error(done_msg, NULL_PARG);
 		break; }
@@ -513,7 +518,7 @@ static int mca_opt_char(char c)
 			parg.p_string = get_cmdbuf();
 			if (parg.p_string == NULL)
 				return (MCA_MORE);
-			error("There is no --%s option", &parg);
+			error(LM(There_is_no__X_option), &parg);
 			return (MCA_DONE);
 		}
 		optgetname = FALSE;
@@ -529,7 +534,7 @@ static int mca_opt_char(char c)
 		if (curropt == NULL)
 		{
 			parg.p_string = propt(c);
-			error("There is no %s option", &parg);
+			error(LM(There_is_no_X_option), &parg);
 			return (MCA_DONE);
 		}
 		opt_lower = ASCII_IS_LOWER(c);
@@ -608,8 +613,14 @@ static int mca_search_char(char c)
 			flag = SRCH_NO_MOVE;
 		break;
 	case CONTROL('S'): { /* SUBSEARCH */
-		char buf[INT_STRLEN_BOUND(int)+24];
-		SNPRINTF1(buf, sizeof(buf), "Sub-pattern (1-%d):", NUM_SEARCH_COLORS);
+		static char *buf = NULL;
+		static size_t buflen;
+		if (buf == NULL)
+		{
+			buflen = INT_STRLEN_BOUND(int)+strlen(LM(Sub_pattern_1_X))+1;
+			buf = ecalloc(buflen, sizeof(char));
+		}
+		SNPRINTF1(buf, buflen, LM(Sub_pattern_1_X), NUM_SEARCH_COLORS);
 		clear_bot();
 		cmd_putstr(buf);
 		flush();
@@ -890,15 +901,15 @@ static void prompt_message(void)
 {
 	if (read_error)
 	{
-		error("read error", NULL_PARG);
+		error(LM(read_error), NULL_PARG);
 		read_error = FALSE;
 	}
 	if (search_wrapped)
 	{
 		if (search_type & SRCH_BACK)
-			error("Search hit top; continuing at bottom", NULL_PARG);
+			error(LM(Search_hit_top), NULL_PARG);
 		else
-			error("Search hit bottom; continuing at top", NULL_PARG);
+			error(LM(Search_hit_bottom), NULL_PARG);
 		search_wrapped = FALSE;
 	}
 #if OSC8_LINK
@@ -906,7 +917,7 @@ static void prompt_message(void)
 	{
 		PARG parg;
 		parg.p_string = osc8_uri;
-		error("Link: %s", &parg);
+		error(LM(Link_X), &parg);
 		free(osc8_uri);
 		osc8_uri = NULL;
 	}
@@ -1328,9 +1339,9 @@ static void multi_search(constant char *pattern, int n, int silent)
 		PARG parg;
 		parg.p_string = prev_pattern_text();
 		if (parg.p_string == NULL) /* {{ can this happen? }} */
-			error("Pattern not found", NULL_PARG);
+			error(LM(Pattern_not_found), NULL_PARG);
 		else
-			error("Pattern not found: %s", &parg);
+			error(LM(Pattern_not_found_X), &parg);
 	}
 
 	if (changed_file)
@@ -1425,6 +1436,72 @@ public lbool is_ignoring_input(int action)
 	return (action != A_PREFIX);
 }
 
+#if PIPEC
+/*
+ * Read a mark char or line number from the user for use by the pipe command.
+ * If mark_char != NULL, accept *mark_char as well as the normal mark chars.
+ * Also return the entered mark char in *mark_char.
+ */
+static POSITION get_pipe_pos(constant char *mark_msg, constant char *line_msg, mutable char *mark_char)
+{
+	lbool get_line = FALSE;
+	char spec_char = '\0';
+	char c;
+
+	start_mca(A_PIPE, mark_msg, NULL, 0);
+	if (mark_char != NULL)
+	{
+		spec_char = *mark_char;
+		*mark_char = '\0';
+	}
+	for (;;)
+	{
+		c = getcc();
+		if (spec_char != '\0' && c == spec_char)
+		{
+			*mark_char = c;
+			return NULL_POSITION;
+		}
+		if (c == CONTROL('N')) /* toggle between reading mark and line number */
+		{
+			get_line = !get_line;
+			cmd_reset();
+			start_mca(A_PIPE, get_line ? line_msg : mark_msg, NULL, 0);
+		} else if (get_line) /* reading line number */
+		{
+			if ((c >= '0' && c <= '9') || is_erase_char(c))
+			{
+				if (cmd_char(c) == CC_QUIT)
+					return NULL_POSITION;
+			} else if (is_newline_char(c))
+			{
+				POSITION pos;
+				LINENUM lnum = cmd_int(NULL);
+				cmd_reset();
+				if (lnum == 0 || (pos = find_pos(lnum)) == NULL_POSITION)
+				{
+					error(LM(Invalid_line_number), NULL_PARG);
+					return NULL_POSITION;
+				}
+				return pos;
+			} else
+				lbell();
+		} else /* reading mark char */
+		{
+			if (is_erase_char(c))
+				return NULL_POSITION;
+			if (is_newline_char(c))
+				c = '.';
+			if (badmark(c))
+				return NULL_POSITION;
+			if (mark_char != NULL)
*** 8772 LINES SKIPPED ***