终端数据保留
Some checks failed
Build Linux / Build Linux x86_64 (push) Has been cancelled
Build Test / Build Test (${{ matrix.platform.name }}) (map[name:Linux os:ubuntu-latest rust-target:x86_64-unknown-linux-gnu]) (push) Has been cancelled
Build Test / Build Test (${{ matrix.platform.name }}) (map[name:Windows os:windows-latest rust-target:x86_64-pc-windows-msvc]) (push) Has been cancelled
Build Test / Build Test (${{ matrix.platform.name }}) (map[name:macOS os:macos-latest rust-target:x86_64-apple-darwin]) (push) Has been cancelled
Build Test / Build Test Summary (push) Has been cancelled
Some checks failed
Build Linux / Build Linux x86_64 (push) Has been cancelled
Build Test / Build Test (${{ matrix.platform.name }}) (map[name:Linux os:ubuntu-latest rust-target:x86_64-unknown-linux-gnu]) (push) Has been cancelled
Build Test / Build Test (${{ matrix.platform.name }}) (map[name:Windows os:windows-latest rust-target:x86_64-pc-windows-msvc]) (push) Has been cancelled
Build Test / Build Test (${{ matrix.platform.name }}) (map[name:macOS os:macos-latest rust-target:x86_64-apple-darwin]) (push) Has been cancelled
Build Test / Build Test Summary (push) Has been cancelled
This commit is contained in:
@@ -1889,8 +1889,8 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
|
||||
visible: true,
|
||||
content: (
|
||||
<MainContentArea isEditing={layout.activeView === 'editor'}>
|
||||
{layout.activeView === 'terminal' ? (
|
||||
// 终端视图
|
||||
{/* 终端始终渲染,通过显示/隐藏控制 */}
|
||||
<div className={cn("absolute inset-0", layout.activeView === 'terminal' ? 'block' : 'hidden')}>
|
||||
<Terminal
|
||||
onClose={closeTerminal}
|
||||
isMaximized={layout.isTerminalMaximized}
|
||||
@@ -1898,14 +1898,18 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
|
||||
projectPath={projectPath}
|
||||
className="h-full w-full"
|
||||
/>
|
||||
) : layout.activeView === 'editor' && layout.editingFile ? (
|
||||
// 文件编辑器视图
|
||||
<FileEditorEnhanced
|
||||
filePath={layout.editingFile}
|
||||
onClose={closeFileEditor}
|
||||
className="h-full"
|
||||
/>
|
||||
) : layout.activeView === 'preview' && layout.previewUrl ? (
|
||||
</div>
|
||||
|
||||
{/* 其他视图 */}
|
||||
<div className={cn("h-full w-full", layout.activeView === 'terminal' ? 'hidden' : 'block')}>
|
||||
{layout.activeView === 'editor' && layout.editingFile ? (
|
||||
// 文件编辑器视图
|
||||
<FileEditorEnhanced
|
||||
filePath={layout.editingFile}
|
||||
onClose={closeFileEditor}
|
||||
className="h-full"
|
||||
/>
|
||||
) : layout.activeView === 'preview' && layout.previewUrl ? (
|
||||
// 预览视图
|
||||
<SplitPane
|
||||
left={
|
||||
@@ -1943,24 +1947,24 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
|
||||
minRightWidth={400}
|
||||
className="h-full"
|
||||
/>
|
||||
) : (
|
||||
// 默认聊天视图
|
||||
<ChatView
|
||||
projectPathInput={projectPathInput}
|
||||
messagesList={messagesList}
|
||||
floatingInput={
|
||||
<div className="w-full max-w-5xl mx-auto px-4">
|
||||
<FloatingPromptInput
|
||||
ref={floatingPromptRef}
|
||||
onSend={handleSendPrompt}
|
||||
onCancel={handleCancelExecution}
|
||||
isLoading={isLoading}
|
||||
disabled={!projectPath}
|
||||
projectPath={projectPath}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
floatingElements={
|
||||
) : (
|
||||
// 默认聊天视图
|
||||
<ChatView
|
||||
projectPathInput={projectPathInput}
|
||||
messagesList={messagesList}
|
||||
floatingInput={
|
||||
<div className="w-full max-w-5xl mx-auto px-4">
|
||||
<FloatingPromptInput
|
||||
ref={floatingPromptRef}
|
||||
onSend={handleSendPrompt}
|
||||
onCancel={handleCancelExecution}
|
||||
isLoading={isLoading}
|
||||
disabled={!projectPath}
|
||||
projectPath={projectPath}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
floatingElements={
|
||||
<>
|
||||
{/* 文件监控展开面板 */}
|
||||
<AnimatePresence>
|
||||
@@ -2113,7 +2117,8 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
</MainContentArea>
|
||||
)
|
||||
},
|
||||
|
@@ -32,6 +32,7 @@ export const Terminal: React.FC<TerminalProps> = ({
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
const [sessionId, setSessionId] = useState<string | null>(null);
|
||||
const [terminalSize, setTerminalSize] = useState({ cols: 80, rows: 24 });
|
||||
const [containerWidth, setContainerWidth] = useState(0);
|
||||
|
||||
// 计算终端应该有的尺寸
|
||||
const calculateOptimalSize = useCallback(() => {
|
||||
@@ -46,17 +47,26 @@ export const Terminal: React.FC<TerminalProps> = ({
|
||||
const lineHeight = fontSize * 1.2; // 行高
|
||||
|
||||
// 计算能容纳的最大列数和行数
|
||||
// 减去一些像素避免滚动条
|
||||
const cols = Math.max(80, Math.floor((rect.width - 2) / charWidth));
|
||||
const rows = Math.max(24, Math.floor((rect.height - 2) / lineHeight));
|
||||
const availableWidth = rect.width - 2;
|
||||
const availableHeight = rect.height - 2;
|
||||
|
||||
console.log('[Terminal] Calculated size:', {
|
||||
const cols = Math.max(80, Math.floor(availableWidth / charWidth));
|
||||
const rows = Math.max(24, Math.floor(availableHeight / lineHeight));
|
||||
|
||||
// 计算实际使用的宽度
|
||||
const usedWidth = cols * charWidth;
|
||||
const unusedWidth = availableWidth - usedWidth;
|
||||
const percentUsed = ((usedWidth / availableWidth) * 100).toFixed(1);
|
||||
|
||||
console.log('[Terminal] Size calculation:', {
|
||||
containerWidth: rect.width,
|
||||
containerHeight: rect.height,
|
||||
cols,
|
||||
rows,
|
||||
availableWidth,
|
||||
charWidth,
|
||||
lineHeight
|
||||
cols,
|
||||
usedWidth,
|
||||
unusedWidth,
|
||||
percentUsed: `${percentUsed}%`,
|
||||
message: unusedWidth > 10 ? `还有 ${unusedWidth.toFixed(1)}px 未使用` : '宽度使用正常'
|
||||
});
|
||||
|
||||
return { cols, rows };
|
||||
@@ -66,29 +76,73 @@ export const Terminal: React.FC<TerminalProps> = ({
|
||||
const resizeTerminal = useCallback(() => {
|
||||
if (!xtermRef.current || !terminalRef.current) return;
|
||||
|
||||
const newSize = calculateOptimalSize();
|
||||
// 先尝试获取实际的字符尺寸
|
||||
let actualCharWidth = 8.4; // 默认值
|
||||
let actualLineHeight = 16.8; // 默认值
|
||||
|
||||
try {
|
||||
const core = (xtermRef.current as any)._core;
|
||||
if (core && core._renderService && core._renderService.dimensions) {
|
||||
const dims = core._renderService.dimensions;
|
||||
if (dims.actualCellWidth) actualCharWidth = dims.actualCellWidth;
|
||||
if (dims.actualCellHeight) actualLineHeight = dims.actualCellHeight;
|
||||
|
||||
console.log('[Terminal] Using actual char dimensions:', {
|
||||
actualCharWidth,
|
||||
actualLineHeight
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
// 使用默认值
|
||||
}
|
||||
|
||||
// 使用实际字符尺寸计算新的列数和行数
|
||||
const rect = terminalRef.current.getBoundingClientRect();
|
||||
const availableWidth = rect.width - 2;
|
||||
const availableHeight = rect.height - 2;
|
||||
|
||||
// 更新容器宽度显示
|
||||
setContainerWidth(rect.width);
|
||||
|
||||
const newCols = Math.max(80, Math.floor(availableWidth / actualCharWidth));
|
||||
const newRows = Math.max(24, Math.floor(availableHeight / actualLineHeight));
|
||||
|
||||
// 计算宽度使用情况
|
||||
const usedWidth = newCols * actualCharWidth;
|
||||
const unusedWidth = availableWidth - usedWidth;
|
||||
const percentUsed = ((usedWidth / availableWidth) * 100).toFixed(1);
|
||||
|
||||
// 只有当尺寸真的改变时才调整
|
||||
if (newSize.cols !== terminalSize.cols || newSize.rows !== terminalSize.rows) {
|
||||
console.log('[Terminal] Resizing from', terminalSize, 'to', newSize);
|
||||
if (newCols !== terminalSize.cols || newRows !== terminalSize.rows) {
|
||||
console.log('[Terminal] Resizing:', {
|
||||
from: terminalSize,
|
||||
to: { cols: newCols, rows: newRows },
|
||||
containerWidth: rect.width,
|
||||
availableWidth,
|
||||
usedWidth,
|
||||
unusedWidth,
|
||||
percentUsed: `${percentUsed}%`
|
||||
});
|
||||
|
||||
setTerminalSize(newSize);
|
||||
xtermRef.current.resize(newSize.cols, newSize.rows);
|
||||
setTerminalSize({ cols: newCols, rows: newRows });
|
||||
xtermRef.current.resize(newCols, newRows);
|
||||
|
||||
// 更新后端
|
||||
if (sessionId) {
|
||||
api.resizeTerminal(sessionId, newSize.cols, newSize.rows).catch(console.error);
|
||||
api.resizeTerminal(sessionId, newCols, newRows).catch(console.error);
|
||||
}
|
||||
|
||||
// 强制刷新渲染
|
||||
if ((xtermRef.current as any)._core) {
|
||||
try {
|
||||
const core = (xtermRef.current as any)._core;
|
||||
if (core._renderService) {
|
||||
core._renderService.onResize(newSize.cols, newSize.rows);
|
||||
if (core && core._renderService && core._renderService._onIntersectionChange) {
|
||||
core._renderService._onIntersectionChange({ intersectionRatio: 1 });
|
||||
}
|
||||
} catch (e) {
|
||||
// 忽略错误,某些版本的 xterm 可能没有这个方法
|
||||
}
|
||||
}
|
||||
}, [calculateOptimalSize, terminalSize, sessionId]);
|
||||
}, [terminalSize, sessionId]);
|
||||
|
||||
// 防抖的resize处理
|
||||
const handleResize = useCallback(() => {
|
||||
@@ -180,10 +234,47 @@ export const Terminal: React.FC<TerminalProps> = ({
|
||||
// 保存引用
|
||||
xtermRef.current = xterm;
|
||||
|
||||
// 延迟一下确保渲染完成,然后调整尺寸
|
||||
// 延迟一下确保渲染完成,然后获取实际字符尺寸并调整
|
||||
setTimeout(() => {
|
||||
if (xtermRef.current && terminalRef.current) {
|
||||
// 尝试获取实际的字符尺寸
|
||||
try {
|
||||
const core = (xtermRef.current as any)._core;
|
||||
if (core && core._renderService && core._renderService.dimensions) {
|
||||
const dims = core._renderService.dimensions;
|
||||
const actualCharWidth = dims.actualCellWidth || dims.scaledCellWidth;
|
||||
const actualLineHeight = dims.actualCellHeight || dims.scaledCellHeight;
|
||||
|
||||
if (actualCharWidth && actualLineHeight) {
|
||||
console.log('[Terminal] Actual character dimensions:', {
|
||||
charWidth: actualCharWidth,
|
||||
lineHeight: actualLineHeight
|
||||
});
|
||||
|
||||
// 使用实际尺寸重新计算
|
||||
const rect = terminalRef.current.getBoundingClientRect();
|
||||
const availableWidth = rect.width - 2;
|
||||
const newCols = Math.floor(availableWidth / actualCharWidth);
|
||||
|
||||
console.log('[Terminal] Recalculating with actual dimensions:', {
|
||||
availableWidth,
|
||||
actualCharWidth,
|
||||
newCols,
|
||||
currentCols: xtermRef.current.cols
|
||||
});
|
||||
|
||||
if (newCols > xtermRef.current.cols) {
|
||||
xtermRef.current.resize(newCols, xtermRef.current.rows);
|
||||
setTerminalSize({ cols: newCols, rows: xtermRef.current.rows });
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[Terminal] Could not get actual char dimensions:', e);
|
||||
}
|
||||
}
|
||||
resizeTerminal();
|
||||
}, 100);
|
||||
}, 150);
|
||||
|
||||
// 创建终端会话
|
||||
const newSessionId = await api.createTerminalSession(projectPath || process.cwd());
|
||||
@@ -259,7 +350,15 @@ export const Terminal: React.FC<TerminalProps> = ({
|
||||
useEffect(() => {
|
||||
if (!terminalRef.current) return;
|
||||
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
// 初始化时立即获取容器宽度
|
||||
const rect = terminalRef.current.getBoundingClientRect();
|
||||
setContainerWidth(rect.width);
|
||||
|
||||
const resizeObserver = new ResizeObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
const { width } = entry.contentRect;
|
||||
setContainerWidth(width);
|
||||
}
|
||||
handleResize();
|
||||
});
|
||||
|
||||
@@ -301,6 +400,9 @@ export const Terminal: React.FC<TerminalProps> = ({
|
||||
<span className="text-xs text-gray-400">
|
||||
{terminalSize.cols}×{terminalSize.rows}
|
||||
</span>
|
||||
<span className="text-xs text-yellow-400">
|
||||
容器宽度: {containerWidth.toFixed(0)}px
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1">
|
||||
|
@@ -13,6 +13,8 @@ interface LayoutState {
|
||||
editingFile: string | null; // 新增:正在编辑的文件
|
||||
previewUrl: string | null; // 新增:预览URL
|
||||
isTerminalMaximized: boolean; // 新增:终端是否最大化
|
||||
isTerminalOpen: boolean; // 新增:终端是否打开(保持会话)
|
||||
previousView: 'chat' | 'editor' | 'preview' | null; // 新增:记录终端打开前的视图
|
||||
}
|
||||
|
||||
interface LayoutBreakpoints {
|
||||
@@ -37,6 +39,8 @@ const DEFAULT_LAYOUT: LayoutState = {
|
||||
editingFile: null,
|
||||
previewUrl: null,
|
||||
isTerminalMaximized: false, // 默认终端不最大化
|
||||
isTerminalOpen: false, // 默认终端关闭
|
||||
previousView: null, // 默认无前一个视图
|
||||
};
|
||||
|
||||
const STORAGE_KEY = 'claudia_layout_preferences';
|
||||
@@ -301,21 +305,32 @@ export function useLayoutManager(projectPath?: string) {
|
||||
});
|
||||
}, [saveLayout]);
|
||||
|
||||
// 打开终端
|
||||
// 打开/切换终端
|
||||
const openTerminal = useCallback(() => {
|
||||
saveLayout({
|
||||
activeView: 'terminal',
|
||||
editingFile: null,
|
||||
previewUrl: null,
|
||||
});
|
||||
}, [saveLayout]);
|
||||
if (layout.activeView === 'terminal') {
|
||||
// 如果终端已经显示,收起它(恢复之前的视图)
|
||||
saveLayout({
|
||||
activeView: layout.previousView || 'chat',
|
||||
previousView: null,
|
||||
});
|
||||
} else {
|
||||
// 显示终端,记住当前视图
|
||||
const prev = layout.activeView as 'chat' | 'editor' | 'preview';
|
||||
saveLayout({
|
||||
activeView: 'terminal',
|
||||
isTerminalOpen: true,
|
||||
previousView: prev,
|
||||
});
|
||||
}
|
||||
}, [layout.activeView, layout.previousView, saveLayout]);
|
||||
|
||||
// 关闭终端
|
||||
// 关闭终端(恢复之前的视图)
|
||||
const closeTerminal = useCallback(() => {
|
||||
saveLayout({
|
||||
activeView: 'chat',
|
||||
activeView: layout.previousView || 'chat',
|
||||
previousView: null,
|
||||
});
|
||||
}, [saveLayout]);
|
||||
}, [layout.previousView, saveLayout]);
|
||||
|
||||
// 切换终端最大化状态
|
||||
const toggleTerminalMaximize = useCallback(() => {
|
||||
|
Reference in New Issue
Block a user