文件管理器以及文件编辑器
This commit is contained in:
@@ -283,7 +283,8 @@ pub async fn get_file_tree(project_path: String) -> Result<Vec<FileNode>, String
|
||||
String::from(".DS_Store"),
|
||||
];
|
||||
|
||||
let root_node = read_directory_recursive(path, 0, 3, &ignore_patterns)
|
||||
// 增加最大深度为 10,以支持更深的文件夹结构
|
||||
let root_node = read_directory_recursive(path, 0, 10, &ignore_patterns)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
// Return children of root node if it has any
|
||||
|
@@ -151,12 +151,13 @@ export const FileEditorEnhanced: React.FC<FileEditorEnhancedProps> = ({
|
||||
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||
const [diagnostics, setDiagnostics] = useState<DiagnosticInfo[]>([]);
|
||||
const [showDiagnostics, setShowDiagnostics] = useState(true);
|
||||
const [theme, setTheme] = useState<'vs-dark-plus' | 'vs-dark' | 'vs' | 'hc-black'>('vs-dark-plus');
|
||||
const [theme, setTheme] = useState<'vs-dark' | 'vs' | 'hc-black'>('vs-dark');
|
||||
const [fontSize, setFontSize] = useState(14);
|
||||
const [minimap, setMinimap] = useState(true);
|
||||
const [wordWrap, setWordWrap] = useState<'on' | 'off'>('on');
|
||||
const [autoSave, setAutoSave] = useState(false);
|
||||
|
||||
const [cursorPosition, setCursorPosition] = useState({ line: 1, column: 1 });
|
||||
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null);
|
||||
const monacoRef = useRef<Monaco | null>(null);
|
||||
const autoSaveTimerRef = useRef<NodeJS.Timeout | null>(null);
|
||||
@@ -296,6 +297,14 @@ export const FileEditorEnhanced: React.FC<FileEditorEnhancedProps> = ({
|
||||
editorRef.current = editor;
|
||||
monacoRef.current = monaco;
|
||||
|
||||
// 监听光标位置变化
|
||||
editor.onDidChangeCursorPosition((e) => {
|
||||
setCursorPosition({
|
||||
line: e.position.lineNumber,
|
||||
column: e.position.column
|
||||
});
|
||||
});
|
||||
|
||||
// 初始化 Monaco 配置
|
||||
initializeMonaco();
|
||||
|
||||
@@ -467,6 +476,79 @@ export const FileEditorEnhanced: React.FC<FileEditorEnhancedProps> = ({
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
{/* 功能信息按钮 */}
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="ghost" size="sm">
|
||||
<Info className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" className="max-w-md p-4">
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<h4 className="font-semibold mb-1">🎨 语法高亮支持</h4>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
JavaScript, TypeScript, Python, Java, C++, C#, Go, Rust, Ruby, PHP, Swift, Kotlin, Dart, Scala, R, MATLAB, SQL, HTML, CSS, JSON, XML, YAML, Markdown 等 40+ 语言
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="font-semibold mb-1">🔧 代码格式化</h4>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
快捷键: Ctrl/Cmd + Shift + F<br/>
|
||||
支持: JS/TS (Prettier), Python (Black), Java, C/C++, Go (gofmt), Rust (rustfmt), HTML/CSS/JSON
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="font-semibold mb-1">💡 智能提示</h4>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
• 代码补全 (IntelliSense)<br/>
|
||||
• 参数提示<br/>
|
||||
• 悬浮文档<br/>
|
||||
• 快速修复建议<br/>
|
||||
• 重构建议
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="font-semibold mb-1">🔍 错误检查</h4>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
实时语法检查、类型检查 (TypeScript/Flow)、Linting (ESLint/TSLint)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="font-semibold mb-1">⚙️ 编辑器功能</h4>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
• 行号显示<br/>
|
||||
• 代码折叠<br/>
|
||||
• 括号匹配高亮<br/>
|
||||
• 多光标编辑<br/>
|
||||
• 列选择 (Alt + 鼠标)<br/>
|
||||
• 小地图导航<br/>
|
||||
• Sticky Scroll (固定显示上下文)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="font-semibold mb-1">⌨️ 快捷键</h4>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Ctrl/Cmd + S: 保存<br/>
|
||||
Ctrl/Cmd + Shift + F: 格式化<br/>
|
||||
Ctrl/Cmd + F: 查找<br/>
|
||||
Ctrl/Cmd + H: 替换<br/>
|
||||
Ctrl/Cmd + /: 注释<br/>
|
||||
F11: 全屏<br/>
|
||||
Alt + Shift + F: 格式化选中代码
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
{/* 设置菜单 */}
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
@@ -475,9 +557,6 @@ export const FileEditorEnhanced: React.FC<FileEditorEnhancedProps> = ({
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onClick={() => setTheme('vs-dark-plus')}>
|
||||
主题: VS Dark+
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => setTheme('vs-dark')}>
|
||||
主题: VS Dark
|
||||
</DropdownMenuItem>
|
||||
@@ -624,8 +703,10 @@ export const FileEditorEnhanced: React.FC<FileEditorEnhancedProps> = ({
|
||||
options={{
|
||||
fontSize: fontSize,
|
||||
minimap: { enabled: minimap },
|
||||
lineNumbers: "on",
|
||||
rulers: [80, 120],
|
||||
lineNumbers: "on", // 显示行号
|
||||
lineNumbersMinChars: 5, // 行号最小宽度,增加到 5 以确保显示
|
||||
renderLineHighlight: "all", // 高亮当前行
|
||||
glyphMargin: true, // 显示字形边距(用于断点等)
|
||||
wordWrap: wordWrap,
|
||||
scrollBeyondLastLine: false,
|
||||
automaticLayout: true,
|
||||
@@ -660,6 +741,7 @@ export const FileEditorEnhanced: React.FC<FileEditorEnhancedProps> = ({
|
||||
<div className="flex items-center gap-4">
|
||||
<span>{language.toUpperCase()}</span>
|
||||
<span>UTF-8</span>
|
||||
<span>行 {cursorPosition.line}, 列 {cursorPosition.column}</span>
|
||||
<span>LF</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
|
@@ -102,11 +102,14 @@ const getFileIcon = (filename: string) => {
|
||||
return iconMap[ext || ""] || <File className="h-4 w-4 text-muted-foreground" />;
|
||||
};
|
||||
|
||||
// 组织文件到文件夹结构
|
||||
// 组织文件到文件夹结构(改进版,支持更深层级)
|
||||
const organizeFilesByFolder = (files: FileNode[]): Map<string, FileNode[]> => {
|
||||
const folderMap = new Map<string, FileNode[]>();
|
||||
|
||||
const processNode = (node: FileNode, parentPath: string = "") => {
|
||||
const processNode = (node: FileNode, parentPath: string = "", depth: number = 0) => {
|
||||
// 限制最大深度为 10 层
|
||||
if (depth > 10) return;
|
||||
|
||||
const currentPath = parentPath || "根目录";
|
||||
|
||||
if (node.file_type === "file") {
|
||||
@@ -116,17 +119,18 @@ const organizeFilesByFolder = (files: FileNode[]): Map<string, FileNode[]> => {
|
||||
folderMap.get(currentPath)!.push(node);
|
||||
} else {
|
||||
const folderPath = parentPath ? `${parentPath}/${node.name}` : node.name;
|
||||
// 创建文件夹条目,即使它没有直接包含文件
|
||||
if (!folderMap.has(folderPath)) {
|
||||
folderMap.set(folderPath, []);
|
||||
}
|
||||
|
||||
if (node.children) {
|
||||
node.children.forEach(child => processNode(child, folderPath));
|
||||
if (node.children && node.children.length > 0) {
|
||||
node.children.forEach(child => processNode(child, folderPath, depth + 1));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
files.forEach(node => processNode(node));
|
||||
files.forEach(node => processNode(node, "", 0));
|
||||
return folderMap;
|
||||
};
|
||||
|
||||
@@ -318,11 +322,8 @@ export const FileExplorerPanelEnhanced: React.FC<FileExplorerPanelEnhancedProps>
|
||||
setFileTree(tree);
|
||||
setFilteredTree(tree);
|
||||
|
||||
// 默认展开第一层目录
|
||||
const firstLevelDirs = tree
|
||||
.filter(node => node.file_type === 'directory')
|
||||
.map(node => node.path);
|
||||
setExpandedNodes(new Set(firstLevelDirs));
|
||||
// 不默认展开任何目录,让用户手动展开或使用展开按钮
|
||||
setExpandedNodes(new Set());
|
||||
} catch (err) {
|
||||
console.error("Failed to load file tree:", err);
|
||||
setError(err instanceof Error ? err.message : "Failed to load file tree");
|
||||
@@ -492,11 +493,12 @@ export const FileExplorerPanelEnhanced: React.FC<FileExplorerPanelEnhancedProps>
|
||||
}
|
||||
}, [onFileSelect, toggleExpand, lastClickTime, lastClickPath, handleOpenFile]);
|
||||
|
||||
// 渲染文件节点
|
||||
// 渲染文件节点(优化深层目录显示)
|
||||
const renderFileNode = (node: FileNode, depth = 0) => {
|
||||
const isExpanded = expandedNodes.has(node.path);
|
||||
const isSelected = selectedPath === node.path;
|
||||
const isDirectory = node.file_type === 'directory';
|
||||
const hasChildren = node.children && node.children.length > 0;
|
||||
|
||||
// 计算显示的路径(处理长路径)
|
||||
const displayName = node.name.length > 30
|
||||
@@ -513,10 +515,10 @@ export const FileExplorerPanelEnhanced: React.FC<FileExplorerPanelEnhancedProps>
|
||||
isSelected && "bg-accent",
|
||||
"select-none"
|
||||
)}
|
||||
style={{ paddingLeft: `${depth * 16 + 8}px` }}
|
||||
style={{ paddingLeft: `${Math.min(depth * 16 + 8, 200)}px` }} // 限制最大缩进
|
||||
onClick={() => handleFileClick(node)}
|
||||
>
|
||||
{isDirectory && (
|
||||
{isDirectory && hasChildren && (
|
||||
<div className="w-4 h-4 flex items-center justify-center">
|
||||
{isExpanded ? (
|
||||
<ChevronDown className="h-3 w-3" />
|
||||
@@ -525,6 +527,9 @@ export const FileExplorerPanelEnhanced: React.FC<FileExplorerPanelEnhancedProps>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{isDirectory && !hasChildren && (
|
||||
<div className="w-4 h-4" /> // 空文件夹的占位符
|
||||
)}
|
||||
|
||||
{isDirectory ? (
|
||||
isExpanded ? (
|
||||
|
@@ -449,7 +449,7 @@ export const GitPanelEnhanced: React.FC<GitPanelEnhancedProps> = ({
|
||||
}, [gitStatus]);
|
||||
|
||||
|
||||
// 渲染文件树节点
|
||||
// 渲染文件树节点(优化深层目录显示)
|
||||
const renderFileTreeNode = (node: FileTreeNode, depth = 0, statusType: 'modified' | 'staged' | 'untracked' | 'conflicted') => {
|
||||
const isExpanded = node.type === 'directory' && expandedNodes.has(node.path);
|
||||
const isDirectory = node.type === 'directory';
|
||||
@@ -463,7 +463,7 @@ export const GitPanelEnhanced: React.FC<GitPanelEnhancedProps> = ({
|
||||
"flex items-center gap-1 px-2 py-1 hover:bg-accent rounded-sm cursor-pointer group",
|
||||
isSelected && "bg-accent"
|
||||
)}
|
||||
style={{ paddingLeft: `${depth * 16 + 8}px` }}
|
||||
style={{ paddingLeft: `${Math.min(depth * 16 + 8, 200)}px` }} // 限制最大缩进
|
||||
onClick={() => {
|
||||
setSelectedPath(node.path);
|
||||
if (isDirectory) {
|
||||
@@ -482,6 +482,9 @@ export const GitPanelEnhanced: React.FC<GitPanelEnhancedProps> = ({
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{isDirectory && !hasChildren && (
|
||||
<div className="w-4 h-4" /> // 空文件夹的占位符
|
||||
)}
|
||||
|
||||
{isDirectory ? (
|
||||
isExpanded ? (
|
||||
|
Reference in New Issue
Block a user