终端数据保留
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,
|
visible: true,
|
||||||
content: (
|
content: (
|
||||||
<MainContentArea isEditing={layout.activeView === 'editor'}>
|
<MainContentArea isEditing={layout.activeView === 'editor'}>
|
||||||
{layout.activeView === 'terminal' ? (
|
{/* 终端始终渲染,通过显示/隐藏控制 */}
|
||||||
// 终端视图
|
<div className={cn("absolute inset-0", layout.activeView === 'terminal' ? 'block' : 'hidden')}>
|
||||||
<Terminal
|
<Terminal
|
||||||
onClose={closeTerminal}
|
onClose={closeTerminal}
|
||||||
isMaximized={layout.isTerminalMaximized}
|
isMaximized={layout.isTerminalMaximized}
|
||||||
@@ -1898,14 +1898,18 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
|
|||||||
projectPath={projectPath}
|
projectPath={projectPath}
|
||||||
className="h-full w-full"
|
className="h-full w-full"
|
||||||
/>
|
/>
|
||||||
) : layout.activeView === 'editor' && layout.editingFile ? (
|
</div>
|
||||||
// 文件编辑器视图
|
|
||||||
<FileEditorEnhanced
|
{/* 其他视图 */}
|
||||||
filePath={layout.editingFile}
|
<div className={cn("h-full w-full", layout.activeView === 'terminal' ? 'hidden' : 'block')}>
|
||||||
onClose={closeFileEditor}
|
{layout.activeView === 'editor' && layout.editingFile ? (
|
||||||
className="h-full"
|
// 文件编辑器视图
|
||||||
/>
|
<FileEditorEnhanced
|
||||||
) : layout.activeView === 'preview' && layout.previewUrl ? (
|
filePath={layout.editingFile}
|
||||||
|
onClose={closeFileEditor}
|
||||||
|
className="h-full"
|
||||||
|
/>
|
||||||
|
) : layout.activeView === 'preview' && layout.previewUrl ? (
|
||||||
// 预览视图
|
// 预览视图
|
||||||
<SplitPane
|
<SplitPane
|
||||||
left={
|
left={
|
||||||
@@ -1943,24 +1947,24 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
|
|||||||
minRightWidth={400}
|
minRightWidth={400}
|
||||||
className="h-full"
|
className="h-full"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
// 默认聊天视图
|
// 默认聊天视图
|
||||||
<ChatView
|
<ChatView
|
||||||
projectPathInput={projectPathInput}
|
projectPathInput={projectPathInput}
|
||||||
messagesList={messagesList}
|
messagesList={messagesList}
|
||||||
floatingInput={
|
floatingInput={
|
||||||
<div className="w-full max-w-5xl mx-auto px-4">
|
<div className="w-full max-w-5xl mx-auto px-4">
|
||||||
<FloatingPromptInput
|
<FloatingPromptInput
|
||||||
ref={floatingPromptRef}
|
ref={floatingPromptRef}
|
||||||
onSend={handleSendPrompt}
|
onSend={handleSendPrompt}
|
||||||
onCancel={handleCancelExecution}
|
onCancel={handleCancelExecution}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
disabled={!projectPath}
|
disabled={!projectPath}
|
||||||
projectPath={projectPath}
|
projectPath={projectPath}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
floatingElements={
|
floatingElements={
|
||||||
<>
|
<>
|
||||||
{/* 文件监控展开面板 */}
|
{/* 文件监控展开面板 */}
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
@@ -2113,7 +2117,8 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
</MainContentArea>
|
</MainContentArea>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
@@ -32,6 +32,7 @@ export const Terminal: React.FC<TerminalProps> = ({
|
|||||||
const [isConnected, setIsConnected] = useState(false);
|
const [isConnected, setIsConnected] = useState(false);
|
||||||
const [sessionId, setSessionId] = useState<string | null>(null);
|
const [sessionId, setSessionId] = useState<string | null>(null);
|
||||||
const [terminalSize, setTerminalSize] = useState({ cols: 80, rows: 24 });
|
const [terminalSize, setTerminalSize] = useState({ cols: 80, rows: 24 });
|
||||||
|
const [containerWidth, setContainerWidth] = useState(0);
|
||||||
|
|
||||||
// 计算终端应该有的尺寸
|
// 计算终端应该有的尺寸
|
||||||
const calculateOptimalSize = useCallback(() => {
|
const calculateOptimalSize = useCallback(() => {
|
||||||
@@ -46,17 +47,26 @@ export const Terminal: React.FC<TerminalProps> = ({
|
|||||||
const lineHeight = fontSize * 1.2; // 行高
|
const lineHeight = fontSize * 1.2; // 行高
|
||||||
|
|
||||||
// 计算能容纳的最大列数和行数
|
// 计算能容纳的最大列数和行数
|
||||||
// 减去一些像素避免滚动条
|
const availableWidth = rect.width - 2;
|
||||||
const cols = Math.max(80, Math.floor((rect.width - 2) / charWidth));
|
const availableHeight = rect.height - 2;
|
||||||
const rows = Math.max(24, Math.floor((rect.height - 2) / lineHeight));
|
|
||||||
|
|
||||||
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,
|
containerWidth: rect.width,
|
||||||
containerHeight: rect.height,
|
availableWidth,
|
||||||
cols,
|
|
||||||
rows,
|
|
||||||
charWidth,
|
charWidth,
|
||||||
lineHeight
|
cols,
|
||||||
|
usedWidth,
|
||||||
|
unusedWidth,
|
||||||
|
percentUsed: `${percentUsed}%`,
|
||||||
|
message: unusedWidth > 10 ? `还有 ${unusedWidth.toFixed(1)}px 未使用` : '宽度使用正常'
|
||||||
});
|
});
|
||||||
|
|
||||||
return { cols, rows };
|
return { cols, rows };
|
||||||
@@ -66,29 +76,73 @@ export const Terminal: React.FC<TerminalProps> = ({
|
|||||||
const resizeTerminal = useCallback(() => {
|
const resizeTerminal = useCallback(() => {
|
||||||
if (!xtermRef.current || !terminalRef.current) return;
|
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) {
|
if (newCols !== terminalSize.cols || newRows !== terminalSize.rows) {
|
||||||
console.log('[Terminal] Resizing from', terminalSize, 'to', newSize);
|
console.log('[Terminal] Resizing:', {
|
||||||
|
from: terminalSize,
|
||||||
|
to: { cols: newCols, rows: newRows },
|
||||||
|
containerWidth: rect.width,
|
||||||
|
availableWidth,
|
||||||
|
usedWidth,
|
||||||
|
unusedWidth,
|
||||||
|
percentUsed: `${percentUsed}%`
|
||||||
|
});
|
||||||
|
|
||||||
setTerminalSize(newSize);
|
setTerminalSize({ cols: newCols, rows: newRows });
|
||||||
xtermRef.current.resize(newSize.cols, newSize.rows);
|
xtermRef.current.resize(newCols, newRows);
|
||||||
|
|
||||||
// 更新后端
|
// 更新后端
|
||||||
if (sessionId) {
|
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;
|
const core = (xtermRef.current as any)._core;
|
||||||
if (core._renderService) {
|
if (core && core._renderService && core._renderService._onIntersectionChange) {
|
||||||
core._renderService.onResize(newSize.cols, newSize.rows);
|
core._renderService._onIntersectionChange({ intersectionRatio: 1 });
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// 忽略错误,某些版本的 xterm 可能没有这个方法
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [calculateOptimalSize, terminalSize, sessionId]);
|
}, [terminalSize, sessionId]);
|
||||||
|
|
||||||
// 防抖的resize处理
|
// 防抖的resize处理
|
||||||
const handleResize = useCallback(() => {
|
const handleResize = useCallback(() => {
|
||||||
@@ -180,10 +234,47 @@ export const Terminal: React.FC<TerminalProps> = ({
|
|||||||
// 保存引用
|
// 保存引用
|
||||||
xtermRef.current = xterm;
|
xtermRef.current = xterm;
|
||||||
|
|
||||||
// 延迟一下确保渲染完成,然后调整尺寸
|
// 延迟一下确保渲染完成,然后获取实际字符尺寸并调整
|
||||||
setTimeout(() => {
|
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();
|
resizeTerminal();
|
||||||
}, 100);
|
}, 150);
|
||||||
|
|
||||||
// 创建终端会话
|
// 创建终端会话
|
||||||
const newSessionId = await api.createTerminalSession(projectPath || process.cwd());
|
const newSessionId = await api.createTerminalSession(projectPath || process.cwd());
|
||||||
@@ -259,7 +350,15 @@ export const Terminal: React.FC<TerminalProps> = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!terminalRef.current) return;
|
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();
|
handleResize();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -301,6 +400,9 @@ export const Terminal: React.FC<TerminalProps> = ({
|
|||||||
<span className="text-xs text-gray-400">
|
<span className="text-xs text-gray-400">
|
||||||
{terminalSize.cols}×{terminalSize.rows}
|
{terminalSize.cols}×{terminalSize.rows}
|
||||||
</span>
|
</span>
|
||||||
|
<span className="text-xs text-yellow-400">
|
||||||
|
容器宽度: {containerWidth.toFixed(0)}px
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
|
@@ -13,6 +13,8 @@ interface LayoutState {
|
|||||||
editingFile: string | null; // 新增:正在编辑的文件
|
editingFile: string | null; // 新增:正在编辑的文件
|
||||||
previewUrl: string | null; // 新增:预览URL
|
previewUrl: string | null; // 新增:预览URL
|
||||||
isTerminalMaximized: boolean; // 新增:终端是否最大化
|
isTerminalMaximized: boolean; // 新增:终端是否最大化
|
||||||
|
isTerminalOpen: boolean; // 新增:终端是否打开(保持会话)
|
||||||
|
previousView: 'chat' | 'editor' | 'preview' | null; // 新增:记录终端打开前的视图
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LayoutBreakpoints {
|
interface LayoutBreakpoints {
|
||||||
@@ -37,6 +39,8 @@ const DEFAULT_LAYOUT: LayoutState = {
|
|||||||
editingFile: null,
|
editingFile: null,
|
||||||
previewUrl: null,
|
previewUrl: null,
|
||||||
isTerminalMaximized: false, // 默认终端不最大化
|
isTerminalMaximized: false, // 默认终端不最大化
|
||||||
|
isTerminalOpen: false, // 默认终端关闭
|
||||||
|
previousView: null, // 默认无前一个视图
|
||||||
};
|
};
|
||||||
|
|
||||||
const STORAGE_KEY = 'claudia_layout_preferences';
|
const STORAGE_KEY = 'claudia_layout_preferences';
|
||||||
@@ -301,21 +305,32 @@ export function useLayoutManager(projectPath?: string) {
|
|||||||
});
|
});
|
||||||
}, [saveLayout]);
|
}, [saveLayout]);
|
||||||
|
|
||||||
// 打开终端
|
// 打开/切换终端
|
||||||
const openTerminal = useCallback(() => {
|
const openTerminal = useCallback(() => {
|
||||||
saveLayout({
|
if (layout.activeView === 'terminal') {
|
||||||
activeView: 'terminal',
|
// 如果终端已经显示,收起它(恢复之前的视图)
|
||||||
editingFile: null,
|
saveLayout({
|
||||||
previewUrl: null,
|
activeView: layout.previousView || 'chat',
|
||||||
});
|
previousView: null,
|
||||||
}, [saveLayout]);
|
});
|
||||||
|
} else {
|
||||||
|
// 显示终端,记住当前视图
|
||||||
|
const prev = layout.activeView as 'chat' | 'editor' | 'preview';
|
||||||
|
saveLayout({
|
||||||
|
activeView: 'terminal',
|
||||||
|
isTerminalOpen: true,
|
||||||
|
previousView: prev,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [layout.activeView, layout.previousView, saveLayout]);
|
||||||
|
|
||||||
// 关闭终端
|
// 关闭终端(恢复之前的视图)
|
||||||
const closeTerminal = useCallback(() => {
|
const closeTerminal = useCallback(() => {
|
||||||
saveLayout({
|
saveLayout({
|
||||||
activeView: 'chat',
|
activeView: layout.previousView || 'chat',
|
||||||
|
previousView: null,
|
||||||
});
|
});
|
||||||
}, [saveLayout]);
|
}, [layout.previousView, saveLayout]);
|
||||||
|
|
||||||
// 切换终端最大化状态
|
// 切换终端最大化状态
|
||||||
const toggleTerminalMaximize = useCallback(() => {
|
const toggleTerminalMaximize = useCallback(() => {
|
||||||
|
Reference in New Issue
Block a user