How to add formatting rules to an already formatted raw block?

Hi everybody!
I’m using Typst for my work and I have some code blocks to show. Inside this code blocks, I want to highlight some parts that are important. In order to do this, I’m using two main functions

  • One is used to make text Bold (I’m not using * Bold * markdown)
  • One contains a Regex and I’m looking for the bold_function and I apply a formatting rule to make the content of the bold_function bold

When I don’t choose any language for formatting the code blocks (add some color to my code), everything is working. But when I want to put some colors by adding a language to the raw function, my custom formatting isn’t working.

If I don’t use any language when declaring the raw block, it’s working well:

```
dfir@dfir-mbp ~ % #text_bold[hello]
```

But when I add the language details in the raw block, the language formatting is erasing my custom one:

```bash
dfir@dfir-mbp ~ % #text_bold[hello]
```

This is a part of my code, I’m using a function to do this:

//------------------------------------------------------------
// Bold Function
//------------------------------------------------------------

#let text_bold(body) = {text( weight: "bold", [#body])}

//------------------------------------------------------------
// Raw converter Function
//------------------------------------------------------------

#let raw_converter(body) = {
  //----------------------------------------------------------
  // Find all matches with function #text_bold
  //----------------------------------------------------------
 
  let matches_text_bold = body.text.matches(regex("#text_bold\[[^]]+\]{1,10}"))

  //----------------------------------------------------------
  // Adding the rules
  //----------------------------------------------------------
  show raw.where(): show_rule => {
    //--------------------------------------------------------
    // Set the #text_bold rule
    //--------------------------------------------------------
    for it in range(matches_text_bold.len()) {
      // Creating the value inside the #text_bold function
      let text_value = matches_text_bold.at(it).text.replace("#text_bold[","").replace("]","")
      show_rule = {
        // Remove the function to only keep the text inside
        show matches_text_bold.at(it).text: text_bold(text_value)
        show_rule
      }
    }
    show_rule
  }
  
  body
}

#let terminalbox(
  title: "",
  body_box,
) = {
      [... Creating the header of the Box ...]
      //------------------------------------------------------
      //  Body of the Box
      //------------------------------------------------------
      rect(
        width: 100%, 
        radius: (bottom-right: 6pt, bottom-left: 6pt),
        fill: body_color
        inset: (y: 8pt, x: 5pt),
        stroke: (top: 0.8pt + line_color)
        text(
          box_text_size, 
          raw_converter(body_box),
          fill: box_text_color, 
          font: box_text_type,
        ),
      ),
    )
  )
}

#terminalbox(title-bash: "Terminal",
```bash
dfir@dfir-mbp ~ % #text_bold[hello]
```)

Hey @Greco, welcome to the forum! I’ve moved your post to the Questions category as you’re asking a question about Typst. Please keep this in mind in the future!

In addition, I’ve changed your question post’s title to better fit the guidelines for the Questions category: How to post in the Questions category
For future posts, make sure your title is a question you’d ask to a friend about Typst. :wink:

Hi there and welcome to the Forum!
Sadly your code snippet doesn’t wok because you left out some things from your code that are needed for it to work and

dfir@dfir-mbp ~ % #text_bold[hello]

by itself does not run the command.
So could you please give us the missing code pieces? You can copy your snippet into a new document to check if it works. Alternatively you could share your project via a link to to project on the web app.

I assume the regex you’re using looks like this:

#show regex("#text_bold\[(.*)\]"): it => strong(it.text.slice(11, -1))

```
dfir@dfir-mbp ~ % #text_bold[hello]
```

the problem is that a regex can only replace contiguous text, and syntax highlighting splits up text into separate parts. consider this:

#show text: box.with(stroke: red, inset: 1pt)

```bash
dfir@dfir-mbp ~ % #text_bold[hello]
```

image

As you can see, the # (despite being the same color) is a separate token from the text_bold[hello] and the regex won’t match. The problem gets worse if you consider that the highlighted text (here hello) may be more complex. So removing the # from your regex won’t help.

What you need to do is resolve the regex before styling and put a syntactically simple “placeholder” into the raw text; here’s an approach to do this:

#show raw: it => {
  let (text, lines, ..fields) = it.fields()

  // from text, create new-text that contains PLACEHOLDERs we can later replace with replacements
  let replacements = ()
  let new-text = ""
  let last-match-end = 0
  for (index, match) in text.matches(regex("#text_bold\[(.*)\]")).enumerate() {
    let unmatched = text.slice(last-match-end, match.start)
    let placeholder = "PLACEHOLDER" + str(index)

    let replacement = match.captures.at(0)
    // we want syntax highlighting of the replacement
    replacement = raw(lang: it.lang, replacement)
    // we want it bold
    replacement = strong(replacement)
    // raw text is automatically a bit smaller to better fit non-raw text;
    // this is not appropriate since we put the raw text into other raw text
    // so we counteract that change
    replacement = std.text(1.25em, replacement)

    new-text += unmatched + placeholder
    replacements.push(replacement)
    last-match-end = match.end
  }
  new-text += text.slice(last-match-end)

  if replacements.len() == 0 {
    // nothing replaced, prevent recursive show rule
    return it
  }

  // re-insert the replacements into the highlighted code block
  show regex("PLACEHOLDER\d+"): it => {
    let index = int(it.text.slice(11))
    replacements.at(index)
  }

  raw(new-text, ..fields)
}

and then:

```bash
# with hightlighting
dfir@dfir-mbp ~ % #text_bold[ls | grep a]
# for comparison
dfir@dfir-mbp ~ % ls | grep a
```

image

What does the code do?

  • at the core it’s still your approach: use a regex to find parts of the code to replace, and use a show rule to do the replacement. however, there is a layer of indirection
  • first, the raw block is scanned for occurrences of the regex to remove them. This means that, manually, all “ordinary” text is preserved and placeholders are inserted. While doing that, the replaced content is analyzed, and the replacements are constructed for later
  • then, a show rule is created that scans not for the original “markup” but the placeholders
  • finally, a new raw block is created with the adapted text
  • to make sure the replacement doesn’t run over and over, raw blocks without matches come out unmodified.