Skip to content

Commit 9b96a31

Browse files
committed
Merge codeblock syntax highlighting. panphora/overtype#35
1 parent dddd0a9 commit 9b96a31

File tree

2 files changed

+103
-16
lines changed

2 files changed

+103
-16
lines changed

browser-extension/src/overtype/overtype.js

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ class OverType {
169169
toolbar: false,
170170
statsFormatter: null,
171171
smartLists: true, // Enable smart list continuation
172+
codeHighlighter: null, // Per-instance code highlighter
172173
};
173174

174175
// Remove theme and colors from options - these are now global
@@ -475,7 +476,8 @@ class OverType {
475476
const html = MarkdownParser.parse(
476477
text,
477478
activeLine,
478-
this.options.showActiveLineRaw
479+
this.options.showActiveLineRaw,
480+
this.options.codeHighlighter
479481
);
480482
this.preview.innerHTML =
481483
html || '<span style="color: #808080;">Start typing...</span>';
@@ -819,11 +821,16 @@ class OverType {
819821
*/
820822
getRenderedHTML(processForPreview = false) {
821823
const markdown = this.getValue();
822-
let html = MarkdownParser.parse(markdown);
824+
let html = MarkdownParser.parse(
825+
markdown,
826+
-1,
827+
false,
828+
this.options.codeHighlighter
829+
);
823830

824831
if (processForPreview) {
825832
// Post-process HTML for preview mode
826-
html = MarkdownParser.postProcessHTML(html);
833+
html = MarkdownParser.postProcessHTML(html, this.options.codeHighlighter);
827834
}
828835

829836
return html;
@@ -869,6 +876,15 @@ class OverType {
869876
this.updatePreview();
870877
}
871878

879+
/**
880+
* Set instance-specific code highlighter
881+
* @param {Function|null} highlighter - Function that takes (code, language) and returns highlighted HTML
882+
*/
883+
setCodeHighlighter(highlighter) {
884+
this.options.codeHighlighter = highlighter;
885+
this.updatePreview();
886+
}
887+
872888
/**
873889
* Update stats bar
874890
* @private
@@ -1144,6 +1160,22 @@ class OverType {
11441160
OverType.stylesInjected = true;
11451161
}
11461162

1163+
/**
1164+
* Set global code highlighter for all OverType instances
1165+
* @param {Function|null} highlighter - Function that takes (code, language) and returns highlighted HTML
1166+
*/
1167+
static setCodeHighlighter(highlighter) {
1168+
MarkdownParser.setCodeHighlighter(highlighter);
1169+
1170+
// Update all existing instances
1171+
document.querySelectorAll(".overtype-wrapper").forEach((wrapper) => {
1172+
const instance = wrapper._instance;
1173+
if (instance && instance.updatePreview) {
1174+
instance.updatePreview();
1175+
}
1176+
});
1177+
}
1178+
11471179
/**
11481180
* Set global theme for all OverType instances
11491181
* @param {string|Object} theme - Theme name or custom theme object

browser-extension/src/overtype/parser.js

Lines changed: 68 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,24 @@ export class MarkdownParser {
1010
// Track link index for anchor naming
1111
static linkIndex = 0;
1212

13+
// Global code highlighter function
14+
static codeHighlighter = null;
15+
1316
/**
1417
* Reset link index (call before parsing a new document)
1518
*/
1619
static resetLinkIndex() {
1720
this.linkIndex = 0;
1821
}
1922

23+
/**
24+
* Set global code highlighter function
25+
* @param {Function|null} highlighter - Function that takes (code, language) and returns highlighted HTML
26+
*/
27+
static setCodeHighlighter(highlighter) {
28+
this.codeHighlighter = highlighter;
29+
}
30+
2031
/**
2132
* Escape HTML special characters
2233
* @param {string} text - Raw text to escape
@@ -323,9 +334,15 @@ export class MarkdownParser {
323334
* @param {string} text - Full markdown text
324335
* @param {number} activeLine - Currently active line index (optional)
325336
* @param {boolean} showActiveLineRaw - Show raw markdown on active line
337+
* @param {Function} instanceHighlighter - Instance-specific code highlighter (optional)
326338
* @returns {string} Parsed HTML
327339
*/
328-
static parse(text, activeLine = -1, showActiveLineRaw = false) {
340+
static parse(
341+
text,
342+
activeLine = -1,
343+
showActiveLineRaw = false,
344+
instanceHighlighter = null
345+
) {
329346
// Reset link counter for each parse
330347
this.resetLinkIndex();
331348

@@ -362,19 +379,20 @@ export class MarkdownParser {
362379
const html = parsedLines.join("");
363380

364381
// Apply post-processing for list consolidation
365-
return this.postProcessHTML(html);
382+
return this.postProcessHTML(html, instanceHighlighter);
366383
}
367384

368385
/**
369386
* Post-process HTML to consolidate lists and code blocks
370387
* @param {string} html - HTML to post-process
388+
* @param {Function} instanceHighlighter - Instance-specific code highlighter (optional)
371389
* @returns {string} Post-processed HTML with consolidated lists and code blocks
372390
*/
373-
static postProcessHTML(html) {
391+
static postProcessHTML(html, instanceHighlighter = null) {
374392
// Check if we're in a browser environment
375393
if (typeof document === "undefined" || !document) {
376394
// In Node.js environment - do manual post-processing
377-
return this.postProcessHTMLManual(html);
395+
return this.postProcessHTMLManual(html, instanceHighlighter);
378396
}
379397

380398
// Parse HTML string into DOM
@@ -421,9 +439,29 @@ export class MarkdownParser {
421439

422440
// Store reference to the code element for adding content
423441
currentCodeBlock._codeElement = codeElement;
442+
currentCodeBlock._language = lang;
443+
currentCodeBlock._codeContent = "";
424444
continue;
425445
} else {
426-
// End of code block - fence stays visible
446+
// End of code block - apply highlighting if needed
447+
const highlighter = instanceHighlighter || this.codeHighlighter;
448+
if (
449+
currentCodeBlock &&
450+
highlighter &&
451+
currentCodeBlock._codeContent
452+
) {
453+
try {
454+
const highlightedCode = highlighter(
455+
currentCodeBlock._codeContent,
456+
currentCodeBlock._language || ""
457+
);
458+
currentCodeBlock._codeElement.innerHTML = highlightedCode;
459+
} catch (error) {
460+
console.warn("Code highlighting failed:", error);
461+
// Keep the plain text content as fallback
462+
}
463+
}
464+
427465
inCodeBlock = false;
428466
currentCodeBlock = null;
429467
continue;
@@ -441,14 +479,18 @@ export class MarkdownParser {
441479
const codeElement =
442480
currentCodeBlock._codeElement ||
443481
currentCodeBlock.querySelector("code");
444-
// Add the line content to the code block
445-
if (codeElement.textContent.length > 0) {
446-
codeElement.textContent += "\n";
482+
// Add the line content to the code block content (for highlighting)
483+
if (currentCodeBlock._codeContent.length > 0) {
484+
currentCodeBlock._codeContent += "\n";
447485
}
448486
// Get the actual text content, preserving spaces
449-
// Use textContent instead of innerHTML to avoid double-escaping
450-
// textContent automatically decodes HTML entities
451487
const lineText = child.textContent.replace(/\u00A0/g, " "); // \u00A0 is nbsp
488+
currentCodeBlock._codeContent += lineText;
489+
490+
// Also add to the code element (fallback if no highlighter)
491+
if (codeElement.textContent.length > 0) {
492+
codeElement.textContent += "\n";
493+
}
452494
codeElement.textContent += lineText;
453495
child.remove();
454496
continue;
@@ -498,9 +540,10 @@ export class MarkdownParser {
498540
/**
499541
* Manual post-processing for Node.js environments (without DOM)
500542
* @param {string} html - HTML to post-process
543+
* @param {Function} instanceHighlighter - Instance-specific code highlighter (optional)
501544
* @returns {string} Post-processed HTML
502545
*/
503-
static postProcessHTMLManual(html) {
546+
static postProcessHTMLManual(html, instanceHighlighter = null) {
504547
let processed = html;
505548

506549
// Process unordered lists
@@ -549,10 +592,22 @@ export class MarkdownParser {
549592
const lang = openFence.slice(3).trim();
550593
const langClass = lang ? ` class="language-${lang}"` : "";
551594

595+
// Apply code highlighting if available
596+
let highlightedContent = codeContent;
597+
const highlighter = instanceHighlighter || this.codeHighlighter;
598+
if (highlighter) {
599+
try {
600+
highlightedContent = highlighter(codeContent, lang);
601+
} catch (error) {
602+
console.warn("Code highlighting failed:", error);
603+
// Fall back to original content
604+
}
605+
}
606+
552607
// Keep fence markers visible as separate divs, with pre/code block between them
553608
let result = `<div><span class="code-fence">${openFence}</span></div>`;
554-
// Content is already escaped, don't double-escape
555-
result += `<pre class="code-block"><code${langClass}>${codeContent}</code></pre>`;
609+
// Use highlighted content if available, otherwise use escaped content
610+
result += `<pre class="code-block"><code${langClass}>${highlightedContent}</code></pre>`;
556611
result += `<div><span class="code-fence">${closeFence}</span></div>`;
557612

558613
return result;

0 commit comments

Comments
 (0)