Skip to content

Commit e13e6d5

Browse files
committed
EC migration is working
1 parent b6a56d7 commit e13e6d5

File tree

5 files changed

+139
-30
lines changed

5 files changed

+139
-30
lines changed

bun.lockb

4.93 KB
Binary file not shown.

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@
2424
"license": "GPL-3.0",
2525
"devDependencies": {
2626
"@expressive-code/core": "^0.33.5",
27+
"@expressive-code/plugin-collapsible-sections": "^0.33.5",
28+
"@expressive-code/plugin-line-numbers": "^0.33.5",
2729
"@expressive-code/plugin-shiki": "^0.33.5",
30+
"@expressive-code/plugin-text-markers": "^0.33.5",
2831
"@happy-dom/global-registrator": "^14.3.6",
2932
"@lemons_dev/parsinom": "^0.0.12",
3033
"@tsconfig/svelte": "^5.0.3",

src/ObsidianTheme.ts

Lines changed: 102 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,105 @@
1-
import { type ThemeInput } from 'shiki';
1+
import { ThemeRegistration } from 'shiki';
2+
import * as hast_util_to_html_lib_types from 'hast-util-to-html/lib/types';
3+
import * as hast_types from 'hast';
24

3-
export const OBSIDIAN_THEME: ThemeInput = {
5+
interface Theme {
6+
theme: ThemeRegistration;
7+
mapping: Map<string, string>;
8+
reverseMapping: Map<string, string>;
9+
}
10+
11+
export class ThemeMapper {
12+
mapCounter: number;
13+
mapping: Map<string, string>;
14+
15+
constructor() {
16+
this.mapCounter = 0;
17+
this.mapping = new Map();
18+
}
19+
20+
getTheme(): Theme {
21+
const theme: ThemeRegistration = {
22+
displayName: OBSIDIAN_THEME.displayName,
23+
name: OBSIDIAN_THEME.name,
24+
semanticHighlighting: OBSIDIAN_THEME.semanticHighlighting,
25+
colors: Object.fromEntries(Object.entries(OBSIDIAN_THEME.colors).map(([key, value]) => [key, this.mapColor(value)])),
26+
tokenColors: OBSIDIAN_THEME.tokenColors.map(token => {
27+
const newToken = { ...token };
28+
29+
if (newToken.settings) {
30+
newToken.settings = { ...newToken.settings };
31+
}
32+
33+
if (newToken.settings.foreground) {
34+
newToken.settings.foreground = this.mapColor(newToken.settings.foreground);
35+
}
36+
37+
return newToken;
38+
}),
39+
};
40+
41+
const reverseMapping = this.getReverseMapping();
42+
43+
return {
44+
theme: theme,
45+
mapping: this.mapping,
46+
reverseMapping: reverseMapping,
47+
};
48+
}
49+
50+
mapColor(color: string): string {
51+
if (this.mapping.has(color)) {
52+
return this.mapping.get(color)!;
53+
} else {
54+
const newColor = `#${this.mapCounter.toString(16).padStart(6, '0').toUpperCase()}`;
55+
this.mapCounter += 1;
56+
this.mapping.set(color, newColor);
57+
return newColor;
58+
}
59+
}
60+
61+
private getReverseMapping(): Map<string, string> {
62+
const reverseMapping = new Map<string, string>();
63+
this.mapping.forEach((value, key) => {
64+
reverseMapping.set(value, key);
65+
});
66+
return reverseMapping;
67+
}
68+
69+
fixAST(ast: hast_util_to_html_lib_types.Parent): hast_util_to_html_lib_types.Parent {
70+
ast.children = ast.children.map(child => {
71+
if (child.type === 'element') {
72+
return this.fixNode(child);
73+
} else {
74+
return child;
75+
}
76+
});
77+
78+
return ast;
79+
}
80+
81+
private fixNode(node: hast_types.Element): hast_types.Element {
82+
if (node.properties?.style) {
83+
let style = node.properties.style as string;
84+
console.log(style);
85+
for (const [key, value] of this.mapping) {
86+
style = style.replaceAll(value, key);
87+
}
88+
console.log(style);
89+
node.properties.style = style;
90+
}
91+
92+
for (const child of node.children) {
93+
if (child.type === 'element') {
94+
this.fixNode(child);
95+
}
96+
}
97+
98+
return node;
99+
}
100+
}
101+
102+
export const OBSIDIAN_THEME = {
4103
displayName: 'Obsidian Theme',
5104
name: 'obsidian-theme',
6105
semanticHighlighting: true,
@@ -691,4 +790,4 @@ export const OBSIDIAN_THEME: ThemeInput = {
691790
},
692791
],
693792
type: 'dark',
694-
};
793+
} satisfies ThemeRegistration;

