Hey.

Steffen Nurpmeso wrote in
 <20210523212903.58vrh%stef...@sdaoden.eu>:
 |Steffen Nurpmeso wrote in
 | <20210523004836.gta8l%stef...@sdaoden.eu>:
 | ...
 |||> Good idea.  I've further changed the Subject: to reflect the flow \
 |||> of the
 |||> discussion.
 || ...
 |||> I also wonder if the pager wars are basically over and less(1) \
 |||> won them.
 |||
 |||That's certainly what I thought...
 || ..
 ||
 ||Ever since less(1) started supporting OSC 8 "Hyperlinks in
 ||Terminal Emulators" as of version 566 i wanted to rewrite my
 ||mdocmx(7) extension to be based upon the OSC 8 sequences that now
 ||become more and more common.
 ...
 |  \X'tty osc8 [id ID] [uri URI]'

Much much better approach indeed.

Mind you, since mdocmx is sitting around since 2014/5 for my
personal fun, but OSC 8 is a nice standard that is available in
a growing number of software...
Please let me attach patches for less (v586 aka git repo, also at
[1]) which implements searching for OSC 8 sequences, grotty (git
repo from a few days ago, also at [2]) that implements the above
\X'', and a readily prepared manual page (from mdocmx, for fun).

You can look at the manual page in a less>=566 via -R, and all
will appear as desired (for mdocmx).  If you would patch less, you
could type ^A (control-A) and enter the anchor to jump to.  This
works even for external manual pages.  (For less, when going over
git: autoreconf;make -f Makefile.aut;make.)

I think i will ask Mr. Nudelman again whether he is interested in
the patch of implementing OSC 8 id= searches.
You know, i really like being able to stay in less :)

Anyhow, with the above OSC 8 things like docbook and other XY-
to-manual page converters could easily pimp the manual pages they
produce.

  [1] https://ftp.sdaoden.eu/less-osc8-search.patch
  [2] https://ftp.sdaoden.eu/grotty-osc8.patch

Ciao,

P.S.: I have not tested the grotty patch with any other groff but
the 1.22.3 i have here.  Just made it fit nicely in 1.22.4 aka git
head.

--steffen
|
|Der Kragenbaer,                The moon bear,
|der holt sich munter           he cheerfully and one by one
|einen nach dem anderen runter  wa.ks himself off
|(By Robert Gernhardt)
From d374e9180166c7a655c7ee0a542df6c3643b2c74 Mon Sep 17 00:00:00 2001
Message-Id: <d374e9180166c7a655c7ee0a542df6c3643b2c74.1621728853.git.stef...@sdaoden.eu>
From: Steffen Nurpmeso <stef...@sdaoden.eu>
Date: Sun, 23 May 2021 01:27:41 +0200
Subject: [PATCH] Support OSC 8 anchors

---
 cmd.h           |   1 +
 command.c       | 174 ++++++++++++++++++++++++++++++++++++++++++++++++
 configure.ac    |   7 ++
 decode.c        |   3 +-
 defines.ds      |   7 ++
 defines.o2      |   7 ++
 defines.o9      |   7 ++
 defines.wn      |   7 ++
 less.h          |   3 +
 less.hlp        |   1 +
 less.nro.VER    |   6 ++
 lesskey_parse.c |   3 +
 search.c        |  21 ++++++
 13 files changed, 246 insertions(+), 1 deletion(-)

diff --git a/cmd.h b/cmd.h
index 7141817bae..b4942d44ed 100644
--- a/cmd.h
+++ b/cmd.h
@@ -69,6 +69,7 @@
 /* Note "X116" refers to extended (1006) X11 mouse reporting. */
 #define A_X116MOUSE_IN         68
 #define A_CLR_SEARCH           70
+#define A_OSC8_SEARCH          84
 
 /* These values must not conflict with any A_* or EC_* value. */
 #define A_INVALID              100
diff --git a/command.c b/command.c
index d4e271324d..4948cc9a7d 100644
--- a/command.c
+++ b/command.c
@@ -57,6 +57,14 @@ extern int incr_search;
 extern int utf_mode;
 #endif
 
+#if HILITE_SEARCH
+extern int hilite_search;
+#endif
+
+#if OSC8_SEARCH
+public char *osc8_search_line /* = NULL */;
+#endif
+
 #if SHELL_ESCAPE
 static char *shellcmd = NULL;   /* For holding last shell command for "!!" */
 #endif
@@ -84,6 +92,11 @@ static struct ungot* ungot = NULL;
 
 static void multi_search LESSPARAMS((char *pattern, int n, int silent));
 
