Add "start with line number" and "continue line number" syntax to highlighted code blocks (#267)

Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de>
This commit is contained in:
mrdrogdrog 2020-06-24 23:53:26 +02:00 committed by GitHub
parent cdadc7b41a
commit 312c86e702
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 31 additions and 17 deletions

View file

@ -25,17 +25,17 @@
display: flex; display: flex;
align-items: flex-end; align-items: flex-end;
&:before { &:before {
content: attr(data-line-number); content: attr(data-line-number);
} }
} }
} }
&.showGutter .line { &.showGutter .codeline {
margin: 0 0 0 16px; margin: 0 0 0 16px;
} }
&.wrapLines .line {
&.wrapLines .codeline {
white-space: pre-wrap; white-space: pre-wrap;
} }

View file

@ -6,7 +6,7 @@ import './highlighted-code.scss'
export interface HighlightedCodeProps { export interface HighlightedCodeProps {
code: string, code: string,
language?: string, language?: string,
showGutter: boolean startLineNumber?: number
wrapLines: boolean wrapLines: boolean
} }
@ -32,7 +32,7 @@ const correctLanguage = (language: string | undefined): string | undefined => {
} }
} }
export const HighlightedCode: React.FC<HighlightedCodeProps> = ({ code, language, showGutter, wrapLines }) => { export const HighlightedCode: React.FC<HighlightedCodeProps> = ({ code, language, startLineNumber, wrapLines }) => {
const highlightedCode = useMemo(() => { const highlightedCode = useMemo(() => {
const replacedLanguage = correctLanguage(language) const replacedLanguage = correctLanguage(language)
return ((!!replacedLanguage && checkIfLanguageIsSupported(replacedLanguage)) ? hljs.highlight(replacedLanguage, code).value : escapeHtml(code)) return ((!!replacedLanguage && checkIfLanguageIsSupported(replacedLanguage)) ? hljs.highlight(replacedLanguage, code).value : escapeHtml(code))
@ -42,13 +42,13 @@ export const HighlightedCode: React.FC<HighlightedCodeProps> = ({ code, language
}, [code, language]) }, [code, language])
return ( return (
<code className={`hljs ${showGutter ? 'showGutter' : ''} ${wrapLines ? 'wrapLines' : ''}`}> <code className={`hljs ${startLineNumber !== undefined ? 'showGutter' : ''} ${wrapLines ? 'wrapLines' : ''}`}>
{ {
highlightedCode highlightedCode
.map((line, index) => { .map((line, index) => {
return <Fragment key={index}> return <Fragment key={index}>
<span className={'linenumber'} data-line-number={index + 1}/> <span className={'linenumber'} data-line-number={(startLineNumber || 1) + index}/>
<div className={'line'}> <div className={'codeline'}>
{line} {line}
</div> </div>
</Fragment> </Fragment>

View file

@ -1,6 +1,6 @@
import MarkdownIt from 'markdown-it/lib' import MarkdownIt from 'markdown-it/lib'
const highlightRegex = /^(\w*)(=?)(!?)$/ const highlightRegex = /^(\w*)(=(\d*|\+))?(!?)$/
export const highlightedCode: MarkdownIt.PluginSimple = (md: MarkdownIt) => { export const highlightedCode: MarkdownIt.PluginSimple = (md: MarkdownIt) => {
md.core.ruler.push('highlighted-code', (state) => { md.core.ruler.push('highlighted-code', (state) => {
@ -14,9 +14,12 @@ export const highlightedCode: MarkdownIt.PluginSimple = (md: MarkdownIt) => {
token.attrJoin('data-highlight-language', highlightInfos[1]) token.attrJoin('data-highlight-language', highlightInfos[1])
} }
if (highlightInfos[2]) { if (highlightInfos[2]) {
token.attrJoin('data-show-gutter', '') token.attrJoin('data-show-line-numbers', '')
} }
if (highlightInfos[3]) { if (highlightInfos[3]) {
token.attrJoin('data-start-line-number', highlightInfos[3])
}
if (highlightInfos[4]) {
token.attrJoin('data-wrap-lines', '') token.attrJoin('data-wrap-lines', '')
} }
} }

View file

@ -1,18 +1,29 @@
import { DomElement } from 'domhandler' import { DomElement } from 'domhandler'
import React from 'react' import React from 'react'
import { HighlightedCode } from '../../../../common/highlighted-code/highlighted-code' import { HighlightedCode } from '../../../../common/highlighted-code/highlighted-code'
import { ComponentReplacer, SubNodeConverter } from '../ComponentReplacer' import { ComponentReplacer } from '../ComponentReplacer'
export class HighlightedCodeReplacer implements ComponentReplacer { export class HighlightedCodeReplacer implements ComponentReplacer {
getReplacement (codeNode: DomElement, index: number, subNodeConverter: SubNodeConverter): React.ReactElement | undefined { private lastLineNumber = 0;
getReplacement (codeNode: DomElement, index: number): React.ReactElement | undefined {
if (codeNode.name !== 'code' || !codeNode.attribs || !codeNode.attribs['data-highlight-language'] || !codeNode.children || !codeNode.children[0]) { if (codeNode.name !== 'code' || !codeNode.attribs || !codeNode.attribs['data-highlight-language'] || !codeNode.children || !codeNode.children[0]) {
return return
} }
const language = codeNode.attribs['data-highlight-language'] const language = codeNode.attribs['data-highlight-language']
const showGutter = codeNode.attribs['data-show-gutter'] !== undefined const showLineNumbers = codeNode.attribs['data-show-line-numbers'] !== undefined
const wrapLines = codeNode.attribs['data-wrap-lines'] !== undefined const startLineNumberAttribute = codeNode.attribs['data-start-line-number']
return <HighlightedCode key={index} language={language} showGutter={showGutter} wrapLines={wrapLines} code={codeNode.children[0].data as string}/> const startLineNumber = startLineNumberAttribute === '+' ? this.lastLineNumber : (parseInt(startLineNumberAttribute) || 1)
const wrapLines = codeNode.attribs['data-wrap-lines'] !== undefined
const code = codeNode.children[0].data as string
if (showLineNumbers) {
this.lastLineNumber = startLineNumber + code.split('\n')
.filter(line => !!line).length
}
return <HighlightedCode key={index} language={language} startLineNumber={showLineNumbers ? startLineNumber : undefined} wrapLines={wrapLines} code={code}/>
} }
} }