@@ -5,8 +5,20 @@ import { SampleCodeFile } from "@/types/sample"
55import { ChevronRight , File , Folder } from "lucide-react"
66import { cn } from "@/lib/utils"
77
8- // Syntax highlighting function
8+ // Syntax highlighting function using markers to prevent regex from matching inside HTML tags
99function highlightCode ( code : string ) : string {
10+ // Use unique markers that won't appear in code
11+ const MARKERS = {
12+ COMMENT : '[[C:' ,
13+ STRING : '[[S:' ,
14+ KEYWORD : '[[K:' ,
15+ NUMBER : '[[N:' ,
16+ LITERAL : '[[L:' ,
17+ FUNCTION : '[[F:' ,
18+ TYPE : '[[T:' ,
19+ END : ':]]'
20+ }
21+
1022 let highlighted = code
1123
1224 // Escape HTML first
@@ -18,45 +30,91 @@ function highlightCode(code: string): string {
1830 // Comments (must be done before other highlighting)
1931 highlighted = highlighted . replace (
2032 / ( \/ \/ .* $ | \/ \* [ \s \S ] * ?\* \/ ) / gm,
21- '<span class="code-comment">$1</span>'
33+ ` ${ MARKERS . COMMENT } $1 ${ MARKERS . END } `
2234 )
2335
24- // Strings
36+ // Strings - but not inside already-marked regions
2537 highlighted = highlighted . replace (
2638 / ( " (?: [ ^ " \\ ] | \\ .) * " | ' (?: [ ^ ' \\ ] | \\ .) * ' | ` (?: [ ^ ` \\ ] | \\ .) * ` ) / g,
27- '<span class="code-string">$1</span>'
39+ ( match ) => {
40+ // Skip if already inside a marker
41+ if ( match . includes ( '[[' ) || match . includes ( ':]]' ) ) return match
42+ return `${ MARKERS . STRING } ${ match } ${ MARKERS . END } `
43+ }
2844 )
2945
30- // Keywords
46+ // Keywords - but not inside already-marked regions
3147 highlighted = highlighted . replace (
3248 / \b ( i m p o r t | e x p o r t | f r o m | c o n s t | l e t | v a r | f u n c t i o n | r e t u r n | i f | e l s e | f o r | w h i l e | c l a s s | i n t e r f a c e | t y p e | e x t e n d s | i m p l e m e n t s | a s y n c | a w a i t | t r y | c a t c h | t h r o w | n e w | t h i s | s u p e r | s t a t i c | p u b l i c | p r i v a t e | p r o t e c t e d | d e f a u l t | c a s e | s w i t c h | b r e a k | c o n t i n u e | d o | i n | o f | t y p e o f | i n s t a n c e o f | v o i d | n u l l | u n d e f i n e d | a s ) \b / g,
33- '<span class="code-keyword">$1</span>'
49+ ( match , p1 , offset , string ) => {
50+ // Check if this match is inside a marker (look for unclosed [[ before this position)
51+ const before = string . substring ( 0 , offset )
52+ const openMarkers = ( before . match ( / \[ \[ / g) || [ ] ) . length
53+ const closeMarkers = ( before . match ( / : ] \] / g) || [ ] ) . length
54+ if ( openMarkers > closeMarkers ) return match
55+ return `${ MARKERS . KEYWORD } ${ match } ${ MARKERS . END } `
56+ }
3457 )
3558
36- // Numbers
59+ // Numbers - but not inside already-marked regions
3760 highlighted = highlighted . replace (
3861 / \b ( \d + \. ? \d * ) \b / g,
39- '<span class="code-number">$1</span>'
62+ ( match , p1 , offset , string ) => {
63+ const before = string . substring ( 0 , offset )
64+ const openMarkers = ( before . match ( / \[ \[ / g) || [ ] ) . length
65+ const closeMarkers = ( before . match ( / : ] \] / g) || [ ] ) . length
66+ if ( openMarkers > closeMarkers ) return match
67+ return `${ MARKERS . NUMBER } ${ match } ${ MARKERS . END } `
68+ }
4069 )
4170
4271 // Boolean and special values
4372 highlighted = highlighted . replace (
4473 / \b ( t r u e | f a l s e ) \b / g,
45- '<span class="code-literal">$1</span>'
74+ ( match , p1 , offset , string ) => {
75+ const before = string . substring ( 0 , offset )
76+ const openMarkers = ( before . match ( / \[ \[ / g) || [ ] ) . length
77+ const closeMarkers = ( before . match ( / : ] \] / g) || [ ] ) . length
78+ if ( openMarkers > closeMarkers ) return match
79+ return `${ MARKERS . LITERAL } ${ match } ${ MARKERS . END } `
80+ }
4681 )
4782
4883 // Function names (before parenthesis)
4984 highlighted = highlighted . replace (
5085 / \b ( [ a - z A - Z _ $ ] [ a - z A - Z 0 - 9 _ $ ] * ) \s * (? = \( ) / g,
51- '<span class="code-function">$1</span>'
86+ ( match , p1 , offset , string ) => {
87+ const before = string . substring ( 0 , offset )
88+ const openMarkers = ( before . match ( / \[ \[ / g) || [ ] ) . length
89+ const closeMarkers = ( before . match ( / : ] \] / g) || [ ] ) . length
90+ if ( openMarkers > closeMarkers ) return match
91+ return `${ MARKERS . FUNCTION } ${ p1 } ${ MARKERS . END } `
92+ }
5293 )
5394
5495 // Types/Classes (PascalCase)
5596 highlighted = highlighted . replace (
5697 / \b ( [ A - Z ] [ a - z A - Z 0 - 9 _ ] * ) \b / g,
57- '<span class="code-type">$1</span>'
98+ ( match , p1 , offset , string ) => {
99+ const before = string . substring ( 0 , offset )
100+ const openMarkers = ( before . match ( / \[ \[ / g) || [ ] ) . length
101+ const closeMarkers = ( before . match ( / : ] \] / g) || [ ] ) . length
102+ if ( openMarkers > closeMarkers ) return match
103+ return `${ MARKERS . TYPE } ${ match } ${ MARKERS . END } `
104+ }
58105 )
59106
107+ // Convert markers to HTML spans
108+ highlighted = highlighted
109+ . replace ( / \[ \[ C : / g, '<span class="code-comment">' )
110+ . replace ( / \[ \[ S : / g, '<span class="code-string">' )
111+ . replace ( / \[ \[ K : / g, '<span class="code-keyword">' )
112+ . replace ( / \[ \[ N : / g, '<span class="code-number">' )
113+ . replace ( / \[ \[ L : / g, '<span class="code-literal">' )
114+ . replace ( / \[ \[ F : / g, '<span class="code-function">' )
115+ . replace ( / \[ \[ T : / g, '<span class="code-type">' )
116+ . replace ( / : ] \] / g, '</span>' )
117+
60118 return highlighted
61119}
62120
0 commit comments