+#if OSC8_SEARCH
+static void osc8_search();
+static void a_osc8_found();
+#endif
+
 /*
  * Move the cursor to start of prompt line before executing a command.
  * This looks nicer if the command takes a long time before
@@ -302,6 +315,11 @@ exec_mca(VOID_PARAM)
 		(void) pipe_mark(pipec, cbuf);
 		error("|done", NULL_PARG);
 		break;
+#endif
+#if OSC8_SEARCH
+	case A_OSC8_SEARCH:
+		osc8_search(cbuf);
+		break;
 #endif
 	}
 }
@@ -1122,6 +1140,151 @@ multi_search(pattern, n, silent)
 	}
 }
 
+/*
+ * OSC 8 search.  Search for an according OSC 8 id= parameter, and scroll the
+ * respective position into view.
+ * If a matching anchor is found it may also be a reference to an external
+ * manual page, if the OSC 8 URI starts with man://: in this case prepare the
+ * necessary man(1) command and give the user the option to confirm the action.
+ * Anyway: set lastmark() so that it is possible to jump back; let the empty
+ * anchor be an alias for the desire to do so
+ */
+#if OSC8_SEARCH
+	static void
+osc8_search(cbuf)
+	char const *cbuf;
+{
+	int const srch_flags = SRCH_NO_REGEX | SRCH_OSC8;
+
+	PARG parg;
+	char *q, *qc;
+	long l;
+# if HILITE_SEARCH
+	int save_hilite_search;
+# endif
+
+	parg.p_string = (char*)cbuf;
+	l = strtol(cbuf, &q, 10);
+
+	/* For convenience */
+	if (*cbuf == '\0')
+		gomark('\'');
+	else if (*q != '\0' || l <= 0)
+		error("Invalid reference anchor: %s", &parg);
+	/* Local references are scrolled into view */
+	else {
+		static char const a_id[] = {ESC,']','8',';','i','d','=','\0'};
+
+		q = ecalloc(sizeof(a_id) + strlen(cbuf) + 1, sizeof *q);
+
+		memcpy(&q[0], a_id, sizeof(a_id) -1);
+		for (qc = &q[sizeof(a_id) -1]; *cbuf != '\0'; ++qc, ++cbuf)
+			*qc = *cbuf;
+		*qc++ = ';';
+		*qc = '\0';
+
+# undef a_OSC8_ID
+
+		lastmark();
+# if HILITE_SEARCH
+		save_hilite_search = hilite_search;
+		repaint_hilite(0);
+		hilite_search = 0;
+# endif
+		if (search(SRCH_FORW | srch_flags, q, 1) == 0 ||
+		    search(SRCH_BACK | srch_flags, q, 1) == 0)
+			a_osc8_found(q);
+		else
+			error("No such anchor: %s", &parg);
+# if HILITE_SEARCH
+		hilite_search = save_hilite_search;
+		repaint_hilite(1);
+# endif
+
+		free(q);
+	}
+}
+
+	static void
+a_osc8_found(anchor)
+	char const *anchor;
+{
+# if HAVE_STRSTR
+	char *cp, *sect, *sect_top, *man, *man_top, *buf;
+
+	/* Find the anchor on the line again; play safe */
+	if ((cp = strstr(osc8_search_line, anchor)) == NULL)
+		goto jleave;
+	cp += strlen(anchor);
+
+	/* We only act upon man://.  (XXX Anything -> LESS_BROWSER?? */
+	if(strncmp(cp, "man://", sizeof("man://") -1))
+		goto jleave;
+	cp += sizeof("man://") -1;
+
+	sect = man_top = NULL;
+	for(man = sect_top = cp; *sect_top != ESC; ++sect_top){
+		if(*sect_top == '\0'){
+			sect = NULL;
+			break;
+		}else if(*sect_top == '.'){
+			man_top = &sect_top[-1];
+			sect = &sect_top[1];
+		}
+	}
+	if(sect == NULL || sect >= sect_top || man >= man_top){
+		error("Bogus OSC 8 man://ual reference", NULL_PARG);
+		goto jleave;
+	}
+
+	/* An external reference we can! */
+	if (secure) {
+		error("External references not available in secure mode",
+			NULL_PARG);
+		goto jleave;
+	}
+
+#  define __Y "!man "
+#  define __X "Read external manual: "
+	buf = ecalloc(1, sizeof(__X __Y) -1 +
+		(int)(sect_top - sect) + 1 + (int)(man_top - man) + 1 +1);
+
+	memcpy(buf, __X __Y, sizeof(__X __Y) -1);
+	cp = &buf[sizeof(__X __Y) -1];
+	for (; sect < sect_top; ++sect)
+		*cp++ = *sect;
+	*cp++ = ' ';
+	for (; man <= man_top; ++man)
+		*cp++ = *man;
+	*cp++ = '\0';
+
+	cmd_putstr(buf);
+	switch (getcc()) {
+	case '\n':
+	case '\r':
+#  if HAVE_SYSTEM
+		lsystem(buf + sizeof(__X)-1 + 1, "!back from external manual");
+#  else
+		error("Command not available", NULL_PARG);
+#  endif
+		/* FALLTHRU */
+	default:
+		break;
+	}
+
+	free(buf);
+#  undef __X
+#  undef __Y
+jleave:
+
+# else /* HAVE_STRSTR */
+	(void)anchor;
+# endif
+
+	free(osc8_search_line);
+}
+#endif /* OSC8_SEARCH */
+
 /*
  * Forward forever, or until a highlighted line appears.
  */
@@ -2039,6 +2202,17 @@ commands(VOID_PARAM)
 		case A_NOACTION:
 			break;
 
+		case A_OSC8_SEARCH:
+#if OSC8_SEARCH
+			start_mca(A_OSC8_SEARCH, "[OSC 8 anchor]:",
+				(void*)NULL, CF_QUIT_ON_ERASE);
+			c = getcc();
+			goto again;
+#else
+			error("Command not available", NULL_PARG);
+			break;
+#endif
+
 		default:
 			bell();
 			break;
