How can I correctly highlight a line by line code, using highlight.js (React)?
Asked Answered
S

2

7

I need to highlight a block of code but at the same time, I need the code to be placed on separate and numbered lines, to leave comments for each line of code, just as can be done on Github. I managed to do this but because I highlight the code for each line, the "SQL" code that is on several lines will not be highlighted correctly (only the first line is highlighted) and I fail to fix this. Do you have any suggestions?

       if (data.success === 1) {
          setCodeLanguage(translateLanguage(data.code.language));
          let rows = [];
          data.code.source_code.split("\n").forEach((line, index) => {
            rows.push(
              <tr key={index} className="line">
                <td className="line-number">{index + 1}</td>
                <td id={"plus" + index} className="plus-square-line">
                  <PlusSquareTwoTone className="plus-square" />
                </td>
                <td id={"codeblock" + index}
                  className={'language-' + codeLanguage} style={rowStyle}>
                  {line}
                </td>
              </tr>
            );
          });
          setCodeRows(rows);
          for (let i = 0; i < codeRows.length; ++i) {
            hljs.highlightBlock(document.getElementById("codeblock" + i));
          } 
Sufficient answered 9/10, 2020 at 13:12 Comment(0)
S
3

You can't simply split the output on \n because spans can cross line boundaries:

var x = <span class="string>"This is a
really long string
that spans multiple lines
super annoying"</span>

You have to write code to turn this into:

var x = <span class="string">"This is a</span>
<span class="string">really long string</span>
<span class="string">that spans multiple lines</span>
<span class="string">super annoying"</span>

IE, at any given moment you have to keep track of all open tags and close them when one line ends, then open them before the next line starts.

Suffice to say this is not really a typical use case for Highlight.js, so you can of have to build it all yourself.


There is no easy way to do this, but if you access the raw parse tree (rather than the generated HTML) you could write something that walked it node by node and figured out where the lines are. How to get access to the parse tree object (the emitter):

highlight(code).__emitter

Or you can simply replace the emitter with your own custom emitter. See the source file to learn about the API you'd need to implement:

https://github.com/highlightjs/highlight.js/blob/master/src/lib/token_tree.js

Then you'd have to walk the tree, keep track of which tags are open and when you found a line end you'd need to close the tags... re-opening them again on the next line. IE, pretty much you need to start from the parse tree and write your own HTML rendering engine.

Please note: The whole __emitter API is not considered part of the public API and could change or break at any time in future updates - though generally it should be "fairly safe" to use as long as you make sure to test new versions. I have no plans to change it significantly in the near future.

[Disclaim: I'm the current Highlight.js maintainer.]

Sarcocarp answered 18/10, 2020 at 0:29 Comment(0)
W
1

We had to implement line numbers recently, here's our implementation in Typescript to add it as a plugin:

hljs.addPlugin({
  "after:highlight": (params: { value: string; }) => {
    const openTags: string[] = [];
    
    params.value = params.value.replace(/(<span [^>]+>)|(<\/span>)|(\n)/g, match => {
      if (match === "\n") {
        return "</span>".repeat(openTags.length) + "\n" + openTags.join("");
      }
      
      if (match === "</span>") {
        openTags.pop();
      } else {
        openTags.push(match);
      }
      
      return match;
    });
  },
});

This changes the string outputted by highlightjs to something that can easily be split by \n.

If you're doing it client side, you may want to use "after:highlightBlock" or "after:highlightElement" instead depending on the version of highlightjs.

Or you can just call the function on the output and then split it by \n.

Woeful answered 10/1, 2022 at 17:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.