Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
feat: support focus, colored diffs, error highlights in code blocks (#…
…1534)

Co-authored-by: Divyansh Singh <40380293+brc-dd@users.noreply.github.com>
  • Loading branch information
innocenzi and brc-dd committed Oct 25, 2022
1 parent a29a4a6 commit 04ab0eb
Show file tree
Hide file tree
Showing 6 changed files with 269 additions and 16 deletions.
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',
'pre'
)

return dark + light
}
Expand Down

0 comments on commit 04ab0eb

Please sign in to comment.