openai-cli-unofficial
Version:
A powerful OpenAI CLI Coding Agent built with TypeScript
306 lines • 12.3 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.StreamRenderer = void 0;
const chalk_1 = __importDefault(require("chalk"));
const cli_highlight_1 = require("cli-highlight");
const language_1 = require("../../services/language");
/**
* 流式渲染器,用于处理流式输入并进行美观输出
*/
class StreamRenderer {
constructor() {
this.buffer = '';
this.isInCodeBlock = false;
this.codeBlockLanguage = '';
this.codeBlockContent = '';
this.currentLine = '';
this.codeLineNumber = 1;
// 为 cli-highlight 自定义主题,以提高在深色和浅色终端上的可读性
this.highlightTheme = {
keyword: chalk_1.default.blueBright,
built_in: chalk_1.default.cyanBright,
type: chalk_1.default.cyanBright.dim,
literal: chalk_1.default.blueBright,
number: chalk_1.default.greenBright,
regexp: chalk_1.default.redBright,
string: chalk_1.default.redBright,
subst: chalk_1.default.redBright,
symbol: chalk_1.default.redBright,
class: chalk_1.default.blueBright,
function: chalk_1.default.yellowBright,
title: chalk_1.default.blueBright,
params: chalk_1.default.redBright,
comment: chalk_1.default.green,
doctag: chalk_1.default.green,
meta: chalk_1.default.gray,
'meta-keyword': chalk_1.default.gray,
'meta-string': chalk_1.default.gray,
section: chalk_1.default.blueBright,
tag: chalk_1.default.gray,
name: chalk_1.default.blueBright,
'builtin-name': chalk_1.default.blueBright,
attr: chalk_1.default.cyanBright,
attribute: chalk_1.default.cyanBright,
variable: chalk_1.default.blueBright,
bullet: chalk_1.default.redBright,
code: chalk_1.default.redBright,
emphasis: chalk_1.default.italic,
strong: chalk_1.default.bold,
formula: chalk_1.default.redBright,
link: chalk_1.default.underline,
quote: chalk_1.default.green,
'selector-attr': chalk_1.default.redBright,
'selector-class': chalk_1.default.blueBright,
'selector-id': chalk_1.default.blueBright,
'selector-pseudo': chalk_1.default.blueBright,
'selector-tag': chalk_1.default.blueBright,
template_variable: chalk_1.default.blueBright,
'template-tag': chalk_1.default.blueBright,
addition: chalk_1.default.green,
deletion: chalk_1.default.red,
default: chalk_1.default.white,
};
// 支持的语言列表(常见的编程语言)
this.supportedLanguages = new Set([
'javascript', 'js', 'typescript', 'ts', 'python', 'py', 'java',
'cpp', 'c++', 'c', 'csharp', 'cs', 'php', 'ruby', 'go',
'rust', 'swift', 'kotlin', 'scala', 'sql', 'html', 'css',
'scss', 'sass', 'less', 'xml', 'json', 'yaml', 'yml',
'bash', 'sh', 'shell', 'powershell', 'dockerfile', 'makefile',
'perl', 'lua', 'r', 'matlab', 'objective-c', 'dart', 'elixir',
'erlang', 'haskell', 'clojure', 'groovy', 'actionscript'
]);
}
/**
* 检查语言是否被支持,如果不支持则返回默认配置
*/
checkLanguageSupport(language) {
if (!language || language.trim() === '') {
return { isSupported: false };
}
const normalizedLang = language.toLowerCase().trim();
// 处理一些特殊的语言别名
const languageAliases = {
'vue': 'html', // Vue文件可以用HTML高亮作为回退
'jsx': 'javascript',
'tsx': 'typescript',
'vue.js': 'html',
'vuejs': 'html',
'svelte': 'html',
'angular': 'typescript',
'react': 'javascript'
};
// 检查是否有别名映射
if (languageAliases[normalizedLang]) {
return { isSupported: true, fallbackLanguage: languageAliases[normalizedLang] };
}
// 检查是否直接支持
if (this.supportedLanguages.has(normalizedLang)) {
return { isSupported: true };
}
return { isSupported: false };
}
/**
* 处理流式输入的文本块
*/
processChunk(chunk) {
let output = '';
this.buffer += chunk;
// 按换行符分割处理
const lines = this.buffer.split('\n');
// 保留最后一行(可能不完整)
this.buffer = lines.pop() || '';
// 处理完整的行
for (const line of lines) {
output += this.processLine(line + '\n');
}
return output;
}
/**
* 处理单行文本
*/
processLine(line) {
const cleanLine = line.replace('\n', '');
// 检测代码块开始/结束
if (cleanLine.startsWith('```')) {
if (!this.isInCodeBlock) {
// 代码块开始
this.isInCodeBlock = true;
this.codeBlockLanguage = cleanLine.substring(3).trim();
this.codeBlockContent = '';
this.codeLineNumber = 1;
// 显示代码块头部
const messages = language_1.languageService.getMessages();
const language = this.codeBlockLanguage || messages.main.messages.codeBlock.unknownLanguage;
const terminalWidth = process.stdout.columns || 120;
const languageLabel = ` ${language.toUpperCase()} `;
const remainingWidth = terminalWidth - languageLabel.length;
return chalk_1.default.bgBlackBright.whiteBright.bold(languageLabel) + chalk_1.default.gray('─'.repeat(Math.max(0, remainingWidth))) + '\n';
}
else {
// 代码块结束
this.isInCodeBlock = false;
this.codeBlockContent = '';
this.codeBlockLanguage = '';
// 显示代码块底部
const terminalWidth = process.stdout.columns || 120;
return chalk_1.default.gray('─'.repeat(terminalWidth)) + '\n';
}
}
if (this.isInCodeBlock) {
// 在代码块内,添加行号和背景色
const lineNum = this.codeLineNumber.toString().padStart(3, ' ');
const highlightedCode = this.highlightCodeLine(cleanLine, this.codeBlockLanguage);
this.codeLineNumber++;
// 获取终端宽度,默认120列
const terminalWidth = process.stdout.columns || 120;
const lineNumberWidth = 5; // 行号部分宽度 " 123 "
const availableWidth = terminalWidth - lineNumberWidth;
// 计算实际字符长度(去除ANSI颜色代码)
const stripAnsi = (str) => str.replace(/\x1b\[[0-9;]*m/g, '');
const actualCodeLength = stripAnsi(highlightedCode).length;
// 确保填充到正确宽度
const paddingNeeded = Math.max(0, availableWidth - actualCodeLength);
const paddedCode = highlightedCode + ' '.repeat(paddingNeeded);
// 更改为更通用的颜色方案,以兼容亮色和暗色主题
return chalk_1.default.bgBlackBright.whiteBright.bold(` ${lineNum} `) +
paddedCode + '\n';
}
// 普通文本行的美化处理
return this.formatRegularLine(cleanLine) + '\n';
}
/**
* 代码高亮(完整代码块)
*/
highlightCode(code, language) {
const languageCheck = this.checkLanguageSupport(language);
try {
if (languageCheck.isSupported) {
const targetLanguage = languageCheck.fallbackLanguage || language;
return (0, cli_highlight_1.highlight)(code, { language: targetLanguage, theme: this.highlightTheme });
}
else {
// 不支持的语言使用黄色高亮
return chalk_1.default.yellow(code);
}
}
catch (error) {
// 如果高亮失败,也使用黄色高亮
return chalk_1.default.yellow(code);
}
}
/**
* 单行代码高亮
*/
highlightCodeLine(codeLine, language) {
const languageCheck = this.checkLanguageSupport(language);
try {
if (languageCheck.isSupported) {
const targetLanguage = languageCheck.fallbackLanguage || language;
return (0, cli_highlight_1.highlight)(codeLine, { language: targetLanguage, theme: this.highlightTheme });
}
else {
// 不支持的语言使用黄色高亮
return chalk_1.default.yellow(codeLine);
}
}
catch (error) {
// 如果高亮失败,也使用黄色高亮
return chalk_1.default.yellow(codeLine);
}
}
/**
* 格式化普通文本行
*/
formatRegularLine(line) {
const messages = language_1.languageService.getMessages();
// 处理标题
if (line.startsWith('# ')) {
return chalk_1.default.bold.cyan(line);
}
else if (line.startsWith('## ')) {
return chalk_1.default.bold.blue(line);
}
else if (line.startsWith('### ')) {
return chalk_1.default.bold.magenta(line);
}
else if (line.startsWith('#### ')) {
return chalk_1.default.bold.yellow(line);
}
// 处理列表项
if (line.match(/^[\s]*[-*+]\s/)) {
return chalk_1.default.green(line);
}
// 处理数字列表
if (line.match(/^[\s]*\d+\.\s/)) {
return chalk_1.default.cyan(line);
}
// 处理行内代码
line = line.replace(/`([^`]+)`/g, (match, code) => {
return chalk_1.default.yellow.bgBlack(` ${code} `);
});
// 处理加粗文本
line = line.replace(/\*\*([^*]+)\*\*/g, (match, text) => {
return chalk_1.default.bold(text);
});
// 处理斜体文本
line = line.replace(/\*([^*]+)\*/g, (match, text) => {
return chalk_1.default.italic(text);
});
// 处理链接
line = line.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, text, url) => {
return chalk_1.default.blue.underline(text) + chalk_1.default.dim(` (${url})`);
});
// 粗体和斜体
line = line.replace(/\*\*\*([^\*]+)\*\*\*/g, chalk_1.default.bold.italic('$1'));
line = line.replace(/\*\*([^\*]+)\*\*/g, chalk_1.default.bold('$1'));
line = line.replace(/\*([^\*]+)\*/g, chalk_1.default.italic('$1'));
return line;
}
/**
* 获取MIME类型
*/
getMimeType(extension) {
const mimeTypes = {
'.png': 'image/png',
'.jpeg': 'image/jpeg',
'.jpg': 'image/jpeg',
'.webp': 'image/webp',
'.gif': 'image/gif',
};
return mimeTypes[extension] || null;
}
/**
* 完成处理,处理缓冲区中剩余的内容
*/
finalize() {
if (this.buffer.length > 0) {
const result = this.processLine(this.buffer + '\n');
this.buffer = '';
return result;
}
// 如果还在代码块中,重置状态(因为现在是按行处理的)
if (this.isInCodeBlock) {
this.isInCodeBlock = false;
this.codeBlockContent = '';
this.codeBlockLanguage = '';
}
return '';
}
/**
* 重置渲染器状态
*/
reset() {
this.buffer = '';
this.isInCodeBlock = false;
this.codeBlockLanguage = '';
this.codeBlockContent = '';
this.currentLine = '';
this.codeLineNumber = 1;
}
}
exports.StreamRenderer = StreamRenderer;
//# sourceMappingURL=stream-renderer.js.map