import { OptionTable } from 'components/table'
import { Callout } from 'nextra/components'
import { useEffect, useRef } from 'react'

# Syntax Highlighting

Nextra uses [Shiki](https://shiki.style) to do syntax highlighting at build
time. It's very reliable and performant. For example, adding this in your
Markdown file:

````md copy=false filename="Markdown"
```js
console.log('hello, world')
```
````

Results in:

```js copy=false
console.log('hello, world')
```

## Features

### Inlined Code

Inlined syntax highlighting like `let x = 1{:jsx}` is also supported via the
`{:}` syntax:

```md copy=false filename="Markdown"
Inlined syntax highlighting is also supported `let x = 1{:jsx}` via:
```

### Highlighting Lines

You can highlight specific lines of code by adding a `{}` attribute to the code
block:

````md copy=false filename="Markdown"
```js {1,4-5}
import { useState } from 'react'

function Counter() {
  const [count, setCount] = useState(0)
  return <button onClick={() => setCount(count + 1)}>{count}</button>
}
```
````

Result:

```js copy=false {1,4-5}
import { useState } from 'react'

function Counter() {
  const [count, setCount] = useState(0)
  return <button onClick={() => setCount(count + 1)}>{count}</button>
}
```

### Highlighting Substrings

You can highlight specific substrings of code by adding a `//` attribute to the
code block:

````md copy=false filename="Markdown"
```js /useState/
import { useState } from 'react'

function Counter() {
  const [count, setCount] = useState(0)
  return <button onClick={() => setCount(count + 1)}>{count}</button>
}
```
````

```js copy=false /useState/
import { useState } from 'react'

function Counter() {
  const [count, setCount] = useState(0)
  return <button onClick={() => setCount(count + 1)}>{count}</button>
}
```

You can highlight only a part of the occurrences of that substring by adding a
number it: `/str/1`, or multiple: `/str/1-3`, `/str/1,3`.

### Copy Button

By adding a `copy` attribute, a copy button will be added to the code block when
the user hovers over it:

````md copy=false filename="Markdown"
```js copy
console.log('hello, world')
```
````

Renders:

```js copy
console.log('hello, world')
```

<Callout type="info">
  You can enable this feature globally by setting `defaultShowCopyCode: true` in
  your Nextra configuration (`next.config.mjs` file). Once it's enabled
  globally, you can disable it via the `copy=false` attribute.
</Callout>

### Line Numbers

You can add line numbers to your code blocks by adding a `showLineNumbers`
attribute:

````md copy=false filename="Markdown"
```js showLineNumbers
import { useState } from 'react'

function Counter() {
  const [count, setCount] = useState(0)
  return <button onClick={() => setCount(count + 1)}>{count}</button>
}
```
````

Renders:

```js copy=false showLineNumbers
import { useState } from 'react'

function Counter() {
  const [count, setCount] = useState(0)
  return <button onClick={() => setCount(count + 1)}>{count}</button>
}
```

### Filenames and Titles

You can add a filename or a title to your code blocks by adding a `filename`
attribute:

````md copy=false filename="Markdown"
```js filename="example.js"
console.log('hello, world')
```
````

Renders:

```js copy=false filename="example.js"
console.log('hello, world')
```

### ANSI Highlighting

You can highlight ANSI escape codes:

````md copy=false filename="Markdown"
```ansi
[0m [0;32m✓[0m [0;2msrc/[0mindex[0;2m.test.ts (1)[0m
  [0;2m Test Files [0m [0;1;32m1 passed[0;98m (1)[0m
  [0;2m      Tests [0m [0;1;32m1 passed[0;98m (1)[0m
  [0;2m   Start at [0m 23:32:41
  [0;2m   Duration [0m 11ms
  [42;1;39;0m PASS [0;32m Waiting for file changes...[0m
         [0;2mpress [0;1mh[0;2m to show help, press [0;1mq[0;2m to quit
```
````

Renders:

```ansi copy=false
[0m [0;32m✓[0m [0;2msrc/[0mindex[0;2m.test.ts (1)[0m
  [0;2m Test Files [0m [0;1;32m1 passed[0;98m (1)[0m
  [0;2m      Tests [0m [0;1;32m1 passed[0;98m (1)[0m
  [0;2m   Start at [0m 23:32:41
  [0;2m   Duration [0m 11ms
  [42;1;39;0m PASS [0;32m Waiting for file changes...[0m
         [0;2mpress [0;1mh[0;2m to show help, press [0;1mq[0;2m to quit
```

## Supported Languages

Check [this list](https://github.com/shikijs/shiki/blob/main/docs/languages.md)
for all supported languages.

{/* ## Customize the Theme */}

{/* Nextra uses CSS variables to define the colors for tokens. */}

## With Dynamic Content

Since syntax highlighting is done at build time, you can’t use dynamic content
in your code blocks. However, since MDX is very powerful there is a workaround
via client JS. For example:

import { Button } from 'nextra/components'

export function DynamicCode({ children }) {
  const ref = useRef(null)
  const tokenRef = useRef()
  // Find the corresponding token from the DOM
  useEffect(() => {
    tokenRef.current = [
      ...ref.current.querySelectorAll('code > span > span')
    ].find(el => el.textContent === '1')
  }, [])
  return (
    <>
      <div ref={ref} className="mt-6">
        {children}
      </div>
      <div className="flex mt-3 gap-3 justify-center text-sm">
        <Button
          variant="outline"
          onClick={() => {
            const prev = tokenRef.current.textContent
            tokenRef.current.textContent = (+prev || 0) + 1
          }}
        >
          Increase the number
        </Button>
        <Button
          variant="outline"
          onClick={() => {
            tokenRef.current.textContent = '1 + 1'
          }}
        >
          Change to `1 + 1`
        </Button>
      </div>
    </>
  )
}

<DynamicCode>
```js copy=false filename="dynamic-code.js"
function hello() {
  const x = 2 + 3
  console.log(1)
}
```
</DynamicCode>

This workaround has a limitation that updated content won't be re-highlighted.
For example if we update the number to `1 + 1`, it will be incorrectly
highlighted.

Check out the
[code](https://github.com/shuding/nextra/blob/main/docs/pages/docs/guide/syntax-highlighting.mdx)
to see how it works.

## Disable Syntax Highlighting

You can opt out of syntax highlighting for using one of your own. You can
disable syntax highlighting globally by setting `codeHighlight: false` in your
Nextra configuration (`next.config.mjs` file).

<OptionTable
  options={[
    [
      'codeHighlight',
      'boolean',
      'Enable or disable syntax highlighting. Defaults to `true`.'
    ]
  ]}
/>

## Custom Grammar

Shiki accepts a
[VSCode TextMate Grammar](https://code.visualstudio.com/api/language-extensions/syntax-highlight-guide)
for syntax highlighting with custom language grammars.

You can provide these grammars by overriding the `getHighlighter` function in
`mdxOptions.rehypePrettyCodeOptions` option in your Nextra config inside
`next.config.mjs`:

```js copy=false filename="next.config.mjs" {13-18}
import { BUNDLED_LANGUAGES } from 'shiki'

nextra({
  // ... other options
  mdxOptions: {
    rehypePrettyCodeOptions: {
      getHighlighter: options =>
        getHighlighter({
          ...options,
          langs: [
            ...BUNDLED_LANGUAGES,
            // custom grammar options, see the Shiki documentation for how to provide these options
            {
              id: 'my-lang',
              scopeName: 'source.my-lang',
              aliases: ['mylang'], // Along with id, aliases will be included in the allowed names you can use when writing Markdown
              path: '../../public/syntax/grammar.tmLanguage.json'
            }
          ]
        })
    }
  }
})
```

## Custom Themes

Within `mdxOptions.rehypePrettyCodeOptions` you may also provide custom themes
instead of [relying on CSS Variables](/docs/guide/syntax-highlighting):

```js copy=false filename="next.config.mjs" {4}
nextra({
  // ... other options
  mdxOptions: {
    rehypePrettyCodeOptions: {
      // VSCode theme or built-in Shiki theme, see Shiki documentation for more information
      theme: JSON.parse(
        readFileSync('./public/syntax/arctis_light.json', 'utf8')
      )
    }
  }
})
```

### Multiple Themes (Dark and Light Mode)

Pass your themes to `theme`, where the keys represent the color mode:

```js copy=false filename="next.config.mjs" {5-8}
nextra({
  // ...
  mdxOptions: {
    rehypePrettyCodeOptions: {
      theme: {
        dark: 'nord',
        light: 'min-light'
      }
    }
  }
})
```
