RegexHighlighter.svelte
1 <script lang="ts"> 2 interface HighlightPattern { 3 pattern: string; 4 color: string; 5 flags?: string; 6 } 7 8 interface Props { 9 patterns: HighlightPattern[]; 10 text: string; 11 } 12 13 let { patterns, text }: Props = $props(); 14 15 interface Segment { 16 text: string; 17 color: string | null; 18 } 19 20 interface Line { 21 segments: Segment[]; 22 } 23 24 function highlightLine(line: string, patterns: HighlightPattern[]): Segment[] { 25 if (!patterns.length || !line) return [{ text: line, color: null }]; 26 27 // Track ranges with their colors - later patterns override earlier ones 28 const ranges: Array<{ start: number; end: number; color: string }> = []; 29 30 for (const { pattern, color, flags } of patterns) { 31 const regex = new RegExp(pattern, flags || "g"); 32 33 let match = regex.exec(line); 34 while (match !== null) { 35 if (match[0].length === 0) { 36 regex.lastIndex++; 37 match = regex.exec(line); 38 continue; 39 } 40 41 const newStart = match.index; 42 const newEnd = match.index + match[0].length; 43 44 for (let i = ranges.length - 1; i >= 0; i--) { 45 const r = ranges[i]; 46 if (r && r.start < newEnd && r.end > newStart) { 47 ranges.splice(i, 1); 48 } 49 } 50 51 ranges.push({ start: newStart, end: newEnd, color }); 52 match = regex.exec(line); 53 } 54 } 55 56 ranges.sort((a, b) => a.start - b.start); 57 58 const segments: Segment[] = []; 59 let lastEnd = 0; 60 61 for (const range of ranges) { 62 if (range.start > lastEnd) { 63 segments.push({ 64 text: line.slice(lastEnd, range.start), 65 color: null, 66 }); 67 } 68 69 segments.push({ 70 text: line.slice(range.start, range.end), 71 color: range.color, 72 }); 73 lastEnd = range.end; 74 } 75 76 if (lastEnd < line.length) { 77 segments.push({ text: line.slice(lastEnd), color: null }); 78 } 79 80 return segments.length ? segments : [{ text: line, color: null }]; 81 } 82 83 function getLines(text: string, patterns: HighlightPattern[]): Line[] { 84 const rawLines = text.split("\n"); 85 return rawLines.map((line) => ({ 86 segments: highlightLine(line, patterns), 87 })); 88 } 89 90 let lines = $derived(getLines(text, patterns)); 91 </script> 92 93 <div class="p-3 border-[0.5px] border-[#e8e0d9] rounded-md bg-[#fffaf5] overflow-x-auto mb-6"> 94 <code class="regex-highlighter before:content-[''] after:content-[''] text-[#443d66]! font-mono! text-sm"> 95 {#each lines as line} 96 <p class="whitespace-nowrap m-0! p-0! leading-relaxed"> 97 {#each line.segments as segment} 98 {#if segment.color} 99 <span style="color: {segment.color}">{segment.text}</span> 100 {:else} 101 {segment.text} 102 {/if} 103 {/each} 104 </p> 105 {/each} 106 </code> 107 </div> 108 109 <style> 110 .regex-highlighter { 111 font-family: inherit; 112 background: transparent; 113 padding: 0; 114 } 115 </style>