Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement support for adding custom CSS classes to specific lines #259

Merged
merged 2 commits into from
Dec 22, 2021
Merged

Implement support for adding custom CSS classes to specific lines #259

merged 2 commits into from
Dec 22, 2021

Conversation

mariusschulz
Copy link
Contributor

@mariusschulz mariusschulz commented Nov 21, 2021

In this PR, I'm implementing support for adding custom CSS classes to specific lines. This is picking up the work that @dceddia started in #83.

API

The codeToHtml now accepts an optional options object with an optional highlightedLines property:

function codeToHtml(
  code: string,
  lang?: StringLiteralUnion<Lang>,
  theme?: StringLiteralUnion<Theme>,
  options?: HtmlOptions
): string {
  // ...
}

export interface HtmlOptions {
  highlightedLines?: HighlightedLines
}

Highlighted lines can be specified one by one, or as a range of lines:

type LineNumber = number
type LineNumberRange = [fromLine: number, toLine: number]

export type HighlightedLines = (LineNumber | LineNumberRange)[]

Example

Here's an example usage that would highlight lines 1, 3, 4, 5, 6, and 8:

const code = `
console.log('line 1');
console.log('line 2');
console.log('line 3');
console.log('line 4');
console.log('line 5');
console.log('line 6');
console.log('line 7');
console.log('line 8');
console.log('line 9');
`.trim()

const html = highlighter.codeToHtml(code, 'js', 'css-variables', {
  highlightedLines: [1, [3, 6], 8]
})

And here's the corresponding HTML output:

<pre class="shiki" style="background-color: var(--shiki-color-background)"><code><span class="line highlighted"><span style="color: var(--shiki-token-constant)">console</span><span style="color: var(--shiki-token-function)">.log</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-string-expression)">&#39;line 1&#39;</span><span style="color: var(--shiki-color-text)">);</span></span>
<span class="line"><span style="color: var(--shiki-token-constant)">console</span><span style="color: var(--shiki-token-function)">.log</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-string-expression)">&#39;line 2&#39;</span><span style="color: var(--shiki-color-text)">);</span></span>
<span class="line highlighted"><span style="color: var(--shiki-token-constant)">console</span><span style="color: var(--shiki-token-function)">.log</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-string-expression)">&#39;line 3&#39;</span><span style="color: var(--shiki-color-text)">);</span></span>
<span class="line highlighted"><span style="color: var(--shiki-token-constant)">console</span><span style="color: var(--shiki-token-function)">.log</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-string-expression)">&#39;line 4&#39;</span><span style="color: var(--shiki-color-text)">);</span></span>
<span class="line highlighted"><span style="color: var(--shiki-token-constant)">console</span><span style="color: var(--shiki-token-function)">.log</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-string-expression)">&#39;line 5&#39;</span><span style="color: var(--shiki-color-text)">);</span></span>
<span class="line highlighted"><span style="color: var(--shiki-token-constant)">console</span><span style="color: var(--shiki-token-function)">.log</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-string-expression)">&#39;line 6&#39;</span><span style="color: var(--shiki-color-text)">);</span></span>
<span class="line"><span style="color: var(--shiki-token-constant)">console</span><span style="color: var(--shiki-token-function)">.log</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-string-expression)">&#39;line 7&#39;</span><span style="color: var(--shiki-color-text)">);</span></span>
<span class="line highlighted"><span style="color: var(--shiki-token-constant)">console</span><span style="color: var(--shiki-token-function)">.log</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-string-expression)">&#39;line 8&#39;</span><span style="color: var(--shiki-color-text)">);</span></span>
<span class="line"><span style="color: var(--shiki-token-constant)">console</span><span style="color: var(--shiki-token-function)">.log</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-string-expression)">&#39;line 9&#39;</span><span style="color: var(--shiki-color-text)">);</span></span></code></pre>

Feedback

Feedback is more than welcome, I'm happy to iterate on this as necessary :)

@mariusschulz
Copy link
Contributor Author

If #263 gets merged, I'll rebase this PR and use the new codeToHtml(code, options?) overload.

@orta
Copy link
Contributor

orta commented Nov 25, 2021

This is really a @octref call, but it does feel like they were looking for a different (more low level) API surface for how this API would look to consumers: #83 (review)

interface LineOption {
  line: number
  classes?: string[]
  skipTokenization?: boolean // default to false
}

interface HTMLRendererOption {
  lineOptions: LineOption[]
}

Having built this in shiki-twoslash, the likely thing that you'd also want the API to be able to do it declare characters x to y as being highlighted in a particular line too. So maybe something more like this is better:

interface LineOption {
  line: number
  span?: [start: number, end : number]
  classes?: string[]
  skipTokenization?: boolean // default to false
}

interface HTMLRendererOption {
  lineOptions: LineOption[]
}

@mariusschulz
Copy link
Contributor Author

Thanks @orta, this is good feedback. I can update the PR accordingly.

@mariusschulz mariusschulz changed the title Add support for line highlights Implement support for adding custom CSS classes to specific lines Nov 26, 2021
@mariusschulz
Copy link
Contributor Author

I implemented the API you suggested, @orta. Could you please take another look?

Copy link
Contributor

@orta orta left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, I will give it somewhere between 1-2 weeks to give @octref a chance to chime in but I feel like this is what they were looking for

@mariusschulz
Copy link
Contributor Author

Rebased onto main now that #263 has been merged.

@mariusschulz
Copy link
Contributor Author

Are we good to merge this PR, @orta? Or would you prefer that we wait for @octref to stamp it?

@orta
Copy link
Contributor

orta commented Dec 16, 2021

Hah, he was just around on the repo - I'd prefer it, but if I get nothing tomorrow I'll merge 👍🏻

@octref
Copy link
Collaborator

octref commented Dec 17, 2021

busy with work today, i'll try to take a look tomorrow. thanks!

@mariusschulz
Copy link
Contributor Author

Thank you, looking forward to your review and feedback.

Copy link
Collaborator

@octref octref left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for my late response. Looks all good to me. I just added a note on the 1-based indexing for line number. Great work 🙌

@gubatenkov
Copy link

Sorry guys, where I can find docs how to use highlighting of add/remove/focus of specific lines? Thanks

@Ivluengo
Copy link

Ivluengo commented Feb 9, 2023

Hi! I tried to use it and everything is fine. In my renderToHtml function, I added the lineOptions property and then each object of each line I want to highlight.

The thing is, I would like to change the color of the highlight for each <Code> component. I know I can add classes, but then I will have to create a class for every color I would like to use.

I would like to ask to add to the lineOption object, the possibility of adding also styles for that specific line. Then I can pass any style and also any custom property of any color I have. I also couldn't find the way to highlight a range of lines in the renderToHtml function.

I tried to add inside the elements property the new line with a style attribute added to it, but seems then that the lineOptions doesn't work and doesn't add any special class to any line…

const lineColor = 'rgb(0 0 0 / 0.5) // this comes from a React props

const codeTags = renderToHtml(tokens, {
  elements: {      
    line({ children: line }) {
      return `<span class="line" style="--lineColor: ${lineColor}">${line}</span>`
    },
  },
  lineOptions: [{ line: 1, classes: ['highlighted'] }, { line: 1, classes: ['highlighted'] }],
})

Hope I explained myself as I don't speak English great
Thanks a lot!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants