From d374e9180166c7a655c7ee0a542df6c3643b2c74 Mon Sep 17 00:00:00 2001 Message-Id: From: Steffen Nurpmeso 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 . M_<_l_e_t_t_e_r_> Mark the current bottom line with . + ^A_<_t_e_x_t_> Go to OSC 8 anchor . '_<_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