修复文件编辑bug

This commit is contained in:
2025-08-12 22:56:01 +08:00
parent dd48999f9a
commit ef0c895f1e
4 changed files with 213 additions and 12 deletions

View File

@@ -21,6 +21,7 @@
"@tailwindcss/vite": "^4.1.8",
"@tanstack/react-virtual": "^3.13.10",
"@tauri-apps/api": "^2.1.1",
"@tauri-apps/plugin-clipboard-manager": "^2.3.0",
"@tauri-apps/plugin-dialog": "^2.0.2",
"@tauri-apps/plugin-global-shortcut": "^2.0.0",
"@tauri-apps/plugin-opener": "^2",
@@ -480,6 +481,8 @@
"@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.7.1", "", { "os": "win32", "cpu": "x64" }, "sha512-D7Q9kDObutuirCNLxYQ7KAg2Xxg99AjcdYz/KuMw5HvyEPbkC9Q7JL0vOrQOrHEHxIQ2lYzFOZvKKoC2yyqXcg=="],
"@tauri-apps/plugin-clipboard-manager": ["@tauri-apps/plugin-clipboard-manager@2.3.0", "https://registry.npmmirror.com/@tauri-apps/plugin-clipboard-manager/-/plugin-clipboard-manager-2.3.0.tgz", { "dependencies": { "@tauri-apps/api": "^2.6.0" } }, "sha512-81NOBA2P+OTY8RLkBwyl9ZR/0CeggLub4F6zxcxUIfFOAqtky7J61+K/MkH2SC1FMxNBxrX0swDuKvkjkHadlA=="],
"@tauri-apps/plugin-dialog": ["@tauri-apps/plugin-dialog@2.2.2", "", { "dependencies": { "@tauri-apps/api": "^2.0.0" } }, "sha512-Pm9qnXQq8ZVhAMFSEPwxvh+nWb2mk7LASVlNEHYaksHvcz8P6+ElR5U5dNL9Ofrm+uwhh1/gYKWswK8JJJAh6A=="],
"@tauri-apps/plugin-global-shortcut": ["@tauri-apps/plugin-global-shortcut@2.2.1", "", { "dependencies": { "@tauri-apps/api": "^2.0.0" } }, "sha512-b64/TI1t5LIi2JY4OWlYjZpPRq60T5GVVL/no27sUuxaNUZY8dVtwsMtDUgxUpln2yR+P2PJsYlqY5V8sLSxEw=="],
@@ -1396,6 +1399,8 @@
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"@tauri-apps/plugin-clipboard-manager/@tauri-apps/api": ["@tauri-apps/api@2.7.0", "https://registry.npmmirror.com/@tauri-apps/api/-/api-2.7.0.tgz", {}, "sha512-v7fVE8jqBl8xJFOcBafDzXFc8FnicoH3j8o8DNNs0tHuEBmXUDqrCOAzMRX0UkfpwqZLqvrvK0GNQ45DfnoVDg=="],
"@types/estree-jsx/@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="],
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.5.tgz", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],

View File

@@ -29,6 +29,7 @@
"@tailwindcss/vite": "^4.1.8",
"@tanstack/react-virtual": "^3.13.10",
"@tauri-apps/api": "^2.1.1",
"@tauri-apps/plugin-clipboard-manager": "^2.3.0",
"@tauri-apps/plugin-dialog": "^2.0.2",
"@tauri-apps/plugin-global-shortcut": "^2.0.0",
"@tauri-apps/plugin-opener": "^2",

View File

