diff --git a/cmd.h b/cmd.h index 44fdab1..10b2310 100644 --- a/cmd.h +++ b/cmd.h @@ -67,6 +67,7 @@ #define A_FILTER 55 #define A_F_UNTIL_HILITE 56 #define A_GOEND_BUF 57 +#define A_MDOCMX 84 #define A_INVALID 100 #define A_NOACTION 101 diff --git a/command.c b/command.c index 89a390e..808d260 100644 --- a/command.c +++ b/command.c @@ -57,6 +57,15 @@ extern int oldbot; extern int forw_prompt; extern int same_pos_bell; +/* For mdocmx */ +#if MDOCMX +# if HILITE_SEARCH +extern int hilite_search; +# endif +public char *mdocmx_line /* = NULL */; +static int mdocmx_enabled /* = 0 */; +#endif + #if SHELL_ESCAPE static char *shellcmd = NULL; /* For holding last shell command for "!!" */ #endif @@ -83,6 +92,10 @@ struct ungot { static struct ungot* ungot = NULL; static void multi_search(); +#if MDOCMX +static void mdocmx_search(); +static void _mdocmx_check_xr(); +#endif /* * Move the cursor to start of prompt line before executing a command. @@ -285,6 +298,11 @@ exec_mca() error("|done", NULL_PARG); break; #endif +#if MDOCMX + case A_MDOCMX: + mdocmx_search(cbuf); + break; +#endif } } @@ -953,6 +971,163 @@ multi_search(pattern, n, silent) } /* + * mdocmx(7) -- reference extension for mdoc(7) semantic markup language. + * This is only enabled if the MDOCMX_ENABLE environment variable is non-empty. + * Search for a specially crafted anchor in the document and scroll the + * respective position into view. + * If a matching anchor is found it may also be a reference to an external + * manual page: 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 MDOCMX + static void +mdocmx_search(cbuf) + char const *cbuf; +{ + const int srch_flags = SRCH_NO_REGEX | SRCH_MDOCMX; + + 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 { + /* Local anchor: '{DIGITS}', each char ^H'd away */ + q = ecalloc(strlen(cbuf)*2 + 2*2 +1, sizeof *q); + + q[0] = '{'; q[1] = '\b'; + for (qc = q + 2; *cbuf != '\0'; qc += 2, ++cbuf) + qc[0] = *cbuf, qc[1] = '\b'; + qc[0] = '}'; qc[1] = '\b'; + /*qc[2] = '\0';*/ + + 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) + _mdocmx_check_xr(q); + else + error("No such anchor: %s", &parg); +# if HILITE_SEARCH + hilite_search = save_hilite_search; + repaint_hilite(1); +# endif + + free(q); + } +} + +/* + * We have found the mdocmx(7) anchor -- is it an external .Xr reference? + */ + static void +_mdocmx_check_xr(anchor) + char const *anchor; +{ +# if HAVE_STRSTR + char c, *cp, *sect, *sect_top, *man, *man_top, *buf; + + /* Find the anchor on the line again; play safe */ + if ((cp = strstr(mdocmx_line, anchor)) == NULL) + goto jleave; + cp += strlen(anchor); + + /* Does a .Xr follow? + * .Xr anchor: '{!SECTION;MANUAL}', each char ^H'd away. + * In the following we simply jump off when we see faulty syntax: + * i wonder wether an error message should be used instead */ + if (strncmp(cp, "{\b!\b", sizeof("{\b!\b") -1)) + goto jleave; + + for (sect = (cp += sizeof("{\b!\b") -1);; cp += 2) { + if ((c = cp[0]) == '\0' || cp[1] != '\b') + goto jleave; + if (c == ';') + break; + } + sect_top = cp; + if (sect_top == sect) { + error("Bogus mdocmx(7) section reference", NULL_PARG); + goto jleave; + } + + for (man = (cp += 2);; cp += 2) { + if ((c = cp[0]) == '\0' || cp[1] != '\b') + goto jleave; + if (c == '}') + break; + } + man_top = cp; + if (man_top == man) { + error("Bogus mdocmx(7) manual reference", NULL_PARG); + goto jleave; + } + + /* This is an external reference! */ + if (secure) { + error("External references not available in secure mode", + NULL_PARG); + goto jleave; + } + +# define __Y "!man " +# define __X "Read external manual: " + buf = ecalloc(sizeof(__X __Y) -1 + + (int)(sect_top - sect) + 1 + (int)(man_top - man) +1); + + memcpy(buf, __X __Y, sizeof(__X __Y) -1); + cp = buf + sizeof(__X __Y) -1; + for (; sect < sect_top; sect += 2) + *cp++ = *sect; + *cp++ = ' '; + for (; man < man_top; man += 2) + *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(mdocmx_line); +} +#endif /* MDOCMX */ + +/* * Forward forever, or until a highlighted line appears. */ static int @@ -1796,6 +1971,30 @@ commands() case A_NOACTION: break; + case A_MDOCMX: +#if MDOCMX + if (!mdocmx_enabled) { + char const *cp = getenv("MDOCMX_ENABLE"); + if (cp != NULL && *cp != '\0') + mdocmx_enabled = 1; + else + mdocmx_enabled = -1; + } + if (mdocmx_enabled < 0) { + error("MDOCMX_ENABLE environ not set or empty", + NULL_PARG); + break; + } + + start_mca(A_MDOCMX, "[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 4ac9a35..3d6bf4d 100755 --- a/configure.ac +++ b/configure.ac @@ -575,6 +575,12 @@ AH_TOP([ #define LOGFILE (!SECURE) /* + * MDOCMX is 1 if you wish to support mdocmx(7), the reference extension + * of the mdoc(7) semantic markup language that is used for manual pages. + */ +#define MDOCMX (!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 c44b464..6717a33 100644 --- a/decode.c +++ b/decode.c @@ -164,7 +164,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_MDOCMX }; static unsigned char edittable[] = diff --git a/defines.h.in b/defines.h.in old mode 100755 new mode 100644 index 46232c0..b68a198 --- a/defines.h.in +++ b/defines.h.in @@ -88,6 +88,12 @@ #define LOGFILE (!SECURE) /* + * MDOCMX is 1 if you wish to support mdocmx(7), the reference extension + * of the mdoc(7) semantic markup language that is used for manual pages. + */ +#define MDOCMX (!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 4f5590e..9a876d6 100644 --- a/less.h +++ b/less.h @@ -364,6 +364,9 @@ struct wchar_range_table #define SRCH_NO_REGEX (1 << 12) /* Don't use regular expressions */ #define SRCH_FILTER (1 << 13) /* Search is for '&' (filter) command */ #define SRCH_AFTER_TARGET (1 << 14) /* Start search after the target line */ +#if MDOCMX +#define SRCH_MDOCMX (1 << 15) /* mdocmx(7) anchor search */ +#endif #define SRCH_REVERSE(t) (((t) & SRCH_FORW) ? \ (((t) & ~SRCH_FORW) | SRCH_BACK) : \ diff --git a/less.hlp b/less.hlp index e4cbf7a..7fc33cf 100755 --- a/less.hlp +++ b/less.hlp @@ -66,6 +66,8 @@ Each "find open bracket" command goes backward to the open bracket matching the (_N-th) close bracket in the bottom line. + ^A_<_t_e_x_t_> Go to mdocmx(7) anchor . + m_<_l_e_t_t_e_r_> Mark the current position with . '_<_l_e_t_t_e_r_> Go to a previously marked position. '' Go to the previous position. diff --git a/less.nro b/less.nro index 9789eaf..f8c17d2 100644 --- a/less.nro +++ b/less.nro @@ -97,6 +97,17 @@ Scroll horizontally left N characters, default half the screen width (see the \-# option). If a number N is specified, it becomes the default for future RIGHTARROW and LEFTARROW commands. +.IP "^A" +Enter the +.I mdocmx +(7) (a reference extension for the +.I mdoc +(7) semantic markup language used for manual pages) +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. +This command is only available if the MDOCMX_ENABLE environment variable is +set to a non-empty value. .IP "r or ^R or ^L" Repaint the screen. .IP R diff --git a/lesskey.c b/lesskey.c index d91b9b7..d917f94 100644 --- a/lesskey.c +++ b/lesskey.c @@ -127,6 +127,9 @@ struct cmdname cmdnames[] = { "index-file", A_INDEX_FILE }, { "invalid", A_UINVALID }, { "left-scroll", A_LSHIFT }, +#if MDOCMX + { "mdocmx", A_MDOCMX }, +#endif { "next-file", A_NEXT_FILE }, { "next-tag", A_NEXT_TAG }, { "noaction", A_NOACTION }, diff --git a/search.c b/search.c index c1aa6a9..f57dc4c 100644 --- a/search.c +++ b/search.c @@ -1269,6 +1269,28 @@ search_range(pos, endpos, search_type, matches, maxlines, plinepos, pendpos) if (is_filtered(linepos)) continue; + /* Special case MDOCMX searches: we actually really want to + * match the plain line content, so avoid any useless + * allocations and text conversions */ +#if MDOCMX + if (search_type & SRCH_MDOCMX) { + line_match = match_pattern(info_compiled(&search_info), + search_info.text, line, line_len, &sp, &ep, 0, + search_type); + if (line_match) { + extern char *mdocmx_line; + + mdocmx_line = (char*)ecalloc(1, line_len +1); + memcpy(mdocmx_line, line, line_len); + mdocmx_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.