feat: support focus, colored diffs, error highlights in code blocks (#1534)

Co-authored-by: Divyansh Singh <40380293+brc-dd@users.noreply.github.com>
pull/1535/head
Enzo Innocenzi 2 years ago committed by GitHub
parent a29a4a62c6
commit 04ab0eb6dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -72,7 +72,7 @@ For more details, see [Frontmatter](./frontmatter).
``` ```
| Tables | Are | Cool | | Tables | Are | Cool |
| ------------- |:-------------:| -----:| | ------------- | :-----------: | ----: |
| col 3 is | right-aligned | $1600 | | col 3 is | right-aligned | $1600 |
| col 2 is | centered | $12 | | col 2 is | centered | $12 |
| zebra stripes | are neat | $1 | | zebra stripes | are neat | $1 |
@ -351,6 +351,131 @@ export default { // Highlighted
} }
``` ```
Alternatively, it's possible to highlight directly in the line by using the `// [!code hl]` comment.
**Input**
````
```js
export default {
data () {
return {
msg: 'Highlighted!' // [!code hl]
}
}
}
```
````
**Output**
```js
export default {
data () {
return {
msg: 'Highlighted!' // [!code hl]
}
}
}
```
## Focus in Code Blocks
Adding the `// [!code focus]` comment on a line will focus it and blur the other parts of the code.
Additionally, you can define a number of lines to focus using `// [!code focus:<lines>]`.
**Input**
````
```js
export default {
data () {
return {
msg: 'Focused!' // [!code focus]
}
}
}
```
````
**Output**
```js
export default {
data () {
return {
msg: 'Focused!' // [!code focus]
}
}
}
```
## Colored diffs in Code Blocks
Adding the `// [!code --]` or `// [!code ++]` comments on a line will create a diff of that line, while keeping the colors of the codeblock.
**Input**
````
```js
export default {
data () {
return {
msg: 'Removed' // [!code --]
msg: 'Added' // [!code ++]
}
}
}
```
````
**Output**
```js
export default {
data () {
return {
msg: 'Removed' // [!code --]
msg: 'Added' // [!code ++]
}
}
}
```
## Errors and warnings
Adding the `// [!code warning]` or `// [!code error]` comments on a line will color it accordingly.
**Input**
````
```js
export default {
data () {
return {
msg: 'Error', // [!code error]
msg: 'Warning' // [!code warning]
}
}
}
```
````
**Output**
```js
export default {
data () {
return {
msg: 'Error', // [!code error]
msg: 'Warning' // [!code warning]
}
}
}
```
## Line Numbers ## Line Numbers
You can enable line numbers for each code blocks via config: You can enable line numbers for each code blocks via config:

@ -88,6 +88,7 @@
"@vueuse/core": "^9.3.0", "@vueuse/core": "^9.3.0",
"body-scroll-lock": "4.0.0-beta.0", "body-scroll-lock": "4.0.0-beta.0",
"shiki": "^0.11.1", "shiki": "^0.11.1",
"shiki-processor": "^0.1.0",
"vite": "^3.1.6", "vite": "^3.1.6",
"vue": "^3.2.40" "vue": "^3.2.40"
}, },

