cs
using UnityEngine;
using UnityEditor;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Linq;
using System.Security.Cryptography;
public class UnityNotepad : EditorWindow
{
// 数据模型
[System.Serializable]
private class NoteTab
{
public string title = "新笔记";
public string content = "";
public Vector2 scrollPosition;
public bool isModified = false;
public string filePath = "";
public DateTime lastModified;
public string lastSavedHash = "";
public DateTime lastChangeTime;
public NoteTab()
{
lastModified = DateTime.Now;
lastChangeTime = DateTime.Now;
}
}
// 备份文件信息
private class BackupFileInfo
{
public string filePath;
public string fileName;
public string originalName;
public DateTime backupTime;
public long fileSize;
public string relativePath;
}
// 主页模式
private enum MainPageMode { Home, Backup }
// 序列化存储
[System.Serializable]
private class NotepadData
{
public List<NoteTab> tabs = new List<NoteTab>();
public int selectedTabIndex = 0;
public bool autoSaveEnabled = true;
public string notesFolder = "Assets/Editor/Notes/";
public string backupFolder = "Assets/Editor/Notes/Backup/";
public int fontSize = 14;
public bool wordWrap = true;
public bool showNoteList = true;
public int mainPageTabIndex = -1; // 主页标签页索引
public MainPageMode mainPageMode = MainPageMode.Home; // 主页当前显示模式
}
// 编辑器状态
private NotepadData data;
private Vector2 tabsScrollPos;
private bool showSettings = false;
private GUIStyle textAreaStyle;
private string searchText = "";
private List<int> searchResults = new List<int>();
private int currentSearchIndex = 0;
private int lastSelectedTabIndex = -1;
private const string DATA_KEY = "UnityNotepad_Data_Fixed";
private const int AUTO_SAVE_INTERVAL = 30;
private DateTime lastAutoSaveTime;
private Dictionary<int, double> scheduledSaves = new Dictionary<int, double>();
private HashSet<string> openedFiles = new HashSet<string>();
// 焦点管理 - 完全禁用自动焦点
private string textAreaControlName = "NoteTextArea";
private bool neverAutoFocus = true; // 完全禁用自动焦点
// 笔记列表管理
private List<string> noteFiles = new List<string>();
private Vector2 noteListScrollPos;
private string[] textExtensions = { ".txt", ".md", ".json", ".cs", ".xml", ".yaml", ".yml", ".html", ".css", ".js" };
private DateTime lastNoteListRefresh = DateTime.MinValue;
private const double NOTE_LIST_REFRESH_INTERVAL = 2.0;
// 备份文件管理
private List<BackupFileInfo> backupFiles = new List<BackupFileInfo>();
private Vector2 backupListScrollPos;
private DateTime lastBackupListRefresh = DateTime.MinValue;
private BackupFileInfo selectedBackupForPreview = null;
// 初始化窗口
[MenuItem("Tools/Unity Notepad %#m")]
public static void ShowWindow()
{
var window = GetWindow<UnityNotepad>();
window.titleContent = new GUIContent("Unity Notepad", EditorGUIUtility.IconContent("TextAsset Icon").image);
window.minSize = new Vector2(500, 400);
window.Show();
}
private void OnEnable()
{
LoadData();
CreateTextAreaStyle();
lastAutoSaveTime = DateTime.Now;
EditorApplication.update += OnEditorUpdate;
UpdateOpenedFiles();
RefreshNoteList();
RefreshBackupList();
// 确保有主页标签页
EnsureMainPageTab();
}
private void OnDisable()
{
SaveAllModifiedTabs();
SaveData();
EditorApplication.update -= OnEditorUpdate;
}
private void OnFocus()
{
RefreshNoteList();
RefreshBackupList();
}
private void UpdateOpenedFiles()
{
openedFiles.Clear();
if (data != null && data.tabs != null)
{
foreach (var tab in data.tabs)
{
if (!string.IsNullOrEmpty(tab.filePath))
{
openedFiles.Add(Path.GetFullPath(tab.filePath));
}
}
}
}
private void OnGUI()
{
if (data == null)
{
data = new NotepadData();
}
// 处理键盘快捷键
HandleKeyboardShortcuts();
// 绘制界面
DrawToolbar();
DrawTabsHeader();
DrawContentArea();
DrawStatusBar();
}
#region GUI 绘制方法
private void DrawToolbar()
{
GUILayout.BeginHorizontal(EditorStyles.toolbar);
// 文件操作按钮
if (GUILayout.Button("新建", EditorStyles.toolbarButton, GUILayout.Width(40)))
{
CreateNewTab();
}
if (GUILayout.Button("打开", EditorStyles.toolbarButton, GUILayout.Width(40)))
{
OpenNoteFile();
}
if (GUILayout.Button("保存", EditorStyles.toolbarButton, GUILayout.Width(40)))
{
SaveCurrentTab();
}
if (GUILayout.Button("另存为", EditorStyles.toolbarButton, GUILayout.Width(50)))
{
SaveAsCurrentTab();
}
GUILayout.Space(10);
// 编辑操作按钮
if (GUILayout.Button("复制", EditorStyles.toolbarButton, GUILayout.Width(40)))
{
var currentTab = GetCurrentTab();
if (currentTab != null && !string.IsNullOrEmpty(currentTab.content))
EditorGUIUtility.systemCopyBuffer = currentTab.content;
}
if (GUILayout.Button("粘贴", EditorStyles.toolbarButton, GUILayout.Width(40)))
{
AppendToCurrentTab(EditorGUIUtility.systemCopyBuffer);
}
GUILayout.Space(10);
// 搜索
GUILayout.Label("搜索:", EditorStyles.miniLabel);
string newSearchText = GUILayout.TextField(searchText, GUILayout.Width(150));
if (newSearchText != searchText)
{
searchText = newSearchText;
PerformSearch();
}
if (searchResults.Count > 0)
{
GUILayout.Label($"{currentSearchIndex + 1}/{searchResults.Count}", EditorStyles.miniLabel);
if (GUILayout.Button("↑", EditorStyles.toolbarButton, GUILayout.Width(20)))
NavigateSearch(-1);
if (GUILayout.Button("↓", EditorStyles.toolbarButton, GUILayout.Width(20)))
NavigateSearch(1);
}
GUILayout.FlexibleSpace();
// 设置按钮
showSettings = GUILayout.Toggle(showSettings, "设置", EditorStyles.toolbarButton, GUILayout.Width(40));
// 主页按钮
if (GUILayout.Button("主页", EditorStyles.toolbarButton, GUILayout.Width(40)))
{
if (data.mainPageTabIndex >= 0 && data.mainPageTabIndex < data.tabs.Count)
{
data.selectedTabIndex = data.mainPageTabIndex;
data.mainPageMode = MainPageMode.Home;
}
}
// 备份按钮
if (GUILayout.Button("备份", EditorStyles.toolbarButton, GUILayout.Width(40)))
{
if (data.mainPageTabIndex >= 0 && data.mainPageTabIndex < data.tabs.Count)
{
data.selectedTabIndex = data.mainPageTabIndex;
data.mainPageMode = MainPageMode.Backup;
}
}
// 关闭笔记本按钮
if (GUILayout.Button("关闭", EditorStyles.toolbarButton, GUILayout.Width(40)))
{
CloseNotepad();
}
GUILayout.EndHorizontal();
if (showSettings)
DrawSettingsPanel();
}
private void DrawTabsHeader()
{
// 确保有标签页(至少有一个主页标签)
if (data.tabs.Count == 0)
{
CreateMainPageTab();
}
GUILayout.BeginHorizontal(EditorStyles.toolbar);
// 显示所有普通笔记标签
for (int i = 0; i < data.tabs.Count; i++)
{
// 跳过主页标签
if (i == data.mainPageTabIndex) continue;
var tab = data.tabs[i];
string tabName = (string.IsNullOrEmpty(tab.title) ? "未命名" : tab.title) + (tab.isModified ? " *" : "");
if (DrawTabButton(tabName, i == data.selectedTabIndex, 120, i))
{
data.selectedTabIndex = i;
// 切换标签时清除焦点,避免全选
GUI.FocusControl(null);
}
}
GUILayout.FlexibleSpace();
// 添加新标签按钮
if (GUILayout.Button("+", EditorStyles.toolbarButton, GUILayout.Width(30)))
{
CreateNewTab();
}
// 关闭所有标签按钮
if (GUILayout.Button("× 全部", EditorStyles.toolbarButton, GUILayout.Width(50)))
{
CloseAllTabs();
}
GUILayout.EndHorizontal();
}
private bool DrawTabButton(string label, bool isSelected, float width, int tabIndex)
{
bool clicked = false;
Rect tabRect = GUILayoutUtility.GetRect(
new GUIContent(label + " ×"),
EditorStyles.toolbarButton,
GUILayout.Width(width),
GUILayout.Height(20)
);
if (isSelected)
{
EditorGUI.DrawRect(tabRect, new Color(0.2f, 0.4f, 0.8f, 0.8f));
}
Rect closeButtonRect = new Rect(
tabRect.xMax - 20,
tabRect.y + (tabRect.height - 12) / 2,
12,
12
);
Rect textRect = new Rect(
tabRect.x,
tabRect.y,
tabRect.width - 20,
tabRect.height
);
Event currentEvent = Event.current;
if (currentEvent.type == EventType.MouseDown && tabRect.Contains(currentEvent.mousePosition))
{
if (currentEvent.button == 0)
{
if (closeButtonRect.Contains(currentEvent.mousePosition))
{
CloseTab(tabIndex);
currentEvent.Use();
}
else
{
clicked = true;
currentEvent.Use();
}
}
}
GUIStyle textStyle = new GUIStyle(EditorStyles.toolbarButton)
{
alignment = TextAnchor.MiddleLeft,
padding = new RectOffset(5, 0, 0, 0)
};
GUI.Label(textRect, label, textStyle);
GUIStyle closeStyle = new GUIStyle(EditorStyles.miniLabel)
{
alignment = TextAnchor.MiddleCenter,
fontSize = 10,
normal = { textColor = closeButtonRect.Contains(currentEvent.mousePosition) ? Color.red : Color.gray }
};
GUI.Label(closeButtonRect, "×", closeStyle);
return clicked;
}
private void DrawContentArea()
{
if (data.tabs.Count == 0)
{
CreateMainPageTab();
return;
}
var currentTab = GetCurrentTab();
if (currentTab == null)
{
data.selectedTabIndex = Mathf.Max(0, data.tabs.Count - 1);
return;
}
// 检查标签页是否切换
if (lastSelectedTabIndex != data.selectedTabIndex)
{
lastSelectedTabIndex = data.selectedTabIndex;
// 标签页切换时清除焦点
GUI.FocusControl(null);
}
EditorGUI.BeginChangeCheck();
currentTab.scrollPosition = EditorGUILayout.BeginScrollView(currentTab.scrollPosition);
// 如果是主页标签,绘制主页页面
if (data.selectedTabIndex == data.mainPageTabIndex)
{
DrawMainPage();
}
else
{
// 正常笔记标签
textAreaStyle.fontSize = data.fontSize;
textAreaStyle.wordWrap = data.wordWrap;
// 使用TextArea但不自动聚焦 - 这里的关键是永远不要调用GUI.SetNextControlName
string newContent = EditorGUILayout.TextArea(
currentTab.content,
textAreaStyle,
GUILayout.ExpandHeight(true)
);
if (EditorGUI.EndChangeCheck() && newContent != currentTab.content)
{
currentTab.content = newContent;
currentTab.isModified = true;
currentTab.lastModified = DateTime.Now;
currentTab.lastChangeTime = DateTime.Now;
ScheduleAutoSave(currentTab);
}
}
EditorGUILayout.EndScrollView();
}
private void DrawMainPage()
{
GUILayout.BeginVertical();
// 顶部导航栏
GUILayout.BeginHorizontal(EditorStyles.toolbar);
bool homeSelected = data.mainPageMode == MainPageMode.Home;
bool backupSelected = data.mainPageMode == MainPageMode.Backup;
// 主页按钮
if (GUILayout.Toggle(homeSelected, " 主页 ", EditorStyles.toolbarButton))
{
data.mainPageMode = MainPageMode.Home;
}
// 备份按钮
if (GUILayout.Toggle(backupSelected, " 备份管理 ", EditorStyles.toolbarButton))
{
data.mainPageMode = MainPageMode.Backup;
}
GUILayout.FlexibleSpace();
// 快速操作按钮
if (data.mainPageMode == MainPageMode.Home)
{
if (GUILayout.Button("新建笔记", EditorStyles.toolbarButton, GUILayout.Width(70)))
{
CreateNewTab();
}
if (GUILayout.Button("打开文件", EditorStyles.toolbarButton, GUILayout.Width(70)))
{
OpenNoteFile();
}
}
else if (data.mainPageMode == MainPageMode.Backup)
{
if (GUILayout.Button("备份所有", EditorStyles.toolbarButton, GUILayout.Width(70)))
{
BackupAllNotes();
}
if (GUILayout.Button("清理旧备份", EditorStyles.toolbarButton, GUILayout.Width(80)))
{
CleanupOldBackups(7);
}
}
GUILayout.EndHorizontal();
GUILayout.Space(10);
// 根据模式显示不同内容
if (data.mainPageMode == MainPageMode.Home)
{
DrawHomeContent();
}
else
{
DrawBackupContent();
}
GUILayout.EndVertical();
}
private void DrawHomeContent()
{
GUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
GUILayout.BeginVertical(GUILayout.Width(350));
GUILayout.Label(EditorGUIUtility.IconContent("TextAsset Icon"), GUILayout.Width(48), GUILayout.Height(48));
GUILayout.Space(10);
GUIStyle titleStyle = new GUIStyle(EditorStyles.largeLabel)
{
fontSize = 18,
alignment = TextAnchor.MiddleCenter,
fontStyle = FontStyle.Bold
};
GUILayout.Label("Unity 记事本", titleStyle);
GUILayout.Space(20);
GUILayout.Label("使用说明:", EditorStyles.boldLabel);
GUILayout.Space(8);
EditorGUILayout.HelpBox(
"• 点击「新建」按钮创建新笔记\n" +
"• 点击「打开」按钮导入现有笔记\n" +
"• 点击「保存」按钮保存当前笔记\n" +
"• 使用「Ctrl+S」快捷键快速保存\n" +
"• 使用「Ctrl+N」快捷键新建笔记\n" +
"• 点击标签右侧的「×」关闭标签\n" +
"• 支持自动保存和搜索功能\n" +
"• 点击「备份」进入备份管理",
MessageType.Info
);
GUILayout.Space(20);
// 快速访问面板
GUILayout.Label("快速访问", EditorStyles.boldLabel);
GUILayout.Space(5);
GUILayout.BeginHorizontal();
if (GUILayout.Button(" 新建笔记 ", GUILayout.Height(35), GUILayout.Width(100)))
{
CreateNewTab();
}
GUILayout.Space(10);
if (GUILayout.Button(" 打开文件 ", GUILayout.Height(35), GUILayout.Width(100)))
{
OpenNoteFile();
}
GUILayout.Space(10);
if (GUILayout.Button(" 备份管理 ", GUILayout.Height(35), GUILayout.Width(100)))
{
data.mainPageMode = MainPageMode.Backup;
}
GUILayout.EndHorizontal();
GUILayout.Space(10);
// 当前笔记快速备份
var currentTab = GetCurrentTab();
if (currentTab != null && data.selectedTabIndex != data.mainPageTabIndex &&
!string.IsNullOrEmpty(currentTab.filePath))
{
GUILayout.BeginVertical("Box");
GUILayout.Label("当前笔记:", EditorStyles.miniBoldLabel);
GUILayout.BeginHorizontal();
GUILayout.Label(currentTab.title, EditorStyles.label, GUILayout.ExpandWidth(true));
if (GUILayout.Button("快速备份", EditorStyles.miniButton, GUILayout.Width(80)))
{
BackupCurrentNote();
}
GUILayout.EndHorizontal();
GUILayout.EndVertical();
}
GUILayout.EndVertical();
GUILayout.FlexibleSpace();
GUILayout.EndHorizontal();
GUILayout.Space(20);
EditorGUILayout.LabelField("", GUI.skin.horizontalSlider);
GUILayout.Space(10);
DrawNoteList();
}
private void DrawBackupContent()
{
GUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
GUILayout.BeginVertical(GUILayout.Width(400));
GUILayout.Label(EditorGUIUtility.IconContent("d_SaveAs").image, GUILayout.Width(48), GUILayout.Height(48));
GUILayout.Space(10);
GUIStyle titleStyle = new GUIStyle(EditorStyles.largeLabel)
{
fontSize = 18,
alignment = TextAnchor.MiddleCenter,
fontStyle = FontStyle.Bold
};
GUILayout.Label("备份管理", titleStyle);
GUILayout.Space(20);
EditorGUILayout.HelpBox(
"• 这里显示所有备份的笔记文件\n" +
"• 备份文件名格式:原文件名_备份时间.扩展名\n" +
"• 点击备份文件名可以打开查看备份内容\n" +
"• 可以删除不需要的备份文件\n" +
"• 备份文件夹:" + data.backupFolder,
MessageType.Info
);
GUILayout.Space(20);
// 快速操作面板
GUILayout.Label("快速操作", EditorStyles.boldLabel);
GUILayout.Space(5);
GUILayout.BeginHorizontal();
// 备份当前笔记按钮(如果当前是笔记标签)
if (data.selectedTabIndex != data.mainPageTabIndex)
{
var currentTab = GetCurrentTab();
if (currentTab != null && !string.IsNullOrEmpty(currentTab.filePath))
{
if (GUILayout.Button(" 备份当前笔记 ", GUILayout.Height(35), GUILayout.Width(120)))
{
BackupCurrentNote();
}
GUILayout.Space(10);
}
}
if (GUILayout.Button(" 备份所有笔记 ", GUILayout.Height(35), GUILayout.Width(120)))
{
BackupAllNotes();
}
GUILayout.Space(10);
if (GUILayout.Button(" 刷新列表 ", GUILayout.Height(35), GUILayout.Width(80)))
{
RefreshBackupList();
}
GUILayout.EndHorizontal();
GUILayout.Space(10);
GUILayout.BeginHorizontal();
if (GUILayout.Button(" 打开备份文件夹 ", GUILayout.Height(35), GUILayout.Width(130)))
{
OpenBackupFolder();
}
GUILayout.Space(10);
if (GUILayout.Button(" 清理7天前备份 ", GUILayout.Height(35), GUILayout.Width(130)))
{
CleanupOldBackups(7);
}
GUILayout.EndHorizontal();
GUILayout.EndVertical();
GUILayout.FlexibleSpace();
GUILayout.EndHorizontal();
GUILayout.Space(20);
EditorGUILayout.LabelField("", GUI.skin.horizontalSlider);
GUILayout.Space(10);
DrawBackupList();
}
private void DrawNoteList()
{
// 刷新笔记列表(如果需要)
double timeSinceStartup = EditorApplication.timeSinceStartup;
TimeSpan timeSinceLastRefresh = DateTime.Now - lastNoteListRefresh;
if (timeSinceLastRefresh.TotalSeconds > NOTE_LIST_REFRESH_INTERVAL)
{
RefreshNoteList();
}
GUILayout.BeginHorizontal();
GUILayout.Label("笔记文件夹: ", EditorStyles.boldLabel);
GUILayout.Label(data.notesFolder, EditorStyles.wordWrappedLabel);
GUILayout.FlexibleSpace();
data.showNoteList = GUILayout.Toggle(data.showNoteList, "显示列表", EditorStyles.miniButton, GUILayout.Width(80));
if (GUILayout.Button("刷新", EditorStyles.miniButton, GUILayout.Width(50)))
{
RefreshNoteList();
}
GUILayout.EndHorizontal();
if (!data.showNoteList) return;
GUILayout.Space(5);
if (noteFiles.Count == 0)
{
EditorGUILayout.HelpBox($"笔记文件夹中没有找到文本文件。\n支持的格式: {string.Join(", ", textExtensions)}", MessageType.Info);
return;
}
GUILayout.Label($"找到 {noteFiles.Count} 个笔记:", EditorStyles.miniBoldLabel);
noteListScrollPos = GUILayout.BeginScrollView(noteListScrollPos, GUILayout.Height(200));
foreach (string filePath in noteFiles)
{
try
{
string fileName = Path.GetFileName(filePath);
string relativePath = GetRelativePath(filePath);
DateTime lastModified = File.GetLastWriteTime(filePath);
string fileInfo = $"{fileName} ({GetFileSize(filePath)}) - {lastModified:yyyy-MM-dd HH:mm}";
GUILayout.BeginHorizontal(EditorStyles.helpBox);
// 使用文件名为按钮,点击即可打开
if (GUILayout.Button(fileName, EditorStyles.miniButton, GUILayout.ExpandWidth(true)))
{
OpenNoteFile(filePath);
}
GUILayout.Space(5);
// 显示更多信息
GUILayout.BeginVertical(GUILayout.Width(100));
GUILayout.Label(GetFileSize(filePath), EditorStyles.miniLabel);
GUILayout.Label(lastModified.ToString("yyyy-MM-dd"), EditorStyles.miniLabel);
GUILayout.EndVertical();
// 显示是否已打开
if (openedFiles.Contains(Path.GetFullPath(filePath)))
{
GUILayout.Label("✓", EditorStyles.miniLabel, GUILayout.Width(20));
}
GUILayout.Space(5);
// 添加删除按钮
if (GUILayout.Button("×", EditorStyles.miniButton, GUILayout.Width(25)))
{
DeleteNoteFile(filePath);
}
GUILayout.EndHorizontal();
GUILayout.Space(2);
}
catch (Exception)
{
continue;
}
}
GUILayout.EndScrollView();
}
private void DrawBackupList()
{
// 刷新备份列表(如果需要)
TimeSpan timeSinceLastRefresh = DateTime.Now - lastBackupListRefresh;
if (timeSinceLastRefresh.TotalSeconds > NOTE_LIST_REFRESH_INTERVAL)
{
RefreshBackupList();
}
GUILayout.BeginHorizontal();
GUILayout.Label("备份文件夹: ", EditorStyles.boldLabel);
GUILayout.Label(data.backupFolder, EditorStyles.wordWrappedLabel);
GUILayout.FlexibleSpace();
if (GUILayout.Button("打开文件夹", EditorStyles.miniButton, GUILayout.Width(80)))
{
OpenBackupFolder();
}
GUILayout.Space(5);
if (GUILayout.Button("刷新", EditorStyles.miniButton, GUILayout.Width(50)))
{
RefreshBackupList();
}
GUILayout.EndHorizontal();
GUILayout.Space(5);
if (backupFiles.Count == 0)
{
EditorGUILayout.HelpBox($"备份文件夹中没有找到备份文件。\n点击上面的按钮备份当前笔记或所有笔记。", MessageType.Info);
return;
}
GUILayout.Label($"找到 {backupFiles.Count} 个备份文件:", EditorStyles.miniBoldLabel);
backupListScrollPos = GUILayout.BeginScrollView(backupListScrollPos, GUILayout.ExpandHeight(true));
// 按备份时间倒序排序
var sortedBackups = backupFiles.OrderByDescending(b => b.backupTime).ToList();
foreach (var backup in sortedBackups)
{
GUILayout.BeginVertical(EditorStyles.helpBox);
// 备份文件信息行
GUILayout.BeginHorizontal();
GUILayout.BeginVertical(GUILayout.ExpandWidth(true));
// 原始文件名
GUILayout.Label(backup.originalName, EditorStyles.boldLabel);
// 备份时间
GUILayout.Label($"备份时间: {backup.backupTime:yyyy-MM-dd HH:mm:ss}", EditorStyles.miniLabel);
// 文件大小
GUILayout.Label($"大小: {FormatFileSize(backup.fileSize)}", EditorStyles.miniLabel);
GUILayout.EndVertical();
// 按钮区域
GUILayout.BeginVertical(GUILayout.Width(200));
GUILayout.BeginHorizontal();
// 打开备份按钮
if (GUILayout.Button("预览", EditorStyles.miniButton, GUILayout.Width(60)))
{
ShowBackupPreview(backup);
}
// 恢复备份按钮
if (GUILayout.Button("恢复", EditorStyles.miniButton, GUILayout.Width(60)))
{
RestoreBackup(backup);
}
// 删除备份按钮
if (GUILayout.Button("删除", EditorStyles.miniButton, GUILayout.Width(60)))
{
DeleteBackupFile(backup.filePath);
}
GUILayout.EndHorizontal();
GUILayout.EndVertical();
GUILayout.EndHorizontal();
// 预览内容(展开时显示)
if (selectedBackupForPreview == backup)
{
GUILayout.Space(5);
DrawBackupPreview(backup);
}
GUILayout.EndVertical();
GUILayout.Space(3);
}
GUILayout.EndScrollView();
}
private void ShowBackupPreview(BackupFileInfo backup)
{
try
{
string content = File.ReadAllText(backup.filePath, Encoding.UTF8);
string preview = content.Length > 200 ? content.Substring(0, 200) + "..." : content;
EditorUtility.DisplayDialog(
$"预览: {backup.originalName}",
$"备份时间: {backup.backupTime:yyyy-MM-dd HH:mm:ss}\n\n" +
$"预览内容:\n{preview}",
"确定"
);
}
catch (Exception e)
{
Debug.LogWarning($"无法预览备份文件: {e.Message}");
}
}
private void DrawBackupPreview(BackupFileInfo backup)
{
try
{
string content = File.ReadAllText(backup.filePath, Encoding.UTF8);
string preview = content.Length > 500 ? content.Substring(0, 500) + "..." : content;
EditorGUILayout.HelpBox(
$"备份时间: {backup.backupTime:yyyy-MM-dd HH:mm:ss}\n\n" +
$"预览内容:\n{preview}",
MessageType.Info
);
GUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
if (GUILayout.Button("在编辑器中打开", EditorStyles.miniButton, GUILayout.Width(120)))
{
OpenBackupFile(backup.filePath);
}
GUILayout.EndHorizontal();
}
catch (Exception e)
{
EditorGUILayout.HelpBox($"无法预览备份文件: {e.Message}", MessageType.Error);
}
}
private void DrawStatusBar()
{
GUILayout.BeginHorizontal("Box");
var currentTab = GetCurrentTab();
if (currentTab != null)
{
if (data.selectedTabIndex == data.mainPageTabIndex)
{
string modeText = data.mainPageMode == MainPageMode.Home ? "主页" : "备份管理";
GUILayout.Label($"{modeText} - 共 {noteFiles.Count} 个笔记, {backupFiles.Count} 个备份", EditorStyles.miniLabel);
}
else
{
string status = $"字数: {currentTab.content.Length}";
if (currentTab.isModified)
status += " | 已修改";
if (!string.IsNullOrEmpty(currentTab.filePath))
{
string fileName = Path.GetFileName(currentTab.filePath);
status += $" | 文件: {fileName}";
}
GUILayout.Label(status, EditorStyles.miniLabel);
}
}
else
{
GUILayout.Label("没有打开的笔记", EditorStyles.miniLabel);
}
GUILayout.FlexibleSpace();
GUILayout.Label(data.autoSaveEnabled ? "自动保存: 开启" : "自动保存: 关闭",
EditorStyles.miniLabel);
GUILayout.EndHorizontal();
}
private void DrawSettingsPanel()
{
GUILayout.BeginVertical("Box");
EditorGUILayout.LabelField("设置", EditorStyles.boldLabel);
data.autoSaveEnabled = EditorGUILayout.Toggle("启用自动保存", data.autoSaveEnabled);
data.fontSize = EditorGUILayout.IntSlider("字体大小", data.fontSize, 10, 24);
data.wordWrap = EditorGUILayout.Toggle("自动换行", data.wordWrap);
data.showNoteList = EditorGUILayout.Toggle("显示笔记列表", data.showNoteList);
EditorGUILayout.Space();
GUILayout.Label("笔记文件夹:", EditorStyles.miniBoldLabel);
GUILayout.BeginHorizontal();
GUILayout.Label(data.notesFolder, EditorStyles.textField);
if (GUILayout.Button("浏览", GUILayout.Width(60)))
{
string newPath = EditorUtility.OpenFolderPanel("选择笔记文件夹", Application.dataPath, "");
if (!string.IsNullOrEmpty(newPath))
{
string relativePath = "Assets" + newPath.Replace(Application.dataPath, "");
if (relativePath != data.notesFolder)
{
data.notesFolder = relativePath;
RefreshNoteList();
}
}
}
GUILayout.EndHorizontal();
GUILayout.Label("备份文件夹:", EditorStyles.miniBoldLabel);
GUILayout.BeginHorizontal();
GUILayout.Label(data.backupFolder, EditorStyles.textField);
if (GUILayout.Button("浏览", GUILayout.Width(60)))
{
string newPath = EditorUtility.OpenFolderPanel("选择备份文件夹", Application.dataPath, "");
if (!string.IsNullOrEmpty(newPath))
{
string relativePath = "Assets" + newPath.Replace(Application.dataPath, "");
if (relativePath != data.backupFolder)
{
data.backupFolder = relativePath;
RefreshBackupList();
}
}
}
GUILayout.EndHorizontal();
EditorGUILayout.Space();
if (GUILayout.Button("导出所有笔记", GUILayout.Height(25)))
{
ExportAllNotes();
}
GUILayout.EndVertical();
}
#endregion
#region 笔记列表功能
private void RefreshNoteList()
{
noteFiles.Clear();
try
{
string fullPath = Path.GetFullPath(Path.Combine(Application.dataPath, "..", data.notesFolder));
if (Directory.Exists(fullPath))
{
foreach (string extension in textExtensions)
{
try
{
// 只搜索笔记文件夹,不搜索子文件夹
string[] files = Directory.GetFiles(fullPath, "*" + extension, SearchOption.TopDirectoryOnly);
foreach (string filePath in files)
{
// 检查是否是备份文件(文件名中包含"备份"字样)
string fileName = Path.GetFileName(filePath);
if (!fileName.Contains("_备份_"))
{
noteFiles.Add(filePath);
}
}
}
catch (Exception) { }
}
noteFiles.Sort((a, b) => File.GetLastWriteTime(b).CompareTo(File.GetLastWriteTime(a)));
}
}
catch (Exception e)
{
Debug.LogWarning($"刷新笔记列表失败: {e.Message}");
}
lastNoteListRefresh = DateTime.Now;
}
private string GetRelativePath(string fullPath)
{
try
{
string projectPath = Path.GetFullPath(Application.dataPath + "/..");
if (fullPath.StartsWith(projectPath))
{
return fullPath.Substring(projectPath.Length + 1);
}
}
catch { }
return fullPath;
}
private string GetFileSize(string filePath)
{
try
{
FileInfo fileInfo = new FileInfo(filePath);
long size = fileInfo.Length;
if (size < 1024) return $"{size} B";
else if (size < 1024 * 1024) return $"{(size / 1024.0):0.0} KB";
else return $"{(size / (1024.0 * 1024.0)):0.0} MB";
}
catch
{
return "未知大小";
}
}
private void DeleteNoteFile(string filePath)
{
try
{
// 检查文件是否已经打开
string fullPath = Path.GetFullPath(filePath);
bool isFileOpened = openedFiles.Contains(fullPath);
string message;
if (isFileOpened)
{
message = $"文件正在编辑中,删除后未保存的内容将丢失!\n\n确定要删除文件吗?\n{Path.GetFileName(filePath)}";
}
else
{
message = $"确定要删除文件吗?\n{Path.GetFileName(filePath)}";
}
// 确认对话框
if (EditorUtility.DisplayDialog("删除文件", message, "删除", "取消"))
{
// 如果文件已经打开,先关闭对应的标签页
if (isFileOpened)
{
for (int i = 0; i < data.tabs.Count; i++)
{
if (i != data.mainPageTabIndex &&
!string.IsNullOrEmpty(data.tabs[i].filePath) &&
Path.GetFullPath(data.tabs[i].filePath) == fullPath)
{
// 先强制关闭标签页,不保存
var tab = data.tabs[i];
if (!string.IsNullOrEmpty(tab.filePath))
{
openedFiles.Remove(Path.GetFullPath(tab.filePath));
}
data.tabs.RemoveAt(i);
// 更新特殊标签的索引
if (i < data.mainPageTabIndex) data.mainPageTabIndex--;
// 调整选中索引
if (data.selectedTabIndex >= i && data.selectedTabIndex > 0)
{
data.selectedTabIndex--;
}
else if (data.selectedTabIndex >= data.tabs.Count)
{
data.selectedTabIndex = data.tabs.Count - 1;
}
break;
}
}
}
// 删除物理文件
File.Delete(filePath);
// 刷新列表
RefreshNoteList();
// 更新界面
Repaint();
// 显示删除成功的提示
EditorUtility.DisplayDialog("删除成功", $"文件已删除:{Path.GetFileName(filePath)}", "确定");
}
}
catch (Exception e)
{
EditorUtility.DisplayDialog("删除失败", $"无法删除文件:{e.Message}", "确定");
}
}
#endregion
#region 备份管理功能
private void RefreshBackupList()
{
backupFiles.Clear();
try
{
string backupFolderPath = Path.GetFullPath(Path.Combine(Application.dataPath, "..", data.backupFolder));
if (Directory.Exists(backupFolderPath))
{
// 获取所有备份文件
foreach (string extension in textExtensions)
{
try
{
string[] files = Directory.GetFiles(backupFolderPath, "*" + extension, SearchOption.TopDirectoryOnly);
foreach (string filePath in files)
{
try
{
FileInfo fileInfo = new FileInfo(filePath);
string fileName = fileInfo.Name;
// 只显示备份文件(文件名中包含"备份"字样)
if (fileName.Contains("_备份_"))
{
// 解析原始文件名(去掉备份时间戳部分)
int backupIndex = fileName.IndexOf("_备份_");
string originalName = fileName.Substring(0, backupIndex) + fileInfo.Extension;
BackupFileInfo backupInfo = new BackupFileInfo
{
filePath = filePath,
fileName = fileName,
originalName = originalName,
backupTime = fileInfo.LastWriteTime,
fileSize = fileInfo.Length,
relativePath = GetRelativePath(filePath)
};
backupFiles.Add(backupInfo);
}
}
catch (Exception) { }
}
}
catch (Exception) { }
}
}
}
catch (Exception e)
{
Debug.LogWarning($"刷新备份列表失败: {e.Message}");
}
lastBackupListRefresh = DateTime.Now;
}
private string FormatFileSize(long size)
{
if (size < 1024) return $"{size} B";
else if (size < 1024 * 1024) return $"{(size / 1024.0):0.0} KB";
else return $"{(size / (1024.0 * 1024.0)):0.0} MB";
}
private void OpenBackupFile(string filePath)
{
try
{
string content = File.ReadAllText(filePath, Encoding.UTF8);
string fileName = Path.GetFileName(filePath);
// 创建新标签页打开备份文件
var newTab = new NoteTab();
newTab.title = "[备份] " + Path.GetFileNameWithoutExtension(fileName);
newTab.content = content;
newTab.filePath = filePath;
newTab.isModified = false; // 备份文件默认只读,不能修改
data.tabs.Add(newTab);
data.selectedTabIndex = data.tabs.Count - 1;
lastSelectedTabIndex = data.selectedTabIndex;
Repaint();
}
catch (Exception e)
{
EditorUtility.DisplayDialog("错误", $"无法打开备份文件: {e.Message}", "确定");
}
}
private void DeleteBackupFile(string filePath)
{
try
{
string fileName = Path.GetFileName(filePath);
if (EditorUtility.DisplayDialog("删除备份", $"确定要删除备份文件吗?\n{fileName}", "删除", "取消"))
{
File.Delete(filePath);
RefreshBackupList();
// 提示用户
EditorUtility.DisplayDialog("删除成功", $"已删除备份文件:{fileName}", "确定");
// 刷新显示
Repaint();
}
}
catch (Exception e)
{
EditorUtility.DisplayDialog("删除失败", $"无法删除备份文件:{e.Message}", "确定");
}
}
private void RestoreBackup(BackupFileInfo backup)
{
try
{
// 询问恢复位置
string defaultName = backup.originalName;
string restorePath = EditorUtility.SaveFilePanel("恢复备份", data.notesFolder, defaultName, "txt");
if (!string.IsNullOrEmpty(restorePath))
{
// 复制备份文件到指定位置
File.Copy(backup.filePath, restorePath, true);
// 刷新笔记列表
RefreshNoteList();
// 提示用户
EditorUtility.DisplayDialog("恢复成功",
$"已恢复备份文件:{backup.fileName}\n恢复到:{restorePath}", "确定");
// 打开恢复的文件
OpenNoteFile(restorePath);
}
}
catch (Exception e)
{
EditorUtility.DisplayDialog("恢复失败", $"无法恢复备份:{e.Message}", "确定");
}
}
private void OpenBackupFolder()
{
try
{
string backupFolderPath = Path.GetFullPath(Path.Combine(Application.dataPath, "..", data.backupFolder));
if (Directory.Exists(backupFolderPath))
{
// 在文件资源管理器中打开文件夹
EditorUtility.RevealInFinder(backupFolderPath);
}
else
{
EditorUtility.DisplayDialog("提示", "备份文件夹不存在,将创建新文件夹", "确定");
Directory.CreateDirectory(backupFolderPath);
EditorUtility.RevealInFinder(backupFolderPath);
}
}
catch (Exception e)
{
EditorUtility.DisplayDialog("错误", $"无法打开备份文件夹:{e.Message}", "确定");
}
}
private void CleanupOldBackups(int days)
{
try
{
string backupFolderPath = Path.GetFullPath(Path.Combine(Application.dataPath, "..", data.backupFolder));
if (Directory.Exists(backupFolderPath))
{
DateTime cutoffDate = DateTime.Now.AddDays(-days);
int deletedCount = 0;
foreach (var extension in textExtensions)
{
string[] files = Directory.GetFiles(backupFolderPath, "*" + extension, SearchOption.TopDirectoryOnly);
foreach (string filePath in files)
{
try
{
FileInfo fileInfo = new FileInfo(filePath);
if (fileInfo.LastWriteTime < cutoffDate)
{
File.Delete(filePath);
deletedCount++;
}
}
catch (Exception) { }
}
}
// 刷新备份列表
RefreshBackupList();
// 提示用户
if (deletedCount > 0)
{
EditorUtility.DisplayDialog("清理完成",
$"已清理 {deletedCount} 个 {days} 天前的备份文件", "确定");
}
else
{
EditorUtility.DisplayDialog("清理完成",
$"没有找到 {days} 天前的备份文件", "确定");
}
// 刷新显示
Repaint();
}
}
catch (Exception e)
{
EditorUtility.DisplayDialog("清理失败", $"清理备份文件失败:{e.Message}", "确定");
}
}
// 备份当前笔记
private void BackupCurrentNote()
{
var currentTab = GetCurrentTab();
if (currentTab == null || string.IsNullOrEmpty(currentTab.filePath))
{
EditorUtility.DisplayDialog("备份失败", "当前笔记没有保存到文件,请先保存", "确定");
return;
}
try
{
// 确保备份文件夹存在
string backupFolderPath = Path.GetFullPath(Path.Combine(Application.dataPath, "..", data.backupFolder));
if (!Directory.Exists(backupFolderPath))
{
Directory.CreateDirectory(backupFolderPath);
}
// 获取文件信息
FileInfo fileInfo = new FileInfo(currentTab.filePath);
string fileName = fileInfo.Name;
string fileNameWithoutExt = Path.GetFileNameWithoutExtension(fileName);
string fileExt = fileInfo.Extension;
// 生成备份文件名(添加时间戳)
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
string backupFileName = $"{fileNameWithoutExt}_备份_{timestamp}{fileExt}";
string backupFilePath = Path.Combine(backupFolderPath, backupFileName);
// 复制文件
File.Copy(currentTab.filePath, backupFilePath, true);
// 刷新备份列表
RefreshBackupList();
// 提示用户
EditorUtility.DisplayDialog("备份成功",
$"已成功备份当前笔记:{fileName}\n备份到:{backupFileName}", "确定");
// 刷新显示
Repaint();
}
catch (Exception e)
{
EditorUtility.DisplayDialog("备份失败", $"无法备份文件:{e.Message}", "确定");
}
}
// 备份所有笔记
private void BackupAllNotes()
{
try
{
// 确保备份文件夹存在
string backupFolderPath = Path.GetFullPath(Path.Combine(Application.dataPath, "..", data.backupFolder));
if (!Directory.Exists(backupFolderPath))
{
Directory.CreateDirectory(backupFolderPath);
}
int backupCount = 0;
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
// 备份所有已打开的且有文件路径的笔记
foreach (var tab in data.tabs)
{
if (!string.IsNullOrEmpty(tab.filePath) && tab.title != "主页")
{
try
{
FileInfo fileInfo = new FileInfo(tab.filePath);
string fileName = fileInfo.Name;
string fileNameWithoutExt = Path.GetFileNameWithoutExtension(fileName);
string fileExt = fileInfo.Extension;
string backupFileName = $"{fileNameWithoutExt}_备份_{timestamp}{fileExt}";
string backupFilePath = Path.Combine(backupFolderPath, backupFileName);
File.Copy(tab.filePath, backupFilePath, true);
backupCount++;
}
catch (Exception) { }
}
}
// 刷新备份列表
RefreshBackupList();
// 提示用户
if (backupCount > 0)
{
EditorUtility.DisplayDialog("备份成功",
$"已成功备份 {backupCount} 个笔记文件\n备份时间:{DateTime.Now:yyyy-MM-dd HH:mm:ss}", "确定");
}
else
{
EditorUtility.DisplayDialog("备份失败", "没有找到可以备份的笔记文件", "确定");
}
// 刷新显示
Repaint();
}
catch (Exception e)
{
EditorUtility.DisplayDialog("备份失败", $"无法备份文件:{e.Message}", "确定");
}
}
#endregion
#region 标签管理
private NoteTab GetCurrentTab()
{
if (data == null || data.tabs.Count == 0) return null;
if (data.selectedTabIndex < 0)
data.selectedTabIndex = 0;
else if (data.selectedTabIndex >= data.tabs.Count)
data.selectedTabIndex = data.tabs.Count - 1;
return data.tabs[data.selectedTabIndex];
}
private void EnsureMainPageTab()
{
// 如果没有主页标签,创建一个
if (data.mainPageTabIndex < 0 || data.mainPageTabIndex >= data.tabs.Count || data.tabs[data.mainPageTabIndex].title != "主页")
{
CreateMainPageTab();
}
}
private void CreateMainPageTab()
{
// 检查是否已经有主页标签
for (int i = 0; i < data.tabs.Count; i++)
{
if (data.tabs[i].title == "主页")
{
data.mainPageTabIndex = i;
return;
}
}
// 创建新的主页标签
var mainTab = new NoteTab();
mainTab.title = "主页";
mainTab.content = ""; // 主页页面不需要内容
data.tabs.Add(mainTab);
data.mainPageTabIndex = data.tabs.Count - 1;
// 如果这是第一个标签,选中它
if (data.tabs.Count == 1)
{
data.selectedTabIndex = 0;
}
}
private void CreateNewTab()
{
if (data == null) data = new NotepadData();
var newTab = new NoteTab();
newTab.title = "笔记 " + (data.tabs.Count);
data.tabs.Add(newTab);
data.selectedTabIndex = data.tabs.Count - 1;
lastSelectedTabIndex = data.selectedTabIndex;
Repaint();
}
private void CloseTab(int index)
{
if (data == null || index < 0 || index >= data.tabs.Count) return;
var tab = data.tabs[index];
// 不允许关闭主页标签
if (index == data.mainPageTabIndex)
{
EditorUtility.DisplayDialog("提示", "主页标签不能关闭", "确定");
return;
}
if (tab.isModified)
{
int result = EditorUtility.DisplayDialogComplex(
"关闭笔记",
$"'{tab.title}' 有未保存的更改。",
"保存并关闭",
"直接关闭",
"取消"
);
if (result == 0)
{
SaveTab(index);
}
else if (result == 2)
{
return;
}
}
if (!string.IsNullOrEmpty(tab.filePath))
{
openedFiles.Remove(Path.GetFullPath(tab.filePath));
}
// 移除标签
data.tabs.RemoveAt(index);
// 更新特殊标签的索引
if (index < data.mainPageTabIndex)
{
data.mainPageTabIndex--;
}
// 如果关闭后没有普通标签,切换到主页标签
if (data.tabs.Count == 1 && data.mainPageTabIndex >= 0)
{
data.selectedTabIndex = data.mainPageTabIndex;
}
else if (data.tabs.Count > 0)
{
if (data.selectedTabIndex >= index && data.selectedTabIndex > 0)
{
data.selectedTabIndex--;
}
else if (data.selectedTabIndex >= data.tabs.Count)
{
data.selectedTabIndex = data.tabs.Count - 1;
}
}
lastSelectedTabIndex = data.selectedTabIndex;
Repaint();
}
private void CloseAllTabs()
{
if (data == null || data.tabs.Count <= 1) return; // 至少有主页标签
// 只关闭普通标签,保留主页标签
List<int> tabsToClose = new List<int>();
for (int i = 0; i < data.tabs.Count; i++)
{
if (i != data.mainPageTabIndex)
{
tabsToClose.Add(i);
}
}
bool hasUnsavedChanges = tabsToClose.Any(i => data.tabs[i].isModified);
if (hasUnsavedChanges)
{
int result = EditorUtility.DisplayDialogComplex(
"关闭所有标签",
"有未保存的笔记。是否保存更改?",
"保存并关闭",
"直接关闭",
"取消"
);
if (result == 0)
{
foreach (int i in tabsToClose)
{
if (data.tabs[i].isModified)
{
SaveTab(i);
}
}
}
else if (result == 2)
{
return;
}
}
// 关闭所有普通标签
for (int i = tabsToClose.Count - 1; i >= 0; i--)
{
int tabIndex = tabsToClose[i];
if (!string.IsNullOrEmpty(data.tabs[tabIndex].filePath))
{
openedFiles.Remove(Path.GetFullPath(data.tabs[tabIndex].filePath));
}
data.tabs.RemoveAt(tabIndex);
}
// 更新特殊标签索引
data.mainPageTabIndex = 0;
data.selectedTabIndex = data.mainPageTabIndex;
lastSelectedTabIndex = data.selectedTabIndex;
Repaint();
}
#endregion
#region 文件操作
private void OpenNoteFile()
{
// 修改这里:使用笔记文件夹路径作为默认打开路径
string defaultPath = Path.Combine(Application.dataPath, "Editor", "Notes");
// 如果文件夹不存在则创建
if (!Directory.Exists(defaultPath))
{
Directory.CreateDirectory(defaultPath);
}
string path = EditorUtility.OpenFilePanel("打开笔记文件", defaultPath, "txt,md,json,cs");
if (!string.IsNullOrEmpty(path))
{
OpenNoteFile(path);
}
}
private void OpenNoteFile(string filePath)
{
try
{
string fullPath = Path.GetFullPath(filePath);
// 检查文件是否已经打开
if (openedFiles.Contains(fullPath))
{
for (int i = 0; i < data.tabs.Count; i++)
{
if (i != data.mainPageTabIndex &&
!string.IsNullOrEmpty(data.tabs[i].filePath) &&
Path.GetFullPath(data.tabs[i].filePath) == fullPath)
{
data.selectedTabIndex = i;
lastSelectedTabIndex = data.selectedTabIndex;
Repaint();
return;
}
}
}
string content = File.ReadAllText(filePath, Encoding.UTF8);
var newTab = new NoteTab();
newTab.title = Path.GetFileNameWithoutExtension(filePath);
newTab.content = content;
newTab.filePath = filePath;
newTab.isModified = false;
data.tabs.Add(newTab);
data.selectedTabIndex = data.tabs.Count - 1;
lastSelectedTabIndex = data.selectedTabIndex;
openedFiles.Add(fullPath);
Repaint();
}
catch (Exception e)
{
EditorUtility.DisplayDialog("错误", $"无法打开文件: {e.Message}", "确定");
}
}
private void SaveCurrentTab()
{
var currentTab = GetCurrentTab();
if (currentTab == null || data.selectedTabIndex == data.mainPageTabIndex)
{
EditorUtility.DisplayDialog("保存", "没有活动的笔记可以保存", "确定");
return;
}
SaveTab(data.selectedTabIndex);
}
private void SaveTab(int index)
{
if (data == null || index < 0 || index >= data.tabs.Count ||
index == data.mainPageTabIndex) return;
var tab = data.tabs[index];
if (string.IsNullOrEmpty(tab.filePath))
{
SaveAsCurrentTab();
return;
}
try
{
string directory = Path.GetDirectoryName(tab.filePath);
if (!Directory.Exists(directory))
Directory.CreateDirectory(directory);
File.WriteAllText(tab.filePath, tab.content, Encoding.UTF8);
tab.isModified = false;
tab.lastModified = DateTime.Now;
tab.lastSavedHash = CalculateContentHash(tab.content);
AssetDatabase.Refresh();
RefreshNoteList();
Repaint();
}
catch (Exception e)
{
EditorUtility.DisplayDialog("错误", $"保存失败: {e.Message}", "确定");
}
}
private void SaveAsCurrentTab()
{
var currentTab = GetCurrentTab();
if (currentTab == null || data.selectedTabIndex == data.mainPageTabIndex)
{
EditorUtility.DisplayDialog("另存为", "没有活动的笔记可以另存为", "确定");
return;
}
// 修改这里:使用笔记文件夹路径作为默认保存路径
string defaultPath = Path.Combine(Application.dataPath, "Editor", "Notes");
// 如果文件夹不存在则创建
if (!Directory.Exists(defaultPath))
{
Directory.CreateDirectory(defaultPath);
}
string defaultName = string.IsNullOrEmpty(currentTab.title) ? "NewNote.txt" : currentTab.title + ".txt";
string defaultFilePath = Path.Combine(defaultPath, defaultName);
string notesPath = Path.Combine(Application.dataPath, "Editor", "Notes");
if (!Directory.Exists(notesPath))
{
Directory.CreateDirectory(notesPath);
}
string path = EditorUtility.SaveFilePanel("另存为", notesPath, defaultName, "txt");
if (!string.IsNullOrEmpty(path))
{
if (!string.IsNullOrEmpty(currentTab.filePath))
{
openedFiles.Remove(Path.GetFullPath(currentTab.filePath));
}
currentTab.filePath = path;
currentTab.title = Path.GetFileNameWithoutExtension(path);
openedFiles.Add(Path.GetFullPath(path));
SaveCurrentTab();
}
}
#endregion
#region 自动保存系统
private void ScheduleAutoSave(NoteTab tab)
{
int tabIndex = data.tabs.IndexOf(tab);
if (tabIndex == -1) return;
scheduledSaves[tabIndex] = EditorApplication.timeSinceStartup + 2.0;
}
private void OnEditorUpdate()
{
CheckScheduledSaves();
if (data != null && data.autoSaveEnabled && (DateTime.Now - lastAutoSaveTime).TotalSeconds >= AUTO_SAVE_INTERVAL)
{
AutoSaveAllModifiedTabs();
lastAutoSaveTime = DateTime.Now;
}
}
private void CheckScheduledSaves()
{
if (scheduledSaves.Count == 0) return;
List<int> toRemove = new List<int>();
double currentTime = EditorApplication.timeSinceStartup;
foreach (var kvp in scheduledSaves)
{
if (currentTime >= kvp.Value)
{
int tabIndex = kvp.Key;
if (tabIndex < data.tabs.Count)
{
var tab = data.tabs[tabIndex];
if (tab.isModified)
{
string currentHash = CalculateContentHash(tab.content);
if (currentHash != tab.lastSavedHash)
{
PerformAutoSave(tab, tabIndex);
tab.lastSavedHash = currentHash;
}
}
}
toRemove.Add(tabIndex);
}
}
foreach (int index in toRemove)
{
scheduledSaves.Remove(index);
}
}
private void AutoSaveAllModifiedTabs()
{
for (int i = 0; i < data.tabs.Count; i++)
{
if (i == data.mainPageTabIndex) continue; // 跳过主页标签
var tab = data.tabs[i];
if (tab.isModified)
{
PerformAutoSave(tab, i);
}
}
}
private void PerformAutoSave(NoteTab tab, int tabIndex)
{
if (string.IsNullOrEmpty(tab.filePath))
{
SaveToTempFile(tab);
}
else
{
SaveTabSilently(tabIndex);
}
}
private void SaveTabSilently(int index)
{
if (index < 0 || index >= data.tabs.Count ||
index == data.mainPageTabIndex) return;
var tab = data.tabs[index];
try
{
string directory = Path.GetDirectoryName(tab.filePath);
if (!Directory.Exists(directory))
Directory.CreateDirectory(directory);
File.WriteAllText(tab.filePath, tab.content, Encoding.UTF8);
tab.isModified = false;
tab.lastModified = DateTime.Now;
tab.lastSavedHash = CalculateContentHash(tab.content);
}
catch (Exception e)
{
Debug.LogWarning($"自动保存失败: {e.Message}");
}
}
private void SaveToTempFile(NoteTab tab)
{
try
{
string tempFolder = Path.Combine(Application.temporaryCachePath, "UnityNotepad/Autosave");
if (!Directory.Exists(tempFolder))
Directory.CreateDirectory(tempFolder);
string safeTitle = string.IsNullOrEmpty(tab.title) ?
"Untitled" :
System.Text.RegularExpressions.Regex.Replace(tab.title, @"[^\w]", "_");
string fileName = $"{safeTitle}_{DateTime.Now:yyyyMMdd_HHmmss}.txt";
string tempFile = Path.Combine(tempFolder, fileName);
File.WriteAllText(tempFile, tab.content, Encoding.UTF8);
CleanupOldTempFiles(tempFolder, 10);
}
catch { }
}
private void SaveAllModifiedTabs()
{
if (data == null) return;
for (int i = 0; i < data.tabs.Count; i++)
{
if (i == data.mainPageTabIndex) continue;
var tab = data.tabs[i];
if (tab.isModified)
{
SaveTabSilently(i);
}
}
}
#endregion
#region 辅助方法
private void CloseNotepad()
{
bool hasUnsavedChanges = data?.tabs?.Any(t => t.isModified) ?? false;
if (hasUnsavedChanges)
{
int result = EditorUtility.DisplayDialogComplex(
"关闭笔记本",
"有未保存的笔记。是否保存更改?",
"保存并关闭",
"直接关闭",
"取消"
);
if (result == 0)
{
SaveAllModifiedTabs();
Close();
}
else if (result == 1)
{
Close();
}
}
else
{
Close();
}
}
private string CalculateContentHash(string content)
{
using (var sha256 = SHA256.Create())
{
byte[] bytes = Encoding.UTF8.GetBytes(content);
byte[] hash = sha256.ComputeHash(bytes);
return Convert.ToBase64String(hash);
}
}
private void CleanupOldTempFiles(string folder, int keepCount)
{
try
{
var files = Directory.GetFiles(folder, "*.txt")
.Select(f => new FileInfo(f))
.OrderByDescending(f => f.LastWriteTime)
.ToList();
for (int i = keepCount; i < files.Count; i++)
{
files[i].Delete();
}
}
catch { }
}
private void CreateTextAreaStyle()
{
textAreaStyle = new GUIStyle(EditorStyles.textArea)
{
wordWrap = true,
fontSize = 14,
padding = new RectOffset(10, 10, 10, 10),
margin = new RectOffset(5, 5, 5, 5)
};
}
private void AppendToCurrentTab(string text)
{
var currentTab = GetCurrentTab();
if (currentTab != null && data.selectedTabIndex != data.mainPageTabIndex &&
!string.IsNullOrEmpty(text))
{
currentTab.content += text;
currentTab.isModified = true;
ScheduleAutoSave(currentTab);
Repaint();
}
}
private void LoadData()
{
string json = EditorPrefs.GetString(DATA_KEY, "");
if (!string.IsNullOrEmpty(json))
{
try
{
data = JsonUtility.FromJson<NotepadData>(json);
UpdateOpenedFiles();
}
catch (Exception e)
{
Debug.LogError($"加载数据失败: {e.Message}");
data = new NotepadData();
}
}
else
{
data = new NotepadData();
// 修改这里:设置默认文件夹
data.notesFolder = "Assets/Editor/Notes/";
data.backupFolder = "Assets/Editor/Notes/Backup/";
}
// 确保文件夹存在
if (!string.IsNullOrEmpty(data.notesFolder))
{
string notesFullPath = Path.GetFullPath(Path.Combine(Application.dataPath, "..", data.notesFolder));
if (!Directory.Exists(notesFullPath))
{
Directory.CreateDirectory(notesFullPath);
}
}
if (!string.IsNullOrEmpty(data.backupFolder))
{
string backupFullPath = Path.GetFullPath(Path.Combine(Application.dataPath, "..", data.backupFolder));
if (!Directory.Exists(backupFullPath))
{
Directory.CreateDirectory(backupFullPath);
}
}
}
private void SaveData()
{
if (data == null) return;
try
{
string json = JsonUtility.ToJson(data, true);
EditorPrefs.SetString(DATA_KEY, json);
}
catch (Exception e)
{
Debug.LogError($"保存数据失败: {e.Message}");
}
}
#endregion
#region 搜索功能
private void PerformSearch()
{
searchResults.Clear();
currentSearchIndex = 0;
if (string.IsNullOrEmpty(searchText) ||
data.selectedTabIndex == data.mainPageTabIndex) return;
var currentTab = GetCurrentTab();
if (currentTab == null || string.IsNullOrEmpty(currentTab.content)) return;
string content = currentTab.content.ToLower();
string searchLower = searchText.ToLower();
int index = 0;
while ((index = content.IndexOf(searchLower, index)) != -1)
{
searchResults.Add(index);
index += searchLower.Length;
}
if (searchResults.Count > 0)
{
ScrollToSearchResult(0);
}
}
private void NavigateSearch(int direction)
{
if (searchResults.Count == 0) return;
currentSearchIndex = (currentSearchIndex + direction + searchResults.Count) % searchResults.Count;
ScrollToSearchResult(currentSearchIndex);
}
private void ScrollToSearchResult(int resultIndex)
{
if (resultIndex < 0 || resultIndex >= searchResults.Count) return;
var currentTab = GetCurrentTab();
if (currentTab == null) return;
int charPosition = searchResults[resultIndex];
int linesBefore = currentTab.content.Substring(0, Math.Min(charPosition, currentTab.content.Length)).Split('\n').Length - 1;
float lineHeight = data.fontSize * 1.5f;
float targetY = linesBefore * lineHeight;
currentTab.scrollPosition.y = Mathf.Max(0, targetY - position.height / 3);
Repaint();
}
#endregion
#region 快捷键
private void HandleKeyboardShortcuts()
{
Event e = Event.current;
if (e.type == EventType.KeyDown)
{
bool control = e.control || e.command;
if (control)
{
switch (e.keyCode)
{
case KeyCode.S:
SaveCurrentTab();
e.Use();
break;
case KeyCode.N:
CreateNewTab();
e.Use();
break;
case KeyCode.W:
if (data.tabs.Count > 0 && data.selectedTabIndex != data.mainPageTabIndex)
{
CloseTab(data.selectedTabIndex);
e.Use();
}
break;
case KeyCode.Tab:
if (e.shift)
{
if (data.tabs.Count > 1)
{
data.selectedTabIndex = (data.selectedTabIndex - 1 + data.tabs.Count) % data.tabs.Count;
e.Use();
}
}
else
{
if (data.tabs.Count > 1)
{
data.selectedTabIndex = (data.selectedTabIndex + 1) % data.tabs.Count;
e.Use();
}
}
break;
case KeyCode.F:
if (e.shift)
{
EditorGUI.FocusTextInControl("SearchField");
e.Use();
}
break;
case KeyCode.H:
if (data.mainPageTabIndex >= 0 && data.mainPageTabIndex < data.tabs.Count)
{
data.selectedTabIndex = data.mainPageTabIndex;
data.mainPageMode = MainPageMode.Home;
e.Use();
}
break;
case KeyCode.B:
if (data.mainPageTabIndex >= 0 && data.mainPageTabIndex < data.tabs.Count)
{
data.selectedTabIndex = data.mainPageTabIndex;
data.mainPageMode = MainPageMode.Backup;
e.Use();
}
break;
}
}
}
}
#endregion
#region 导入导出
private void ExportAllNotes()
{
if (data == null || data.tabs.Count <= 1) // 只有主页标签
{
EditorUtility.DisplayDialog("导出", "没有可导出的笔记", "确定");
return;
}
string exportFolder = EditorUtility.SaveFolderPanel("导出笔记", Application.dataPath, "NotesExport");
if (!string.IsNullOrEmpty(exportFolder))
{
try
{
int exported = 0;
foreach (var tab in data.tabs)
{
if (!string.IsNullOrEmpty(tab.content) && tab.title != "主页")
{
string safeTitle = System.Text.RegularExpressions.Regex.Replace(tab.title, @"[^\w\u4e00-\u9fa5]", "_");
string fileName = $"{safeTitle}_{DateTime.Now:yyyyMMdd_HHmmss}.txt";
string filePath = Path.Combine(exportFolder, fileName);
File.WriteAllText(filePath, tab.content, Encoding.UTF8);
exported++;
}
}
EditorUtility.DisplayDialog("导出完成", $"已导出 {exported} 个笔记到:\n{exportFolder}", "确定");
}
catch (Exception e)
{
EditorUtility.DisplayDialog("导出失败", e.Message, "确定");
}
}
}
#endregion
}
版本2
cs
using UnityEngine;
using UnityEditor;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Linq;
using System.Security.Cryptography;
public class UnityNotepad : EditorWindow
{
// 数据模型
[System.Serializable]
private class NoteTab
{
public string title = "新笔记";
public string content = "";
public Vector2 scrollPosition;
public bool isModified = false;
public string filePath = "";
public DateTime lastModified;
public string lastSavedHash = "";
public DateTime lastChangeTime;
public NoteTab()
{
lastModified = DateTime.Now;
lastChangeTime = DateTime.Now;
}
}
// 备份文件信息
private class BackupFileInfo
{
public string filePath;
public string fileName;
public string originalName;
public DateTime backupTime;
public long fileSize;
public string relativePath;
}
// 主页模式
private enum MainPageMode { Home, Backup }
// 序列化存储
[System.Serializable]
private class NotepadData
{
public List<NoteTab> tabs = new List<NoteTab>();
public int selectedTabIndex = 0;
public bool autoSaveEnabled = true;
public string notesFolder = "Assets/Editor/Notes/";
public string backupFolder = "Assets/Editor/Notes/Backup/";
public int fontSize = 14;
public bool wordWrap = true;
public bool showNoteList = true;
public int mainPageTabIndex = -1; // 主页标签页索引
public MainPageMode mainPageMode = MainPageMode.Home; // 主页当前显示模式
}
// 编辑器状态
private NotepadData data;
private Vector2 tabsScrollPos;
private bool showSettings = false;
private GUIStyle textAreaStyle;
private string searchText = "";
private List<int> searchResults = new List<int>();
private int currentSearchIndex = 0;
private int lastSelectedTabIndex = -1;
private const string DATA_KEY = "UnityNotepad_Data_Fixed";
private const int AUTO_SAVE_INTERVAL = 30;
private DateTime lastAutoSaveTime;
private Dictionary<int, double> scheduledSaves = new Dictionary<int, double>();
private HashSet<string> openedFiles = new HashSet<string>();
// 焦点管理 - 完全禁用自动焦点
private string textAreaControlName = "NoteTextArea";
private bool neverAutoFocus = true; // 完全禁用自动焦点
// 笔记列表管理
private List<string> noteFiles = new List<string>();
private Vector2 noteListScrollPos;
private string[] textExtensions = { ".txt", ".md", ".json", ".cs", ".xml", ".yaml", ".yml", ".html", ".css", ".js" };
private DateTime lastNoteListRefresh = DateTime.MinValue;
private const double NOTE_LIST_REFRESH_INTERVAL = 2.0;
// 备份文件管理
private List<BackupFileInfo> backupFiles = new List<BackupFileInfo>();
private Vector2 backupListScrollPos;
private DateTime lastBackupListRefresh = DateTime.MinValue;
private BackupFileInfo selectedBackupForPreview = null;
// 初始化窗口
[MenuItem("Tools/Unity Notepad %#m")]
public static void ShowWindow()
{
var window = GetWindow<UnityNotepad>();
window.titleContent = new GUIContent("Unity Notepad", EditorGUIUtility.IconContent("TextAsset Icon").image);
window.minSize = new Vector2(500, 400);
window.Show();
}
private void OnEnable()
{
LoadData();
CreateTextAreaStyle();
lastAutoSaveTime = DateTime.Now;
EditorApplication.update += OnEditorUpdate;
UpdateOpenedFiles();
RefreshNoteList();
RefreshBackupList();
// 确保有主页标签页
EnsureMainPageTab();
}
private void OnDisable()
{
SaveAllModifiedTabs();
SaveData();
EditorApplication.update -= OnEditorUpdate;
}
private void OnFocus()
{
RefreshNoteList();
RefreshBackupList();
}
private void UpdateOpenedFiles()
{
openedFiles.Clear();
if (data != null && data.tabs != null)
{
foreach (var tab in data.tabs)
{
if (!string.IsNullOrEmpty(tab.filePath))
{
openedFiles.Add(Path.GetFullPath(tab.filePath));
}
}
}
}
private void OnGUI()
{
if (data == null)
{
data = new NotepadData();
}
// 处理键盘快捷键
HandleKeyboardShortcuts();
// 绘制界面
DrawToolbar();
DrawTabsHeader();
DrawContentArea();
DrawStatusBar();
}
#region GUI 绘制方法
private void DrawToolbar()
{
GUILayout.BeginHorizontal(EditorStyles.toolbar);
// 文件操作按钮
if (GUILayout.Button("新建", EditorStyles.toolbarButton, GUILayout.Width(40)))
{
CreateNewTab();
}
if (GUILayout.Button("打开", EditorStyles.toolbarButton, GUILayout.Width(40)))
{
OpenNoteFile();
}
if (GUILayout.Button("保存", EditorStyles.toolbarButton, GUILayout.Width(40)))
{
SaveCurrentTab();
}
if (GUILayout.Button("另存为", EditorStyles.toolbarButton, GUILayout.Width(50)))
{
SaveAsCurrentTab();
}
GUILayout.Space(10);
// 编辑操作按钮
if (GUILayout.Button("复制", EditorStyles.toolbarButton, GUILayout.Width(40)))
{
var currentTab = GetCurrentTab();
if (currentTab != null && !string.IsNullOrEmpty(currentTab.content))
EditorGUIUtility.systemCopyBuffer = currentTab.content;
}
if (GUILayout.Button("粘贴", EditorStyles.toolbarButton, GUILayout.Width(40)))
{
AppendToCurrentTab(EditorGUIUtility.systemCopyBuffer);
}
GUILayout.Space(10);
// 搜索
GUILayout.Label("搜索:", EditorStyles.miniLabel);
string newSearchText = GUILayout.TextField(searchText, GUILayout.Width(150));
if (newSearchText != searchText)
{
searchText = newSearchText;
PerformSearch();
}
if (searchResults.Count > 0)
{
GUILayout.Label($"{currentSearchIndex + 1}/{searchResults.Count}", EditorStyles.miniLabel);
if (GUILayout.Button("↑", EditorStyles.toolbarButton, GUILayout.Width(20)))
NavigateSearch(-1);
if (GUILayout.Button("↓", EditorStyles.toolbarButton, GUILayout.Width(20)))
NavigateSearch(1);
}
GUILayout.FlexibleSpace();
// 设置按钮
showSettings = GUILayout.Toggle(showSettings, "设置", EditorStyles.toolbarButton, GUILayout.Width(40));
// 主页按钮
if (GUILayout.Button("主页", EditorStyles.toolbarButton, GUILayout.Width(40)))
{
if (data.mainPageTabIndex >= 0 && data.mainPageTabIndex < data.tabs.Count)
{
data.selectedTabIndex = data.mainPageTabIndex;
data.mainPageMode = MainPageMode.Home;
}
}
// 备份按钮
if (GUILayout.Button("备份", EditorStyles.toolbarButton, GUILayout.Width(40)))
{
if (data.mainPageTabIndex >= 0 && data.mainPageTabIndex < data.tabs.Count)
{
data.selectedTabIndex = data.mainPageTabIndex;
data.mainPageMode = MainPageMode.Backup;
}
}
// 关闭笔记本按钮
if (GUILayout.Button("关闭", EditorStyles.toolbarButton, GUILayout.Width(40)))
{
CloseNotepad();
}
GUILayout.EndHorizontal();
if (showSettings)
DrawSettingsPanel();
}
private void DrawTabsHeader()
{
// 确保有标签页(至少有一个主页标签)
if (data.tabs.Count == 0)
{
CreateMainPageTab();
}
GUILayout.BeginHorizontal(EditorStyles.toolbar);
// 显示所有普通笔记标签
for (int i = 0; i < data.tabs.Count; i++)
{
// 跳过主页标签
if (i == data.mainPageTabIndex) continue;
var tab = data.tabs[i];
string tabName = (string.IsNullOrEmpty(tab.title) ? "未命名" : tab.title) + (tab.isModified ? " *" : "");
if (DrawTabButton(tabName, i == data.selectedTabIndex, 120, i))
{
data.selectedTabIndex = i;
// 切换标签时清除焦点,避免全选
GUI.FocusControl(null);
}
}
GUILayout.FlexibleSpace();
// 添加新标签按钮
if (GUILayout.Button("+", EditorStyles.toolbarButton, GUILayout.Width(30)))
{
CreateNewTab();
}
// 关闭所有标签按钮
if (GUILayout.Button("× 全部", EditorStyles.toolbarButton, GUILayout.Width(50)))
{
CloseAllTabs();
}
GUILayout.EndHorizontal();
}
private bool DrawTabButton(string label, bool isSelected, float width, int tabIndex)
{
bool clicked = false;
Rect tabRect = GUILayoutUtility.GetRect(
new GUIContent(label + " ×"),
EditorStyles.toolbarButton,
GUILayout.Width(width),
GUILayout.Height(20)
);
if (isSelected)
{
EditorGUI.DrawRect(tabRect, new Color(0.2f, 0.4f, 0.8f, 0.8f));
}
Rect closeButtonRect = new Rect(
tabRect.xMax - 20,
tabRect.y + (tabRect.height - 12) / 2,
12,
12
);
Rect textRect = new Rect(
tabRect.x,
tabRect.y,
tabRect.width - 20,
tabRect.height
);
Event currentEvent = Event.current;
if (currentEvent.type == EventType.MouseDown && tabRect.Contains(currentEvent.mousePosition))
{
if (currentEvent.button == 0)
{
if (closeButtonRect.Contains(currentEvent.mousePosition))
{
CloseTab(tabIndex);
currentEvent.Use();
}
else
{
clicked = true;
currentEvent.Use();
}
}
}
GUIStyle textStyle = new GUIStyle(EditorStyles.toolbarButton)
{
alignment = TextAnchor.MiddleLeft,
padding = new RectOffset(5, 0, 0, 0)
};
GUI.Label(textRect, label, textStyle);
GUIStyle closeStyle = new GUIStyle(EditorStyles.miniLabel)
{
alignment = TextAnchor.MiddleCenter,
fontSize = 10,
normal = { textColor = closeButtonRect.Contains(currentEvent.mousePosition) ? Color.red : Color.gray }
};
GUI.Label(closeButtonRect, "×", closeStyle);
return clicked;
}
private void DrawContentArea()
{
if (data.tabs.Count == 0)
{
CreateMainPageTab();
return;
}
var currentTab = GetCurrentTab();
if (currentTab == null)
{
data.selectedTabIndex = Mathf.Max(0, data.tabs.Count - 1);
return;
}
// 检查标签页是否切换
if (lastSelectedTabIndex != data.selectedTabIndex)
{
lastSelectedTabIndex = data.selectedTabIndex;
// 标签页切换时清除焦点
GUI.FocusControl(null);
}
EditorGUI.BeginChangeCheck();
currentTab.scrollPosition = EditorGUILayout.BeginScrollView(currentTab.scrollPosition);
// 如果是主页标签,绘制主页页面
if (data.selectedTabIndex == data.mainPageTabIndex)
{
DrawMainPage();
}
else
{
// 正常笔记标签
textAreaStyle.fontSize = data.fontSize;
textAreaStyle.wordWrap = data.wordWrap;
// 使用TextArea但不自动聚焦 - 这里的关键是永远不要调用GUI.SetNextControlName
string newContent = EditorGUILayout.TextArea(
currentTab.content,
textAreaStyle,
GUILayout.ExpandHeight(true)
);
if (EditorGUI.EndChangeCheck() && newContent != currentTab.content)
{
currentTab.content = newContent;
currentTab.isModified = true;
currentTab.lastModified = DateTime.Now;
currentTab.lastChangeTime = DateTime.Now;
ScheduleAutoSave(currentTab);
}
}
EditorGUILayout.EndScrollView();
}
private void DrawMainPage()
{
GUILayout.BeginVertical();
// 顶部导航栏
GUILayout.BeginHorizontal(EditorStyles.toolbar);
bool homeSelected = data.mainPageMode == MainPageMode.Home;
bool backupSelected = data.mainPageMode == MainPageMode.Backup;
// 主页按钮
if (GUILayout.Toggle(homeSelected, " 主页 ", EditorStyles.toolbarButton))
{
data.mainPageMode = MainPageMode.Home;
}
// 备份按钮
if (GUILayout.Toggle(backupSelected, " 备份管理 ", EditorStyles.toolbarButton))
{
data.mainPageMode = MainPageMode.Backup;
}
GUILayout.FlexibleSpace();
// 快速操作按钮
if (data.mainPageMode == MainPageMode.Home)
{
if (GUILayout.Button("新建笔记", EditorStyles.toolbarButton, GUILayout.Width(70)))
{
CreateNewTab();
}
if (GUILayout.Button("打开文件", EditorStyles.toolbarButton, GUILayout.Width(70)))
{
OpenNoteFile();
}
}
else if (data.mainPageMode == MainPageMode.Backup)
{
if (GUILayout.Button("备份所有", EditorStyles.toolbarButton, GUILayout.Width(70)))
{
BackupAllNotes();
}
if (GUILayout.Button("清理旧备份", EditorStyles.toolbarButton, GUILayout.Width(80)))
{
CleanupOldBackups(7);
}
}
GUILayout.EndHorizontal();
GUILayout.Space(10);
// 根据模式显示不同内容
if (data.mainPageMode == MainPageMode.Home)
{
DrawHomeContent();
}
else
{
DrawBackupContent();
}
GUILayout.EndVertical();
}
private void DrawHomeContent()
{
GUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
GUILayout.BeginVertical(GUILayout.Width(350));
GUILayout.Label(EditorGUIUtility.IconContent("TextAsset Icon"), GUILayout.Width(48), GUILayout.Height(48));
GUILayout.Space(10);
GUIStyle titleStyle = new GUIStyle(EditorStyles.largeLabel)
{
fontSize = 18,
alignment = TextAnchor.MiddleCenter,
fontStyle = FontStyle.Bold
};
GUILayout.Label("Unity 记事本", titleStyle);
GUILayout.Space(20);
GUILayout.Label("使用说明:", EditorStyles.boldLabel);
GUILayout.Space(8);
EditorGUILayout.HelpBox(
"• 点击「新建」按钮创建新笔记\n" +
"• 点击「打开」按钮导入现有笔记\n" +
"• 点击「保存」按钮保存当前笔记\n" +
"• 使用「Ctrl+S」快捷键快速保存\n" +
"• 使用「Ctrl+N」快捷键新建笔记\n" +
"• 点击标签右侧的「×」关闭标签\n" +
"• 支持自动保存和搜索功能\n" +
"• 点击「备份」进入备份管理",
MessageType.Info
);
GUILayout.Space(20);
// 快速访问面板
GUILayout.Label("快速访问", EditorStyles.boldLabel);
GUILayout.Space(5);
GUILayout.BeginHorizontal();
if (GUILayout.Button(" 新建笔记 ", GUILayout.Height(35), GUILayout.Width(100)))
{
CreateNewTab();
}
GUILayout.Space(10);
if (GUILayout.Button(" 打开文件 ", GUILayout.Height(35), GUILayout.Width(100)))
{
OpenNoteFile();
}
GUILayout.Space(10);
if (GUILayout.Button(" 备份管理 ", GUILayout.Height(35), GUILayout.Width(100)))
{
data.mainPageMode = MainPageMode.Backup;
}
GUILayout.EndHorizontal();
GUILayout.Space(10);
// 当前笔记快速备份
var currentTab = GetCurrentTab();
if (currentTab != null && data.selectedTabIndex != data.mainPageTabIndex &&
!string.IsNullOrEmpty(currentTab.filePath))
{
GUILayout.BeginVertical("Box");
GUILayout.Label("当前笔记:", EditorStyles.miniBoldLabel);
GUILayout.BeginHorizontal();
GUILayout.Label(currentTab.title, EditorStyles.label, GUILayout.ExpandWidth(true));
if (GUILayout.Button("快速备份", EditorStyles.miniButton, GUILayout.Width(80)))
{
BackupCurrentNote();
}
GUILayout.EndHorizontal();
GUILayout.EndVertical();
}
GUILayout.EndVertical();
GUILayout.FlexibleSpace();
GUILayout.EndHorizontal();
GUILayout.Space(20);
EditorGUILayout.LabelField("", GUI.skin.horizontalSlider);
GUILayout.Space(10);
DrawNoteList();
}
private void DrawBackupContent()
{
GUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
GUILayout.BeginVertical(GUILayout.Width(400));
GUILayout.Label(EditorGUIUtility.IconContent("d_SaveAs").image, GUILayout.Width(48), GUILayout.Height(48));
GUILayout.Space(10);
GUIStyle titleStyle = new GUIStyle(EditorStyles.largeLabel)
{
fontSize = 18,
alignment = TextAnchor.MiddleCenter,
fontStyle = FontStyle.Bold
};
GUILayout.Label("备份管理", titleStyle);
GUILayout.Space(20);
EditorGUILayout.HelpBox(
"• 这里显示所有备份的笔记文件\n" +
"• 备份文件名格式:原文件名_备份时间.扩展名\n" +
"• 点击备份文件名可以打开查看备份内容\n" +
"• 可以删除不需要的备份文件\n" +
"• 备份文件夹:" + data.backupFolder,
MessageType.Info
);
GUILayout.Space(20);
// 快速操作面板
GUILayout.Label("快速操作", EditorStyles.boldLabel);
GUILayout.Space(5);
GUILayout.BeginHorizontal();
// 备份当前笔记按钮(如果当前是笔记标签)
if (data.selectedTabIndex != data.mainPageTabIndex)
{
var currentTab = GetCurrentTab();
if (currentTab != null && !string.IsNullOrEmpty(currentTab.filePath))
{
if (GUILayout.Button(" 备份当前笔记 ", GUILayout.Height(35), GUILayout.Width(120)))
{
BackupCurrentNote();
}
GUILayout.Space(10);
}
}
if (GUILayout.Button(" 备份所有笔记 ", GUILayout.Height(35), GUILayout.Width(120)))
{
BackupAllNotes();
}
GUILayout.Space(10);
if (GUILayout.Button(" 刷新列表 ", GUILayout.Height(35), GUILayout.Width(80)))
{
RefreshBackupList();
}
GUILayout.EndHorizontal();
GUILayout.Space(10);
GUILayout.BeginHorizontal();
if (GUILayout.Button(" 打开备份文件夹 ", GUILayout.Height(35), GUILayout.Width(130)))
{
OpenBackupFolder();
}
GUILayout.Space(10);
if (GUILayout.Button(" 清理7天前备份 ", GUILayout.Height(35), GUILayout.Width(130)))
{
CleanupOldBackups(7);
}
GUILayout.EndHorizontal();
GUILayout.EndVertical();
GUILayout.FlexibleSpace();
GUILayout.EndHorizontal();
GUILayout.Space(20);
EditorGUILayout.LabelField("", GUI.skin.horizontalSlider);
GUILayout.Space(10);
DrawBackupList();
}
private void DrawNoteList()
{
// 刷新笔记列表(如果需要)
double timeSinceStartup = EditorApplication.timeSinceStartup;
TimeSpan timeSinceLastRefresh = DateTime.Now - lastNoteListRefresh;
if (timeSinceLastRefresh.TotalSeconds > NOTE_LIST_REFRESH_INTERVAL)
{
RefreshNoteList();
}
GUILayout.BeginHorizontal();
GUILayout.Label("笔记文件夹: ", EditorStyles.boldLabel);
GUILayout.Label(data.notesFolder, EditorStyles.wordWrappedLabel);
GUILayout.FlexibleSpace();
data.showNoteList = GUILayout.Toggle(data.showNoteList, "显示列表", EditorStyles.miniButton, GUILayout.Width(80));
if (GUILayout.Button("刷新", EditorStyles.miniButton, GUILayout.Width(50)))
{
RefreshNoteList();
}
GUILayout.EndHorizontal();
if (!data.showNoteList) return;
GUILayout.Space(5);
if (noteFiles.Count == 0)
{
EditorGUILayout.HelpBox($"笔记文件夹中没有找到文本文件。\n支持的格式: {string.Join(", ", textExtensions)}", MessageType.Info);
return;
}
GUILayout.Label($"找到 {noteFiles.Count} 个笔记:", EditorStyles.miniBoldLabel);
noteListScrollPos = GUILayout.BeginScrollView(noteListScrollPos, GUILayout.Height(200));
foreach (string filePath in noteFiles)
{
try
{
string fileName = Path.GetFileName(filePath);
string relativePath = GetRelativePath(filePath);
DateTime lastModified = File.GetLastWriteTime(filePath);
string fileInfo = $"{fileName} ({GetFileSize(filePath)}) - {lastModified:yyyy-MM-dd HH:mm}";
GUILayout.BeginHorizontal(EditorStyles.helpBox);
// 使用文件名为按钮,点击即可打开
if (GUILayout.Button(fileName, EditorStyles.miniButton, GUILayout.ExpandWidth(true)))
{
OpenNoteFile(filePath);
}
GUILayout.Space(5);
// 显示更多信息
GUILayout.BeginVertical(GUILayout.Width(100));
GUILayout.Label(GetFileSize(filePath), EditorStyles.miniLabel);
GUILayout.Label(lastModified.ToString("yyyy-MM-dd"), EditorStyles.miniLabel);
GUILayout.EndVertical();
// 显示是否已打开
if (openedFiles.Contains(Path.GetFullPath(filePath)))
{
GUILayout.Label("✓", EditorStyles.miniLabel, GUILayout.Width(20));
}
GUILayout.Space(5);
// 添加删除按钮
if (GUILayout.Button("×", EditorStyles.miniButton, GUILayout.Width(25)))
{
DeleteNoteFile(filePath);
}
GUILayout.EndHorizontal();
GUILayout.Space(2);
}
catch (Exception)
{
continue;
}
}
GUILayout.EndScrollView();
}
private void DrawBackupList()
{
// 刷新备份列表(如果需要)
TimeSpan timeSinceLastRefresh = DateTime.Now - lastBackupListRefresh;
if (timeSinceLastRefresh.TotalSeconds > NOTE_LIST_REFRESH_INTERVAL)
{
RefreshBackupList();
}
GUILayout.BeginHorizontal();
GUILayout.Label("备份文件夹: ", EditorStyles.boldLabel);
GUILayout.Label(data.backupFolder, EditorStyles.wordWrappedLabel);
GUILayout.FlexibleSpace();
if (GUILayout.Button("打开文件夹", EditorStyles.miniButton, GUILayout.Width(80)))
{
OpenBackupFolder();
}
GUILayout.Space(5);
if (GUILayout.Button("刷新", EditorStyles.miniButton, GUILayout.Width(50)))
{
RefreshBackupList();
}
GUILayout.EndHorizontal();
GUILayout.Space(5);
if (backupFiles.Count == 0)
{
EditorGUILayout.HelpBox($"备份文件夹中没有找到备份文件。\n点击上面的按钮备份当前笔记或所有笔记。", MessageType.Info);
return;
}
GUILayout.Label($"找到 {backupFiles.Count} 个备份文件:", EditorStyles.miniBoldLabel);
backupListScrollPos = GUILayout.BeginScrollView(backupListScrollPos, GUILayout.ExpandHeight(true));
// 按备份时间倒序排序
var sortedBackups = backupFiles.OrderByDescending(b => b.backupTime).ToList();
foreach (var backup in sortedBackups)
{
GUILayout.BeginVertical(EditorStyles.helpBox);
// 备份文件信息行
GUILayout.BeginHorizontal();
GUILayout.BeginVertical(GUILayout.ExpandWidth(true));
// 原始文件名
GUILayout.Label(backup.originalName, EditorStyles.boldLabel);
// 备份时间
GUILayout.Label($"备份时间: {backup.backupTime:yyyy-MM-dd HH:mm:ss}", EditorStyles.miniLabel);
// 文件大小
GUILayout.Label($"大小: {FormatFileSize(backup.fileSize)}", EditorStyles.miniLabel);
GUILayout.EndVertical();
// 按钮区域
GUILayout.BeginVertical(GUILayout.Width(200));
GUILayout.BeginHorizontal();
// 打开备份按钮
if (GUILayout.Button("预览", EditorStyles.miniButton, GUILayout.Width(60)))
{
ShowBackupPreview(backup);
}
// 恢复备份按钮
if (GUILayout.Button("恢复", EditorStyles.miniButton, GUILayout.Width(60)))
{
RestoreBackup(backup);
}
// 删除备份按钮
if (GUILayout.Button("删除", EditorStyles.miniButton, GUILayout.Width(60)))
{
DeleteBackupFile(backup.filePath);
}
GUILayout.EndHorizontal();
GUILayout.EndVertical();
GUILayout.EndHorizontal();
// 预览内容(展开时显示)
if (selectedBackupForPreview == backup)
{
GUILayout.Space(5);
DrawBackupPreview(backup);
}
GUILayout.EndVertical();
GUILayout.Space(3);
}
GUILayout.EndScrollView();
}
private void ShowBackupPreview(BackupFileInfo backup)
{
try
{
string content = File.ReadAllText(backup.filePath, Encoding.UTF8);
string preview = content.Length > 200 ? content.Substring(0, 200) + "..." : content;
EditorUtility.DisplayDialog(
$"预览: {backup.originalName}",
$"备份时间: {backup.backupTime:yyyy-MM-dd HH:mm:ss}\n\n" +
$"预览内容:\n{preview}",
"确定"
);
}
catch (Exception e)
{
Debug.LogWarning($"无法预览备份文件: {e.Message}");
}
}
private void DrawBackupPreview(BackupFileInfo backup)
{
try
{
string content = File.ReadAllText(backup.filePath, Encoding.UTF8);
string preview = content.Length > 500 ? content.Substring(0, 500) + "..." : content;
EditorGUILayout.HelpBox(
$"备份时间: {backup.backupTime:yyyy-MM-dd HH:mm:ss}\n\n" +
$"预览内容:\n{preview}",
MessageType.Info
);
GUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
if (GUILayout.Button("在编辑器中打开", EditorStyles.miniButton, GUILayout.Width(120)))
{
OpenBackupFile(backup.filePath);
}
GUILayout.EndHorizontal();
}
catch (Exception e)
{
EditorGUILayout.HelpBox($"无法预览备份文件: {e.Message}", MessageType.Error);
}
}
private void DrawStatusBar()
{
GUILayout.BeginHorizontal("Box");
var currentTab = GetCurrentTab();
if (currentTab != null)
{
if (data.selectedTabIndex == data.mainPageTabIndex)
{
string modeText = data.mainPageMode == MainPageMode.Home ? "主页" : "备份管理";
GUILayout.Label($"{modeText} - 共 {noteFiles.Count} 个笔记, {backupFiles.Count} 个备份", EditorStyles.miniLabel);
}
else
{
string status = $"字数: {currentTab.content.Length}";
if (currentTab.isModified)
status += " | 已修改";
if (!string.IsNullOrEmpty(currentTab.filePath))
{
string fileName = Path.GetFileName(currentTab.filePath);
status += $" | 文件: {fileName}";
}
GUILayout.Label(status, EditorStyles.miniLabel);
}
}
else
{
GUILayout.Label("没有打开的笔记", EditorStyles.miniLabel);
}
GUILayout.FlexibleSpace();
GUILayout.Label(data.autoSaveEnabled ? "自动保存: 开启" : "自动保存: 关闭",
EditorStyles.miniLabel);
GUILayout.EndHorizontal();
}
private void DrawSettingsPanel()
{
GUILayout.BeginVertical("Box");
EditorGUILayout.LabelField("设置", EditorStyles.boldLabel);
data.autoSaveEnabled = EditorGUILayout.Toggle("启用自动保存", data.autoSaveEnabled);
data.fontSize = EditorGUILayout.IntSlider("字体大小", data.fontSize, 10, 24);
data.wordWrap = EditorGUILayout.Toggle("自动换行", data.wordWrap);
data.showNoteList = EditorGUILayout.Toggle("显示笔记列表", data.showNoteList);
EditorGUILayout.Space();
GUILayout.Label("笔记文件夹:", EditorStyles.miniBoldLabel);
GUILayout.BeginHorizontal();
GUILayout.Label(data.notesFolder, EditorStyles.textField);
if (GUILayout.Button("浏览", GUILayout.Width(60)))
{
string newPath = EditorUtility.OpenFolderPanel("选择笔记文件夹", Application.dataPath, "");
if (!string.IsNullOrEmpty(newPath))
{
string relativePath = "Assets" + newPath.Replace(Application.dataPath, "");
if (relativePath != data.notesFolder)
{
data.notesFolder = relativePath;
RefreshNoteList();
}
}
}
GUILayout.EndHorizontal();
GUILayout.Label("备份文件夹:", EditorStyles.miniBoldLabel);
GUILayout.BeginHorizontal();
GUILayout.Label(data.backupFolder, EditorStyles.textField);
if (GUILayout.Button("浏览", GUILayout.Width(60)))
{
string newPath = EditorUtility.OpenFolderPanel("选择备份文件夹", Application.dataPath, "");
if (!string.IsNullOrEmpty(newPath))
{
string relativePath = "Assets" + newPath.Replace(Application.dataPath, "");
if (relativePath != data.backupFolder)
{
data.backupFolder = relativePath;
RefreshBackupList();
}
}
}
GUILayout.EndHorizontal();
EditorGUILayout.Space();
if (GUILayout.Button("导出所有笔记", GUILayout.Height(25)))
{
ExportAllNotes();
}
GUILayout.EndVertical();
}
#endregion
#region 笔记列表功能
private void RefreshNoteList()
{
noteFiles.Clear();
try
{
string fullPath = Path.GetFullPath(Path.Combine(Application.dataPath, "..", data.notesFolder));
if (Directory.Exists(fullPath))
{
foreach (string extension in textExtensions)
{
try
{
// 只搜索笔记文件夹,不搜索子文件夹
string[] files = Directory.GetFiles(fullPath, "*" + extension, SearchOption.TopDirectoryOnly);
foreach (string filePath in files)
{
// 检查是否是备份文件(文件名中包含"备份"字样)
string fileName = Path.GetFileName(filePath);
if (!fileName.Contains("_备份_"))
{
noteFiles.Add(filePath);
}
}
}
catch (Exception) { }
}
noteFiles.Sort((a, b) => File.GetLastWriteTime(b).CompareTo(File.GetLastWriteTime(a)));
}
}
catch (Exception e)
{
Debug.LogWarning($"刷新笔记列表失败: {e.Message}");
}
lastNoteListRefresh = DateTime.Now;
}
private string GetRelativePath(string fullPath)
{
try
{
string projectPath = Path.GetFullPath(Application.dataPath + "/..");
if (fullPath.StartsWith(projectPath))
{
return fullPath.Substring(projectPath.Length + 1);
}
}
catch { }
return fullPath;
}
private string GetFileSize(string filePath)
{
try
{
FileInfo fileInfo = new FileInfo(filePath);
long size = fileInfo.Length;
if (size < 1024) return $"{size} B";
else if (size < 1024 * 1024) return $"{(size / 1024.0):0.0} KB";
else return $"{(size / (1024.0 * 1024.0)):0.0} MB";
}
catch
{
return "未知大小";
}
}
private void DeleteNoteFile(string filePath)
{
try
{
// 检查文件是否已经打开
string fullPath = Path.GetFullPath(filePath);
bool isFileOpened = openedFiles.Contains(fullPath);
string message;
if (isFileOpened)
{
message = $"文件正在编辑中,删除后未保存的内容将丢失!\n\n确定要删除文件吗?\n{Path.GetFileName(filePath)}";
}
else
{
message = $"确定要删除文件吗?\n{Path.GetFileName(filePath)}";
}
// 确认对话框
if (EditorUtility.DisplayDialog("删除文件", message, "删除", "取消"))
{
// 如果文件已经打开,先关闭对应的标签页
if (isFileOpened)
{
for (int i = 0; i < data.tabs.Count; i++)
{
if (i != data.mainPageTabIndex &&
!string.IsNullOrEmpty(data.tabs[i].filePath) &&
Path.GetFullPath(data.tabs[i].filePath) == fullPath)
{
// 先强制关闭标签页,不保存
var tab = data.tabs[i];
if (!string.IsNullOrEmpty(tab.filePath))
{
openedFiles.Remove(Path.GetFullPath(tab.filePath));
}
data.tabs.RemoveAt(i);
// 更新特殊标签的索引
if (i < data.mainPageTabIndex) data.mainPageTabIndex--;
// 调整选中索引
if (data.selectedTabIndex >= i && data.selectedTabIndex > 0)
{
data.selectedTabIndex--;
}
else if (data.selectedTabIndex >= data.tabs.Count)
{
data.selectedTabIndex = data.tabs.Count - 1;
}
break;
}
}
}
// 删除物理文件
File.Delete(filePath);
// 刷新列表
RefreshNoteList();
// 更新界面
Repaint();
// 显示删除成功的提示
EditorUtility.DisplayDialog("删除成功", $"文件已删除:{Path.GetFileName(filePath)}", "确定");
}
}
catch (Exception e)
{
EditorUtility.DisplayDialog("删除失败", $"无法删除文件:{e.Message}", "确定");
}
}
#endregion
#region 备份管理功能
private void RefreshBackupList()
{
backupFiles.Clear();
try
{
string backupFolderPath = Path.GetFullPath(Path.Combine(Application.dataPath, "..", data.backupFolder));
if (Directory.Exists(backupFolderPath))
{
// 获取所有备份文件
foreach (string extension in textExtensions)
{
try
{
string[] files = Directory.GetFiles(backupFolderPath, "*" + extension, SearchOption.TopDirectoryOnly);
foreach (string filePath in files)
{
try
{
FileInfo fileInfo = new FileInfo(filePath);
string fileName = fileInfo.Name;
// 只显示备份文件(文件名中包含"备份"字样)
if (fileName.Contains("_备份_"))
{
// 解析原始文件名(去掉备份时间戳部分)
int backupIndex = fileName.IndexOf("_备份_");
string originalName = fileName.Substring(0, backupIndex) + fileInfo.Extension;
BackupFileInfo backupInfo = new BackupFileInfo
{
filePath = filePath,
fileName = fileName,
originalName = originalName,
backupTime = fileInfo.LastWriteTime,
fileSize = fileInfo.Length,
relativePath = GetRelativePath(filePath)
};
backupFiles.Add(backupInfo);
}
}
catch (Exception) { }
}
}
catch (Exception) { }
}
}
}
catch (Exception e)
{
Debug.LogWarning($"刷新备份列表失败: {e.Message}");
}
lastBackupListRefresh = DateTime.Now;
}
private string FormatFileSize(long size)
{
if (size < 1024) return $"{size} B";
else if (size < 1024 * 1024) return $"{(size / 1024.0):0.0} KB";
else return $"{(size / (1024.0 * 1024.0)):0.0} MB";
}
private void OpenBackupFile(string filePath)
{
try
{
string content = File.ReadAllText(filePath, Encoding.UTF8);
string fileName = Path.GetFileName(filePath);
// 创建新标签页打开备份文件
var newTab = new NoteTab();
newTab.title = "[备份] " + Path.GetFileNameWithoutExtension(fileName);
newTab.content = content;
newTab.filePath = filePath;
newTab.isModified = false; // 备份文件默认只读,不能修改
data.tabs.Add(newTab);
data.selectedTabIndex = data.tabs.Count - 1;
lastSelectedTabIndex = data.selectedTabIndex;
Repaint();
}
catch (Exception e)
{
EditorUtility.DisplayDialog("错误", $"无法打开备份文件: {e.Message}", "确定");
}
}
private void DeleteBackupFile(string filePath)
{
try
{
string fileName = Path.GetFileName(filePath);
if (EditorUtility.DisplayDialog("删除备份", $"确定要删除备份文件吗?\n{fileName}", "删除", "取消"))
{
File.Delete(filePath);
RefreshBackupList();
// 提示用户
EditorUtility.DisplayDialog("删除成功", $"已删除备份文件:{fileName}", "确定");
// 刷新显示
Repaint();
}
}
catch (Exception e)
{
EditorUtility.DisplayDialog("删除失败", $"无法删除备份文件:{e.Message}", "确定");
}
}
private void RestoreBackup(BackupFileInfo backup)
{
try
{
// 询问恢复位置
string defaultName = backup.originalName;
string restorePath = EditorUtility.SaveFilePanel("恢复备份", data.notesFolder, defaultName, "txt");
if (!string.IsNullOrEmpty(restorePath))
{
// 复制备份文件到指定位置
File.Copy(backup.filePath, restorePath, true);
// 刷新笔记列表
RefreshNoteList();
// 提示用户
EditorUtility.DisplayDialog("恢复成功",
$"已恢复备份文件:{backup.fileName}\n恢复到:{restorePath}", "确定");
// 打开恢复的文件
OpenNoteFile(restorePath);
}
}
catch (Exception e)
{
EditorUtility.DisplayDialog("恢复失败", $"无法恢复备份:{e.Message}", "确定");
}
}
private void OpenBackupFolder()
{
try
{
string backupFolderPath = Path.GetFullPath(Path.Combine(Application.dataPath, "..", data.backupFolder));
if (Directory.Exists(backupFolderPath))
{
// 在文件资源管理器中打开文件夹
EditorUtility.RevealInFinder(backupFolderPath);
}
else
{
EditorUtility.DisplayDialog("提示", "备份文件夹不存在,将创建新文件夹", "确定");
Directory.CreateDirectory(backupFolderPath);
EditorUtility.RevealInFinder(backupFolderPath);
}
}
catch (Exception e)
{
EditorUtility.DisplayDialog("错误", $"无法打开备份文件夹:{e.Message}", "确定");
}
}
private void CleanupOldBackups(int days)
{
try
{
string backupFolderPath = Path.GetFullPath(Path.Combine(Application.dataPath, "..", data.backupFolder));
if (Directory.Exists(backupFolderPath))
{
DateTime cutoffDate = DateTime.Now.AddDays(-days);
int deletedCount = 0;
foreach (var extension in textExtensions)
{
string[] files = Directory.GetFiles(backupFolderPath, "*" + extension, SearchOption.TopDirectoryOnly);
foreach (string filePath in files)
{
try
{
FileInfo fileInfo = new FileInfo(filePath);
if (fileInfo.LastWriteTime < cutoffDate)
{
File.Delete(filePath);
deletedCount++;
}
}
catch (Exception) { }
}
}
// 刷新备份列表
RefreshBackupList();
// 提示用户
if (deletedCount > 0)
{
EditorUtility.DisplayDialog("清理完成",
$"已清理 {deletedCount} 个 {days} 天前的备份文件", "确定");
}
else
{
EditorUtility.DisplayDialog("清理完成",
$"没有找到 {days} 天前的备份文件", "确定");
}
// 刷新显示
Repaint();
}
}
catch (Exception e)
{
EditorUtility.DisplayDialog("清理失败", $"清理备份文件失败:{e.Message}", "确定");
}
}
// 备份当前笔记
private void BackupCurrentNote()
{
var currentTab = GetCurrentTab();
if (currentTab == null || string.IsNullOrEmpty(currentTab.filePath))
{
EditorUtility.DisplayDialog("备份失败", "当前笔记没有保存到文件,请先保存", "确定");
return;
}
try
{
// 确保备份文件夹存在
string backupFolderPath = Path.GetFullPath(Path.Combine(Application.dataPath, "..", data.backupFolder));
if (!Directory.Exists(backupFolderPath))
{
Directory.CreateDirectory(backupFolderPath);
}
// 获取文件信息
FileInfo fileInfo = new FileInfo(currentTab.filePath);
string fileName = fileInfo.Name;
string fileNameWithoutExt = Path.GetFileNameWithoutExtension(fileName);
string fileExt = fileInfo.Extension;
// 生成备份文件名(添加时间戳)
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
string backupFileName = $"{fileNameWithoutExt}_备份_{timestamp}{fileExt}";
string backupFilePath = Path.Combine(backupFolderPath, backupFileName);
// 复制文件
File.Copy(currentTab.filePath, backupFilePath, true);
// 刷新备份列表
RefreshBackupList();
// 提示用户
EditorUtility.DisplayDialog("备份成功",
$"已成功备份当前笔记:{fileName}\n备份到:{backupFileName}", "确定");
// 刷新显示
Repaint();
}
catch (Exception e)
{
EditorUtility.DisplayDialog("备份失败", $"无法备份文件:{e.Message}", "确定");
}
}
// 备份所有笔记
private void BackupAllNotes()
{
try
{
// 确保备份文件夹存在
string backupFolderPath = Path.GetFullPath(Path.Combine(Application.dataPath, "..", data.backupFolder));
if (!Directory.Exists(backupFolderPath))
{
Directory.CreateDirectory(backupFolderPath);
}
int backupCount = 0;
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
// 备份所有已打开的且有文件路径的笔记
foreach (var tab in data.tabs)
{
if (!string.IsNullOrEmpty(tab.filePath) && tab.title != "主页")
{
try
{
FileInfo fileInfo = new FileInfo(tab.filePath);
string fileName = fileInfo.Name;
string fileNameWithoutExt = Path.GetFileNameWithoutExtension(fileName);
string fileExt = fileInfo.Extension;
string backupFileName = $"{fileNameWithoutExt}_备份_{timestamp}{fileExt}";
string backupFilePath = Path.Combine(backupFolderPath, backupFileName);
File.Copy(tab.filePath, backupFilePath, true);
backupCount++;
}
catch (Exception) { }
}
}
// 刷新备份列表
RefreshBackupList();
// 提示用户
if (backupCount > 0)
{
EditorUtility.DisplayDialog("备份成功",
$"已成功备份 {backupCount} 个笔记文件\n备份时间:{DateTime.Now:yyyy-MM-dd HH:mm:ss}", "确定");
}
else
{
EditorUtility.DisplayDialog("备份失败", "没有找到可以备份的笔记文件", "确定");
}
// 刷新显示
Repaint();
}
catch (Exception e)
{
EditorUtility.DisplayDialog("备份失败", $"无法备份文件:{e.Message}", "确定");
}
}
#endregion
#region 标签管理
private NoteTab GetCurrentTab()
{
if (data == null || data.tabs.Count == 0) return null;
if (data.selectedTabIndex < 0)
data.selectedTabIndex = 0;
else if (data.selectedTabIndex >= data.tabs.Count)
data.selectedTabIndex = data.tabs.Count - 1;
return data.tabs[data.selectedTabIndex];
}
private void EnsureMainPageTab()
{
// 如果没有主页标签,创建一个
if (data.mainPageTabIndex < 0 || data.mainPageTabIndex >= data.tabs.Count || data.tabs[data.mainPageTabIndex].title != "主页")
{
CreateMainPageTab();
}
}
private void CreateMainPageTab()
{
// 检查是否已经有主页标签
for (int i = 0; i < data.tabs.Count; i++)
{
if (data.tabs[i].title == "主页")
{
data.mainPageTabIndex = i;
return;
}
}
// 创建新的主页标签
var mainTab = new NoteTab();
mainTab.title = "主页";
mainTab.content = ""; // 主页页面不需要内容
data.tabs.Add(mainTab);
data.mainPageTabIndex = data.tabs.Count - 1;
// 如果这是第一个标签,选中它
if (data.tabs.Count == 1)
{
data.selectedTabIndex = 0;
}
}
private void CreateNewTab()
{
if (data == null) data = new NotepadData();
var newTab = new NoteTab();
newTab.title = "笔记 " + (data.tabs.Count);
data.tabs.Add(newTab);
data.selectedTabIndex = data.tabs.Count - 1;
lastSelectedTabIndex = data.selectedTabIndex;
Repaint();
}
private void CloseTab(int index)
{
if (data == null || index < 0 || index >= data.tabs.Count) return;
var tab = data.tabs[index];
// 不允许关闭主页标签
if (index == data.mainPageTabIndex)
{
EditorUtility.DisplayDialog("提示", "主页标签不能关闭", "确定");
return;
}
if (tab.isModified)
{
int result = EditorUtility.DisplayDialogComplex(
"关闭笔记",
$"'{tab.title}' 有未保存的更改。",
"保存并关闭",
"直接关闭",
"取消"
);
if (result == 0)
{
SaveTab(index);
}
else if (result == 2)
{
return;
}
}
if (!string.IsNullOrEmpty(tab.filePath))
{
openedFiles.Remove(Path.GetFullPath(tab.filePath));
}
// 移除标签
data.tabs.RemoveAt(index);
// 更新特殊标签的索引
if (index < data.mainPageTabIndex)
{
data.mainPageTabIndex--;
}
// 如果关闭后没有普通标签,切换到主页标签
if (data.tabs.Count == 1 && data.mainPageTabIndex >= 0)
{
data.selectedTabIndex = data.mainPageTabIndex;
}
else if (data.tabs.Count > 0)
{
if (data.selectedTabIndex >= index && data.selectedTabIndex > 0)
{
data.selectedTabIndex--;
}
else if (data.selectedTabIndex >= data.tabs.Count)
{
data.selectedTabIndex = data.tabs.Count - 1;
}
}
lastSelectedTabIndex = data.selectedTabIndex;
Repaint();
}
private void CloseAllTabs()
{
if (data == null || data.tabs.Count <= 1) return; // 至少有主页标签
// 只关闭普通标签,保留主页标签
List<int> tabsToClose = new List<int>();
for (int i = 0; i < data.tabs.Count; i++)
{
if (i != data.mainPageTabIndex)
{
tabsToClose.Add(i);
}
}
bool hasUnsavedChanges = tabsToClose.Any(i => data.tabs[i].isModified);
if (hasUnsavedChanges)
{
int result = EditorUtility.DisplayDialogComplex(
"关闭所有标签",
"有未保存的笔记。是否保存更改?",
"保存并关闭",
"直接关闭",
"取消"
);
if (result == 0)
{
foreach (int i in tabsToClose)
{
if (data.tabs[i].isModified)
{
SaveTab(i);
}
}
}
else if (result == 2)
{
return;
}
}
// 关闭所有普通标签
for (int i = tabsToClose.Count - 1; i >= 0; i--)
{
int tabIndex = tabsToClose[i];
if (!string.IsNullOrEmpty(data.tabs[tabIndex].filePath))
{
openedFiles.Remove(Path.GetFullPath(data.tabs[tabIndex].filePath));
}
data.tabs.RemoveAt(tabIndex);
}
// 更新特殊标签索引
data.mainPageTabIndex = 0;
data.selectedTabIndex = data.mainPageTabIndex;
lastSelectedTabIndex = data.selectedTabIndex;
Repaint();
}
#endregion
#region 文件操作
private void OpenNoteFile()
{
string path = EditorUtility.OpenFilePanel("打开笔记文件", Application.dataPath, "txt,md,json,cs");
if (!string.IsNullOrEmpty(path))
{
OpenNoteFile(path);
}
}
private void OpenNoteFile(string filePath)
{
try
{
string fullPath = Path.GetFullPath(filePath);
// 检查文件是否已经打开
if (openedFiles.Contains(fullPath))
{
for (int i = 0; i < data.tabs.Count; i++)
{
if (i != data.mainPageTabIndex &&
!string.IsNullOrEmpty(data.tabs[i].filePath) &&
Path.GetFullPath(data.tabs[i].filePath) == fullPath)
{
data.selectedTabIndex = i;
lastSelectedTabIndex = data.selectedTabIndex;
Repaint();
return;
}
}
}
string content = File.ReadAllText(filePath, Encoding.UTF8);
var newTab = new NoteTab();
newTab.title = Path.GetFileNameWithoutExtension(filePath);
newTab.content = content;
newTab.filePath = filePath;
newTab.isModified = false;
data.tabs.Add(newTab);
data.selectedTabIndex = data.tabs.Count - 1;
lastSelectedTabIndex = data.selectedTabIndex;
openedFiles.Add(fullPath);
Repaint();
}
catch (Exception e)
{
EditorUtility.DisplayDialog("错误", $"无法打开文件: {e.Message}", "确定");
}
}
private void SaveCurrentTab()
{
var currentTab = GetCurrentTab();
if (currentTab == null || data.selectedTabIndex == data.mainPageTabIndex)
{
EditorUtility.DisplayDialog("保存", "没有活动的笔记可以保存", "确定");
return;
}
SaveTab(data.selectedTabIndex);
}
private void SaveTab(int index)
{
if (data == null || index < 0 || index >= data.tabs.Count ||
index == data.mainPageTabIndex) return;
var tab = data.tabs[index];
if (string.IsNullOrEmpty(tab.filePath))
{
SaveAsCurrentTab();
return;
}
try
{
string directory = Path.GetDirectoryName(tab.filePath);
if (!Directory.Exists(directory))
Directory.CreateDirectory(directory);
File.WriteAllText(tab.filePath, tab.content, Encoding.UTF8);
tab.isModified = false;
tab.lastModified = DateTime.Now;
tab.lastSavedHash = CalculateContentHash(tab.content);
AssetDatabase.Refresh();
RefreshNoteList();
Repaint();
}
catch (Exception e)
{
EditorUtility.DisplayDialog("错误", $"保存失败: {e.Message}", "确定");
}
}
private void SaveAsCurrentTab()
{
var currentTab = GetCurrentTab();
if (currentTab == null || data.selectedTabIndex == data.mainPageTabIndex)
{
EditorUtility.DisplayDialog("另存为", "没有活动的笔记可以另存为", "确定");
return;
}
string defaultName = string.IsNullOrEmpty(currentTab.title) ? "NewNote.txt" : currentTab.title + ".txt";
string path = EditorUtility.SaveFilePanel("另存为", Application.dataPath, defaultName, "txt");
if (!string.IsNullOrEmpty(path))
{
if (!string.IsNullOrEmpty(currentTab.filePath))
{
openedFiles.Remove(Path.GetFullPath(currentTab.filePath));
}
currentTab.filePath = path;
currentTab.title = Path.GetFileNameWithoutExtension(path);
openedFiles.Add(Path.GetFullPath(path));
SaveCurrentTab();
}
}
#endregion
#region 自动保存系统
private void ScheduleAutoSave(NoteTab tab)
{
int tabIndex = data.tabs.IndexOf(tab);
if (tabIndex == -1) return;
scheduledSaves[tabIndex] = EditorApplication.timeSinceStartup + 2.0;
}
private void OnEditorUpdate()
{
CheckScheduledSaves();
if (data != null && data.autoSaveEnabled && (DateTime.Now - lastAutoSaveTime).TotalSeconds >= AUTO_SAVE_INTERVAL)
{
AutoSaveAllModifiedTabs();
lastAutoSaveTime = DateTime.Now;
}
}
private void CheckScheduledSaves()
{
if (scheduledSaves.Count == 0) return;
List<int> toRemove = new List<int>();
double currentTime = EditorApplication.timeSinceStartup;
foreach (var kvp in scheduledSaves)
{
if (currentTime >= kvp.Value)
{
int tabIndex = kvp.Key;
if (tabIndex < data.tabs.Count)
{
var tab = data.tabs[tabIndex];
if (tab.isModified)
{
string currentHash = CalculateContentHash(tab.content);
if (currentHash != tab.lastSavedHash)
{
PerformAutoSave(tab, tabIndex);
tab.lastSavedHash = currentHash;
}
}
}
toRemove.Add(tabIndex);
}
}
foreach (int index in toRemove)
{
scheduledSaves.Remove(index);
}
}
private void AutoSaveAllModifiedTabs()
{
for (int i = 0; i < data.tabs.Count; i++)
{
if (i == data.mainPageTabIndex) continue; // 跳过主页标签
var tab = data.tabs[i];
if (tab.isModified)
{
PerformAutoSave(tab, i);
}
}
}
private void PerformAutoSave(NoteTab tab, int tabIndex)
{
if (string.IsNullOrEmpty(tab.filePath))
{
SaveToTempFile(tab);
}
else
{
SaveTabSilently(tabIndex);
}
}
private void SaveTabSilently(int index)
{
if (index < 0 || index >= data.tabs.Count ||
index == data.mainPageTabIndex) return;
var tab = data.tabs[index];
try
{
string directory = Path.GetDirectoryName(tab.filePath);
if (!Directory.Exists(directory))
Directory.CreateDirectory(directory);
File.WriteAllText(tab.filePath, tab.content, Encoding.UTF8);
tab.isModified = false;
tab.lastModified = DateTime.Now;
tab.lastSavedHash = CalculateContentHash(tab.content);
}
catch (Exception e)
{
Debug.LogWarning($"自动保存失败: {e.Message}");
}
}
private void SaveToTempFile(NoteTab tab)
{
try
{
string tempFolder = Path.Combine(Application.temporaryCachePath, "UnityNotepad/Autosave");
if (!Directory.Exists(tempFolder))
Directory.CreateDirectory(tempFolder);
string safeTitle = string.IsNullOrEmpty(tab.title) ?
"Untitled" :
System.Text.RegularExpressions.Regex.Replace(tab.title, @"[^\w]", "_");
string fileName = $"{safeTitle}_{DateTime.Now:yyyyMMdd_HHmmss}.txt";
string tempFile = Path.Combine(tempFolder, fileName);
File.WriteAllText(tempFile, tab.content, Encoding.UTF8);
CleanupOldTempFiles(tempFolder, 10);
}
catch { }
}
private void SaveAllModifiedTabs()
{
if (data == null) return;
for (int i = 0; i < data.tabs.Count; i++)
{
if (i == data.mainPageTabIndex) continue;
var tab = data.tabs[i];
if (tab.isModified)
{
SaveTabSilently(i);
}
}
}
#endregion
#region 辅助方法
private void CloseNotepad()
{
bool hasUnsavedChanges = data?.tabs?.Any(t => t.isModified) ?? false;
if (hasUnsavedChanges)
{
int result = EditorUtility.DisplayDialogComplex(
"关闭笔记本",
"有未保存的笔记。是否保存更改?",
"保存并关闭",
"直接关闭",
"取消"
);
if (result == 0)
{
SaveAllModifiedTabs();
Close();
}
else if (result == 1)
{
Close();
}
}
else
{
Close();
}
}
private string CalculateContentHash(string content)
{
using (var sha256 = SHA256.Create())
{
byte[] bytes = Encoding.UTF8.GetBytes(content);
byte[] hash = sha256.ComputeHash(bytes);
return Convert.ToBase64String(hash);
}
}
private void CleanupOldTempFiles(string folder, int keepCount)
{
try
{
var files = Directory.GetFiles(folder, "*.txt")
.Select(f => new FileInfo(f))
.OrderByDescending(f => f.LastWriteTime)
.ToList();
for (int i = keepCount; i < files.Count; i++)
{
files[i].Delete();
}
}
catch { }
}
private void CreateTextAreaStyle()
{
textAreaStyle = new GUIStyle(EditorStyles.textArea)
{
wordWrap = true,
fontSize = 14,
padding = new RectOffset(10, 10, 10, 10),
margin = new RectOffset(5, 5, 5, 5)
};
}
private void AppendToCurrentTab(string text)
{
var currentTab = GetCurrentTab();
if (currentTab != null && data.selectedTabIndex != data.mainPageTabIndex &&
!string.IsNullOrEmpty(text))
{
currentTab.content += text;
currentTab.isModified = true;
ScheduleAutoSave(currentTab);
Repaint();
}
}
private void LoadData()
{
string json = EditorPrefs.GetString(DATA_KEY, "");
if (!string.IsNullOrEmpty(json))
{
try
{
data = JsonUtility.FromJson<NotepadData>(json);
UpdateOpenedFiles();
}
catch (Exception e)
{
Debug.LogError($"加载数据失败: {e.Message}");
data = new NotepadData();
}
}
else
{
data = new NotepadData();
}
// 确保文件夹存在
if (!string.IsNullOrEmpty(data.notesFolder) && !Directory.Exists(data.notesFolder))
{
Directory.CreateDirectory(data.notesFolder);
}
if (!string.IsNullOrEmpty(data.backupFolder) && !Directory.Exists(data.backupFolder))
{
Directory.CreateDirectory(data.backupFolder);
}
}
private void SaveData()
{
if (data == null) return;
try
{
string json = JsonUtility.ToJson(data, true);
EditorPrefs.SetString(DATA_KEY, json);
}
catch (Exception e)
{
Debug.LogError($"保存数据失败: {e.Message}");
}
}
#endregion
#region 搜索功能
private void PerformSearch()
{
searchResults.Clear();
currentSearchIndex = 0;
if (string.IsNullOrEmpty(searchText) ||
data.selectedTabIndex == data.mainPageTabIndex) return;
var currentTab = GetCurrentTab();
if (currentTab == null || string.IsNullOrEmpty(currentTab.content)) return;
string content = currentTab.content.ToLower();
string searchLower = searchText.ToLower();
int index = 0;
while ((index = content.IndexOf(searchLower, index)) != -1)
{
searchResults.Add(index);
index += searchLower.Length;
}
if (searchResults.Count > 0)
{
ScrollToSearchResult(0);
}
}
private void NavigateSearch(int direction)
{
if (searchResults.Count == 0) return;
currentSearchIndex = (currentSearchIndex + direction + searchResults.Count) % searchResults.Count;
ScrollToSearchResult(currentSearchIndex);
}
private void ScrollToSearchResult(int resultIndex)
{
if (resultIndex < 0 || resultIndex >= searchResults.Count) return;
var currentTab = GetCurrentTab();
if (currentTab == null) return;
int charPosition = searchResults[resultIndex];
int linesBefore = currentTab.content.Substring(0, Math.Min(charPosition, currentTab.content.Length)).Split('\n').Length - 1;
float lineHeight = data.fontSize * 1.5f;
float targetY = linesBefore * lineHeight;
currentTab.scrollPosition.y = Mathf.Max(0, targetY - position.height / 3);
Repaint();
}
#endregion
#region 快捷键
private void HandleKeyboardShortcuts()
{
Event e = Event.current;
if (e.type == EventType.KeyDown)
{
bool control = e.control || e.command;
if (control)
{
switch (e.keyCode)
{
case KeyCode.S:
SaveCurrentTab();
e.Use();
break;
case KeyCode.N:
CreateNewTab();
e.Use();
break;
case KeyCode.W:
if (data.tabs.Count > 0 && data.selectedTabIndex != data.mainPageTabIndex)
{
CloseTab(data.selectedTabIndex);
e.Use();
}
break;
case KeyCode.Tab:
if (e.shift)
{
if (data.tabs.Count > 1)
{
data.selectedTabIndex = (data.selectedTabIndex - 1 + data.tabs.Count) % data.tabs.Count;
e.Use();
}
}
else
{
if (data.tabs.Count > 1)
{
data.selectedTabIndex = (data.selectedTabIndex + 1) % data.tabs.Count;
e.Use();
}
}
break;
case KeyCode.F:
if (e.shift)
{
EditorGUI.FocusTextInControl("SearchField");
e.Use();
}
break;
case KeyCode.H:
if (data.mainPageTabIndex >= 0 && data.mainPageTabIndex < data.tabs.Count)
{
data.selectedTabIndex = data.mainPageTabIndex;
data.mainPageMode = MainPageMode.Home;
e.Use();
}
break;
case KeyCode.B:
if (data.mainPageTabIndex >= 0 && data.mainPageTabIndex < data.tabs.Count)
{
data.selectedTabIndex = data.mainPageTabIndex;
data.mainPageMode = MainPageMode.Backup;
e.Use();
}
break;
}
}
}
}
#endregion
#region 导入导出
private void ExportAllNotes()
{
if (data == null || data.tabs.Count <= 1) // 只有主页标签
{
EditorUtility.DisplayDialog("导出", "没有可导出的笔记", "确定");
return;
}
string exportFolder = EditorUtility.SaveFolderPanel("导出笔记", Application.dataPath, "NotesExport");
if (!string.IsNullOrEmpty(exportFolder))
{
try
{
int exported = 0;
foreach (var tab in data.tabs)
{
if (!string.IsNullOrEmpty(tab.content) && tab.title != "主页")
{
string safeTitle = System.Text.RegularExpressions.Regex.Replace(tab.title, @"[^\w\u4e00-\u9fa5]", "_");
string fileName = $"{safeTitle}_{DateTime.Now:yyyyMMdd_HHmmss}.txt";
string filePath = Path.Combine(exportFolder, fileName);
File.WriteAllText(filePath, tab.content, Encoding.UTF8);
exported++;
}
}
EditorUtility.DisplayDialog("导出完成", $"已导出 {exported} 个笔记到:\n{exportFolder}", "确定");
}
catch (Exception e)
{
EditorUtility.DisplayDialog("导出失败", e.Message, "确定");
}
}
}
#endregion
}
4. 使用说明
-
安装:
-
将脚本放在
Assets/Editor/文件夹中 -
菜单栏选择
Tools/Unity Notepad或按Ctrl+Shift+N打开
-
-
主要功能:
-
多标签编辑
-
自动保存(每30秒)
-
文件导入/导出
-
文本搜索
-
自定义字体大小和换行
-
-
快捷键:
-
Ctrl/Cmd + S:保存 -
Ctrl/Cmd + N:新建标签 -
Ctrl/Cmd + W:关闭标签 -
Ctrl/Cmd + Tab:切换标签 -
Ctrl/Cmd + F:搜索
-
5. 扩展建议
如需增强功能,可考虑:
-
语法高亮:为不同文件类型(C#、JSON、Markdown)添加颜色
-
云端同步:集成Google Drive或OneDrive
-
版本控制:集成Git,记录修改历史
-
Markdown预览:添加实时预览面板
-
插件系统:支持自定义扩展
这个记事本完全运行在Unity Editor中,适合记录开发笔记、TODO列表、临时代码片段等。数据会自动保存到EditorPrefs中,不会影响项目文件。