Files
kandev/apps/web/lib/editor/language-map.ts
T
2026-02-16 09:55:04 +00:00

195 lines
6.7 KiB
TypeScript

/**
* 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<string, LanguageIds> = {
// 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<string, string> = {
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;
}