@ -75,6 +75,7 @@ importers:
rollup-plugin-esbuild: ^4.10.1 rollup-plugin-esbuild: ^4.10.1
semver: ^7.3.8 semver: ^7.3.8
shiki: ^0.11.1 shiki: ^0.11.1
shiki-processor: ^0.1.0
simple-git-hooks: ^2.8.0 simple-git-hooks: ^2.8.0
sirv: ^2.0.2 sirv: ^2.0.2
supports-color: ^9.2.3 supports-color: ^9.2.3
@ -92,6 +93,7 @@ importers:
'@vueuse/core': 9.3.0_vue@3.2.40 '@vueuse/core': 9.3.0_vue@3.2.40
body-scroll-lock: 4.0.0-beta.0 body-scroll-lock: 4.0.0-beta.0
shiki: 0.11.1 shiki: 0.11.1
shiki-processor: 0.1.0_shiki@0.11.1
vite: 3.1.6 vite: 3.1.6
vue: 3.2.40 vue: 3.2.40
devDependencies: devDependencies:
@ -3441,6 +3443,14 @@ packages:
resolution: {integrity: sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==} resolution: {integrity: sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==}
dev: true dev: true
/shiki-processor/0.1.0_shiki@0.11.1:
resolution: {integrity: sha512-7ty3VouP7AQMlERKeiobVeyhjUW6rPMM1b+xFcFF/XwhkN4//Fg9Ju6hPfIOvO4ztylkbLqYufbJmLJmw7SfQA==}
peerDependencies:
shiki: ^0.11.1
dependencies:
shiki: 0.11.1
dev: false
/shiki/0.11.1: /shiki/0.11.1:
resolution: {integrity: sha512-EugY9VASFuDqOexOgXR18ZV+TbFrQHeCpEYaXamO+SZlsnT/2LxuLBX25GGtIrwaEVFXUAbUQ601SWE2rMwWHA==} resolution: {integrity: sha512-EugY9VASFuDqOexOgXR18ZV+TbFrQHeCpEYaXamO+SZlsnT/2LxuLBX25GGtIrwaEVFXUAbUQ601SWE2rMwWHA==}
dependencies: dependencies:

@ -329,6 +329,62 @@
display: inline-block; display: inline-block;
} }
.vp-doc [class*='language-'] code .highlighted.error {
background-color: var(--vp-code-line-error-color);
}
.vp-doc [class*='language-'] code .highlighted.warning {
background-color: var(--vp-code-line-warning-color);
}
.vp-doc [class*='language-'] code .diff {
transition: background-color 0.5s;
margin: 0 -24px;
padding: 0 24px;
width: calc(100% + 2 * 24px);
display: inline-block;
}
.vp-doc [class*='language-'] code .diff::before {
position: absolute;
left: 1rem;
}
.vp-doc [class*='language-'] .has-focused-lines .line:not(.has-focus) {
filter: blur(0.095rem);
opacity: 0.4;
transition: filter 0.35s, opacity 0.35s;
}
.vp-doc [class*='language-'] .has-focused-lines .line:not(.has-focus) {
opacity: 0.7;
transition: filter 0.35s, opacity 0.35s;
}
.vp-doc [class*='language-']:hover .has-focused-lines .line:not(.has-focus) {
filter: blur(0);
opacity: 1;
}
.vp-doc [class*='language-'] code .diff.remove {
background-color: var(--vp-code-line-diff-remove-color);
opacity: 0.7;
}
.vp-doc [class*='language-'] code .diff.remove::before {
content: '-';
color: var(--vp-code-line-diff-remove-symbol-color);
}
.vp-doc [class*='language-'] code .diff.add {
background-color: var(--vp-code-line-diff-add-color);
}
.vp-doc [class*='language-'] code .diff.add::before {
content: '+';
color: var(--vp-code-line-diff-add-symbol-color);
}
.vp-doc div[class*='language-'].line-numbers-mode { .vp-doc div[class*='language-'].line-numbers-mode {
padding-left: 32px; padding-left: 32px;
} }

@ -209,6 +209,15 @@
--vp-code-line-highlight-color: rgba(0, 0, 0, 0.5); --vp-code-line-highlight-color: rgba(0, 0, 0, 0.5);
--vp-code-line-number-color: var(--vp-c-text-dark-3); --vp-code-line-number-color: var(--vp-c-text-dark-3);
--vp-code-line-diff-add-color: rgba(125, 191, 123, 0.1);
--vp-code-line-diff-add-symbol-color: rgba(125, 191, 123, 0.5);
--vp-code-line-diff-remove-color: rgba(255, 128, 128, 0.05);
--vp-code-line-diff-remove-symbol-color: rgba(255, 128, 128, 0.5);
--vp-code-line-error-color: var(--vp-c-red-dimm-2);
--vp-code-line-warning-color: var(--vp-c-yellow-dimm-2);
--vp-code-copy-code-hover-bg: rgba(255, 255, 255, 0.05); --vp-code-copy-code-hover-bg: rgba(255, 255, 255, 0.05);
--vp-code-copy-code-active-text: var(--vp-c-text-dark-2); --vp-code-copy-code-active-text: var(--vp-c-text-dark-2);
} }

