Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 4 commits into from Oct 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
133 changes: 129 additions & 4 deletions docs/guide/markdown.md
Expand Up @@ -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**
Expand Down Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -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"
},
Expand Down
10 changes: 10 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

56 changes: 56 additions & 0 deletions src/client/theme-default/styles/components/vp-doc.css
Expand Up @@ -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;
}
Expand Down
9 changes: 9 additions & 0 deletions src/client/theme-default/styles/vars.css
Expand Up @@ -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);
}
Expand Down
76 changes: 64 additions & 12 deletions src/node/markdown/plugins/highlight.ts
@@ -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'

/**
Expand Down Expand Up @@ -32,38 +42,80 @@ 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> {
const hasSingleTheme = typeof theme === 'string' || 'name' in theme
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) => {
const vPre = vueRE.test(lang) ? '' : 'v-pre'
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',
brc-dd marked this conversation as resolved.
Show resolved Hide resolved
'pre'
)

return dark + light
}
Expand Down