373 lines · 7.3 KB · TypeScript
RawDownload
import React from 'react'; // =============================================================================// Syntax Highlighting (Basic Token-based)// ============================================================================= // Basic keyword sets for common languagesconst KEYWORDS: Record<string, Set<string>> = {  typescript: new Set([    'import',    'export',    'from',    'const',    'let',    'var',    'function',    'class',    'interface',    'type',    'enum',    'return',    'if',    'else',    'for',    'while',    'do',    'switch',    'case',    'break',    'continue',    'try',    'catch',    'finally',    'throw',    'new',    'this',    'super',    'extends',    'implements',    'async',    'await',    'public',    'private',    'protected',    'static',    'readonly',    'abstract',    'as',    'typeof',    'instanceof',    'in',    'of',    'keyof',    'void',    'never',    'unknown',    'any',    'null',    'undefined',    'true',    'false',    'default',  ]),  javascript: new Set([    'import',    'export',    'from',    'const',    'let',    'var',    'function',    'class',    'return',    'if',    'else',    'for',    'while',    'do',    'switch',    'case',    'break',    'continue',    'try',    'catch',    'finally',    'throw',    'new',    'this',    'super',    'extends',    'async',    'await',    'typeof',    'instanceof',    'in',    'of',    'void',    'null',    'undefined',    'true',    'false',    'default',  ]),  python: new Set([    'import',    'from',    'as',    'def',    'class',    'return',    'if',    'elif',    'else',    'for',    'while',    'break',    'continue',    'try',    'except',    'finally',    'raise',    'with',    'as',    'pass',    'lambda',    'yield',    'global',    'nonlocal',    'assert',    'and',    'or',    'not',    'in',    'is',    'True',    'False',    'None',    'async',    'await',    'self',    'cls',  ]),  rust: new Set([    'fn',    'let',    'mut',    'const',    'static',    'struct',    'enum',    'impl',    'trait',    'pub',    'mod',    'use',    'crate',    'self',    'super',    'where',    'for',    'loop',    'while',    'if',    'else',    'match',    'return',    'break',    'continue',    'move',    'ref',    'as',    'in',    'unsafe',    'async',    'await',    'dyn',    'true',    'false',    'type',    'extern',  ]),  go: new Set([    'package',    'import',    'func',    'var',    'const',    'type',    'struct',    'interface',    'map',    'chan',    'go',    'defer',    'return',    'if',    'else',    'for',    'range',    'switch',    'case',    'default',    'break',    'continue',    'fallthrough',    'select',    'nil',    'true',    'false',  ]),}; // Extend tsx/jsx to use typescript/javascript keywordsKEYWORDS.tsx = KEYWORDS.typescript;KEYWORDS.jsx = KEYWORDS.javascript; /** * Very basic tokenization for syntax highlighting. * This is a simple approach without a full parser. */export function highlightLine(line: string, language: string): React.ReactNode[] {  const keywords = KEYWORDS[language] || new Set();   // If no highlighting support, return plain text as single-element array  if (keywords.size === 0 && !['json', 'css', 'html', 'bash', 'markdown'].includes(language)) {    return [line];  }   const segments: React.ReactNode[] = [];  let currentPos = 0;  const lineLength = line.length;   while (currentPos < lineLength) {    const remaining = line.slice(currentPos);     // Check for string (double quote)    if (remaining.startsWith('"')) {      const endQuote = remaining.indexOf('"', 1);      if (endQuote !== -1) {        const str = remaining.slice(0, endQuote + 1);        segments.push(          React.createElement(            'span',            { key: currentPos, style: { color: 'var(--syntax-string)' } },            str          )        );        currentPos += str.length;        continue;      }    }     // Check for string (single quote)    if (remaining.startsWith("'")) {      const endQuote = remaining.indexOf("'", 1);      if (endQuote !== -1) {        const str = remaining.slice(0, endQuote + 1);        segments.push(          React.createElement(            'span',            { key: currentPos, style: { color: 'var(--syntax-string)' } },            str          )        );        currentPos += str.length;        continue;      }    }     // Check for template literal (backtick)    if (remaining.startsWith('`')) {      const endQuote = remaining.indexOf('`', 1);      if (endQuote !== -1) {        const str = remaining.slice(0, endQuote + 1);        segments.push(          React.createElement(            'span',            { key: currentPos, style: { color: 'var(--syntax-string)' } },            str          )        );        currentPos += str.length;        continue;      }    }     // Check for comment (// style)    if (remaining.startsWith('//')) {      segments.push(        React.createElement(          'span',          { key: currentPos, style: { color: 'var(--syntax-comment)', fontStyle: 'italic' } },          remaining        )      );      break;    }     // Check for comment (# style for Python/Shell)    if ((language === 'python' || language === 'bash') && remaining.startsWith('#')) {      segments.push(        React.createElement(          'span',          { key: currentPos, style: { color: 'var(--syntax-comment)', fontStyle: 'italic' } },          remaining        )      );      break;    }     // Check for numbers    const numberMatch = /^(\d+\.?\d*)/.exec(remaining);    if (numberMatch && (currentPos === 0 || /\W/.test(line[currentPos - 1]))) {      segments.push(        React.createElement(          'span',          { key: currentPos, style: { color: 'var(--syntax-number)' } },          numberMatch[1]        )      );      currentPos += numberMatch[1].length;      continue;    }     // Check for keywords and identifiers    const wordMatch = /^([a-zA-Z_$][a-zA-Z0-9_$]*)/.exec(remaining);    if (wordMatch) {      const word = wordMatch[1];      if (keywords.has(word)) {        segments.push(          React.createElement(            'span',            { key: currentPos, style: { color: 'var(--syntax-keyword)', fontWeight: 500 } },            word          )        );      } else if ((word[0]?.toUpperCase() ?? '') === word[0] && word.length > 1) {        // Likely a type/class name        segments.push(          React.createElement(            'span',            { key: currentPos, style: { color: 'var(--syntax-type)' } },            word          )        );      } else {        segments.push(word);      }      currentPos += word.length;      continue;    }     // Check for operators and punctuation    const opMatch = /^([=<>!+\-*/%&|^~?:;,.{}()[\]])/.exec(remaining);    if (opMatch) {      segments.push(        React.createElement(          'span',          { key: currentPos, style: { color: 'var(--syntax-operator)' } },          opMatch[1]        )      );      currentPos += 1;      continue;    }     // Default: just add the character    segments.push(remaining[0]);    currentPos += 1;  }   return segments;}