From 13f9e5b71c78c123a79c59723afa44556714951d Mon Sep 17 00:00:00 2001 From: Ethanfly Date: Mon, 5 Jan 2026 13:50:22 +0800 Subject: [PATCH] feat: optimize command suggestions - cursor position, Tab only selection, no placeholder args --- src/components/CommandSuggestions.js | 344 +++++++++++++++------------ src/components/Terminal.js | 20 +- 2 files changed, 206 insertions(+), 158 deletions(-) diff --git a/src/components/CommandSuggestions.js b/src/components/CommandSuggestions.js index 9ff544e..b742556 100644 --- a/src/components/CommandSuggestions.js +++ b/src/components/CommandSuggestions.js @@ -2,128 +2,192 @@ 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 命令库 +// 常用 Linux/Unix 命令库 (不显示参数占位符) const COMMAND_DATABASE = [ // 文件操作 - { cmd: 'ls', desc: '列出目录内容', category: 'file', args: '-la' }, - { cmd: 'cd', desc: '切换目录', category: 'file', args: '/path' }, + { 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', args: '-p dirname' }, - { cmd: 'rmdir', desc: '删除空目录', category: 'file', args: 'dirname' }, - { cmd: 'rm', desc: '删除文件/目录', category: 'file', args: '-rf path' }, - { cmd: 'cp', desc: '复制文件/目录', category: 'file', args: '-r src dst' }, - { cmd: 'mv', desc: '移动/重命名', category: 'file', args: 'src dst' }, - { cmd: 'touch', desc: '创建空文件', category: 'file', args: 'filename' }, - { cmd: 'cat', desc: '查看文件内容', category: 'file', args: 'filename' }, - { cmd: 'head', desc: '查看文件头部', category: 'file', args: '-n 20 file' }, - { cmd: 'tail', desc: '查看文件尾部', category: 'file', args: '-f logfile' }, - { cmd: 'less', desc: '分页查看文件', category: 'file', args: 'filename' }, - { cmd: 'more', desc: '分页显示文件', category: 'file', args: 'filename' }, - { cmd: 'find', desc: '查找文件', category: 'file', args: '. -name "*.log"' }, - { cmd: 'locate', desc: '快速定位文件', category: 'file', args: 'filename' }, - { cmd: 'chmod', desc: '修改权限', category: 'file', args: '755 file' }, - { cmd: 'chown', desc: '修改所有者', category: 'file', args: 'user:group file' }, - { cmd: 'ln', desc: '创建链接', category: 'file', args: '-s target link' }, - { cmd: 'tar', desc: '打包/解包', category: 'file', args: '-zxvf file.tar.gz' }, - { cmd: 'zip', desc: '压缩文件', category: 'file', args: '-r archive.zip dir' }, - { cmd: 'unzip', desc: '解压 zip', category: 'file', args: 'archive.zip' }, - { cmd: 'gzip', desc: '压缩文件', category: 'file', args: 'filename' }, - { cmd: 'gunzip', desc: '解压 gz', category: 'file', args: 'file.gz' }, + { 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', args: '-rn "pattern" .' }, - { cmd: 'sed', desc: '流编辑器', category: 'text', args: "'s/old/new/g' file" }, - { cmd: 'awk', desc: '文本处理', category: 'text', args: "'{print $1}' file" }, - { cmd: 'sort', desc: '排序', category: 'text', args: '-n file' }, - { cmd: 'uniq', desc: '去重', category: 'text', args: '-c file' }, - { cmd: 'wc', desc: '统计行/字/字符', category: 'text', args: '-l file' }, - { cmd: 'cut', desc: '切割文本', category: 'text', args: "-d':' -f1 file" }, - { cmd: 'diff', desc: '比较文件', category: 'text', args: 'file1 file2' }, - { cmd: 'echo', desc: '输出文本', category: 'text', args: '"Hello World"' }, - { cmd: 'printf', desc: '格式化输出', category: 'text', args: '"%s\\n" text' }, + { 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', desc: '查看进程', category: 'system', args: 'aux' }, - { cmd: 'kill', desc: '终止进程', category: 'system', args: '-9 PID' }, - { cmd: 'killall', desc: '按名称终止', category: 'system', args: 'process_name' }, - { cmd: 'df', desc: '磁盘使用', category: 'system', args: '-h' }, - { cmd: 'du', desc: '目录大小', category: 'system', args: '-sh *' }, - { cmd: 'free', desc: '内存使用', category: 'system', args: '-h' }, + { 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', desc: '系统信息', category: 'system', args: '-a' }, + { 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', desc: '内核消息', category: 'system', args: '| tail' }, - { cmd: 'lsof', desc: '打开的文件', category: 'system', args: '-i :80' }, + { 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', desc: '磁盘分区', category: 'system', args: '-l' }, + { cmd: 'fdisk -l', desc: '磁盘分区', category: 'system' }, // 网络 - { cmd: 'ping', desc: '测试连通性', category: 'network', args: '-c 4 host' }, - { cmd: 'curl', desc: 'HTTP 请求', category: 'network', args: '-I url' }, - { cmd: 'wget', desc: '下载文件', category: 'network', args: 'url' }, - { cmd: 'ssh', desc: 'SSH 连接', category: 'network', args: 'user@host' }, - { cmd: 'scp', desc: '安全复制', category: 'network', args: 'file user@host:path' }, - { cmd: 'rsync', desc: '同步文件', category: 'network', args: '-avz src dst' }, - { cmd: 'netstat', desc: '网络统计', category: 'network', args: '-tunlp' }, - { cmd: 'ss', desc: '套接字统计', category: 'network', args: '-tunlp' }, - { cmd: 'ip', desc: 'IP 配置', category: 'network', args: 'addr show' }, + { 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', desc: '路由表', category: 'network', args: '-n' }, - { cmd: 'iptables', desc: '防火墙', category: 'network', args: '-L -n' }, - { cmd: 'nslookup', desc: 'DNS 查询', category: 'network', args: 'domain' }, - { cmd: 'dig', desc: 'DNS 详细查询', category: 'network', args: 'domain' }, - { cmd: 'traceroute', desc: '路由追踪', category: 'network', args: 'host' }, - { cmd: 'tcpdump', desc: '抓包', category: 'network', args: '-i eth0' }, + { 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', desc: 'Debian 包管理', category: 'package', args: 'update && apt upgrade' }, - { cmd: 'apt-get', desc: 'APT 包管理', category: 'package', args: 'install package' }, - { cmd: 'yum', desc: 'RHEL 包管理', category: 'package', args: 'install package' }, - { cmd: 'dnf', desc: 'Fedora 包管理', category: 'package', args: 'install package' }, - { cmd: 'pacman', desc: 'Arch 包管理', category: 'package', args: '-S package' }, - { cmd: 'npm', desc: 'Node 包管理', category: 'package', args: 'install package' }, - { cmd: 'pip', desc: 'Python 包管理', category: 'package', args: 'install package' }, - { cmd: 'pip3', desc: 'Python3 包管理', category: 'package', args: 'install package' }, + { 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', desc: '服务管理', category: 'service', args: 'status service' }, - { cmd: 'service', desc: '服务控制', category: 'service', args: 'nginx restart' }, - { cmd: 'journalctl', desc: '查看日志', category: 'service', args: '-u service -f' }, + { 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', desc: 'Docker 命令', category: 'docker', args: 'ps -a' }, - { cmd: 'docker-compose', desc: 'Docker Compose', category: 'docker', args: 'up -d' }, + { 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', desc: 'Git 版本控制', category: 'git', args: 'status' }, + { 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', args: 'command' }, - { cmd: 'whereis', desc: '二进制位置', category: 'other', args: 'command' }, - { cmd: 'man', desc: '查看手册', category: 'other', args: 'command' }, + { 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', args: 'VAR=value' }, - { cmd: 'source', desc: '执行脚本', category: 'other', args: 'script.sh' }, - { cmd: 'crontab', desc: '定时任务', category: 'other', args: '-l' }, - { cmd: 'nohup', desc: '后台运行', category: 'other', args: 'command &' }, - { cmd: 'screen', desc: '终端复用', category: 'other', args: '-S session' }, - { cmd: 'tmux', desc: '终端复用', category: 'other', args: 'new -s session' }, - { cmd: 'vim', desc: '文本编辑器', category: 'other', args: 'filename' }, - { cmd: 'nano', desc: '简易编辑器', category: 'other', args: 'filename' }, - { cmd: 'vi', desc: 'Vi 编辑器', category: 'other', args: 'filename' }, + { 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' }, ]; // 获取分类图标 @@ -141,7 +205,7 @@ const getCategoryIcon = (category) => { } }; -function CommandSuggestions({ input, position, onSelect, onClose, visible }) { +function CommandSuggestions({ input, position, onSelect, onClose, visible, cursorPosition }) { const [selectedIndex, setSelectedIndex] = useState(0); const listRef = useRef(null); const selectedRef = useRef(null); @@ -152,43 +216,11 @@ function CommandSuggestions({ input, position, onSelect, onClose, visible }) { const query = input.toLowerCase().trim(); - // 分词,获取最后一个输入的词 - const parts = query.split(/\s+/); - const lastWord = parts[parts.length - 1]; - const isFirstWord = parts.length === 1; - - if (!lastWord) return []; - - let results = []; - - if (isFirstWord) { - // 第一个词:搜索命令 - results = COMMAND_DATABASE.filter(item => - item.cmd.toLowerCase().startsWith(lastWord) || - item.desc.toLowerCase().includes(lastWord) - ); - } else { - // 后续词:搜索该命令的常用参数或相关命令 - const baseCmd = parts[0]; - const cmdInfo = COMMAND_DATABASE.find(c => c.cmd === baseCmd); - - if (cmdInfo && cmdInfo.args) { - // 如果有预设参数,显示参数提示 - results = [{ - cmd: `${baseCmd} ${cmdInfo.args}`, - desc: `${cmdInfo.desc} (常用参数)`, - category: cmdInfo.category, - isFullCmd: true - }]; - } - - // 也搜索其他可能的命令组合 - const otherResults = COMMAND_DATABASE.filter(item => - item.cmd.toLowerCase().startsWith(lastWord) - ).slice(0, 3); - - results = [...results, ...otherResults]; - } + // 匹配以输入开头的命令 + const results = COMMAND_DATABASE.filter(item => + item.cmd.toLowerCase().startsWith(query) || + item.desc.toLowerCase().includes(query) + ); return results.slice(0, 8); }, [input]); @@ -222,12 +254,11 @@ function CommandSuggestions({ input, position, onSelect, onClose, visible }) { setSelectedIndex(prev => (prev - 1 + suggestions.length) % suggestions.length); break; case 'Tab': - case 'Enter': + // 只用 Tab 选择 if (suggestions[selectedIndex]) { e.preventDefault(); e.stopPropagation(); - const item = suggestions[selectedIndex]; - onSelect(item.isFullCmd ? item.cmd : item.cmd); + onSelect(suggestions[selectedIndex].cmd); } break; case 'Escape': @@ -245,32 +276,38 @@ function CommandSuggestions({ input, position, onSelect, onClose, visible }) { if (!visible || suggestions.length === 0) return null; + // 计算提示框位置(在光标下方) + const positionStyle = cursorPosition ? { + top: cursorPosition.top + 20, + left: cursorPosition.left, + } : { + bottom: position?.bottom || 60, + left: position?.left || 16, + }; + return ( - {/* 标题栏 */} -
- - 命令提示 + {/* 简化的标题栏 */} +
+ + ↑↓ 导航 - - Tab 选择 · Esc 关闭 + + Tab 补全
{/* 建议列表 */} -
+
{suggestions.map((item, index) => { const Icon = getCategoryIcon(item.category); const isSelected = index === selectedIndex; @@ -279,9 +316,9 @@ function CommandSuggestions({ input, position, onSelect, onClose, visible }) {
onSelect(item.isFullCmd ? item.cmd : item.cmd)} + onClick={() => onSelect(item.cmd)} className={` - px-3 py-2 cursor-pointer flex items-center gap-3 transition-all + 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' @@ -289,23 +326,16 @@ function CommandSuggestions({ input, position, onSelect, onClose, visible }) { `} > -
-
- - {item.cmd} - - {item.args && !item.isFullCmd && ( - - {item.args} - - )} -
-
+
+ + {item.cmd} + + {item.desc} -
+
); diff --git a/src/components/Terminal.js b/src/components/Terminal.js index d39e14b..63b16a3 100644 --- a/src/components/Terminal.js +++ b/src/components/Terminal.js @@ -83,6 +83,7 @@ function Terminal({ tabId, hostId, isActive, onConnectionChange, onShowCommandPa const [isConnecting, setIsConnecting] = useState(false); const [currentInput, setCurrentInput] = useState(''); const [showSuggestions, setShowSuggestions] = useState(false); + const [cursorPosition, setCursorPosition] = useState(null); const inputBufferRef = useRef(''); const [error, setError] = useState(null); const [isReady, setIsReady] = useState(false); @@ -280,6 +281,21 @@ 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') { // 回车:清空输入缓冲区 @@ -291,6 +307,7 @@ function Terminal({ tabId, hostId, isActive, onConnectionChange, onShowCommandPa 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); @@ -302,6 +319,7 @@ function Terminal({ tabId, hostId, isActive, onConnectionChange, onShowCommandPa inputBufferRef.current += data; setCurrentInput(inputBufferRef.current); setShowSuggestions(inputBufferRef.current.length > 0); + updateCursorPosition(); } else if (data === '\x03') { // Ctrl+C:清空 inputBufferRef.current = ''; @@ -611,7 +629,7 @@ function Terminal({ tabId, hostId, isActive, onConnectionChange, onShowCommandPa