[comp.text] [nt]roff problem: hanging tags

simpson%trwarcadia.uucp@usc.edu (Scott Simpson) (05/04/89)

I wrote some [nt]roff macros to automatically figure out the width of 
hanging tags and to use the longest hanging tag to format a list of 
hanging tags (like an \halign in TeX).  I tried using them in the -man
macros for the FILE section like

	.SH FILES
	.FB /etc/motd
	The message of the day.
	.FE
	.FB /etc/rc.local
	Local system startup file.
	.FE
	.FS

but the output comes out too far over horizontally.  That is, I want

	/etc/motd The message of the day.
	/etc/rc   Local system startup file.

and I get
					/etc/motd The message of the day.
					/etc/rc   Local system startup file.

Any ideas?

Here are the macros (mostly comments):

.\" %W% %G%
.\" General purpose macros.	Scott Simpson.
.\" Macros provided:
.\"	.FB file_name		Begin a File Section line in the -man
.\"				macros.  For example, in the -man section
.\"				where you say .SH FILES, you could say
.\"					.FB /etc/motd
.\"					The message of the day.
.\"					.FE
.\"					.FS
.\"				to format the file line.  .TP inserts a 
.\"				blank line so we don't use it.  Each .FB
.\"				must end with a .FE and a final .FS must end
.\"				the last .FB-.FE pair.  The widest tag is
.\"				computed and used for all the .FS-.FE pairs.
.\"	.FE			Terminates an .FB command.
.\"	.FS			Flushes a file section.
.\"
.\" This file uses the following:
.\"	Number registers:
.\"		mw	maximum width of a hanging tag
.\"	Macros, diversions and strings:
.\"		FD	diversion for .FB, .FE, .FS
.\"
.nr mw 0	\"indent size of hanging tag
.de FB		\"File Begin section
.if \w'\\$1'u+1m>\\n(mw .nr mw \w'\\$1'u+1m
.nf		\"get output in no-fill mode
.da FD		\"start diversion
.ft R		\"put environment in sane state
.ps 10
.vs 12p
.br		\"start a new paragraph
.ti -\\n(mwu	\"dedent
\\$1\\t\c
..
.de FE		\"File section End
.di		\"end diversion
..
.de FS		\"Flush Section
.in +\\n(mwu	\"set indentation to size of largest tag
.ta \\n(mwu	\"set tab to size of hanging tag
.fi		\"set fill mode
.FD		\"output diversion
.\" Adding a comment to the end of the next line causes an infinite loop!
.\" Remove the macro request.
.rm FD
.in -\\n(mwu	\"dedent indent
.nr mw 0	\"reset hanging tag size
..
	Scott Simpson
	TRW Space and Defense Sector
	oberon!trwarcadia!simpson  	(UUCP)
	trwarcadia!simpson@usc.edu	(Internet)

morrell@hpsal2.HP.COM (Michael Morrell) (05/06/89)

The following changes seemed to work for me (note I can not replicate the
infinite loop when there is a comment on the "rm FD" line).
According to the documentation you should process diversions normally (i.e.,
in fill mode), then output them in no-fill mode to avoid further processing.

    Michael

    .de FB		\"File Begin section
    .if \w'\\$1'u+1m>\\n(mw .nr mw \w'\\$1'u+1m
    .nf		\"get output in fill mode
    .da FD		\"start diversion
    .ft R		\"put environment in sane state
    .ps 10
    .vs 12p
    .br		\"start a new paragraph
    .ti -\\n(mwu	\"dedent
    \\$1\t\c
    ..
    .de FS		\"Flush Section
    .in +\\n(mwu	\"set indentation to size of largest tag
    .ta \\n(mwu	\"set tab to size of hanging tag
    .nf		\"set no-fill mode
    .FD		\"output diversion
    .\" Adding a comment to the end of the next line causes an infinite loop!
    .\" Remove the macro request.
    .rm FD		\"test comment
    .in -\\n(mwu	\"dedent indent
    .nr mw 0	\"reset hanging tag size
    ..

msb@sq.com (Mark Brader) (05/11/89)

> I wrote some [nt]roff macros to automatically figure out the width of 
> hanging tags and to use the longest hanging tag to format a list of 
> hanging tags (like an \halign in TeX). ...
> 	.FB /etc/motd
> 	The message of the day.
> 	.FE
> 	.FB /etc/rc.local
> 	Local system startup file.
> 	.FE
> 	.FS

If you get too much indentation when using diversions, typically it's
because you forgot that the indentation in effect at diversion time is
added to that in effect when the diversion is read back.  A second cause
of indentation problems is forgetting that a tab character is NOT whitespace
in troff, even on request lines, but has a special meaning.

But for this task you don't want to use diversions anyway.  The reason
is that a diversion contains a block of *formatted* text, but you don't want
to format the text until after you know what the correct indentation is.
A better method to use in this case is to have the .FB macro begin a
*macro definition* which is ended by the .FE call.  Remember that there
are two ways to define a macro, though only one is usually used:

  .de FD		|		.de FD FE
  body			|		body
  ..			|		.FE

And these are identical except that in the second case the .FE line will
additionally be treated as a call to macro/request FE if there is one.
(You can put arguments to FE on the line, too, if you want.)  And there's
no rule against starting a definition from within a macro call and having
it continue to read text from below the macro.  Oh, and all this applies
to the .am request just as to the .de.

Putting it all together, we get:

  .de FB                                   \" File Begin section
  .if \w'\\$1'u>\\n(mwu .nr mw \w'\\$1'u   \" accumulate max tag width in mw
  .am FD FE                                \" append paragraph to FD until .FE
  .ti -\\\\n(mwu                           \" tag isn't indented
  \\$1\t\c                                 \" tag, and tab to body position
  ..
  .de FS                                   \" Flush Section
  .nr mw +1m                               \" allow 1m gutter after widest tag
  .in +\\n(mwu                             \" indent around tags
  .ta \\n(mwu                              \" set tab for body position
  .FD                                      \" write out the section
  .rm FD                                   \" and remove it
  .in -\\n(mwu+1m                          \" restore indent
  ..

No .FE macro is required, and the above will work even in the presence
of existing indentation (because all indentation changes are relative).

There is one thing to note:  the argument of the .FB macro must look
like a single word to troff, to avoid padding inside it.  If it's a
filename that's no problem, but for other things it might be, so you
have to use \  (backslash-space) for word spaces within it.

This could be avoided by putting the hanging tab on a separate output
line and using .sp -1 to move back up, but then you'd have to take
special measures (which I discussed in this newsgroup a month or two ago)
to avoid springing traps between the tag and the rest of the line.

In my code above, note that there are spaces and not tabs between
the requests and the comments.  The bug that put nroff into an
infinite loop when you put a comment on the .rm request was triggered
by your use of a tab there.  (This bug is fixed in sqtroff, the version
that we sell as part of the package SoftQuad Publishing Software.)
There are other places where tabs on request lines can cause trouble
and my advice is to avoid them altogether.

-- 
Mark Brader		   "I don't care HOW you format   char c; while ((c =
SoftQuad Inc., Toronto	    getchar()) != EOF) putchar(c);   ... this code is a
utzoo!sq!msb, msb@sq.com    bug waiting to happen from the outset." --Doug Gwyn

This article is in the public domain.