@@ -87,6 +87,7 @@ fn main() {
tauri::Builder::default()
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_clipboard_manager::init())
.setup(|app| {
// Initialize agents database
let conn = init_database(&app.handle()).expect("Failed to initialize agents database");

View File

@@ -1,6 +1,7 @@
import React, { useState, useEffect, useCallback, useRef } from "react";
import { invoke } from "@tauri-apps/api/core";
import { listen, type UnlistenFn } from "@tauri-apps/api/event";
import { writeText, readText } from "@tauri-apps/plugin-clipboard-manager";
import {
X,
Save,
@@ -174,6 +175,8 @@ export const FileEditorEnhanced: React.FC<FileEditorEnhancedProps> = ({
const loadFile = useCallback(async () => {
if (!filePath) return;
console.log('[FileEditor] Loading file:', filePath);
try {
setLoading(true);
setError(null);
@@ -182,18 +185,20 @@ export const FileEditorEnhanced: React.FC<FileEditorEnhancedProps> = ({
path: filePath,
});
console.log('[FileEditor] File loaded, content length:', fileContent.length);
setContent(fileContent);
setOriginalContent(fileContent);
setHasChanges(false);
setFileChanged(false);
setLastCheckTime(Date.now());
} catch (err) {
console.error("Failed to load file:", err);
console.error("[FileEditor] Failed to load file:", err);
setError(err instanceof Error ? err.message : "Failed to load file");
} finally {
setLoading(false);
}
}, [filePath, hasChanges]);
}, [filePath]);
// 保存文件
const saveFile = useCallback(async () => {
@@ -244,17 +249,20 @@ export const FileEditorEnhanced: React.FC<FileEditorEnhancedProps> = ({
}, [autoSave, hasChanges, saveFile]);
// 处理内容变化
const handleContentChange = (value: string | undefined) => {
const handleContentChange = useCallback((value: string | undefined) => {
console.log('[FileEditor] Content change detected, new length:', value?.length);
if (value !== undefined) {
setContent(value);
setHasChanges(value !== originalContent);
const changed = value !== originalContent;
setHasChanges(changed);
console.log('[FileEditor] Has changes:', changed);
// 触发语法检查
if (editorRef.current && (language === 'typescript' || language === 'javascript')) {
validateCode(value);
}
}
};
}, [originalContent, language]);
// 验证代码
const validateCode = async (_code: string) => {
@@ -303,9 +311,58 @@ export const FileEditorEnhanced: React.FC<FileEditorEnhancedProps> = ({
// Monaco Editor 挂载时的处理
const handleEditorDidMount = (editor: monaco.editor.IStandaloneCodeEditor, monaco: Monaco) => {
console.log('[FileEditor] Editor mounted successfully');
editorRef.current = editor;
monacoRef.current = monaco;
// 检查编辑器是否可编辑
const model = editor.getModel();
if (model) {
const options = editor.getOptions();
console.log('[FileEditor] Editor readOnly:', options.get(monaco.editor.EditorOption.readOnly));
console.log('[FileEditor] Editor value length:', model.getValue().length);
// 强制设置模型可编辑
model.updateOptions({ tabSize: 2, insertSpaces: true });
}
// 确保编辑器获得焦点
editor.focus();
// 手动处理回车键
editor.addCommand(monaco.KeyCode.Enter, () => {
const position = editor.getPosition();
if (position) {
const model = editor.getModel();
if (model) {
// 获取当前行内容
const lineContent = model.getLineContent(position.lineNumber);
const beforeCursor = lineContent.substring(0, position.column - 1);
// 计算缩进
const indent = beforeCursor.match(/^\s*/)?.[0] || '';
// 插入新行
editor.executeEdits('enter', [{
range: new monaco.Range(
position.lineNumber,
position.column,
position.lineNumber,
position.column
),
text: '\n' + indent,
forceMoveMarkers: true
}]);
// 移动光标到新行
editor.setPosition({
lineNumber: position.lineNumber + 1,
column: indent.length + 1
});
}
}
});
// 监听光标位置变化
editor.onDidChangeCursorPosition((e) => {
setCursorPosition({
@@ -314,6 +371,105 @@ export const FileEditorEnhanced: React.FC<FileEditorEnhancedProps> = ({
});
});
// 使用简单的复制处理,避免剪贴板权限问题
editor.addAction({
id: 'custom-copy',
label: 'Copy',
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyC],
contextMenuGroupId: 'navigation',
contextMenuOrder: 1.5,
run: async (ed) => {
const selection = ed.getSelection();
if (selection) {
const text = ed.getModel()?.getValueInRange(selection);
if (text) {
try {
// 尝试使用 Tauri 的剪贴板 API
await writeText(text).catch(() => {
// 如果失败,使用浏览器原生 API
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).catch(console.error);
}
});
console.log('[FileEditor] Text copied');
} catch (err) {
console.error('[FileEditor] Copy failed:', err);
}
}
}
}
});
editor.addAction({
id: 'custom-paste',
label: 'Paste',
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyV],
contextMenuGroupId: 'navigation',
contextMenuOrder: 1.6,
run: async (ed) => {
try {
let text = '';
try {
// 尝试使用 Tauri API
text = await readText();
} catch {
// 如果失败,使用浏览器原生 API
if (navigator.clipboard && navigator.clipboard.readText) {
text = await navigator.clipboard.readText().catch(() => '');
}
}
if (text) {
const selection = ed.getSelection();
if (selection) {
ed.executeEdits('paste', [{
range: selection,
text: text,
forceMoveMarkers: true
}]);
}
}
} catch (err) {
console.error('[FileEditor] Paste failed:', err);
}
}
});
editor.addAction({
id: 'custom-cut',
label: 'Cut',
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyX],
contextMenuGroupId: 'navigation',
contextMenuOrder: 1.4,
run: async (ed) => {
const selection = ed.getSelection();
if (selection) {
const text = ed.getModel()?.getValueInRange(selection);
if (text) {
try {
// 尝试复制到剪贴板
await writeText(text).catch(() => {
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).catch(console.error);
}
});
// 删除选中的文本
ed.executeEdits('cut', [{
range: selection,
text: '',
forceMoveMarkers: true
}]);
console.log('[FileEditor] Text cut');
} catch (err) {
console.error('[FileEditor] Cut failed:', err);
}
}
}
}
});
// 初始化 Monaco 配置
initializeMonaco();
@@ -326,13 +482,16 @@ export const FileEditorEnhanced: React.FC<FileEditorEnhancedProps> = ({
showErrorsInMinimap: true,
});
// 设置快捷键
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => {
saveFile();
});
// 移除原有的快捷键绑定,使用 Monaco 内置的
// 这些快捷键会自动工作,不需要额外处理
editor.addCommand(monaco.KeyMod.Alt | monaco.KeyMod.Shift | monaco.KeyCode.KeyF, () => {
handleFormat();
// 监听内容变化事件(作为备用)
editor.onDidChangeModelContent(() => {
const value = editor.getValue();
if (value !== content) {
console.log('[FileEditor] Content changed via editor event');
handleContentChange(value);
}
});
// 监听光标位置变化
@@ -349,16 +508,27 @@ export const FileEditorEnhanced: React.FC<FileEditorEnhancedProps> = ({
// 快捷键处理
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
// 如果焦点在编辑器内,除了特定快捷键外不处理其他按键
const activeElement = document.activeElement;
const isInEditor = activeElement?.closest('.monaco-editor');
// Ctrl/Cmd + S 保存
if ((e.ctrlKey || e.metaKey) && e.key === "s") {
e.preventDefault();
saveFile();
return;
}
// Ctrl/Cmd + Shift + F 格式化
if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === "f") {
e.preventDefault();
handleFormat();
return;
}
// 如果在编辑器内,不处理其他快捷键
if (isInEditor) {
return;
}
// F11 全屏
@@ -848,6 +1018,7 @@ export const FileEditorEnhanced: React.FC<FileEditorEnhancedProps> = ({
onMount={handleEditorDidMount}
theme={theme}
options={{
readOnly: false, // 确保编辑器可编辑
fontSize: fontSize,
minimap: { enabled: minimap },
lineNumbers: "on", // 显示行号
@@ -862,7 +1033,11 @@ export const FileEditorEnhanced: React.FC<FileEditorEnhancedProps> = ({
formatOnPaste: true,
formatOnType: true,
suggestOnTriggerCharacters: true,
quickSuggestions: true,
quickSuggestions: {
other: true,
comments: true,
strings: true
},
parameterHints: { enabled: true },
folding: true,
foldingStrategy: 'indentation',
@@ -878,6 +1053,25 @@ export const FileEditorEnhanced: React.FC<FileEditorEnhancedProps> = ({
hover: { enabled: true, delay: 300 },
definitionLinkOpensInPeek: true,
peekWidgetDefaultFocus: 'editor',
// 确保回车键和其他基本编辑功能正常工作
acceptSuggestionOnEnter: "off", // 关闭回车接受建议,避免冲突
autoClosingBrackets: "always",
autoClosingQuotes: "always",
autoIndent: "full",
emptySelectionClipboard: false, // 禁用空选择剪贴板
copyWithSyntaxHighlighting: false, // 禁用语法高亮复制
multiCursorModifier: "alt",
snippetSuggestions: "bottom",
tabCompletion: "on",
wordBasedSuggestions: "currentDocument",
// 确保编辑器可以接收输入
domReadOnly: false,
readOnlyMessage: undefined,
// 添加更多编辑器配置
cursorBlinking: "blink",
cursorSmoothCaretAnimation: "on",
mouseWheelZoom: true,
smoothScrolling: true,
}}
/>
</div>