feat: statusline support script

This commit is contained in:
musistudio
2025-08-17 00:25:22 +08:00
parent d2969e4332
commit d6b11e1b60
5 changed files with 123 additions and 19 deletions

View File

@@ -10,6 +10,7 @@ export interface StatusLineModuleConfig {
text: string; text: string;
color?: string; color?: string;
background?: string; background?: string;
scriptPath?: string; // 用于script类型的模块指定要执行的Node.js脚本文件路径
} }
export interface StatusLineThemeConfig { export interface StatusLineThemeConfig {
@@ -132,11 +133,58 @@ function getColorCode(colorName: string): string {
// 变量替换函数,支持{{var}}格式的变量替换 // 变量替换函数,支持{{var}}格式的变量替换
function replaceVariables(text: string, variables: Record<string, string>): string { function replaceVariables(text: string, variables: Record<string, string>): string {
return text.replace(/\{\{(\w+)\}\}/g, (match, varName) => { return text.replace(/\{\{(\w+)\}\}/g, (_match, varName) => {
return variables[varName] || match; return variables[varName] || "";
}); });
} }
// 执行脚本并获取输出
async function executeScript(scriptPath: string, variables: Record<string, string>): Promise<string> {
try {
// 检查文件是否存在
await fs.access(scriptPath);
// 使用require动态加载脚本模块
const scriptModule = require(scriptPath);
// 如果导出的是函数,则调用它并传入变量
if (typeof scriptModule === 'function') {
const result = scriptModule(variables);
// 如果返回的是Promise则等待它完成
if (result instanceof Promise) {
return await result;
}
return result;
}
// 如果导出的是default函数则调用它
if (scriptModule.default && typeof scriptModule.default === 'function') {
const result = scriptModule.default(variables);
// 如果返回的是Promise则等待它完成
if (result instanceof Promise) {
return await result;
}
return result;
}
// 如果导出的是字符串,则直接返回
if (typeof scriptModule === 'string') {
return scriptModule;
}
// 如果导出的是default字符串则返回它
if (scriptModule.default && typeof scriptModule.default === 'string') {
return scriptModule.default;
}
// 默认情况下返回空字符串
return "";
} catch (error) {
console.error(`执行脚本 ${scriptPath} 时出错:`, error);
return "";
}
}
// 默认主题配置 - 使用Nerd Fonts图标和美观配色 // 默认主题配置 - 使用Nerd Fonts图标和美观配色
const DEFAULT_THEME: StatusLineThemeConfig = { const DEFAULT_THEME: StatusLineThemeConfig = {
modules: [ modules: [
@@ -490,9 +538,9 @@ export async function parseStatusLineData(input: StatusLineInput): Promise<strin
// 根据风格渲染状态行 // 根据风格渲染状态行
if (isPowerline) { if (isPowerline) {
return renderPowerlineStyle(theme, variables); return await renderPowerlineStyle(theme, variables);
} else { } else {
return renderDefaultStyle(theme, variables); return await renderDefaultStyle(theme, variables);
} }
} catch (error) { } catch (error) {
// 发生错误时返回空字符串 // 发生错误时返回空字符串
@@ -529,10 +577,10 @@ async function getProjectThemeConfigForStyle(style: string): Promise<StatusLineT
} }
// 渲染默认风格的状态行 // 渲染默认风格的状态行
function renderDefaultStyle( async function renderDefaultStyle(
theme: StatusLineThemeConfig, theme: StatusLineThemeConfig,
variables: Record<string, string> variables: Record<string, string>
): string { ): Promise<string> {
const modules = theme.modules || DEFAULT_THEME.modules; const modules = theme.modules || DEFAULT_THEME.modules;
const parts: string[] = []; const parts: string[] = [];
@@ -542,19 +590,30 @@ function renderDefaultStyle(
const color = module.color ? getColorCode(module.color) : ""; const color = module.color ? getColorCode(module.color) : "";
const background = module.background ? getColorCode(module.background) : ""; const background = module.background ? getColorCode(module.background) : "";
const icon = module.icon || ""; const icon = module.icon || "";
const text = replaceVariables(module.text, variables);
// 如果text为空且不是usage类型则跳过该模块 // 如果是script类型执行脚本获取文本
if (!text && module.type !== "usage") { let text = "";
if (module.type === "script" && module.scriptPath) {
text = await executeScript(module.scriptPath, variables);
} else {
text = replaceVariables(module.text, variables);
}
// 构建显示文本
let displayText = "";
if (icon) {
displayText += `${icon} `;
}
displayText += text;
// 如果displayText为空或者只有图标没有实际文本则跳过该模块
if (!displayText || !text) {
continue; continue;
} }
// 构建模块字符串 // 构建模块字符串
let part = `${background}${color}`; let part = `${background}${color}`;
if (icon) { part += `${displayText}${COLORS.reset}`;
part += `${icon} `;
}
part += `${text}${COLORS.reset}`;
parts.push(part); parts.push(part);
} }
@@ -701,10 +760,10 @@ function segment(text: string, textFg: string, bgColor: string, nextBgColor: str
} }
// 渲染Powerline风格的状态行 // 渲染Powerline风格的状态行
function renderPowerlineStyle( async function renderPowerlineStyle(
theme: StatusLineThemeConfig, theme: StatusLineThemeConfig,
variables: Record<string, string> variables: Record<string, string>
): string { ): Promise<string> {
const modules = theme.modules || POWERLINE_THEME.modules; const modules = theme.modules || POWERLINE_THEME.modules;
const segments: string[] = []; const segments: string[] = [];
@@ -714,11 +773,13 @@ function renderPowerlineStyle(
const color = module.color || "white"; const color = module.color || "white";
const backgroundName = module.background || ""; const backgroundName = module.background || "";
const icon = module.icon || ""; const icon = module.icon || "";
const text = replaceVariables(module.text, variables);
// 如果text为空且不是usage类型则跳过该模块 // 如果是script类型执行脚本获取文本
if (!text && module.type !== "usage") { let text = "";
continue; if (module.type === "script" && module.scriptPath) {
text = await executeScript(module.scriptPath, variables);
} else {
text = replaceVariables(module.text, variables);
} }
// 构建显示文本 // 构建显示文本
@@ -728,6 +789,11 @@ function renderPowerlineStyle(
} }
displayText += text; displayText += text;
// 如果displayText为空或者只有图标没有实际文本则跳过该模块
if (!displayText || !text) {
continue;
}
// 获取下一个模块的背景色(用于分隔符) // 获取下一个模块的背景色(用于分隔符)
let nextBackground: string | null = null; let nextBackground: string | null = null;
if (i < modules.length - 1) { if (i < modules.length - 1) {

View File

@@ -50,6 +50,7 @@ const MODULE_TYPES = [
{ label: "gitBranch", value: "gitBranch" }, { label: "gitBranch", value: "gitBranch" },
{ label: "model", value: "model" }, { label: "model", value: "model" },
{ label: "usage", value: "usage" }, { label: "usage", value: "usage" },
{ label: "script", value: "script" },
]; ];
// ANSI颜色代码映射 // ANSI颜色代码映射
@@ -675,6 +676,15 @@ export function StatusLineConfigDialog({
color: "bright_magenta", color: "bright_magenta",
}; };
break; break;
case "script":
newModule = {
type: "script",
icon: "📜",
text: "Script Module",
color: "bright_cyan",
scriptPath: "",
};
break;
default: default:
newModule = { ...DEFAULT_MODULE, type: moduleType }; newModule = { ...DEFAULT_MODULE, type: moduleType };
} }
@@ -920,6 +930,31 @@ export function StatusLineConfigDialog({
</p> </p>
</div> </div>
{/* Script Path 输入框 - 仅在type为script时显示 */}
{selectedModule.type === "script" && (
<div className="space-y-2">
<Label htmlFor="module-script-path">
</Label>
<Input
id="module-script-path"
value={selectedModule.scriptPath || ""}
onChange={(e) =>
handleModuleChange(
selectedModuleIndex,
"scriptPath",
e.target.value
)
}
placeholder="例如: /path/to/your/script.js"
/>
<p className="text-xs text-muted-foreground">
Node.js脚本文件的绝对路径
</p>
</div>
)}
<Button <Button
variant="destructive" variant="destructive"
size="sm" size="sm"

View File

@@ -135,6 +135,7 @@
"gitBranch": "Git Branch", "gitBranch": "Git Branch",
"model": "Model", "model": "Model",
"usage": "Usage", "usage": "Usage",
"script": "Script",
"background_none": "None", "background_none": "None",
"color_black": "Black", "color_black": "Black",
"color_red": "Red", "color_red": "Red",

View File

@@ -135,6 +135,7 @@
"gitBranch": "Git分支", "gitBranch": "Git分支",
"model": "模型", "model": "模型",
"usage": "使用情况", "usage": "使用情况",
"script": "脚本",
"background_none": "无", "background_none": "无",
"color_black": "黑色", "color_black": "黑色",
"color_red": "红色", "color_red": "红色",

View File

@@ -33,6 +33,7 @@ export interface StatusLineModuleConfig {
text: string; text: string;
color?: string; color?: string;
background?: string; background?: string;
scriptPath?: string; // 用于script类型的模块指定要执行的Node.js脚本文件路径
} }
export interface StatusLineThemeConfig { export interface StatusLineThemeConfig {