09839b15a0
- 文件写入/编辑前显示 diff 对比供用户确认 - LSP 诊断只显示错误和警告,忽略 hint/info - 优化 LSP 等待时间:首次启动 2s,后续 300ms
187 lines
4.9 KiB
TypeScript
187 lines
4.9 KiB
TypeScript
/**
|
||
* 文件操作确认提示
|
||
* 显示 diff 对比并让用户确认
|
||
*/
|
||
|
||
import * as readline from 'readline';
|
||
import * as fs from 'fs/promises';
|
||
import chalk from 'chalk';
|
||
import type { FilePermissionContext, PermissionDecision } from './types.js';
|
||
import { computeDiff, formatDiff, countChanges, formatEditDiff } from '../utils/diff.js';
|
||
|
||
/**
|
||
* 显示文件写入的 diff 并请求确认
|
||
*/
|
||
export async function promptFileWrite(ctx: FilePermissionContext): Promise<PermissionDecision> {
|
||
const { path: filePath, newContent } = ctx;
|
||
|
||
if (!newContent) {
|
||
// 没有内容,使用简单确认
|
||
return promptSimpleConfirm(ctx);
|
||
}
|
||
|
||
// 读取原文件内容
|
||
let oldContent: string | null = null;
|
||
try {
|
||
oldContent = await fs.readFile(filePath, 'utf-8');
|
||
} catch {
|
||
// 文件不存在,是新文件
|
||
}
|
||
|
||
// 如果内容相同,直接允许
|
||
if (oldContent === newContent) {
|
||
return { allow: true, remember: false };
|
||
}
|
||
|
||
// 计算 diff
|
||
const diff = computeDiff(oldContent, newContent);
|
||
const changes = countChanges(diff);
|
||
|
||
// 显示 diff
|
||
console.log('');
|
||
console.log(chalk.yellow('📝 文件写入预览'));
|
||
console.log(chalk.cyan('文件: ') + chalk.white(filePath));
|
||
|
||
if (diff.isNew) {
|
||
console.log(chalk.green('状态: ') + chalk.white('新文件'));
|
||
console.log(chalk.green(`+${changes.additions} 行`));
|
||
} else {
|
||
console.log(chalk.green(`+${changes.additions} 行`) + ' / ' + chalk.red(`-${changes.deletions} 行`));
|
||
}
|
||
|
||
console.log('');
|
||
console.log(chalk.gray('─'.repeat(60)));
|
||
|
||
// 限制显示行数
|
||
const diffOutput = formatDiff(diff, filePath);
|
||
const lines = diffOutput.split('\n');
|
||
const MAX_LINES = 50;
|
||
|
||
if (lines.length > MAX_LINES) {
|
||
console.log(lines.slice(0, MAX_LINES).join('\n'));
|
||
console.log(chalk.yellow(`\n... 省略 ${lines.length - MAX_LINES} 行 ...`));
|
||
} else {
|
||
console.log(diffOutput);
|
||
}
|
||
|
||
console.log(chalk.gray('─'.repeat(60)));
|
||
console.log('');
|
||
|
||
// 询问用户确认
|
||
return promptConfirm();
|
||
}
|
||
|
||
/**
|
||
* 显示文件编辑的 diff 并请求确认
|
||
*/
|
||
export async function promptFileEdit(ctx: FilePermissionContext): Promise<PermissionDecision> {
|
||
const { path: filePath, oldContent, newContent } = ctx;
|
||
|
||
if (!oldContent || !newContent) {
|
||
// 没有内容,使用简单确认
|
||
return promptSimpleConfirm(ctx);
|
||
}
|
||
|
||
// 显示编辑 diff
|
||
console.log('');
|
||
console.log(chalk.yellow('✏️ 文件编辑预览'));
|
||
console.log(chalk.cyan('文件: ') + chalk.white(filePath));
|
||
console.log('');
|
||
console.log(chalk.gray('─'.repeat(60)));
|
||
console.log(formatEditDiff(oldContent, newContent));
|
||
console.log(chalk.gray('─'.repeat(60)));
|
||
console.log('');
|
||
|
||
// 询问用户确认
|
||
return promptConfirm();
|
||
}
|
||
|
||
/**
|
||
* 简单确认(无 diff)
|
||
*/
|
||
async function promptSimpleConfirm(ctx: FilePermissionContext): Promise<PermissionDecision> {
|
||
const rl = readline.createInterface({
|
||
input: process.stdin,
|
||
output: process.stdout,
|
||
});
|
||
|
||
return new Promise((resolve) => {
|
||
console.log('');
|
||
console.log(chalk.yellow('⚠️ 文件操作确认'));
|
||
console.log(chalk.cyan('操作: ') + chalk.white(ctx.operation));
|
||
console.log(chalk.cyan('文件: ') + chalk.white(ctx.path));
|
||
console.log('');
|
||
|
||
showConfirmOptions();
|
||
|
||
rl.question(chalk.yellow('请选择 [y/Y/n/N]: '), (answer) => {
|
||
rl.close();
|
||
resolve(parseAnswer(answer));
|
||
});
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 通用确认提示
|
||
*/
|
||
async function promptConfirm(): Promise<PermissionDecision> {
|
||
const rl = readline.createInterface({
|
||
input: process.stdin,
|
||
output: process.stdout,
|
||
});
|
||
|
||
return new Promise((resolve) => {
|
||
showConfirmOptions();
|
||
|
||
rl.question(chalk.yellow('请选择 [y/Y/n/N]: '), (answer) => {
|
||
rl.close();
|
||
resolve(parseAnswer(answer));
|
||
});
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 显示确认选项
|
||
*/
|
||
function showConfirmOptions(): void {
|
||
console.log(chalk.white('选择操作:'));
|
||
console.log(chalk.green(' [y] ') + '确认执行');
|
||
console.log(chalk.green(' [Y] ') + '确认执行,并记住此类操作(本次会话)');
|
||
console.log(chalk.red(' [n] ') + '拒绝执行');
|
||
console.log(chalk.red(' [N] ') + '拒绝执行,并记住此类操作(本次会话)');
|
||
console.log('');
|
||
}
|
||
|
||
/**
|
||
* 解析用户输入
|
||
*/
|
||
function parseAnswer(answer: string): PermissionDecision {
|
||
const choice = answer.trim();
|
||
|
||
switch (choice) {
|
||
case 'y':
|
||
return { allow: true, remember: false };
|
||
case 'Y':
|
||
return { allow: true, remember: true };
|
||
case 'N':
|
||
return { allow: false, remember: true };
|
||
case 'n':
|
||
default:
|
||
return { allow: false, remember: false };
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 根据操作类型选择合适的确认提示
|
||
*/
|
||
export async function promptFilePermission(ctx: FilePermissionContext): Promise<PermissionDecision> {
|
||
switch (ctx.operation) {
|
||
case 'write':
|
||
return promptFileWrite(ctx);
|
||
case 'edit':
|
||
return promptFileEdit(ctx);
|
||
default:
|
||
return promptSimpleConfirm(ctx);
|
||
}
|
||
}
|