forked from code-hike/codehike
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhighlight.ts
More file actions
172 lines (161 loc) · 4.29 KB
/
Copy pathhighlight.ts
File metadata and controls
172 lines (161 loc) · 4.29 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
import { splitAnnotationsAndCode } from "./extract-annotations.js"
import {
CodeAnnotation,
RawCode,
HighlightedCode,
Token,
Whitespace,
isWhitespace,
} from "./types.js"
import {
Lines,
Tokens,
highlight as lighter,
LANG_NAMES,
Theme,
} from "@code-hike/lighter"
type AnyToken = Token | Whitespace
export async function highlight(
data: RawCode,
theme: Theme,
config: { annotationPrefix?: string } = {},
): Promise<HighlightedCode> {
let { value, lang = "txt" } = data
if (!LANG_NAMES.includes(lang)) {
console.warn(`Code Hike warning: Unknown language "${lang}"`)
lang = "txt"
}
const { code, annotations } = await splitAnnotationsAndCode(
value,
lang,
config.annotationPrefix || "!",
)
const {
lines,
lang: lighterLang,
style,
} = await lighter(code, lang, theme as any, {
annotations: [],
scopes: false, // true for better token transitions, but breaks css themes
})
const tokens = joinLines(lines)
// split surrounding whitespace for each token
const splitTokens = splitWhitespace(tokens)
// join consecutive whitespace tokens
const joinedTokens = joinWhitespace(splitTokens)
return {
...data,
code,
tokens: joinedTokens,
lang: lighterLang,
annotations: compatAnnotations(annotations),
themeName: typeof theme === "string" ? theme : theme?.name || "unknown",
style,
}
}
function compatAnnotations(annotations: any[]): CodeAnnotation[] {
const newAnnotations: CodeAnnotation[] = []
for (const a of annotations) {
const { name, query, ranges } = a
for (const r of ranges) {
if (r.lineNumber) {
const { lineNumber, fromColumn, toColumn } = r
newAnnotations.push({
name,
query,
lineNumber,
fromColumn,
toColumn,
})
} else {
const { fromLineNumber, toLineNumber } = r
newAnnotations.push({
name,
query,
fromLineNumber,
toLineNumber,
})
}
}
}
return newAnnotations
}
// group the Lines into one array
function joinLines(lines: Lines): AnyToken[] {
const joinedTokens: AnyToken[] = []
lines.forEach((lineOrGroup, i) => {
if ("lines" in lineOrGroup) {
throw new Error("Shouldnt be groups")
} else {
const tokens = joinTokens(lineOrGroup.tokens)
joinedTokens.push(...tokens)
if (i < lines.length - 1) {
joinedTokens.push("\n")
}
}
})
return joinedTokens
}
function joinTokens(tokens: Tokens): AnyToken[] {
return tokens.map((tokenOrGroup) => {
if ("tokens" in tokenOrGroup) {
throw new Error("Shouldnt be groups")
} else {
const t = [tokenOrGroup.content] as Token
const { color, ...rest } = tokenOrGroup.style || {}
t.push(color)
if (Object.keys(rest).length) {
t.push(rest)
}
return t
}
})
}
function splitWhitespace(tokens: AnyToken[]) {
const ejected: AnyToken[] = []
tokens.forEach((tokenOrGroup) => {
if (isWhitespace(tokenOrGroup)) {
ejected.push(tokenOrGroup)
} else {
const [before, content, after] = splitSurroundingWhitespace(
tokenOrGroup[0],
)
if (before?.length) {
ejected.push(before)
}
if (content.length) {
const copy = [...tokenOrGroup] as Token
copy[0] = content
ejected.push(copy)
}
if (after?.length) {
ejected.push(after)
}
}
})
return ejected
}
function joinWhitespace(tokens: AnyToken[]) {
const joinedTokens: AnyToken[] = []
tokens.forEach((tokenOrGroup) => {
if (isWhitespace(tokenOrGroup)) {
let last = joinedTokens[joinedTokens.length - 1]
if (last && isWhitespace(last)) {
joinedTokens[joinedTokens.length - 1] += tokenOrGroup
} else if (tokenOrGroup !== "") {
joinedTokens.push(tokenOrGroup)
}
} else if (tokenOrGroup[0].length > 0) {
joinedTokens.push(tokenOrGroup)
}
})
return joinedTokens
}
// splits " \t foo bar \n" into [" \t ","foo bar"," \n"]
// "foo bar" -> ["","foo bar",""]
function splitSurroundingWhitespace(content: string) {
const trimmed = content.trim()
const before = content.slice(0, content.indexOf(trimmed))
const after = content.slice(content.indexOf(trimmed) + trimmed.length)
return [before, trimmed, after]
}