diff --git a/configure.ac b/configure.ac
index 008a3d619d..0415e7f2bc 100644
--- a/configure.ac
+++ b/configure.ac
@@ -602,6 +602,13 @@ AH_TOP([
  */
 #define	LOGFILE		(!SECURE)
 
+/*
+ * OSC8_SEARCH is 1 if you wish to support searching for OSC8 local references
+ * (as can be created via OSC 8 id= parameters).
+ * As well as external OSC 8 man://ual page references.
+ */
+#define	OSC8_SEARCH	(!SECURE)
+
 /*
  * GNU_OPTIONS is 1 if you wish to support the GNU-style command
  * line options --help and --version.
diff --git a/decode.c b/decode.c
index 9fa891969d..baa185c445 100644
--- a/decode.c
+++ b/decode.c
@@ -169,7 +169,8 @@ static unsigned char cmdtable[] =
 	'Q',0,                          A_QUIT,
 	':','q',0,                      A_QUIT,
 	':','Q',0,                      A_QUIT,
-	'Z','Z',0,                      A_QUIT
+	'Z','Z',0,                      A_QUIT,
+	CONTROL('A'),0,			A_OSC8_SEARCH
 };
 
 static unsigned char edittable[] =
diff --git a/defines.ds b/defines.ds
index 9d30129029..46e5f8a9c4 100644
--- a/defines.ds
+++ b/defines.ds
@@ -90,6 +90,13 @@
  */
 #define	LOGFILE		(!SECURE)
 
+/*
+ * OSC8_SEARCH is 1 if you wish to support searching for OSC8 local references
+ * (as can be created via OSC 8 id= parameters).
+ * As well as external OSC 8 man://ual page references.
+ */
+#define	OSC8_SEARCH	(!SECURE)
+
 /*
  * GNU_OPTIONS is 1 if you wish to support the GNU-style command
  * line options --help and --version.
diff --git a/defines.o2 b/defines.o2
index c3a0ed4c6a..908777825d 100644
--- a/defines.o2
+++ b/defines.o2
@@ -83,6 +83,13 @@
  */
 #define	LOGFILE		(!SECURE)
 
+/*
+ * OSC8_SEARCH is 1 if you wish to support searching for OSC8 local references
+ * (as can be created via OSC 8 id= parameters).
+ * As well as external OSC 8 man://ual page references.
+ */
+#define	OSC8_SEARCH	(!SECURE)
+
 /*
  * GNU_OPTIONS is 1 if you wish to support the GNU-style command
  * line options --help and --version.
diff --git a/defines.o9 b/defines.o9
index 55fdb85af2..b6415449d5 100644
--- a/defines.o9
+++ b/defines.o9
@@ -82,6 +82,13 @@
  */
 #define	LOGFILE		(!SECURE)
 
+/*
+ * OSC8_SEARCH is 1 if you wish to support searching for OSC8 local references
+ * (as can be created via OSC 8 id= parameters).
+ * As well as external OSC 8 man://ual page references.
+ */
+#define	OSC8_SEARCH	(!SECURE)
+
 /*
  * GNU_OPTIONS is 1 if you wish to support the GNU-style command
  * line options --help and --version.
diff --git a/defines.wn b/defines.wn
index 9127ca8a9c..69f412fb87 100644
--- a/defines.wn
+++ b/defines.wn
@@ -83,6 +83,13 @@
  */
 #define	LOGFILE		(!SECURE)
 
+/*
+ * OSC8_SEARCH is 1 if you wish to support searching for OSC8 local references
+ * (as can be created via OSC 8 id= parameters).
+ * As well as external OSC 8 man://ual page references.
+ */
+#define	OSC8_SEARCH	(!SECURE)
+
 /*
  * GNU_OPTIONS is 1 if you wish to support the GNU-style command
  * line options --help and --version.
diff --git a/less.h b/less.h
index a00a64db49..e8970ac93b 100644
--- a/less.h
+++ b/less.h
@@ -360,6 +360,9 @@ struct wchar_range_table
 #define SRCH_FILTER     (1 << 13) /* Search is for '&' (filter) command */
 #define SRCH_AFTER_TARGET (1 << 14) /* Start search after the target line */
 #define SRCH_WRAP       (1 << 15) /* Wrap-around search (continue at BOF/EOF) */
+#if OSC8_SEARCH
+# define SRCH_OSC8	(1 << 16) /* OSC 8 id= search */
+#endif
 
 #define SRCH_REVERSE(t) (((t) & SRCH_FORW) ? \
                                 (((t) & ~SRCH_FORW) | SRCH_BACK) : \
diff --git a/less.hlp b/less.hlp
index 333a0b576c..41c65f7e8f 100644
--- a/less.hlp
+++ b/less.hlp
@@ -73,6 +73,7 @@
 
   m_<_l_e_t_t_e_r_>            Mark the current top line with <letter>.
   M_<_l_e_t_t_e_r_>            Mark the current bottom line with <letter>.
+  ^A_<_t_e_x_t_>             Go to OSC 8 anchor <text>.
   '_<_l_e_t_t_e_r_>            Go to a previously marked position.
   ''                   Go to the previous position.
   ^X^X                 Same as '.
diff --git a/less.nro.VER b/less.nro.VER
index 2f7d956262..bbb6f8cd60 100644
--- a/less.nro.VER
+++ b/less.nro.VER
@@ -105,6 +105,12 @@ and LEFTARROW commands.
 Scroll horizontally right to show the end of the longest displayed line.
 .IP "ESC-{ or ^LEFTARROW"
 Scroll horizontally left back to the first column.
+.IP "^A"
+Enter the
+.I OSC 8
+anchor to go to; the position before scrolling to an anchor will be
+recognized as the last position mark and can thus be scrolled to either
+by using ^A again with an empty anchor or by using the "''" command sequence.
 .IP "r or ^R or ^L"
 Repaint the screen.
 .IP R
diff --git a/lesskey_parse.c b/lesskey_parse.c
index 3190cd113e..3fc4fc3aa3 100644
--- a/lesskey_parse.c
+++ b/lesskey_parse.c
@@ -55,6 +55,9 @@ static struct lesskey_cmdname cmdnames[] =
 	{ "index-file",           A_INDEX_FILE },
 	{ "invalid",              A_UINVALID },
 	{ "left-scroll",          A_LSHIFT },
+#if OSC8_SEARCH
+	{ "osc8-search",          A_OSC8_SEARCH },
+#endif
 	{ "next-file",            A_NEXT_FILE },
 	{ "next-tag",             A_NEXT_TAG },
 	{ "noaction",             A_NOACTION },
diff --git a/search.c b/search.c
index 6277fe3c4e..01daec7d7c 100644
--- a/search.c
+++ b/search.c
@@ -1375,6 +1375,27 @@ search_range(pos, endpos, search_type, matches, maxlines, plinepos, pendpos, pla
 			continue;
 #endif
 
+		/* Special case OSC 8 searches: we want to match plain line
+		 * content, so avoid allocations and text conversions */
+#if OSC8_SEARCH
+		if (search_type & SRCH_OSC8) {
+			line_match = match_pattern(info_compiled(&search_info),
+				search_info.text, line, line_len, &sp, &ep, 0,
+				search_type);
+			if (line_match) {
+				extern char *osc8_search_line;
+
+				osc8_search_line = (char*)ecalloc(1, line_len +1);
+				memcpy(osc8_search_line, line, line_len);
+				osc8_search_line[line_len] = '\0';
+				if (plinepos != NULL)
+					*plinepos = linepos;
+				return (0);
+			}
+			continue;
+		}
+#endif
+
 		/*
 		 * If it's a caseless search, convert the line to lowercase.
 		 * If we're doing backspace processing, delete backspaces.
-- 
2.31.1

From ee1627a04640576719d45c6be0807aeb9dbdacec Mon Sep 17 00:00:00 2001
Message-Id: <ee1627a04640576719d45c6be0807aeb9dbdacec.1621901733.git.stef...@sdaoden.eu>
From: Steffen Nurpmeso <stef...@sdaoden.eu>
Date: Tue, 25 May 2021 02:07:15 +0200
Subject: [PATCH] src/devices/grotty/tty.cpp: add support for \X'tty: osc8 [id
 ID] [uri URI]'

---
 src/devices/grotty/tty.cpp | 79 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 79 insertions(+)

diff --git a/src/devices/grotty/tty.cpp b/src/devices/grotty/tty.cpp
index 72f963e3de..550f0ef170 100644
--- a/src/devices/grotty/tty.cpp
+++ b/src/devices/grotty/tty.cpp
@@ -19,6 +19,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>. */
 
 #include "driver.h"
 #include "device.h"
+#include "cset.h"
 #include "ptable.h"
 
 typedef signed char schar;
@@ -56,6 +57,7 @@ static int italic_flag;
 static int reverse_flag_option = 0;
 static int reverse_flag;
 static int old_drawing_scheme = 0;
+static int no_osc8_refs = 0;
 
 static void update_options();
 static void usage(FILE *stream);
@@ -80,8 +82,12 @@ static unsigned char bold_underline_mode;
 
 #ifndef IS_EBCDIC_HOST
 #define CSI "\033["
+#define OSC8 "\033]8;"
+#define ST "\033\\"
 #else
 #define CSI "\047["
+#define OSC8 "\047]8;"
+#define ST "\047\\"
 #endif
 
 // SGR handling (ISO 6429)
@@ -189,6 +195,7 @@ class tty_printer : public printer {
   void line(int, int, int, int, color *, color *);
   void draw_line(int *, int, const environment *);
   void draw_polygon(int *, int, const environment *);
+  void special_osc8(char *, const environment *);
 public:
   tty_printer();
   ~tty_printer();
@@ -443,6 +450,76 @@ void tty_printer::special(char *arg, const environment *env, char type)
       old_drawing_scheme = 0;
     update_options();
   }
+  else if (strncmp(command, "osc8", p - command) == 0 && !no_osc8_refs)
+    special_osc8(p, env);
+}
+
+void
+tty_printer::special_osc8(char *ap, const environment *env){
+  size_t idl, uril, *lp;
+  char *ap_orig, *idp, *urip, *cp, **bpp;
+
+  ap_orig = ap;
+  idl = uril = 0;
+  idp = urip = NULL;
+
+  for(lp = NULL; *ap != '\0';){
+    for(; *ap == ' ' || *ap == '\n'; ++ap)
+      ;
+    cp = ap;
+    for(; *ap != '\0' && *ap != ' ' && *ap != '\n'; ++ap)
+      ;
+    if(cp == ap){
+      assert(*ap == '\0');
+      break;
+    }
+
+    if(lp != NULL){
+      *bpp = cp;
+      *lp = (size_t)(ap - cp);
+      lp = NULL;
+
+      for(; cp < ap; ++cp)
+        if(!csprint(*cp)){
+          error("invalid X osc8 parameter content, ignoring: %1", ap_orig);
+          goto jleave;
+        }
+    }else if(!strncmp(cp, "id", (size_t)(ap - cp))){
+      lp = &idl;
+      bpp = &idp;
+    }else if(!strncmp(cp, "uri", (size_t)(ap - cp))){
+      lp = &uril;
+      bpp = &urip;
+    }else{
+      error("unknown X osc8 parameter, ignoring: %1", ap_orig);
+      goto jleave;
+    }
+  }
+
+  if(lp != NULL){
+    error("missing argument of X osc8 parameter, ignoring: %1", ap_orig);
+    goto jleave;
+  }
+
+  for(char const *ccp = OSC8; *ccp != '\0'; ++ccp)
+    add_char(*ccp, 0, env->hpos, env->vpos, env->col, env->fill, 0);
+
+  if(idp != NULL){
+    for(char const *ccp = "id="; *ccp != '\0'; ++ccp)
+      add_char(*ccp, 0, env->hpos, env->vpos, env->col, env->fill, 0);
+    while(idl-- != 0)
+      add_char(*idp++, 0, env->hpos, env->vpos, env->col, env->fill, 0);
+  }
+
+  add_char(';', 0, env->hpos, env->vpos, env->col, env->fill, 0);
+
+  while(uril-- != 0)
+    add_char(*urip++, 0, env->hpos, env->vpos, env->col, env->fill, 0);
+
+  for(char const *ccp = ST; *ccp != '\0'; ++ccp)
+    add_char(*ccp, 0, env->hpos, env->vpos, env->col, env->fill, 0);
+
+jleave:;
 }
 
 void tty_printer::change_color(const environment * const env)
@@ -836,6 +913,8 @@ int main(int argc, char **argv)
   static char stderr_buf[BUFSIZ];
   if (getenv("GROFF_NO_SGR"))
     old_drawing_scheme = 1;
+  if(getenv("GROFF_NO_OSC8"))
+    no_osc8_refs = 1;
   setbuf(stderr, stderr_buf);
   setlocale(LC_CTYPE, "");
   int c;
-- 
2.31.1

MDOCMX(7)            BSD Miscellaneous Information Manual            MDOCMX(7)

NAME]8;id=1;\]8;;\
     .Mx — Reference extension for the mdoc semantic markup language

