Skip to content

Commit bf2715e

Browse files
authored
feat: prevent $ symbol selection in shell code (#5025)
1 parent 8da5e74 commit bf2715e

3 files changed

Lines changed: 46 additions & 4 deletions

File tree

src/client/app/composables/copyCode.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { inBrowser } from 'vitepress'
2+
import { isShell } from '../../shared'
23

3-
const shellRE = /language-(shellscript|shell|bash|sh|zsh)/
44
const ignoredNodes = ['.vp-copy-ignore', '.diff.remove'].join(', ')
55

66
export function useCopyCode() {
@@ -15,8 +15,6 @@ export function useCopyCode() {
1515
return
1616
}
1717

18-
const isShell = shellRE.test(parent.className)
19-
2018
// Clone the node and remove the ignored nodes
2119
const clone = sibling.cloneNode(true) as HTMLElement
2220
clone.querySelectorAll(ignoredNodes).forEach((node) => node.remove())
@@ -26,7 +24,10 @@ export function useCopyCode() {
2624

2725
let text = clone.textContent || ''
2826

29-
if (isShell) {
27+
// NOTE: Any changes to this the code here may also need to update
28+
// `transformerDisableShellSymbolSelect` in `src/node/markdown/plugins/highlight.ts`
29+
const lang = /language-(\w+)/.exec(parent.className)?.[1] || ''
30+
if (isShell(lang)) {
3031
text = text.replace(/^ *(\$|>) /gm, '').trim()
3132
}
3233

src/node/markdown/plugins/highlight.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import c from 'picocolors'
1111
import type { BundledLanguage, ShikiTransformer } from 'shiki'
1212
import { createHighlighter, guessEmbeddedLanguages, isSpecialLang } from 'shiki'
1313
import type { Logger } from 'vite'
14+
import { isShell } from '../../shared'
1415
import type { MarkdownOptions, ThemeOptions } from '../markdown'
1516

1617
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 10)
@@ -47,6 +48,40 @@ function attrsToLines(attrs: string): TransformerCompactLineOption[] {
4748
}))
4849
}
4950

51+
/**
52+
* Prevents the leading '$' symbol etc from being selectable/copyable. Also
53+
* normalizes its syntax so there's no leading spaces, and only a single
54+
* trailing space.
55+
*
56+
* NOTE: Any changes to this function may also need to update
57+
* `src/client/app/composables/copyCode.ts`
58+
*/
59+
function transformerDisableShellSymbolSelect(): ShikiTransformer {
60+
return {
61+
name: 'vitepress:disable-shell-symbol-select',
62+
tokens(tokensByLine) {
63+
if (!isShell(this.options.lang)) return
64+
65+
for (const tokens of tokensByLine) {
66+
if (tokens.length < 2) continue
67+
68+
// The first token should only be a symbol token
69+
const firstTokenText = tokens[0].content.trim()
70+
if (firstTokenText !== '$' && firstTokenText !== '>') continue
71+
72+
// The second token must have a leading space (separates the symbol)
73+
if (tokens[1].content[0] !== ' ') continue
74+
75+
tokens[0].content = firstTokenText + ' '
76+
tokens[0].htmlStyle ??= {}
77+
tokens[0].htmlStyle['user-select'] = 'none'
78+
tokens[0].htmlStyle['-webkit-user-select'] = 'none'
79+
tokens[1].content = tokens[1].content.slice(1)
80+
}
81+
}
82+
}
83+
}
84+
5085
export async function highlight(
5186
theme: ThemeOptions,
5287
options: MarkdownOptions,
@@ -83,6 +118,7 @@ export async function highlight(
83118
}),
84119
transformerNotationHighlight(),
85120
transformerNotationErrorLevel(),
121+
transformerDisableShellSymbolSelect(),
86122
{
87123
name: 'vitepress:add-dir',
88124
pre(node) {

src/shared/shared.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,3 +353,8 @@ type ObjectType = Record<PropertyKey, any>
353353
export function isObject(value: unknown): value is ObjectType {
354354
return Object.prototype.toString.call(value) === '[object Object]'
355355
}
356+
357+
const shellLangs = ['shellscript', 'shell', 'bash', 'sh', 'zsh']
358+
export function isShell(lang: string): boolean {
359+
return shellLangs.includes(lang)
360+
}

0 commit comments

Comments
 (0)