source@mandoc.bsd.lv
 help / color / Atom feed
* mandoc: Split tagging into a validation part including prioritization in 
@ 2020-03-13 15:33 schwarze
  0 siblings, 0 replies; only message in thread
From: schwarze @ 2020-03-13 15:33 UTC (permalink / raw)
  To: source

Log Message:
-----------
Split tagging into a validation part including prioritization
in tag.{h,c} and {mdoc,man}_validate.c
and into a formatting part including command line argument checking
in term_tag.{h,c}, html.c, and {mdoc|man}_{term|html}.c.

Immediate functional benefits include:
* Improved prioritization of automatic tags for .Em and .Sy.
* Avoiding bogus automatic tags when .Em, .Fn, or .Sy are explicitly tagged.
* Explicit tagging of .Er and .Fl now works in HTML output.
* Automatic tagging of .IP and .TP now works in HTML output.
But mainly, this patch provides clean earth to build further improvements on.

Technical changes:
* Main program: Write a tag file for ASCII and UTF-8 output only.
* All formatters: There is no more need to delay writing the tags.
* mdoc(7)+man(7) formatters: No more need for elaborate syntax tree inspection.
* HTML formatter: If available, use the "string" attribute as the tag.
* HTML formatter: New function to write permalinks, to reduce code duplication.

Style cleanup in the vicinity while here:
* mdoc(7) terminal formatter: To set up bold font for children,
defer to termp_bold_pre() rather than calling term_fontpush() manually.
* mdoc(7) terminal formatter: Garbage collect some duplicate functions.
* mdoc(7) HTML formatter: Unify <code> handling, delete redundant functions.
* Where possible, use switch statements rather than if cascades.
* Get rid of some more Yoda notation.

The necessity for such changes was first discussed with kn@, but i didn't
bother him with a request to review the resulting -673/+782 line patch.

Modified Files:
--------------
    mandoc:
        Makefile
        Makefile.depend
        html.c
        html.h
        main.c
        man_html.c
        man_term.c
        man_validate.c
        mandoc_headers.3
        mandoc_html.3
        mdoc_html.c
        mdoc_term.c
        mdoc_validate.c
        read.c
        tag.c
        tag.h
        tree.c
    mandoc/regress:
        regress.pl
        regress.pl.1
    mandoc/regress/man/IP:
        Makefile
        empty.in
        empty.out_ascii
        empty.out_lint
        literal.out_html
    mandoc/regress/man/TP:
        Makefile
        literal.out_html
        vert.out_html
    mandoc/regress/mdoc:
        Makefile
    mandoc/regress/mdoc/Cm:
        Makefile
    mandoc/regress/mdoc/Dv:
        Makefile
    mandoc/regress/mdoc/Em:
        Makefile
    mandoc/regress/mdoc/Er:
        Makefile
    mandoc/regress/mdoc/Ev:
        Makefile
    mandoc/regress/mdoc/Fl:
        Makefile
    mandoc/regress/mdoc/Fo:
        Makefile
    mandoc/regress/mdoc/Ic:
        Makefile
    mandoc/regress/mdoc/Li:
        Makefile
    mandoc/regress/mdoc/Ms:
        Makefile
    mandoc/regress/mdoc/No:
        Makefile
        punct.out_lint
    mandoc/regress/mdoc/Sy:
        Makefile

Added Files:
-----------
    mandoc:
        term_tag.c
        term_tag.h
    mandoc/regress:
        copyless
    mandoc/regress/man/IP:
        empty.out_html
        empty.out_tag
        tag.in
        tag.out_ascii
        tag.out_html
        tag.out_tag
    mandoc/regress/man/TP:
        tag.in
        tag.out_ascii
        tag.out_html
        tag.out_tag
    mandoc/regress/mdoc/Cm:
        tag.in
        tag.out_ascii
        tag.out_html
        tag.out_markdown
        tag.out_tag
    mandoc/regress/mdoc/Dv:
        tag.in
        tag.out_ascii
        tag.out_html
        tag.out_markdown
        tag.out_tag
    mandoc/regress/mdoc/Em:
        tag.in
        tag.out_ascii
        tag.out_html
        tag.out_markdown
        tag.out_tag
    mandoc/regress/mdoc/Er:
        tag.in
        tag.out_ascii
        tag.out_html
        tag.out_markdown
        tag.out_tag
    mandoc/regress/mdoc/Ev:
        tag.in
        tag.out_ascii
        tag.out_html
        tag.out_markdown
        tag.out_tag
    mandoc/regress/mdoc/Fl:
        tag.in
        tag.out_ascii
        tag.out_html
        tag.out_markdown
        tag.out_tag
    mandoc/regress/mdoc/Fo:
        tag.in
        tag.out_ascii
        tag.out_html
        tag.out_markdown
        tag.out_tag
    mandoc/regress/mdoc/Ic:
        tag.in
        tag.out_ascii
        tag.out_html
        tag.out_markdown
        tag.out_tag
    mandoc/regress/mdoc/Li:
        tag.in
        tag.out_ascii
        tag.out_html
        tag.out_markdown
        tag.out_tag
    mandoc/regress/mdoc/Ms:
        tag.in
        tag.out_ascii
        tag.out_html
        tag.out_markdown
        tag.out_tag
    mandoc/regress/mdoc/No:
        tag.in
        tag.out_ascii
        tag.out_html
        tag.out_markdown
        tag.out_tag
    mandoc/regress/mdoc/Sy:
        tag.in
        tag.out_ascii
        tag.out_html
        tag.out_markdown
        tag.out_tag
    mandoc/regress/mdoc/Tg:
        Makefile
        warn.in
        warn.out_ascii
        warn.out_html
        warn.out_lint
        warn.out_markdown
        warn.out_tag

Revision Data
-------------
Index: tag.h
===================================================================
RCS file: /home/cvs/mandoc/mandoc/tag.h,v
retrieving revision 1.10
retrieving revision 1.11
diff -Ltag.h -Ltag.h -u -p -r1.10 -r1.11
--- tag.h
+++ tag.h
@@ -1,4 +1,4 @@
-/*      $Id$    */
+/* $Id$ */
 /*
  * Copyright (c) 2015, 2018, 2019, 2020 Ingo Schwarze <schwarze@openbsd.org>
  *
@@ -13,6 +13,9 @@
  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Internal interfaces to tag syntax tree nodes.
+ * For use by mandoc(1) validation modules only.
  */
 
 /*
@@ -25,19 +28,17 @@
 #define	TAG_FALLBACK	(INT_MAX - 1)	/* Tag only used if unique. */
 #define	TAG_DELETE	(INT_MAX)	/* Tag not used at all. */
 