@ -1,4 +1,14 @@
import { IThemeRegistration, getHighlighter, HtmlRendererOptions } from 'shiki' import { IThemeRegistration, HtmlRendererOptions } from 'shiki'
import {
createDiffProcessor,
createFocusProcessor,
createHighlightProcessor,
createRangeProcessor,
getHighlighter,
Processor,
addClass,
defineProcessor
} from 'shiki-processor'
import type { ThemeOptions } from '../markdown' import type { ThemeOptions } from '../markdown'
/** /**
@ -32,6 +42,14 @@ const attrsToLines = (attrs: string): HtmlRendererOptions['lineOptions'] => {
})) }))
} }
const errorLevelProcessor = defineProcessor({
name: 'error-level',
handler: createRangeProcessor({
error: ['highlighted', 'error'],
warning: ['highlighted', 'warning']
})
})
export async function highlight( export async function highlight(
theme: ThemeOptions = 'material-palenight' theme: ThemeOptions = 'material-palenight'
): Promise<(str: string, lang: string, attrs: string) => string> { ): Promise<(str: string, lang: string, attrs: string) => string> {
@ -39,10 +57,20 @@ export async function highlight(
const getThemeName = (themeValue: IThemeRegistration) => const getThemeName = (themeValue: IThemeRegistration) =>
typeof themeValue === 'string' ? themeValue : themeValue.name typeof themeValue === 'string' ? themeValue : themeValue.name
const processors: Processor[] = [
createFocusProcessor(),
createHighlightProcessor({ hasHighlightClass: 'highlighted' }),
createDiffProcessor(),
errorLevelProcessor
]
const highlighter = await getHighlighter({ const highlighter = await getHighlighter({
themes: hasSingleTheme ? [theme] : [theme.dark, theme.light] themes: hasSingleTheme ? [theme] : [theme.dark, theme.light],
processors
}) })
const preRE = /^<pre.*?>/
const styleRE = /<pre .* (style=".*")><code>/
const preRE = /^<pre(.*?)>/
const vueRE = /-vue$/ const vueRE = /-vue$/
return (str: string, lang: string, attrs: string) => { return (str: string, lang: string, attrs: string) => {
@ -50,20 +78,44 @@ export async function highlight(
lang = lang.replace(vueRE, '').toLowerCase() lang = lang.replace(vueRE, '').toLowerCase()
const lineOptions = attrsToLines(attrs) const lineOptions = attrsToLines(attrs)
const cleanup = (str: string) =>
str
.replace(preRE, (_, attributes) => `<pre ${vPre}${attributes}>`)
.replace(styleRE, (_, style) => _.replace(style, ''))
if (hasSingleTheme) { if (hasSingleTheme) {
return highlighter return cleanup(
.codeToHtml(str, { lang, lineOptions, theme: getThemeName(theme) }) highlighter.codeToHtml(str, {
.replace(preRE, `<pre ${vPre}>`) lang,
lineOptions,
theme: getThemeName(theme)
})
)
} }
const dark = highlighter const dark = addClass(
.codeToHtml(str, { lang, lineOptions, theme: getThemeName(theme.dark) }) cleanup(
.replace(preRE, `<pre ${vPre} class="vp-code-dark">`) highlighter.codeToHtml(str, {
lang,
lineOptions,
theme: getThemeName(theme.dark)
})
),
'vp-code-dark',
'pre'
)
const light = highlighter const light = addClass(
.codeToHtml(str, { lang, lineOptions, theme: getThemeName(theme.light) }) cleanup(
.replace(preRE, `<pre ${vPre} class="vp-code-light">`) highlighter.codeToHtml(str, {
lang,
lineOptions,
theme: getThemeName(theme.light)
})
),
'vp-code-light',
'pre'
)
return dark + light return dark + light
} }

Loading…
Cancel
Save