Skip to content

Commit

Permalink
feat: code line highlight (#82)
Browse files Browse the repository at this point in the history
* feat: code line highlight

* docs: typo

* docs: file structure and typo
  • Loading branch information
Flower-F committed Oct 11, 2022
1 parent 1358b13 commit e5bb344
Show file tree
Hide file tree
Showing 3 changed files with 228 additions and 2 deletions.
86 changes: 86 additions & 0 deletions docs/en/guide/use-mdx.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,89 @@ The properties defined in front matter will be passed to the component as `meta`
```

You can see more front matter config detail in [config-front-matter](/en/api/config-front-matter).

## Code Line Highlighting

Island.js supports highlighting of specified code lines. You can specify line highlighting in any of the following ways.

**Input:**

````bash
```js{1}
import { defineConfig } from 'islandjs';
export default defineConfig({
themeConfig: {
navbar: [
{
text: 'Home',
link: '/',
activeMatch: '^/$|^/'
}
]
}
});
```
````
**Output:**
```js{1}
import { defineConfig } from 'islandjs';
export default defineConfig({
themeConfig: {
navbar: [
{
text: 'Home',
link: '/',
activeMatch: '^/$|^/'
}
]
}
});
```
In addition to highlight the single line, you can also specify multiple lines to highlight at a time.
- A Line Range: `{1-10}`
- Multiple single lines: `{1,3,5}`
- Combine Line Range and Single Lines:`{3,5-13,20}`
**Input:**
````bash
```js{1,4-7,10}
import { defineConfig } from 'islandjs';
export default defineConfig({
themeConfig: {
navbar: [
{
text: 'Home',
link: '/',
activeMatch: '^/$|^/'
}
]
}
});
```
````
**Output:**
```js{1,4-7,10}
import { defineConfig } from 'islandjs';
export default defineConfig({
themeConfig: {
navbar: [
{
text: 'Home',
link: '/',
activeMatch: '^/$|^/'
}
]
}
});
```
86 changes: 86 additions & 0 deletions docs/zh/guide/use-mdx.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,89 @@ title: Hello World
```

更多的配置详情请参考 [config-front-matter](/zh/api/config-front-matter)

## 指定代码行高亮

Island.js 支持指定代码行的高亮显示。你可以用以下任意方式指定代码行高亮。

**输入:**

````bash
```js{1}
import { defineConfig } from 'islandjs';
export default defineConfig({
themeConfig: {
navbar: [
{
text: 'Home',
link: '/',
activeMatch: '^/$|^/'
}
]
}
});
```
````
**输出:**
```js{1}
import { defineConfig } from 'islandjs';
export default defineConfig({
themeConfig: {
navbar: [
{
text: 'Home',
link: '/',
activeMatch: '^/$|^/'
}
]
}
});
```
除了单行之外,你还可以同时指定多行代码高亮。
- 指定一个范围:`{1-10}`
- 指定多个单行:`{1,3,5}`
- 多行和单行结合:`{3,5-13,20}`
**输入:**
````bash
```js{1,4-7,10}
import { defineConfig } from 'islandjs';
export default defineConfig({
themeConfig: {
navbar: [
{
text: 'Home',
link: '/',
activeMatch: '^/$|^/'
}
]
}
});
```
````
**输出:**
```js{1,4-7,10}
import { defineConfig } from 'islandjs';
export default defineConfig({
themeConfig: {
navbar: [
{
text: 'Home',
link: '/',
activeMatch: '^/$|^/'
}
]
}
});
```
58 changes: 56 additions & 2 deletions src/node/plugin-mdx/rehypePlugins/shiki.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,29 @@ interface Options {
highlighter: shiki.Highlighter;
}

function highlightSingleLine(
line: number,
fragmentAst: ReturnType<typeof fromHtml>
) {
// Children are composed of span and \n alternately, so we should get the even rows to highlight
if (
line >= 1 &&
line * 2 - 2 <
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
fragmentAst.children[0].children[0].children.length &&
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
fragmentAst.children[0].children[0].children?.[line * 2 - 2]?.properties
) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
fragmentAst.children[0].children[0].children[
line * 2 - 2
].properties.className = 'line highlighted';
}
}

// https://github.com/leafac/rehype-shiki/blob/41e64054d72ab29d5ad48c4c070499fc075090e9/source/index.ts
// The plugin cannot be used directly because it won't reserve the class name `language-xxx` in the code tag
// It cause conflict with preWrapper plugin, so we should integrate it manually
Expand All @@ -25,18 +48,49 @@ export const rehypePluginShiki: Plugin<[Options], import('hast').Root> = ({
const codeNode = node.children[0];
const codeContent = (node.children[0].children[0] as Text).value;
const codeClassName = codeNode.properties?.className?.toString() || '';
const lang = codeClassName.split('-')[1];

const highlightLinesReg = /language-([a-zA-Z]*)\s*({[\d,-]*})?/i;
const highlightRegExecResult = highlightLinesReg.exec(codeClassName);

if (!highlightRegExecResult) {
return;
}

const lang = highlightRegExecResult[1];
if (!lang) {
return;
}

// Support single line and line range
const highlightLines: (number | number[])[] = [];
highlightRegExecResult[2]
?.slice(1, highlightRegExecResult[2].length - 1)
?.split(',')
.forEach((str) => {
if (str.includes('-')) {
const [start, end] = str.split('-');
highlightLines.push([parseInt(start), parseInt(end)]);
} else {
highlightLines.push(parseInt(str));
}
});

const highlightedCode = highlighter.codeToHtml(codeContent, { lang });
const fragmentAst = fromHtml(highlightedCode, { fragment: true });
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore Reserve the class name `language-xxx` in the code tag
fragmentAst.children[0].children[0].properties.className =
codeClassName;
'language-' + lang;

highlightLines.forEach((line) => {
if (line instanceof Array) {
for (let i = line[0]; i <= line[1]; i++) {
highlightSingleLine(i, fragmentAst);
}
} else {
highlightSingleLine(line, fragmentAst);
}
});
parent?.children.splice(index!, 1, ...fragmentAst.children);
}
});
Expand Down

1 comment on commit e5bb344

@vercel
Copy link

@vercel vercel bot commented on e5bb344 Oct 11, 2022

Choose a reason for hiding this comment

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

Please sign in to comment.