feat: add syntax highlighting and line highlighting features with customizable themes

This commit is contained in:
2026-01-07 16:50:20 +02:00
parent 866929c358
commit 05b80d1e0c
8 changed files with 410 additions and 54 deletions
+50 -4
View File
@@ -1,16 +1,17 @@
import { codeToHtml } from 'shiki';
import { codeToHtml, codeToTokens, type BundledTheme, type BundledLanguage } from 'shiki';
import { CODE_THEMES, type CodeThemeId } from '../types';
let highlighterReady = false;
export async function highlightCode(
code: string,
language: string,
theme: 'dark' | 'light'
theme: CodeThemeId
): Promise<string> {
try {
const html = await codeToHtml(code, {
lang: language,
theme: theme === 'dark' ? 'github-dark' : 'github-light',
lang: language as BundledLanguage,
theme: theme as BundledTheme,
});
highlighterReady = true;
return html;
@@ -20,6 +21,51 @@ export async function highlightCode(
}
}
export interface TokenInfo {
content: string;
color: string;
}
export interface LineTokens {
tokens: TokenInfo[];
}
export async function tokenizeCode(
code: string,
language: string,
theme: CodeThemeId
): Promise<LineTokens[]> {
try {
const result = await codeToTokens(code, {
lang: language as BundledLanguage,
theme: theme as BundledTheme,
});
highlighterReady = true;
return result.tokens.map(line => ({
tokens: line.map(token => ({
content: token.content,
color: token.color || '#ffffff',
})),
}));
} catch (error) {
console.error('Tokenization error:', error);
// Fallback: return plain text tokens
return code.split('\n').map(line => ({
tokens: [{ content: line, color: '#ffffff' }],
}));
}
}
export function getThemeBackground(themeId: CodeThemeId): string {
const theme = CODE_THEMES.find(t => t.id === themeId);
return theme?.bg || '#1e1e2e';
}
export function isLightTheme(themeId: CodeThemeId): boolean {
const lightThemes = ['github-light', 'vitesse-light', 'catppuccin-latte', 'min-light', 'solarized-light'];
return lightThemes.includes(themeId);
}
function escapeHtml(text: string): string {
return text
.replace(/&/g, '&amp;')