diff --git a/src/components/ClaudeCodeSession.tsx b/src/components/ClaudeCodeSession.tsx index 7e15b26..9cd2424 100644 --- a/src/components/ClaudeCodeSession.tsx +++ b/src/components/ClaudeCodeSession.tsx @@ -1889,8 +1889,8 @@ export const ClaudeCodeSession: React.FC = ({ visible: true, content: ( - {layout.activeView === 'terminal' ? ( - // 终端视图 + {/* 终端始终渲染,通过显示/隐藏控制 */} +
= ({ projectPath={projectPath} className="h-full w-full" /> - ) : layout.activeView === 'editor' && layout.editingFile ? ( - // 文件编辑器视图 - - ) : layout.activeView === 'preview' && layout.previewUrl ? ( +
+ + {/* 其他视图 */} +
+ {layout.activeView === 'editor' && layout.editingFile ? ( + // 文件编辑器视图 + + ) : layout.activeView === 'preview' && layout.previewUrl ? ( // 预览视图 = ({ minRightWidth={400} className="h-full" /> - ) : ( - // 默认聊天视图 - - -
- } - floatingElements={ + ) : ( + // 默认聊天视图 + + + + } + floatingElements={ <> {/* 文件监控展开面板 */} @@ -2113,7 +2117,8 @@ export const ClaudeCodeSession: React.FC = ({ } /> - )} + )} +
) }, diff --git a/src/components/Terminal.tsx b/src/components/Terminal.tsx index 53de618..a5561a1 100644 --- a/src/components/Terminal.tsx +++ b/src/components/Terminal.tsx @@ -32,6 +32,7 @@ export const Terminal: React.FC = ({ const [isConnected, setIsConnected] = useState(false); const [sessionId, setSessionId] = useState(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 = ({ 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 = ({ 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 = ({ // 保存引用 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 = ({ 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 = ({ {terminalSize.cols}×{terminalSize.rows} + + 容器宽度: {containerWidth.toFixed(0)}px +
diff --git a/src/hooks/useLayoutManager.ts b/src/hooks/useLayoutManager.ts index 9affe7a..81429f7 100644 --- a/src/hooks/useLayoutManager.ts +++ b/src/hooks/useLayoutManager.ts @@ -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(() => {