Again with man pages and BBEdit

Julia Evans has been posting on Mastodon recently about the GNU Project’s insistence on documenting its commands through info pages instead of man pages and (the following may be biased by my own thoughts) how absolutely awful that is. The posts reminded me that although I wrote about how I open and read man pages in BBEdit last year, I never showed how I use references to related man pages as links. Time to change that.

So you don’t have to go through the terrible burden of reading an earlier blog post, here’s the source code of the command I use from the Terminal (or iTerm) to open man pages in a new BBEdit window:

 1  #!/bin/bash
 2  
 3  # Interpret the arguments as command name and section. As with `man`,
 4  # the section is optional and comes first if present.
 5  if [[ $# -lt 2 ]]; then
 6    cmd=${1}
 7    sec=''
 8  else
 9    cmd=${2}
10    sec=${1}
11  fi
12  
13  # Get the formatted man page, filter out backspaces and convert tabs
14  # to spaces, and open the text in a new BBEdit document. Set the title
15  # of the window and scroll to the top.
16  man $sec $cmd | col -bx | bbedit --view-top --clean -t $cmd

This is slightly different from the code I originally posted. It incorporates options suggested by readers to the bbedit command in the pipeline of Line 16:

  1. --view-top puts the cursor and the scroll position at the top of the document.
  2. --clean sets the state of the document to unmodified so you can close it without getting the “do you want to save this?” warning.
  3. -t $cmd sets the title of the document to the command name.

I’ve also changed the name of the command from bman to bbman to better fit the naming pattern set by bbedit, bbfind, and bbdiff. So if I type

bbman ls

at the command line, a new BBEdit window will open

with the text of the ls man page. The bold characters that I’d see if I ran

man ls

don’t appear in the BBEdit window, but I’ve never gotten any value out of that limited sort of text formatting, so I don’t miss it.

Although most of what I do in a man page is search and read, sometimes I like to use the hints within the text to open a new, related man page. So I wrote an AppleScript (BBEdit has great AppleScript support) that uses the cursor postion or the selected text to open a new page. Here’s how it works:

Say I’m in SEE ALSO section of the ls man page, and I want to open the chmod man page that’s referred to there. I can either double-click to select chmod or just single-click to put the cursor within the word.

Man page in BBEdit

I then select Man Page from the Scripts menu—or, more likely, use the ⌃⇧M shortcut I’ve assigned to it—and up will pop a new window with the chmod man page.

Here’s the Man Page AppleScript:

 1  use AppleScript version "2.4" -- Yosemite (10.10) or later
 2  use scripting additions
 3  
 4  -- This script is expected to be run either with the command name selected (as if
 5  -- by double-clicking) or with the cursor within the command name. The section
 6  -- (in parentheses) may be immediately after the command name.
 7  
 8  -- Function for getting the man page section from between parenthesis.
 9  -- Input is the character position of the opening parenthesis (if present).
10  -- Returns the section or an empty string.
11  on getSection(parenPos)
12    tell application "BBEdit"
13      if (character parenPos of front document as text) is "(" then
14        set secStart to parenPos + 1
15        set secEnd to find ")" searching in front document
16        set secEnd to (characterOffset of secEnd's found object) - 1
17        return characters secStart through secEnd of front document as text
18      else
19        return ""
20      end if
21    end tell
22  end getSection
23  
24  -- Start by selecting the word the cursor is in (via ⌥←, ⌥⇧→) if there isn't already a selection.
25  tell application "BBEdit"
26    if length of selection is 0 then
27      tell application "System Events"
28        key code 123 using option down
29        delay 0.125
30        key code 124 using {option down, shift down}
31        delay 0.125
32      end tell
33    end if
34  end tell
35  
36  -- Set the command name according to the selection and the section according to
37  -- whatever may be in parentheses immediately after the command name. This is
38  -- in a new tell block to ensure that the selection has been updated by the
39  -- previous tell block.
40  tell application "BBEdit"
41    set cmdName to selection as text
42    set parenPos to (characterOffset of selection) + (length of selection)
43    set manSection to my getSection(parenPos)
44  end tell
45  
46  -- Get the man page and pipe it through col to delete backspaces and expand tabs. Then
47  -- pipe that to bbedit with appropriate options. The --clean option means the new
48  -- document is treated as unmodified so it can be closed without a confirmation dialog.
49  set manCmd to "man " & manSection & " " & cmdName & " | col -bx "
50  set manCmd to manCmd & "| /usr/local/bin/bbedit --view-top --clean -t " & cmdName
51  do shell script manCmd

I think it’s pretty well commented. You can see that Lines 48–51 invoke the same shell pipeline used in the bbman script. The main features that make this different from the shell script are:

  1. Handling the case in which no text is selected but the cursor is within the name of the command. Lines 25–34 check for this condition and select the enclosing word by simulating ⌥← followed by ⌥⇧→.
  2. Figuring out the man page section by looking inside the parentheses that may follow the name of the command. That’s handled by the getSection function in Lines 11–22, which uses the character position of the end of the command name to start its search.

Over the years, I’ve seen lots of ways to turn man pages into HTML, which would make the linking more natural. But they’ve all seemed more trouble than they’re worth. The trick of passing the man output through col -bx to turn it into plain text is something I’ve always come back to, whether my preferred text editor has been BBEdit, TextMate, or (on Linux) NEdit.