miestrode / sourcerer

Typst package for displaying stylized source code blocks, with some extra features.
MIT License
4 stars 1 forks source link

Feature request: Line highlighting #2

Open data-niklas opened 3 months ago

data-niklas commented 3 months ago

I would like to highlight certain lines with a certain colors, e.g.: lines 3-5 in a light purple.

A simple interface would take a highlighted-lines parameter, which is a list of all lines to highlight, and there would be a highlight-color parameter with the color.

A more complex interface would take a color for each lines, so that there could be different highlight colors in the same code block.

I currently have a small sketch that wraps around the code function:

#let highlight(lines, line-spacing: 5pt, max-width: false, bg: color.hsv(300deg, 100%, 70%, 20%), body) = {
  let attrs = (:)
  if max-width {
    attrs.insert("width", 1fr)
  }
  show raw.line: line => {
      if lines.contains(line.number) and line.count > 1{
        let line-height = measure(line).height

        box(height: line-height,
          move(dy: -line-spacing/2,
            box(line,
              fill: bg,
              inset: (y: line-spacing/2),
              height: line-height + line-spacing,
              ..attrs
            )
          )
        )
      }
      else {
        line
      }
    }
  body
}

The current problems are:

Using the line-spacing solves the problem of white spaces between highlighted lines. The new container now has the same line height as before, but extends slightly above and below with the highlighted background, while showing the line in the middle using the inset.

image

#highlight((1,2), max-width: true, code(```py
def a
  b
c```, lang:"Py"))

What seems to find the correct width for all lines is using measure to find the width of the child raw element.

Maybe some of that behavior could be integrated into the sourcerer library.

~ Update ~: The following logic currently solves the line width problem, line spacing and allows different highlight colors:

#let highlight(lines, line-spacing: 5pt, bg: color.hsv(300deg, 100%, 70%, 20%), body) = context {
  let attrs = (:)
  if "child" in body.fields() {
   // Detect the code function
    let maximum-width = 1fr
    attrs.insert("width", maximum-width)
  }
  show raw.line: line => {
      let found-hl = lines.find(l => (type(l) == int and l == line.number) or (type(l) == array and l.at(0) == line.number))
      if found-hl != none and line.count > 1{
        let line-height = measure(line).height
        let line-bg = bg
        if type(found-hl) == array {
          line-bg = found-hl.at(1)
        }
        box(height: line-height,
          move(dy: -line-spacing/2,
            box(line,
              fill: line-bg,
              inset: (y: line-spacing/2),
              height: line-height + line-spacing,
              ..attrs
            )
          )
        )
      }
      else {
        line
      }
    }
  body
}
miestrode commented 2 months ago

Hello! Sorry for the late reply. I've been kind of busy, working on other projects.

Highlighting lines is a feature I really want to support, especially considering that with it, I would be able to support zebra coloring for the lines.

Briefly, we currently display the code as a table nested in a block. The block is breakable, and so looks good while being broken across multiple pages. This is important to me.

To do line highlighting well, we need to instead display the code as a stack (or grid, or table) of rectangles, or a similar element, so we can style them individually.

Originally, I thought combining this with the page breakability was impossible, or really annoying to do, because you had to hijack the headers and footers for the page. Recently however, repeatable table headers and footers were added, and I think I can now come up with a much better solution.

I am currently therefore working on an official implementation of your feature suggestion.