筋肉チョットデキル

コードブロックの指定行をハイライトさせる

2020年06月26日

技術ブログReact

つくったもの

技術ブログを書いているときに、サンプルコードの指定行をハイライトしたいことがありました。
サンプルコードのどこを説明しているのか読みやすくしたいと思ったからです!

たとえば.mdxファイルでline={1,4-6}と行を指定すると、

副作用 (effect) フック により、関数コンポーネント内で副作用を実行することができるようになります。
```js line={1,4-6}
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
```

コードブロック部分は 1 行目と 4~6 行目がハイライトされます。

import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}

実装

```で表示するコンポーネントを用意

マークダウン記法の```を使って表示される見た目を、MDXProvider を使って変えたいと思います。

{ code: <表示したいコンポーネント /> }
MDXProvider の state である components に渡すことで、
```で表示される見た目を変えることが出来ます。

MDXProvider例
import { MDXProvider } from '@mdx-js/react';
const CodeBlock = props => <pre style={{ background: 'gray' }} {...props} />;
const components = {
code: CodeBlock,
};
export const wrapRootElement = ({ element }) => (
<MDXProvider components={components}>{element}</MDXProvider>
);

MDXProvider について詳細は以下の記事を参照ください。

技術ブログ
Gatsby

2020年06月26日

MDXを使う準備 この記事ではMDXを使った前提で進めます。MDXは不要でシンプルなMarkdownで良い方は適宜読み替えていただけると幸いです。 MDXとは、Markdown…

自分の場合は、
CodeBlockコンポーネントの中で、Highlightという色付けされたコード部分を表示するコンポーネントを使っています。

CodeBlockコンポーネントのコードを見る
Highlightコンポーネントのコードを見る

このHighlightコンポーネントで指定した行をハイライトしています。

指定した行を受け取る

MDXProvider の components の code に指定したCodeBlockコンポーネントでは、
MDX 内でline={1,3-5}のように指定した{1,3-5}という文字列をlineprops で受け取れます。

mdx
```js line={1,3-5}
console.log('macho');
console.log('macho!');
console.log('macho!!');
console.log('macho!!!');
console.log('macho!!!!');
console.log('macho!!!!!');
```

そして、Highlightコンポーネントに渡しています。

<Highlight title={title} code={code} language={language} line={line} />
もう一度、CodeBlockコンポーネントのコードを見る

受け取った行から、ハイライトすべきか判別する

受け取った行の{1,3-5}のような文字列を受け取る、
calculateLinesToHighlightという高階関数を用意します。

calculateLinesToHighlight関数
const calculateLinesToHighlight = meta => {
const RE = /{([\d,-]+)}/;
if (RE.test(meta)) {
const strLineNumbers = RE.exec(meta)[1];
const lineNumbers = rangeParser(strLineNumbers);
return index => lineNumbers.includes(index + 1);
} else {
return () => false;
}
};

この公開関数が返す関数は、
行番号を渡せばそれが指定行に含まれているかどうか真偽値で返すものです。

行番号を私指定行に含まれているかどうか真偽値で返す関数を作成
const shouldHighlightLine = calculateLinesToHighlight(line);

それを、受け取ったコードを行ごとにループで回している箇所で使い、その行をハイライトすべきか判定しています。

{tokens.map((line, i) => (
<div key={i} {...getLineProps({ line, key: i })}>
<Line shouldHighlight={shouldHighlightLine(i)}>
...

ハイライトすべき行の場合、styleを加える

あとは、LineコンポーネントのshouldHighlightがtrueの場合にstyleを加えるだけです!

Lineコンポーネント
const Line = ({ shouldHighlight, ...props }) => {
const highlightProps = shouldHighlight && {
d: 'block',
bg: 'gray.700',
borderLeftWidth: '.5rem',
borderLeftColor: 'purple.200',
px: '3',
};
return <Box d='table-cell' px='5' {...props} {...highlightProps} />;
};
もう一度、Highlightコンポーネントのコードを見る

ちなみに、ホバーした単語のハイライトも実装しているのでこちらも御覧ください!

🥰

以上です!シンプルに少しずつ改善していくのは楽しいので、ぜひ皆さんも自作技術ブログライフを!


岡本 侑貴@筋肉チョットデキル

筋肉バカ。筋トレしてコード書いて、毎日幸せに生きてる。ボディビルにドハマリ。2021年東京オープンボディビル選手権で最高の身体に仕上げてつよつよエンジニアになる!!

Made with Gatsby& ChakraUi

Yuuki Okamoto • 2020