SYNOPSIS]8;id=2;\]8;;\
     .Mx -enable [output-formats]

     .Mx -disable

     .Mx
     .Mx macro
     .Mx macro key

     .Mx -ix [category] key
     .Mx -sx [category]

     .Mx -toc [output-formats] [-tree 
output-formats]

TABLE OF CONTENTS]8;id=3;\]8;;\
     NAME[]8;;#1\1]8;;\]
     SYNOPSIS[]8;;#2\2]8;;\]
     TABLE OF CONTENTS[]8;;#3\3]8;;\]
     DESCRIPTION[]8;;#4\4]8;;\]
           Creating referenceable 
anchors[]8;;#5\5]8;;\]
           String cleanup and reference 
prevention[]8;;#6\6]8;;\]
           Freely definable anchors and 
references[]8;;#7\7]8;;\]
           Creating table of 
contents[]8;;#8\8]8;;\]
           Strings that affect 
mdocmx[]8;;#9\9]8;;\]
     IMPLEMENTATION NOTES[]8;;#10\10]8;;\]
           Internal extended 
synopsis[]8;;#11\11]8;;\]
     ENVIRONMENT[]8;;#12\12]8;;\]
     EXAMPLES[]8;;#13\13]8;;\]
     COMPATIBILITY[]8;;#14\14]8;;\]
     SEE ALSO[]8;;#15\15]8;;\]
     HISTORY[]8;;#16\16]8;;\]
     AUTHORS[]8;;#17\17]8;;\]
     CAVEATS[]8;;#18\18]8;;\]

