优化终端
This commit is contained in:
15
bun.lock
15
bun.lock
@@ -50,6 +50,11 @@
|
|||||||
"remark-gfm": "^4.0.0",
|
"remark-gfm": "^4.0.0",
|
||||||
"tailwind-merge": "^2.6.0",
|
"tailwind-merge": "^2.6.0",
|
||||||
"tailwindcss": "^4.1.8",
|
"tailwindcss": "^4.1.8",
|
||||||
|
"xterm": "^5.3.0",
|
||||||
|
"xterm-addon-fit": "^0.8.0",
|
||||||
|
"xterm-addon-search": "^0.13.0",
|
||||||
|
"xterm-addon-unicode11": "^0.6.0",
|
||||||
|
"xterm-addon-web-links": "^0.9.0",
|
||||||
"zod": "^3.24.1",
|
"zod": "^3.24.1",
|
||||||
"zustand": "^5.0.6",
|
"zustand": "^5.0.6",
|
||||||
},
|
},
|
||||||
@@ -1363,6 +1368,16 @@
|
|||||||
|
|
||||||
"xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="],
|
"xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="],
|
||||||
|
|
||||||
|
"xterm": ["xterm@5.3.0", "https://registry.npmmirror.com/xterm/-/xterm-5.3.0.tgz", {}, "sha512-8QqjlekLUFTrU6x7xck1MsPzPA571K5zNqWm0M0oroYEWVOptZ0+ubQSkQ3uxIEhcIHRujJy6emDWX4A7qyFzg=="],
|
||||||
|
|
||||||
|
"xterm-addon-fit": ["xterm-addon-fit@0.8.0", "https://registry.npmmirror.com/xterm-addon-fit/-/xterm-addon-fit-0.8.0.tgz", { "peerDependencies": { "xterm": "^5.0.0" } }, "sha512-yj3Np7XlvxxhYF/EJ7p3KHaMt6OdwQ+HDu573Vx1lRXsVxOcnVJs51RgjZOouIZOczTsskaS+CpXspK81/DLqw=="],
|
||||||
|
|
||||||
|
"xterm-addon-search": ["xterm-addon-search@0.13.0", "https://registry.npmmirror.com/xterm-addon-search/-/xterm-addon-search-0.13.0.tgz", { "peerDependencies": { "xterm": "^5.0.0" } }, "sha512-sDUwG4CnqxUjSEFh676DlS3gsh3XYCzAvBPSvJ5OPgF3MRL3iHLPfsb06doRicLC2xXNpeG2cWk8x1qpESWJMA=="],
|
||||||
|
|
||||||
|
"xterm-addon-unicode11": ["xterm-addon-unicode11@0.6.0", "https://registry.npmmirror.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.6.0.tgz", { "peerDependencies": { "xterm": "^5.0.0" } }, "sha512-5pkb8YoS/deRtNqQRw8t640mu+Ga8B2MG3RXGQu0bwgcfr8XiXIRI880TWM49ICAHhTmnOLPzIIBIjEnCq7k2A=="],
|
||||||
|
|
||||||
|
"xterm-addon-web-links": ["xterm-addon-web-links@0.9.0", "https://registry.npmmirror.com/xterm-addon-web-links/-/xterm-addon-web-links-0.9.0.tgz", { "peerDependencies": { "xterm": "^5.0.0" } }, "sha512-LIzi4jBbPlrKMZF3ihoyqayWyTXAwGfu4yprz1aK2p71e9UKXN6RRzVONR0L+Zd+Ik5tPVI9bwp9e8fDTQh49Q=="],
|
||||||
|
|
||||||
"yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="],
|
"yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="],
|
||||||
|
|
||||||
"yocto-queue": ["yocto-queue@0.1.0", "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
"yocto-queue": ["yocto-queue@0.1.0", "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
||||||
|
@@ -63,6 +63,7 @@
|
|||||||
"xterm": "^5.3.0",
|
"xterm": "^5.3.0",
|
||||||
"xterm-addon-fit": "^0.8.0",
|
"xterm-addon-fit": "^0.8.0",
|
||||||
"xterm-addon-search": "^0.13.0",
|
"xterm-addon-search": "^0.13.0",
|
||||||
|
"xterm-addon-unicode11": "^0.6.0",
|
||||||
"xterm-addon-web-links": "^0.9.0",
|
"xterm-addon-web-links": "^0.9.0",
|
||||||
"zod": "^3.24.1",
|
"zod": "^3.24.1",
|
||||||
"zustand": "^5.0.6"
|
"zustand": "^5.0.6"
|
||||||
|
@@ -52,8 +52,8 @@ pub async fn create_terminal_session(
|
|||||||
|
|
||||||
// Create PTY pair with size
|
// Create PTY pair with size
|
||||||
let pty_pair = pty_system.openpty(PtySize {
|
let pty_pair = pty_system.openpty(PtySize {
|
||||||
rows: 24,
|
rows: 30,
|
||||||
cols: 80,
|
cols: 120,
|
||||||
pixel_width: 0,
|
pixel_width: 0,
|
||||||
pixel_height: 0,
|
pixel_height: 0,
|
||||||
}).map_err(|e| format!("Failed to create PTY: {}", e))?;
|
}).map_err(|e| format!("Failed to create PTY: {}", e))?;
|
||||||
@@ -75,6 +75,16 @@ pub async fn create_terminal_session(
|
|||||||
// Set environment variables
|
// Set environment variables
|
||||||
cmd.env("TERM", "xterm-256color");
|
cmd.env("TERM", "xterm-256color");
|
||||||
cmd.env("COLORTERM", "truecolor");
|
cmd.env("COLORTERM", "truecolor");
|
||||||
|
cmd.env("LANG", std::env::var("LANG").unwrap_or_else(|_| "en_US.UTF-8".to_string()));
|
||||||
|
cmd.env("LC_ALL", std::env::var("LC_ALL").unwrap_or_else(|_| "en_US.UTF-8".to_string()));
|
||||||
|
cmd.env("LC_CTYPE", std::env::var("LC_CTYPE").unwrap_or_else(|_| "en_US.UTF-8".to_string()));
|
||||||
|
|
||||||
|
// 继承其他环境变量
|
||||||
|
for (key, value) in std::env::vars() {
|
||||||
|
if !key.starts_with("TERM") && !key.starts_with("COLORTERM") && !key.starts_with("LC_") && !key.starts_with("LANG") {
|
||||||
|
cmd.env(&key, &value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Spawn the shell process
|
// Spawn the shell process
|
||||||
let _child = pty_pair.slave.spawn_command(cmd)
|
let _child = pty_pair.slave.spawn_command(cmd)
|
||||||
|
@@ -60,21 +60,52 @@ export const Terminal: React.FC<TerminalProps> = ({
|
|||||||
// 创建终端实例
|
// 创建终端实例
|
||||||
const xterm = new XTerm({
|
const xterm = new XTerm({
|
||||||
theme: {
|
theme: {
|
||||||
background: '#000000',
|
background: '#1e1e1e',
|
||||||
foreground: '#ffffff',
|
foreground: '#d4d4d4',
|
||||||
cursor: '#ffffff',
|
cursor: '#ffffff',
|
||||||
cursorAccent: '#000000',
|
cursorAccent: '#000000',
|
||||||
selectionBackground: '#404040',
|
selectionBackground: '#264f78',
|
||||||
|
// ANSI 颜色
|
||||||
|
black: '#000000',
|
||||||
|
red: '#cd3131',
|
||||||
|
green: '#0dbc79',
|
||||||
|
yellow: '#e5e510',
|
||||||
|
blue: '#2472c8',
|
||||||
|
magenta: '#bc3fbc',
|
||||||
|
cyan: '#11a8cd',
|
||||||
|
white: '#e5e5e5',
|
||||||
|
brightBlack: '#666666',
|
||||||
|
brightRed: '#f14c4c',
|
||||||
|
brightGreen: '#23d18b',
|
||||||
|
brightYellow: '#f5f543',
|
||||||
|
brightBlue: '#3b8eea',
|
||||||
|
brightMagenta: '#d670d6',
|
||||||
|
brightCyan: '#29b8db',
|
||||||
|
brightWhite: '#e5e5e5',
|
||||||
},
|
},
|
||||||
fontFamily: '"JetBrains Mono", "SF Mono", "Monaco", "Inconsolata", "Fira Code", "Source Code Pro", monospace',
|
// 使用支持 Powerline 和 Nerd Font 的字体
|
||||||
|
fontFamily: '"MesloLGS NF", "JetBrainsMono Nerd Font", "FiraCode Nerd Font", "Hack Nerd Font", "SauceCodePro Nerd Font", "JetBrains Mono", "SF Mono", "Monaco", "Inconsolata", "Fira Code", "Source Code Pro", monospace',
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
|
fontWeight: 'normal',
|
||||||
|
fontWeightBold: 'bold',
|
||||||
lineHeight: 1.2,
|
lineHeight: 1.2,
|
||||||
|
letterSpacing: 0,
|
||||||
cols: 80,
|
cols: 80,
|
||||||
rows: 24,
|
rows: 24,
|
||||||
allowTransparency: true,
|
allowTransparency: false,
|
||||||
scrollback: 1000,
|
scrollback: 10000,
|
||||||
convertEol: true,
|
convertEol: true,
|
||||||
cursorBlink: true,
|
cursorBlink: true,
|
||||||
|
cursorStyle: 'block',
|
||||||
|
drawBoldTextInBrightColors: true,
|
||||||
|
macOptionIsMeta: true,
|
||||||
|
rightClickSelectsWord: true,
|
||||||
|
// 启用提议的 API 以支持 Unicode 插件
|
||||||
|
allowProposedApi: true,
|
||||||
|
// 使用 canvas 渲染器以获得更好的性能
|
||||||
|
// @ts-ignore - xterm.js 类型定义可能过时
|
||||||
|
rendererType: 'canvas',
|
||||||
|
windowsMode: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 添加插件
|
// 添加插件
|
||||||
@@ -91,8 +122,15 @@ export const Terminal: React.FC<TerminalProps> = ({
|
|||||||
xterm.open(terminalRef.current);
|
xterm.open(terminalRef.current);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 适应容器大小
|
// 适应容器大小 - 延迟一点确保容器尺寸计算正确
|
||||||
setTimeout(() => fitAddon.fit(), 100);
|
setTimeout(() => {
|
||||||
|
fitAddon.fit();
|
||||||
|
// 发送 resize 命令到后端(虽然当前未实现)
|
||||||
|
const { cols, rows } = fitAddon.proposeDimensions() || { cols: 120, rows: 30 };
|
||||||
|
if (newSessionId) {
|
||||||
|
api.resizeTerminal(newSessionId, cols, rows).catch(console.error);
|
||||||
|
}
|
||||||
|
}, 150);
|
||||||
|
|
||||||
// 存储引用
|
// 存储引用
|
||||||
xtermRef.current = xterm;
|
xtermRef.current = xterm;
|
||||||
@@ -189,7 +227,7 @@ export const Terminal: React.FC<TerminalProps> = ({
|
|||||||
}, [isMaximized, handleResize]);
|
}, [isMaximized, handleResize]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn('flex flex-col h-full bg-black', className)}>
|
<div className={cn('flex flex-col h-full bg-[#1e1e1e]', className)}>
|
||||||
{/* 终端头部 */}
|
{/* 终端头部 */}
|
||||||
<div className="flex items-center justify-between px-3 py-2 bg-gray-900 border-b border-gray-700">
|
<div className="flex items-center justify-between px-3 py-2 bg-gray-900 border-b border-gray-700">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
@@ -238,12 +276,12 @@ export const Terminal: React.FC<TerminalProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 终端主体 */}
|
{/* 终端主体 */}
|
||||||
<div className="flex-1 relative">
|
<div className="flex-1 relative bg-[#1e1e1e]">
|
||||||
<div
|
<div
|
||||||
ref={terminalRef}
|
ref={terminalRef}
|
||||||
className="absolute inset-0 p-2"
|
className="absolute inset-0"
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: '#1e1e1e',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user