mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
* fix(editor): enable Lean syntax highlighting * fix(editor): enable Lean syntax highlighting Adapt #31930 onto current main and retain its bundled Lean grammar repair. Register the lazy TextMate provider and cover detection, proof syntax, nested comments, and bounded tokenization. * test(editor): verify Lean highlighting in the full app --------- Co-authored-by: Neil <neil@stably.ai>
175 lines
4.9 KiB
TypeScript
175 lines
4.9 KiB
TypeScript
import { detectMonacoFilenameLanguage } from './monaco-filename-language'
|
|
|
|
function extname(filePath: string): string {
|
|
const lastDot = filePath.lastIndexOf('.')
|
|
const lastSep = Math.max(filePath.lastIndexOf('/'), filePath.lastIndexOf('\\'))
|
|
if (lastDot <= lastSep) {
|
|
return ''
|
|
}
|
|
return filePath.slice(lastDot)
|
|
}
|
|
|
|
const EXT_TO_LANGUAGE: Record<string, string> = {
|
|
// Why: Monaco's built-in language registry maps .tsx/.cts/.mts onto the
|
|
// 'typescript' language id and .jsx/.mjs/.cjs onto 'javascript' — there is
|
|
// no separate 'typescriptreact'/'javascriptreact' id. Returning the base id
|
|
// is what gives .tsx/.jsx files syntax highlighting in the editor.
|
|
'.ts': 'typescript',
|
|
'.tsx': 'typescript',
|
|
'.cts': 'typescript',
|
|
'.mts': 'typescript',
|
|
'.js': 'javascript',
|
|
'.jsx': 'javascript',
|
|
'.mjs': 'javascript',
|
|
'.cjs': 'javascript',
|
|
'.json': 'json',
|
|
'.jsonc': 'json',
|
|
// Why: JSONL is one JSON value per line; a dedicated 'jsonl' language gives
|
|
// JSON-style color without attaching JSON whole-document diagnostics that
|
|
// would flag every record after line one as trailing content.
|
|
'.jsonl': 'jsonl',
|
|
'.ipynb': 'notebook',
|
|
'.md': 'markdown',
|
|
'.mdx': 'markdown',
|
|
// Why: Quarto/R Markdown are Markdown plus a YAML header and executable
|
|
// cells; the 'markdown' grammar leaves ```{r}/```{python} cells uncolored.
|
|
'.qmd': 'quarto',
|
|
'.rmd': 'quarto',
|
|
'.rmarkdown': 'quarto',
|
|
'.mmd': 'mermaid',
|
|
'.mermaid': 'mermaid',
|
|
'.css': 'css',
|
|
'.scss': 'scss',
|
|
'.less': 'less',
|
|
'.html': 'html',
|
|
'.htm': 'html',
|
|
// Why: stopgap until a real JSP grammar — 'html' colors the markup; <% %> and ${} stay plain.
|
|
'.jsp': 'html',
|
|
'.jspf': 'html',
|
|
// Why: Monaco declares Liquid as both '.liquid' and '.html.liquid'; the final-extension
|
|
// lookup below covers the compound form, so the single entry is enough.
|
|
'.liquid': 'liquid',
|
|
'.twig': 'twig',
|
|
'.xml': 'xml',
|
|
'.svg': 'xml',
|
|
'.py': 'python',
|
|
'.rs': 'rust',
|
|
'.go': 'go',
|
|
'.java': 'java',
|
|
'.kt': 'kotlin',
|
|
'.kts': 'kotlin',
|
|
'.c': 'c',
|
|
'.h': 'c',
|
|
'.cpp': 'cpp',
|
|
'.cc': 'cpp',
|
|
'.cxx': 'cpp',
|
|
'.hpp': 'cpp',
|
|
'.cs': 'csharp',
|
|
// Why: Monaco's apex grammar claims only '.cls'; triggers and anonymous-Apex scripts share it.
|
|
'.cls': 'apex',
|
|
'.trigger': 'apex',
|
|
'.apex': 'apex',
|
|
'.rb': 'ruby',
|
|
'.php': 'php',
|
|
'.swift': 'swift',
|
|
'.sh': 'shell',
|
|
'.bash': 'shell',
|
|
'.zsh': 'shell',
|
|
'.fish': 'shell',
|
|
'.bat': 'bat',
|
|
'.cmd': 'bat',
|
|
'.ps1': 'powershell',
|
|
'.yaml': 'yaml',
|
|
'.yml': 'yaml',
|
|
'.toml': 'ini',
|
|
'.ini': 'ini',
|
|
'.cfg': 'ini',
|
|
'.conf': 'ini',
|
|
'.sql': 'sql',
|
|
'.graphql': 'graphql',
|
|
'.gql': 'graphql',
|
|
'.dockerfile': 'dockerfile',
|
|
'.proto': 'proto',
|
|
'.lua': 'lua',
|
|
'.r': 'r',
|
|
'.scala': 'scala',
|
|
'.dart': 'dart',
|
|
'.ex': 'elixir',
|
|
'.exs': 'elixir',
|
|
'.erl': 'erlang',
|
|
'.hrl': 'erlang',
|
|
'.hs': 'haskell',
|
|
'.clj': 'clojure',
|
|
// Why: Monaco registers Solidity under the id 'sol'; 'solidity' is only an alias.
|
|
'.sol': 'sol',
|
|
'.vue': 'vue',
|
|
'.svelte': 'svelte',
|
|
'.astro': 'astro',
|
|
'.sv': 'systemverilog',
|
|
'.svh': 'systemverilog',
|
|
'.v': 'verilog',
|
|
'.vh': 'verilog',
|
|
'.nim': 'nim',
|
|
'.nims': 'nim',
|
|
'.nimble': 'nim',
|
|
'.lean': 'lean',
|
|
'.typ': 'typst',
|
|
'.tf': 'hcl',
|
|
'.hcl': 'hcl',
|
|
'.abap': 'abap',
|
|
'.prisma': 'graphql',
|
|
'.csv': 'csv',
|
|
'.tsv': 'tsv'
|
|
}
|
|
|
|
const FILENAME_TO_LANGUAGE: Record<string, string> = {
|
|
Dockerfile: 'dockerfile',
|
|
Makefile: 'makefile',
|
|
'CMakeLists.txt': 'cmake',
|
|
'.gitignore': 'ini',
|
|
'.gitattributes': 'ini',
|
|
'.editorconfig': 'ini',
|
|
'.env': 'ini',
|
|
'.env.local': 'ini',
|
|
'.env.development': 'ini',
|
|
'.env.production': 'ini',
|
|
'.bashrc': 'shell',
|
|
'.bash_profile': 'shell',
|
|
'.bash_login': 'shell',
|
|
'.bash_logout': 'shell',
|
|
'.profile': 'shell',
|
|
'.zshrc': 'shell',
|
|
'.zshenv': 'shell',
|
|
'.zprofile': 'shell',
|
|
'.zlogin': 'shell',
|
|
'.zlogout': 'shell'
|
|
}
|
|
|
|
// Exact match wins; lowercase map covers case-insensitive filesystems.
|
|
const FILENAME_LOWER_TO_LANGUAGE: Record<string, string> = Object.fromEntries(
|
|
Object.entries(FILENAME_TO_LANGUAGE).map(([name, language]) => [name.toLowerCase(), language])
|
|
)
|
|
|
|
export function detectLanguage(filePath: string): string {
|
|
// Check exact filename first
|
|
const parts = filePath.split(/[\\/]/)
|
|
const filename = parts.at(-1)!
|
|
if (Object.hasOwn(FILENAME_TO_LANGUAGE, filename)) {
|
|
return FILENAME_TO_LANGUAGE[filename]
|
|
}
|
|
const lowerFilename = filename.toLowerCase()
|
|
if (Object.hasOwn(FILENAME_LOWER_TO_LANGUAGE, lowerFilename)) {
|
|
return FILENAME_LOWER_TO_LANGUAGE[lowerFilename]
|
|
}
|
|
|
|
// Check extension
|
|
const ext = extname(filename).toLowerCase()
|
|
const lowerName = filename.toLowerCase()
|
|
// Scoped dotenv names fall back to INI only when no specific extension matches.
|
|
return (
|
|
EXT_TO_LANGUAGE[ext] ??
|
|
detectMonacoFilenameLanguage(filename) ??
|
|
(lowerName === '.env' || lowerName.startsWith('.env.') ? 'ini' : 'plaintext')
|
|
)
|
|
}
|