highlight.js + Vue で行番号(line-numbers)を加えてhilight表示する。

![スクリーンショット 2021-04-15 1.11.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/518243/a4f749c1-95d4-a074-bf51-0045310c91f4.png)

hilight + 行番号を表示したものをゴールとして目指します。
参考にした記事、コードは下記です
[highlight.jsで行ごとに区切りつつハイライトする](https://blog.hirokiky.org/entry/2019/09/04/193222)
[highlightjs/vue-plugin](https://github.com/highlightjs/vue-plugin/blob/main/src/vue.js)

#行番号 + ハイライト を行う関数を作る

[highlight.jsで行ごとに区切りつつハイライトする](https://blog.hirokiky.org/entry/2019/09/04/193222)
を参考にすると、
単純に行区切りにhilightを適用しても、うまくいかないことがわかります。
そこで

```
let state = null
const result = hljs.highlight('markdown', row, true, state)
state = result.top
```

とし、
`result.top`
を継承することで行違いのhilightを働かせることができます。
これを利用し、

```javascript:lineByLineHighilght
function lineByLineHighilght (language, body) {
  let state = null
  const output = []
  const bodySplit = body.split('\n')
  let maxLength = String(bodySplit.length).length
  for (let line = 0; line < bodySplit.length; line++) {
    const row = bodySplit[line]
    const result = hljs.highlight(language, row, true, state)
    let setLineNumber = `<div style="float: left; width: ${maxLength}7px;"><span style="float: right; padding-right: 5px;">${line}:</span></div>`
    let setLine = `<div class="lineNumber">${setLineNumber}<span class="line-value">${result.value}</span></div>`
    if (result.value.length === 0) {
      setLine = `<div class="lineNumber">${setLineNumber}</div></br>`
    }
    state = result.top
    output.push(setLine)
  }
  return output.join('')
}
```

とすることで、行番号を付与することができます。
ちなみに、

```
    if (result.value.length === 0) {
      setLine = `<div class="lineNumber">${setLineNumber}</div></br>`
    }
```

の部分ですが、これは`result.value.length === 0`の時、改行されない問題が合ったので行ってます。
（ もっと上手い解決策があれば教えてください:) ）

#VueJs上でhilight + 行番号表示を行う

vueのコンポーネントとして、hilightJsを扱えれば一番良く、
また公式側でvueでhilight.jsを扱えるようになっています。
[hilight.js](https://github.com/highlightjs/highlight.js)
ただ、今回はこのままでは扱えないので、
[highlightjs/vue-plugin](https://github.com/highlightjs/vue-plugin/blob/main/src/vue.js)の中身を参考にし、

```javascript:hilightVue.js

import hljs from 'highlight.js'

function hasValueOrEmptyAttribute (value) {
  return Boolean(value || value === '')
}

function lineByLineHighilght (language, body) {
  let state = null
  const output = []
  const bodySplit = body.split('\n')
  let maxLength = String(bodySplit.length).length
  for (let line = 0; line < bodySplit.length; line++) {
    const row = bodySplit[line]
    const result = hljs.highlight(language, row, true, state)
    let setLineNumber = `<div style="float: left; width: ${maxLength}7px;"><span style="float: right; padding-right: 5px;">${line}:</span></div>`
    let setLine = `<div class="lineNumber">${setLineNumber}<span class="line-value">${result.value}</span></div>`
    if (result.value.length === 0) {
      setLine = `<div class="lineNumber">${setLineNumber}</div></br>`
    }
    state = result.top
    output.push(setLine)
  }
  return output.join('')
}

const Component = {
  props: ['language', 'code'],
  data: function () {
    return {
      detectedLanguage: '',
      unknownLanguage: false
    }
  },
  computed: {
    className () {
      if (this.unknownLanguage) return ''

      return 'hljs ' + this.detectedLanguage
    },
    highlighted () {
      return lineByLineHighilght(this.language, this.code)
    },
    ignoreIllegals () {
      return true
    }
  },
  render (createElement) {
    return createElement('pre', {}, [
      createElement('code', {
        class: this.className,
        domProps: { innerHTML: this.highlighted }
      })
    ])
  }
}

export default {
  install (Vue) {
    Vue.component('highlightjs', Component)
  },
  component: Component
}


```

このように行うことで、任意なvueファイルで

```vue
<highlightjs language="HTML" :code="code"/>
```

と、
その任意のvueファイルが扱われる以前にrenderされているか、もしくは
その任意のファイル自身で

```javascript
import hljs from '@/plugins/highlightVue.js'
Vue.use(hljs)
```

とすると、
下記のような結果にhilight + 行番号表示が行えます

![スクリーンショット 2021-04-15 1.11.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/518243/1aca45cb-828c-2a72-d6d5-1e854457b07d.png)