DESCRIPTION]8;id=4;\]8;;\
     mdocmx(7)[]8;id=43;man://mdocmx.7\43]8;;\] introduces 
referenceable index anchors to the 
mdoc(7)[]8;id=44;man://mdoc.7\44]8;;\]
     semantic markup language that is used for UNIX manual pages by defining a
     single new multiplexer command: .Mx.  Users can truly enable this 
exten‐
     sion by setting the environment variable 
MDOCMX_ENABLE[]8;;#42\42]8;;\] to a non-empty
     value (non-empty is a troff(1)[]8;id=45;man://troff.1\45]8;;\] 
requirement).

     The mdocmx(7)[]8;id=46;man://mdocmx.7\46]8;;\] reference 
extension augments the standard 
mdoc(7)[]8;id=47;man://mdoc.7\47]8;;\]
     document prologue – .Dd, .Dt and .Os – with the 
new command ‘.Mx
     -enable’.  It can be restricted to specific output formats by 
adding
     those troff(1)[]8;id=48;man://troff.1\48]8;;\] output devices 
for which expansion is desired as fur‐
     ther arguments to ‘-enable’; for convenience all typewriter-like 
devices
     can be addressed via ‘tty’.  It is not an error to specify a 
device for
     which no special mdocmx(7)[]8;id=49;man://mdocmx.7\49]8;;\] 
