Files
ccstatusline/src/utils/powerline.ts
T

336 lines
12 KiB
TypeScript

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<PowerlineFontStatus> {
// 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`;
}
}