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 = §_top[-1]; + 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) [1mNAME[22m]8;id=1;\]8;;\ [1m.Mx [22m— Reference extension for the mdoc semantic markup language [1mSYNOPSIS[22m]8;id=2;\]8;;\ [1m.Mx -enable [22m[[4moutput-formats[24m] [1m.Mx -disable[0m [1m.Mx[0m [1m.Mx [4m[22mmacro[0m [1m.Mx [4m[22mmacro[24m [4mkey[0m [1m.Mx -ix [22m[[4mcategory[24m] [4mkey[0m [1m.Mx -sx [22m[[4mcategory[24m] [1m.Mx -toc [22m[[4moutput-formats[24m] [[1m-tree [4m[22moutput-formats[24m] [1mTABLE OF CONTENTS[22m]8;id=3;\]8;;\ [4mNAME[34m[24m[]8;;#1\1]8;;\][0m [4mSYNOPSIS[34m[24m[]8;;#2\2]8;;\][0m [4mTABLE[24m [4mOF[24m [4mCONTENTS[34m[24m[]8;;#3\3]8;;\][0m [4mDESCRIPTION[34m[24m[]8;;#4\4]8;;\][0m [4mCreating[24m [4mreferenceable[24m [4manchors[34m[24m[]8;;#5\5]8;;\][0m [4mString[24m [4mcleanup[24m [4mand[24m [4mreference[24m [4mprevention[34m[24m[]8;;#6\6]8;;\][0m [4mFreely[24m [4mdefinable[24m [4manchors[24m [4mand[24m [4mreferences[34m[24m[]8;;#7\7]8;;\][0m [4mCreating[24m [4mtable[24m [4mof[24m [4mcontents[34m[24m[]8;;#8\8]8;;\][0m [4mStrings[24m [4mthat[24m [4maffect[24m [4mmdocmx[34m[24m[]8;;#9\9]8;;\][0m [4mIMPLEMENTATION[24m [4mNOTES[34m[24m[]8;;#10\10]8;;\][0m [4mInternal[24m [4mextended[24m [4msynopsis[34m[24m[]8;;#11\11]8;;\][0m [4mENVIRONMENT[34m[24m[]8;;#12\12]8;;\][0m [4mEXAMPLES[34m[24m[]8;;#13\13]8;;\][0m [4mCOMPATIBILITY[34m[24m[]8;;#14\14]8;;\][0m [4mSEE[24m [4mALSO[34m[24m[]8;;#15\15]8;;\][0m [4mHISTORY[34m[24m[]8;;#16\16]8;;\][0m [4mAUTHORS[34m[24m[]8;;#17\17]8;;\][0m [4mCAVEATS[34m[24m[]8;;#18\18]8;;\][0m [1mDESCRIPTION[22m]8;id=4;\]8;;\ mdocmx(7)[34m[]8;id=43;man://mdocmx.7\43]8;;\] [0mintroduces referenceable index anchors to the mdoc(7)[34m[]8;id=44;man://mdoc.7\44]8;;\][0m semantic markup language that is used for UNIX manual pages by defining a single new multiplexer command: [1m.Mx[22m. Users can truly enable this exten‐ sion by setting the environment variable MDOCMX_ENABLE[34m[]8;;#42\42]8;;\] [0mto a non-empty value (non-empty is a troff(1)[34m[]8;id=45;man://troff.1\45]8;;\] [0mrequirement). The mdocmx(7)[34m[]8;id=46;man://mdocmx.7\46]8;;\] [0mreference extension augments the standard mdoc(7)[34m[]8;id=47;man://mdoc.7\47]8;;\][0m document prologue – [1m.Dd[22m, [1m.Dt [22mand [1m.Os [22m– with the new command ‘[1m.Mx[0m [1m-enable[22m’. It can be restricted to specific output formats by adding those troff(1)[34m[]8;id=48;man://troff.1\48]8;;\] [0moutput devices for which expansion is desired as fur‐ ther arguments to ‘[1m-enable[22m’; for convenience all typewriter-like devices can be addressed via ‘[4mtty[24m’. It is not an error to specify a device for which no special mdocmx(7)[34m[]8;id=49;man://mdocmx.7\49]8;;\] [0msupport is available, but such requests are simply ignored. Because macros driven by single-pass troff implementations cannot create forward references mdoc(7)[34m[]8;id=50;man://mdoc.7\50]8;;\] [0mdocuments which use this extension need to be preprocessed with the mdocmx(1)[34m[]8;id=51;man://mdocmx.1\51]8;;\] [0mpreprocessor, which is a regular part of mdocmx(7)[34m[]8;id=52;man://mdocmx.7\52]8;;\] [0mand implemented in portable sh(1)[34m[]8;id=53;man://sh.1\53]8;;\] [0mand awk(1)[34m[]8;id=54;man://awk.1\54]8;;\][0m. 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 [4mCOMPATIBILITY[34m[24m[]8;;#14\14]8;;\] [0msection for more on that. Sometimes it may be desirable to actively suppress any processing of the mdocmx(7)[34m[]8;id=55;man://mdocmx.7\55]8;;\] [0mreference extension: this can either be accomplished by using [1m.Mx [22mwith the ‘[1m-disable[22m’ argument or by defining the string ‘mx-disable[34m[]8;;#36\36]8;;\][0m’, as in $ MDOCMX_ENABLE=anyval; export MDOCMX_ENABLE $ < xy.z mdocmx.sh | troff -Tutf8 -mdoc -a -dmx-disable=1 [1mCreating referenceable anchors[22m]8;id=5;\]8;;\ After the extension was ‘[1m-enable[22m’d in the document prologue the third group of [1m.Mx [22musage 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)[34m[]8;id=56;man://mdoc.7\56]8;;\] [0m[4mmacro[0m which supports referenceable index entries. The indices are managed with distinct namespaces for each supported [4mmacro[24m, meaning that, e.g., ‘[1m.Mx [22mIc sendmail’ and ‘[1m.Mx [22mVa sendmail’ will create distinct index anchors. Using the plain macro [1m.Mx [22mwithout arguments creates a stack entry for which both, the name of the [4mmacro[24m as well as the [4mkey[24m will be taken from the document content. ‘[1m.Mx [4m[22mmacro[24m’ will create a stack entry that will be consumed by the next occurrence of [4mmacro[24m only, then taking the [4mkey[24m off the document content, whereas ‘[1m.Mx [4m[22mmacro[24m [4mkey[24m’ creates a stack entry that also has its [4mkey[24m defined, and which will be consumed once an exactly matching macro / key pair is seen in the document only. (The [4mEXAMPLES[34m[24m[]8;;#13\13]8;;\] [0msection gives a use case for this form.) Using the mdocmx(1)[34m[]8;id=57;man://mdocmx.1\57]8;;\] [0mpreprocessor will also create referenceable anchors for the mdoc(7)[34m[]8;id=58;man://mdoc.7\58]8;;\] [0msection header commands [1m.Sh [22mand [1m.Ss [22mautomati‐ cally, so that a mdoc(7)[34m[]8;id=59;man://mdoc.7\59]8;;\] [0mmacro package which supports the mdocmx(7)[34m[]8;id=60;man://mdocmx.7\60]8;;\] [0mextension will be enabled to actually create references with the [1m.Sx [22mcommand, and, dependent on the output device, cross-refer‐ ences defined via the command [1m.Xr [22mwill also be backed with functionality. The following macros gain support for referenceable anchors via [1m.Mx[22m: [1m.Ar[22m]8;id=19;\]8;;\ Command argument. [1m.Cd[22m]8;id=20;\]8;;\ Configuration declaration. [1m.Cm[22m]8;id=21;\]8;;\ Command modifier. [1m.Dv[22m]8;id=22;\]8;;\ Defined variable or preprocessor constant. [1m.Er[22m]8;id=23;\]8;;\ Error constant. [1m.Ev[22m]8;id=24;\]8;;\ Environment variable. [1m.Fl[22m]8;id=25;\]8;;\ Command line option (flag). [1m.Fn[22m]8;id=26;\]8;;\ Function name. [1m.Fo[22m]8;id=27;\]8;;\ Function name (in function block syntax). This is mapped to [1m.Fn[34m[22m[]8;;#26\26]8;;\][0m, [1m.Fo [22mhas no index by itself. [1m.Ic[22m]8;id=28;\]8;;\ Internal or interactive command. [1m.In[22m]8;id=29;\]8;;\ An ‘include’ file for, e.g., the C programming language. [1m.Pa[22m]8;id=30;\]8;;\ File system path. [1m.Va[22m]8;id=31;\]8;;\ Variable name. [1m.Vt[22m]8;id=32;\]8;;\ Variable type, type reference. [1mString cleanup and reference prevention[22m]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’. [1mFreely definable anchors and references[22m]8;id=7;\]8;;\ Via the ‘[1m.Mx -ix [4m[22mcategory[24m [4mkey[24m’ and ‘[1m.Mx -ix [4m[22mkey[24m’ usage forms anchors can be defined almost anywhere, e.g., ‘[1m.Mx -ix [22msubsubsection "An interesting topic"’ defines the anchor ‘An interesting topic’ for the “key” ‘subsubsection’. The form without a specified [4mcategory[24m will use the builtin name ]8;id=33;\]8;;\‘ixsx’ instead. References to anchors that have been created via [1m-ix [22mcan be made by acti‐ vating the [1m.Sx [22msearch extension via ‘[1m.Mx -sx [4m[22mcategory[24m’ (or ‘[1m.Mx -sx[22m’ for the builtin ‘[4mixsx[34m[24m[]8;;#33\33]8;;\][0m’ [4mcategory[24m) 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)[34m[]8;id=61;man://mdoc.7\61]8;;\][0m) and the resulting visual output should always be verified! As a rule of thumb anchors should [4malways[24m be created inside some “normal” text so that they can be attached to something “physical”. [1mCreating table of contents[22m]8;id=8;\]8;;\ The final usage form of the mdocmx(7)[34m[]8;id=62;man://mdocmx.7\62]8;;\] [0mreference extension allows the creation of a document table of contents, which is of special interest when converting a mdoc(7)[34m[]8;id=63;man://mdoc.7\63]8;;\] [0mdocument 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)[34m[]8;id=64;man://troff.1\64]8;;\] [0moutput devices for which expansion is desired as further arguments to ‘[1m-toc[22m’; for conve‐ nience all typewriter-like devices can be addressed via ‘[4mtty[24m’. By default only [1m.Sh [22msection headers are a vivid part of the TOC; in order to include [1m.Ss [22msubsections also add a ‘[1m-tree[22m’ argument. Note that if ‘[1m-tree[22m’ 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 [1mStrings that affect mdocmx[22m]8;id=9;\]8;;\ [1mNote [22mthat due to deficiencies in some implementations of troff(1)[34m[]8;id=65;man://troff.1\65]8;;\][0m strings given on the command line (via option ‘[1m-d[22m’) 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)[34m[]8;id=66;man://mdocmx.7\66]8;;\] [0mmacros 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 ‘[1m.Mx -disable[22m’. 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 ‘[1m-toc[22m’ 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)[34m[]8;id=67;man://mdocmx.1\67]8;;\] [0mpre‐ processor then the resulting document already includes a definition of this string to ensure com‐ patibility with, at least, mandoc(1)[34m[]8;id=68;man://mandoc.1\68]8;;\][0m.) mx-toc-numbered]8;id=41;\]8;;\ If defined the first level of the table of contents will be numbered. [1mIMPLEMENTATION NOTES[22m]8;id=10;\]8;;\ The [1m.Mx [22mrequest 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)[34m[]8;id=69;man://mdocmx.7\69]8;;\][0m, it also simplified implementation of the pre‐ processor. [1mInternal extended synopsis[22m]8;id=11;\]8;;\ In addition to those usage forms that have been described above the [1m.Mx[0m 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)[34m[]8;id=70;man://mdocmx.1\70]8;;\] [0mpre‐ processor and are solely ment to communicate the preprocessed state of the document to the actual consumers. For one a ‘[1m-preprocessed[22m’ flag is appended to the single ‘[1m-enable[22m’ com‐ mand in the document prologue. And then an additional ‘[1m-anchor-spass[22m’ 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 [1m.Sh [22mcommands it defines a running one-based index count of section headers, for [1m.Ss [22mcommands it instead specifies the index of the section header they belong to, therefore creating the possi‐ bility to generate TOCs. [1mENVIRONMENT[22m]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)[34m[]8;id=71;man://mdocmx.7\71]8;;\] [0mmacros generate the necessary information that the chosen output device of troff(1)[34m[]8;id=72;man://troff.1\72]8;;\] [0mcan, sufficient support provided, use to generate table of contents, internal as well as external references. [1mEXAMPLES[22m]8;id=13;\]8;;\ A complete, but completely fanciful mdoc(7)[34m[]8;id=73;man://mdoc.7\73]8;;\] [0mdocument that uses the mdocmx(7)[34m[]8;id=74;man://mdocmx.7\74]8;;\] [0mextension 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 [1mCOMPATIBILITY[22m]8;id=14;\]8;;\ Using the mdocmx(7)[34m[]8;id=75;man://mdocmx.7\75]8;;\] [0mextension in mdoc(7)[34m[]8;id=76;man://mdoc.7\76]8;;\] [0mmanual 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)[34m[]8;id=77;man://mdocmx.7\77]8;;\] [0mmay be to preprocess manuals with mdocmx(1)[34m[]8;id=78;man://mdocmx.1\78]8;;\] [0mon developer machines and instead distribute the resulting documents. [1mSEE ALSO[22m]8;id=15;\]8;;\ awk(1)[34m[]8;id=79;man://awk.1\79]8;;\][0m, mandoc(1)[34m[]8;id=80;man://mandoc.1\80]8;;\][0m, mdocmx(1)[34m[]8;id=81;man://mdocmx.1\81]8;;\][0m, sh(1)[34m[]8;id=82;man://sh.1\82]8;;\][0m, troff(1)[34m[]8;id=83;man://troff.1\83]8;;\][0m, mdoc(7)[34m[]8;id=84;man://mdoc.7\84]8;;\][0m [1mHISTORY[22m]8;id=16;\]8;;\ The [1m.Mx [22menvironment appeared in 2014. [1mAUTHORS[22m]8;id=17;\]8;;\ Idea and implementation by Steffen Nurpmeso <[4mstef...@sdaoden.eu[24m>. [1mCAVEATS[22m]8;id=18;\]8;;\ Be aware that the content of the ‘[1m-width[22m’ argument to mdoc(7)[34m[]8;id=85;man://mdoc.7\85]8;;\] [0mlists 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 [1m.Mx[22m: .Bl -tag -width ".It Fn _atexit" When developing a manual it may be helpful to increase verbosity of the mdocmx(1)[34m[]8;id=86;man://mdocmx.1\86]8;;\] [0mpreprocessor on its standard error I/O channel by using the ‘[1m-v[22m’ 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