support is available, but such requests
     are simply ignored.

     Because macros driven by single-pass troff implementations cannot create
     forward references mdoc(7)[]8;id=50;man://mdoc.7\50]8;;\] 
documents which use this extension need to
     be preprocessed with the 
mdocmx(1)[]8;id=51;man://mdocmx.1\51]8;;\] preprocessor, which is 
a regular
     part of mdocmx(7)[]8;id=52;man://mdocmx.7\52]8;;\] and 
implemented in portable sh(1)[]8;id=53;man://sh.1\53]8;;\] and
     awk(1)[]8;id=54;man://awk.1\54]8;;\].  Specialized manual 
formatters and macros driven by multi-
     pass troff interpreters may not require a preprocessor to support this
     extension.  It is also possible to preprocess the manual once and dis‐
     tribute the resulting document – refer to the 
COMPATIBILITY[]8;;#14\14]8;;\] section
     for more on that.

     Sometimes it may be desirable to actively suppress any processing of the
     mdocmx(7)[]8;id=55;man://mdocmx.7\55]8;;\] reference 
extension: this can either be accomplished by
     using .Mx with the ‘-disable’ argument or by defining 
the string
     ‘mx-disable[]8;;#36\36]8;;\]’, as in

           $ MDOCMX_ENABLE=anyval; export MDOCMX_ENABLE
           $ < xy.z mdocmx.sh | troff -Tutf8 -mdoc -a -dmx-disable=1

   Creating referenceable anchors]8;id=5;\]8;;\
     After the extension was ‘-enable’d in the document prologue the 
third
     group of .Mx usage forms can be used to enqueue index anchor 
requests.
     These requests form a last–in — first–out stack which will be consumed
     (popped) by the later occurrence of a (corresponding) 
mdoc(7)[]8;id=56;man://mdoc.7\56]8;;\] macro
     which supports referenceable index entries.  The indices are managed with
     distinct namespaces for each supported macro, meaning that, e.g., 
‘.Mx Ic
     sendmail’ and ‘.Mx Va sendmail’ will create distinct index 
anchors.

     Using the plain macro .Mx without arguments creates a stack entry 
for
     which both, the name of the macro as well as the key 
will be taken from
     the document content.  ‘.Mx macro’ will create a stack 
entry that will be
     consumed by the next occurrence of macro only, then taking the 
key off
     the document content, whereas ‘.Mx macro key’ 
creates a stack entry that
     also has its key defined, and which will be consumed once an 
exactly
     matching macro / key pair is seen in the document only.  (The
     EXAMPLES[]8;;#13\13]8;;\] section gives a use case 
for this form.)

     Using the mdocmx(1)[]8;id=57;man://mdocmx.1\57]8;;\] 
preprocessor will also create referenceable
     anchors for the mdoc(7)[]8;id=58;man://mdoc.7\58]8;;\] 
section header commands .Sh and .Ss automati‐
     cally, so that a mdoc(7)[]8;id=59;man://mdoc.7\59]8;;\] macro 
package which supports the
     mdocmx(7)[]8;id=60;man://mdocmx.7\60]8;;\] extension will be 
enabled to actually create references
     with the .Sx command, and, dependent on the output device, 
cross-refer‐
     ences defined via the command .Xr will also be backed with 
functionality.
     The following macros gain support for referenceable anchors via 
.Mx:

     .Ar]8;id=19;\]8;;\  Command argument.
     .Cd]8;id=20;\]8;;\  Configuration declaration.
     .Cm]8;id=21;\]8;;\  Command modifier.
     .Dv]8;id=22;\]8;;\  Defined variable or preprocessor constant.
     .Er]8;id=23;\]8;;\  Error constant.
     .Ev]8;id=24;\]8;;\  Environment variable.
     .Fl]8;id=25;\]8;;\  Command line option (flag).
     .Fn]8;id=26;\]8;;\  Function name.
     .Fo]8;id=27;\]8;;\  Function name (in function block syntax). 
 This is mapped to
          .Fn[]8;;#26\26]8;;\], .Fo has no index 
by itself.
     .Ic]8;id=28;\]8;;\  Internal or interactive command.
     .In]8;id=29;\]8;;\  An ‘include’ file for, e.g., the C 
programming language.
     .Pa]8;id=30;\]8;;\  File system path.
     .Va]8;id=31;\]8;;\  Variable name.
     .Vt]8;id=32;\]8;;\  Variable type, type reference.

   String cleanup and reference prevention]8;id=6;\]8;;\
     Before strings get used for anchor creation or reference look up any sur‐
     rounding whitespace will be removed, as well as any preceding ‘\&’, ‘\%’
     and postposed ‘\&’, ‘\%’, ‘\/’, and ‘\c’ escape characters.  Yet, prefix‐
     ing a command with two zero-width glyphs (after possible whitespace), as
     in ‘\&\&’, has the special meaning that reference lookup will be actively
     prevented for all remaining arguments of the command.  For example, in
     the hypothetic ‘.Ic if , elif , else , endif’ all four commands would be
     linked, but in ‘.Ic \&\&if , elif , else , endif’ none of them; if that
     is not desired, a new command needs to be started: ‘.Ic \&\&if , .Ic
     elif , else , endif’.

   Freely definable anchors and references]8;id=7;\]8;;\
     Via the ‘.Mx -ix category key’ and ‘.Mx -ix 
