如何计算行号

174 阅读1分钟

代码块功能实现,借助prismjs 进行高亮,通过 /\n(?!$)/g 进行行截断。

// 正则demo
const NEW_LINE_EXP = /\n(?!$)/g
const string = `123
123`
string.split(NEW_LINE_EXP)
// [123,123]
> `?!`:正向否定预查(negative assert),在任何不匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。

然后通过一个往编辑去塞入一个空元素,往这个空元素中填充之前通过正则分割出的字符,再获取该空元素 的行高,push进行号的数组lineNumber

就算是这种自动换行的形式,也能兼容。

import * as React from 'react'
import { debounce } from 'lodash'
import { reaction } from 'mobx'
import { inject } from 'injection'

const useLineNumber = (id, code, language, isLineNumber, isLineBreak, isLigatures) => {
  const editorUI = inject('editorUI')
  const [lineNumber, lineNumberSet] = React.useState([])
  const isLineBreakRef = React.useRef(isLineBreak)
  const isLineNumberRef = React.useRef(isLineNumber)
  const codeLinesLength = React.useRef(0)
  const computeLineNumber = () => {
    const res = []
    const element = document.querySelector(`#id-${id}`)
    if (isLineNumber && element) {
      const codeElement = element.querySelector('[contenteditable="true"]')
      const lineNumberElement: any = element.querySelector('.lineNumber')

      if (!codeElement) {
        return
      }
      const NEW_LINE_EXP = /\n(?!$)/g
      let lineNumberSizer: any = element.querySelector('.line-numbers-sizer')
      const codeLines = codeElement.textContent.split(NEW_LINE_EXP)
      let cacheMinWidth = ''
      if (lineNumberElement) {
        cacheMinWidth = lineNumberElement.style.minWidth
        lineNumberElement.style.minWidth = `${String(codeLines.length).length * 10}px`
      }

      if (!lineNumberSizer) {
        lineNumberSizer = document.createElement('span')
        lineNumberSizer.className = 'line-numbers-sizer'
        codeElement.appendChild(lineNumberSizer)
      }
      lineNumberSizer.style.display = 'block'
      lineNumberSizer.style.visibility = 'visible'
      codeLines.forEach(line => {
        if (lineNumberSizer) {
          lineNumberSizer.textContent = line || '\n'
          const lineSize = lineNumberSizer.getBoundingClientRect().height
          res.push(lineSize)
        }
      })

      if (lineNumberElement) {
        lineNumberElement.style.minWidth = cacheMinWidth
      }

      lineNumberSizer.textContent = ''
      lineNumberSizer.style.display = 'none'
    }
    lineNumberSet(res)
  }
  const debounceComputeLineNumber = React.useCallback(debounce(computeLineNumber, 300), [isLineNumber])

  React.useEffect(() => {
    window.addEventListener('resize', debounceComputeLineNumber)
    const reactionMainEditorWidth = reaction(
      () => editorUI.mainEditorWidth,
      () => {
        debounceComputeLineNumber()
      },
    )
    return () => {
      reactionMainEditorWidth()
      window.removeEventListener('resize', debounceComputeLineNumber)
    }
  }, [isLineNumber])

  React.useLayoutEffect(() => {
    const element = document.querySelector(`#id-${id}`)
    if (element && isLineNumber) {
      const NEW_LINE_EXP = /\n(?!$)/g
      const codeElement = element.querySelector('[contenteditable="true"]')
      if (codeElement) {
        const codeLines = codeElement.textContent.split(NEW_LINE_EXP)
        if (isLineBreakRef.current !== isLineBreak || isLineNumberRef.current !== isLineNumber) {
          computeLineNumber()
        } else {
          if (codeLinesLength.current !== codeLines.length) {
            computeLineNumber()
          } else {
            debounceComputeLineNumber()
          }
        }
        codeLinesLength.current = codeLines.length
        isLineBreakRef.current = isLineBreak
        isLineNumberRef.current = isLineNumber
      }
    }
  }, [code, language, isLineNumber, isLineBreak, isLigatures])
  return [lineNumber]
}
export default useLineNumber