src/main.ts

Lines changed: 18 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,33 @@
11
import { Plugin } from 'obsidian';
2-
import { bundledLanguages, getHighlighter } from 'shiki';
3-
import { OBSIDIAN_THEME } from 'src/ObsidianTheme';
2+
import { bundledLanguages } from 'shiki';
3+
import { ThemeMapper } from 'src/ObsidianTheme';
44
import { ExpressiveCodeEngine, ExpressiveCodeTheme } from '@expressive-code/core';
55
import { pluginShiki } from '@expressive-code/plugin-shiki';
66
import { toHtml } from 'hast-util-to-html';
7+
import { pluginTextMarkers } from '@expressive-code/plugin-text-markers';
78

89
// some languages break obsidian's `registerMarkdownCodeBlockProcessor`, so we blacklist them
910
const languageNameBlacklist = new Set(['c++', 'c#', 'f#']);
1011

1112
export default class ShikiPlugin extends Plugin {
1213
async onload(): Promise<void> {
13-
// const highlighter = await getHighlighter({
14-
// themes: [OBSIDIAN_THEME],
15-
// langs: Object.keys(bundledLanguages),
16-
// });
17-
18-
const registeredLanguages = new Set<string>();
14+
const themeMapper = new ThemeMapper();
15+
const theme = themeMapper.getTheme();
1916

2017
const ec = new ExpressiveCodeEngine({
21-
themes: [new ExpressiveCodeTheme(OBSIDIAN_THEME as any)],
18+
themes: [new ExpressiveCodeTheme(theme.theme)],
2219
plugins: [
2320
pluginShiki({
24-
langs: Object.entries(bundledLanguages).map(([_, langFn]) => langFn),
21+
langs: Object.values(bundledLanguages),
2522
}),
23+
// pluginCollapsibleSections(),
24+
pluginTextMarkers(),
25+
// pluginLineNumbers(),
2626
],
27+
minSyntaxHighlightingColorContrast: 0,
2728
});
2829

29-
console.log(ec);
30+
const registeredLanguages = new Set<string>();
3031

3132
for (const [shikiLanguage, registration] of Object.entries(bundledLanguages)) {
3233
// the last element of the array is seemingly the most recent version of the language
@@ -52,25 +53,16 @@ export default class ShikiPlugin extends Plugin {
5253

5354
// register the language with obsidian
5455
this.registerMarkdownCodeBlockProcessor(languageAlias, async (source, el, ctx) => {
55-
// yes, this is innerHTML, but we trust shiki
56-
// and this is how shiki recommends using it
57-
// https://shiki.style/guide/install#shorthands
58-
// this is also async, against what eslint thinks
59-
// eslint-disable-next-line @typescript-eslint/await-thenable
60-
// el.innerHTML = await highlighter.codeToHtml(source, {
61-
// lang: shikiLanguage,
62-
// theme: 'obsidian-theme',
63-
// meta: {
64-
// __raw: '',
65-
// },
66-
// });
67-
6856
const rederResult = await ec.render({
6957
code: source,
70-
language: language.name,
58+
language: shikiLanguage,
59+
meta: '{1-2}',
7160
});
7261

73-
el.innerHTML = toHtml(rederResult.renderedGroupAst);
62+
const ast = themeMapper.fixAST(rederResult.renderedGroupAst);
63+
64+
// yes, this is innerHTML, but we trust hast
65+
el.innerHTML = toHtml(ast);
7466
});
7567
}
7668
}

styles.css

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,23 @@ body {
1212
}
1313

1414
/* Disable ligatures in code blocks, as they can cause incorrect highlighting */
15-
pre.shiki {
15+
div.expressive-code {
1616
font-variant-ligatures: no-contextual;
17+
18+
div.ec-line {
19+
span {
20+
color: var(--0);
21+
font-style: var(--0fs);
22+
}
23+
24+
&.highlight {
25+
background-color: var(--highlight);
26+
}
27+
28+
&.mark {
29+
--highlight: var(--background-modifier-hover);
30+
}
31+
}
1732
}
1833

1934
/* Hide the frontmatter code block in reading view again */

0 commit comments

Comments
 (0)