import { execSync } from 'child_process'; import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; import type { PowerlineFontStatus } from '../types/PowerlineFontStatus'; // Re-export for backward compatibility export type { PowerlineFontStatus }; // Track if fonts were installed during this session (for DEBUG_FONT_INSTALL) let fontsInstalledThisSession = false; /** * Check if Powerline fonts are installed by testing if Powerline symbols render correctly */ export function checkPowerlineFonts(): PowerlineFontStatus { // Debug mode: pretend fonts aren't installed (unless we installed them this session) if (process.env.DEBUG_FONT_INSTALL === '1' && !fontsInstalledThisSession) { return { installed: false, checkedSymbol: '\uE0B0' }; } try { // Test if we can display the common Powerline separator symbols // These are the key characters that require Powerline fonts const testSymbols = { rightArrow: '\uE0B0', // rightThinArrow: '\uE0B1', // leftArrow: '\uE0B2', // leftThinArrow: '\uE0B3' // }; // Try to detect if fonts are available // This is a heuristic check - we assume if the system has common Powerline font files, they're available const platform = os.platform(); let fontPaths: string[] = []; if (platform === 'darwin') { fontPaths = [ path.join(os.homedir(), 'Library', 'Fonts'), '/Library/Fonts', '/System/Library/Fonts' ]; } else if (platform === 'linux') { fontPaths = [ path.join(os.homedir(), '.local', 'share', 'fonts'), path.join(os.homedir(), '.fonts'), '/usr/share/fonts', '/usr/local/share/fonts' ]; } else if (platform === 'win32') { fontPaths = [ path.join(os.homedir(), 'AppData', 'Local', 'Microsoft', 'Windows', 'Fonts'), 'C:\\Windows\\Fonts' ]; } // Common Powerline font patterns to look for const powerlineFontPatterns = [ /powerline/i, /nerd font/i, /for powerline/i, /meslo.*lg/i, // Meslo LG fonts often include Powerline /source.*code.*pro.*powerline/i, /dejavu.*powerline/i, /ubuntu.*mono.*powerline/i, /cascadia.*code.*pl/i, // Cascadia Code PL /fira.*code.*nerd/i ]; // Check if any Powerline fonts exist for (const fontPath of fontPaths) { if (fs.existsSync(fontPath)) { try { const files = fs.readdirSync(fontPath); for (const file of files) { for (const pattern of powerlineFontPatterns) { if (pattern.test(file)) { return { installed: true, checkedSymbol: testSymbols.rightArrow }; } } } } catch { // Ignore read errors for specific directories } } } return { installed: false, checkedSymbol: testSymbols.rightArrow }; } catch { return { installed: false }; } } /** * Check if Powerline fonts are installed (async version with fc-list check) */ export async function checkPowerlineFontsAsync(): Promise { // Ensure this is always async await Promise.resolve(); // Debug mode: pretend fonts aren't installed (unless we installed them this session) if (process.env.DEBUG_FONT_INSTALL === '1' && !fontsInstalledThisSession) { return { installed: false, checkedSymbol: '\uE0B0' }; } try { // First do the quick synchronous check const quickCheck = checkPowerlineFonts(); if (quickCheck.installed) { return quickCheck; } // Additional check: See if fontconfig knows about Powerline fonts (Linux/macOS) const platform = os.platform(); if (platform === 'linux' || platform === 'darwin') { try { const { exec } = await import('child_process'); const { promisify } = await import('util'); const execAsync = promisify(exec); const { stdout } = await execAsync('fc-list 2>/dev/null | grep -i powerline', { encoding: 'utf8' }); if (stdout.trim()) { return { installed: true, checkedSymbol: '\uE0B0' }; } } catch { // fc-list not available or no Powerline fonts found } } return quickCheck; } catch { return { installed: false }; } } /** * Install Powerline fonts on the system */ export async function installPowerlineFonts(): Promise<{ success: boolean; message: string }> { // Ensure this is always async await Promise.resolve(); try { const platform = os.platform(); let fontDir: string; // Determine font directory based on platform if (platform === 'darwin') { fontDir = path.join(os.homedir(), 'Library', 'Fonts'); } else if (platform === 'linux') { fontDir = path.join(os.homedir(), '.local', 'share', 'fonts'); } else if (platform === 'win32') { fontDir = path.join( os.homedir(), 'AppData', 'Local', 'Microsoft', 'Windows', 'Fonts' ); } else { return { success: false, message: 'Unsupported platform for font installation' }; } // Create font directory if it doesn't exist if (!fs.existsSync(fontDir)) { fs.mkdirSync(fontDir, { recursive: true }); } // Create temporary directory for font download const tempDir = path.join(os.tmpdir(), `ccstatusline-powerline-fonts-${Date.now()}`); try { // Clean up if temp directory exists if (fs.existsSync(tempDir)) { fs.rmSync(tempDir, { recursive: true, force: true }); } // Clone Powerline fonts repository execSync( `git clone --depth=1 https://github.com/powerline/fonts.git "${tempDir}"`, { stdio: 'pipe', encoding: 'utf8' } ); // Run the install script based on platform if (platform === 'darwin' || platform === 'linux') { const installScript = path.join(tempDir, 'install.sh'); if (fs.existsSync(installScript)) { // Make script executable fs.chmodSync(installScript, 0o755); // Run install script execSync(`cd "${tempDir}" && ./install.sh`, { stdio: 'pipe', encoding: 'utf8', shell: '/bin/bash' }); // On Linux, update font cache if (platform === 'linux') { try { execSync('fc-cache -f -v', { stdio: 'pipe', encoding: 'utf8' }); } catch { // fc-cache might not be available } } // Mark as installed for DEBUG_FONT_INSTALL mode if (process.env.DEBUG_FONT_INSTALL === '1') { fontsInstalledThisSession = true; } return { success: true, message: 'Powerline fonts installed successfully! Please restart your terminal and select a Powerline font (e.g., "Source Code Pro for Powerline", "Meslo LG S for Powerline", etc.)' }; } else { throw new Error('Install script not found in Powerline fonts repository'); } } else { // For Windows, we need to copy font files manually const fontFiles: string[] = []; // Find all font files in the repository function findFontFiles(dir: string): void { const files = fs.readdirSync(dir); for (const file of files) { const filePath = path.join(dir, file); const stat = fs.statSync(filePath); if (stat.isDirectory() && !file.startsWith('.')) { findFontFiles(filePath); } else if (file.endsWith('.ttf') || file.endsWith('.otf')) { // Only include fonts that have "Powerline" in their path if (filePath.toLowerCase().includes('powerline')) { fontFiles.push(filePath); } } } } findFontFiles(tempDir); // Copy font files to Windows font directory let installedCount = 0; for (const fontFile of fontFiles) { const fileName = path.basename(fontFile); const destPath = path.join(fontDir, fileName); try { fs.copyFileSync(fontFile, destPath); installedCount++; } catch { // Ignore individual file errors } } if (installedCount > 0) { return { success: true, message: `Installed ${installedCount} Powerline fonts. Please restart your terminal and select a Powerline font from your terminal settings.` }; } else { throw new Error('No fonts were installed'); } } return { success: false, message: 'Platform-specific installation not implemented' }; } finally { // Clean up temporary directory if (fs.existsSync(tempDir)) { try { fs.rmSync(tempDir, { recursive: true, force: true }); } catch { // Ignore cleanup errors } } } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); // Provide helpful error messages if (errorMessage.includes('git')) { return { success: false, message: 'Git is required to install Powerline fonts. Please install Git and try again.' }; } return { success: false, message: `Failed to install Powerline fonts: ${errorMessage}. You can manually install fonts from: https://github.com/powerline/fonts` }; } } /** * Get a user-friendly message about Powerline font status */ export function getPowerlineStatusMessage(status: PowerlineFontStatus): string { if (status.installed) { return `✓ Powerline fonts detected`; } else { return `✗ Powerline fonts not detected - some symbols may not display correctly`; } }