Skip to content

Commit 07f189a

Browse files
committed
some more tweaks
1 parent 7036382 commit 07f189a

File tree

3 files changed

+87
-25
lines changed

3 files changed

+87
-25
lines changed

exampleVault/index.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ test: hello
44

55
TypeScript code
66

7-
```ts title="A part of ParsiNOM" {13-15}
7+
```ts title="A part of ParsiNOM" {13-15, 22-29} showLineNumbers
88
export class Parser<const SType extends STypeBase> {
99
public p: ParseFunction<SType>;
1010

@@ -62,7 +62,7 @@ input:is([data-task="字"], [data-task="字"] > *):checked::after {
6262

6363
Bash
6464

65-
```bash title="My Bash Script"
65+
```bash title="Other Title"
6666
echo "Hello"
6767
```
6868

src/CodeBlock.ts

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { MarkdownRenderChild, type MarkdownSectionInformation } from 'obsidian';
1+
import { type MarkdownPostProcessorContext, MarkdownRenderChild } from 'obsidian';
22
import type ShikiPlugin from 'src/main';
33
import { toHtml } from 'hast-util-to-html';
44

@@ -7,32 +7,29 @@ export class CodeBlock extends MarkdownRenderChild {
77
source: string;
88
language: string;
99
languageShorthand: string;
10-
sectionInfo: MarkdownSectionInformation | null;
11-
12-
constructor(
13-
plugin: ShikiPlugin,
14-
containerEl: HTMLElement,
15-
source: string,
16-
language: string,
17-
languageShorthand: string,
18-
sectionInfo: MarkdownSectionInformation | null,
19-
) {
10+
ctx: MarkdownPostProcessorContext;
11+
cachedMetaString: string;
12+
13+
constructor(plugin: ShikiPlugin, containerEl: HTMLElement, source: string, language: string, languageShorthand: string, ctx: MarkdownPostProcessorContext) {
2014
super(containerEl);
2115

2216
this.plugin = plugin;
2317
this.source = source;
2418
this.language = language;
2519
this.languageShorthand = languageShorthand;
26-
this.sectionInfo = sectionInfo;
20+
this.ctx = ctx;
21+
this.cachedMetaString = '';
2722
}
2823

29-
getMetaString(): string {
30-
if (this.sectionInfo === null) {
24+
private getMetaString(): string {
25+
const sectionInfo = this.ctx.getSectionInfo(this.containerEl);
26+
27+
if (sectionInfo === null) {
3128
return '';
3229
}
3330

34-
const lines = this.sectionInfo.text.split('\n');
35-
const startLine = lines[this.sectionInfo.lineStart];
31+
const lines = sectionInfo.text.split('\n');
32+
const startLine = lines[sectionInfo.lineStart];
3633

3734
// regexp to match the text after the code block language
3835
const regex = new RegExp('^[^`~]*?(```+|~~~+)' + this.languageShorthand + ' (.*)', 'g');
@@ -44,11 +41,7 @@ export class CodeBlock extends MarkdownRenderChild {
4441
}
4542
}
4643

47-
public async onload(): Promise<void> {
48-
super.onload();
49-
50-
const metaString = this.getMetaString();
51-
44+
private async render(metaString: string): Promise<void> {
5245
const renderResult = await this.plugin.ec.render({
5346
code: this.source,
5447
language: this.language,
@@ -61,7 +54,32 @@ export class CodeBlock extends MarkdownRenderChild {
6154
this.containerEl.innerHTML = toHtml(ast);
6255
}
6356

57+
public async rerenderOnNoteChange(): Promise<void> {
58+
// compare the new meta string to the cached one
59+
// only rerender if they are different, to avoid unnecessary work
60+
// since the meta string is likely to be the same most of the time
61+
// and if the code block content changes obsidian will rerender for us
62+
const newMetaString = this.getMetaString();
63+
if (newMetaString !== this.cachedMetaString) {
64+
this.cachedMetaString = newMetaString;
65+
await this.render(newMetaString);
66+
}
67+
}
68+
69+
public async onload(): Promise<void> {
70+
super.onload();
71+
72+
this.plugin.addActiveCodeBlock(this);
73+
74+
this.cachedMetaString = this.getMetaString();
75+
await this.render(this.cachedMetaString);
76+
}
77+
6478
public onunload(): void {
79+
super.onunload();
80+
81+
this.plugin.removeActiveCodeBlock(this);
82+
6583
this.containerEl.empty();
6684
this.containerEl.innerText = 'unloaded shiki code block';
6785
}

src/main.ts

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Plugin } from 'obsidian';
1+
import { Plugin, TFile } from 'obsidian';
22
import { bundledLanguages } from 'shiki';
33
import { ExpressiveCodeEngine, ExpressiveCodeTheme } from '@expressive-code/core';
44
import { pluginShiki } from '@expressive-code/plugin-shiki';
@@ -20,13 +20,33 @@ export default class ShikiPlugin extends Plugin {
2020
ec: ExpressiveCodeEngine;
2121
// @ts-expect-error TS2564
2222
ecElements: HTMLElement[];
23+
// @ts-expect-error TS2564
24+
activeCodeBlocks: Map<string, CodeBlock[]>;
2325

2426
async onload(): Promise<void> {
2527
this.themeMapper = new ThemeMapper();
28+
this.activeCodeBlocks = new Map();
2629

2730
await this.loadEC();
2831

2932
await this.registerCodeBlockProcessors();
33+
34+
// this is a workaround for the fact that obsidian does not rerender the code block
35+
// when the start line with the language changes, and we need that for the EC meta string
36+
this.registerEvent(
37+
this.app.vault.on('modify', async file => {
38+
// sleep 0 so that the code block context is updated before we rerender
39+
await sleep(100);
40+
41+
if (file instanceof TFile) {
42+
if (this.activeCodeBlocks.has(file.path)) {
43+
for (const codeBlock of this.activeCodeBlocks.get(file.path)!) {
44+
void codeBlock.rerenderOnNoteChange();
45+
}
46+
}
47+
}
48+
}),
49+
);
3050
}
3151

3252
async loadEC(): Promise<void> {
@@ -44,6 +64,9 @@ export default class ShikiPlugin extends Plugin {
4464
styleOverrides: EC_THEME,
4565
minSyntaxHighlightingColorContrast: 0,
4666
themeCssRoot: 'div.expressive-code',
67+
defaultProps: {
68+
showLineNumbers: false,
69+
},
4770
});
4871

4972
this.ecElements = [];
@@ -84,7 +107,7 @@ export default class ShikiPlugin extends Plugin {
84107

85108
// register the language with obsidian
86109
this.registerMarkdownCodeBlockProcessor(languageAlias, async (source, el, ctx) => {
87-
const codeBlock = new CodeBlock(this, el, source, shikiLanguage, languageAlias, ctx.getSectionInfo(el));
110+
const codeBlock = new CodeBlock(this, el, source, shikiLanguage, languageAlias, ctx);
88111

89112
ctx.addChild(codeBlock);
90113
});
@@ -102,4 +125,25 @@ export default class ShikiPlugin extends Plugin {
102125
}
103126
this.ecElements = [];
104127
}
128+
129+
addActiveCodeBlock(codeBlock: CodeBlock): void {
130+
const filePath = codeBlock.ctx.sourcePath;
131+
132+
if (!this.activeCodeBlocks.has(filePath)) {
133+
this.activeCodeBlocks.set(filePath, [codeBlock]);
134+
} else {
135+
this.activeCodeBlocks.get(filePath)!.push(codeBlock);
136+
}
137+
}
138+
139+
removeActiveCodeBlock(codeBlock: CodeBlock): void {
140+
const filePath = codeBlock.ctx.sourcePath;
141+
142+
if (this.activeCodeBlocks.has(filePath)) {
143+
const index = this.activeCodeBlocks.get(filePath)!.indexOf(codeBlock);
144+
if (index !== -1) {
145+
this.activeCodeBlocks.get(filePath)!.splice(index, 1);
146+
}
147+
}
148+
}
105149
}

0 commit comments

Comments
 (0)