/** * Unified language mapping for all editor implementations. * Maps file extensions to language IDs for Monaco, CodeMirror, and lowlight. */ type LanguageIds = { monaco: string; codemirror: string; lowlight: string; }; const LANGUAGE_MAP: Record = { // JavaScript/TypeScript '.js': { monaco: 'javascript', codemirror: 'javascript', lowlight: 'javascript' }, '.mjs': { monaco: 'javascript', codemirror: 'javascript', lowlight: 'javascript' }, '.cjs': { monaco: 'javascript', codemirror: 'javascript', lowlight: 'javascript' }, '.jsx': { monaco: 'javascript', codemirror: 'jsx', lowlight: 'javascript' }, '.ts': { monaco: 'typescript', codemirror: 'typescript', lowlight: 'typescript' }, '.mts': { monaco: 'typescript', codemirror: 'typescript', lowlight: 'typescript' }, '.cts': { monaco: 'typescript', codemirror: 'typescript', lowlight: 'typescript' }, '.tsx': { monaco: 'typescript', codemirror: 'tsx', lowlight: 'typescript' }, // Python '.py': { monaco: 'python', codemirror: 'python', lowlight: 'python' }, '.pyw': { monaco: 'python', codemirror: 'python', lowlight: 'python' }, '.pyi': { monaco: 'python', codemirror: 'python', lowlight: 'python' }, // Go '.go': { monaco: 'go', codemirror: 'go', lowlight: 'go' }, // Rust '.rs': { monaco: 'rust', codemirror: 'rust', lowlight: 'rust' }, // Ruby '.rb': { monaco: 'ruby', codemirror: 'ruby', lowlight: 'ruby' }, '.erb': { monaco: 'ruby', codemirror: 'ruby', lowlight: 'erb' }, // Java/JVM '.java': { monaco: 'java', codemirror: 'java', lowlight: 'java' }, '.kt': { monaco: 'kotlin', codemirror: 'kotlin', lowlight: 'kotlin' }, '.kts': { monaco: 'kotlin', codemirror: 'kotlin', lowlight: 'kotlin' }, '.scala': { monaco: 'scala', codemirror: 'scala', lowlight: 'scala' }, '.groovy': { monaco: 'groovy', codemirror: 'groovy', lowlight: 'groovy' }, // Swift '.swift': { monaco: 'swift', codemirror: 'swift', lowlight: 'swift' }, // C/C++ '.c': { monaco: 'c', codemirror: 'cpp', lowlight: 'c' }, '.h': { monaco: 'c', codemirror: 'cpp', lowlight: 'c' }, '.cpp': { monaco: 'cpp', codemirror: 'cpp', lowlight: 'cpp' }, '.cc': { monaco: 'cpp', codemirror: 'cpp', lowlight: 'cpp' }, '.cxx': { monaco: 'cpp', codemirror: 'cpp', lowlight: 'cpp' }, '.hpp': { monaco: 'cpp', codemirror: 'cpp', lowlight: 'cpp' }, '.hxx': { monaco: 'cpp', codemirror: 'cpp', lowlight: 'cpp' }, // C# '.cs': { monaco: 'csharp', codemirror: 'csharp', lowlight: 'csharp' }, // PHP '.php': { monaco: 'php', codemirror: 'php', lowlight: 'php' }, // Web '.html': { monaco: 'html', codemirror: 'html', lowlight: 'xml' }, '.htm': { monaco: 'html', codemirror: 'html', lowlight: 'xml' }, '.css': { monaco: 'css', codemirror: 'css', lowlight: 'css' }, '.scss': { monaco: 'scss', codemirror: 'css', lowlight: 'scss' }, '.sass': { monaco: 'scss', codemirror: 'css', lowlight: 'scss' }, '.less': { monaco: 'less', codemirror: 'css', lowlight: 'less' }, // Data formats '.json': { monaco: 'json', codemirror: 'json', lowlight: 'json' }, '.jsonc': { monaco: 'json', codemirror: 'json', lowlight: 'json' }, '.yaml': { monaco: 'yaml', codemirror: 'yaml', lowlight: 'yaml' }, '.yml': { monaco: 'yaml', codemirror: 'yaml', lowlight: 'yaml' }, '.xml': { monaco: 'xml', codemirror: 'html', lowlight: 'xml' }, '.toml': { monaco: 'ini', codemirror: 'toml', lowlight: 'ini' }, '.ini': { monaco: 'ini', codemirror: 'ini', lowlight: 'ini' }, '.env': { monaco: 'ini', codemirror: 'properties', lowlight: 'ini' }, // Markup '.md': { monaco: 'markdown', codemirror: 'markdown', lowlight: 'markdown' }, '.mdx': { monaco: 'markdown', codemirror: 'markdown', lowlight: 'markdown' }, // Shell '.sh': { monaco: 'shell', codemirror: 'bash', lowlight: 'bash' }, '.bash': { monaco: 'shell', codemirror: 'bash', lowlight: 'bash' }, '.zsh': { monaco: 'shell', codemirror: 'bash', lowlight: 'bash' }, '.fish': { monaco: 'shell', codemirror: 'fish', lowlight: 'bash' }, // SQL '.sql': { monaco: 'sql', codemirror: 'sql', lowlight: 'sql' }, // Docker '.dockerfile': { monaco: 'dockerfile', codemirror: 'dockerfile', lowlight: 'dockerfile' }, // GraphQL '.graphql': { monaco: 'graphql', codemirror: 'graphql', lowlight: 'graphql' }, '.gql': { monaco: 'graphql', codemirror: 'graphql', lowlight: 'graphql' }, // Vue/Svelte '.vue': { monaco: 'html', codemirror: 'vue', lowlight: 'xml' }, '.svelte': { monaco: 'html', codemirror: 'svelte', lowlight: 'xml' }, }; /** * Language name aliases (used when a language string is provided instead of file ext). * Maps common language names to Monaco language IDs. */ const LANGUAGE_ALIASES: Record = { javascript: 'javascript', js: 'javascript', jsx: 'javascript', typescript: 'typescript', ts: 'typescript', tsx: 'typescript', python: 'python', py: 'python', go: 'go', golang: 'go', rust: 'rust', rs: 'rust', java: 'java', kotlin: 'kotlin', scala: 'scala', swift: 'swift', c: 'c', cpp: 'cpp', 'c++': 'cpp', csharp: 'csharp', 'c#': 'csharp', php: 'php', ruby: 'ruby', rb: 'ruby', html: 'html', css: 'css', scss: 'scss', less: 'less', json: 'json', yaml: 'yaml', yml: 'yaml', xml: 'xml', markdown: 'markdown', md: 'markdown', bash: 'shell', shell: 'shell', sh: 'shell', zsh: 'shell', sql: 'sql', dockerfile: 'dockerfile', graphql: 'graphql', plaintext: 'plaintext', text: 'plaintext', }; function getExtension(filePath: string): string { const lastSlash = filePath.lastIndexOf('/'); const name = lastSlash >= 0 ? filePath.slice(lastSlash + 1) : filePath; // Handle Dockerfile (no extension) if (name.toLowerCase() === 'dockerfile') return '.dockerfile'; const dot = name.lastIndexOf('.'); if (dot === -1) return ''; return name.slice(dot).toLowerCase(); } export function getMonacoLanguage(filePath: string): string { const ext = getExtension(filePath); return LANGUAGE_MAP[ext]?.monaco ?? 'plaintext'; } export function getMonacoLanguageFromName(language: string): string { return LANGUAGE_ALIASES[language.toLowerCase()] ?? 'plaintext'; } export function getLowlightLanguage(filePath: string): string { const ext = getExtension(filePath); return LANGUAGE_MAP[ext]?.lowlight ?? 'plaintext'; } export function getLowlightLanguageFromName(language: string): string { // For lowlight, the language name is typically the same const lower = language.toLowerCase(); // Try to find via alias → then look it up in the map values const monacoLang = LANGUAGE_ALIASES[lower]; if (!monacoLang) return lower; // Find a matching entry in the map for (const ids of Object.values(LANGUAGE_MAP)) { if (ids.monaco === monacoLang) return ids.lowlight; } return lower; }