-
-struct	tag_files {
-	char	 ofn[20];
-	char	 tfn[20];
-	char	*tagname;
-	int	 ofd;
-	int	 tfd;
-	pid_t	 tcpgid;
-	pid_t	 pager_pid;
+/*
+ * Return values of tag_check().
+ */
+enum tag_result {
+	TAG_OK,		/* Argument exists as a tag. */
+	TAG_MISS,	/* Argument not found. */
+	TAG_EMPTY	/* No tag exists at all. */
 };
 
 
-struct tag_files *tag_init(char *);
-void	 tag_put(const char *, int, size_t);
-void	 tag_write(void);
-void	 tag_unlink(void);
+void		 tag_alloc(void);
+void		 tag_put(const char *, int, struct roff_node *);
+enum tag_result	 tag_check(const char *);
+void		 tag_free(void);
Index: mandoc_html.3
===================================================================
RCS file: /home/cvs/mandoc/mandoc/mandoc_html.3,v
retrieving revision 1.19
retrieving revision 1.20
diff -Lmandoc_html.3 -Lmandoc_html.3 -u -p -r1.19 -r1.20
--- mandoc_html.3
+++ mandoc_html.3
@@ -53,10 +53,14 @@
 .Ft char *
 .Fo html_make_id
 .Fa "const struct roff_node *n"
+.Fa "int unique"
 .Fc
-.Ft int
-.Fo html_strlen
-.Fa "const char *cp"
+.Ft struct tag *
+.Fo print_otag_id
+.Fa "struct html *h"
+.Fa "enum htmltag tag"
+.Fa "const char *cattr"
+.Fa "struct roff_node *n"
 .Fc
 .Sh DESCRIPTION
 The mandoc HTML formatter is not a formal library.
@@ -257,23 +261,77 @@ functions.
 .Pp
 The function
 .Fn html_make_id
-takes a node containing one or more text children
-and returns a newly allocated string containing the concatenation
-of the child strings, with blanks replaced by underscores.
-If the node
+allocates a string to be used for the
+.Cm id
+attribute of an HTML element and/or as a segment identifier for a URI in an
+.Aq Ic A
+element.
+If
 .Fa n
-contains any non-text child node,
-.Fn html_make_id
-returns
+contains a
+.Fa string
+attribute, it is used; otherwise, child nodes are used.
+If
+.Fa n
+is an
+.Ic \&Sh ,
+.Ic \&Ss ,
+.Ic \&Sx ,
+.Ic SH ,
+or
+.Ic SS
+node, the resulting string is the concatenation of the child strings;
+for other node types, only the first child is used.
+Bytes not permitted in URI-fragment strings are replaced by underscores.
+If any of the children to be used is not a text node,
+no string is generated and
 .Dv NULL
-instead.
-The caller is responsible for freeing the returned string.
+is returned instead.
+If the
+.Fa unique
+argument is non-zero, deduplication is performed by appending an
+underscore and a decimal integer, if necessary.
 .Pp
 The function
-.Fn html_strlen
-counts the number of characters in
-.Fa cp .
-It is used as a crude estimate of the width needed to display a string.
+.Fn print_otag_id
+opens a
+.Fa tag
+element of class
+.Fa cattr
+for the node
+.Fa n .
+If the flag
+.Dv NODE_ID
+is set in
+.Fa n ,
+it attempts to generate an
+.Cm id
+attribute with
+.Fn html_make_id .
+If an
+.Cm id
+attribute is written,
+.Fn print_otag_id
+also adds an
+.Aq Ic A
+element of class
+.Qq permalink :
+outside if
+.Fa n
+generates a phrasing element, or inside otherwise.
+This function is a wrapper around
+.Fn html_make_id
+and
+.Fn print_otag ,
+fixing the
+.Fa unique
+argument to 1 and the
+.Fa fmt
+arguments to
+.Qq chR
+and
+.Qq ci ,
+respectively.
 .Pp
 The functions
 .Fn print_eqn ,
@@ -281,6 +339,49 @@ The functions
 and
 .Fn print_tblclose
 are not yet documented.
+.Sh RETURN VALUES
+The functions
+.Fn print_otag
+and
+.Fn print_otag_id
+return a pointer to a new element on the stack of HTML elements.
+When
+.Fn print_otag_id
+opens two elements, a pointer to the outer one is returned.
+The memory pointed to is owned by the library and is automatically
+.Xr free 3 Ns d
+when
+.Fn print_tagq
+is called on it or when
+.Fn print_stagq
+is called on a parent element.
+.Pp
+The function
+.Fn html_make_id
+returns a newly allocated string or
+.Dv NULL
+if
+.Fa n
+lacks text data to create the attribute from.
+If the
+.Fa unique
+argument is 0, the caller is responsible for
+.Xr free 3 Ns ing
+the returned string after using it.
+If the
+.Fa unique
+argument is non-zero, the
+.Va id_unique
+ohash table is used for de-duplication and owns the returned string.
+In this case, it will be freed automatically by
+.Fn html_reset
+or
+.Fn html_free .
+.Pp
+In case of
+.Xr malloc 3
+failure, these functions do not return but call
+.Xr err 3 .
 .Sh FILES
 .Bl -tag -width mandoc_aux.c -compact
 .It Pa main.h
Index: html.h
===================================================================
RCS file: /home/cvs/mandoc/mandoc/html.h,v
retrieving revision 1.106
retrieving revision 1.107
diff -Lhtml.h -Lhtml.h -u -p -r1.106 -r1.107
--- html.h
+++ html.h
@@ -1,7 +1,7 @@
-/*	$Id$ */
+/* $Id$ */
 /*
+ * Copyright (c) 2017, 2018, 2019, 2020 Ingo Schwarze <schwarze@openbsd.org>
  * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
- * Copyright (c) 2017, 2018, 2019 Ingo Schwarze <schwarze@openbsd.org>
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -14,6 +14,9 @@
  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Internal interfaces for mandoc(1) HTML formatters.
+ * For use by the individual HTML formatters only.
  */
 
 enum	htmltag {
@@ -120,6 +123,8 @@ void		  print_gen_comment(struct html *,
 void		  print_gen_decls(struct html *);
 void		  print_gen_head(struct html *);
 struct tag	 *print_otag(struct html *, enum htmltag, const char *, ...);
+struct tag	 *print_otag_id(struct html *, enum htmltag, const char *,
+			struct roff_node *);
 void		  print_tagq(struct html *, const struct tag *);
 void		  print_stagq(struct html *, const struct tag *);
 void		  print_text(struct html *, const char *);
Index: tag.c
===================================================================
RCS file: /home/cvs/mandoc/mandoc/tag.c,v
retrieving revision 1.27
retrieving revision 1.28
diff -Ltag.c -Ltag.c -u -p -r1.27 -r1.28
--- tag.c
+++ tag.c
@@ -1,4 +1,4 @@
-/*	$Id$ */
+/* $Id$ */
 /*
  * Copyright (c) 2015,2016,2018,2019,2020 Ingo Schwarze <schwarze@openbsd.org>
  *
@@ -13,134 +13,67 @@
  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Functions to tag syntax tree nodes.
+ * For internal use by mandoc(1) validation modules only.
  */
 #include "config.h"
 
 #include <sys/types.h>
 
 #include <assert.h>
-#include <errno.h>
 #include <limits.h>
-#include <signal.h>
 #include <stddef.h>
-#include <stdint.h>
-#include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <unistd.h>
 
 #include "mandoc_aux.h"
 #include "mandoc_ohash.h"
-#include "mandoc.h"
+#include "roff.h"
 #include "tag.h"
 
 struct tag_entry {
-	size_t	*lines;
-	size_t	 maxlines;
-	size_t	 nlines;
+	struct roff_node **nodes;
+	size_t	 maxnodes;
+	size_t	 nnodes;
 	int	 prio;
 	char	 s[];
 };
 
-static	void	 tag_signal(int) __attribute__((__noreturn__));
-
 static struct ohash	 tag_data;
-static struct tag_files	 tag_files;
 
 
 /*
- * Prepare for using a pager.
- * Not all pagers are capable of using a tag file,
- * but for simplicity, create it anyway.
+ * Set up the ohash table to collect nodes
+ * where various marked-up terms are documented.
  */
-struct tag_files *
-tag_init(char *tagname)
+void
+tag_alloc(void)
 {
-	struct sigaction	 sa;
-	int			 ofd;
-
-	ofd = -1;
-	tag_files.tfd = -1;
-	tag_files.tcpgid = -1;
-	tag_files.tagname = tagname;
-
-	/* Clean up when dying from a signal. */
-
-	memset(&sa, 0, sizeof(sa));
-	sigfillset(&sa.sa_mask);
-	sa.sa_handler = tag_signal;
-	sigaction(SIGHUP, &sa, NULL);
-	sigaction(SIGINT, &sa, NULL);
-	sigaction(SIGTERM, &sa, NULL);
-
-	/*
-	 * POSIX requires that a process calling tcsetpgrp(3)
-	 * from the background gets a SIGTTOU signal.
-	 * In that case, do not stop.
-	 */
-
-	sa.sa_handler = SIG_IGN;
-	sigaction(SIGTTOU, &sa, NULL);
-
-	/* Save the original standard output for use by the pager. */
-
-	if ((tag_files.ofd = dup(STDOUT_FILENO)) == -1) {
-		mandoc_msg(MANDOCERR_DUP, 0, 0, "%s", strerror(errno));
-		goto fail;
-	}
+	mandoc_ohash_init(&tag_data, 4, offsetof(struct tag_entry, s));
+}
 
-	/* Create both temporary output files. */
+void
+tag_free(void)
+{
+	struct tag_entry	*entry;
+	unsigned int		 slot;
 
-	(void)strlcpy(tag_files.ofn, "/tmp/man.XXXXXXXXXX",
-	    sizeof(tag_files.ofn));
-	(void)strlcpy(tag_files.tfn, "/tmp/man.XXXXXXXXXX",
-	    sizeof(tag_files.tfn));
-	if ((ofd = mkstemp(tag_files.ofn)) == -1) {
-		mandoc_msg(MANDOCERR_MKSTEMP, 0, 0,
-		    "%s: %s", tag_files.ofn, strerror(errno));
-		goto fail;
-	}
-	if ((tag_files.tfd = mkstemp(tag_files.tfn)) == -1) {
-		mandoc_msg(MANDOCERR_MKSTEMP, 0, 0,
-		    "%s: %s", tag_files.tfn, strerror(errno));
-		goto fail;
-	}
-	if (dup2(ofd, STDOUT_FILENO) == -1) {
-		mandoc_msg(MANDOCERR_DUP, 0, 0, "%s", strerror(errno));
-		goto fail;
+	entry = ohash_first(&tag_data, &slot);
+	while (entry != NULL) {
+		free(entry->nodes);
+		free(entry);
+		entry = ohash_next(&tag_data, &slot);
 	}
-	close(ofd);
-
-	/*
-	 * Set up the ohash table to collect output line numbers
-	 * where various marked-up terms are documented.
-	 */
-
-	mandoc_ohash_init(&tag_data, 4, offsetof(struct tag_entry, s));
-	return &tag_files;
-
-fail:
-	tag_unlink();
-	if (ofd != -1)
-		close(ofd);
-	if (tag_files.ofd != -1)
-		close(tag_files.ofd);
-	if (tag_files.tfd != -1)
-		close(tag_files.tfd);
-	*tag_files.ofn = '\0';
-	*tag_files.tfn = '\0';
-	tag_files.ofd = -1;
-	tag_files.tfd = -1;
-	tag_files.tagname = NULL;
-	return NULL;
+	ohash_delete(&tag_data);
 }
 
 /*
- * Set the line number where a term is defined,
+ * Set a node where a term is defined,
  * unless it is already defined at a lower priority.
  */
 void
-tag_put(const char *s, int prio, size_t line)
+tag_put(const char *s, int prio, struct roff_node *n)
 {
 	struct tag_entry	*entry;
 	const char		*se;
@@ -148,11 +81,14 @@ tag_put(const char *s, int prio, size_t 
 	unsigned int		 slot;
 
 	assert(prio <= TAG_FALLBACK);
-	if (tag_files.tfd <= 0)
-		return;
 
-	if (s[0] == '\\' && (s[1] == '&' || s[1] == 'e'))
-		s += 2;
+	if (s == NULL) {
+		if (n->child == NULL || n->child->type != ROFFT_TEXT)
+			return;
+		s = n->child->string;
+		if (s[0] == '\\' && (s[1] == '&' || s[1] == 'e'))
+			s += 2;
+	}
 
 	/*
 	 * Skip whitespace and escapes and whatever follows,
@@ -170,131 +106,67 @@ tag_put(const char *s, int prio, size_t 
 	slot = ohash_qlookupi(&tag_data, s, &se);
 	entry = ohash_find(&tag_data, slot);
 
-	if (entry == NULL) {
-
-		/* Build a new entry. */
+	/* Build a new entry. */
 
+	if (entry == NULL) {
 		entry = mandoc_malloc(sizeof(*entry) + len + 1);
 		memcpy(entry->s, s, len);
 		entry->s[len] = '\0';
-		entry->lines = NULL;
-		entry->maxlines = entry->nlines = 0;
+		entry->nodes = NULL;
+		entry->maxnodes = entry->nnodes = 0;
 		ohash_insert(&tag_data, slot, entry);
+	}
 
-	} else {
-
-		/*
-		 * Lower priority numbers take precedence,
-		 * but TAG_FALLBACK is special.
-		 * A tag with priority TAG_FALLBACK is only used
-		 * if the tag occurs exactly once.
-		 */
+	/*
+	 * Lower priority numbers take precedence.
+	 * If a better entry is already present, ignore the new one.
+	 */
 
-		if (prio == TAG_FALLBACK) {
-			if (entry->prio == TAG_FALLBACK)
-				entry->prio = TAG_DELETE;
+	else if (entry->prio < prio)
 			return;
-		}
-
-		/* A better entry is already present, ignore the new one. */
 
-		if (entry->prio < prio)
-			return;
+	/*
+	 * If the existing entry is worse, clear it.
+	 * In addition, a tag with priority TAG_FALLBACK
+	 * is only used if the tag occurs exactly once.
+	 */
 
-		/* The existing entry is worse, clear it. */
+	else if (entry->prio > prio || prio == TAG_FALLBACK) {
+		while (entry->nnodes > 0)
+			entry->nodes[--entry->nnodes]->flags &= ~NODE_ID;
 
-		if (entry->prio > prio)
-			entry->nlines = 0;
+		if (prio == TAG_FALLBACK) {
+			entry->prio = TAG_DELETE;
+			return;
+		}
 	}
 
-	/* Remember the new line. */
+	/* Remember the new node. */
 
-	if (entry->maxlines == entry->nlines) {
-		entry->maxlines += 4;
-		entry->lines = mandoc_reallocarray(entry->lines,
-		    entry->maxlines, sizeof(*entry->lines));
+	if (entry->maxnodes == entry->nnodes) {
+		entry->maxnodes += 4;
+		entry->nodes = mandoc_reallocarray(entry->nodes,
+		    entry->maxnodes, sizeof(*entry->nodes));
 	}
-	entry->lines[entry->nlines++] = line;
+	entry->nodes[entry->nnodes++] = n;
 	entry->prio = prio;
-}
-
-/*
- * Write out the tags file using the previously collected
- * information and clear the ohash table while going along.
- */
-void
-tag_write(void)
-{
-	FILE			*stream;
-	struct tag_entry	*entry;
-	size_t			 i;
-	unsigned int		 slot;
-	int			 empty;
-
-	if (tag_files.tfd <= 0)
-		return;
-	if (tag_files.tagname != NULL && ohash_find(&tag_data,
-            ohash_qlookup(&tag_data, tag_files.tagname)) == NULL) {
-		mandoc_msg(MANDOCERR_TAG, 0, 0, "%s", tag_files.tagname);
-		tag_files.tagname = NULL;
-	}
-	if ((stream = fdopen(tag_files.tfd, "w")) == NULL)
-		mandoc_msg(MANDOCERR_FDOPEN, 0, 0, "%s", strerror(errno));
-	empty = 1;
-	entry = ohash_first(&tag_data, &slot);
-	while (entry != NULL) {
-		if (stream != NULL && entry->prio < TAG_DELETE) {
-			for (i = 0; i < entry->nlines; i++) {
-				fprintf(stream, "%s %s %zu\n",
-				    entry->s, tag_files.ofn, entry->lines[i]);
-				empty = 0;
-			}
-		}
-		free(entry->lines);
-		free(entry);
-		entry = ohash_next(&tag_data, &slot);
-	}
-	ohash_delete(&tag_data);
-	if (stream != NULL)
-		fclose(stream);
-	else
-		close(tag_files.tfd);
-	tag_files.tfd = -1;
-	if (empty) {
-		unlink(tag_files.tfn);
-		*tag_files.tfn = '\0';
+	n->flags |= NODE_ID;
+	if (n->child == NULL || n->child->string != s || *se != '\0') {
+		assert(n->string == NULL);
+		n->string = mandoc_strndup(s, len);
 	}
 }
 
-void
-tag_unlink(void)
+enum tag_result
+tag_check(const char *test_tag)
 {
-	pid_t	 tc_pgid;
+	unsigned int slot;
 
-	if (tag_files.tcpgid != -1) {
-		tc_pgid = tcgetpgrp(tag_files.ofd);
-		if (tc_pgid == tag_files.pager_pid ||
-		    tc_pgid == getpgid(0) ||
-		    getpgid(tc_pgid) == -1)
-			(void)tcsetpgrp(tag_files.ofd, tag_files.tcpgid);
-	}
-	if (*tag_files.ofn != '\0')
-		unlink(tag_files.ofn);
-	if (*tag_files.tfn != '\0')
-		unlink(tag_files.tfn);
-}
-
-static void
-tag_signal(int signum)
-{
-	struct sigaction	 sa;
-
-	tag_unlink();
-	memset(&sa, 0, sizeof(sa));
-	sigemptyset(&sa.sa_mask);
-	sa.sa_handler = SIG_DFL;
-	sigaction(signum, &sa, NULL);
-	kill(getpid(), signum);
-	/* NOTREACHED */
-	_exit(1);
+	if (ohash_first(&tag_data, &slot) == NULL)
+		return TAG_EMPTY;
+	else if (test_tag != NULL && ohash_find(&tag_data,
+	    ohash_qlookup(&tag_data, test_tag)) == NULL)
+		return TAG_MISS;
+	else
+		return TAG_OK;
 }
Index: mdoc_html.c
===================================================================
RCS file: /home/cvs/mandoc/mandoc/mdoc_html.c,v
retrieving revision 1.335
retrieving revision 1.336
diff -Lmdoc_html.c -Lmdoc_html.c -u -p -r1.335 -r1.336
--- mdoc_html.c
+++ mdoc_html.c
@@ -1,7 +1,7 @@
-/*	$Id$ */
+/* $Id$ */
 /*
- * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
  * Copyright (c) 2014-2020 Ingo Schwarze <schwarze@openbsd.org>
+ * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -14,6 +14,8 @@
  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * HTML formatter for mdoc(7) used by mandoc(1).
  */
 #include "config.h"
 
@@ -47,7 +49,6 @@ struct	mdoc_html_act {
 	void		(*post)(MDOC_ARGS);
 };
 
-static	char		 *cond_id(const struct roff_node *);
 static	void		  print_mdoc_head(const struct roff_meta *,
 				struct html *);
 static	void		  print_mdoc_node(MDOC_ARGS);
@@ -72,9 +73,8 @@ static	void		  mdoc_bk_post(MDOC_ARGS);
 static	int		  mdoc_bk_pre(MDOC_ARGS);
 static	int		  mdoc_bl_pre(MDOC_ARGS);
 static	int		  mdoc_cd_pre(MDOC_ARGS);
-static	int		  mdoc_cm_pre(MDOC_ARGS);
+static	int		  mdoc_code_pre(MDOC_ARGS);
 static	int		  mdoc_d1_pre(MDOC_ARGS);
-static	int		  mdoc_dv_pre(MDOC_ARGS);
 static	int		  mdoc_fa_pre(MDOC_ARGS);
 static	int		  mdoc_fd_pre(MDOC_ARGS);
 static	int		  mdoc_fl_pre(MDOC_ARGS);
@@ -83,20 +83,15 @@ static	int		  mdoc_ft_pre(MDOC_ARGS);
 static	int		  mdoc_em_pre(MDOC_ARGS);
 static	void		  mdoc_eo_post(MDOC_ARGS);
 static	int		  mdoc_eo_pre(MDOC_ARGS);
-static	int		  mdoc_er_pre(MDOC_ARGS);
-static	int		  mdoc_ev_pre(MDOC_ARGS);
 static	int		  mdoc_ex_pre(MDOC_ARGS);
 static	void		  mdoc_fo_post(MDOC_ARGS);
 static	int		  mdoc_fo_pre(MDOC_ARGS);
-static	int		  mdoc_ic_pre(MDOC_ARGS);
 static	int		  mdoc_igndelim_pre(MDOC_ARGS);
 static	int		  mdoc_in_pre(MDOC_ARGS);
 static	int		  mdoc_it_pre(MDOC_ARGS);
 static	int		  mdoc_lb_pre(MDOC_ARGS);
-static	int		  mdoc_li_pre(MDOC_ARGS);
 static	int		  mdoc_lk_pre(MDOC_ARGS);
 static	int		  mdoc_mt_pre(MDOC_ARGS);
-static	int		  mdoc_ms_pre(MDOC_ARGS);
 static	int		  mdoc_nd_pre(MDOC_ARGS);
 static	int		  mdoc_nm_pre(MDOC_ARGS);
 static	int		  mdoc_no_pre(MDOC_ARGS);
@@ -139,19 +134,19 @@ static const struct mdoc_html_act mdoc_h
 	{mdoc_ap_pre, NULL}, /* Ap */
 	{mdoc_ar_pre, NULL}, /* Ar */
 	{mdoc_cd_pre, NULL}, /* Cd */
-	{mdoc_cm_pre, NULL}, /* Cm */
-	{mdoc_dv_pre, NULL}, /* Dv */
-	{mdoc_er_pre, NULL}, /* Er */
-	{mdoc_ev_pre, NULL}, /* Ev */
+	{mdoc_code_pre, NULL}, /* Cm */
+	{mdoc_code_pre, NULL}, /* Dv */
+	{mdoc_code_pre, NULL}, /* Er */
+	{mdoc_code_pre, NULL}, /* Ev */
 	{mdoc_ex_pre, NULL}, /* Ex */
 	{mdoc_fa_pre, NULL}, /* Fa */
 	{mdoc_fd_pre, NULL}, /* Fd */
 	{mdoc_fl_pre, NULL}, /* Fl */
 	{mdoc_fn_pre, NULL}, /* Fn */
 	{mdoc_ft_pre, NULL}, /* Ft */
-	{mdoc_ic_pre, NULL}, /* Ic */
+	{mdoc_code_pre, NULL}, /* Ic */
 	{mdoc_in_pre, NULL}, /* In */
-	{mdoc_li_pre, NULL}, /* Li */
+	{mdoc_code_pre, NULL}, /* Li */
 	{mdoc_nd_pre, NULL}, /* Nd */
 	{mdoc_nm_pre, NULL}, /* Nm */
 	{mdoc_quote_pre, mdoc_quote_post}, /* Op */
@@ -192,7 +187,7 @@ static const struct mdoc_html_act mdoc_h
 	{mdoc_em_pre, NULL}, /* Em */
 	{mdoc_eo_pre, mdoc_eo_post}, /* Eo */
 	{mdoc_xx_pre, NULL}, /* Fx */
-	{mdoc_ms_pre, NULL}, /* Ms */
+	{mdoc_no_pre, NULL}, /* Ms */
 	{mdoc_no_pre, NULL}, /* No */
 	{mdoc_ns_pre, NULL}, /* Ns */
 	{mdoc_xx_pre, NULL}, /* Nx */
@@ -507,20 +502,11 @@ mdoc_root_pre(const struct roff_meta *me
 	return 1;
 }
 
-static char *
-cond_id(const struct roff_node *n)
+static int
+mdoc_code_pre(MDOC_ARGS)
 {
-	if (n->child != NULL &&
-	    n->child->type == ROFFT_TEXT &&
-	    (n->prev == NULL ||
-	     (n->prev->type == ROFFT_TEXT &&
-	      strcmp(n->prev->string, "|") == 0)) &&
-	    (n->parent->tok == MDOC_It ||
-	     (n->parent->tok == MDOC_Xo &&
-	      n->parent->parent->prev == NULL &&
-	      n->parent->parent->parent->tok == MDOC_It)))
-		return html_make_id(n, 1);
-	return NULL;
+	print_otag_id(h, TAG_CODE, roff_name[n->tok], n);
+	return 1;
 }
 
 static int
@@ -583,10 +569,8 @@ mdoc_sh_pre(MDOC_ARGS)
 		print_otag(h, TAG_SECTION, "c", "Sh");
 		break;
 	case ROFFT_HEAD:
-		id = html_make_id(n, 1);
-		print_otag(h, TAG_H1, "ci", "Sh", id);
-		if (id != NULL)
-			print_otag(h, TAG_A, "chR", "permalink", id);
+		n->flags |= NODE_ID;
+		print_otag_id(h, TAG_H1, "Sh", n);
 		break;
 	case ROFFT_BODY:
 		if (n->sec == SEC_AUTHORS)
@@ -601,25 +585,20 @@ mdoc_sh_pre(MDOC_ARGS)
 static int
 mdoc_ss_pre(MDOC_ARGS)
 {
-	char	*id;
-
 	switch (n->type) {
 	case ROFFT_BLOCK:
 		html_close_paragraph(h);
 		print_otag(h, TAG_SECTION, "c", "Ss");
-		return 1;
+		break;
 	case ROFFT_HEAD:
+		n->flags |= NODE_ID;
+		print_otag_id(h, TAG_H2, "Ss", n);
 		break;
 	case ROFFT_BODY:
-		return 1;
+		break;
 	default:
 		abort();
 	}
-
-	id = html_make_id(n, 1);
-	print_otag(h, TAG_H2, "ci", "Ss", id);
-	if (id != NULL)
-		print_otag(h, TAG_A, "chR", "permalink", id);
 	return 1;
 }
 
@@ -627,12 +606,8 @@ static int
 mdoc_fl_pre(MDOC_ARGS)
 {
 	struct roff_node	*nn;
-	char			*id;
-
-	if ((id = cond_id(n)) != NULL)
-		print_otag(h, TAG_A, "chR", "permalink", id);
-	print_otag(h, TAG_CODE, "ci", "Fl", id);
 
+	print_otag_id(h, TAG_CODE, "Fl", n);
 	print_text(h, "\\-");
 	if (n->child != NULL ||
 	    ((nn = roff_node_next(n)) != NULL &&
@@ -644,17 +619,6 @@ mdoc_fl_pre(MDOC_ARGS)
 }
 
 static int
-mdoc_cm_pre(MDOC_ARGS)
-{
-	char	*id;
-
-	if ((id = cond_id(n)) != NULL)
-		print_otag(h, TAG_A, "chR", "permalink", id);
-	print_otag(h, TAG_CODE, "ci", "Cm", id);
-	return 1;
-}
-
-static int
 mdoc_nd_pre(MDOC_ARGS)
 {
 	switch (n->type) {
@@ -926,7 +890,7 @@ mdoc_st_pre(MDOC_ARGS)
 static int
 mdoc_em_pre(MDOC_ARGS)
 {
-	print_otag(h, TAG_I, "c", "Em");
+	print_otag_id(h, TAG_I, "Em", n);
 	return 1;
 }
 
@@ -1052,45 +1016,6 @@ mdoc_cd_pre(MDOC_ARGS)
 }
 
 static int
-mdoc_dv_pre(MDOC_ARGS)
-{
-	char	*id;
-
-	if ((id = cond_id(n)) != NULL)
-		print_otag(h, TAG_A, "chR", "permalink", id);
-	print_otag(h, TAG_CODE, "ci", "Dv", id);
-	return 1;
-}
-
-static int
-mdoc_ev_pre(MDOC_ARGS)
-{
-	char	*id;
-
-	if ((id = cond_id(n)) != NULL)
-		print_otag(h, TAG_A, "chR", "permalink", id);
-	print_otag(h, TAG_CODE, "ci", "Ev", id);
-	return 1;
-}
-
-static int
-mdoc_er_pre(MDOC_ARGS)
-{
-	char	*id;
-
-	id = n->sec == SEC_ERRORS &&
-	    (n->parent->tok == MDOC_It ||
-	     (n->parent->tok == MDOC_Bq &&
-	      n->parent->parent->parent->tok == MDOC_It)) ?
-	    html_make_id(n, 1) : NULL;
-
-	if (id != NULL)
-		print_otag(h, TAG_A, "chR", "permalink", id);
-	print_otag(h, TAG_CODE, "ci", "Er", id);
-	return 1;
-}
-
-static int
 mdoc_fa_pre(MDOC_ARGS)
 {
 	const struct roff_node	*nn;
@@ -1222,7 +1147,7 @@ mdoc_fn_pre(MDOC_ARGS)
 		print_tagq(h, t);
 	}
 
-	t = print_otag(h, TAG_CODE, "c", "Fn");
+	t = print_otag_id(h, TAG_CODE, "Fn", n);
 
 	if (sp)
 		print_text(h, sp);
@@ -1341,14 +1266,12 @@ mdoc_mt_pre(MDOC_ARGS)
 
 	for (n = n->child; n; n = n->next) {
 		assert(n->type == ROFFT_TEXT);
-
 		mandoc_asprintf(&cp, "mailto:%s", n->string);
 		t = print_otag(h, TAG_A, "ch", "Mt", cp);
 		print_text(h, n->string);
 		print_tagq(h, t);
 		free(cp);
 	}
-
 	return 0;
 }
 
@@ -1357,30 +1280,30 @@ mdoc_fo_pre(MDOC_ARGS)
 {
 	struct tag	*t;
 
-	if (n->type == ROFFT_BODY) {
+	switch (n->type) {
+	case ROFFT_BLOCK:
+		synopsis_pre(h, n);
+		return 1;
+	case ROFFT_HEAD:
+		if (n->child != NULL) {
+			t = print_otag_id(h, TAG_CODE, "Fn", n);
+			print_text(h, n->child->string);
+			print_tagq(h, t);
+		}
+		return 0;
+	case ROFFT_BODY:
 		h->flags |= HTML_NOSPACE;
 		print_text(h, "(");
 		h->flags |= HTML_NOSPACE;
 		return 1;
-	} else if (n->type == ROFFT_BLOCK) {
-		synopsis_pre(h, n);
-		return 1;
+	default:
+		abort();
 	}
-
-	if (n->child == NULL)
-		return 0;
-
-	assert(n->child->string);
-	t = print_otag(h, TAG_CODE, "c", "Fn");
-	print_text(h, n->child->string);
-	print_tagq(h, t);
-	return 0;
 }
 
 static void
 mdoc_fo_post(MDOC_ARGS)
 {
-
 	if (n->type != ROFFT_BODY)
 		return;
 	h->flags |= HTML_NOSPACE;
@@ -1430,22 +1353,10 @@ mdoc_in_pre(MDOC_ARGS)
 		assert(n->type == ROFFT_TEXT);
 		print_text(h, n->string);
 	}
-
 	return 0;
 }
 
 static int
-mdoc_ic_pre(MDOC_ARGS)
-{
-	char	*id;
-
-	if ((id = cond_id(n)) != NULL)
-		print_otag(h, TAG_A, "chR", "permalink", id);
-	print_otag(h, TAG_CODE, "ci", "Ic", id);
-	return 1;
-}
-
-static int
 mdoc_va_pre(MDOC_ARGS)
 {
 	print_otag(h, TAG_VAR, "c", "Va");
@@ -1455,7 +1366,6 @@ mdoc_va_pre(MDOC_ARGS)
 static int
 mdoc_ap_pre(MDOC_ARGS)
 {
-
 	h->flags |= HTML_NOSPACE;
 	print_text(h, "\\(aq");
 	h->flags |= HTML_NOSPACE;
@@ -1494,20 +1404,8 @@ mdoc_bf_pre(MDOC_ARGS)
 }
 
 static int
-mdoc_ms_pre(MDOC_ARGS)
-{
-	char *id;
-
-	if ((id = cond_id(n)) != NULL)
-		print_otag(h, TAG_A, "chR", "permalink", id);
-	print_otag(h, TAG_SPAN, "ci", "Ms", id);
-	return 1;
-}
-
-static int
 mdoc_igndelim_pre(MDOC_ARGS)
 {
-
 	h->flags |= HTML_IGNDELIM;
 	return 1;
 }
@@ -1515,7 +1413,6 @@ mdoc_igndelim_pre(MDOC_ARGS)
 static void
 mdoc_pf_post(MDOC_ARGS)
 {
-
 	if ( ! (n->next == NULL || n->next->flags & NODE_LINE))
 		h->flags |= HTML_NOSPACE;
 }
@@ -1544,29 +1441,14 @@ mdoc_rs_pre(MDOC_ARGS)
 static int
 mdoc_no_pre(MDOC_ARGS)
 {
-	char *id;
-
-	if ((id = cond_id(n)) != NULL)
-		print_otag(h, TAG_A, "chR", "permalink", id);
-	print_otag(h, TAG_SPAN, "ci", "No", id);
-	return 1;
-}
-
-static int
-mdoc_li_pre(MDOC_ARGS)
-{
-	char	*id;
-
-	if ((id = cond_id(n)) != NULL)
-		print_otag(h, TAG_A, "chR", "permalink", id);
-	print_otag(h, TAG_CODE, "ci", "Li", id);
+	print_otag_id(h, TAG_SPAN, roff_name[n->tok], n);
 	return 1;
 }
 
 static int
 mdoc_sy_pre(MDOC_ARGS)
 {
-	print_otag(h, TAG_B, "c", "Sy");
+	print_otag_id(h, TAG_B, "Sy", n);
 	return 1;
 }
 
Index: tree.c
===================================================================
RCS file: /home/cvs/mandoc/mandoc/tree.c,v
retrieving revision 1.86
retrieving revision 1.87
diff -Ltree.c -Ltree.c -u -p -r1.86 -r1.87
--- tree.c
+++ tree.c
@@ -1,7 +1,7 @@
-/*	$Id$ */
+/* $Id$ */
 /*
- * Copyright (c) 2008, 2009, 2011, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
  * Copyright (c) 2013-2015, 2017-2020 Ingo Schwarze <schwarze@openbsd.org>
+ * Copyright (c) 2008, 2009, 2011, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -14,6 +14,9 @@
  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Formatting module to let mandoc(1) show
+ * a human readable representation of the syntax tree.
  */
 #include "config.h"
 
@@ -305,6 +308,11 @@ print_man(const struct roff_node *n, int
 			putchar(')');
 		if (n->flags & NODE_EOS)
 			putchar('.');
+		if (n->flags & NODE_ID) {
+			printf(" ID");
+			if (n->string != NULL)
+				printf("=%s", n->string);
+		}
 		if (n->flags & NODE_NOFILL)
 			printf(" NOFILL");
 		putchar('\n');
Index: read.c
===================================================================
RCS file: /home/cvs/mandoc/mandoc/read.c,v
retrieving revision 1.214
retrieving revision 1.215
diff -Lread.c -Lread.c -u -p -r1.214 -r1.215
--- read.c
+++ read.c
@@ -1,7 +1,7 @@
-/*	$Id$ */
+/* $Id$ */
 /*
- * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
  * Copyright (c) 2010-2019 Ingo Schwarze <schwarze@openbsd.org>
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
  * Copyright (c) 2010, 2012 Joerg Sonnenberger <joerg@netbsd.org>
  *
  * Permission to use, copy, modify, and distribute this software for any
@@ -15,6 +15,12 @@
  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Top-level functions of the mandoc(3) parser:
+ * Parser and input encoding selection, decompression,
+ * handling of input bytes, characters, lines, and files,
+ * handling of roff(7) loops and file inclusion,
+ * and steering of the various parsers.
  */
 #include "config.h"
 
@@ -36,6 +42,7 @@
 #include "mandoc_aux.h"
 #include "mandoc.h"
 #include "roff.h"
+#include "tag.h"
 #include "mdoc.h"
 #include "man.h"
 #include "mandoc_parse.h"
@@ -664,6 +671,7 @@ mparse_alloc(int options, enum mandoc_os
 	}
 	curp->man->meta.first->tok = TOKEN_NONE;
 	curp->man->meta.os_e = os_e;
+	tag_alloc();
 	return curp;
 }
 
@@ -680,6 +688,7 @@ mparse_reset(struct mparse *curp)
 void
 mparse_free(struct mparse *curp)
 {
+	tag_free();
 	roffhash_free(curp->man->mdocmac);
 	roffhash_free(curp->man->manmac);
 	roff_man_free(curp->man);
Index: main.c
===================================================================
RCS file: /home/cvs/mandoc/mandoc/main.c,v
retrieving revision 1.344
retrieving revision 1.345
diff -Lmain.c -Lmain.c -u -p -r1.344 -r1.345
--- main.c
+++ main.c
@@ -1,7 +1,7 @@
-/*	$Id$ */
+/* $Id$ */
 /*
- * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
  * Copyright (c) 2010-2012, 2014-2020 Ingo Schwarze <schwarze@openbsd.org>
+ * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
  * Copyright (c) 2010 Joerg Sonnenberger <joerg@netbsd.org>
  *
  * Permission to use, copy, modify, and distribute this software for any
@@ -15,6 +15,8 @@
  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Main program for mandoc(1), man(1), apropos(1), whatis(1), and help(1).
  */
 #include "config.h"
 
@@ -52,7 +54,7 @@
 #include "mdoc.h"
 #include "man.h"
 #include "mandoc_parse.h"
-#include "tag.h"
+#include "term_tag.h"
 #include "main.h"
 #include "manconf.h"
 #include "mansearch.h"
@@ -598,7 +600,6 @@ main(int argc, char *argv[])
 	 * readable: Maybe it won't be needed after all.
 	 */
 	startdir = open(".", O_RDONLY | O_DIRECTORY);
-
 	for (i = 0; i < ressz; i++) {
 		process_onefile(mp, res + i, startdir, &outst, &conf);
 		if (outst.wstop && mandoc_msg_getrc() != MANDOCLEVEL_OK)
@@ -608,7 +609,6 @@ main(int argc, char *argv[])
 		(void)fchdir(startdir);
 		close(startdir);
 	}
-
 	if (outst.outdata != NULL) {
 		switch (outst.outtype) {
 		case OUTT_HTML:
@@ -617,6 +617,7 @@ main(int argc, char *argv[])
 		case OUTT_UTF8:
 		case OUTT_LOCALE:
 		case OUTT_ASCII:
+			term_tag_finish();
 			ascii_free(outst.outdata);
 			break;
 		case OUTT_PDF:
@@ -638,9 +639,8 @@ out:
 
 	if (outst.tag_files != NULL) {
 		fclose(stdout);
-		tag_write();
 		run_pager(outst.tag_files);
-		tag_unlink();
+		term_tag_unlink();
 	} else if (outst.had_output && outst.outtype != OUTT_LINT)
 		mandoc_msg_summary();
 
@@ -831,15 +831,16 @@ process_onefile(struct mparse *mp, struc
 	} else
 		fd = STDIN_FILENO;
 
-	if (outst->use_pager) {
-		outst->use_pager = 0;
-		outst->tag_files = tag_init(conf->output.tag);
-	}
-
-	if (outst->had_output && outst->outtype <= OUTT_UTF8) {
-		if (outst->outdata == NULL)
-			outdata_alloc(outst, &conf->output);
-		terminal_sepline(outst->outdata);
+	if (outst->outtype <= OUTT_UTF8) {
+		if (outst->use_pager) {
+			outst->use_pager = 0;
+			outst->tag_files = term_tag_init(conf->output.tag);
+		}
+		if (outst->had_output) {
+			if (outst->outdata == NULL)
+				outdata_alloc(outst, &conf->output);
+			terminal_sepline(outst->outdata);
+		}
 	}
 
 	if (resp->form == FORM_SRC)
@@ -853,7 +854,7 @@ process_onefile(struct mparse *mp, struc
 		if (outst->tag_files != NULL) {
 			mandoc_msg(MANDOCERR_WRITE, 0, 0, "%s: %s",
 			    outst->tag_files->ofn, strerror(errno));
-			tag_unlink();
+			term_tag_unlink();
 			outst->tag_files = NULL;
 		} else
 			mandoc_msg(MANDOCERR_WRITE, 0, 0, "%s",
@@ -1278,7 +1279,7 @@ spawn_pager(struct tag_files *tag_files)
 		_exit(mandoc_msg_getrc());
 	}
 	close(tag_files->ofd);
-	assert(tag_files->tfd == -1);
+	assert(tag_files->tfs == NULL);
 
 	/* Do not start the pager before controlling the terminal. */
 
Index: man_validate.c
===================================================================
RCS file: /home/cvs/mandoc/mandoc/man_validate.c,v
retrieving revision 1.150
retrieving revision 1.151
diff -Lman_validate.c -Lman_validate.c -u -p -r1.150 -r1.151
--- man_validate.c
+++ man_validate.c
@@ -1,7 +1,7 @@
-/*	$Id$ */
+/* $Id$ */
 /*
- * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
  * Copyright (c) 2010, 2012-2020 Ingo Schwarze <schwarze@openbsd.org>
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -14,6 +14,8 @@
  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Validation module for man(7) syntax trees used by mandoc(1).
  */
 #include "config.h"
 
@@ -32,6 +34,7 @@
 #include "mandoc_aux.h"
 #include "mandoc.h"
 #include "roff.h"
+#include "tag.h"
 #include "man.h"
 #include "libmandoc.h"
 #include "roff_int.h"
@@ -45,6 +48,7 @@ static	void	  check_abort(CHKARGS) __att
 static	void	  check_par(CHKARGS);
 static	void	  check_part(CHKARGS);
 static	void	  check_root(CHKARGS);
+static	void	  check_tag(struct roff_node *, struct roff_node *);
 static	void	  check_text(CHKARGS);
 
 static	void	  post_AT(CHKARGS);
@@ -54,6 +58,7 @@ static	void	  post_IP(CHKARGS);
 static	void	  post_OP(CHKARGS);
 static	void	  post_SH(CHKARGS);
 static	void	  post_TH(CHKARGS);
+static	void	  post_TP(CHKARGS);
 static	void	  post_UC(CHKARGS);
 static	void	  post_UR(CHKARGS);
 static	void	  post_in(CHKARGS);
@@ -62,8 +67,8 @@ static	const v_check man_valids[MAN_MAX 
 	post_TH,    /* TH */
 	post_SH,    /* SH */
 	post_SH,    /* SS */
-	NULL,       /* TP */
-	NULL,       /* TQ */
+	post_TP,    /* TP */
+	post_TP,    /* TQ */
 	check_abort,/* LP */
 	check_par,  /* PP */
 	check_abort,/* P */
@@ -201,6 +206,66 @@ check_abort(CHKARGS)
 	abort();
 }
 
+/*
+ * Skip leading whitespace, dashes, backslashes, and font escapes,
+ * then create a tag if the first following byte is a letter.
+ * Priority is high unless whitespace is present.
+ */
+static void
+check_tag(struct roff_node *n, struct roff_node *nt)
+{
+	const char	*cp, *arg;
+	int		 prio, sz;
+
+	if (nt == NULL || nt->type != ROFFT_TEXT)
+		return;
+
+	cp = nt->string;
+	prio = TAG_STRONG;
+	for (;;) {
+		switch (*cp) {
+		case ' ':
+		case '\t':
+			prio = TAG_WEAK;
+			/* FALLTHROUGH */
+		case '-':
+			cp++;
+			break;
+		case '\\':
+			cp++;
+			switch (mandoc_escape(&cp, &arg, &sz)) {
+			case ESCAPE_FONT:
+			case ESCAPE_FONTBOLD:
+			case ESCAPE_FONTITALIC:
+			case ESCAPE_FONTBI:
+			case ESCAPE_FONTROMAN:
+			case ESCAPE_FONTCW:
+			case ESCAPE_FONTPREV:
+			case ESCAPE_IGNORE:
+				break;
+			case ESCAPE_SPECIAL:
+				if (sz != 1)
+					return;
+				switch (*arg) {
+				case '-':
+				case 'e':
+					break;
+				default:
+					return;
+				}
+				break;
+			default:
+				return;
+			}
+			break;
+		default:
+			if (isalpha((unsigned char)*cp))
+				tag_put(cp, prio, n);
+			return;
+		}
+	}
+}
+
 static void
 check_text(CHKARGS)
 {
@@ -332,12 +397,14 @@ check_par(CHKARGS)
 static void
 post_IP(CHKARGS)
 {
-
 	switch (n->type) {
 	case ROFFT_BLOCK:
 		if (n->head->child == NULL && n->body->child == NULL)
 			roff_node_delete(man, n);
 		break;
+	case ROFFT_HEAD:
+		check_tag(n, n->child);
+		break;
 	case ROFFT_BODY:
 		if (n->parent->head->child == NULL && n->child == NULL)
 			mandoc_msg(MANDOCERR_PAR_SKIP, n->line, n->pos,
@@ -346,6 +413,37 @@ post_IP(CHKARGS)
 	default:
 		break;
 	}
+}
+
+/*
+ * The first next-line element in the head is the tag.
+ * If that's a font macro, use its first child instead.
+ */
+static void
+post_TP(CHKARGS)
+{
+	struct roff_node *nt;
+
+	if (n->type != ROFFT_HEAD || (nt = n->child) == NULL)
+		return;
+
+	while ((nt->flags & NODE_LINE) == 0)
+		if ((nt = nt->next) == NULL)
+			return;
+
+	switch (nt->tok) {
+	case MAN_B:
+	case MAN_BI:
+	case MAN_BR:
+	case MAN_I:
+	case MAN_IB:
+	case MAN_IR:
+		nt = nt->child;
+		break;
+	default:
+		break;
+	}
+	check_tag(n, nt);
 }
 
 static void
Index: mandoc_headers.3
===================================================================
RCS file: /home/cvs/mandoc/mandoc/mandoc_headers.3,v
retrieving revision 1.32
retrieving revision 1.33
diff -Lmandoc_headers.3 -Lmandoc_headers.3 -u -p -r1.32 -r1.33
--- mandoc_headers.3
+++ mandoc_headers.3
@@ -1,6 +1,6 @@
 .\"	$Id$
 .\"
-.\" Copyright (c) 2014-2019 Ingo Schwarze <schwarze@openbsd.org>
+.\" Copyright (c) 2014-2020 Ingo Schwarze <schwarze@openbsd.org>
 .\"
 .\" Permission to use, copy, modify, and distribute this software for any
 .\" purpose with or without fee is hereby granted, provided that the above
@@ -232,6 +232,30 @@ and the functions
 .Fn mandoc_xr_get ,
 and
 .Fn mandoc_xr_free .
+.It Qq Pa tag.h
+Internal interfaces to tag syntax tree nodes,
+for use by validation modules only.
+.Pp
+Requires
+.In limits.h
+for
+.Dv INT_MAX .
+.Pp
+Provides the functions
+.Fn tag_alloc ,
+.Fn tag_put ,
+.Fn tag_check ,
+and
+.Fn tag_free
+and some
+.Dv TAG_*
+constants.
+.Pp
+Uses the type
+.Vt struct roff_node
+from
+.Qq Pa roff.h
+as an opaque type for function prototypes.
 .El
 .Pp
 The following two require
@@ -587,6 +611,33 @@ When this header is included, the same f
 .Qq Pa html.h
 or
 .Qq Pa mansearch.h .
+.It Qq Pa tag_term.h
+Requires
+.In sys/types.h
+for
+.Vt size_t
+and
+.In stdio.h
+for
+.Vt FILE .
+.Pp
+Provides an interface to generate
+.Xr ctags 1
+files for the
+.Ic :t
+functionality mentioned in
+.Xr man 1 .
+.Pp
+Uses the type
+.Vt struct roff_node
+from
+.Qq Pa roff.h
+as an opaque type for function prototypes.
+.Pp
+When this header is included, the same file should not include
+.Qq Pa html.h
+or
+.Qq Pa mansearch.h .
 .It Qq Pa html.h
 Requires
 .In sys/types.h
@@ -629,25 +680,10 @@ from
 as opaque types for function prototypes.
 .Pp
 When this header is included, the same file should not include
-.Qq Pa term.h
+.Qq Pa term.h ,
+.Qq Pa tab_term.h ,
 or
 .Qq Pa mansearch.h .
-.It Qq Pa tag.h
-Requires
-.In sys/types.h
-for
-.Vt size_t
-and
-.In limits.h
-for
-.Dv INT_MAX .
-.Pp
-Provides an interface to generate
-.Xr ctags 1
-files for the
-.Ic :t
-functionality mentioned in
-.Xr man 1 .
 .It Qq Pa main.h
 Provides the top level steering functions for all formatters.
 .Pp
@@ -700,6 +736,7 @@ as an opaque type for function prototype
 When this header is included, the same file should not include
 .Qq Pa out.h ,
 .Qq Pa term.h ,
+.Qq Pa tab_term.h ,
 or
 .Qq Pa html.h .
 .El
Index: man_term.c
===================================================================
RCS file: /home/cvs/mandoc/mandoc/man_term.c,v
retrieving revision 1.234
retrieving revision 1.235
diff -Lman_term.c -Lman_term.c -u -p -r1.234 -r1.235
--- man_term.c
+++ man_term.c
@@ -1,7 +1,7 @@
-/*	$Id$ */
+/* $Id$ */
 /*
- * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
  * Copyright (c) 2010-2015, 2017-2020 Ingo Schwarze <schwarze@openbsd.org>
+ * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -14,6 +14,9 @@
  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Plain text formatter for man(7), used by mandoc(1)
+ * for ASCII, UTF-8, PostScript, and PDF output.
  */
 #include "config.h"
 
@@ -32,7 +35,7 @@
 #include "man.h"
 #include "out.h"
 #include "term.h"
-#include "tag.h"
+#include "term_tag.h"
 #include "main.h"
 
 #define	MAXMARGINS	  64 /* maximum number of indented scopes */
@@ -94,8 +97,6 @@ static	void		  post_SY(DECL_ARGS);
 static	void		  post_TP(DECL_ARGS);
 static	void		  post_UR(DECL_ARGS);
 
-static	void		  tag_man(struct termp *, struct roff_node *);
-
 static const struct man_term_act man_term_acts[MAN_MAX - MAN_TH] = {
 	{ NULL, NULL, 0 }, /* TH */
 	{ pre_SH, post_SH, 0 }, /* SH */
@@ -539,10 +540,8 @@ pre_IP(DECL_ARGS)
 	case ROFFT_HEAD:
 		p->tcol->offset = mt->offset;
 		p->tcol->rmargin = mt->offset + len;
-		if (n->child != NULL) {
+		if (n->child != NULL)
 			print_man_node(p, mt, n->child, meta);
-			tag_man(p, n->child);
-		}
 		return 0;
 	case ROFFT_BODY:
 		p->tcol->offset = mt->offset + len;
@@ -622,18 +621,6 @@ pre_TP(DECL_ARGS)
 		while (nn != NULL && (nn->flags & NODE_LINE) == 0)
 			nn = nn->next;
 
-		if (nn == NULL)
-			return 0;
-
-		if (nn->type == ROFFT_TEXT)
-			tag_man(p, nn);
-		else if (nn->child != NULL &&
-		    nn->child->type == ROFFT_TEXT &&
-		    (nn->tok == MAN_B || nn->tok == MAN_BI ||
-		     nn->tok == MAN_BR || nn->tok == MAN_I ||
-		     nn->tok == MAN_IB || nn->tok == MAN_IR))
-			tag_man(p, nn->child);
-
 		while (nn != NULL) {
 			print_man_node(p, mt, nn, meta);
 			nn = nn->next;
@@ -913,6 +900,9 @@ print_man_node(DECL_ARGS)
 	const struct man_term_act *act;
 	int c;
 
+	if (n->flags & NODE_ID)
+		term_tag_write(n, p->line);
+
 	switch (n->type) {
 	case ROFFT_TEXT:
 		/*
@@ -1158,61 +1148,4 @@ print_man_head(struct termp *p, const st
 		term_vspace(p);
 	}
 	free(title);
-}
-
-/*
- * Skip leading whitespace, dashes, backslashes, and font escapes,
- * then create a tag if the first following byte is a letter.
- * Priority is high unless whitespace is present.
- */
-static void
-tag_man(struct termp *p, struct roff_node *n)
-{
-	const char	*cp, *arg;
-	int		 prio, sz;
-
-	assert(n->type == ROFFT_TEXT);
-	cp = n->string;
-	prio = TAG_STRONG;
-	for (;;) {
-		switch (*cp) {
-		case ' ':
-		case '\t':
-			prio = TAG_WEAK;
-			/* FALLTHROUGH */
-		case '-':
-			cp++;
-			break;
-		case '\\':
-			cp++;
-			switch (mandoc_escape(&cp, &arg, &sz)) {
-			case ESCAPE_FONT:
-			case ESCAPE_FONTROMAN:
-			case ESCAPE_FONTITALIC:
-			case ESCAPE_FONTBOLD:
-			case ESCAPE_FONTPREV:
-			case ESCAPE_FONTBI:
-				break;
-			case ESCAPE_SPECIAL:
-				if (sz != 1)
-					return;
-				switch (*arg) {
-				case '&':
-				case '-':
-				case 'e':
-					break;
-				default:
-					return;
-				}
-				break;
-			default:
-				return;
-			}
-			break;
-		default:
-			if (isalpha((unsigned char)*cp))
-				tag_put(cp, prio, p->line);
-			return;
-		}
-	}
 }
Index: Makefile.depend
===================================================================
RCS file: /home/cvs/mandoc/mandoc/Makefile.depend,v
retrieving revision 1.45
retrieving revision 1.46
diff -LMakefile.depend -LMakefile.depend -u -p -r1.45 -r1.46
--- Makefile.depend
+++ Makefile.depend
@@ -33,12 +33,12 @@ eqn_html.o: eqn_html.c config.h mandoc.h
 eqn_term.o: eqn_term.c config.h eqn.h out.h term.h
 html.o: html.c config.h mandoc_aux.h mandoc_ohash.h compat_ohash.h mandoc.h roff.h out.h html.h manconf.h main.h
 lib.o: lib.c config.h roff.h libmdoc.h lib.in
-main.o: main.c config.h mandoc_aux.h mandoc.h mandoc_xr.h roff.h mdoc.h man.h mandoc_parse.h tag.h main.h manconf.h mansearch.h
+main.o: main.c config.h mandoc_aux.h mandoc.h mandoc_xr.h roff.h mdoc.h man.h mandoc_parse.h term_tag.h main.h manconf.h mansearch.h
 man.o: man.c config.h mandoc_aux.h mandoc.h roff.h man.h libmandoc.h roff_int.h libman.h
 man_html.o: man_html.c config.h mandoc_aux.h mandoc.h roff.h man.h out.h html.h main.h
 man_macro.o: man_macro.c config.h mandoc.h roff.h man.h libmandoc.h roff_int.h libman.h
-man_term.o: man_term.c config.h mandoc_aux.h mandoc.h roff.h man.h out.h term.h tag.h main.h
-man_validate.o: man_validate.c config.h mandoc_aux.h mandoc.h roff.h man.h libmandoc.h roff_int.h libman.h
+man_term.o: man_term.c config.h mandoc_aux.h mandoc.h roff.h man.h out.h term.h term_tag.h main.h
+man_validate.o: man_validate.c config.h mandoc_aux.h mandoc.h roff.h tag.h man.h libmandoc.h roff_int.h libman.h
 mandoc.o: mandoc.c config.h mandoc_aux.h mandoc.h roff.h libmandoc.h roff_int.h
 mandoc_aux.o: mandoc_aux.c config.h mandoc.h mandoc_aux.h
 mandoc_msg.o: mandoc_msg.c config.h mandoc.h
@@ -55,19 +55,19 @@ mdoc_macro.o: mdoc_macro.c config.h mand
 mdoc_man.o: mdoc_man.c config.h mandoc_aux.h mandoc.h roff.h mdoc.h man.h out.h main.h
 mdoc_markdown.o: mdoc_markdown.c mandoc_aux.h mandoc.h roff.h mdoc.h main.h
 mdoc_state.o: mdoc_state.c mandoc.h roff.h mdoc.h libmandoc.h roff_int.h libmdoc.h
-mdoc_term.o: mdoc_term.c config.h mandoc_aux.h roff.h mdoc.h out.h term.h tag.h main.h
-mdoc_validate.o: mdoc_validate.c config.h mandoc_aux.h mandoc.h mandoc_xr.h roff.h mdoc.h libmandoc.h roff_int.h libmdoc.h
+mdoc_term.o: mdoc_term.c config.h mandoc_aux.h roff.h mdoc.h out.h term.h term_tag.h main.h
+mdoc_validate.o: mdoc_validate.c config.h mandoc_aux.h mandoc.h mandoc_xr.h roff.h tag.h mdoc.h libmandoc.h roff_int.h libmdoc.h
 msec.o: msec.c config.h mandoc.h libmandoc.h msec.in
 out.o: out.c config.h mandoc_aux.h tbl.h out.h
 preconv.o: preconv.c config.h mandoc.h roff.h mandoc_parse.h libmandoc.h
-read.o: read.c config.h mandoc_aux.h mandoc.h roff.h mdoc.h man.h mandoc_parse.h libmandoc.h roff_int.h
+read.o: read.c config.h mandoc_aux.h mandoc.h roff.h tag.h mdoc.h man.h mandoc_parse.h libmandoc.h roff_int.h
 roff.o: roff.c config.h mandoc_aux.h mandoc_ohash.h compat_ohash.h mandoc.h roff.h mandoc_parse.h libmandoc.h roff_int.h tbl_parse.h eqn_parse.h predefs.in
 roff_html.o: roff_html.c mandoc.h roff.h out.h html.h
 roff_term.o: roff_term.c mandoc.h roff.h out.h term.h
 roff_validate.o: roff_validate.c mandoc.h roff.h libmandoc.h roff_int.h
 soelim.o: soelim.c config.h compat_stringlist.h
 st.o: st.c config.h mandoc.h roff.h libmdoc.h
-tag.o: tag.c config.h mandoc_aux.h mandoc_ohash.h compat_ohash.h mandoc.h tag.h
+tag.o: tag.c config.h mandoc_aux.h mandoc_ohash.h compat_ohash.h roff.h tag.h
 tbl.o: tbl.c config.h mandoc_aux.h mandoc.h tbl.h libmandoc.h tbl_parse.h tbl_int.h
 tbl_data.o: tbl_data.c config.h mandoc_aux.h mandoc.h tbl.h libmandoc.h tbl_int.h
 tbl_html.o: tbl_html.c config.h mandoc.h roff.h tbl.h out.h html.h
@@ -78,4 +78,5 @@ term.o: term.c config.h mandoc.h mandoc_
 term_ascii.o: term_ascii.c config.h mandoc.h mandoc_aux.h out.h term.h manconf.h main.h
 term_ps.o: term_ps.c config.h mandoc_aux.h out.h term.h manconf.h main.h
 term_tab.o: term_tab.c mandoc_aux.h out.h term.h
+term_tag.o: term_tag.c config.h mandoc.h roff.h tag.h term_tag.h
 tree.o: tree.c config.h mandoc.h roff.h mdoc.h man.h tbl.h eqn.h main.h
Index: mdoc_term.c
===================================================================
RCS file: /home/cvs/mandoc/mandoc/mdoc_term.c,v
retrieving revision 1.378
retrieving revision 1.379
diff -Lmdoc_term.c -Lmdoc_term.c -u -p -r1.378 -r1.379
--- mdoc_term.c
+++ mdoc_term.c
@@ -1,7 +1,7 @@
-/*	$Id$ */
+/* $Id$ */
 /*
- * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
  * Copyright (c) 2010, 2012-2020 Ingo Schwarze <schwarze@openbsd.org>
+ * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
  * Copyright (c) 2013 Franco Fichtner <franco@lastsummer.de>
  *
  * Permission to use, copy, modify, and distribute this software for any
@@ -15,6 +15,9 @@
  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Plain text formatter for mdoc(7), used by mandoc(1)
+ * for ASCII, UTF-8, PostScript, and PDF output.
  */
 #include "config.h"
 
@@ -33,7 +36,7 @@
 #include "mdoc.h"
 #include "out.h"
 #include "term.h"
-#include "tag.h"
+#include "term_tag.h"
 #include "main.h"
 
 struct	termpair {
@@ -89,11 +92,8 @@ static	int	  termp_bf_pre(DECL_ARGS);
 static	int	  termp_bk_pre(DECL_ARGS);
 static	int	  termp_bl_pre(DECL_ARGS);
 static	int	  termp_bold_pre(DECL_ARGS);
-static	int	  termp_cd_pre(DECL_ARGS);
 static	int	  termp_d1_pre(DECL_ARGS);
 static	int	  termp_eo_pre(DECL_ARGS);
-static	int	  termp_em_pre(DECL_ARGS);
-static	int	  termp_er_pre(DECL_ARGS);
 static	int	  termp_ex_pre(DECL_ARGS);
 static	int	  termp_fa_pre(DECL_ARGS);
 static	int	  termp_fd_pre(DECL_ARGS);
@@ -115,8 +115,6 @@ static	int	  termp_skip_pre(DECL_ARGS);
 static	int	  termp_sm_pre(DECL_ARGS);
 static	int	  termp_pp_pre(DECL_ARGS);
 static	int	  termp_ss_pre(DECL_ARGS);
-static	int	  termp_sy_pre(DECL_ARGS);
-static	int	  termp_tag_pre(DECL_ARGS);
 static	int	  termp_under_pre(DECL_ARGS);
 static	int	  termp_vt_pre(DECL_ARGS);
 static	int	  termp_xr_pre(DECL_ARGS);
@@ -140,11 +138,11 @@ static const struct mdoc_term_act mdoc_t
 	{ termp_an_pre, NULL }, /* An */
 	{ termp_ap_pre, NULL }, /* Ap */
 	{ termp_under_pre, NULL }, /* Ar */
-	{ termp_cd_pre, NULL }, /* Cd */
+	{ termp_fd_pre, NULL }, /* Cd */
 	{ termp_bold_pre, NULL }, /* Cm */
 	{ termp_li_pre, NULL }, /* Dv */
-	{ termp_er_pre, NULL }, /* Er */
-	{ termp_tag_pre, NULL }, /* Ev */
+	{ NULL, NULL }, /* Er */
+	{ NULL, NULL }, /* Ev */
 	{ termp_ex_pre, NULL }, /* Ex */
 	{ termp_fa_pre, NULL }, /* Fa */
 	{ termp_fd_pre, termp_fd_post }, /* Fd */
@@ -191,7 +189,7 @@ static const struct mdoc_term_act mdoc_t
 	{ termp_quote_pre, termp_quote_post }, /* Dq */
 	{ NULL, NULL }, /* Ec */ /* FIXME: no space */
 	{ NULL, NULL }, /* Ef */
-	{ termp_em_pre, NULL }, /* Em */
+	{ termp_under_pre, NULL }, /* Em */
 	{ termp_eo_pre, termp_eo_post }, /* Eo */
 	{ termp_xx_pre, termp_xx_post }, /* Fx */
 	{ termp_bold_pre, NULL }, /* Ms */
@@ -214,7 +212,7 @@ static const struct mdoc_term_act mdoc_t
 	{ termp_quote_pre, termp_quote_post }, /* Sq */
 	{ termp_sm_pre, NULL }, /* Sm */
 	{ termp_under_pre, NULL }, /* Sx */
-	{ termp_sy_pre, NULL }, /* Sy */
+	{ termp_bold_pre, NULL }, /* Sy */
 	{ NULL, NULL }, /* Tn */
 	{ termp_xx_pre, termp_xx_post }, /* Ux */
 	{ NULL, NULL }, /* Xc */
@@ -246,8 +244,6 @@ static const struct mdoc_term_act mdoc_t
 	{ termp_skip_pre, NULL }, /* Tg */
 };
 
-static	int	 fn_prio = TAG_STRONG;
-
 
 void
 terminal_mdoc(void *arg, const struct roff_meta *mdoc)
@@ -300,7 +296,6 @@ terminal_mdoc(void *arg, const struct ro
 static void
 print_mdoc_nodelist(DECL_ARGS)
 {
-
 	while (n != NULL) {
 		print_mdoc_node(p, pair, meta, n);
 		n = n->next;
@@ -341,8 +336,7 @@ print_mdoc_node(DECL_ARGS)
 	npair.ppair = pair;
 
 	if (n->flags & NODE_ID)
-		tag_put(n->string == NULL ? n->child->string : n->string,
-		    TAG_MANUAL, p->line);
+		term_tag_write(n, p->line);
 
 	/*
 	 * Keeps only work until the end of a line.  If a keep was
@@ -1008,24 +1002,30 @@ termp_nm_pre(DECL_ARGS)
 			p->flags |= TERMP_HANG;
 		}
 	}
-
-	term_fontpush(p, TERMFONT_BOLD);
-	return 1;
+	return termp_bold_pre(p, pair, meta, n);
 }
 
 static void
 termp_nm_post(DECL_ARGS)
 {
-
-	if (n->type == ROFFT_BLOCK) {
+	switch (n->type) {
+	case ROFFT_BLOCK:
 		p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP);
-	} else if (n->type == ROFFT_HEAD &&
-	    NULL != n->next && NULL != n->next->child) {
+		break;
+	case ROFFT_HEAD:
+		if (n->next == NULL || n->next->child == NULL)
+			break;
 		term_flushln(p);
 		p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND | TERMP_HANG);
 		p->trailspace = 0;
-	} else if (n->type == ROFFT_BODY && n->child != NULL)
-		term_flushln(p);
+		break;
+	case ROFFT_BODY:
+		if (n->child != NULL)
+			term_flushln(p);
+		break;
+	default:
+		break;
+	}
 }
 
 static int
@@ -1033,7 +1033,6 @@ termp_fl_pre(DECL_ARGS)
 {
 	struct roff_node *nn;
 
-	termp_tag_pre(p, pair, meta, n);
 	term_fontpush(p, TERMFONT_BOLD);
 	term_word(p, "\\-");
 
@@ -1219,24 +1218,22 @@ synopsis_pre(struct termp *p, struct rof
 static int
 termp_vt_pre(DECL_ARGS)
 {
-
-	if (n->type == ROFFT_ELEM) {
-		synopsis_pre(p, n);
-		return termp_under_pre(p, pair, meta, n);
-	} else if (n->type == ROFFT_BLOCK) {
+	switch (n->type) {
+	case ROFFT_ELEM:
+		return termp_ft_pre(p, pair, meta, n);
+	case ROFFT_BLOCK:
 		synopsis_pre(p, n);
 		return 1;
-	} else if (n->type == ROFFT_HEAD)
+	case ROFFT_HEAD:
 		return 0;
-
-	return termp_under_pre(p, pair, meta, n);
+	default:
+		return termp_under_pre(p, pair, meta, n);
+	}
 }
 
 static int
 termp_bold_pre(DECL_ARGS)
 {
-
-	termp_tag_pre(p, pair, meta, n);
 	term_fontpush(p, TERMFONT_BOLD);
 	return 1;
 }
@@ -1244,7 +1241,6 @@ termp_bold_pre(DECL_ARGS)
 static int
 termp_fd_pre(DECL_ARGS)
 {
-
 	synopsis_pre(p, n);
 	return termp_bold_pre(p, pair, meta, n);
 }
@@ -1252,7 +1248,6 @@ termp_fd_pre(DECL_ARGS)
 static void
 termp_fd_post(DECL_ARGS)
 {
-
 	term_newln(p);
 }
 
@@ -1273,23 +1268,14 @@ termp_sh_pre(DECL_ARGS)
 			term_vspace(p);
 		break;
 	case ROFFT_HEAD:
-		term_fontpush(p, TERMFONT_BOLD);
-		break;
+		return termp_bold_pre(p, pair, meta, n);
 	case ROFFT_BODY:
 		p->tcol->offset = term_len(p, p->defindent);
 		term_tab_set(p, NULL);
 		term_tab_set(p, "T");
 		term_tab_set(p, ".5i");
-		switch (n->sec) {
-		case SEC_DESCRIPTION:
-			fn_prio = TAG_STRONG;
-			break;
-		case SEC_AUTHORS:
+		if (n->sec == SEC_AUTHORS)
 			p->flags &= ~(TERMP_SPLIT|TERMP_NOSPLIT);
-			break;
-		default:
-			break;
-		}
 		break;
 	default:
 		break;
@@ -1300,7 +1286,6 @@ termp_sh_pre(DECL_ARGS)
 static void
 termp_sh_post(DECL_ARGS)
 {
-
 	switch (n->type) {
 	case ROFFT_HEAD:
 		term_newln(p);
@@ -1317,15 +1302,13 @@ termp_sh_post(DECL_ARGS)
 static void
 termp_lb_post(DECL_ARGS)
 {
-
-	if (SEC_LIBRARY == n->sec && NODE_LINE & n->flags)
+	if (n->sec == SEC_LIBRARY && n->flags & NODE_LINE)
 		term_newln(p);
 }
 
 static int
 termp_d1_pre(DECL_ARGS)
 {
-
 	if (n->type != ROFFT_BLOCK)
 		return 1;
 	term_newln(p);
@@ -1339,11 +1322,8 @@ termp_d1_pre(DECL_ARGS)
 static int
 termp_ft_pre(DECL_ARGS)
 {
-
-	/* NB: NODE_LINE does not effect this! */
 	synopsis_pre(p, n);
-	term_fontpush(p, TERMFONT_UNDER);
-	return 1;
+	return termp_under_pre(p, pair, meta, n);
 }
 
 static int
@@ -1352,11 +1332,9 @@ termp_fn_pre(DECL_ARGS)
 	size_t		 rmargin = 0;
 	int		 pretty;
 
-	pretty = NODE_SYNPRETTY & n->flags;
-
 	synopsis_pre(p, n);
-
-	if (NULL == (n = n->child))
+	pretty = n->flags & NODE_SYNPRETTY;
+	if ((n = n->child) == NULL)
 		return 0;
 
 	if (pretty) {
@@ -1370,9 +1348,6 @@ termp_fn_pre(DECL_ARGS)
 	term_word(p, n->string);
 	term_fontpop(p);
 
-	if (n->sec == SEC_DESCRIPTION || n->sec == SEC_CUSTOM)
-		tag_put(n->string, fn_prio++, p->line);
-
 	if (pretty) {
 		term_flushln(p);
 		p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND | TERMP_HANG);
@@ -1407,7 +1382,6 @@ termp_fn_pre(DECL_ARGS)
 		term_word(p, ";");
 		term_flushln(p);
 	}
-
 	return 0;
 }
 
@@ -1416,10 +1390,9 @@ termp_fa_pre(DECL_ARGS)
 {
 	const struct roff_node	*nn;
 
-	if (n->parent->tok != MDOC_Fo) {
-		term_fontpush(p, TERMFONT_UNDER);
-		return 1;
-	}
+	if (n->parent->tok != MDOC_Fo)
+		return termp_under_pre(p, pair, meta, n);
+
 	for (nn = n->child; nn != NULL; nn = nn->next) {
 		term_fontpush(p, TERMFONT_UNDER);
 		p->flags |= TERMP_NBRWORD;
@@ -1530,9 +1503,8 @@ termp_ss_pre(DECL_ARGS)
 			term_vspace(p);
 		break;
 	case ROFFT_HEAD:
-		term_fontpush(p, TERMFONT_BOLD);
 		p->tcol->offset = term_len(p, (p->defindent+1)/2);
-		break;
+		return termp_bold_pre(p, pair, meta, n);
 	case ROFFT_BODY:
 		p->tcol->offset = term_len(p, p->defindent);
 		term_tab_set(p, NULL);
@@ -1553,21 +1525,10 @@ termp_ss_post(DECL_ARGS)
 }
 
 static int
-termp_cd_pre(DECL_ARGS)
-{
-
-	synopsis_pre(p, n);
-	term_fontpush(p, TERMFONT_BOLD);
-	return 1;
-}
-
-static int
 termp_in_pre(DECL_ARGS)
 {
-
 	synopsis_pre(p, n);
-
-	if (NODE_SYNPRETTY & n->flags && NODE_LINE & n->flags) {
+	if (n->flags & NODE_SYNPRETTY && n->flags & NODE_LINE) {
 		term_fontpush(p, TERMFONT_BOLD);
 		term_word(p, "#include");
 		term_word(p, "<");
@@ -1575,7 +1536,6 @@ termp_in_pre(DECL_ARGS)
 		term_word(p, "<");
 		term_fontpush(p, TERMFONT_UNDER);
 	}
-
 	p->flags |= TERMP_NOSPACE;
 	return 1;
 }
@@ -1583,21 +1543,17 @@ termp_in_pre(DECL_ARGS)
 static void
 termp_in_post(DECL_ARGS)
 {
-
-	if (NODE_SYNPRETTY & n->flags)
+	if (n->flags & NODE_SYNPRETTY)
 		term_fontpush(p, TERMFONT_BOLD);
-
 	p->flags |= TERMP_NOSPACE;
 	term_word(p, ">");
-
-	if (NODE_SYNPRETTY & n->flags)
+	if (n->flags & NODE_SYNPRETTY)
 		term_fontpop(p);
 }
 
 static int
 termp_pp_pre(DECL_ARGS)
 {
-	fn_prio = TAG_STRONG;
 	term_vspace(p);
 	return 0;
 }
@@ -1605,14 +1561,12 @@ termp_pp_pre(DECL_ARGS)
 static int
 termp_skip_pre(DECL_ARGS)
 {
-
 	return 0;
 }
 
 static int
 termp_quote_pre(DECL_ARGS)
 {
-
 	if (n->type != ROFFT_BODY && n->type != ROFFT_ELEM)
 		return 1;
 
@@ -1769,17 +1723,15 @@ termp_eo_post(DECL_ARGS)
 static int
 termp_fo_pre(DECL_ARGS)
 {
-	size_t		 rmargin = 0;
-	int		 pretty;
-
-	pretty = NODE_SYNPRETTY & n->flags;
+	size_t rmargin;
 
-	if (n->type == ROFFT_BLOCK) {
+	switch (n->type) {
+	case ROFFT_BLOCK:
 		synopsis_pre(p, n);
 		return 1;
-	} else if (n->type == ROFFT_BODY) {
-		if (pretty) {
-			rmargin = p->tcol->rmargin;
+	case ROFFT_BODY:
+		rmargin = p->tcol->rmargin;
+		if (n->flags & NODE_SYNPRETTY) {
 			p->tcol->rmargin = p->tcol->offset + term_len(p, 4);
 			p->flags |= TERMP_NOBREAK | TERMP_BRIND |
 					TERMP_HANG;
@@ -1787,7 +1739,7 @@ termp_fo_pre(DECL_ARGS)
 		p->flags |= TERMP_NOSPACE;
 		term_word(p, "(");
 		p->flags |= TERMP_NOSPACE;
-		if (pretty) {
+		if (n->flags & NODE_SYNPRETTY) {
 			term_flushln(p);
 			p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND |
 					TERMP_HANG);
@@ -1796,30 +1748,21 @@ termp_fo_pre(DECL_ARGS)
 			p->tcol->rmargin = rmargin;
 		}
 		return 1;
+	default:
+		return termp_bold_pre(p, pair, meta, n);
 	}
-
-	if (NULL == n->child)
-		return 0;
-
-	/* XXX: we drop non-initial arguments as per groff. */
-
-	assert(n->child->string);
-	term_fontpush(p, TERMFONT_BOLD);
-	term_word(p, n->child->string);
-	return 0;
 }
 
 static void
 termp_fo_post(DECL_ARGS)
 {
-
 	if (n->type != ROFFT_BODY)
 		return;
 
 	p->flags |= TERMP_NOSPACE;
 	term_word(p, ")");
 
-	if (NODE_SYNPRETTY & n->flags) {
+	if (n->flags & NODE_SYNPRETTY) {
 		p->flags |= TERMP_NOSPACE;
 		term_word(p, ";");
 		term_flushln(p);
@@ -1829,29 +1772,30 @@ termp_fo_post(DECL_ARGS)
 static int
 termp_bf_pre(DECL_ARGS)
 {
-
-	if (n->type == ROFFT_HEAD)
+	switch (n->type) {
+	case ROFFT_HEAD:
 		return 0;
-	else if (n->type != ROFFT_BODY)
+	case ROFFT_BODY:
+		break;
+	default:
 		return 1;
-
-	if (FONT_Em == n->norm->Bf.font)
-		term_fontpush(p, TERMFONT_UNDER);
-	else if (FONT_Sy == n->norm->Bf.font)
-		term_fontpush(p, TERMFONT_BOLD);
-	else
-		term_fontpush(p, TERMFONT_NONE);
-
-	return 1;
+	}
+	switch (n->norm->Bf.font) {
+	case FONT_Em:
+		return termp_under_pre(p, pair, meta, n);
+	case FONT_Sy:
+		return termp_bold_pre(p, pair, meta, n);
+	default:
+		return termp_li_pre(p, pair, meta, n);
+	}
 }
 
 static int
 termp_sm_pre(DECL_ARGS)
 {
-
-	if (NULL == n->child)
+	if (n->child == NULL)
 		p->flags ^= TERMP_NONOSPACE;
-	else if (0 == strcmp("on", n->child->string))
+	else if (strcmp(n->child->string, "on") == 0)
 		p->flags &= ~TERMP_NONOSPACE;
 	else
 		p->flags |= TERMP_NONOSPACE;
@@ -1865,7 +1809,6 @@ termp_sm_pre(DECL_ARGS)
 static int
 termp_ap_pre(DECL_ARGS)
 {
-
 	p->flags |= TERMP_NOSPACE;
 	term_word(p, "'");
 	p->flags |= TERMP_NOSPACE;
@@ -1904,8 +1847,6 @@ termp____post(DECL_ARGS)
 static int
 termp_li_pre(DECL_ARGS)
 {
-
-	termp_tag_pre(p, pair, meta, n);
 	term_fontpush(p, TERMFONT_NONE);
 	return 1;
 }
@@ -1955,7 +1896,6 @@ termp_lk_pre(DECL_ARGS)
 static int
 termp_bk_pre(DECL_ARGS)
 {
-
 	switch (n->type) {
 	case ROFFT_BLOCK:
 		break;
@@ -1968,103 +1908,43 @@ termp_bk_pre(DECL_ARGS)
 	default:
 		abort();
 	}
-
 	return 1;
 }
 
 static void
 termp_bk_post(DECL_ARGS)
 {
-
 	if (n->type == ROFFT_BODY)
 		p->flags &= ~(TERMP_KEEP | TERMP_PREKEEP);
 }
 
+/*
+ * If we are in an `Rs' and there is a journal present,
+ * then quote us instead of underlining us (for disambiguation).
+ */
 static void
 termp__t_post(DECL_ARGS)
 {
-
-	/*
-	 * If we're in an `Rs' and there's a journal present, then quote
-	 * us instead of underlining us (for disambiguation).
-	 */
-	if (n->parent && MDOC_Rs == n->parent->tok &&
+	if (n->parent != NULL && n->parent->tok == MDOC_Rs &&
 	    n->parent->norm->Rs.quote_T)
 		termp_quote_post(p, pair, meta, n);
-
 	termp____post(p, pair, meta, n);
 }
 
 static int
 termp__t_pre(DECL_ARGS)
 {
-
-	/*
-	 * If we're in an `Rs' and there's a journal present, then quote
-	 * us instead of underlining us (for disambiguation).
-	 */
-	if (n->parent && MDOC_Rs == n->parent->tok &&
+	if (n->parent != NULL && n->parent->tok == MDOC_Rs &&
 	    n->parent->norm->Rs.quote_T)
 		return termp_quote_pre(p, pair, meta, n);
-
-	term_fontpush(p, TERMFONT_UNDER);
-	return 1;
+	else
+		return termp_under_pre(p, pair, meta, n);
 }
 
 static int
 termp_under_pre(DECL_ARGS)
 {
-
-	term_fontpush(p, TERMFONT_UNDER);
-	return 1;
-}
-
-static int
-termp_em_pre(DECL_ARGS)
-{
-	if (n->child != NULL &&
-	    n->child->type == ROFFT_TEXT)
-		tag_put(n->child->string, TAG_FALLBACK, p->line);
 	term_fontpush(p, TERMFONT_UNDER);
-	return 1;
-}
-
-static int
-termp_sy_pre(DECL_ARGS)
-{
-	if (n->child != NULL &&
-	    n->child->type == ROFFT_TEXT)
-		tag_put(n->child->string, TAG_FALLBACK, p->line);
-	term_fontpush(p, TERMFONT_BOLD);
-	return 1;
-}
-
-static int
-termp_er_pre(DECL_ARGS)
-{
-
-	if (n->sec == SEC_ERRORS &&
-	    (n->parent->tok == MDOC_It ||
-	     (n->parent->tok == MDOC_Bq &&
-	      n->parent->parent->parent->tok == MDOC_It)))
-		tag_put(n->child->string, TAG_STRONG, p->line);
-	return 1;
-}
-
-static int
-termp_tag_pre(DECL_ARGS)
-{
-
-	if (n->child != NULL &&
-	    n->child->type == ROFFT_TEXT &&
-	    (n->prev == NULL ||
-	     (n->prev->type == ROFFT_TEXT &&
-	      strcmp(n->prev->string, "|") == 0)) &&
-	    (n->parent->tok == MDOC_It ||
-	     (n->parent->tok == MDOC_Xo &&
-	      n->parent->parent->prev == NULL &&
-	      n->parent->parent->parent->tok == MDOC_It)))
-		tag_put(n->child->string, TAG_STRONG, p->line);
 	return 1;
 }
 
Index: Makefile
===================================================================
RCS file: /home/cvs/mandoc/mandoc/Makefile,v
retrieving revision 1.530
retrieving revision 1.531
diff -LMakefile -LMakefile -u -p -r1.530 -r1.531
--- Makefile
+++ Makefile
@@ -1,7 +1,7 @@
 # $Id$
 #
+# Copyright (c) 2011, 2013-2020 Ingo Schwarze <schwarze@openbsd.org>
 # Copyright (c) 2010, 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
-# Copyright (c) 2011, 2013-2019 Ingo Schwarze <schwarze@openbsd.org>
 #
 # Permission to use, copy, modify, and distribute this software for any
 # purpose with or without fee is hereby granted, provided that the above
@@ -134,6 +134,7 @@ SRCS		 = arch.c \
 		   term_ascii.c \
 		   term_ps.c \
 		   term_tab.c \
+		   term_tag.c \
 		   tree.c
 
 DISTFILES	 = INSTALL \
@@ -209,6 +210,7 @@ DISTFILES	 = INSTALL \
 		   tbl_int.h \
 		   tbl_parse.h \
 		   term.h \
+		   term_tag.h \
 		   $(SRCS) \
 		   $(TESTSRCS)
 
@@ -245,7 +247,8 @@ LIBMANDOC_OBJS	 = $(LIBMAN_OBJS) \
 		   mandoc_xr.o \
 		   msec.o \
 		   preconv.o \
-		   read.o
+		   read.o \
+		   tag.o
 
 COMPAT_OBJS	 = compat_err.o \
 		   compat_fts.o \
@@ -280,6 +283,7 @@ MANDOC_TERM_OBJS = eqn_term.o \
 		   term_ascii.o \
 		   term_ps.o \
 		   term_tab.o \
+		   term_tag.o \
 		   tbl_term.o
 
 DBM_OBJS	 = dbm.o \
@@ -302,7 +306,6 @@ MAIN_OBJS	 = $(MANDOC_HTML_OBJS) \
 		   mdoc_man.o \
 		   mdoc_markdown.o \
 		   out.o \
-		   tag.o \
 		   tree.o
 
 CGI_OBJS	 = $(MANDOC_HTML_OBJS) \
@@ -313,8 +316,7 @@ CGI_OBJS	 = $(MANDOC_HTML_OBJS) \
 MANDOCD_OBJS	 = $(MANDOC_HTML_OBJS) \
 		   $(MANDOC_TERM_OBJS) \
 		   mandocd.o \
-		   out.o \
-		   tag.o
+		   out.o
 
 DEMANDOC_OBJS	 = demandoc.o
 
@@ -393,7 +395,7 @@ distclean: clean
 
 clean:
 	rm -f libmandoc.a $(LIBMANDOC_OBJS) $(COMPAT_OBJS)
-	rm -f mandoc $(MAIN_OBJS)
+	rm -f mandoc man $(MAIN_OBJS)
 	rm -f man.cgi $(CGI_OBJS)
 	rm -f mandocd catman catman.o $(MANDOCD_OBJS)
 	rm -f demandoc $(DEMANDOC_OBJS)
@@ -501,7 +503,7 @@ uninstall:
 	rm -f $(DESTDIR)$(INCLUDEDIR)/tbl.h
 	[ ! -e $(DESTDIR)$(INCLUDEDIR) ] || rmdir $(DESTDIR)$(INCLUDEDIR)
 
-regress: all
+regress: all man
 	cd regress && ./regress.pl
 
 regress-clean:
@@ -516,6 +518,9 @@ libmandoc.a: $(COMPAT_OBJS) $(LIBMANDOC_
 
 mandoc: $(MAIN_OBJS) libmandoc.a
 	$(CC) -o $@ $(LDFLAGS) $(MAIN_OBJS) libmandoc.a $(LDADD)
+
+man: mandoc
+	$(LN) mandoc man
 
 man.cgi: $(CGI_OBJS) libmandoc.a
 	$(CC) $(STATIC) -o $@ $(LDFLAGS) $(CGI_OBJS) libmandoc.a $(LDADD)
Index: html.c
===================================================================
RCS file: /home/cvs/mandoc/mandoc/html.c,v
retrieving revision 1.263
retrieving revision 1.264
diff -Lhtml.c -Lhtml.c -u -p -r1.263 -r1.264
--- html.c
+++ html.c
@@ -1,7 +1,7 @@
-/*	$Id$ */
+/* $Id$ */
 /*
- * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
  * Copyright (c) 2011-2015, 2017-2020 Ingo Schwarze <schwarze@openbsd.org>
+ * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -14,6 +14,9 @@
  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Common functions for mandoc(1) HTML formatters.
+ * For use by individual formatters and by the main program.
  */
 #include "config.h"
 
@@ -321,6 +324,18 @@ html_fillmode(struct html *h, enum roff_
 	return had;
 }
 
+/*
+ * Allocate a string to be used for the "id=" attribute of an HTML
+ * element and/or as a segment identifier for a URI in an <a> element.
+ * The function may fail and return NULL if the node lacks text data
+ * to create the attribute from.
+ * If the "unique" argument is 0, the caller is responsible for
+ * free(3)ing the returned string after using it.
+ * If the "unique" argument is non-zero, the "id_unique" ohash table
+ * is used for de-duplication and owns the returned string, so the
+ * caller must not free(3) it.  In this case, it will be freed
+ * automatically by html_reset() or html_free().
+ */
 char *
 html_make_id(const struct roff_node *n, int unique)
 {
@@ -329,14 +344,30 @@ html_make_id(const struct roff_node *n, 
 	unsigned int		 slot;
 	int			 suffix;
 
-	for (nch = n->child; nch != NULL; nch = nch->next)
-		if (nch->type != ROFFT_TEXT)
-			return NULL;
-
-	buf = NULL;
-	deroff(&buf, n);
-	if (buf == NULL)
-		return NULL;
+	if (n->string != NULL)
+		buf = mandoc_strdup(n->string);
+	else {
+		switch (n->tok) {
+		case MDOC_Sh:
+		case MDOC_Ss:
+		case MDOC_Sx:
+		case MAN_SH:
+		case MAN_SS:
+			for (nch = n->child; nch != NULL; nch = nch->next)
+				if (nch->type != ROFFT_TEXT)
+					return NULL;
+			buf = NULL;
+			deroff(&buf, n);
+			if (buf == NULL)
+				return NULL;
+			break;
+		default:
+			if (n->child->type != ROFFT_TEXT)
+				return NULL;
+			buf = mandoc_strdup(n->child->string);
+			break;
+		}
+	}
 
 	/*
 	 * In ID attributes, only use ASCII characters that are
@@ -734,6 +765,33 @@ print_otag(struct html *h, enum htmltag 
 		h->noindent++;
 
 	return t;
+}
+
+/*
+ * Print an element with an optional "id=" attribute.
+ * If there is an "id=" attribute, also add a permalink:
+ * outside if it's a phrasing element, or inside otherwise.
+ */
+struct tag *
+print_otag_id(struct html *h, enum htmltag elemtype, const char *cattr,
+    struct roff_node *n)
+{
+	struct tag	*ret, *t;
+	const char	*id;
+
+	ret = NULL;
+	id = NULL;
+	if (n->flags & NODE_ID)
+		id = html_make_id(n, 1);
+	if (id != NULL && htmltags[elemtype].flags & HTML_INPHRASE)
+		ret = print_otag(h, TAG_A, "chR", "permalink", id);
+	t = print_otag(h, elemtype, "ci", cattr, id);
+	if (ret == NULL) {
+		ret = t;
+		if (id != NULL)
+			print_otag(h, TAG_A, "chR", "permalink", id);
+	}
+	return ret;
 }
 
 static void
--- /dev/null
+++ term_tag.c
@@ -0,0 +1,206 @@
+/* $Id: term_tag.c,v 1.1 2020/03/13 15:32:29 schwarze Exp $ */
+/*
+ * Copyright (c) 2015,2016,2018,2019,2020 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Functions to write a ctags(1) file.
+ * For use by the mandoc(1) ASCII and UTF-8 formatters only.
+ */
+#include "config.h"
+
+#include <sys/types.h>
+
+#include <errno.h>
+#include <signal.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "mandoc.h"
+#include "roff.h"
+#include "tag.h"
+#include "term_tag.h"
+
+static void tag_signal(int) __attribute__((__noreturn__));
+
+static struct tag_files tag_files;
+
+
+/*
+ * Prepare for using a pager.
+ * Not all pagers are capable of using a tag file,
+ * but for simplicity, create it anyway.
+ */
+struct tag_files *
+term_tag_init(char *tagname)
+{
+	struct sigaction	 sa;
+	int			 ofd;	/* In /tmp/, dup(2)ed to stdout. */
+	int			 tfd;
+
+	ofd = tfd = -1;
+	tag_files.tfs = NULL;
+	tag_files.tcpgid = -1;
+	tag_files.tagname = tagname;
+
+	/* Clean up when dying from a signal. */
+
+	memset(&sa, 0, sizeof(sa));
+	sigfillset(&sa.sa_mask);
+	sa.sa_handler = tag_signal;
+	sigaction(SIGHUP, &sa, NULL);
+	sigaction(SIGINT, &sa, NULL);
+	sigaction(SIGTERM, &sa, NULL);
+
+	/*
+	 * POSIX requires that a process calling tcsetpgrp(3)
+	 * from the background gets a SIGTTOU signal.
+	 * In that case, do not stop.
+	 */
+
+	sa.sa_handler = SIG_IGN;
+	sigaction(SIGTTOU, &sa, NULL);
+
+	/* Save the original standard output for use by the pager. */
+
+	if ((tag_files.ofd = dup(STDOUT_FILENO)) == -1) {
+		mandoc_msg(MANDOCERR_DUP, 0, 0, "%s", strerror(errno));
+		goto fail;
+	}
+
+	/* Create both temporary output files. */
+
+	(void)strlcpy(tag_files.ofn, "/tmp/man.XXXXXXXXXX",
+	    sizeof(tag_files.ofn));
+	(void)strlcpy(tag_files.tfn, "/tmp/man.XXXXXXXXXX",
+	    sizeof(tag_files.tfn));
+	if ((ofd = mkstemp(tag_files.ofn)) == -1) {
+		mandoc_msg(MANDOCERR_MKSTEMP, 0, 0,
+		    "%s: %s", tag_files.ofn, strerror(errno));
+		goto fail;
+	}
+	if ((tfd = mkstemp(tag_files.tfn)) == -1) {
+		mandoc_msg(MANDOCERR_MKSTEMP, 0, 0,
+		    "%s: %s", tag_files.tfn, strerror(errno));
+		goto fail;
+	}
+	if ((tag_files.tfs = fdopen(tfd, "w")) == NULL) {
+		mandoc_msg(MANDOCERR_FDOPEN, 0, 0, "%s", strerror(errno));
+		goto fail;
+	}
+	tfd = -1;
+	if (dup2(ofd, STDOUT_FILENO) == -1) {
+		mandoc_msg(MANDOCERR_DUP, 0, 0, "%s", strerror(errno));
+		goto fail;
+	}
+	close(ofd);
+	return &tag_files;
+
+fail:
+	term_tag_unlink();
+	if (ofd != -1)
+		close(ofd);
+	if (tfd != -1)
+		close(tfd);
+	if (tag_files.ofd != -1) {
+		close(tag_files.ofd);
+		tag_files.ofd = -1;
+	}
+	tag_files.tagname = NULL;
+	return NULL;
+}
+
+void
+term_tag_write(struct roff_node *n, size_t line)
+{
+	const char	*cp;
+	int		 len;
+
+	if (tag_files.tfs == NULL)
+		return;
+	if (n->string == NULL)
+		n = n->child;
+	cp = n->string;
+	if (cp[0] == '\\' && (cp[1] == '&' || cp[1] == 'e'))
+		cp += 2;
+	len = strcspn(cp, " \t\\");
+	fprintf(tag_files.tfs, "%.*s %s %zu\n",
+	    len, cp, tag_files.ofn, line);
+}
+
+void
+term_tag_finish(void)
+{
+	if (tag_files.tfs == NULL)
+		return;
+	fclose(tag_files.tfs);
+	tag_files.tfs = NULL;
+	switch (tag_check(tag_files.tagname)) {
+	case TAG_EMPTY:
+		unlink(tag_files.tfn);
+		*tag_files.tfn = '\0';
+		/* FALLTHROUGH */
+	case TAG_MISS:
+		if (tag_files.tagname == NULL)
+			break;
+		mandoc_msg(MANDOCERR_TAG, 0, 0, "%s", tag_files.tagname);
+		tag_files.tagname = NULL;
+		break;
+	case TAG_OK:
+		break;
+	}
+}
+
+void
+term_tag_unlink(void)
+{
+	pid_t	 tc_pgid;
+
+	if (tag_files.tcpgid != -1) {
+		tc_pgid = tcgetpgrp(tag_files.ofd);
+		if (tc_pgid == tag_files.pager_pid ||
+		    tc_pgid == getpgid(0) ||
+		    getpgid(tc_pgid) == -1)
+			(void)tcsetpgrp(tag_files.ofd, tag_files.tcpgid);
+	}
+	if (*tag_files.ofn != '\0') {
+		unlink(tag_files.ofn);
+		*tag_files.ofn = '\0';
+	}
+	if (*tag_files.tfn != '\0') {
+		unlink(tag_files.tfn);
+		*tag_files.tfn = '\0';
+	}
+	if (tag_files.tfs != NULL) {
+		fclose(tag_files.tfs);
+		tag_files.tfs = NULL;
+	}
+}
+
+static void
+tag_signal(int signum)
+{
+	struct sigaction	 sa;
+
+	term_tag_unlink();
+	memset(&sa, 0, sizeof(sa));
+	sigemptyset(&sa.sa_mask);
+	sa.sa_handler = SIG_DFL;
+	sigaction(signum, &sa, NULL);
+	kill(getpid(), signum);
+	/* NOTREACHED */
+	_exit(1);
+}
Index: mdoc_validate.c
===================================================================
RCS file: /home/cvs/mandoc/mandoc/mdoc_validate.c,v
retrieving revision 1.379
retrieving revision 1.380
diff -Lmdoc_validate.c -Lmdoc_validate.c -u -p -r1.379 -r1.380
--- mdoc_validate.c
+++ mdoc_validate.c
@@ -1,7 +1,7 @@
-/*	$Id$ */
+/* $Id$ */
 /*
- * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
  * Copyright (c) 2010-2020 Ingo Schwarze <schwarze@openbsd.org>
+ * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
  * Copyright (c) 2010 Joerg Sonnenberger <joerg@netbsd.org>
  *
  * Permission to use, copy, modify, and distribute this software for any
@@ -15,6 +15,8 @@
  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Validation module for mdoc(7) syntax trees used by mandoc(1).
  */
 #include "config.h"
 
@@ -35,6 +37,7 @@
 #include "mandoc.h"
 #include "mandoc_xr.h"
 #include "roff.h"
+#include "tag.h"
 #include "mdoc.h"
 #include "libmandoc.h"
 #include "roff_int.h"
@@ -82,7 +85,9 @@ static	void	 post_dd(POST_ARGS);
 static	void	 post_delim(POST_ARGS);
 static	void	 post_delim_nb(POST_ARGS);
 static	void	 post_dt(POST_ARGS);
+static	void	 post_em(POST_ARGS);
 static	void	 post_en(POST_ARGS);
+static	void	 post_er(POST_ARGS);
 static	void	 post_es(POST_ARGS);
 static	void	 post_eoln(POST_ARGS);
 static	void	 post_ex(POST_ARGS);
@@ -113,6 +118,7 @@ static	void	 post_sm(POST_ARGS);
 static	void	 post_st(POST_ARGS);
 static	void	 post_std(POST_ARGS);
 static	void	 post_sx(POST_ARGS);
+static	void	 post_tag(POST_ARGS);
 static	void	 post_tg(POST_ARGS);
 static	void	 post_useless(POST_ARGS);
 static	void	 post_xr(POST_ARGS);
@@ -137,19 +143,19 @@ static	const v_post mdoc_valids[MDOC_MAX
 	NULL,		/* Ap */
 	post_defaults,	/* Ar */
 	NULL,		/* Cd */
-	post_delim_nb,	/* Cm */
-	post_delim_nb,	/* Dv */
-	post_delim_nb,	/* Er */
-	post_delim_nb,	/* Ev */
+	post_tag,	/* Cm */
+	post_tag,	/* Dv */
+	post_er,	/* Er */
+	post_tag,	/* Ev */
 	post_ex,	/* Ex */
 	post_fa,	/* Fa */
 	NULL,		/* Fd */
-	post_delim_nb,	/* Fl */
+	post_tag,	/* Fl */
 	post_fn,	/* Fn */
 	post_delim_nb,	/* Ft */
-	post_delim_nb,	/* Ic */
+	post_tag,	/* Ic */
 	post_delim_nb,	/* In */
-	post_defaults,	/* Li */
+	post_tag,	/* Li */
 	post_nd,	/* Nd */
 	post_nm,	/* Nm */
 	post_delim_nb,	/* Op */
@@ -187,11 +193,11 @@ static	const v_post mdoc_valids[MDOC_MAX
 	NULL,		/* Dq */
 	NULL,		/* Ec */
 	NULL,		/* Ef */
-	post_delim_nb,	/* Em */
+	post_em,	/* Em */
 	NULL,		/* Eo */
 	post_xx,	/* Fx */
-	post_delim_nb,	/* Ms */
-	NULL,		/* No */
+	post_tag,	/* Ms */
+	post_tag,	/* No */
 	post_ns,	/* Ns */
 	post_xx,	/* Nx */
 	post_xx,	/* Ox */
@@ -210,7 +216,7 @@ static	const v_post mdoc_valids[MDOC_MAX
 	post_delim_nb,	/* Sq */
 	post_sm,	/* Sm */
 	post_sx,	/* Sx */
-	post_delim_nb,	/* Sy */
+	post_em,	/* Sy */
 	post_useless,	/* Tn */
 	post_xx,	/* Ux */
 	NULL,		/* Xc */
@@ -287,6 +293,8 @@ static	const char * const secnames[SEC__
 	NULL
 };
 
+static	int	  fn_prio = TAG_STRONG;
+
 
 /* Validate the subtree rooted at mdoc->last. */
 void
@@ -1094,8 +1102,11 @@ post_st(POST_ARGS)
 static void
 post_tg(POST_ARGS)
 {
-	struct roff_node	*n, *nch, *nn;
-	size_t			len;
+	struct roff_node *n;	/* The .Tg node. */
+	struct roff_node *nch;	/* The first child of the .Tg node. */
+	struct roff_node *nn;   /* The next node after the .Tg node. */
+	struct roff_node *nt;	/* The TEXT node containing the tag. */
+	size_t		  len;	/* The number of bytes in the tag. */
 
 	/* Find the next node. */
 	n = mdoc->last;
@@ -1106,30 +1117,26 @@ post_tg(POST_ARGS)
 		}
 	}
 
-	/* Add the default argument, if needed. */
-	nch = n->child;
-	if (nch == NULL && nn != NULL && nn->child->type == ROFFT_TEXT) {
-		mdoc->next = ROFF_NEXT_CHILD;
-		roff_word_alloc(mdoc, n->line, n->pos, n->next->child->string);
-		nch = mdoc->last;
-		nch->flags |= NODE_NOSRC;
-		mdoc->last = n;
-	}
+	/* Find the tag. */
+	nt = nch = n->child;
+	if (nch == NULL && nn != NULL && nn->child != NULL &&
+	    nn->child->type == ROFFT_TEXT)
+		nt = nn->child;
 
-	/* Validate the first argument. */
-	if (nch == NULL || *nch->string == '\0')
+	/* Validate the tag. */
+	if (nt == NULL || *nt->string == '\0')
 		mandoc_msg(MANDOCERR_MACRO_EMPTY, n->line, n->pos, "Tg");
-	if (nch == NULL) {
+	if (nt == NULL) {
 		roff_node_delete(mdoc, n);
 		return;
 	}
-	len = strcspn(nch->string, " \t");
-	if (nch->string[len] != '\0')
-		mandoc_msg(MANDOCERR_TG_SPC, nch->line, nch->pos + len + 1,
-		    "Tg %s", nch->string);
+	len = strcspn(nt->string, " \t\\");
+	if (nt->string[len] != '\0')
+		mandoc_msg(MANDOCERR_TG_SPC, nt->line,
+		    nt->pos + len, "Tg %s", nt->string);
 
 	/* Keep only the first argument. */
-	if (nch->next != NULL) {
+	if (nch != NULL && nch->next != NULL) {
 		mandoc_msg(MANDOCERR_ARG_EXCESS, nch->next->line,
 		    nch->next->pos, "Tg ... %s", nch->next->string);
 		while (nch->next != NULL)
@@ -1137,32 +1144,44 @@ post_tg(POST_ARGS)
 	}
 
 	/* Drop the macro if the first argument is invalid. */
-	if (len == 0 || nch->string[len] != '\0') {
+	if (len == 0 || nt->string[len] != '\0') {
 		roff_node_delete(mdoc, n);
 		return;
 	}
 
-	/* By default, write a <mark> element. */
-	n->flags |= NODE_ID;
+	/* By default, tag the .Tg node itself. */
 	if (nn == NULL)
-		return;
+		nn = n;
 
 	/* Explicit tagging of specific macros. */
 	switch (nn->tok) {
 	case MDOC_Sh:
 	case MDOC_Ss:
-		if (nn->head->flags & NODE_ID || nn->head->child == NULL)
+	case MDOC_Fo:
+		nn = nn->head;
+		/* FALLTHROUGH */
+	case MDOC_Cm:
+	case MDOC_Dv:
+	case MDOC_Em:
+	case MDOC_Er:
+	case MDOC_Ev:
+	case MDOC_Fl:
+	case MDOC_Fn:
+	case MDOC_Ic:
+	case MDOC_Li:
+	case MDOC_Ms:
+	case MDOC_No:
+	case MDOC_Sy:
+		if (nn->child != NULL && (nn->flags & NODE_ID) == 0)
 			break;
-		n->flags |= NODE_NOPRT;
-		nn->head->flags |= NODE_ID | NODE_HREF;
-		assert(nn->head->string == NULL);
-		nn->head->string = mandoc_strdup(nch->string);
-		break;
+		/* FALLTHROUGH */
 	default:
+		nn = n;
 		break;
 	}
-	if (n->flags & NODE_NOPRT)
-		n->flags &= ~NODE_ID;
+	tag_put(nt->string, TAG_MANUAL, nn);
+	if (nn != n)
+		n->flags |= NODE_NOPRT;
 }
 
 static void
@@ -1257,28 +1276,32 @@ post_bf(POST_ARGS)
 static void
 post_fname(POST_ARGS)
 {
-	const struct roff_node	*n;
+	struct roff_node	*n, *nch;
 	const char		*cp;
 	size_t			 pos;
 
-	n = mdoc->last->child;
-	cp = n->string;
+	n = mdoc->last;
+	nch = n->child;
+	cp = nch->string;
 	if (*cp == '(') {
 		if (cp[strlen(cp + 1)] == ')')
 			return;
 		pos = 0;
 	} else {
 		pos = strcspn(cp, "()");
-		if (cp[pos] == '\0')
+		if (cp[pos] == '\0') {
+			if (n->sec == SEC_DESCRIPTION ||
+			    n->sec == SEC_CUSTOM)
+				tag_put(NULL, fn_prio++, n);
 			return;
+		}
 	}
-	mandoc_msg(MANDOCERR_FN_PAREN, n->line, n->pos + pos, "%s", cp);
+	mandoc_msg(MANDOCERR_FN_PAREN, nch->line, nch->pos + pos, "%s", cp);
 }
 
 static void
 post_fn(POST_ARGS)
 {
-
 	post_fname(mdoc);
 	post_fa(mdoc);
 }
@@ -1442,38 +1465,29 @@ post_display(POST_ARGS)
 static void
 post_defaults(POST_ARGS)
 {
-	struct roff_node *nn;
+	struct roff_node *n;
 
-	if (mdoc->last->child != NULL) {
+	n = mdoc->last;
+	if (n->child != NULL) {
 		post_delim_nb(mdoc);
 		return;
 	}
-
-	/*
-	 * The `Ar' defaults to "file ..." if no value is provided as an
-	 * argument; the `Mt' and `Pa' macros use "~"; the `Li' just
-	 * gets an empty string.
-	 */
-
-	nn = mdoc->last;
-	switch (nn->tok) {
+	mdoc->next = ROFF_NEXT_CHILD;
+	switch (n->tok) {
 	case MDOC_Ar:
-		mdoc->next = ROFF_NEXT_CHILD;
-		roff_word_alloc(mdoc, nn->line, nn->pos, "file");
-		mdoc->last->flags |= NODE_NOSRC;
-		roff_word_alloc(mdoc, nn->line, nn->pos, "...");
+		roff_word_alloc(mdoc, n->line, n->pos, "file");
 		mdoc->last->flags |= NODE_NOSRC;
+		roff_word_alloc(mdoc, n->line, n->pos, "...");
 		break;
 	case MDOC_Pa:
 	case MDOC_Mt:
-		mdoc->next = ROFF_NEXT_CHILD;
-		roff_word_alloc(mdoc, nn->line, nn->pos, "~");
-		mdoc->last->flags |= NODE_NOSRC;
+		roff_word_alloc(mdoc, n->line, n->pos, "~");
 		break;
 	default:
 		abort();
 	}
-	mdoc->last = nn;
+	mdoc->last->flags |= NODE_NOSRC;
+	mdoc->last = n;
 }
 
 static void
@@ -1527,18 +1541,54 @@ post_an(POST_ARGS)
 }
 
 static void
-post_en(POST_ARGS)
+post_em(POST_ARGS)
 {
+	post_tag(mdoc);
+	tag_put(NULL, TAG_FALLBACK, mdoc->last);
+}
 
+static void
+post_en(POST_ARGS)
+{
 	post_obsolete(mdoc);
 	if (mdoc->last->type == ROFFT_BLOCK)
 		mdoc->last->norm->Es = mdoc->last_es;
 }
 
 static void
-post_es(POST_ARGS)
+post_er(POST_ARGS)
+{
+	struct roff_node *n;
+
+	n = mdoc->last;
+	if (n->sec == SEC_ERRORS &&
+	    (n->parent->tok == MDOC_It ||
+	     (n->parent->tok == MDOC_Bq &&
+	      n->parent->parent->parent->tok == MDOC_It)))
+		tag_put(NULL, TAG_STRONG, n);
+	post_delim_nb(mdoc);
+}
+
+static void
+post_tag(POST_ARGS)
 {
+	struct roff_node *n;
 
+	n = mdoc->last;
+	if ((n->prev == NULL ||
+	     (n->prev->type == ROFFT_TEXT &&
+	      strcmp(n->prev->string, "|") == 0)) &&
+	    (n->parent->tok == MDOC_It ||
+	     (n->parent->tok == MDOC_Xo &&
+	      n->parent->parent->prev == NULL &&
+	      n->parent->parent->parent->tok == MDOC_It)))
+		tag_put(NULL, TAG_STRONG, n);
+	post_delim_nb(mdoc);
+}
+
+static void
+post_es(POST_ARGS)
+{
 	post_obsolete(mdoc);
 	mdoc->last_es = mdoc->last;
 }
@@ -1635,8 +1685,8 @@ post_it(POST_ARGS)
 		if ((nch = nit->head->child) != NULL)
 			mandoc_msg(MANDOCERR_ARG_SKIP,
 			    nit->line, nit->pos, "It %s",
-			    nch->string == NULL ? roff_name[nch->tok] :
-			    nch->string);
+			    nch->type == ROFFT_TEXT ? nch->string :
+			    roff_name[nch->tok]);
 		break;
 	case LIST_column:
 		cols = (int)nbl->norm->Bl.ncols;
@@ -2152,7 +2202,6 @@ post_sx(POST_ARGS)
 static void
 post_sh(POST_ARGS)
 {
-
 	post_ignpar(mdoc);
 
 	switch (mdoc->last->type) {
@@ -2384,6 +2433,8 @@ post_sh_head(POST_ARGS)
 		roff_setreg(mdoc->roff, "nS", 0, '=');
 		mdoc->flags &= ~MDOC_SYNOPSIS;
 	}
+	if (sec == SEC_DESCRIPTION)
+		fn_prio = TAG_STRONG;
 
 	/* Mark our last section. */
 
@@ -2555,6 +2606,7 @@ post_par(POST_ARGS)
 {
 	struct roff_node *np;
 
+	fn_prio = TAG_STRONG;
 	post_prevpar(mdoc);
 
 	np = mdoc->last;
--- /dev/null
+++ term_tag.h
@@ -0,0 +1,35 @@
+/* $Id: term_tag.h,v 1.1 2020/03/13 15:32:29 schwarze Exp $ */
+/*
+ * Copyright (c) 2015, 2018, 2019, 2020 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Internal interfaces to write a ctags(1) file.
+ * For use by the mandoc(1) ASCII and UTF-8 formatters only.
+ */
+
+struct	tag_files {
+	char	 ofn[20];	/* Output file name. */
+	char	 tfn[20];	/* Tag file name. */
+	char	*tagname;	/* Target specified with -O. */
+	FILE	*tfs;		/* Tag file object. */
+	int	 ofd;		/* Original output file descriptor. */
+	pid_t	 tcpgid;	/* Process group controlling the terminal. */
+	pid_t	 pager_pid;	/* Process ID of the pager. */
+};
+
+
+struct tag_files	*term_tag_init(char *);
+void			 term_tag_write(struct roff_node *, size_t);
+void			 term_tag_finish(void);
+void			 term_tag_unlink(void);
Index: man_html.c
===================================================================
RCS file: /home/cvs/mandoc/mandoc/man_html.c,v
retrieving revision 1.176
retrieving revision 1.177
diff -Lman_html.c -Lman_html.c -u -p -r1.176 -r1.177
--- man_html.c
+++ man_html.c
@@ -1,7 +1,7 @@
-/*	$Id$ */
+/* $Id$ */
 /*
- * Copyright (c) 2008-2012, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
  * Copyright (c) 2013-2015, 2017-2020 Ingo Schwarze <schwarze@openbsd.org>
+ * Copyright (c) 2008-2012, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -14,6 +14,8 @@
  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * HTML formatter for man(7) used by mandoc(1).
  */
 #include "config.h"
 
@@ -310,7 +312,6 @@ static int
 man_SH_pre(MAN_ARGS)
 {
 	const char	*class;
-	char		*id;
 	enum htmltag	 tag;
 
 	if (n->tok == MAN_SH) {
@@ -326,10 +327,8 @@ man_SH_pre(MAN_ARGS)
 		print_otag(h, TAG_SECTION, "c", class);
 		break;
 	case ROFFT_HEAD:
-		id = html_make_id(n, 1);
-		print_otag(h, tag, "ci", class, id);
-		if (id != NULL)
-			print_otag(h, TAG_A, "chR", "permalink", id);
+		n->flags |= NODE_ID;
+		print_otag_id(h, tag, class, n);
 		break;
 	case ROFFT_BODY:
 		break;
@@ -489,7 +488,7 @@ man_IP_pre(MAN_ARGS)
 	case ROFFT_HEAD:
 		if (body_elem == TAG_LI)
 			return 0;
-		print_otag(h, TAG_DT, "");
+		print_otag_id(h, TAG_DT, NULL, n);
 		break;
 	case ROFFT_BODY:
 		print_otag(h, body_elem, "");
@@ -497,7 +496,6 @@ man_IP_pre(MAN_ARGS)
 	default:
 		abort();
 	}
-
 	switch(n->tok) {
 	case MAN_IP:  /* Only print the first header element. */
 		if (n->child != NULL)
--- /dev/null
+++ regress/mdoc/Sy/tag.out_tag
@@ -0,0 +1,5 @@
+one 9
+two 9
+three 12
+four 13
+explicit 13
--- /dev/null
+++ regress/mdoc/Sy/tag.out_html
@@ -0,0 +1,10 @@
+<dl class="Bl-tag">
+  <dt><a class="permalink" href="#one"><b class="Sy" id="one">one</b></a> |
+    <a class="permalink" href="#two"><b class="Sy" id="two">two</b></a></dt>
+  <dd>text</dd>
+  <dt><a class="permalink" href="#three"><b class="Sy" id="three">three</b></a></dt>
+  <dd>text</dd>
+</dl>
+<a class="permalink" href="#four"><b class="Sy" id="four">four</b></a>
+  <b class="Sy">one</b>
+  <a class="permalink" href="#explicit"><b class="Sy" id="explicit">five</b></a>
--- /dev/null
+++ regress/mdoc/Sy/tag.out_ascii
@@ -0,0 +1,17 @@
+SY-TAG(1)                   General Commands Manual                  SY-TAG(1)
+
+N\bNA\bAM\bME\bE
+     S\bSy\by-\b-t\bta\bag\bg - tagging of symbolic font macros
+
+D\bDE\bES\bSC\bCR\bRI\bIP\bPT\bTI\bIO\bON\bN
+     BEGINTEST
+
+     o\bon\bne\be | t\btw\bwo\bo
+             text
+
+     t\bth\bhr\bre\bee\be   text
+     f\bfo\bou\bur\br o\bon\bne\be f\bfi\biv\bve\be
+
+     ENDTEST
+
+OpenBSD                         March 13, 2020                         OpenBSD
--- /dev/null
+++ regress/mdoc/Sy/tag.out_markdown
@@ -0,0 +1,25 @@
+SY-TAG(1) - General Commands Manual
+
+# NAME
+
+**Sy-tag** - tagging of symbolic font macros
+
+# DESCRIPTION
+
+BEGINTEST
+
+**one** | **two**
+
+> text
+
+**three**
+
+> text
+
+**four**
+**one**
+**five**
+
+ENDTEST
+
+OpenBSD - March 13, 2020
--- /dev/null
+++ regress/mdoc/Sy/tag.in
@@ -0,0 +1,23 @@
+.\" $OpenBSD: tag.in,v 1.1 2020/03/13 00:31:06 schwarze Exp $
+.Dd $Mdocdate: March 13 2020 $
+.Dt SY-TAG 1
+.Os
+.Sh NAME
+.Nm Sy-tag
+.Nd tagging of symbolic font macros
+.Sh DESCRIPTION
+BEGINTEST
+.Bl -tag -width Ds
+.It Sy one | two
+text
+.It Xo
+.Sy three
+.Xc
+text
+.El
+.Sy four
+.Sy one
+.Tg explicit
+.Sy five
+.Pp
+ENDTEST
Index: Makefile
===================================================================
RCS file: /home/cvs/mandoc/mandoc/regress/mdoc/Sy/Makefile,v
retrieving revision 1.3
retrieving revision 1.4
diff -Lregress/mdoc/Sy/Makefile -Lregress/mdoc/Sy/Makefile -u -p -r1.3 -r1.4
--- regress/mdoc/Sy/Makefile
+++ regress/mdoc/Sy/Makefile
@@ -1,6 +1,8 @@
-# $OpenBSD: Makefile,v 1.5 2014/11/17 06:44:35 schwarze Exp $
+# $OpenBSD: Makefile,v 1.8 2020/03/13 00:31:06 schwarze Exp $
 
-REGRESS_TARGETS = noarg font punct
+REGRESS_TARGETS = noarg font punct tag
+TAG_TARGETS	= tag
 LINT_TARGETS	= noarg punct
+HTML_TARGETS	= tag
 
 .include <bsd.regress.mk>
Index: regress.pl.1
===================================================================
RCS file: /home/cvs/mandoc/mandoc/regress/regress.pl.1,v
retrieving revision 1.4
retrieving revision 1.5
diff -Lregress/regress.pl.1 -Lregress/regress.pl.1 -u -p -r1.4 -r1.5
--- regress/regress.pl.1
+++ regress/regress.pl.1
@@ -1,6 +1,6 @@
 .\"	$Id$
 .\"
-.\" Copyright (c) 2017 Ingo Schwarze <schwarze@openbsd.org>
+.\" Copyright (c) 2017, 2019, 2020 Ingo Schwarze <schwarze@openbsd.org>
 .\"
 .\" Permission to use, copy, modify, and distribute this software for any
 .\" purpose with or without fee is hereby granted, provided that the above
@@ -78,6 +78,8 @@ output mode.
 Run subtests for
 .Fl T Cm markdown
 output mode.
+.It Cm tag
+Run subtests for automatic and manual tagging.
 .It Cm utf8
 Run subtests for
 .Fl T Cm utf8
@@ -146,15 +148,6 @@ subdirectory of the regression suite is 
 It uses a Makefile structure that differs vastly from the
 rest of the suite.
 .Sh BUGS
-On Oracle Solaris 11,
-.Xr diff 1
-does not support the
-.Fl a
-option.
-Delete that option from the following line in this script:
-.Pp
-.Dl my @diff = qw(diff -au);
-.Pp
 The C library function
 .Xr wcwidth 3
 is known to be buggy on Solaris, which may cause failures in the
Index: regress.pl
===================================================================
RCS file: /home/cvs/mandoc/mandoc/regress/regress.pl,v
retrieving revision 1.13
retrieving revision 1.14
diff -Lregress/regress.pl -Lregress/regress.pl -u -p -r1.13 -r1.14
--- regress/regress.pl
+++ regress/regress.pl
@@ -2,7 +2,7 @@
 #
 # $Id$
 #
-# Copyright (c) 2017 Ingo Schwarze <schwarze@openbsd.org>
+# Copyright (c) 2017, 2018, 2019, 2020 Ingo Schwarze <schwarze@openbsd.org>
 #
 # Permission to use, copy, modify, and distribute this software for any
 # purpose with or without fee is hereby granted, provided that the above
@@ -120,16 +120,17 @@ sub fail ($$) {
 
 my $onlytest = shift // '';
 for (@ARGV) {
-	/^(all|ascii|utf8|man|html|markdown|lint|clean|verbose)$/
+	/^(all|ascii|tag|man|utf8|html|markdown|lint|clean|verbose)$/
 	    or usage "$_: invalid modifier";
 	$targets{$_} = 1;
 }
 $targets{all} = 1
-    unless $targets{ascii} || $targets{utf8} || $targets{man} ||
-      $targets{html} || $targets{markdown} ||
+    unless $targets{ascii} || $targets{tag} || $targets{man} ||
+      $targets{utf8} || $targets{html} || $targets{markdown} ||
       $targets{lint} || $targets{clean};
-$targets{ascii} = $targets{utf8} = $targets{man} = $targets{html} =
-    $targets{markdown} = $targets{lint} = 1 if $targets{all};
+$targets{ascii} = $targets{tag} = $targets{man} = $targets{utf8} =
+    $targets{html} = $targets{markdown} = $targets{lint} = 1
+    if $targets{all};
 
 
 # --- parse Makefiles --------------------------------------------------
@@ -157,7 +158,7 @@ sub parse_makefile ($%) {
 }
 
 my (@regress_tests, @utf8_tests, @lint_tests, @html_tests);
-my (%skip_ascii, %skip_man, %skip_markdown);
+my (%tag_tests, %skip_ascii, %skip_man, %skip_markdown);
 foreach my $module (qw(roff char mdoc man tbl eqn)) {
 	my %modvars;
 	parse_makefile "$module/Makefile", \%modvars;
@@ -168,36 +169,41 @@ foreach my $module (qw(roff char mdoc ma
 		delete $subvars{GOPTS};
 		delete $subvars{SKIP_GROFF};
 		delete $subvars{SKIP_GROFF_ASCII};
-		my @mandoc = ('../mandoc', split ' ', $subvars{MOPTS});
+		my @mopts = split ' ', $subvars{MOPTS};
 		delete $subvars{MOPTS};
 		my @regress_testnames;
+		if (defined $subvars{TAG_TARGETS}) {
+			$tag_tests{"$module/$subdir/$_"} = 1
+			    for split ' ', $subvars{TAG_TARGETS};
+			delete $subvars{TAG_TARGETS};
+		}
 		if (defined $subvars{REGRESS_TARGETS}) {
 			push @regress_testnames,
 			    split ' ', $subvars{REGRESS_TARGETS};
 			push @regress_tests, {
 			    NAME => "$module/$subdir/$_",
-			    MANDOC => \@mandoc,
+			    MOPTS => \@mopts,
 			} foreach @regress_testnames;
 			delete $subvars{REGRESS_TARGETS};
 		}
 		if (defined $subvars{UTF8_TARGETS}) {
 			push @utf8_tests, {
 			    NAME => "$module/$subdir/$_",
-			    MANDOC => \@mandoc,
+			    MOPTS => \@mopts,
 			} foreach split ' ', $subvars{UTF8_TARGETS};
 			delete $subvars{UTF8_TARGETS};
 		}
 		if (defined $subvars{HTML_TARGETS}) {
 			push @html_tests, {
 			    NAME => "$module/$subdir/$_",
-			    MANDOC => \@mandoc,
+			    MOPTS => \@mopts,
 			} foreach split ' ', $subvars{HTML_TARGETS};
 			delete $subvars{HTML_TARGETS};
 		}
 		if (defined $subvars{LINT_TARGETS}) {
 			push @lint_tests, {
 			    NAME => "$module/$subdir/$_",
-			    MANDOC => \@mandoc,
+			    MOPTS => \@mopts,
 			} foreach split ' ', $subvars{LINT_TARGETS};
 			delete $subvars{LINT_TARGETS};
 		}
@@ -243,22 +249,44 @@ foreach my $module (qw(roff char mdoc ma
 
 my $count_total = 0;
 my $count_ascii = 0;
+my $count_tag = 0;
 my $count_man = 0;
 my $count_rm = 0;
-if ($targets{ascii} || $targets{man}) {
-	print "Running ascii and man tests ";
+if ($targets{ascii} || $targets{tag} || $targets{man}) {
+	print "Running ascii, tag, and man tests ";
 	print "...\n" if $targets{verbose};
 }
 for my $test (@regress_tests) {
 	my $i = "$test->{NAME}.in";
 	my $o = "$test->{NAME}.mandoc_ascii";
 	my $w = "$test->{NAME}.out_ascii";
-	if ($targets{ascii} && !$skip_ascii{$test->{NAME}} &&
+	my $to = "$test->{NAME}.mandoc_tag";
+	my $tw = "$test->{NAME}.out_tag";
+	my $diff_ascii;
+	if ($targets{tag} && $tag_tests{$test->{NAME}} &&
 	    $test->{NAME} =~ /^$onlytest/) {
-		$count_ascii++;
+		$count_tag++;
 		$count_total++;
-		sysout $o, @{$test->{MANDOC}}, qw(-I os=OpenBSD -T ascii), $i
+		local $ENV{MANPAGER} = "./copyless $test->{NAME}";
+		my @cmd = (qw(../man -l), @{$test->{MOPTS}},
+		    qw(-I os=OpenBSD -T ascii), $i);
+		print "@cmd\n" if $targets{verbose};
+		system @cmd
+		    and fail $test->{NAME}, 'tag:man';
+		system @diff, $tw, $to
+		    and fail $test->{NAME}, 'tag:diff';
+		print "." unless $targets{verbose};
+		$diff_ascii = $targets{ascii};
+	} elsif ($targets{ascii} && !$skip_ascii{$test->{NAME}} &&
+	    $test->{NAME} =~ /^$onlytest/) {
+		sysout $o, '../mandoc', @{$test->{MOPTS}},
+		    qw(-I os=OpenBSD -T ascii), $i
 		    and fail $test->{NAME}, 'ascii:mandoc';
+		$diff_ascii = 1;
+	}
+	if ($diff_ascii) {
+		$count_ascii++;
+		$count_total++;
 		system @diff, $w, $o
 		    and fail $test->{NAME}, 'ascii:diff';
 		print "." unless $targets{verbose};
@@ -269,9 +297,10 @@ for my $test (@regress_tests) {
 	    $test->{NAME} =~ /^$onlytest/) {
 		$count_man++;
 		$count_total++;
-		sysout $m, @{$test->{MANDOC}}, qw(-I os=OpenBSD -T man), $i
+		sysout $m, '../mandoc', @{$test->{MOPTS}},
+		    qw(-I os=OpenBSD -T man), $i
 		    and fail $test->{NAME}, 'man:man';
-		sysout $mo, @{$test->{MANDOC}},
+		sysout $mo, '../mandoc', @{$test->{MOPTS}},
 		    qw(-man -I os=OpenBSD -T ascii -O mdoc), $m
 		    and fail $test->{NAME}, 'man:mandoc';
 		system @diff, $w, $mo
@@ -279,13 +308,13 @@ for my $test (@regress_tests) {
 		print "." unless $targets{verbose};
 	}
 	if ($targets{clean}) {
-		print "rm $o $m $mo\n" if $targets{verbose};
-		$count_rm += unlink $o, $m, $mo;
+		print "rm $o $to $m $mo\n" if $targets{verbose};
+		$count_rm += unlink $o, $to, $m, $mo;
 	}
 }
-if ($targets{ascii} || $targets{man}) {
-	print "Number of ascii and man tests:" if $targets{verbose};
-	print " $count_ascii + $count_man tests run.\n";
+if ($targets{ascii} || $targets{tag} || $targets{man}) {
+	print "Number of ascii, tag, and man tests:" if $targets{verbose};
+	print " $count_ascii + $count_tag + $count_man tests run.\n";
 }
 
 my $count_utf8 = 0;
@@ -300,7 +329,8 @@ for my $test (@utf8_tests) {
 	if ($targets{utf8} && $test->{NAME} =~ /^$onlytest/o) {
 		$count_utf8++;
 		$count_total++;
-		sysout $o, @{$test->{MANDOC}}, qw(-I os=OpenBSD -T utf8), $i
+		sysout $o, '../mandoc', @{$test->{MOPTS}},
+		    qw(-I os=OpenBSD -T utf8), $i
 		    and fail $test->{NAME}, 'utf8:mandoc';
 		system @diff, $w, $o
 		    and fail $test->{NAME}, 'utf8:diff';
@@ -328,7 +358,8 @@ for my $test (@html_tests) {
 	if ($targets{html} && $test->{NAME} =~ /^$onlytest/) {
 		$count_html++;
 		$count_total++;
-		syshtml $o, @{$test->{MANDOC}}, qw(-T html), $i
+		syshtml $o, '../mandoc', @{$test->{MOPTS}},
+		    qw(-T html), $i
 		    and fail $test->{NAME}, 'html:mandoc';
 		system @diff, $w, $o
 		    and fail $test->{NAME}, 'html:diff';
@@ -357,7 +388,7 @@ for my $test (@regress_tests) {
 	    $test->{NAME} =~ /^$onlytest/) {
 		$count_markdown++;
 		$count_total++;
-		sysout $o, @{$test->{MANDOC}},
+		sysout $o, '../mandoc', @{$test->{MOPTS}},
 		    qw(-I os=OpenBSD -T markdown), $i
 		    and fail $test->{NAME}, 'markdown:mandoc';
 		system @diff, $w, $o
@@ -386,7 +417,7 @@ for my $test (@lint_tests) {
 	if ($targets{lint} && $test->{NAME} =~ /^$onlytest/) {
 		$count_lint++;
 		$count_total++;
-		syslint $o, @{$test->{MANDOC}},
+		syslint $o, '../mandoc', @{$test->{MOPTS}},
 		    qw(-I os=OpenBSD -T lint -W all), $i
 		    and fail $test->{NAME}, 'lint:mandoc';
 		system @diff, $w, $o
@@ -418,6 +449,7 @@ if ($count_total == 1) {
 } elsif ($count_total) {
 	print "All $count_total tests OK:";
 	print " $count_ascii ascii" if $count_ascii;
+	print " $count_tag tag" if $count_tag;
 	print " $count_man man" if $count_man;
 	print " $count_utf8 utf8" if $count_utf8;
 	print " $count_html html" if $count_html;
--- /dev/null
+++ regress/copyless
@@ -0,0 +1,14 @@
+#!/bin/sh
+set -e
+umask 022
+if [ "$#" -ne 4 ]; then
+	echo "$0 $*: $# args instead of 4" 1>&2
+	exit 1
+fi
+if [ "$2" != "-T" ]; then
+	echo "$0 $*: second arg is not -T" 1>&2
+	exit 1
+fi
+cut -d ' ' -f 1,3 "$3" > "$1.mandoc_tag"
+cp "$4" "$1.mandoc_ascii"
+exit 0
--- /dev/null
+++ regress/man/IP/tag.out_tag
@@ -0,0 +1,2 @@
+strong 13
+weak 15
--- /dev/null
+++ regress/man/IP/tag.in
@@ -0,0 +1,18 @@
+.\" $OpenBSD: tag.in,v 1.1 2020/03/13 00:31:05 schwarze Exp $
+.TH IP-TAG 1 "March 10, 2020"
+.SH NAME
+IP-tag \- automatic tagging of indented blocks
+.SH DESCRIPTION
+BEGINTEST
+initial
+text
+.IP " strong" 10n
+text
+.IP "-strong"
+text
+.IP "\&\fI \-weak\fP"
+text
+.IP " strong"
+text
+.PP
+ENDTEST
--- /dev/null
+++ regress/man/IP/tag.out_ascii
@@ -0,0 +1,23 @@
+IP-TAG(1)                   General Commands Manual                  IP-TAG(1)
+
+
+
+N\bNA\bAM\bME\bE
+       IP-tag - automatic tagging of indented blocks
+
+D\bDE\bES\bSC\bCR\bRI\bIP\bPT\bTI\bIO\bON\bN
+       BEGINTEST initial text
+
+        strong   text
+
+       -strong   text
+
+        _\b-_\bw_\be_\ba_\bk    text
+
+        strong   text
+
+       ENDTEST
+
+
+
+OpenBSD                         March 10, 2020                       IP-TAG(1)
Index: empty.in
===================================================================
RCS file: /home/cvs/mandoc/mandoc/regress/man/IP/empty.in,v
retrieving revision 1.2
retrieving revision 1.3
diff -Lregress/man/IP/empty.in -Lregress/man/IP/empty.in -u -p -r1.2 -r1.3
--- regress/man/IP/empty.in
+++ regress/man/IP/empty.in
@@ -1,8 +1,9 @@
-.\" $OpenBSD: empty.in,v 1.2 2017/07/04 14:53:23 schwarze Exp $
+.\" $OpenBSD: empty.in,v 1.3 2020/03/13 00:31:05 schwarze Exp $
 .TH IP-EMPTY 1 "July 17, 2012"
 .SH NAME
 IP-empty \- empty indented paragraphs
 .SH DESCRIPTION
+BEGINTEST
 regular
 text
 .IP
@@ -25,3 +26,4 @@ text
 .RE
 regular
 text
+ENDTEST
--- /dev/null
+++ regress/man/IP/empty.out_html
@@ -0,0 +1,18 @@
+<dl class="Bl-tag">
+  <dt></dt>
+  <dd>indented text</dd>
+</dl>
+<p class="Pp">Empty IP is deleted:</p>
+<dl class="Bl-tag">
+  <dt id="tag1"><a class="permalink" href="#tag1">tag1</a></dt>
+  <dd></dd>
+  <dt id="tag2"><a class="permalink" href="#tag2">tag2</a></dt>
+  <dd>indented text</dd>
+</dl>
+<p class="Pp">Empty IP is deleted, RS does not cause additional spacing:</p>
+<div class="Bd-indent">
+<dl class="Bl-tag">
+  <dt id="tag"><a class="permalink" href="#tag">tag</a></dt>
+  <dd>indented text</dd>
+</dl>
+</div>
--- /dev/null
+++ regress/man/IP/empty.out_tag
@@ -0,0 +1,3 @@
+tag1 15
+tag2 17
+tag 21
Index: literal.out_html
===================================================================
RCS file: /home/cvs/mandoc/mandoc/regress/man/IP/literal.out_html,v
retrieving revision 1.4
retrieving revision 1.5
diff -Lregress/man/IP/literal.out_html -Lregress/man/IP/literal.out_html -u -p -r1.4 -r1.5
--- regress/man/IP/literal.out_html
+++ regress/man/IP/literal.out_html
@@ -1,5 +1,5 @@
 <dl class="Bl-tag">
-  <dt>tag</dt>
+  <dt id="tag"><a class="permalink" href="#tag">tag</a></dt>
   <dd>indented regular text</dd>
 </dl>
 <p class="Pp">new regular paragraph</p>
@@ -8,7 +8,7 @@ literal
 text
 </pre>
 <dl class="Bl-tag">
-  <dt>tag</dt>
+  <dt id="tag_2"><a class="permalink" href="#tag_2">tag</a></dt>
   <dd>
     <pre>
 indented
@@ -32,7 +32,7 @@ literal
 text
 </pre>
 <dl class="Bl-tag">
-  <dt>tag</dt>
+  <dt id="tag_3"><a class="permalink" href="#tag_3">tag</a></dt>
   <dd>
     <pre>
 indented
@@ -48,7 +48,7 @@ text
   out of indented paragraph</a></h2>
 <p class="Pp">regular text</p>
 <dl class="Bl-tag">
-  <dt>tag</dt>
+  <dt id="tag_4"><a class="permalink" href="#tag_4">tag</a></dt>
   <dd>indented regular text
     <pre>
 indented
Index: Makefile
===================================================================
RCS file: /home/cvs/mandoc/mandoc/regress/man/IP/Makefile,v
retrieving revision 1.3
retrieving revision 1.4
diff -Lregress/man/IP/Makefile -Lregress/man/IP/Makefile -u -p -r1.3 -r1.4
--- regress/man/IP/Makefile
+++ regress/man/IP/Makefile
@@ -1,8 +1,9 @@
-# $OpenBSD: Makefile,v 1.10 2020/02/27 01:25:58 schwarze Exp $
+# $OpenBSD: Makefile,v 1.11 2020/03/13 00:31:05 schwarze Exp $
 
-REGRESS_TARGETS = bullet empty literal longhead manyargs spacing vert width
+REGRESS_TARGETS = bullet empty literal longhead manyargs spacing tag vert width
+TAG_TARGETS	= empty tag
 UTF8_TARGETS	= bullet
 LINT_TARGETS	= empty
-HTML_TARGETS	= bullet literal
+HTML_TARGETS	= bullet empty literal tag
 
 .include <bsd.regress.mk>
--- /dev/null
+++ regress/man/IP/tag.out_html
@@ -0,0 +1,10 @@
+<dl class="Bl-tag">
+  <dt> strong</dt>
+  <dd>text</dd>
+  <dt id="strong"><a class="permalink" href="#strong">-strong</a></dt>
+  <dd>text</dd>
+  <dt id="weak"><a class="permalink" href="#weak"><i> -weak</i></a></dt>
+  <dd>text</dd>
+  <dt> strong</dt>
+  <dd>text</dd>
+</dl>
Index: empty.out_lint
===================================================================
RCS file: /home/cvs/mandoc/mandoc/regress/man/IP/empty.out_lint,v
retrieving revision 1.4
retrieving revision 1.5
diff -Lregress/man/IP/empty.out_lint -Lregress/man/IP/empty.out_lint -u -p -r1.4 -r1.5
--- regress/man/IP/empty.out_lint
+++ regress/man/IP/empty.out_lint
@@ -1,2 +1,2 @@
-mandoc: empty.in:13:2: WARNING: skipping paragraph macro: IP empty
-mandoc: empty.in:20:2: WARNING: skipping paragraph macro: IP empty
+mandoc: empty.in:14:2: WARNING: skipping paragraph macro: IP empty
+mandoc: empty.in:21:2: WARNING: skipping paragraph macro: IP empty
Index: empty.out_ascii
===================================================================
RCS file: /home/cvs/mandoc/mandoc/regress/man/IP/empty.out_ascii,v
retrieving revision 1.1
retrieving revision 1.2
diff -Lregress/man/IP/empty.out_ascii -Lregress/man/IP/empty.out_ascii -u -p -r1.1 -r1.2
--- regress/man/IP/empty.out_ascii
+++ regress/man/IP/empty.out_ascii
@@ -6,7 +6,7 @@ N\bNA\bAM\bME\bE
        IP-empty - empty indented paragraphs
 
 D\bDE\bES\bSC\bCR\bRI\bIP\bPT\bTI\bIO\bON\bN
-       regular text
+       BEGINTEST regular text
 
               indented text
 
@@ -19,7 +19,7 @@ D\bDE\bES\bSC\bCR\bRI\bIP\bPT\bTI\bIO\bON\bN
        Empty IP is deleted, RS does not cause additional spacing:
 
               tag    indented text
-       regular text
+       regular text ENDTEST
 
 
 
Index: Makefile
===================================================================
RCS file: /home/cvs/mandoc/mandoc/regress/man/TP/Makefile,v
retrieving revision 1.5
retrieving revision 1.6
diff -Lregress/man/TP/Makefile -Lregress/man/TP/Makefile -u -p -r1.5 -r1.6
--- regress/man/TP/Makefile
+++ regress/man/TP/Makefile
@@ -1,9 +1,10 @@
-# $OpenBSD: Makefile,v 1.16 2020/02/27 01:25:58 schwarze Exp $
+# $OpenBSD: Makefile,v 1.17 2020/03/13 00:31:05 schwarze Exp $
 
 REGRESS_TARGETS	 = badarg broken double eof fill indent literal longhead
-REGRESS_TARGETS	+= macrotag manyargs sameline spacing vert width
+REGRESS_TARGETS	+= macrotag manyargs sameline spacing tag vert width
+TAG_TARGETS	 = tag
 LINT_TARGETS	 = broken double eof
-HTML_TARGETS	 = literal vert
+HTML_TARGETS	 = literal tag vert
 
 # groff-1.22.3 defects:
 # - If .TP precedes .RE, the latter does not properly reset indentation.
--- /dev/null
+++ regress/man/TP/tag.out_tag
@@ -0,0 +1,3 @@
+plain 13
+strong 19
+weak 21
--- /dev/null
+++ regress/man/TP/tag.out_html
@@ -0,0 +1,16 @@
+<dl class="Bl-tag">
+  <dt><i> plain</i></dt>
+  <dd>text</dd>
+  <dt id="plain"><a class="permalink" href="#plain">plain</a></dt>
+  <dd>text</dd>
+  <dt><i>plain </i></dt>
+  <dd>text</dd>
+  <dt> strong</dt>
+  <dd>text</dd>
+  <dt id="strong"><a class="permalink" href="#strong"><b>-strong</b></a></dt>
+  <dd>text</dd>
+  <dt id="weak"><a class="permalink" href="#weak"><i> -weak</i></a></dt>
+  <dd>text</dd>
+  <dt><b>strong </b></dt>
+  <dd>text</dd>
+</dl>
--- /dev/null
+++ regress/man/TP/tag.out_ascii
@@ -0,0 +1,29 @@
+IP-TAG(1)                   General Commands Manual                  IP-TAG(1)
+
+
+
+N\bNA\bAM\bME\bE
+       IP-tag - automatic tagging of indented blocks
+
+D\bDE\bES\bSC\bCR\bRI\bIP\bPT\bTI\bIO\bON\bN
+       BEGINTEST initial text
+
+        _\bp_\bl_\ba_\bi_\bn    text
+
+       plain     text
+
+       _\bp_\bl_\ba_\bi_\bn     text
+
+        strong   text
+
+       -\b-s\bst\btr\bro\bon\bng\bg   text
+
+        _\b-_\bw_\be_\ba_\bk    text
+
+       s\bst\btr\bro\bon\bng\bg    text
+
+       ENDTEST
+
+
+
+OpenBSD                         March 10, 2020                       IP-TAG(1)
Index: literal.out_html
===================================================================
RCS file: /home/cvs/mandoc/mandoc/regress/man/TP/literal.out_html,v
retrieving revision 1.2
retrieving revision 1.3
diff -Lregress/man/TP/literal.out_html -Lregress/man/TP/literal.out_html -u -p -r1.2 -r1.3
--- regress/man/TP/literal.out_html
+++ regress/man/TP/literal.out_html
@@ -1,5 +1,5 @@
 <dl class="Bl-tag">
-  <dt>tag</dt>
+  <dt id="tag"><a class="permalink" href="#tag">tag</a></dt>
   <dd>regular indented text</dd>
 </dl>
 <p class="Pp">regular paragraph</p>
@@ -8,7 +8,7 @@ literal
 text
 </pre>
 <dl class="Bl-tag">
-  <dt>tag</dt>
+  <dt id="tag_2"><a class="permalink" href="#tag_2">tag</a></dt>
   <dd>
     <pre>
 indented
--- /dev/null
+++ regress/man/TP/tag.in
@@ -0,0 +1,31 @@
+.\" $OpenBSD: tag.in,v 1.1 2020/03/13 00:31:05 schwarze Exp $
+.TH IP-TAG 1 "March 10, 2020"
+.SH NAME
+IP-tag \- automatic tagging of indented blocks
+.SH DESCRIPTION
+BEGINTEST
+initial
+text
+.TP 10n
+.I " plain"
+text
+.TP
+plain
+text
+.TP
+.I "plain "
+text
+.TP
+\& strong
+text
+.TP
+.B -strong
+text
+.TP
+\&\fI \-weak\fP
+text
+.TP
+.B "strong "
+text
+.PP
+ENDTEST
Index: vert.out_html
===================================================================
RCS file: /home/cvs/mandoc/mandoc/regress/man/TP/vert.out_html,v
retrieving revision 1.1
retrieving revision 1.2
diff -Lregress/man/TP/vert.out_html -Lregress/man/TP/vert.out_html -u -p -r1.1 -r1.2
--- regress/man/TP/vert.out_html
+++ regress/man/TP/vert.out_html
@@ -2,8 +2,8 @@
 <section class="Sh">
 <h1 class="Sh" id="DESCRIPTION"><a class="permalink" href="#DESCRIPTION">DESCRIPTION</a></h1>
 <dl class="Bl-tag">
-  <dt>tag</dt>
+  <dt id="tag"><a class="permalink" href="#tag">tag</a></dt>
   <dd>text</dd>
-  <dt>tag</dt>
+  <dt id="tag_2"><a class="permalink" href="#tag_2">tag</a></dt>
   <dd>text</dd>
 </dl>
Index: Makefile
===================================================================
RCS file: /home/cvs/mandoc/mandoc/regress/mdoc/Makefile,v
retrieving revision 1.1
retrieving revision 1.2
diff -Lregress/mdoc/Makefile -Lregress/mdoc/Makefile -u -p -r1.1 -r1.2
--- regress/mdoc/Makefile
+++ regress/mdoc/Makefile
@@ -1,9 +1,9 @@
-# $OpenBSD: Makefile,v 1.32 2017/01/11 17:39:45 schwarze Exp $
+# $OpenBSD: Makefile,v 1.33 2020/03/13 00:31:05 schwarze Exp $
 
 SUBDIR  = Ad An Ap Aq Ar At Bd Bf Bk Bl Brq Bx Cd Cm
 SUBDIR += D1 Db Dd Dl Dq Dt Dv Em Eo Er Ev Ex Fd Fl Fo Ft Ic In Lb Li Lk
 SUBDIR += Ms Mt Nd Nm No Ns Oo Op Os Ox Pa Pf Pp Qq Rs Rv
-SUBDIR += Sh Sm Sq St Sx Sy Tn Ud Ux Va Vt Xr blank break
+SUBDIR += Sh Sm Sq St Sx Sy Tg Tn Ud Ux Va Vt Xr blank break
 
 .include "../Makefile.sub"
 .include <bsd.subdir.mk>
--- /dev/null
+++ regress/mdoc/Cm/tag.out_tag
@@ -0,0 +1,4 @@
+one 9
+two 9
+three 12
+four 13
--- /dev/null
+++ regress/mdoc/Cm/tag.out_markdown
@@ -0,0 +1,23 @@
+CM-TAG(1) - General Commands Manual
+
+# NAME
+
+**Cm-tag** - tagging of command modifier macros
+
+# DESCRIPTION
+
+BEGINTEST
+
+**one** | **two**
+
+> text
+
+**three**
+
+> text
+
+**four**
+
+ENDTEST
+
+OpenBSD - March 13, 2020
--- /dev/null
+++ regress/mdoc/Cm/tag.out_ascii
@@ -0,0 +1,17 @@
+CM-TAG(1)                   General Commands Manual                  CM-TAG(1)
+
+N\bNA\bAM\bME\bE
+     C\bCm\bm-\b-t\bta\bag\bg - tagging of command modifier macros
+
+D\bDE\bES\bSC\bCR\bRI\bIP\bPT\bTI\bIO\bON\bN
+     BEGINTEST
+
+     o\bon\bne\be | t\btw\bwo\bo
+             text
+
+     t\bth\bhr\bre\bee\be   text
+     f\bfo\bou\bur\br
+
+     ENDTEST
+
+OpenBSD                         March 13, 2020                         OpenBSD
--- /dev/null
+++ regress/mdoc/Cm/tag.in
@@ -0,0 +1,21 @@
+.\" $OpenBSD: tag.in,v 1.1 2020/03/13 00:31:05 schwarze Exp $
+.Dd $Mdocdate: March 13 2020 $
+.Dt CM-TAG 1
+.Os
+.Sh NAME
+.Nm Cm-tag
+.Nd tagging of command modifier macros
+.Sh DESCRIPTION
+BEGINTEST
+.Bl -tag -width Ds
+.It Cm one | two
+text
+.It Xo
+.Cm three
+.Xc
+text
+.El
+.Tg
+.Cm four
+.Pp
+ENDTEST
Index: Makefile
===================================================================
RCS file: /home/cvs/mandoc/mandoc/regress/mdoc/Cm/Makefile,v
retrieving revision 1.4
retrieving revision 1.5
diff -Lregress/mdoc/Cm/Makefile -Lregress/mdoc/Cm/Makefile -u -p -r1.4 -r1.5
--- regress/mdoc/Cm/Makefile
+++ regress/mdoc/Cm/Makefile
@@ -1,6 +1,8 @@
-# $OpenBSD: Makefile,v 1.3 2014/07/02 20:18:42 schwarze Exp $
+# $OpenBSD: Makefile,v 1.7 2020/03/13 00:31:05 schwarze Exp $
 
-REGRESS_TARGETS = basic font noarg punct
+REGRESS_TARGETS = basic font noarg punct tag
+TAG_TARGETS	= tag
 LINT_TARGETS	= noarg
+HTML_TARGETS	= tag
 
 .include <bsd.regress.mk>
--- /dev/null
+++ regress/mdoc/Cm/tag.out_html
@@ -0,0 +1,9 @@
+<dl class="Bl-tag">
+  <dt><a class="permalink" href="#one"><code class="Cm" id="one">one</code></a>
+    |
+    <a class="permalink" href="#two"><code class="Cm" id="two">two</code></a></dt>
+  <dd>text</dd>
+  <dt><a class="permalink" href="#three"><code class="Cm" id="three">three</code></a></dt>
+  <dd>text</dd>
+</dl>
+<a class="permalink" href="#four"><code class="Cm" id="four">four</code></a>
--- /dev/null
+++ regress/mdoc/Dv/tag.out_tag
@@ -0,0 +1,4 @@
+one 9
+two 9
+three 12
+four 13
--- /dev/null
+++ regress/mdoc/Dv/tag.out_html
@@ -0,0 +1,9 @@
+<dl class="Bl-tag">
+  <dt><a class="permalink" href="#one"><code class="Dv" id="one">one</code></a>
+    |
+    <a class="permalink" href="#two"><code class="Dv" id="two">two</code></a></dt>
+  <dd>text</dd>
+  <dt><a class="permalink" href="#three"><code class="Dv" id="three">three</code></a></dt>
+  <dd>text</dd>
+</dl>
+<a class="permalink" href="#four"><code class="Dv" id="four">four</code></a>
--- /dev/null
+++ regress/mdoc/Dv/tag.out_markdown
@@ -0,0 +1,23 @@
+DV-TAG(1) - General Commands Manual
+
+# NAME
+
+**Dv-tag** - tagging of defined variable macros
+
+# DESCRIPTION
+
+BEGINTEST
+
+`one` | `two`
+
+> text
+
+`three`
+
+> text
+
+`four`
+
+ENDTEST
+
+OpenBSD - March 13, 2020
Index: Makefile
===================================================================
RCS file: /home/cvs/mandoc/mandoc/regress/mdoc/Dv/Makefile,v
retrieving revision 1.3
retrieving revision 1.4
diff -Lregress/mdoc/Dv/Makefile -Lregress/mdoc/Dv/Makefile -u -p -r1.3 -r1.4
--- regress/mdoc/Dv/Makefile
+++ regress/mdoc/Dv/Makefile
@@ -1,6 +1,8 @@
-# $OpenBSD: Makefile,v 1.2 2014/07/02 20:18:42 schwarze Exp $
+# $OpenBSD: Makefile,v 1.5 2020/03/13 00:31:05 schwarze Exp $
 
-REGRESS_TARGETS	= font noarg
+REGRESS_TARGETS	= font noarg tag
+TAG_TARGETS	= tag
 LINT_TARGETS	= noarg
+HTML_TARGETS	= tag
 
 .include <bsd.regress.mk>
--- /dev/null
+++ regress/mdoc/Dv/tag.in
@@ -0,0 +1,21 @@
+.\" $OpenBSD: tag.in,v 1.1 2020/03/13 00:31:05 schwarze Exp $
+.Dd $Mdocdate: March 13 2020 $
+.Dt DV-TAG 1
+.Os
+.Sh NAME
+.Nm Dv-tag
+.Nd tagging of defined variable macros
+.Sh DESCRIPTION
+BEGINTEST
+.Bl -tag -width Ds
+.It Dv one | two
+text
+.It Xo
+.Dv three
+.Xc
+text
+.El
+.Tg
+.Dv four
+.Pp
+ENDTEST
--- /dev/null
+++ regress/mdoc/Dv/tag.out_ascii
@@ -0,0 +1,17 @@
+DV-TAG(1)                   General Commands Manual                  DV-TAG(1)
+
+N\bNA\bAM\bME\bE
+     D\bDv\bv-\b-t\bta\bag\bg - tagging of defined variable macros
+
+D\bDE\bES\bSC\bCR\bRI\bIP\bPT\bTI\bIO\bON\bN
+     BEGINTEST
+
+     one | two
+             text
+
+     three   text
+     four
+
+     ENDTEST
+
+OpenBSD                         March 13, 2020                         OpenBSD
--- /dev/null
+++ regress/mdoc/Em/tag.out_html
@@ -0,0 +1,10 @@
+<dl class="Bl-tag">
+  <dt><a class="permalink" href="#one"><i class="Em" id="one">one</i></a> |
+    <a class="permalink" href="#two"><i class="Em" id="two">two</i></a></dt>
+  <dd>text</dd>
+  <dt><a class="permalink" href="#three"><i class="Em" id="three">three</i></a></dt>
+  <dd>text</dd>
+</dl>
+<a class="permalink" href="#four"><i class="Em" id="four">four</i></a>
+  <i class="Em">one</i>
+  <a class="permalink" href="#explicit"><i class="Em" id="explicit">five</i></a>
--- /dev/null
+++ regress/mdoc/Em/tag.out_ascii
@@ -0,0 +1,17 @@
+EM-TAG(1)                   General Commands Manual                  EM-TAG(1)
+
+N\bNA\bAM\bME\bE
+     E\bEm\bm-\b-t\bta\bag\bg - tagging of emphasis macros
+
+D\bDE\bES\bSC\bCR\bRI\bIP\bPT\bTI\bIO\bON\bN
+     BEGINTEST
+
+     _\bo_\bn_\be | _\bt_\bw_\bo
+             text
+
+     _\bt_\bh_\br_\be_\be   text
+     _\bf_\bo_\bu_\br _\bo_\bn_\be _\bf_\bi_\bv_\be
+
+     ENDTEST
+
+OpenBSD                         March 13, 2020                         OpenBSD
--- /dev/null
+++ regress/mdoc/Em/tag.in
@@ -0,0 +1,23 @@
+.\" $OpenBSD: tag.in,v 1.1 2020/03/13 00:31:05 schwarze Exp $
+.Dd $Mdocdate: March 13 2020 $
+.Dt EM-TAG 1
+.Os
+.Sh NAME
+.Nm Em-tag
+.Nd tagging of emphasis macros
+.Sh DESCRIPTION
+BEGINTEST
+.Bl -tag -width Ds
+.It Em one | two
+text
+.It Xo
+.Em three
+.Xc
+text
+.El
+.Em four
+.Em one
+.Tg explicit
+.Em five
+.Pp
+ENDTEST
--- /dev/null
+++ regress/mdoc/Em/tag.out_markdown
@@ -0,0 +1,25 @@
+EM-TAG(1) - General Commands Manual
+
+# NAME
+
+**Em-tag** - tagging of emphasis macros
+
+# DESCRIPTION
+
+BEGINTEST
+
+*one* | *two*
+
+> text
+
+*three*
+
+> text
+
+*four*
+*one*
+*five*
+
+ENDTEST
+
+OpenBSD - March 13, 2020
Index: Makefile
===================================================================
RCS file: /home/cvs/mandoc/mandoc/regress/mdoc/Em/Makefile,v
retrieving revision 1.3
retrieving revision 1.4
diff -Lregress/mdoc/Em/Makefile -Lregress/mdoc/Em/Makefile -u -p -r1.3 -r1.4
--- regress/mdoc/Em/Makefile
+++ regress/mdoc/Em/Makefile
@@ -1,6 +1,8 @@
-# $OpenBSD: Makefile,v 1.3 2014/11/17 06:44:35 schwarze Exp $
+# $OpenBSD: Makefile,v 1.6 2020/03/13 00:31:05 schwarze Exp $
 
-REGRESS_TARGETS	= font noarg punct
+REGRESS_TARGETS	= font noarg punct tag
+TAG_TARGETS	= tag
 LINT_TARGETS	= noarg punct
+HTML_TARGETS	= tag
 
 .include <bsd.regress.mk>
--- /dev/null
+++ regress/mdoc/Em/tag.out_tag
@@ -0,0 +1,5 @@
+one 9
+two 9
+three 12
+four 13
+explicit 13
--- /dev/null
+++ regress/mdoc/Er/tag.out_html
@@ -0,0 +1,12 @@
+<dl class="Bl-tag">
+  <dt><code class="Er">one</code></dt>
+  <dd>text</dd>
+</dl>
+<a class="permalink" href="#two"><code class="Er" id="two">two</code></a>
+</section>
+<section class="Sh">
+<h1 class="Sh" id="ERRORS"><a class="permalink" href="#ERRORS">ERRORS</a></h1>
+<dl class="Bl-tag">
+  <dt>[<a class="permalink" href="#ENOENT"><code class="Er" id="ENOENT">ENOENT</code></a>]</dt>
+  <dd>text</dd>
+</dl>
Index: Makefile
===================================================================
RCS file: /home/cvs/mandoc/mandoc/regress/mdoc/Er/Makefile,v
retrieving revision 1.3
retrieving revision 1.4
diff -Lregress/mdoc/Er/Makefile -Lregress/mdoc/Er/Makefile -u -p -r1.3 -r1.4
--- regress/mdoc/Er/Makefile
+++ regress/mdoc/Er/Makefile
@@ -1,6 +1,8 @@
-# $OpenBSD: Makefile,v 1.4 2014/07/02 20:18:42 schwarze Exp $
+# $OpenBSD: Makefile,v 1.7 2020/03/13 00:31:05 schwarze Exp $
 
-REGRESS_TARGETS = noarg font
+REGRESS_TARGETS = noarg font tag
+TAG_TARGETS	= tag
 LINT_TARGETS	= noarg
+HTML_TARGETS	= tag
 
 .include <bsd.regress.mk>
--- /dev/null
+++ regress/mdoc/Er/tag.out_markdown
@@ -0,0 +1,25 @@
+ER-TAG(1) - General Commands Manual
+
+# NAME
+
+**Er-tag** - tagging of error number macros
+
+# DESCRIPTION
+
+BEGINTEST
+
+`one`
+
+> text
+
+`two`
+
+# ERRORS
+
+\[`ENOENT`]
+
+> text
+
+ENDTEST
+
+OpenBSD - March 13, 2020
--- /dev/null
+++ regress/mdoc/Er/tag.out_tag
@@ -0,0 +1,2 @@
+two 10
+ENOENT 13
--- /dev/null
+++ regress/mdoc/Er/tag.in
@@ -0,0 +1,21 @@
+.\" $OpenBSD: tag.in,v 1.1 2020/03/13 00:31:05 schwarze Exp $
+.Dd $Mdocdate: March 13 2020 $
+.Dt ER-TAG 1
+.Os
+.Sh NAME
+.Nm Er-tag
+.Nd tagging of error number macros
+.Sh DESCRIPTION
+BEGINTEST
+.Bl -tag -width Ds
+.It Er one
+text
+.El
+.Tg
+.Er two
+.Sh ERRORS
+.Bl -tag -width Er
+.It Bq Er ENOENT
+text
+.El
+ENDTEST
--- /dev/null
+++ regress/mdoc/Er/tag.out_ascii
@@ -0,0 +1,16 @@
+ER-TAG(1)                   General Commands Manual                  ER-TAG(1)
+
+N\bNA\bAM\bME\bE
+     E\bEr\br-\b-t\bta\bag\bg - tagging of error number macros
+
+D\bDE\bES\bSC\bCR\bRI\bIP\bPT\bTI\bIO\bON\bN
+     BEGINTEST
+
+     one     text
+     two
+
+E\bER\bRR\bRO\bOR\bRS\bS
+     [ENOENT]           text
+     ENDTEST
+
+OpenBSD                         March 13, 2020                         OpenBSD
--- /dev/null
+++ regress/mdoc/Ev/tag.in
@@ -0,0 +1,21 @@
+.\" $OpenBSD: tag.in,v 1.1 2020/03/13 00:31:05 schwarze Exp $
+.Dd $Mdocdate: March 13 2020 $
+.Dt EV-TAG 1
+.Os
+.Sh NAME
+.Nm Ev-tag
+.Nd tagging of environment variable macros
+.Sh DESCRIPTION
+BEGINTEST
+.Bl -tag -width Ds
+.It Ev one | two
+text
+.It Xo
+.Ev three
+.Xc
+text
+.El
+.Tg
+.Ev four
+.Pp
+ENDTEST
Index: Makefile
===================================================================
RCS file: /home/cvs/mandoc/mandoc/regress/mdoc/Ev/Makefile,v
retrieving revision 1.3
retrieving revision 1.4
diff -Lregress/mdoc/Ev/Makefile -Lregress/mdoc/Ev/Makefile -u -p -r1.3 -r1.4
--- regress/mdoc/Ev/Makefile
+++ regress/mdoc/Ev/Makefile
@@ -1,6 +1,8 @@
-# $OpenBSD: Makefile,v 1.2 2014/07/02 20:18:42 schwarze Exp $
+# $OpenBSD: Makefile,v 1.5 2020/03/13 00:31:05 schwarze Exp $
 
-REGRESS_TARGETS	= font noarg
+REGRESS_TARGETS	= font noarg tag
+TAG_TARGETS	= tag
 LINT_TARGETS	= noarg
+HTML_TARGETS	= tag
 
 .include <bsd.regress.mk>
--- /dev/null
+++ regress/mdoc/Ev/tag.out_tag
@@ -0,0 +1,4 @@
+one 9
+two 9
+three 12
+four 13
--- /dev/null
+++ regress/mdoc/Ev/tag.out_markdown
@@ -0,0 +1,23 @@
+EV-TAG(1) - General Commands Manual
+
+# NAME
+
+**Ev-tag** - tagging of environment variable macros
+
+# DESCRIPTION
+
+BEGINTEST
+
+`one` | `two`
+
+> text
+
+`three`
+
+> text
+
+`four`
+
+ENDTEST
+
+OpenBSD - March 13, 2020
--- /dev/null
+++ regress/mdoc/Ev/tag.out_html
@@ -0,0 +1,9 @@
+<dl class="Bl-tag">
+  <dt><a class="permalink" href="#one"><code class="Ev" id="one">one</code></a>
+    |
+    <a class="permalink" href="#two"><code class="Ev" id="two">two</code></a></dt>
+  <dd>text</dd>
+  <dt><a class="permalink" href="#three"><code class="Ev" id="three">three</code></a></dt>
+  <dd>text</dd>
+</dl>
+<a class="permalink" href="#four"><code class="Ev" id="four">four</code></a>
--- /dev/null
+++ regress/mdoc/Ev/tag.out_ascii
@@ -0,0 +1,17 @@
+EV-TAG(1)                   General Commands Manual                  EV-TAG(1)
+
+N\bNA\bAM\bME\bE
+     E\bEv\bv-\b-t\bta\bag\bg - tagging of environment variable macros
+
+D\bDE\bES\bSC\bCR\bRI\bIP\bPT\bTI\bIO\bON\bN
+     BEGINTEST
+
+     one | two
+             text
+
+     three   text
+     four
+
+     ENDTEST
+
+OpenBSD                         March 13, 2020                         OpenBSD
--- /dev/null
+++ regress/mdoc/Fl/tag.out_markdown
@@ -0,0 +1,23 @@
+FL-TAG(1) - General Commands Manual
+
+# NAME
+
+**Fl-tag** - tagging of command line option macros
+
+# DESCRIPTION
+
+BEGINTEST
+
+**-a** | **-b**
+
+> text
+
+**-c**
+
+> text
+
+**-d**
+
+ENDTEST
+
+OpenBSD - March 13, 2020
--- /dev/null
+++ regress/mdoc/Fl/tag.out_tag
@@ -0,0 +1,4 @@
+a 9
+b 9
+c 12
+d 13
Index: Makefile
===================================================================
RCS file: /home/cvs/mandoc/mandoc/regress/mdoc/Fl/Makefile,v
retrieving revision 1.6
retrieving revision 1.7
diff -Lregress/mdoc/Fl/Makefile -Lregress/mdoc/Fl/Makefile -u -p -r1.6 -r1.7
--- regress/mdoc/Fl/Makefile
+++ regress/mdoc/Fl/Makefile
@@ -1,6 +1,9 @@
-# $OpenBSD: Makefile,v 1.13 2020/02/27 01:25:58 schwarze Exp $
+# $OpenBSD: Makefile,v 1.14 2020/03/13 00:31:06 schwarze Exp $
 
-REGRESS_TARGETS = font multiarg noarg parsed punct spacing
+REGRESS_TARGETS = font multiarg noarg parsed punct spacing tag
+TAG_TARGETS	= tag
 LINT_TARGETS	= punct
+HTML_TARGETS	= tag
+SKIP_TMAN	= tag
 
 .include <bsd.regress.mk>
--- /dev/null
+++ regress/mdoc/Fl/tag.in
@@ -0,0 +1,21 @@
+.\" $OpenBSD: tag.in,v 1.1 2020/03/13 00:31:06 schwarze Exp $
+.Dd $Mdocdate: March 13 2020 $
+.Dt FL-TAG 1
+.Os
+.Sh NAME
+.Nm Fl-tag
+.Nd tagging of command line option macros
+.Sh DESCRIPTION
+BEGINTEST
+.Bl -tag -width Ds
+.It Fl a | b
+text
+.It Xo
+.Fl c
+.Xc
+text
+.El
+.Tg
+.Fl d
+.Pp
+ENDTEST
--- /dev/null
+++ regress/mdoc/Fl/tag.out_ascii
@@ -0,0 +1,17 @@
+FL-TAG(1)                   General Commands Manual                  FL-TAG(1)
+
+N\bNA\bAM\bME\bE
+     F\bFl\bl-\b-t\bta\bag\bg - tagging of command line option macros
+
+D\bDE\bES\bSC\bCR\bRI\bIP\bPT\bTI\bIO\bON\bN
+     BEGINTEST
+
+     -\b-a\ba | -\b-b\bb
+             text
+
+     -\b-c\bc      text
+     -\b-d\bd
+
+     ENDTEST
+
+OpenBSD                         March 13, 2020                         OpenBSD
--- /dev/null
+++ regress/mdoc/Fl/tag.out_html
@@ -0,0 +1,8 @@
+<dl class="Bl-tag">
+  <dt><a class="permalink" href="#a"><code class="Fl" id="a">-a</code></a> |
+    <a class="permalink" href="#b"><code class="Fl" id="b">-b</code></a></dt>
+  <dd>text</dd>
+  <dt><a class="permalink" href="#c"><code class="Fl" id="c">-c</code></a></dt>
+  <dd>text</dd>
+</dl>
+<a class="permalink" href="#d"><code class="Fl" id="d">-d</code></a>
--- /dev/null
+++ regress/mdoc/Fo/tag.out_html
@@ -0,0 +1,9 @@
+<p class="Pp">automatic:
+    <a class="permalink" href="#first"><code class="Fn" id="first">first</code></a>()
+    and <code class="Fn">second</code>()</p>
+<p class="Pp"><a class="permalink" href="#second"><code class="Fn" id="second">second</code></a>()
+    and <code class="Fn">first</code>()</p>
+<p class="Pp">explicit:
+    <a class="permalink" href="#e3"><code class="Fn" id="e3">third</code></a>()
+    and
+    <a class="permalink" href="#e4"><code class="Fn" id="e4">fourth</code></a>(<var class="Fa">void</var>);</p>
--- /dev/null
+++ regress/mdoc/Fo/tag.out_tag
@@ -0,0 +1,4 @@
+first 9
+second 11
+e3 13
+e4 13
--- /dev/null
+++ regress/mdoc/Fo/tag.in
@@ -0,0 +1,29 @@
+.\" $OpenBSD: tag.in,v 1.1 2020/03/13 00:31:06 schwarze Exp $
+.Dd $Mdocdate: March 13 2020 $
+.Dt FO-TAG 1
+.Os
+.Sh NAME
+.Nm Fo-tag
+.Nd tagging of function name macros
+.Sh DESCRIPTION
+BEGINTEST
+.Pp
+automatic:
+.Fn first
+and
+.Fn second
+.Pp
+.Fn second
+and
+.Fn first
+.Pp
+explicit:
+.Tg e3
+.Fn third
+and
+.Tg e4
+.Fo fourth
+.Fa void
+.Fc
+.Pp
+ENDTEST
--- /dev/null
+++ regress/mdoc/Fo/tag.out_markdown
@@ -0,0 +1,27 @@
+FO-TAG(1) - General Commands Manual
+
+# NAME
+
+**Fo-tag** - tagging of function name macros
+
+# DESCRIPTION
+
+BEGINTEST
+
+automatic:
+**first**()
+and
+**second**()
+
+**second**()
+and
+**first**()
+
+explicit:
+**third**()
+and
+**fourth**(*void*)
+
+ENDTEST
+
+OpenBSD - March 13, 2020
Index: Makefile
===================================================================
RCS file: /home/cvs/mandoc/mandoc/regress/mdoc/Fo/Makefile,v
retrieving revision 1.5
retrieving revision 1.6
diff -Lregress/mdoc/Fo/Makefile -Lregress/mdoc/Fo/Makefile -u -p -r1.5 -r1.6
--- regress/mdoc/Fo/Makefile
+++ regress/mdoc/Fo/Makefile
@@ -1,8 +1,10 @@
-# $OpenBSD: Makefile,v 1.17 2020/02/27 01:25:58 schwarze Exp $
+# $OpenBSD: Makefile,v 1.18 2020/03/13 00:31:06 schwarze Exp $
 
 REGRESS_TARGETS	 = basic break eos font noarg nohead
-REGRESS_TARGETS += obsolete punct section transp warn
+REGRESS_TARGETS += obsolete punct section tag transp warn
+TAG_TARGETS	 = tag
 LINT_TARGETS	 = noarg nohead obsolete punct warn
+HTML_TARGETS	 = tag
 
 # groff-1.22.3 defects:
 # - .Fo without an argument prints unbalanced parentheses
--- /dev/null
+++ regress/mdoc/Fo/tag.out_ascii
@@ -0,0 +1,17 @@
+FO-TAG(1)                   General Commands Manual                  FO-TAG(1)
+
+N\bNA\bAM\bME\bE
+     F\bFo\bo-\b-t\bta\bag\bg - tagging of function name macros
+
+D\bDE\bES\bSC\bCR\bRI\bIP\bPT\bTI\bIO\bON\bN
+     BEGINTEST
+
+     automatic: f\bfi\bir\brs\bst\bt() and s\bse\bec\bco\bon\bnd\bd()
+
+     s\bse\bec\bco\bon\bnd\bd() and f\bfi\bir\brs\bst\bt()
+
+     explicit: t\bth\bhi\bir\brd\bd() and f\bfo\bou\bur\brt\bth\bh(_\bv_\bo_\bi_\bd)
+
+     ENDTEST
+
+OpenBSD                         March 13, 2020                         OpenBSD
--- /dev/null
+++ regress/mdoc/Ic/tag.in
@@ -0,0 +1,21 @@
+.\" $OpenBSD: tag.in,v 1.1 2020/03/13 00:31:06 schwarze Exp $
+.Dd $Mdocdate: March 13 2020 $
+.Dt IC-TAG 1
+.Os
+.Sh NAME
+.Nm Ic-tag
+.Nd tagging of internal command macros
+.Sh DESCRIPTION
+BEGINTEST
+.Bl -tag -width Ds
+.It Ic one | two
+text
+.It Xo
+.Ic three
+.Xc
+text
+.El
+.Tg
+.Ic four
+.Pp
+ENDTEST
--- /dev/null
+++ regress/mdoc/Ic/tag.out_html
@@ -0,0 +1,9 @@
+<dl class="Bl-tag">
+  <dt><a class="permalink" href="#one"><code class="Ic" id="one">one</code></a>
+    |
+    <a class="permalink" href="#two"><code class="Ic" id="two">two</code></a></dt>
+  <dd>text</dd>
+  <dt><a class="permalink" href="#three"><code class="Ic" id="three">three</code></a></dt>
+  <dd>text</dd>
+</dl>
+<a class="permalink" href="#four"><code class="Ic" id="four">four</code></a>
--- /dev/null
+++ regress/mdoc/Ic/tag.out_tag
@@ -0,0 +1,4 @@
+one 9
+two 9
+three 12
+four 13
Index: Makefile
===================================================================
RCS file: /home/cvs/mandoc/mandoc/regress/mdoc/Ic/Makefile,v
retrieving revision 1.4
retrieving revision 1.5
diff -Lregress/mdoc/Ic/Makefile -Lregress/mdoc/Ic/Makefile -u -p -r1.4 -r1.5
--- regress/mdoc/Ic/Makefile
+++ regress/mdoc/Ic/Makefile
@@ -1,6 +1,8 @@
-# $OpenBSD: Makefile,v 1.4 2014/07/02 20:18:42 schwarze Exp $
+# $OpenBSD: Makefile,v 1.8 2020/03/13 00:31:06 schwarze Exp $
 
-REGRESS_TARGETS = font noarg punct
+REGRESS_TARGETS = font noarg punct tag
+TAG_TARGETS	= tag
 LINT_TARGETS	= noarg
+HTML_TARGETS	= tag
 
 .include <bsd.regress.mk>
--- /dev/null
+++ regress/mdoc/Ic/tag.out_ascii
@@ -0,0 +1,17 @@
+IC-TAG(1)                   General Commands Manual                  IC-TAG(1)
+
+N\bNA\bAM\bME\bE
+     I\bIc\bc-\b-t\bta\bag\bg - tagging of internal command macros
+
+D\bDE\bES\bSC\bCR\bRI\bIP\bPT\bTI\bIO\bON\bN
+     BEGINTEST
+
+     o\bon\bne\be | t\btw\bwo\bo
+             text
+
+     t\bth\bhr\bre\bee\be   text
+     f\bfo\bou\bur\br
+
+     ENDTEST
+
+OpenBSD                         March 13, 2020                         OpenBSD
--- /dev/null
+++ regress/mdoc/Ic/tag.out_markdown
@@ -0,0 +1,23 @@
+IC-TAG(1) - General Commands Manual
+
+# NAME
+
+**Ic-tag** - tagging of internal command macros
+
+# DESCRIPTION
+
+BEGINTEST
+
+**one** | **two**
+
+> text
+
+**three**
+
+> text
+
+**four**
+
+ENDTEST
+
+OpenBSD - March 13, 2020
Index: Makefile
===================================================================
RCS file: /home/cvs/mandoc/mandoc/regress/mdoc/Li/Makefile,v
retrieving revision 1.4
retrieving revision 1.5
diff -Lregress/mdoc/Li/Makefile -Lregress/mdoc/Li/Makefile -u -p -r1.4 -r1.5
--- regress/mdoc/Li/Makefile
+++ regress/mdoc/Li/Makefile
@@ -1,6 +1,8 @@
-# $OpenBSD: Makefile,v 1.7 2018/12/21 16:58:49 schwarze Exp $
+# $OpenBSD: Makefile,v 1.8 2020/03/13 00:31:06 schwarze Exp $
 
-REGRESS_TARGETS = arg punct font
+REGRESS_TARGETS = arg punct font tag
+TAG_TARGETS	= tag
 LINT_TARGETS	= punct
+HTML_TARGETS	= tag
 
 .include <bsd.regress.mk>
--- /dev/null
+++ regress/mdoc/Li/tag.out_ascii
@@ -0,0 +1,17 @@
+LI-TAG(1)                   General Commands Manual                  LI-TAG(1)
+
+N\bNA\bAM\bME\bE
+     L\bLi\bi-\b-t\bta\bag\bg - tagging of literal font macros
+
+D\bDE\bES\bSC\bCR\bRI\bIP\bPT\bTI\bIO\bON\bN
+     BEGINTEST
+
+     one | two
+             text
+
+     three   text
+     four
+
+     ENDTEST
+
+OpenBSD                         March 13, 2020                         OpenBSD
--- /dev/null
+++ regress/mdoc/Li/tag.out_markdown
@@ -0,0 +1,23 @@
+LI-TAG(1) - General Commands Manual
+
+# NAME
+
+**Li-tag** - tagging of literal font macros
+
+# DESCRIPTION
+
+BEGINTEST
+
+`one` | `two`
+
+> text
+
+`three`
+
+> text
+
+`four`
+
+ENDTEST
+
+OpenBSD - March 13, 2020
--- /dev/null
+++ regress/mdoc/Li/tag.out_tag
@@ -0,0 +1,4 @@
+one 9
+two 9
+three 12
+four 13
--- /dev/null
+++ regress/mdoc/Li/tag.out_html
@@ -0,0 +1,9 @@
+<dl class="Bl-tag">
+  <dt><a class="permalink" href="#one"><code class="Li" id="one">one</code></a>
+    |
+    <a class="permalink" href="#two"><code class="Li" id="two">two</code></a></dt>
+  <dd>text</dd>
+  <dt><a class="permalink" href="#three"><code class="Li" id="three">three</code></a></dt>
+  <dd>text</dd>
+</dl>
+<a class="permalink" href="#four"><code class="Li" id="four">four</code></a>
--- /dev/null
+++ regress/mdoc/Li/tag.in
@@ -0,0 +1,21 @@
+.\" $OpenBSD: tag.in,v 1.1 2020/03/13 00:31:06 schwarze Exp $
+.Dd $Mdocdate: March 13 2020 $
+.Dt LI-TAG 1
+.Os
+.Sh NAME
+.Nm Li-tag
+.Nd tagging of literal font macros
+.Sh DESCRIPTION
+BEGINTEST
+.Bl -tag -width Ds
+.It Li one | two
+text
+.It Xo
+.Li three
+.Xc
+text
+.El
+.Tg
+.Li four
+.Pp
+ENDTEST
--- /dev/null
+++ regress/mdoc/Ms/tag.out_tag
@@ -0,0 +1,4 @@
+one 9
+two 9
+three 12
+four 13
Index: Makefile
===================================================================
RCS file: /home/cvs/mandoc/mandoc/regress/mdoc/Ms/Makefile,v
retrieving revision 1.3
retrieving revision 1.4
diff -Lregress/mdoc/Ms/Makefile -Lregress/mdoc/Ms/Makefile -u -p -r1.3 -r1.4
--- regress/mdoc/Ms/Makefile
+++ regress/mdoc/Ms/Makefile
@@ -1,6 +1,8 @@
-# $OpenBSD: Makefile,v 1.4 2014/07/02 20:18:42 schwarze Exp $
+# $OpenBSD: Makefile,v 1.8 2020/03/13 00:58:48 schwarze Exp $
 
-REGRESS_TARGETS = noarg font
+REGRESS_TARGETS = noarg font tag
+TAG_TARGETS	= tag
 LINT_TARGETS	= noarg
+HTML_TARGETS	= tag
 
 .include <bsd.regress.mk>
--- /dev/null
+++ regress/mdoc/Ms/tag.out_markdown
@@ -0,0 +1,23 @@
+MS-TAG(1) - General Commands Manual
+
+# NAME
+
+**Ms-tag** - tagging of mathematical symbol macros
+
+# DESCRIPTION
+
+BEGINTEST
+
+**one** | **two**
+
+> text
+
+**three**
+
+> text
+
+**four**
+
+ENDTEST
+
+OpenBSD - March 13, 2020
--- /dev/null
+++ regress/mdoc/Ms/tag.in
@@ -0,0 +1,21 @@
+.\" $OpenBSD: tag.in,v 1.1 2020/03/13 00:31:06 schwarze Exp $
+.Dd $Mdocdate: March 13 2020 $
+.Dt MS-TAG 1
+.Os
+.Sh NAME
+.Nm Ms-tag
+.Nd tagging of mathematical symbol macros
+.Sh DESCRIPTION
+BEGINTEST
+.Bl -tag -width Ds
+.It Ms one | two
+text
+.It Xo
+.Ms three
+.Xc
+text
+.El
+.Tg
+.Ms four
+.Pp
+ENDTEST
--- /dev/null
+++ regress/mdoc/Ms/tag.out_html
@@ -0,0 +1,9 @@
+<dl class="Bl-tag">
+  <dt><a class="permalink" href="#one"><span class="Ms" id="one">one</span></a>
+    |
+    <a class="permalink" href="#two"><span class="Ms" id="two">two</span></a></dt>
+  <dd>text</dd>
+  <dt><a class="permalink" href="#three"><span class="Ms" id="three">three</span></a></dt>
+  <dd>text</dd>
+</dl>
+<a class="permalink" href="#four"><span class="Ms" id="four">four</span></a>
--- /dev/null
+++ regress/mdoc/Ms/tag.out_ascii
@@ -0,0 +1,17 @@
+MS-TAG(1)                   General Commands Manual                  MS-TAG(1)
+
+N\bNA\bAM\bME\bE
+     M\bMs\bs-\b-t\bta\bag\bg - tagging of mathematical symbol macros
+
+D\bDE\bES\bSC\bCR\bRI\bIP\bPT\bTI\bIO\bON\bN
+     BEGINTEST
+
+     o\bon\bne\be | t\btw\bwo\bo
+             text
+
+     t\bth\bhr\bre\bee\be   text
+     f\bfo\bou\bur\br
+
+     ENDTEST
+
+OpenBSD                         March 13, 2020                         OpenBSD
--- /dev/null
+++ regress/mdoc/No/tag.out_markdown
@@ -0,0 +1,23 @@
+NO-TAG(1) - General Commands Manual
+
+# NAME
+
+**No-tag** - tagging of normal font macros
+
+# DESCRIPTION
+
+BEGINTEST
+
+one | two
+
+> text
+
+three
+
+> text
+
+four
+
+ENDTEST
+
+OpenBSD - March 13, 2020
Index: punct.out_lint
===================================================================
RCS file: /home/cvs/mandoc/mandoc/regress/mdoc/No/punct.out_lint,v
retrieving revision 1.8
retrieving revision 1.9
diff -Lregress/mdoc/No/punct.out_lint -Lregress/mdoc/No/punct.out_lint -u -p -r1.8 -r1.9
--- regress/mdoc/No/punct.out_lint
+++ regress/mdoc/No/punct.out_lint
@@ -23,3 +23,4 @@ mandoc: punct.in:72:7: WARNING: skipping
 mandoc: punct.in:75:7: WARNING: skipping empty macro: No
 mandoc: punct.in:76:7: WARNING: skipping empty macro: No
 mandoc: punct.in:84:2: WARNING: skipping empty macro: No
+mandoc: punct.in:87:6: STYLE: no blank before trailing delimiter: No a.
--- /dev/null
+++ regress/mdoc/No/tag.out_html
@@ -0,0 +1,9 @@
+<dl class="Bl-tag">
+  <dt><a class="permalink" href="#one"><span class="No" id="one">one</span></a>
+    |
+    <a class="permalink" href="#two"><span class="No" id="two">two</span></a></dt>
+  <dd>text</dd>
+  <dt><a class="permalink" href="#three"><span class="No" id="three">three</span></a></dt>
+  <dd>text</dd>
+</dl>
+<a class="permalink" href="#four"><span class="No" id="four">four</span></a>
--- /dev/null
+++ regress/mdoc/No/tag.out_ascii
@@ -0,0 +1,17 @@
+NO-TAG(1)                   General Commands Manual                  NO-TAG(1)
+
+N\bNA\bAM\bME\bE
+     N\bNo\bo-\b-t\bta\bag\bg - tagging of normal font macros
+
+D\bDE\bES\bSC\bCR\bRI\bIP\bPT\bTI\bIO\bON\bN
+     BEGINTEST
+
+     one | two
+             text
+
+     three   text
+     four
+
+     ENDTEST
+
+OpenBSD                         March 13, 2020                         OpenBSD
--- /dev/null
+++ regress/mdoc/No/tag.in
@@ -0,0 +1,21 @@
+.\" $OpenBSD: tag.in,v 1.1 2020/03/13 00:31:06 schwarze Exp $
+.Dd $Mdocdate: March 13 2020 $
+.Dt NO-TAG 1
+.Os
+.Sh NAME
+.Nm No-tag
+.Nd tagging of normal font macros
+.Sh DESCRIPTION
+BEGINTEST
+.Bl -tag -width Ds
+.It No one | two
+text
+.It Xo
+.No three
+.Xc
+text
+.El
+.Tg
+.No four
+.Pp
+ENDTEST
--- /dev/null
+++ regress/mdoc/No/tag.out_tag
@@ -0,0 +1,4 @@
+one 9
+two 9
+three 12
+four 13
Index: Makefile
===================================================================
RCS file: /home/cvs/mandoc/mandoc/regress/mdoc/No/Makefile,v
retrieving revision 1.3
retrieving revision 1.4
diff -Lregress/mdoc/No/Makefile -Lregress/mdoc/No/Makefile -u -p -r1.3 -r1.4
--- regress/mdoc/No/Makefile
+++ regress/mdoc/No/Makefile
@@ -1,6 +1,8 @@
-# $OpenBSD: Makefile,v 1.4 2014/11/17 06:44:35 schwarze Exp $
+# $OpenBSD: Makefile,v 1.7 2020/03/13 00:31:06 schwarze Exp $
 
-REGRESS_TARGETS = punct spacing
+REGRESS_TARGETS = punct spacing tag
+TAG_TARGETS	= tag
 LINT_TARGETS	= punct
+HTML_TARGETS	= tag
 
 .include <bsd.regress.mk>
--- /dev/null
+++ regress/mdoc/Tg/warn.out_html
@@ -0,0 +1,11 @@
+<p class="Pp"><mark id="start"></mark>initial text
+    <a class="permalink" href="#macro"><code class="Ic" id="macro">macro</code></a>
+    too many badstart badend whitespace <mark id="sub"></mark></p>
+<section class="Ss">
+<h2 class="Ss" id="double"><a class="permalink" href="#double">Subsection</a></h2>
+<p class="Pp">subtext</p>
+</section>
+</section>
+<section class="Sh">
+<h1 class="Sh" id="examples"><a class="permalink" href="#examples">EXAMPLES</a></h1>
+<p class="Pp">example text</p>
--- /dev/null
+++ regress/mdoc/Tg/warn.out_tag
@@ -0,0 +1,5 @@
+start 9
+macro 9
+sub 9
+double 11
+examples 14
--- /dev/null
+++ regress/mdoc/Tg/warn.out_markdown
@@ -0,0 +1,30 @@
+TG-WARN(1) - General Commands Manual
+
+# NAME
+
+**Tg-warn** - warnings about tagging macros
+
+# DESCRIPTION
+
+BEGINTEST
+
+initial
+text
+**macro**
+too many
+badstart
+badend
+whitespace
+
+## Subsection
+
+subtext
+
+# EXAMPLES
+
+example
+text
+
+ENDTEST
+
+OpenBSD - March 13, 2020
--- /dev/null
+++ regress/mdoc/Tg/Makefile
@@ -0,0 +1,8 @@
+# $OpenBSD: Makefile,v 1.1 2020/03/13 00:31:06 schwarze Exp $
+
+REGRESS_TARGETS = warn
+TAG_TARGETS	= warn
+LINT_TARGETS	= warn
+HTML_TARGETS	= warn
+
+.include <bsd.regress.mk>
--- /dev/null
+++ regress/mdoc/Tg/warn.out_lint
@@ -0,0 +1,6 @@
+mandoc: warn.in:16:2: WARNING: skipping empty macro: Tg
+mandoc: warn.in:16:8: ERROR: skipping excess arguments: Tg ... ignored
+mandoc: warn.in:18:5: ERROR: skipping tag containing whitespace: Tg \&badstart
+mandoc: warn.in:20:11: ERROR: skipping tag containing whitespace: Tg badend\&
+mandoc: warn.in:22:10: ERROR: skipping tag containing whitespace: Tg white space
+mandoc: warn.in:34:2: WARNING: skipping empty macro: Tg
--- /dev/null
+++ regress/mdoc/Tg/warn.out_ascii
@@ -0,0 +1,19 @@
+TG-WARN(1)                  General Commands Manual                 TG-WARN(1)
+
+N\bNA\bAM\bME\bE
+     T\bTg\bg-\b-w\bwa\bar\brn\bn - warnings about tagging macros
+
+D\bDE\bES\bSC\bCR\bRI\bIP\bPT\bTI\bIO\bON\bN
+     BEGINTEST
+
+     initial text m\bma\bac\bcr\bro\bo too many badstart badend whitespace
+
+   S\bSu\bub\bbs\bse\bec\bct\bti\bio\bon\bn
+     subtext
+
+E\bEX\bXA\bAM\bMP\bPL\bLE\bES\bS
+     example text
+
+     ENDTEST
+
+OpenBSD                         March 13, 2020                         OpenBSD
--- /dev/null
+++ regress/mdoc/Tg/warn.in
@@ -0,0 +1,34 @@
+.\" $OpenBSD: warn.in,v 1.1 2020/03/13 00:31:06 schwarze Exp $
+.Dd $Mdocdate: March 13 2020 $
+.Dt TG-WARN 1
+.Os
+.Sh NAME
+.Nm Tg-warn
+.Nd warnings about tagging macros
+.Sh DESCRIPTION
+BEGINTEST
+.Pp
+.Tg start
+initial
+text
+.Tg
+.Ic macro
+.Tg "" ignored arguments
+too many
+.Tg \&badstart
+badstart
+.Tg badend\&
+badend
+.Tg "white space"
+whitespace
+.Tg sub
+.Tg double
+.Ss Subsection
+subtext
+.Tg examples
+.Sh EXAMPLES
+example
+text
+.Pp
+ENDTEST
+.Tg
--
 To unsubscribe send an email to source+unsubscribe@mandoc.bsd.lv

^ permalink raw reply	[flat|nested] only message in thread

only message in thread, back to index

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-03-13 15:33 mandoc: Split tagging into a validation part including prioritization in schwarze

source@mandoc.bsd.lv

Archives are clonable: git clone --mirror http://inbox.vuxu.org/mandoc-source

Example config snippet for mirrors

Newsgroup available over NNTP:
	nntp://inbox.vuxu.org/vuxu.archive.mandoc.source


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git