How can I indent text to a specified tabstop for code presentation?

I would like to present some code with alignment. How can I do that effectively?

Consider this example (source):

An example of Clojure code aligned in a proportional font

In Latex, it would be done this way:

\begin{tabbing}
    (equals [this that] \\
    \quad (or \=(identical? this that) \\
            \>(and \=(instance? java.util.Set that) \\
            \>  \>(= (count this) (count that)) \\
            \>  \>(every? \#(contains? this \%) that))))
\end{tabbing}

In Typst, I did that by padding the code with hidden text because I don’t know a better way:

(equals [this that] \
#h(1em) (or (identical? this that) \
#hide[#h(1em) (or ]\(and (instance? java.util.Set that) \
#hide[#h(1em) (or \(and ]\(= (count this) (count that)) \
#hide[#h(1em) (or \(and ]\(every? \#(contains? this %) that))))

That is very ineffective because I need to repeat all text before the tabstop on every line where it is used.

How can I do it better? Is there a way to set tabstops in Typst? Does any package for code typesetting do that?

Or a wild idea: Is there a way to set the width of initial spaces in raw mode to the width of the characters above? This would allow alignment with spaces to work with proportional fonts. It has been implemented in spaceship-mode for Emacs, but I haven’t seen it anywhere else.

Here’s the code:

/// Align point
#let ap = metadata("tabbing.align-point")
/// Next align point
#let nap = metadata("tabbing.next-align-point")
#let tabbing(code) = {
  assert(
    code.func() in ([].func(), [ ].func(), [A].func()),
    message: "Tabbing function accepts content only",
  )


  if code.func() != [].func() {
    // No align points inside
    return code
  }

  let lines = code.children.split(linebreak())
  let align-text = ()
  let res = ()

  for line in lines {
    assert(
      line.filter(it => it == nap).len() <= 1,
      message: "Each line can have at most one next-align-point",
    )
    let pieces = line.split(ap)
    let new-line = []
    for (ind, piece) in pieces.enumerate() {
      if ind == 0 {
        new-line += place(
          piece.join(),
          dx: 0pt,
        )
      } else {
        new-line += context place(
          piece.join(),
          dx: measure(align-text.slice(0, ind).join()).width,
        )
      }
      if nap in piece {
        align-text = align-text.slice(0, ind) + (piece.split(nap).at(0).join(),)
      }
    }
    new-line += linebreak()
    res.push(new-line)
  }

  return res.join()
}

In your example it should be

#tabbing([
  (equals [this that] \
  #h(2em) (or #nap;(identical? this that) \
  #ap;(and #nap;(instance? java.util.Set that) \
  #ap #ap;(= (count this) (count that)) \
  #ap #ap;(every? \#(contains? this %) that))))
])
Result

Result-1

Another example:

#tabbing([
  #lorem(1) #nap\
  #text(size: 1.5em)[OO]ops! #ap #text(fill: gray)[Overlap!] \
  Reseting it #nap to \
  #ap #sym.arrow.t here
])
Result

Resuslt-2

You can see that there are baseline and alignment issues, but this is just a simple workaround and if you need more I guess waiting for the official align support in markup mode would be a better option.

Thanks. How did you make that the code can be rendered in the comment with the button “Result”?

I rendered it locally on my machine using VS Code + Tinymist and take a screenshot and paste it in the “Hide Details” here: