diff --git a/cmd.h b/cmd.h index 9a72160..1106f61 100644 --- a/cmd.h +++ b/cmd.h @@ -66,6 +66,7 @@ #define A_PREV_TAG 54 #define A_FILTER 55 #define A_F_UNTIL_HILITE 56 +#define A_MDOCMX 84 #define A_INVALID 100 #define A_NOACTION 101 diff --git a/command.c b/command.c index ed5b323..0f4f552 100644 --- a/command.c +++ b/command.c @@ -55,6 +55,12 @@ extern int shift_count; extern int oldbot; extern int forw_prompt; +/* For mdocmx */ +#if HILITE_SEARCH +extern int hilite_search; +#endif +public char *mdocmx_line; + #if SHELL_ESCAPE static char *shellcmd = NULL; /* For holding last shell command for "!!" */ #endif @@ -80,6 +86,8 @@ static struct ungot* ungot = NULL; static int unget_end = 0; static void multi_search(); +static void mdocmx_search(); +static void _mdocmx_check_xr(); /* * Move the cursor to start of prompt line before executing a command. @@ -282,6 +290,9 @@ exec_mca() error("|done", NULL_PARG); break; #endif + case A_MDOCMX: + mdocmx_search(cbuf); + break; } } @@ -956,6 +967,159 @@ multi_search(pattern, n) } /* + * mdocmx(7) -- reference extension for mdoc(7) semantic markup language. + * 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 + */ + 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); + + if (*cbuf == '\0') + error("Need a reference anchor", NULL_PARG); + else if (*q != '\0' || l <= 0) + error("Invalid reference anchor: %s", &parg); + /* Local references are scrolled into view */ + else { + /* Local anchor: '{DIGITS}', each char doubled and ^H'd away */ + q = ecalloc(strlen(cbuf)*4 + 4*2 +1, sizeof *q); + + q[0] = '{'; q[1] = '\b'; q[2] = '{'; q[3] = '\b'; + for (qc = q + 4; *cbuf != '\0'; qc += 4, ++cbuf) + qc[0] = *cbuf, qc[1] = '\b', + qc[2] = *cbuf, qc[3] = '\b'; + qc[0] = '}'; qc[1] = '\b'; qc[2] = '}'; qc[3] = '\b'; + /*qc[4] = '\0';*/ + +#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 doubled and ^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!\b!\b", sizeof("{\b{\b!\b!\b") -1)) + goto jleave; + + for (sect = (cp += sizeof("{\b{\b!\b!\b") -1);; cp += 4) { + if ((c = cp[0]) == '\0' || cp[1] != '\b' || + cp[2] != c || cp[3] != '\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 += 4);; cp += 4) { + if ((c = cp[0]) == '\0' || cp[1] != '\b' || + cp[2] != c || cp[3] != '\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("Feature 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 += 4) + *cp++ = *sect; + *cp++ = ' '; + for (; man < man_top; man += 4) + *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: + free(mdocmx_line); + +#else /* HAVE_STRSTR */ + (void)anchor; +#endif +} + +/* * Forward forever, or until a highlighted line appears. */ static int @@ -1784,6 +1948,12 @@ commands() case A_NOACTION: break; + case A_MDOCMX: + start_mca(A_MDOCMX, "[anchor]:", + (void*)NULL, CF_QUIT_ON_ERASE); + c = getcc(); + goto again; + default: bell(); break; diff --git a/decode.c b/decode.c index 6d0312d..3c99fc8 100644 --- a/decode.c +++ b/decode.c @@ -163,7 +163,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('X'),CONTROL('R'),0, A_MDOCMX }; static unsigned char edittable[] = diff --git a/less.h b/less.h index fada513..ef742f0 100644 --- a/less.h +++ b/less.h @@ -344,6 +344,7 @@ struct textlist #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 */ +#define SRCH_MDOCMX (1 << 15) /* mdocmx(7) anchor search */ #define SRCH_REVERSE(t) (((t) & SRCH_FORW) ? \ (((t) & ~SRCH_FORW) | SRCH_BACK) : \ diff --git a/lesskey.c b/lesskey.c index 3d7571e..e987a45 100644 --- a/lesskey.c +++ b/lesskey.c @@ -126,6 +126,7 @@ struct cmdname cmdnames[] = { "index-file", A_INDEX_FILE }, { "invalid", A_UINVALID }, { "left-scroll", A_LSHIFT }, + { "mdocmx", A_MDOCMX }, { "next-file", A_NEXT_FILE }, { "next-tag", A_NEXT_TAG }, { "noaction", A_NOACTION }, diff --git a/search.c b/search.c index 24d4210..d37c210 100644 --- a/search.c +++ b/search.c @@ -827,6 +827,26 @@ 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 (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; + } + /* * If it's a caseless search, convert the line to lowercase. * If we're doing backspace processing, delete backspaces.