key’ usage forms anchors can
     be defined almost anywhere, e.g., ‘.Mx -ix subsubsection "An 
interesting
     topic"’ defines the anchor ‘An interesting topic’ for the “key”
     ‘subsubsection’.  The form without a specified category will use 
the
     builtin name ]8;id=33;\]8;;\‘ixsx’ instead.

     References to anchors that have been created via -ix can be made 
by acti‐
     vating the .Sx search extension via ‘.Mx -sx 
category’ (or ‘.Mx -sx’ for
     the builtin ‘ixsx[]8;;#33\33]8;;\]’ 
category) followed by a normal local reference
     lookup:

           .Mx -sx subsubsection
           .Sx "An interesting topic"

     It should be noted that these usage forms are mostly ment for automated
     conversion tools rather than for human manual creators: their use is non-
     trivial (which is owed to the implementation of 
mdoc(7)[]8;id=61;man://mdoc.7\61]8;;\]) and the
     resulting visual output should always be verified!  As a rule of thumb
     anchors should always be created inside some “normal” text so 
that they
     can be attached to something “physical”.

   Creating table of contents]8;id=8;\]8;;\
     The final usage form of the 
mdocmx(7)[]8;id=62;man://mdocmx.7\62]8;;\] reference extension 
allows the
     creation of a document table of contents, which is of special interest
     when converting a mdoc(7)[]8;id=63;man://mdoc.7\63]8;;\] 
document into formats such as HTML, XHTML
     or PDF.  To restrict the creation of the table of contents to special
     output formats, add the names of those 
troff(1)[]8;id=64;man://troff.1\64]8;;\] output devices for
     which expansion is desired as further arguments to ‘-toc’; for 
conve‐
     nience all typewriter-like devices can be addressed via ‘tty’.

     By default only .Sh section headers are a vivid part of the TOC; 
in order
     to include .Ss subsections also add a ‘-tree’ argument.  
Note that if
     ‘-tree’ is used in conjunction with output-device restrictions it 
will
     only affect those devices that appear later on the line.

     In the first of the following examples a table of contents will be gener‐
     ated for PDF and typewriter-like devices.  In the second example a tree
     of contents will instead be generated for the output formats PDF and
     HTML, whereas typewriter-like devices will see a flat table of contents
     with only section headers.

           .Mx -toc pdf tty
           .Mx -toc tty -tree html pdf

   Strings that affect mdocmx]8;id=9;\]8;;\
     Note that due to deficiencies in some implementations of 
troff(1)[]8;id=65;man://troff.1\65]8;;\]
     strings given on the command line (via option ‘-d’) have to be 
given an
     argument in order to be perceived on the macro level.

     mx-debug]8;id=34;\]8;;\            If defined 
mdocmx(7)[]8;id=66;man://mdocmx.7\66]8;;\] macros will offer some 
ver‐
                         bosity.  In addition not only references will produce
                         visual output, but also anchors.

     mx-anchor-dump]8;id=35;\]8;;\      If this is set to a filename then 
the list of anchors
                         is dumped to it.

     mx-disable]8;id=36;\]8;;\          Has the same effect as ‘.Mx 
-disable’.

     mx-toc-disable]8;id=37;\]8;;\      Forcefully turn off any table of 
contents creation.

     mx-toc-emerged]8;id=38;\]8;;\      Normally compact display is used 
for the table of
                         contents, but when this string is set an emerged dis‐
                         play is used for the first level that lists the head‐
                         ings.

     mx-toc-force]8;id=39;\]8;;\        Defining this string can be used to 
enforce the cre‐
                         ation of a table of contents as specified, even if
                         the documents ‘-toc’ configuration would not 
create
                         one for the targeted output device.  A flat table of
                         contents will be generated unless the string value is
                         ‘tree’.

     mx-toc-name]8;id=40;\]8;;\         If defined its content is used as 
the headline of the
                         table of contents, which can be used for, e.g.,
                         localization purposes.  The default is “TABLE OF
                         CONTENTS”.  (Note that if the table of contents has
                         instead been generated by the 
mdocmx(1)[]8;id=67;man://mdocmx.1\67]8;;\] pre‐
                         processor then the resulting document already
                         includes a definition of this string to ensure com‐
                         patibility with, at least, 
mandoc(1)[]8;id=68;man://mandoc.1\68]8;;\].)

     mx-toc-numbered]8;id=41;\]8;;\     If defined the first level of the 
table of contents
                         will be numbered.

IMPLEMENTATION NOTES]8;id=10;\]8;;\
     The .Mx request cannot share a line with other macros, neither in 
the
     document prologue nor in its content.  Whereas that is mostly owed to the
     necessity of ensuring (backward) compatibility with environments that do
     not support mdocmx(7)[]8;id=69;man://mdocmx.7\69]8;;\], it 
also simplified implementation of the pre‐
     processor.

   Internal extended synopsis]8;id=11;\]8;;\
     In addition to those usage forms that have been described above the 
.Mx
     multiplexer command also understands further flags and arguments which
     are of possible interest for formatter and macro implementors.  These
     further flags and arguments are only generated by the 
