diff --git a/src/components/CommandSuggestPanel.js b/src/components/CommandSuggestPanel.js
new file mode 100644
index 0000000..c3ac02f
--- /dev/null
+++ b/src/components/CommandSuggestPanel.js
@@ -0,0 +1,288 @@
+import React, { useState, useEffect, useRef, useMemo } from 'react';
+import { motion, AnimatePresence } from 'framer-motion';
+import { FiTerminal, FiX, FiCommand, FiFolder, FiFile, FiServer, FiDatabase, FiPackage, FiSettings, FiSearch } from 'react-icons/fi';
+
+// 常用命令库
+const COMMAND_DATABASE = [
+ // 文件操作
+ { cmd: 'ls', desc: '列出目录内容', category: 'file' },
+ { cmd: 'ls -la', desc: '详细列出所有文件', category: 'file' },
+ { cmd: 'ls -lh', desc: '人类可读格式', category: 'file' },
+ { cmd: 'cd', desc: '切换目录', category: 'file' },
+ { cmd: 'cd ..', desc: '返回上级目录', category: 'file' },
+ { cmd: 'cd ~', desc: '返回主目录', category: 'file' },
+ { cmd: 'cd -', desc: '返回上次目录', category: 'file' },
+ { cmd: 'pwd', desc: '显示当前目录', category: 'file' },
+ { cmd: 'mkdir', desc: '创建目录', category: 'file' },
+ { cmd: 'mkdir -p', desc: '递归创建目录', category: 'file' },
+ { cmd: 'rm', desc: '删除文件', category: 'file' },
+ { cmd: 'rm -rf', desc: '强制递归删除', category: 'file' },
+ { cmd: 'cp', desc: '复制文件', category: 'file' },
+ { cmd: 'cp -r', desc: '递归复制目录', category: 'file' },
+ { cmd: 'mv', desc: '移动/重命名', category: 'file' },
+ { cmd: 'touch', desc: '创建空文件', category: 'file' },
+ { cmd: 'cat', desc: '查看文件内容', category: 'file' },
+ { cmd: 'head -n 20', desc: '查看前20行', category: 'file' },
+ { cmd: 'tail -f', desc: '实时追踪日志', category: 'file' },
+ { cmd: 'less', desc: '分页查看文件', category: 'file' },
+ { cmd: 'find . -name', desc: '按名称查找', category: 'file' },
+ { cmd: 'chmod 755', desc: '设置可执行权限', category: 'file' },
+ { cmd: 'chown', desc: '修改所有者', category: 'file' },
+ { cmd: 'tar -zxvf', desc: '解压 tar.gz', category: 'file' },
+ { cmd: 'tar -zcvf', desc: '压缩为 tar.gz', category: 'file' },
+ { cmd: 'unzip', desc: '解压 zip', category: 'file' },
+
+ // 文本处理
+ { cmd: 'grep', desc: '文本搜索', category: 'text' },
+ { cmd: 'grep -rn', desc: '递归搜索带行号', category: 'text' },
+ { cmd: 'sed', desc: '流编辑器', category: 'text' },
+ { cmd: 'awk', desc: '文本处理', category: 'text' },
+ { cmd: 'sort', desc: '排序', category: 'text' },
+ { cmd: 'uniq -c', desc: '去重并计数', category: 'text' },
+ { cmd: 'wc -l', desc: '统计行数', category: 'text' },
+
+ // 系统
+ { cmd: 'top', desc: '系统监控', category: 'system' },
+ { cmd: 'htop', desc: '增强版 top', category: 'system' },
+ { cmd: 'ps aux', desc: '查看所有进程', category: 'system' },
+ { cmd: 'kill -9', desc: '强制终止进程', category: 'system' },
+ { cmd: 'df -h', desc: '磁盘使用情况', category: 'system' },
+ { cmd: 'du -sh *', desc: '当前目录各项大小', category: 'system' },
+ { cmd: 'free -h', desc: '内存使用情况', category: 'system' },
+ { cmd: 'uptime', desc: '运行时间', category: 'system' },
+ { cmd: 'uname -a', desc: '系统信息', category: 'system' },
+ { cmd: 'whoami', desc: '当前用户', category: 'system' },
+
+ // 网络
+ { cmd: 'ping -c 4', desc: '测试连通性', category: 'network' },
+ { cmd: 'curl -I', desc: '获取响应头', category: 'network' },
+ { cmd: 'wget', desc: '下载文件', category: 'network' },
+ { cmd: 'netstat -tunlp', desc: '监听端口', category: 'network' },
+ { cmd: 'ss -tunlp', desc: '套接字统计', category: 'network' },
+ { cmd: 'ip addr', desc: 'IP 地址', category: 'network' },
+
+ // 包管理
+ { cmd: 'apt update', desc: '更新软件源', category: 'package' },
+ { cmd: 'apt install', desc: '安装软件', category: 'package' },
+ { cmd: 'yum install', desc: 'RHEL 安装', category: 'package' },
+ { cmd: 'npm install', desc: 'Node 安装', category: 'package' },
+ { cmd: 'pip install', desc: 'Python 安装', category: 'package' },
+
+ // 服务
+ { cmd: 'systemctl status', desc: '服务状态', category: 'service' },
+ { cmd: 'systemctl start', desc: '启动服务', category: 'service' },
+ { cmd: 'systemctl stop', desc: '停止服务', category: 'service' },
+ { cmd: 'systemctl restart', desc: '重启服务', category: 'service' },
+ { cmd: 'journalctl -f', desc: '实时日志', category: 'service' },
+
+ // Docker
+ { cmd: 'docker ps', desc: '运行中容器', category: 'docker' },
+ { cmd: 'docker ps -a', desc: '所有容器', category: 'docker' },
+ { cmd: 'docker images', desc: '镜像列表', category: 'docker' },
+ { cmd: 'docker exec -it', desc: '进入容器', category: 'docker' },
+ { cmd: 'docker logs -f', desc: '查看日志', category: 'docker' },
+ { cmd: 'docker-compose up -d', desc: '启动服务', category: 'docker' },
+ { cmd: 'docker-compose down', desc: '停止服务', category: 'docker' },
+
+ // Git
+ { cmd: 'git status', desc: '查看状态', category: 'git' },
+ { cmd: 'git add .', desc: '添加所有文件', category: 'git' },
+ { cmd: 'git commit -m', desc: '提交', category: 'git' },
+ { cmd: 'git push', desc: '推送', category: 'git' },
+ { cmd: 'git pull', desc: '拉取', category: 'git' },
+ { cmd: 'git log --oneline', desc: '简洁日志', category: 'git' },
+
+ // 其他
+ { cmd: 'clear', desc: '清屏', category: 'other' },
+ { cmd: 'history', desc: '命令历史', category: 'other' },
+ { cmd: 'vim', desc: '文本编辑器', category: 'other' },
+ { cmd: 'nano', desc: '简易编辑器', category: 'other' },
+ { cmd: 'exit', desc: '退出终端', category: 'other' },
+];
+
+// 获取分类图标
+const getCategoryIcon = (category) => {
+ switch (category) {
+ case 'file': return FiFolder;
+ case 'text': return FiFile;
+ case 'system': return FiServer;
+ case 'network': return FiDatabase;
+ case 'package': return FiPackage;
+ case 'service': return FiSettings;
+ case 'docker': return FiPackage;
+ case 'git': return FiSearch;
+ default: return FiTerminal;
+ }
+};
+
+function CommandSuggestPanel({ visible, input, onSelect, onClose, onOpenCommandPalette, disabled }) {
+ const [selectedIndex, setSelectedIndex] = useState(0);
+ const [searchText, setSearchText] = useState('');
+ const listRef = useRef(null);
+ const selectedRef = useRef(null);
+
+ // 使用搜索框或输入内容过滤
+ const filterText = searchText || input || '';
+
+ // 过滤命令
+ const suggestions = useMemo(() => {
+ if (!filterText) return COMMAND_DATABASE.slice(0, 20);
+
+ const query = filterText.toLowerCase().trim();
+ return COMMAND_DATABASE.filter(item =>
+ item.cmd.toLowerCase().includes(query) ||
+ item.desc.toLowerCase().includes(query)
+ ).slice(0, 20);
+ }, [filterText]);
+
+ // 重置选中索引
+ useEffect(() => {
+ setSelectedIndex(0);
+ }, [filterText]);
+
+ // 滚动到选中项
+ useEffect(() => {
+ if (selectedRef.current && listRef.current) {
+ selectedRef.current.scrollIntoView({ block: 'nearest' });
+ }
+ }, [selectedIndex]);
+
+ // 键盘导航
+ useEffect(() => {
+ if (!visible) return;
+
+ const handleKeyDown = (e) => {
+ if (e.key === 'ArrowDown') {
+ e.preventDefault();
+ setSelectedIndex(prev => (prev + 1) % suggestions.length);
+ } else if (e.key === 'ArrowUp') {
+ e.preventDefault();
+ setSelectedIndex(prev => (prev - 1 + suggestions.length) % suggestions.length);
+ } else if (e.key === 'Tab' && suggestions[selectedIndex]) {
+ e.preventDefault();
+ onSelect(suggestions[selectedIndex].cmd);
+ }
+ };
+
+ window.addEventListener('keydown', handleKeyDown);
+ return () => window.removeEventListener('keydown', handleKeyDown);
+ }, [visible, suggestions, selectedIndex, onSelect]);
+
+ // 同步输入到搜索框
+ useEffect(() => {
+ if (input && !searchText) {
+ // 如果有终端输入且搜索框为空,显示终端输入的内容
+ }
+ }, [input, searchText]);
+
+ if (!visible) return null;
+
+ return (
+
+
+ {/* 头部 */}
+
+
+ {/* 搜索框 */}
+
+
+
+ setSearchText(e.target.value)}
+ placeholder={input ? `当前: ${input}` : "搜索命令..."}
+ className="w-full pl-9 pr-3 py-2 bg-shell-card/50 border border-shell-border/50 rounded-lg
+ text-sm text-shell-text placeholder-shell-text-dim focus:outline-none
+ focus:border-shell-accent/50 transition-colors"
+ />
+
+
+
+ {/* 命令列表 */}
+
+ {disabled ? (
+
+ 请先连接服务器
+
+ ) : suggestions.length === 0 ? (
+
+ 未找到匹配的命令
+
+ ) : (
+ suggestions.map((item, index) => {
+ const Icon = getCategoryIcon(item.category);
+ const isSelected = index === selectedIndex;
+
+ return (
+
!disabled && onSelect(item.cmd)}
+ className={`
+ px-3 py-2 cursor-pointer flex items-center gap-3 transition-all border-l-2
+ ${isSelected
+ ? 'bg-shell-accent/10 border-shell-accent'
+ : 'border-transparent hover:bg-shell-card/30'
+ }
+ ${disabled ? 'opacity-50 cursor-not-allowed' : ''}
+ `}
+ >
+
+
+
+ {item.cmd}
+
+
+ {item.desc}
+
+
+
+ );
+ })
+ )}
+
+
+ {/* 底部按钮 */}
+
+
+ ↑↓ 导航 · Tab 补全 · 点击选择
+
+
+
+
+
+ );
+}
+
+export default CommandSuggestPanel;
diff --git a/src/components/CommandSuggestions.js b/src/components/CommandSuggestions.js
deleted file mode 100644
index 0cd9e26..0000000
--- a/src/components/CommandSuggestions.js
+++ /dev/null
@@ -1,373 +0,0 @@
-import React, { useEffect, useRef, useState } from 'react';
-import { motion, AnimatePresence } from 'framer-motion';
-import { FiTerminal, FiFolder, FiFile, FiSettings, FiSearch, FiPackage, FiServer, FiDatabase } from 'react-icons/fi';
-
-// 常用 Linux/Unix 命令库 (不显示参数占位符)
-const COMMAND_DATABASE = [
- // 文件操作
- { cmd: 'ls', desc: '列出目录内容', category: 'file' },
- { cmd: 'ls -la', desc: '详细列出所有文件', category: 'file' },
- { cmd: 'ls -lh', desc: '人类可读格式', category: 'file' },
- { cmd: 'cd', desc: '切换目录', category: 'file' },
- { cmd: 'cd ..', desc: '返回上级目录', category: 'file' },
- { cmd: 'cd ~', desc: '返回主目录', category: 'file' },
- { cmd: 'cd -', desc: '返回上次目录', category: 'file' },
- { cmd: 'pwd', desc: '显示当前目录', category: 'file' },
- { cmd: 'mkdir', desc: '创建目录', category: 'file' },
- { cmd: 'mkdir -p', desc: '递归创建目录', category: 'file' },
- { cmd: 'rmdir', desc: '删除空目录', category: 'file' },
- { cmd: 'rm', desc: '删除文件', category: 'file' },
- { cmd: 'rm -rf', desc: '强制递归删除', category: 'file' },
- { cmd: 'cp', desc: '复制文件', category: 'file' },
- { cmd: 'cp -r', desc: '递归复制目录', category: 'file' },
- { cmd: 'mv', desc: '移动/重命名', category: 'file' },
- { cmd: 'touch', desc: '创建空文件', category: 'file' },
- { cmd: 'cat', desc: '查看文件内容', category: 'file' },
- { cmd: 'head', desc: '查看文件头部', category: 'file' },
- { cmd: 'head -n 20', desc: '查看前20行', category: 'file' },
- { cmd: 'tail', desc: '查看文件尾部', category: 'file' },
- { cmd: 'tail -f', desc: '实时追踪日志', category: 'file' },
- { cmd: 'less', desc: '分页查看文件', category: 'file' },
- { cmd: 'more', desc: '分页显示文件', category: 'file' },
- { cmd: 'find', desc: '查找文件', category: 'file' },
- { cmd: 'find . -name', desc: '按名称查找', category: 'file' },
- { cmd: 'locate', desc: '快速定位文件', category: 'file' },
- { cmd: 'chmod', desc: '修改权限', category: 'file' },
- { cmd: 'chmod 755', desc: '设置可执行权限', category: 'file' },
- { cmd: 'chmod 644', desc: '设置只读权限', category: 'file' },
- { cmd: 'chown', desc: '修改所有者', category: 'file' },
- { cmd: 'ln -s', desc: '创建软链接', category: 'file' },
- { cmd: 'tar', desc: '打包文件', category: 'file' },
- { cmd: 'tar -zxvf', desc: '解压 tar.gz', category: 'file' },
- { cmd: 'tar -zcvf', desc: '压缩为 tar.gz', category: 'file' },
- { cmd: 'zip -r', desc: '压缩目录', category: 'file' },
- { cmd: 'unzip', desc: '解压 zip', category: 'file' },
- { cmd: 'gzip', desc: '压缩文件', category: 'file' },
- { cmd: 'gunzip', desc: '解压 gz', category: 'file' },
-
- // 文本处理
- { cmd: 'grep', desc: '文本搜索', category: 'text' },
- { cmd: 'grep -rn', desc: '递归搜索带行号', category: 'text' },
- { cmd: 'grep -i', desc: '忽略大小写搜索', category: 'text' },
- { cmd: 'sed', desc: '流编辑器', category: 'text' },
- { cmd: 'awk', desc: '文本处理', category: 'text' },
- { cmd: 'sort', desc: '排序', category: 'text' },
- { cmd: 'sort -n', desc: '数字排序', category: 'text' },
- { cmd: 'uniq', desc: '去重', category: 'text' },
- { cmd: 'uniq -c', desc: '去重并计数', category: 'text' },
- { cmd: 'wc -l', desc: '统计行数', category: 'text' },
- { cmd: 'wc -w', desc: '统计单词数', category: 'text' },
- { cmd: 'cut', desc: '切割文本', category: 'text' },
- { cmd: 'diff', desc: '比较文件', category: 'text' },
- { cmd: 'echo', desc: '输出文本', category: 'text' },
- { cmd: 'printf', desc: '格式化输出', category: 'text' },
-
- // 系统信息
- { cmd: 'top', desc: '系统监控', category: 'system' },
- { cmd: 'htop', desc: '增强版 top', category: 'system' },
- { cmd: 'ps aux', desc: '查看所有进程', category: 'system' },
- { cmd: 'ps -ef', desc: '进程树', category: 'system' },
- { cmd: 'kill', desc: '终止进程', category: 'system' },
- { cmd: 'kill -9', desc: '强制终止进程', category: 'system' },
- { cmd: 'killall', desc: '按名称终止', category: 'system' },
- { cmd: 'df -h', desc: '磁盘使用情况', category: 'system' },
- { cmd: 'du -sh', desc: '目录大小', category: 'system' },
- { cmd: 'du -sh *', desc: '当前目录各项大小', category: 'system' },
- { cmd: 'free -h', desc: '内存使用情况', category: 'system' },
- { cmd: 'uptime', desc: '运行时间', category: 'system' },
- { cmd: 'uname -a', desc: '系统信息', category: 'system' },
- { cmd: 'hostname', desc: '主机名', category: 'system' },
- { cmd: 'whoami', desc: '当前用户', category: 'system' },
- { cmd: 'id', desc: '用户信息', category: 'system' },
- { cmd: 'w', desc: '登录用户', category: 'system' },
- { cmd: 'last', desc: '登录历史', category: 'system' },
- { cmd: 'dmesg | tail', desc: '内核消息', category: 'system' },
- { cmd: 'lsof', desc: '打开的文件', category: 'system' },
- { cmd: 'lsof -i', desc: '网络连接', category: 'system' },
- { cmd: 'lscpu', desc: 'CPU 信息', category: 'system' },
- { cmd: 'lsmem', desc: '内存信息', category: 'system' },
- { cmd: 'lsblk', desc: '块设备', category: 'system' },
- { cmd: 'fdisk -l', desc: '磁盘分区', category: 'system' },
-
- // 网络
- { cmd: 'ping', desc: '测试连通性', category: 'network' },
- { cmd: 'ping -c 4', desc: '发送4次ping', category: 'network' },
- { cmd: 'curl', desc: 'HTTP 请求', category: 'network' },
- { cmd: 'curl -I', desc: '获取响应头', category: 'network' },
- { cmd: 'curl -O', desc: '下载文件', category: 'network' },
- { cmd: 'wget', desc: '下载文件', category: 'network' },
- { cmd: 'ssh', desc: 'SSH 连接', category: 'network' },
- { cmd: 'scp', desc: '安全复制', category: 'network' },
- { cmd: 'rsync -avz', desc: '同步文件', category: 'network' },
- { cmd: 'netstat -tunlp', desc: '监听端口', category: 'network' },
- { cmd: 'ss -tunlp', desc: '套接字统计', category: 'network' },
- { cmd: 'ip addr', desc: 'IP 地址', category: 'network' },
- { cmd: 'ip route', desc: '路由表', category: 'network' },
- { cmd: 'ifconfig', desc: '网络接口', category: 'network' },
- { cmd: 'route -n', desc: '路由表', category: 'network' },
- { cmd: 'iptables -L -n', desc: '防火墙规则', category: 'network' },
- { cmd: 'nslookup', desc: 'DNS 查询', category: 'network' },
- { cmd: 'dig', desc: 'DNS 详细查询', category: 'network' },
- { cmd: 'traceroute', desc: '路由追踪', category: 'network' },
- { cmd: 'tcpdump -i', desc: '抓包', category: 'network' },
-
- // 包管理
- { cmd: 'apt update', desc: '更新软件源', category: 'package' },
- { cmd: 'apt upgrade', desc: '升级软件包', category: 'package' },
- { cmd: 'apt install', desc: '安装软件', category: 'package' },
- { cmd: 'apt remove', desc: '卸载软件', category: 'package' },
- { cmd: 'apt search', desc: '搜索软件', category: 'package' },
- { cmd: 'yum install', desc: 'RHEL 安装', category: 'package' },
- { cmd: 'yum update', desc: 'RHEL 更新', category: 'package' },
- { cmd: 'dnf install', desc: 'Fedora 安装', category: 'package' },
- { cmd: 'pacman -S', desc: 'Arch 安装', category: 'package' },
- { cmd: 'pacman -Syu', desc: 'Arch 更新', category: 'package' },
- { cmd: 'npm install', desc: 'Node 安装', category: 'package' },
- { cmd: 'npm run', desc: 'Node 运行脚本', category: 'package' },
- { cmd: 'pip install', desc: 'Python 安装', category: 'package' },
- { cmd: 'pip3 install', desc: 'Python3 安装', category: 'package' },
-
- // 服务管理
- { cmd: 'systemctl status', desc: '服务状态', category: 'service' },
- { cmd: 'systemctl start', desc: '启动服务', category: 'service' },
- { cmd: 'systemctl stop', desc: '停止服务', category: 'service' },
- { cmd: 'systemctl restart', desc: '重启服务', category: 'service' },
- { cmd: 'systemctl enable', desc: '开机启动', category: 'service' },
- { cmd: 'systemctl disable', desc: '禁止开机启动', category: 'service' },
- { cmd: 'journalctl -f', desc: '实时日志', category: 'service' },
- { cmd: 'journalctl -u', desc: '服务日志', category: 'service' },
-
- // Docker
- { cmd: 'docker ps', desc: '运行中容器', category: 'docker' },
- { cmd: 'docker ps -a', desc: '所有容器', category: 'docker' },
- { cmd: 'docker images', desc: '镜像列表', category: 'docker' },
- { cmd: 'docker run', desc: '运行容器', category: 'docker' },
- { cmd: 'docker exec -it', desc: '进入容器', category: 'docker' },
- { cmd: 'docker logs -f', desc: '查看日志', category: 'docker' },
- { cmd: 'docker stop', desc: '停止容器', category: 'docker' },
- { cmd: 'docker rm', desc: '删除容器', category: 'docker' },
- { cmd: 'docker rmi', desc: '删除镜像', category: 'docker' },
- { cmd: 'docker-compose up -d', desc: '启动服务', category: 'docker' },
- { cmd: 'docker-compose down', desc: '停止服务', category: 'docker' },
- { cmd: 'docker-compose logs -f', desc: '查看日志', category: 'docker' },
-
- // Git
- { cmd: 'git status', desc: '查看状态', category: 'git' },
- { cmd: 'git add .', desc: '添加所有文件', category: 'git' },
- { cmd: 'git commit -m', desc: '提交', category: 'git' },
- { cmd: 'git push', desc: '推送', category: 'git' },
- { cmd: 'git pull', desc: '拉取', category: 'git' },
- { cmd: 'git log --oneline', desc: '简洁日志', category: 'git' },
- { cmd: 'git diff', desc: '查看差异', category: 'git' },
- { cmd: 'git branch', desc: '分支列表', category: 'git' },
- { cmd: 'git checkout', desc: '切换分支', category: 'git' },
- { cmd: 'git merge', desc: '合并分支', category: 'git' },
-
- // 其他
- { cmd: 'clear', desc: '清屏', category: 'other' },
- { cmd: 'history', desc: '命令历史', category: 'other' },
- { cmd: 'alias', desc: '别名列表', category: 'other' },
- { cmd: 'which', desc: '命令路径', category: 'other' },
- { cmd: 'whereis', desc: '二进制位置', category: 'other' },
- { cmd: 'man', desc: '查看手册', category: 'other' },
- { cmd: 'date', desc: '日期时间', category: 'other' },
- { cmd: 'cal', desc: '日历', category: 'other' },
- { cmd: 'env', desc: '环境变量', category: 'other' },
- { cmd: 'export', desc: '设置变量', category: 'other' },
- { cmd: 'source', desc: '执行脚本', category: 'other' },
- { cmd: 'crontab -l', desc: '查看定时任务', category: 'other' },
- { cmd: 'crontab -e', desc: '编辑定时任务', category: 'other' },
- { cmd: 'nohup', desc: '后台运行', category: 'other' },
- { cmd: 'screen -S', desc: '创建会话', category: 'other' },
- { cmd: 'screen -r', desc: '恢复会话', category: 'other' },
- { cmd: 'tmux new -s', desc: '创建会话', category: 'other' },
- { cmd: 'tmux attach -t', desc: '恢复会话', category: 'other' },
- { cmd: 'vim', desc: '文本编辑器', category: 'other' },
- { cmd: 'nano', desc: '简易编辑器', category: 'other' },
- { cmd: 'vi', desc: 'Vi 编辑器', category: 'other' },
- { cmd: 'exit', desc: '退出终端', category: 'other' },
- { cmd: 'reboot', desc: '重启系统', category: 'other' },
- { cmd: 'shutdown -h now', desc: '立即关机', category: 'other' },
-];
-
-// 获取分类图标
-const getCategoryIcon = (category) => {
- switch (category) {
- case 'file': return FiFolder;
- case 'text': return FiFile;
- case 'system': return FiServer;
- case 'network': return FiDatabase;
- case 'package': return FiPackage;
- case 'service': return FiSettings;
- case 'docker': return FiPackage;
- case 'git': return FiSearch;
- default: return FiTerminal;
- }
-};
-
-function CommandSuggestions({ input, onSelect, onClose, visible, cursorPosition, containerHeight }) {
- const [selectedIndex, setSelectedIndex] = useState(0);
- const listRef = useRef(null);
- const selectedRef = useRef(null);
- const suggestionsRef = useRef(null);
- const [showAbove, setShowAbove] = useState(false);
-
- // 根据输入过滤命令
- const suggestions = React.useMemo(() => {
- if (!input || input.length < 1) return [];
-
- const query = input.toLowerCase().trim();
-
- // 匹配以输入开头的命令
- const results = COMMAND_DATABASE.filter(item =>
- item.cmd.toLowerCase().startsWith(query) ||
- item.desc.toLowerCase().includes(query)
- );
-
- return results.slice(0, 8);
- }, [input]);
-
- // 重置选中索引
- useEffect(() => {
- setSelectedIndex(0);
- }, [input]);
-
- // 滚动到选中项
- useEffect(() => {
- if (selectedRef.current && listRef.current) {
- selectedRef.current.scrollIntoView({ block: 'nearest' });
- }
- }, [selectedIndex]);
-
- // 键盘导航
- useEffect(() => {
- if (!visible || suggestions.length === 0) return;
-
- const handleKeyDown = (e) => {
- switch (e.key) {
- case 'ArrowDown':
- e.preventDefault();
- e.stopPropagation();
- setSelectedIndex(prev => (prev + 1) % suggestions.length);
- break;
- case 'ArrowUp':
- e.preventDefault();
- e.stopPropagation();
- setSelectedIndex(prev => (prev - 1 + suggestions.length) % suggestions.length);
- break;
- case 'Tab':
- // 只用 Tab 选择
- if (suggestions[selectedIndex]) {
- e.preventDefault();
- e.stopPropagation();
- onSelect(suggestions[selectedIndex].cmd);
- }
- break;
- case 'Escape':
- e.preventDefault();
- onClose();
- break;
- default:
- break;
- }
- };
-
- window.addEventListener('keydown', handleKeyDown, true);
- return () => window.removeEventListener('keydown', handleKeyDown, true);
- }, [visible, suggestions, selectedIndex, onSelect, onClose]);
-
- if (!visible || suggestions.length === 0) return null;
-
- // 估算提示框高度(标题 + 每项约28px,最多8项)
- const estimatedHeight = 28 + Math.min(suggestions.length, 8) * 28;
-
- // 计算提示框位置,智能判断显示在光标上方还是下方
- let positionStyle = {};
-
- if (cursorPosition) {
- const spaceBelow = (containerHeight || 600) - cursorPosition.top - 20;
- const shouldShowAbove = spaceBelow < estimatedHeight && cursorPosition.top > estimatedHeight;
-
- if (shouldShowAbove) {
- // 显示在光标上方
- positionStyle = {
- bottom: (containerHeight || 600) - cursorPosition.top + 5,
- left: Math.max(10, Math.min(cursorPosition.left, 200)),
- };
- } else {
- // 显示在光标下方
- positionStyle = {
- top: cursorPosition.top + 20,
- left: Math.max(10, Math.min(cursorPosition.left, 200)),
- };
- }
- } else {
- positionStyle = {
- bottom: 60,
- left: 16,
- };
- }
-
- return (
-
-
- {/* 简化的标题栏 */}
-
-
- ↑↓ 导航
-
-
- Tab 补全
-
-
-
- {/* 建议列表 */}
-
- {suggestions.map((item, index) => {
- const Icon = getCategoryIcon(item.category);
- const isSelected = index === selectedIndex;
-
- return (
-
onSelect(item.cmd)}
- className={`
- px-2 py-1.5 cursor-pointer flex items-center gap-2 transition-all
- ${isSelected
- ? 'bg-shell-accent/20 border-l-2 border-shell-accent'
- : 'hover:bg-shell-card/50 border-l-2 border-transparent'
- }
- `}
- >
-
-
-
- {item.cmd}
-
-
- {item.desc}
-
-
-
- );
- })}
-
-
-
- );
-}
-
-export default CommandSuggestions;
diff --git a/src/components/Terminal.js b/src/components/Terminal.js
index 4a701d7..9836b22 100644
--- a/src/components/Terminal.js
+++ b/src/components/Terminal.js
@@ -5,8 +5,8 @@ import { FitAddon } from '@xterm/addon-fit';
import { WebLinksAddon } from '@xterm/addon-web-links';
import { WebglAddon } from '@xterm/addon-webgl';
import '@xterm/xterm/css/xterm.css';
-import { FiCommand, FiRefreshCw, FiInfo, FiFolder, FiActivity, FiZap } from 'react-icons/fi';
-import CommandSuggestions from './CommandSuggestions';
+import { FiCommand, FiRefreshCw, FiInfo, FiFolder, FiActivity, FiZap, FiTerminal, FiX } from 'react-icons/fi';
+import CommandSuggestPanel from './CommandSuggestPanel';
function Terminal({ tabId, hostId, isActive, onConnectionChange, onShowCommandPalette, onToggleInfoPanel, onOpenSFTP, showInfoPanel, onCloseTab }) {
const containerRef = useRef(null);
@@ -43,8 +43,6 @@ function Terminal({ tabId, hostId, isActive, onConnectionChange, onShowCommandPa
// 清除当前输入的内容(发送对应数量的退格)
const backspaces = '\b'.repeat(inputBufferRef.current.length);
- // 也发送删除字符来清除显示
- const deleteChars = ' '.repeat(inputBufferRef.current.length) + '\b'.repeat(inputBufferRef.current.length);
// 先清除当前输入
if (inputBufferRef.current.length > 0) {
@@ -57,16 +55,14 @@ function Terminal({ tabId, hostId, isActive, onConnectionChange, onShowCommandPa
// 更新状态
inputBufferRef.current = command;
setCurrentInput(command);
- setShowSuggestions(false);
// 重新聚焦终端
xtermRef.current?.focus();
}, []);
- // 关闭建议
- const handleCloseSuggestions = useCallback(() => {
- setShowSuggestions(false);
- xtermRef.current?.focus();
+ // 切换建议面板
+ const toggleSuggestPanel = useCallback(() => {
+ setShowSuggestPanel(prev => !prev);
}, []);
// 当标签页变为活动状态时,自动聚焦终端
@@ -82,8 +78,7 @@ function Terminal({ tabId, hostId, isActive, onConnectionChange, onShowCommandPa
const [connectionId, setConnectionId] = useState(null);
const [isConnecting, setIsConnecting] = useState(false);
const [currentInput, setCurrentInput] = useState('');
- const [showSuggestions, setShowSuggestions] = useState(false);
- const [cursorPosition, setCursorPosition] = useState(null);
+ const [showSuggestPanel, setShowSuggestPanel] = useState(false);
const inputBufferRef = useRef('');
const [error, setError] = useState(null);
const [isReady, setIsReady] = useState(false);
@@ -281,50 +276,23 @@ function Terminal({ tabId, hostId, isActive, onConnectionChange, onShowCommandPa
window.electronAPI.ssh.write(connectionIdRef.current, data);
}
- // 更新光标位置
- const updateCursorPosition = () => {
- if (terminalRef.current && xtermRef.current) {
- const cursorY = xtermRef.current.buffer.active.cursorY;
- const cursorX = xtermRef.current.buffer.active.cursorX;
- const cellWidth = terminalRef.current.offsetWidth / xtermRef.current.cols;
- const cellHeight = terminalRef.current.offsetHeight / xtermRef.current.rows;
-
- setCursorPosition({
- top: (cursorY + 1) * cellHeight + 60, // 加上工具栏高度
- left: cursorX * cellWidth + 16,
- });
- }
- };
-
- // 追踪用户输入以显示命令建议
+ // 追踪用户输入(用于右侧面板搜索)
if (data === '\r' || data === '\n') {
// 回车:清空输入缓冲区
inputBufferRef.current = '';
setCurrentInput('');
- setShowSuggestions(false);
} else if (data === '\x7f' || data === '\b') {
// 退格:删除最后一个字符
inputBufferRef.current = inputBufferRef.current.slice(0, -1);
setCurrentInput(inputBufferRef.current);
- setShowSuggestions(inputBufferRef.current.length > 0);
- updateCursorPosition();
- } else if (data === '\x1b' || data.startsWith('\x1b[')) {
- // ESC 或方向键:隐藏建议
- setShowSuggestions(false);
- } else if (data === '\t') {
- // Tab:如果有建议就不传递给终端
- // (Tab 补全由 CommandSuggestions 组件处理)
} else if (data.length === 1 && data.charCodeAt(0) >= 32) {
// 普通字符输入
inputBufferRef.current += data;
setCurrentInput(inputBufferRef.current);
- setShowSuggestions(inputBufferRef.current.length > 0);
- updateCursorPosition();
} else if (data === '\x03') {
// Ctrl+C:清空
inputBufferRef.current = '';
setCurrentInput('');
- setShowSuggestions(false);
}
});
@@ -575,14 +543,17 @@ function Terminal({ tabId, hostId, isActive, onConnectionChange, onShowCommandPa
{/* 右侧工具按钮 */}
- {/* 命令提示 */}
+ {/* 智能提示面板开关 */}
@@ -625,14 +596,14 @@ function Terminal({ tabId, hostId, isActive, onConnectionChange, onShowCommandPa
style={{ minHeight: '300px', minWidth: '400px' }}
/>
- {/* 命令提示 */}
-
setShowSuggestPanel(false)}
+ onOpenCommandPalette={onShowCommandPalette}
+ disabled={!connectionId}
/>
{/* 底部装饰线 */}