[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[groff] 03/04: [grotty]: Add OSC 8 hyperlink support.
From: |
G. Branden Robinson |
Subject: |
[groff] 03/04: [grotty]: Add OSC 8 hyperlink support. |
Date: |
Fri, 1 Oct 2021 07:53:04 -0400 (EDT) |
gbranden pushed a commit to branch master
in repository groff.
commit ab73e8189988d15bae12a82c6b3eb07948eda1d7
Author: G. Branden Robinson <g.branden.robinson@gmail.com>
AuthorDate: Fri Oct 1 20:43:30 2021 +1000
[grotty]: Add OSC 8 hyperlink support.
* src/devices/grotty/tty.cpp: Do it. Define `OSC` (Operating System
Command) and `ST` (String Terminator) preprocessor symbols for these
ECMA-48 (ISO 6429) character sequences.
(tty_printer::simple_add_char): Add stripped-down alternative to
`add_char()` member function for cases where we want to use many
defaults because we're writing a device control command, not rendering
a glyph. (A function like `add_char()` that take 8 arguments of
varying types is a code smell--phew!)
(tty_printer::special): Call `special_link()` member function if the
device control command is `link`.
(tty_printer::special_link): Add new member function to generate OSC 8
hyperlinks.
* src/devices/grotty/grotty.1.man: Document it. Observe in multiple
places that disablement of SGR escape sequences disables OSC 8 too.
* src/devices/grotty/tests/osc8_works.sh: Test it.
* src/devices/grotty/grotty.am (TESTS, grotty_TESTS): Run test.
(EXTRA_DIST): Ship test.
* NEWS: Add item.
Fixes <https://savannah.gnu.org/bugs/?60666>. Thanks to Steffen
Nurpmeso for supplying a proof-of-concept. (I went with my own
implementation, though, so blame me if it breaks.)
---
ChangeLog | 31 +++++++++
NEWS | 4 ++
src/devices/grotty/grotty.1.man | 93 +++++++++++++++++++-------
src/devices/grotty/grotty.am | 6 +-
src/devices/grotty/tests/osc8_works.sh | 118 +++++++++++++++++++++++++++++++++
src/devices/grotty/tty.cpp | 95 +++++++++++++++++++++++++-
6 files changed, 320 insertions(+), 27 deletions(-)
diff --git a/ChangeLog b/ChangeLog
index 1653477..f1c5fca 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,36 @@
2021-10-01 G. Branden Robinson <g.branden.robinson@gmail.com>
+ [grotty]: Add OSC 8 hyperlink support.
+
+ * src/devices/grotty/tty.cpp: Do it. Define `OSC` (Operating
+ System Command) and `ST` (String Terminator) preprocessor
+ symbols for these ECMA-48 (ISO 6429) character sequences.
+ (tty_printer::simple_add_char): Add stripped-down alternative to
+ `add_char()` member function for cases where we want to use many
+ defaults because we're writing a device control command, not
+ rendering a glyph. (A function like `add_char()` that take 8
+ arguments of varying types is a code smell--phew!)
+ (tty_printer::special): Call `special_link()` member function if
+ the device control command is `link`.
+ (tty_printer::special_link): Add new member function to generate
+ OSC 8 hyperlinks.
+
+ * src/devices/grotty/grotty.1.man: Document it. Observe in
+ multiple places that disablement of SGR escape sequences
+ disables OSC 8 too.
+
+ * src/devices/grotty/tests/osc8_works.sh: Test it.
+ * src/devices/grotty/grotty.am (TESTS, grotty_TESTS): Run test.
+ (EXTRA_DIST): Ship test.
+
+ * NEWS: Add item.
+
+ Fixes <https://savannah.gnu.org/bugs/?60666>. Thanks to Steffen
+ Nurpmeso for supplying a proof-of-concept. (I went with my own
+ implementation, though, so blame me if it breaks.)
+
+2021-10-01 G. Branden Robinson <g.branden.robinson@gmail.com>
+
[troff]: Convert special character glyphs corresponding to
Unicode Basic Latin ("ASCII") code points to those code points
when they occur in device escapes. (They should be correct for
diff --git a/NEWS b/NEWS
index 1a7ab30..2ef7d9c 100644
--- a/NEWS
+++ b/NEWS
@@ -206,6 +206,10 @@ o The s (ms) macro package has added strings, \*< and \*>,
to perform
grotty
------
+o A new device control command, "link", generates OSC 8 hyperlinks.
+ This means that groff documents can produce clickable links in the
+ terminal window for emulators that support such escape sequences.
+
o On the Latin-1 output device ("groff -T latin1") the output glyph
\[oq] (opening quote) is now rendered as code point 0x27 (apostrophe)
instead of 0x60 (grave accent). The ISO 8859/ECMA-94 Latin character
diff --git a/src/devices/grotty/grotty.1.man b/src/devices/grotty/grotty.1.man
index 44de5df..a048747 100644
--- a/src/devices/grotty/grotty.1.man
+++ b/src/devices/grotty/grotty.1.man
@@ -7,7 +7,7 @@ grotty \- groff output driver for typewriter-like (terminal)
devices
.\" Legal Terms
.\" ====================================================================
.\"
-.\" Copyright (C) 1989-2020 Free Software Foundation, Inc.
+.\" Copyright (C) 1989-2021 Free Software Foundation, Inc.
.\"
.\" Permission is granted to make and distribute verbatim copies of this
.\" manual provided the copyright notice and this permission notice are
@@ -105,7 +105,7 @@ reads the standard input stream.
Output is written to the standard output stream.
.
.
-.LP
+.P
By default,
.I grotty
emits SGR escape sequences
@@ -118,8 +118,6 @@ Devices supporting the appropriate sequences can view
.I roff
documents using eight different background and foreground colors.
.
-.
-.LP
Following ISO\~6429,
the following colors are defined in
.IR tty.tmac :
@@ -128,8 +126,10 @@ black, white, red, green, blue, yellow, magenta, and cyan.
Unrecognized colors are mapped to the default color,
which is dependent on the settings of the terminal.
.
+OSC\~8 hyperlinks are produced for these devices.
+.
.
-.LP
+.P
In keeping with long-standing practice and the rarity of terminals,
hardware or emulated,
that support oblique or italic fonts,
@@ -140,18 +140,20 @@ option below.
.
.
.\" ====================================================================
-.SS "SGR support in pagers"
+.SS "SGR and OSC support in pagers"
.\" ====================================================================
.
When paging
.IR grotty 's
output with
.IR less (1),
-the latter program must be instructed to pass SGR sequences through to
-the device;
+the latter program must be instructed to pass SGR and OSC sequences
+through to the device;
its
.B \-R
-option is one way to achieve this.
+option is one way to achieve this
+.RI ( less
+version 566 or later is required for OSC\~8 support).
.
Consequently,
programs like
@@ -212,7 +214,7 @@ see subsection \[lq]Device control commands\[rq],
below.
.
.
-.LP
+.P
The legacy output format can be rendered on a video terminal
(or emulator)
by piping
@@ -251,13 +253,41 @@ There is therefore no need to filter its output through
.\" ====================================================================
.
.I grotty
-understands a single device control function produced using the
+understands the following device control functions produced using the
.I roff
.B \[rs]X
escape sequence in a document.
.
.
.TP
+.BR "\[rs]X\[aq]tty: link " [\c
+.IR uri \~[ key\c
+.BI = value\c
+] \|.\|.\|.\|]\c
+.B \[aq]
+.
+Embed a hyperlink using the OSC 8 terminal escape sequence.
+.
+Specifying
+.I uri
+starts hyperlinked text,
+and omitting it ends the hyperlink.
+.
+When
+.I uri
+is is present,
+any number of additional key/value pairs can be specified;
+their interpretation is the responsibility of the terminal emulator.
+.
+Spaces or tabs cannot appear literally in
+.IR uri ,
+.IR key ,
+or
+.IR value ;
+they must be represented in an alternate form.
+.
+.
+.TP
.BR "\[rs]X\[aq]tty: sgr " [\c
.IR n ]\c
.B \[aq]
@@ -269,12 +299,17 @@ is non-zero or missing, enable SGR sequences
otherwise,
use the legacy output format.
.
+This device control command,
+like the
+.B \-c
+option,
+also prevents the emission of terminal OSC 8 hyperlink escape sequences.
+.
.
.\" ====================================================================
.SS "Device description files"
.\" ====================================================================
.
-.LP
If
.I DESC
file for the character encoding contains the keyword
@@ -290,7 +325,7 @@ See
for more details.
.
.
-.LP
+.P
A font description file may contain a command
.RB \[lq] internalname\~\c
.IR n \[rq]
@@ -354,6 +389,8 @@ Use
legacy output format
(see subsection \[lq]Legacy output format\[rq] above).
.
+OSC\~8 hyperlink terminal escape sequences are not emitted.
+.
.
.TP
.B \-d
@@ -423,7 +460,7 @@ Render italic-styled text
with the SGR attribute for italic text
rather than underlined text.
.
-Note that many terminals don't support this attribute;
+Many terminals don't support this attribute;
however,
.IR xterm (1),
since patch\~#314 (2014-12-28),
@@ -563,7 +600,7 @@ Additional character definitions for use with
.
.\" The following no longer seems to be true; an inspection of the
.\" font/*/dev*.am files suggests no evidence of it, at any rate.
-.\".LP
+.\".P
.\"Note that on EBCDIC hosts,
.\"only files for the \[lq]cp1047\[rq] device are installed.
.
@@ -576,24 +613,24 @@ Additional character definitions for use with
is intended only for simple documents.
.
.
-.LP
+.P
There is no support for fractional horizontal or vertical motions.
.
.
-.LP
+.P
There is no support for the
.I roff
.B \[rs]D
escape sequence (draw command) other than horizontal and vertical lines.
.
.
-.LP
+.P
Characters above the first line
(i.e., with a vertical position of\~0)
cannot be printed.
.
.
-.LP
+.P
Color handling differs from
.IR grops (@MAN1EXT@).
.
@@ -625,7 +662,7 @@ not all of which may be supported by a given output device:
(7)\~horizontal and vertical line-drawing.
.
.
-.LP
+.P
.RS
.EX
You might see \ef[B]bold\ef[] and \ef[I]italic\ef[].
@@ -640,11 +677,11 @@ Black on cyan can have a
\eM[cyan]\em[black]prominent\em[]\eM[]
.RE
.
.
-.LP
+.P
Compare and contrast the output of the following:
.
.
-.LP
+.P
.RS
.EX
$ \c
@@ -666,7 +703,7 @@ $ \c
.
.
.\" I wish this went without saying...
-.LP
+.P
Note that the example file above is a \[lq]raw\[rq]
.I groff
document,
@@ -705,7 +742,15 @@ ECMA\-ST/\:Ecma\-048\:.pdf
.UE .
.
.
-.LP
+.P
+.UR https://\:gist\:.github\:.com/\:egmontkob/\:\
+eb114294efbcd5adb1944c9f3cb5feda
+\[lq]Hyperlinks in Terminal Emulators\[rq]
+.UE ,
+Egmont Koblinger.
+.
+.
+.P
.IR groff (@MAN1EXT@),
.IR \%@g@troff (@MAN1EXT@),
.IR groff_out (@MAN5EXT@),
diff --git a/src/devices/grotty/grotty.am b/src/devices/grotty/grotty.am
index 6a881fd..a741001 100644
--- a/src/devices/grotty/grotty.am
+++ b/src/devices/grotty/grotty.am
@@ -1,4 +1,4 @@
-# Copyright (C) 2014-2020 Free Software Foundation, Inc.
+# Copyright (C) 2014-2021 Free Software Foundation, Inc.
#
# This file is part of groff.
#
@@ -26,6 +26,10 @@ EXTRA_DIST += \
src/devices/grotty/grotty.1.man \
src/devices/grotty/TODO
+grotty_TESTS = \
+ src/devices/grotty/tests/osc8_works.sh
+TESTS += $(grotty_TESTS)
+EXTRA_DIST += $(grotty_TESTS)
# Local Variables:
# fill-column: 72
diff --git a/src/devices/grotty/tests/osc8_works.sh
b/src/devices/grotty/tests/osc8_works.sh
new file mode 100755
index 0000000..2e79d2a
--- /dev/null
+++ b/src/devices/grotty/tests/osc8_works.sh
@@ -0,0 +1,118 @@
+#!/bin/sh
+#
+# Copyright (C) 2021 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+set -e
+
+grotty="${abs_top_builddir:-.}/grotty"
+
+input="x T utf8
+x res 240 24 40
+x init
+p1
+x font 1 R
+f1
+s10
+V40
+H0
+md
+DFd
+tA
+n40 0
+x X tty: link
+x X tty: link h
+x X tty: link http://example.com/1
+x X tty: link
+x X tty: link http://example.com/2
+tB
+x X tty: link
+x X tty: link mailto:g.branden.robinson@gmail.com
+tBranden
+x X tty: link
+x trailer
+V2640
+x stop"
+
+# We expect diagnostics from the first few "x X tty: link" lines. The
+# first should complain about a link ending without having been started.
+# The second is bogus ("h") but it's not grotty's job to validate the
+# structure of a URI. The third should draw complaint because we didn't
+# end the (bogus) URI that we started with the second.
+
+# The remaining input is well-formed. The URI ending in "1" is
+# effectively hidden because no character cells are drawn while it is
+# active.
+output=$(echo "$input" | "$grotty" -F font -F build/font | od -t c)
+
+# Expected:
+#0000000 A 033 ] 8 ; ; 033 \ 033 ] 8 ; ; h 033 \
+#0000020 033 ] 8 ; ; 033 \ 033 ] 8 ; ; h t t p
+#0000040 : / / e x a m p l e . c o m / 1
+#0000060 033 \ 033 ] 8 ; ; 033 \ 033 ] 8 ; ; h t
+#0000100 t p : / / e x a m p l e . c o m
+#0000120 / 2 033 \ B 033 ] 8 ; ; 033 \ 033 ] 8 ;
+#0000140 ; m a i l t o : g . b r a n d e
+#0000160 n . r o b i n s o n @ g m a i l
+#0000200 . c o m 033 \ B r a n d e n 033 ] 8
+#0000220 ; ; 033 \ \n \n \n \n \n \n \n \n \n \n \n \n
+#0000240 \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
+#*
+#0000320 \n \n \n \n \n \n
+#0000326
+
+echo "testing for URI that corresponds to no character cells" >&2
+echo "$output" | grep -Eq 'A 033 +] +8 +; +; +033 +\\'
+
+echo "testing http URI (1)" >&2
+echo "$output" | grep -Eq '0000020 +.*033 +] +8 +; +; +h + t +t +p'
+
+echo "testing http URI (2)" >&2
+echo "$output" | grep -Eq '0000040 +: +/ +/ +e +x +a +m +p +l +e +\. +c'
+
+echo "testing http URI (3)" >&2
+echo "$output" | grep -Eq '0000040.* +o +m +/ +1'
+
+echo "testing http URI (4)" >&2
+echo "$output" | grep -Eq '0000060 +033 +\\'
+
+echo "testing mailto URI (1)" >&2
+echo "$output" | grep -Eq '0000120 +.*+033 +] +8 +;$'
+
+echo "testing mailto URI (2)" >&2
+echo "$output" | grep -Eq '0000140 +; +m +a +i +l +t +o +: +g +\. +b'
+
+echo "testing mailto URI (3)" >&2
+echo "$output" | grep -Eq '0000140.* +r +a +n +d +e$'
+
+echo "testing mailto URI (4)" >&2
+echo "$output" | grep -Eq '0000160 +n +\. +r +o +b +i +n +s +o +n +@'
+
+echo "testing mailto URI (5)" >&2
+echo "$output" | grep -Eq '0000160.* +g +m +a +i +l$'
+
+echo "testing mailto URI (6)" >&2
+echo "$output" | grep -Eq '0000200 +\. +c +o +m +033 +\\ +B +r +a +n +d'
+
+echo "testing mailto URI (7)" >&2
+echo "$output" | grep -Eq '0000200.* +e +n +033 +] +8$'
+
+echo "testing mailto URI (8)" >&2
+echo "$output" | grep -Eq '0000220 +; +; +033 +\\'
+
+# vim:set ai et sw=4 ts=4 tw=72:
diff --git a/src/devices/grotty/tty.cpp b/src/devices/grotty/tty.cpp
index 3efd852..bd295a7 100644
--- a/src/devices/grotty/tty.cpp
+++ b/src/devices/grotty/tty.cpp
@@ -1,6 +1,6 @@
-// -*- C++ -*-
-/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+/* Copyright (C) 1989-2021 Free Software Foundation, Inc.
Written by James Clark (jjc@jclark.com)
+ OSC 8 support by G. Branden Robinson
This file is part of groff.
@@ -80,8 +80,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)
@@ -183,12 +187,14 @@ class tty_printer : public printer {
schar color_to_idx(color *);
void add_char(output_character, int, int, int, color *, color *,
unsigned char);
+ void simple_add_char(const output_character, const environment *);
char *make_rgb_string(unsigned int, unsigned int, unsigned int);
int tty_color(unsigned int, unsigned int, unsigned int, schar *,
schar = DEFAULT_COLOR_IDX);
void line(int, int, int, int, color *, color *);
void draw_line(int *, int, const environment *);
void draw_polygon(int *, int, const environment *);
+ void special_link(const char *, const environment *);
public:
tty_printer();
~tty_printer();
@@ -404,6 +410,12 @@ void tty_printer::add_char(output_character c, int w,
*pp = g;
}
+void tty_printer::simple_add_char(const output_character c,
+ const environment *env)
+{
+ add_char(c, 0, env->hpos, env->vpos, env->col, env->fill, 0);
+}
+
void tty_printer::special(char *arg, const environment *env, char type)
{
if (type == 'u') {
@@ -443,6 +455,79 @@ void tty_printer::special(char *arg, const environment
*env, char type)
old_drawing_scheme = 0;
update_options();
}
+ else if (strncmp(command, "link", p - command) == 0)
+ special_link(p, env);
+}
+
+// Produce an OSC 8 hyperlink. Given ditroff input of the form:
+// x X tty: link [URI[ KEY=VALUE] ...]
+// produce "OSC 8 [;KEY=VALUE:]...;[URI]; ST ". KEY/VALUE pairs can be
+// repeated arbitrarily and are separated by colons. Omission of the
+// URI ends the hyperlink that was begun by specifying it. See
+// <https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda>.
+void tty_printer::special_link(const char *arg, const environment *env)
+{
+ static bool is_link_active = false;
+ if (old_drawing_scheme)
+ return;
+ for (const char *s = OSC8; *s != '\0'; s++)
+ simple_add_char(*s, env);
+ simple_add_char(';', env);
+ char c = *arg;
+ if ('\0' == c || '\n' == c) {
+ simple_add_char(';', env);
+ if (!is_link_active)
+ warning("ending hyperlink when none was started");
+ is_link_active = false;
+ }
+ else {
+ // Our caller ensures that we see white space after 'link'.
+ assert(c == ' ' || c == '\t');
+ if (is_link_active) {
+ warning("new hyperlink started without ending previous one;"
+ " recovering");
+ simple_add_char(';', env);
+ for (const char *s = ST; *s != '\0'; s++)
+ simple_add_char(*s, env);
+ for (const char *s = OSC8; *s != '\0'; s++)
+ simple_add_char(*s, env);
+ simple_add_char(';', env);
+ }
+ is_link_active = true;
+ do
+ c = *arg++;
+ while (c == ' ' || c == '\t');
+ arg--;
+ // The first argument is the URI.
+ char *uri = (char *)arg;
+ while (c != '\0' && c != ' ' && c != '\t')
+ c = *arg++;
+ ptrdiff_t uri_len = arg - uri - 1;
+ arg--;
+ // Any remaining arguments are "key=value" pairs.
+ char *pair = 0;
+ bool done = false;
+ do {
+ if (pair != 0)
+ simple_add_char(':', env);
+ pair = (char *)arg;
+ bool in_pair = true;
+ do {
+ c = *arg++;
+ if ('\0' == c)
+ done = true;
+ else if (' ' == c || '\t' == c)
+ in_pair = false;
+ else
+ simple_add_char(c, env);
+ } while (!done && in_pair);
+ } while (!done);
+ simple_add_char(';', env);
+ for (size_t i = uri_len; i > 0; i--)
+ simple_add_char(*uri++, env);
+ }
+ for (const char *s = ST; *s != '\0'; s++)
+ simple_add_char(*s, env);
}
void tty_printer::change_color(const environment * const env)
@@ -926,3 +1011,9 @@ static void usage(FILE *stream)
fprintf(stream, "usage: %s [-bBcdfhioruUv] [-F dir] [files ...]\n",
program_name);
}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [groff] 03/04: [grotty]: Add OSC 8 hyperlink support.,
G. Branden Robinson <=