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

@ -71,11 +71,11 @@ For more details, see [Frontmatter](./frontmatter).
**Input**
```
| Tables | Are | Cool |
| ------------- |:-------------:| -----:|
| Tables | Are | Cool |
| ------------- | :-----------: | ----: |
| col 3 is | right-aligned | $1600 |
| col 2 is | centered | $12 |
| zebra stripes | are neat | $1 |
| col 2 is | centered | $12 |
| zebra stripes | are neat | $1 |
```
**Output**
@ -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
You can enable line numbers for each code blocks via config:

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

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

@ -329,6 +329,62 @@
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 {
padding-left: 32px;
}

@ -209,6 +209,15 @@
--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-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-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'
/**
@ -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(
theme: ThemeOptions = 'material-palenight'
): Promise<(str: string, lang: string, attrs: string) => string> {
@ -39,10 +57,20 @@ export async function highlight(
const getThemeName = (themeValue: IThemeRegistration) =>
typeof themeValue === 'string' ? themeValue : themeValue.name
const processors: Processor[] = [
createFocusProcessor(),
createHighlightProcessor({ hasHighlightClass: 'highlighted' }),
createDiffProcessor(),
errorLevelProcessor
]
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$/
return (str: string, lang: string, attrs: string) => {
@ -50,20 +78,44 @@ export async function highlight(
lang = lang.replace(vueRE, '').toLowerCase()
const lineOptions = attrsToLines(attrs)
const cleanup = (str: string) =>
str
.replace(preRE, (_, attributes) => `<pre ${vPre}${attributes}>`)
.replace(styleRE, (_, style) => _.replace(style, ''))
if (hasSingleTheme) {
return highlighter
.codeToHtml(str, { lang, lineOptions, theme: getThemeName(theme) })
.replace(preRE, `<pre ${vPre}>`)
return cleanup(
highlighter.codeToHtml(str, {
lang,
lineOptions,
theme: getThemeName(theme)
})
)
}
const dark = highlighter
.codeToHtml(str, { lang, lineOptions, theme: getThemeName(theme.dark) })
.replace(preRE, `<pre ${vPre} class="vp-code-dark">`)
const dark = addClass(
cleanup(
highlighter.codeToHtml(str, {
lang,
lineOptions,
theme: getThemeName(theme.dark)
})
),
'vp-code-dark',
'pre'
)
const light = highlighter
.codeToHtml(str, { lang, lineOptions, theme: getThemeName(theme.light) })
.replace(preRE, `<pre ${vPre} class="vp-code-light">`)
const light = addClass(
cleanup(
highlighter.codeToHtml(str, {
lang,
lineOptions,
theme: getThemeName(theme.light)
})
),
'vp-code-light',
'pre'
)
return dark + light
}

Loading…
Cancel
Save