mdocmx(1)[]8;id=70;man://mdocmx.1\70]8;;\] pre‐
     processor and are solely ment to communicate the preprocessed state of
     the document to the actual consumers.

     For one a ‘-preprocessed’ flag is appended to the single 
‘-enable’ com‐
     mand in the document prologue.  And then an additional 
‘-anchor-spass’
     form is introduced, which takes two or three arguments – the macro (name
     of the command) for which this defines an anchor as well as its key, pos‐
     sibly followed by a numeric argument that describes the relationship in
     between section headings: for .Sh commands it defines a running 
one-based
     index count of section headers, for .Ss commands it instead 
specifies the
     index of the section header they belong to, therefore creating the possi‐
     bility to generate TOCs.

ENVIRONMENT]8;id=12;\]8;;\
     Only if the environment variable ‘MDOCMX_ENABLE]8;id=42;\]8;;\’ is set 
to a non-empty
     value will the mdocmx(7)[]8;id=71;man://mdocmx.7\71]8;;\] 
macros generate the necessary information
     that the chosen output device of 
troff(1)[]8;id=72;man://troff.1\72]8;;\] can, sufficient support
     provided, use to generate table of contents, internal as well as external
     references.

EXAMPLES]8;id=13;\]8;;\
     A complete, but completely fanciful 
mdoc(7)[]8;id=73;man://mdoc.7\73]8;;\] document that uses the
     mdocmx(7)[]8;id=74;man://mdocmx.7\74]8;;\] extension would 
for example be:

           .Dd April 22, 2015
           .Dt MDOCMX-EXAMPLE 7
           .Os
           .Mx -enable tty
           .
           .Sh NAME
           .Nm mdocmx-example
           .Nd An example for the mdocmx mdoc reference extension
           .
           .Mx -toc
           .
           .Sh DESCRIPTION
           Sors salutis et virtutis michi nunc contraria.
           .
           .Bl -tag -width ".It Fn _a_e_i_"
           .Mx
           .It Ic .Ar
           This will create an anchor for a macro
           .Ql \&Ic ,
           key
           .Ql .Ar .
           .Mx
           .It Ic .Cm
           Anchor for
           .Ql \&Ic ,
           key
           .Ql .Cm .
           .Mx
           .It Ic .Dv
           And an anchor for
           .Ql \&Ic ,
           key
           .Ql .Dv .
           .Mx Ic
           .Mx Ic "final anchor"
           .Mx Fn _atexit
           .It Fn exit
           No anchor here.
           .It Fn at_quick_exit , Fn _atexit
           Not for the first, but for the second
           .Ql \&Fn
           there will be an anchor with the key
           .Ql _atexit .
           .It Ic "no anchor here"
           .It Ic "final anchor"
           Pops the pushed
           .Ql \&Ic
           /
           .Ql final anchor
           macro / key pair.
           .It Ic ciao
           Pops the
           .Ql \&Ic
           and assigns the key
           .Ql Ciao .
           .El

COMPATIBILITY]8;id=14;\]8;;\
     Using the mdocmx(7)[]8;id=75;man://mdocmx.7\75]8;;\] 
extension in mdoc(7)[]8;id=76;man://mdoc.7\76]8;;\] manual 
pages should not
     cause any compatibility problems in sofar as all tested environments
     silently ignore the unknown commands by default.  Because of this, and
     due to the nature of this extension, an interesting, backward as well as
     forward compatible approach to use 
mdocmx(7)[]8;id=77;man://mdocmx.7\77]8;;\] may be to preprocess
     manuals with mdocmx(1)[]8;id=78;man://mdocmx.1\78]8;;\] on 
developer machines and instead distribute
     the resulting documents.

SEE ALSO]8;id=15;\]8;;\
     awk(1)[]8;id=79;man://awk.1\79]8;;\], 
mandoc(1)[]8;id=80;man://mandoc.1\80]8;;\], 
mdocmx(1)[]8;id=81;man://mdocmx.1\81]8;;\], 
sh(1)[]8;id=82;man://sh.1\82]8;;\], 
troff(1)[]8;id=83;man://troff.1\83]8;;\],
     mdoc(7)[]8;id=84;man://mdoc.7\84]8;;\]

HISTORY]8;id=16;\]8;;\
     The .Mx environment appeared in 2014.

AUTHORS]8;id=17;\]8;;\
     Idea and implementation by Steffen Nurpmeso <stef...@sdaoden.eu>.

CAVEATS]8;id=18;\]8;;\
     Be aware that the content of the ‘-width’ argument to 
mdoc(7)[]8;id=85;man://mdoc.7\85]8;;\] lists
     etc. is evaluated as if it were normal document content; e.g., in the
     following example the ‘Fn _atexit’ will be evaluated and may thus get
     used by .Mx:

           .Bl -tag -width ".It Fn _atexit"

     When developing a manual it may be helpful to increase verbosity of the
     mdocmx(1)[]8;id=86;man://mdocmx.1\86]8;;\] preprocessor on 
its standard error I/O channel by using the
     ‘-v’ command line flag in order to get a notion on what is going 
on:

           $ MDOCMX_ENABLE=1; export MDOCMX_ENABLE
           $ mdocmx.sh -vv < mdocmx.7 2> stderr.txt | \
             groff -Tutf8 -mdoc -dmx-toc-force=tree -dmx-debug=1 | \
             less -R
           $ cat stderr.txt

BSD                              May 25, 2021                              BSD

Reply via email to