htmlcsstext-alignmenttypographytabstop

How can I align code in code listings in HTML?


I would like to have a code listing with alignment in HTML. This is an example of how it is supposed to look:

code listing in a proportional font with alignment

In this example, the beginning of the second line is aligned with the beginning of the last line, the parentheses before “url”, “method” and “parameters” are aligned, and the columns of symbols starting with colons are aligned. The distance from “url” to “:reader” and the like is supposed to be one text space. The distance between the two parentheses at the beginning of the second line is supposed to be zero, but one text space is acceptable.

How can I have that in HTML? Here are my ideas:

I tried to do it with tables. The following snippet is the best that I could do. The code is ugly and the vertical alignment of the parenthesis on the second line is off.

body {
  font-family: sans-serif;
}
pre {
  font-family: sans-serif;
  display: inline;
}
table {
  display: inline-table;
}
td {
  vertical-align: top;
}
.indent {
  width: 1.5em; display: inline-block;
}
<!doctype html><html>
<body>
  (defclass request ()<br>
    <span class="indent"></span><table><tr><td>(</td><td><table><tr><td>(url</td><td>:reader request-url</td></tr>
          <tr><td></td><td>:initarg :url</td></tr>
          <tr><td></td><td>:type string</td></tr>
          <tr><td></td><td>:documentation "<pre>Request URL.</pre>")</td></tr></table></td>
        <tr><td></td><td><table><tr><td>(method</td><td>:reader request-method</td></tr>
          <tr><td></td><td>:initarg :method</td></tr>
          <tr><td></td><td>:initform :get</td></tr>
          <tr><td></td><td>:type keyword</td></tr>
          <tr><td></td><td>:documentation "<pre>Request method, e.g :get, :post.</pre>")</td></tr></table></td></tr>
        <tr><td></td><td><table><tr><td>(parameters</td><td>:reader request-parameters</td></tr>
          <tr><td></td><td>:initarg :parameters</td></tr>
          <tr><td></td><td>:initform nil</td></tr>
          <tr><td></td><td>:type association-list</td></tr>
          <tr><td></td><td>:documentation "<pre>The request parameters, as an association list.</pre>"))</td></tr></table></td></tr>
    </table><br>
    <span class="indent"></span>(:documentation "<pre>A general HTTP request.</pre>"))
</body>
</html>

Which solution is the most viable? is there a cleaner solution that I missed?


Solution

  • I implemented custom tabstops (the second idea) how I described it in the question.

    The HTML code has tags marking alignment anchors (<align-anchor>). Then, it has tags for elements having an id as the attribute anchor (<align->); they are supposed to extend to the left border of the element with the given id. The id could be of any element, but I use only <align-anchor> for clarity. There are also <indent->; they are supposed to indent text from the beginning of the line, and they have a fixed width.

    Questions about how to make this better:

    How can I have custom self-closing HTML tags? all tags described in the previous paragraph are empty, so they don't need a closing tag, and not having it would make it much cleaner.

    How can it be done so that the code is aligned first and then displayed? If I load the page, the code is not aligned for a short time, and then gets aligned. I'd like the code to be aligned as soon as it's displayed.

    The code

    function main() {
      let tabs = document.getElementsByTagName("align-");
      for (let tab of tabs) {
        let x = tab.offsetLeft;
        let tabstop = document.getElementById(tab.getAttribute("anchor"));
        let tabstopX = tabstop.offsetLeft;
        let width = tabstopX - x;
        tab.style.width = `${width}px`;
      }
    }
    
    window.onload = main
    code-block {
      white-space: pre-line;
      font-family: sans-serif;
    }
    pre {
      font-family: sans-serif;
    }
    code-block pre {
      display: inline;
    }
    indent- {
      display: inline-block;
      width: 1.5em;
    }
    align- {
      display: inline-block;
    }
    <!doctype html>
    <html>
    <body>
      <code-block>
        (defclass request ()
          <indent-></indent->(<align-anchor id=1></align-anchor>(url <align-anchor id=2></align-anchor>:reader request-url
              <align- anchor=2></align->:initarg :url
              <align- anchor=2></align->:type string
              <align- anchor=2></align->:documentation "<pre>Request URL.</pre>")
            <align- anchor=1></align->(method <align-anchor id=3></align-anchor>:reader request-method
              <align- anchor=3></align->:initarg :method
              <align- anchor=3></align->:initform :get
              <align- anchor=3></align->:type keyword
              <align- anchor=3></align->:documentation "<pre>Request method, e.g :get, :post.</pre>")
            <align- anchor=1></align->(parameters <align-anchor id=4></align-anchor>:reader request-parameters
              <align- anchor=4></align->:initarg :parameters
              <align- anchor=4></align->:initform nil
              <align- anchor=4></align->:type association-list
              <align- anchor=4></align->:documentation "<pre>The request parameters, as an association list.</pre>"))
          <indent-></indent->(:documentation "<pre>A general HTTP request.</pre>"))
      </code-block>
    </body>
    </html>