/**
* Syntax Highlighting Utilities
*
* Provides language detection and Prism.js integration
*/
/**
* Map file extensions to Prism.js language identifiers
*/
const EXTENSION_MAP = {
// JavaScript/TypeScript
js: 'javascript',
mjs: 'javascript',
cjs: 'javascript',
jsx: 'jsx',
ts: 'typescript',
tsx: 'tsx',
// Web
html: 'html',
htm: 'html',
xml: 'xml',
svg: 'svg',
css: 'css',
scss: 'scss',
sass: 'sass',
less: 'less',
// Data formats
json: 'json',
yaml: 'yaml',
yml: 'yaml',
toml: 'toml',
ini: 'ini',
// Shell/Scripts
sh: 'bash',
bash: 'bash',
zsh: 'bash',
fish: 'bash',
ps1: 'powershell',
bat: 'batch',
cmd: 'batch',
// Python
py: 'python',
pyw: 'python',
pyx: 'python',
// Ruby
rb: 'ruby',
erb: 'erb',
rake: 'ruby',
gemspec: 'ruby',
// Go
go: 'go',
mod: 'go-module',
// Rust
rs: 'rust',
// C/C++
c: 'c',
h: 'c',
cpp: 'cpp',
cc: 'cpp',
cxx: 'cpp',
hpp: 'cpp',
hxx: 'cpp',
// Java/Kotlin
java: 'java',
kt: 'kotlin',
kts: 'kotlin',
gradle: 'groovy',
// C#/F#
cs: 'csharp',
fs: 'fsharp',
// PHP
php: 'php',
// Swift/Objective-C
swift: 'swift',
m: 'objectivec',
mm: 'objectivec',
// SQL
sql: 'sql',
// Markdown/Docs
md: 'markdown',
markdown: 'markdown',
rst: 'rest',
tex: 'latex',
// Config files
dockerfile: 'docker',
makefile: 'makefile',
cmake: 'cmake',
nginx: 'nginx',
apache: 'apacheconf',
// Other
lua: 'lua',
perl: 'perl',
pl: 'perl',
r: 'r',
scala: 'scala',
clj: 'clojure',
cljs: 'clojure',
ex: 'elixir',
exs: 'elixir',
erl: 'erlang',
hrl: 'erlang',
hs: 'haskell',
ml: 'ocaml',
vim: 'vim',
diff: 'diff',
patch: 'diff',
gitignore: 'gitignore',
env: 'dotenv',
proto: 'protobuf',
graphql: 'graphql',
gql: 'graphql',
wasm: 'wasm',
asm: 'nasm',
s: 'nasm',
zig: 'zig',
v: 'v',
nim: 'nim',
dart: 'dart',
sol: 'solidity'
}
/**
* Special filename mappings (no extension)
*/
const FILENAME_MAP = {
dockerfile: 'docker',
makefile: 'makefile',
cmakelists: 'cmake',
gemfile: 'ruby',
rakefile: 'ruby',
vagrantfile: 'ruby',
brewfile: 'ruby',
guardfile: 'ruby',
podfile: 'ruby',
fastfile: 'ruby',
appfile: 'ruby',
dangerfile: 'ruby',
berksfile: 'ruby',
thorfile: 'ruby',
capfile: 'ruby',
puppetfile: 'ruby',
appraisals: 'ruby',
procfile: 'yaml',
'.gitignore': 'gitignore',
'.gitattributes': 'gitignore',
'.gitmodules': 'gitignore',
'.editorconfig': 'ini',
'.prettierrc': 'json',
'.eslintrc': 'json',
'.babelrc': 'json',
'tsconfig.json': 'json',
'package.json': 'json',
'package-lock.json': 'json',
'composer.json': 'json',
'cargo.toml': 'toml',
'go.mod': 'go-module',
'go.sum': 'go-module'
}
/**
* Detect language from file path
* @param {string} filePath - Full file path or filename
* @returns {string} Prism.js language identifier or 'plaintext'
*/
export function detectLanguage(filePath) {
if (!filePath) return 'plaintext'
// Get filename (last part of path)
const parts = filePath.split('/')
const filename = parts[parts.length - 1].toLowerCase()
// Check special filenames first
if (FILENAME_MAP[filename]) {
return FILENAME_MAP[filename]
}
// Get extension
const extParts = filename.split('.')
if (extParts.length > 1) {
const ext = extParts[extParts.length - 1].toLowerCase()
if (EXTENSION_MAP[ext]) {
return EXTENSION_MAP[ext]
}
}
return 'plaintext'
}
/**
* Get Prism.js CDN URL for a specific language
* @param {string} language - Prism language identifier
* @returns {string} CDN URL for the language component
*/
export function getPrismLanguageUrl(language, pathPrefix = '') {
if (language === 'plaintext') return null
return `${pathPrefix}/vendor/prism-components/prism-${language}.min.js`
}
/**
* Get all required Prism.js CDN URLs (core + theme + language)
* @param {string} language - Prism language identifier
* @returns {object} { css: string[], js: string[] }
*/
export function getPrismAssets(language, pathPrefix = '') {
const v = pathPrefix + '/vendor'
const assets = {
css: [
v + '/prism-tomorrow.min.css',
v + '/prism-line-numbers.min.css'
],
js: [
v + '/prism.min.js',
v + '/prism-line-numbers.min.js',
v + '/prism-autoloader.min.js'
]
}
// Add language-specific component if not plaintext
const langUrl = getPrismLanguageUrl(language)
if (langUrl) {
assets.js.push(langUrl)
}
return assets
}
/**
* Check if content is likely binary
* @param {string} content - File content
* @returns {boolean}
*/
export function isBinaryContent(content) {
if (!content) return false
// Check for null bytes (common in binary files)
if (content.includes('\x00')) return true
// Check for high ratio of non-printable characters
let nonPrintable = 0
const sample = content.slice(0, 8000) // Check first 8KB
for (let i = 0; i < sample.length; i++) {
const code = sample.charCodeAt(i)
// Count characters outside printable ASCII range (excluding common whitespace)
if (code < 32 && code !== 9 && code !== 10 && code !== 13) {
nonPrintable++
}
}
// If more than 10% non-printable, likely binary
return (nonPrintable / sample.length) > 0.1
}
/**
* Get file icon based on language/type
* @param {string} language - Prism language identifier
* @param {string} fileType - 'file', 'directory', 'submodule'
* @returns {string} Emoji icon
*/
export function getFileIcon(language, fileType = 'file') {
if (fileType === 'directory') return '📁'
if (fileType === 'submodule') return '📦'
// Language-specific icons
const iconMap = {
javascript: '📜',
typescript: '📘',
python: '🐍',
ruby: '💎',
go: '🐹',
rust: '🦀',
java: '☕',
kotlin: '🎯',
swift: '🍎',
c: '⚙️',
cpp: '⚙️',
csharp: '🎮',
php: '🐘',
html: '🌐',
css: '🎨',
json: '📋',
yaml: '📋',
markdown: '📝',
docker: '🐳',
bash: '💻',
sql: '🗃️',
graphql: '◼️'
}
return iconMap[language] || '📄'
}
export default {
detectLanguage,
getPrismLanguageUrl,
getPrismAssets,
isBinaryContent,
getFileIcon
}