diff --git a/frontend/bindings/voidraft/internal/models/models.ts b/frontend/bindings/voidraft/internal/models/models.ts index a5950c0..2437949 100644 --- a/frontend/bindings/voidraft/internal/models/models.ts +++ b/frontend/bindings/voidraft/internal/models/models.ts @@ -28,11 +28,6 @@ export class AppConfig { */ "appearance": AppearanceConfig; - /** - * 快捷键设置 - */ - "keyBindings": KeyBindingsConfig; - /** * 更新设置 */ @@ -54,9 +49,6 @@ export class AppConfig { if (!("appearance" in $$source)) { this["appearance"] = (new AppearanceConfig()); } - if (!("keyBindings" in $$source)) { - this["keyBindings"] = (new KeyBindingsConfig()); - } if (!("updates" in $$source)) { this["updates"] = (new UpdatesConfig()); } @@ -76,7 +68,6 @@ export class AppConfig { const $$createField2_0 = $$createType2; const $$createField3_0 = $$createType3; const $$createField4_0 = $$createType4; - const $$createField5_0 = $$createType5; let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; if ("general" in $$parsedSource) { $$parsedSource["general"] = $$createField0_0($$parsedSource["general"]); @@ -87,14 +78,11 @@ export class AppConfig { if ("appearance" in $$parsedSource) { $$parsedSource["appearance"] = $$createField2_0($$parsedSource["appearance"]); } - if ("keyBindings" in $$parsedSource) { - $$parsedSource["keyBindings"] = $$createField3_0($$parsedSource["keyBindings"]); - } if ("updates" in $$parsedSource) { - $$parsedSource["updates"] = $$createField4_0($$parsedSource["updates"]); + $$parsedSource["updates"] = $$createField3_0($$parsedSource["updates"]); } if ("metadata" in $$parsedSource) { - $$parsedSource["metadata"] = $$createField5_0($$parsedSource["metadata"]); + $$parsedSource["metadata"] = $$createField4_0($$parsedSource["metadata"]); } return new AppConfig($$parsedSource as Partial); } @@ -139,23 +127,15 @@ export class AppearanceConfig { * ConfigMetadata 配置元数据 */ export class ConfigMetadata { - /** - * 配置版本 - */ - "version": string; - /** * 最后更新时间 */ - "lastUpdated": time$0.Time; + "lastUpdated": string; /** Creates a new ConfigMetadata instance. */ constructor($$source: Partial = {}) { - if (!("version" in $$source)) { - this["version"] = ""; - } if (!("lastUpdated" in $$source)) { - this["lastUpdated"] = null; + this["lastUpdated"] = ""; } Object.assign(this, $$source); @@ -200,7 +180,7 @@ export class Document { * Creates a new Document instance from a string or object. */ static createFrom($$source: any = {}): Document { - const $$createField0_0 = $$createType6; + const $$createField0_0 = $$createType5; let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; if ("meta" in $$parsedSource) { $$parsedSource["meta"] = $$createField0_0($$parsedSource["meta"]); @@ -401,7 +381,7 @@ export class GeneralConfig { * Creates a new GeneralConfig instance from a string or object. */ static createFrom($$source: any = {}): GeneralConfig { - const $$createField4_0 = $$createType7; + const $$createField4_0 = $$createType6; let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; if ("globalHotkey" in $$parsedSource) { $$parsedSource["globalHotkey"] = $$createField4_0($$parsedSource["globalHotkey"]); @@ -470,25 +450,426 @@ export class HotkeyCombo { } /** - * KeyBindingsConfig 快捷键设置配置 + * KeyBinding 单个快捷键绑定 */ -export class KeyBindingsConfig { +export class KeyBinding { + /** + * 快捷键唯一标识 + */ + "id": string; - /** Creates a new KeyBindingsConfig instance. */ - constructor($$source: Partial = {}) { + /** + * 快捷键动作 + */ + "action": KeyBindingAction; + + /** + * 快捷键分类 + */ + "category": KeyBindingCategory; + + /** + * 快捷键作用域 + */ + "scope": KeyBindingScope; + + /** + * 快捷键组合(如 "Mod-f", "Ctrl-Shift-p") + */ + "key": string; + + /** + * 是否启用 + */ + "enabled": boolean; + + /** + * 是否为默认快捷键 + */ + "isDefault": boolean; + + /** Creates a new KeyBinding instance. */ + constructor($$source: Partial = {}) { + if (!("id" in $$source)) { + this["id"] = ""; + } + if (!("action" in $$source)) { + this["action"] = ("" as KeyBindingAction); + } + if (!("category" in $$source)) { + this["category"] = ("" as KeyBindingCategory); + } + if (!("scope" in $$source)) { + this["scope"] = ("" as KeyBindingScope); + } + if (!("key" in $$source)) { + this["key"] = ""; + } + if (!("enabled" in $$source)) { + this["enabled"] = false; + } + if (!("isDefault" in $$source)) { + this["isDefault"] = false; + } Object.assign(this, $$source); } /** - * Creates a new KeyBindingsConfig instance from a string or object. + * Creates a new KeyBinding instance from a string or object. */ - static createFrom($$source: any = {}): KeyBindingsConfig { + static createFrom($$source: any = {}): KeyBinding { let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; - return new KeyBindingsConfig($$parsedSource as Partial); + return new KeyBinding($$parsedSource as Partial); } } +/** + * KeyBindingAction 快捷键动作类型 + */ +export enum KeyBindingAction { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero = "", + + /** + * 搜索相关 + * 显示搜索 + */ + ActionShowSearch = "showSearch", + + /** + * 隐藏搜索 + */ + ActionHideSearch = "hideSearch", + + /** + * 查找下一个 + */ + ActionFindNext = "findNext", + + /** + * 查找上一个 + */ + ActionFindPrevious = "findPrevious", + + /** + * 显示替换 + */ + ActionShowReplace = "showReplace", + + /** + * 替换下一个 + */ + ActionReplaceNext = "replaceNext", + + /** + * 替换全部 + */ + ActionReplaceAll = "replaceAll", + + /** + * 切换大小写匹配 + */ + ActionToggleCase = "toggleCase", + + /** + * 切换全词匹配 + */ + ActionToggleWholeWord = "toggleWholeWord", + + /** + * 切换正则表达式 + */ + ActionToggleRegex = "toggleRegex", + + /** + * 编辑相关 + * 全选 + */ + ActionSelectAll = "selectAll", + + /** + * 复制 + */ + ActionCopy = "copy", + + /** + * 剪切 + */ + ActionCut = "cut", + + /** + * 粘贴 + */ + ActionPaste = "paste", + + /** + * 撤销 + */ + ActionUndo = "undo", + + /** + * 重做 + */ + ActionRedo = "redo", + + /** + * 复制行 + */ + ActionDuplicateLine = "duplicateLine", + + /** + * 删除行 + */ + ActionDeleteLine = "deleteLine", + + /** + * 上移行 + */ + ActionMoveLineUp = "moveLineUp", + + /** + * 下移行 + */ + ActionMoveLineDown = "moveLineDown", + + /** + * 切换注释 + */ + ActionToggleComment = "toggleComment", + + /** + * 缩进 + */ + ActionIndent = "indent", + + /** + * 取消缩进 + */ + ActionOutdent = "outdent", + + /** + * 代码块相关 + * 新建代码块 + */ + ActionNewCodeBlock = "newCodeBlock", + + /** + * 删除代码块 + */ + ActionDeleteCodeBlock = "deleteCodeBlock", + + /** + * 选择代码块 + */ + ActionSelectCodeBlock = "selectCodeBlock", + + /** + * 格式化代码 + */ + ActionFormatCode = "formatCode", + + /** + * 更改语言 + */ + ActionChangeLanguage = "changeLanguage", + + /** + * 导航相关 + * 跳转到行 + */ + ActionGoToLine = "goToLine", + + /** + * 折叠所有 + */ + ActionFoldAll = "foldAll", + + /** + * 展开所有 + */ + ActionUnfoldAll = "unfoldAll", + + /** + * 切换折叠 + */ + ActionToggleFold = "toggleFold", + + /** + * 视图相关 + * 放大 + */ + ActionZoomIn = "zoomIn", + + /** + * 缩小 + */ + ActionZoomOut = "zoomOut", + + /** + * 重置缩放 + */ + ActionResetZoom = "resetZoom", + + /** + * 切换小地图 + */ + ActionToggleMinimap = "toggleMinimap", + + /** + * 切换行号 + */ + ActionToggleLineNumbers = "toggleLineNumbers", + + /** + * 文件相关 + * 保存 + */ + ActionSave = "save", +}; + +/** + * KeyBindingCategory 快捷键分类 + */ +export enum KeyBindingCategory { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero = "", + + /** + * 搜索相关 + */ + CategorySearch = "search", + + /** + * 编辑相关 + */ + CategoryEdit = "edit", + + /** + * 代码块相关 + */ + CategoryCodeBlock = "codeblock", + + /** + * 导航相关 + */ + CategoryNavigation = "navigation", + + /** + * 视图相关 + */ + CategoryView = "view", + + /** + * 文件相关 + */ + CategoryFile = "file", + + /** + * 应用相关 + */ + CategoryApp = "app", +}; + +/** + * KeyBindingConfig 快捷键配置 + */ +export class KeyBindingConfig { + /** + * 快捷键列表 + */ + "keyBindings": KeyBinding[]; + + /** + * 配置元数据 + */ + "metadata": KeyBindingMetadata; + + /** Creates a new KeyBindingConfig instance. */ + constructor($$source: Partial = {}) { + if (!("keyBindings" in $$source)) { + this["keyBindings"] = []; + } + if (!("metadata" in $$source)) { + this["metadata"] = (new KeyBindingMetadata()); + } + + Object.assign(this, $$source); + } + + /** + * Creates a new KeyBindingConfig instance from a string or object. + */ + static createFrom($$source: any = {}): KeyBindingConfig { + const $$createField0_0 = $$createType8; + const $$createField1_0 = $$createType9; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("keyBindings" in $$parsedSource) { + $$parsedSource["keyBindings"] = $$createField0_0($$parsedSource["keyBindings"]); + } + if ("metadata" in $$parsedSource) { + $$parsedSource["metadata"] = $$createField1_0($$parsedSource["metadata"]); + } + return new KeyBindingConfig($$parsedSource as Partial); + } +} + +/** + * KeyBindingMetadata 快捷键配置元数据 + */ +export class KeyBindingMetadata { + /** + * 最后更新时间 + */ + "lastUpdated": string; + + /** Creates a new KeyBindingMetadata instance. */ + constructor($$source: Partial = {}) { + if (!("lastUpdated" in $$source)) { + this["lastUpdated"] = ""; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new KeyBindingMetadata instance from a string or object. + */ + static createFrom($$source: any = {}): KeyBindingMetadata { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new KeyBindingMetadata($$parsedSource as Partial); + } +} + +/** + * KeyBindingScope 快捷键作用域 + */ +export enum KeyBindingScope { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero = "", + + /** + * 全局作用域 + */ + ScopeGlobal = "global", + + /** + * 编辑器作用域 + */ + ScopeEditor = "editor", + + /** + * 搜索面板作用域 + */ + ScopeSearch = "search", +}; + /** * LanguageType 语言类型定义 */ @@ -578,8 +959,10 @@ export class UpdatesConfig { const $$createType0 = GeneralConfig.createFrom; const $$createType1 = EditingConfig.createFrom; const $$createType2 = AppearanceConfig.createFrom; -const $$createType3 = KeyBindingsConfig.createFrom; -const $$createType4 = UpdatesConfig.createFrom; -const $$createType5 = ConfigMetadata.createFrom; -const $$createType6 = DocumentMeta.createFrom; -const $$createType7 = HotkeyCombo.createFrom; +const $$createType3 = UpdatesConfig.createFrom; +const $$createType4 = ConfigMetadata.createFrom; +const $$createType5 = DocumentMeta.createFrom; +const $$createType6 = HotkeyCombo.createFrom; +const $$createType7 = KeyBinding.createFrom; +const $$createType8 = $Create.Array($$createType7); +const $$createType9 = KeyBindingMetadata.createFrom; diff --git a/frontend/bindings/voidraft/internal/services/index.ts b/frontend/bindings/voidraft/internal/services/index.ts index ca32ba9..5dfbc99 100644 --- a/frontend/bindings/voidraft/internal/services/index.ts +++ b/frontend/bindings/voidraft/internal/services/index.ts @@ -5,6 +5,7 @@ import * as ConfigService from "./configservice.js"; import * as DialogService from "./dialogservice.js"; import * as DocumentService from "./documentservice.js"; import * as HotkeyService from "./hotkeyservice.js"; +import * as KeyBindingService from "./keybindingservice.js"; import * as MigrationService from "./migrationservice.js"; import * as SystemService from "./systemservice.js"; import * as TrayService from "./trayservice.js"; @@ -13,6 +14,7 @@ export { DialogService, DocumentService, HotkeyService, + KeyBindingService, MigrationService, SystemService, TrayService diff --git a/frontend/bindings/voidraft/internal/services/keybindingservice.ts b/frontend/bindings/voidraft/internal/services/keybindingservice.ts new file mode 100644 index 0000000..00c8639 --- /dev/null +++ b/frontend/bindings/voidraft/internal/services/keybindingservice.ts @@ -0,0 +1,180 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +/** + * KeyBindingService 快捷键管理服务 + * @module + */ + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import {Call as $Call, Create as $Create} from "@wailsio/runtime"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as models$0 from "../models/models.js"; + +/** + * DisableKeyBinding 禁用快捷键 + */ +export function DisableKeyBinding(id: string): Promise & { cancel(): void } { + let $resultPromise = $Call.ByID(1594003006, id) as any; + return $resultPromise; +} + +/** + * EnableKeyBinding 启用快捷键 + */ +export function EnableKeyBinding(id: string): Promise & { cancel(): void } { + let $resultPromise = $Call.ByID(1462644129, id) as any; + return $resultPromise; +} + +/** + * ExportKeyBindings 导出快捷键配置 + */ +export function ExportKeyBindings(): Promise & { cancel(): void } { + let $resultPromise = $Call.ByID(4089030977) as any; + let $typingPromise = $resultPromise.then(($result: any) => { + return $$createType1($result); + }) as any; + $typingPromise.cancel = $resultPromise.cancel.bind($resultPromise); + return $typingPromise; +} + +/** + * GetAllKeyBindings 获取所有快捷键配置 + */ +export function GetAllKeyBindings(): Promise & { cancel(): void } { + let $resultPromise = $Call.ByID(1633502882) as any; + let $typingPromise = $resultPromise.then(($result: any) => { + return $$createType1($result); + }) as any; + $typingPromise.cancel = $resultPromise.cancel.bind($resultPromise); + return $typingPromise; +} + +/** + * GetKeyBindingByAction 根据动作获取快捷键 + */ +export function GetKeyBindingByAction(action: models$0.KeyBindingAction): Promise & { cancel(): void } { + let $resultPromise = $Call.ByID(752637777, action) as any; + let $typingPromise = $resultPromise.then(($result: any) => { + return $$createType2($result); + }) as any; + $typingPromise.cancel = $resultPromise.cancel.bind($resultPromise); + return $typingPromise; +} + +/** + * GetKeyBindingByID 根据ID获取快捷键 + */ +export function GetKeyBindingByID(id: string): Promise & { cancel(): void } { + let $resultPromise = $Call.ByID(1578192526, id) as any; + let $typingPromise = $resultPromise.then(($result: any) => { + return $$createType2($result); + }) as any; + $typingPromise.cancel = $resultPromise.cancel.bind($resultPromise); + return $typingPromise; +} + +/** + * GetKeyBindingCategories 获取所有快捷键分类 + */ +export function GetKeyBindingCategories(): Promise & { cancel(): void } { + let $resultPromise = $Call.ByID(3141399810) as any; + let $typingPromise = $resultPromise.then(($result: any) => { + return $$createType3($result); + }) as any; + $typingPromise.cancel = $resultPromise.cancel.bind($resultPromise); + return $typingPromise; +} + +/** + * GetKeyBindingConfig 获取完整快捷键配置 + */ +export function GetKeyBindingConfig(): Promise & { cancel(): void } { + let $resultPromise = $Call.ByID(3804318356) as any; + let $typingPromise = $resultPromise.then(($result: any) => { + return $$createType5($result); + }) as any; + $typingPromise.cancel = $resultPromise.cancel.bind($resultPromise); + return $typingPromise; +} + +/** + * GetKeyBindingScopes 获取所有快捷键作用域 + */ +export function GetKeyBindingScopes(): Promise & { cancel(): void } { + let $resultPromise = $Call.ByID(2984736455) as any; + let $typingPromise = $resultPromise.then(($result: any) => { + return $$createType6($result); + }) as any; + $typingPromise.cancel = $resultPromise.cancel.bind($resultPromise); + return $typingPromise; +} + +/** + * GetKeyBindingsByCategory 根据分类获取快捷键 + */ +export function GetKeyBindingsByCategory(category: models$0.KeyBindingCategory): Promise & { cancel(): void } { + let $resultPromise = $Call.ByID(1686146606, category) as any; + let $typingPromise = $resultPromise.then(($result: any) => { + return $$createType1($result); + }) as any; + $typingPromise.cancel = $resultPromise.cancel.bind($resultPromise); + return $typingPromise; +} + +/** + * GetKeyBindingsByScope 根据作用域获取快捷键 + */ +export function GetKeyBindingsByScope(scope: models$0.KeyBindingScope): Promise & { cancel(): void } { + let $resultPromise = $Call.ByID(1179712594, scope) as any; + let $typingPromise = $resultPromise.then(($result: any) => { + return $$createType1($result); + }) as any; + $typingPromise.cancel = $resultPromise.cancel.bind($resultPromise); + return $typingPromise; +} + +/** + * ImportKeyBindings 导入快捷键配置 + */ +export function ImportKeyBindings(keyBindings: models$0.KeyBinding[]): Promise & { cancel(): void } { + let $resultPromise = $Call.ByID(642201520, keyBindings) as any; + return $resultPromise; +} + +/** + * ResetAllKeyBindings 重置所有快捷键到默认值 + */ +export function ResetAllKeyBindings(): Promise & { cancel(): void } { + let $resultPromise = $Call.ByID(2771372645) as any; + return $resultPromise; +} + +/** + * ResetKeyBinding 重置快捷键到默认值 + */ +export function ResetKeyBinding(id: string): Promise & { cancel(): void } { + let $resultPromise = $Call.ByID(3466323405, id) as any; + return $resultPromise; +} + +/** + * UpdateKeyBinding 更新快捷键 + */ +export function UpdateKeyBinding(id: string, newKey: string): Promise & { cancel(): void } { + let $resultPromise = $Call.ByID(1469368983, id, newKey) as any; + return $resultPromise; +} + +// Private type creation functions +const $$createType0 = models$0.KeyBinding.createFrom; +const $$createType1 = $Create.Array($$createType0); +const $$createType2 = $Create.Nullable($$createType0); +const $$createType3 = $Create.Array($Create.Any); +const $$createType4 = models$0.KeyBindingConfig.createFrom; +const $$createType5 = $Create.Nullable($$createType4); +const $$createType6 = $Create.Array($Create.Any); diff --git a/frontend/src/stores/configStore.ts b/frontend/src/stores/configStore.ts index de1c49a..5e19e2f 100644 --- a/frontend/src/stores/configStore.ts +++ b/frontend/src/stores/configStore.ts @@ -9,7 +9,7 @@ import { LanguageType, SystemThemeType, TabType, -} from '../../bindings/voidraft/internal/models/models'; +} from '@/../bindings/voidraft/internal/models/models'; import {useI18n} from 'vue-i18n'; import {useErrorHandler} from '@/utils/errorHandler'; import {ConfigUtils} from '@/utils/configUtils'; @@ -47,27 +47,27 @@ type NumberConfigKey = 'fontSize' | 'tabSize' | 'lineHeight'; // 配置键映射 const GENERAL_CONFIG_KEY_MAP: GeneralConfigKeyMap = { - alwaysOnTop: 'general.always_on_top', - dataPath: 'general.data_path', - enableSystemTray: 'general.enable_system_tray', - enableGlobalHotkey: 'general.enable_global_hotkey', - globalHotkey: 'general.global_hotkey' + alwaysOnTop: 'general.alwaysOnTop', + dataPath: 'general.dataPath', + enableSystemTray: 'general.enableSystemTray', + enableGlobalHotkey: 'general.enableGlobalHotkey', + globalHotkey: 'general.globalHotkey' } as const; const EDITING_CONFIG_KEY_MAP: EditingConfigKeyMap = { - fontSize: 'editing.font_size', - fontFamily: 'editing.font_family', - fontWeight: 'editing.font_weight', - lineHeight: 'editing.line_height', - enableTabIndent: 'editing.enable_tab_indent', - tabSize: 'editing.tab_size', - tabType: 'editing.tab_type', - autoSaveDelay: 'editing.auto_save_delay' + fontSize: 'editing.fontSize', + fontFamily: 'editing.fontFamily', + fontWeight: 'editing.fontWeight', + lineHeight: 'editing.lineHeight', + enableTabIndent: 'editing.enableTabIndent', + tabSize: 'editing.tabSize', + tabType: 'editing.tabType', + autoSaveDelay: 'editing.autoSaveDelay' } as const; const APPEARANCE_CONFIG_KEY_MAP: AppearanceConfigKeyMap = { language: 'appearance.language', - systemTheme: 'appearance.system_theme' + systemTheme: 'appearance.systemTheme' } as const; // 配置限制 @@ -141,11 +141,9 @@ const DEFAULT_CONFIG: AppConfig = { language: LanguageType.LangZhCN, systemTheme: SystemThemeType.SystemThemeDark }, - keyBindings: {}, updates: {}, metadata: { - version: '1.0.0', - lastUpdated: null + lastUpdated: new Date().toString() } }; @@ -226,7 +224,6 @@ export const useConfigStore = defineStore('config', () => { if (appConfig.general) Object.assign(state.config.general, appConfig.general); if (appConfig.editing) Object.assign(state.config.editing, appConfig.editing); if (appConfig.appearance) Object.assign(state.config.appearance, appConfig.appearance); - if (appConfig.keyBindings) Object.assign(state.config.keyBindings, appConfig.keyBindings); if (appConfig.updates) Object.assign(state.config.updates, appConfig.updates); if (appConfig.metadata) Object.assign(state.config.metadata, appConfig.metadata); } diff --git a/frontend/src/views/settings/Settings.vue b/frontend/src/views/settings/Settings.vue index 0813df1..92c67d8 100644 --- a/frontend/src/views/settings/Settings.vue +++ b/frontend/src/views/settings/Settings.vue @@ -83,7 +83,7 @@ const goBackToEditor = async () => { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; .settings-sidebar { - width: 200px; + width: 180px; height: 100%; background-color: var(--settings-card-bg); border-right: 1px solid var(--settings-border); @@ -92,7 +92,7 @@ const goBackToEditor = async () => { box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1); .settings-header { - padding: 20px 16px; + padding: 16px 14px; border-bottom: 1px solid var(--settings-border); background-color: var(--settings-card-bg); @@ -125,7 +125,7 @@ const goBackToEditor = async () => { } h1 { - font-size: 18px; + font-size: 16px; font-weight: 600; margin: 0; color: var(--settings-text); @@ -138,10 +138,10 @@ const goBackToEditor = async () => { padding: 10px 0; overflow-y: auto; - .nav-item { + .nav-item { display: flex; align-items: center; - padding: 12px 16px; + padding: 10px 14px; cursor: pointer; transition: all 0.2s ease; border-left: 3px solid transparent; @@ -157,19 +157,19 @@ const goBackToEditor = async () => { } .nav-icon { - margin-right: 10px; - font-size: 16px; + margin-right: 8px; + font-size: 14px; opacity: 0.9; } .nav-text { - font-size: 14px; + font-size: 13px; } } } .settings-footer { - padding: 12px 16px 16px 16px; + padding: 10px 14px 14px 14px; border-top: 1px solid var(--settings-border); background-color: var(--settings-card-bg); @@ -179,7 +179,7 @@ const goBackToEditor = async () => { gap: 8px; .section-title { - font-size: 10px; + font-size: 9px; color: var(--settings-text-secondary); font-weight: 500; margin-bottom: 0; @@ -193,7 +193,7 @@ const goBackToEditor = async () => { .settings-content { flex: 1; height: 100%; - padding: 24px 24px 48px 24px; + padding: 20px 20px 40px 20px; overflow-y: auto; background-color: var(--settings-bg); } diff --git a/frontend/src/views/settings/components/SettingItem.vue b/frontend/src/views/settings/components/SettingItem.vue index 0564e86..f3c791b 100644 --- a/frontend/src/views/settings/components/SettingItem.vue +++ b/frontend/src/views/settings/components/SettingItem.vue @@ -20,7 +20,7 @@ defineProps<{ \ No newline at end of file diff --git a/frontend/src/views/settings/components/ToggleSwitch.vue b/frontend/src/views/settings/components/ToggleSwitch.vue index 57a6f45..105113d 100644 --- a/frontend/src/views/settings/components/ToggleSwitch.vue +++ b/frontend/src/views/settings/components/ToggleSwitch.vue @@ -1,29 +1,45 @@ \ No newline at end of file diff --git a/frontend/src/views/settings/pages/UpdatesPage.vue b/frontend/src/views/settings/pages/UpdatesPage.vue index e0bcb72..ccf2bd4 100644 --- a/frontend/src/views/settings/pages/UpdatesPage.vue +++ b/frontend/src/views/settings/pages/UpdatesPage.vue @@ -103,7 +103,7 @@ const downloadUpdate = () => { margin-bottom: 20px; .current-version { - font-size: 14px; + font-size: 13px; .label { color: var(--text-muted); @@ -123,7 +123,7 @@ const downloadUpdate = () => { border-radius: 4px; color: var(--settings-text); cursor: pointer; - font-size: 13px; + font-size: 12px; transition: all 0.2s ease; display: flex; align-items: center; @@ -172,7 +172,7 @@ const downloadUpdate = () => { margin-bottom: 16px; .update-title { - font-size: 14px; + font-size: 13px; font-weight: 500; color: #4a9eff; } @@ -184,7 +184,7 @@ const downloadUpdate = () => { border-radius: 4px; color: #ffffff; cursor: pointer; - font-size: 13px; + font-size: 12px; transition: all 0.2s ease; &:hover { @@ -199,7 +199,7 @@ const downloadUpdate = () => { .update-notes { .notes-title { - font-size: 13px; + font-size: 12px; color: var(--settings-text-secondary); margin-bottom: 8px; } @@ -209,7 +209,7 @@ const downloadUpdate = () => { padding-left: 20px; li { - font-size: 13px; + font-size: 12px; color: var(--settings-text-secondary); margin-bottom: 6px; diff --git a/internal/models/config.go b/internal/models/config.go index 0744f22..0958a15 100644 --- a/internal/models/config.go +++ b/internal/models/config.go @@ -40,50 +40,45 @@ const ( // GeneralConfig 通用设置配置 type GeneralConfig struct { - AlwaysOnTop bool `json:"alwaysOnTop" yaml:"always_on_top" mapstructure:"always_on_top"` // 窗口是否置顶 - DataPath string `json:"dataPath" yaml:"data_path" mapstructure:"data_path"` // 数据存储路径 - EnableSystemTray bool `json:"enableSystemTray" yaml:"enable_system_tray" mapstructure:"enable_system_tray"` // 是否启用系统托盘 + AlwaysOnTop bool `json:"alwaysOnTop"` // 窗口是否置顶 + DataPath string `json:"dataPath"` // 数据存储路径 + EnableSystemTray bool `json:"enableSystemTray"` // 是否启用系统托盘 // 全局热键设置 - EnableGlobalHotkey bool `json:"enableGlobalHotkey" yaml:"enable_global_hotkey" mapstructure:"enable_global_hotkey"` // 是否启用全局热键 - GlobalHotkey HotkeyCombo `json:"globalHotkey" yaml:"global_hotkey" mapstructure:"global_hotkey"` // 全局热键组合 + EnableGlobalHotkey bool `json:"enableGlobalHotkey"` // 是否启用全局热键 + GlobalHotkey HotkeyCombo `json:"globalHotkey"` // 全局热键组合 } // HotkeyCombo 热键组合定义 type HotkeyCombo struct { - Ctrl bool `json:"ctrl" yaml:"ctrl" mapstructure:"ctrl"` // Ctrl键 - Shift bool `json:"shift" yaml:"shift" mapstructure:"shift"` // Shift键 - Alt bool `json:"alt" yaml:"alt" mapstructure:"alt"` // Alt键 - Win bool `json:"win" yaml:"win" mapstructure:"win"` // Win键 - Key string `json:"key" yaml:"key" mapstructure:"key"` // 主键(如 'X', 'F1' 等) + Ctrl bool `json:"ctrl"` // Ctrl键 + Shift bool `json:"shift"` // Shift键 + Alt bool `json:"alt"` // Alt键 + Win bool `json:"win"` // Win键 + Key string `json:"key"` // 主键(如 'X', 'F1' 等) } // EditingConfig 编辑设置配置 type EditingConfig struct { // 字体设置 - FontSize int `json:"fontSize" yaml:"font_size" mapstructure:"font_size"` // 字体大小 - FontFamily string `json:"fontFamily" yaml:"font_family" mapstructure:"font_family"` // 字体族 - FontWeight string `json:"fontWeight" yaml:"font_weight" mapstructure:"font_weight"` // 字体粗细 - LineHeight float64 `json:"lineHeight" yaml:"line_height" mapstructure:"line_height"` // 行高 + FontSize int `json:"fontSize"` // 字体大小 + FontFamily string `json:"fontFamily"` // 字体族 + FontWeight string `json:"fontWeight"` // 字体粗细 + LineHeight float64 `json:"lineHeight"` // 行高 // Tab设置 - EnableTabIndent bool `json:"enableTabIndent" yaml:"enable_tab_indent" mapstructure:"enable_tab_indent"` // 是否启用Tab缩进 - TabSize int `json:"tabSize" yaml:"tab_size" mapstructure:"tab_size"` // Tab大小 - TabType TabType `json:"tabType" yaml:"tab_type" mapstructure:"tab_type"` // Tab类型(空格或Tab) + EnableTabIndent bool `json:"enableTabIndent"` // 是否启用Tab缩进 + TabSize int `json:"tabSize"` // Tab大小 + TabType TabType `json:"tabType"` // Tab类型(空格或Tab) // 保存选项 - AutoSaveDelay int `json:"autoSaveDelay" yaml:"auto_save_delay" mapstructure:"auto_save_delay"` // 自动保存延迟(毫秒) + AutoSaveDelay int `json:"autoSaveDelay"` // 自动保存延迟(毫秒) } // AppearanceConfig 外观设置配置 type AppearanceConfig struct { - Language LanguageType `json:"language" yaml:"language" mapstructure:"language"` // 界面语言 - SystemTheme SystemThemeType `json:"systemTheme" yaml:"system_theme" mapstructure:"system_theme"` // 系统界面主题 -} - -// KeyBindingsConfig 快捷键设置配置 -type KeyBindingsConfig struct { - // 预留给未来的快捷键配置 + Language LanguageType `json:"language"` // 界面语言 + SystemTheme SystemThemeType `json:"systemTheme"` // 系统界面主题 } // UpdatesConfig 更新设置配置 @@ -93,18 +88,16 @@ type UpdatesConfig struct { // AppConfig 应用配置 - 按照前端设置页面分类组织 type AppConfig struct { - General GeneralConfig `json:"general" yaml:"general" mapstructure:"general"` // 通用设置 - Editing EditingConfig `json:"editing" yaml:"editing" mapstructure:"editing"` // 编辑设置 - Appearance AppearanceConfig `json:"appearance" yaml:"appearance" mapstructure:"appearance"` // 外观设置 - KeyBindings KeyBindingsConfig `json:"keyBindings" yaml:"key_bindings" mapstructure:"key_bindings"` // 快捷键设置 - Updates UpdatesConfig `json:"updates" yaml:"updates" mapstructure:"updates"` // 更新设置 - Metadata ConfigMetadata `json:"metadata" yaml:"metadata" mapstructure:"metadata"` // 配置元数据 + General GeneralConfig `json:"general"` // 通用设置 + Editing EditingConfig `json:"editing"` // 编辑设置 + Appearance AppearanceConfig `json:"appearance"` // 外观设置 + Updates UpdatesConfig `json:"updates"` // 更新设置 + Metadata ConfigMetadata `json:"metadata"` // 配置元数据 } // ConfigMetadata 配置元数据 type ConfigMetadata struct { - Version string `json:"version" yaml:"version" mapstructure:"version"` // 配置版本 - LastUpdated time.Time `json:"lastUpdated" yaml:"last_updated" mapstructure:"last_updated"` // 最后更新时间 + LastUpdated string `json:"lastUpdated"` // 最后更新时间 } // NewDefaultAppConfig 创建默认应用配置 @@ -149,15 +142,9 @@ func NewDefaultAppConfig() *AppConfig { Language: LangZhCN, SystemTheme: SystemThemeDark, // 默认使用深色系统主题 }, - KeyBindings: KeyBindingsConfig{ - // 预留给未来的快捷键配置 - }, - Updates: UpdatesConfig{ - // 预留给未来的更新配置 - }, + Updates: UpdatesConfig{}, Metadata: ConfigMetadata{ - Version: "1.0.0", - LastUpdated: time.Now(), + LastUpdated: time.Now().Format(time.RFC3339), }, } } diff --git a/internal/models/key_bindings.go b/internal/models/key_bindings.go new file mode 100644 index 0000000..a539178 --- /dev/null +++ b/internal/models/key_bindings.go @@ -0,0 +1,462 @@ +package models + +import "time" + +// KeyBinding 单个快捷键绑定 +type KeyBinding struct { + ID string `json:"id"` // 快捷键唯一标识 + Action KeyBindingAction `json:"action"` // 快捷键动作 + Category KeyBindingCategory `json:"category"` // 快捷键分类 + Scope KeyBindingScope `json:"scope"` // 快捷键作用域 + Key string `json:"key"` // 快捷键组合(如 "Mod-f", "Ctrl-Shift-p") + Enabled bool `json:"enabled"` // 是否启用 + IsDefault bool `json:"isDefault"` // 是否为默认快捷键 +} + +// KeyBindingCategory 快捷键分类 +type KeyBindingCategory string + +const ( + CategorySearch KeyBindingCategory = "search" // 搜索相关 + CategoryEdit KeyBindingCategory = "edit" // 编辑相关 + CategoryCodeBlock KeyBindingCategory = "codeblock" // 代码块相关 + CategoryNavigation KeyBindingCategory = "navigation" // 导航相关 + CategoryView KeyBindingCategory = "view" // 视图相关 + CategoryFile KeyBindingCategory = "file" // 文件相关 + CategoryApp KeyBindingCategory = "app" // 应用相关 +) + +// KeyBindingScope 快捷键作用域 +type KeyBindingScope string + +const ( + ScopeGlobal KeyBindingScope = "global" // 全局作用域 + ScopeEditor KeyBindingScope = "editor" // 编辑器作用域 + ScopeSearch KeyBindingScope = "search" // 搜索面板作用域 +) + +// KeyBindingAction 快捷键动作类型 +type KeyBindingAction string + +const ( + // 搜索相关 + ActionShowSearch KeyBindingAction = "showSearch" // 显示搜索 + ActionHideSearch KeyBindingAction = "hideSearch" // 隐藏搜索 + ActionFindNext KeyBindingAction = "findNext" // 查找下一个 + ActionFindPrevious KeyBindingAction = "findPrevious" // 查找上一个 + ActionShowReplace KeyBindingAction = "showReplace" // 显示替换 + ActionReplaceNext KeyBindingAction = "replaceNext" // 替换下一个 + ActionReplaceAll KeyBindingAction = "replaceAll" // 替换全部 + ActionToggleCase KeyBindingAction = "toggleCase" // 切换大小写匹配 + ActionToggleWholeWord KeyBindingAction = "toggleWholeWord" // 切换全词匹配 + ActionToggleRegex KeyBindingAction = "toggleRegex" // 切换正则表达式 + + // 编辑相关 + ActionSelectAll KeyBindingAction = "selectAll" // 全选 + ActionCopy KeyBindingAction = "copy" // 复制 + ActionCut KeyBindingAction = "cut" // 剪切 + ActionPaste KeyBindingAction = "paste" // 粘贴 + ActionUndo KeyBindingAction = "undo" // 撤销 + ActionRedo KeyBindingAction = "redo" // 重做 + ActionDuplicateLine KeyBindingAction = "duplicateLine" // 复制行 + ActionDeleteLine KeyBindingAction = "deleteLine" // 删除行 + ActionMoveLineUp KeyBindingAction = "moveLineUp" // 上移行 + ActionMoveLineDown KeyBindingAction = "moveLineDown" // 下移行 + ActionToggleComment KeyBindingAction = "toggleComment" // 切换注释 + ActionIndent KeyBindingAction = "indent" // 缩进 + ActionOutdent KeyBindingAction = "outdent" // 取消缩进 + + // 代码块相关 + ActionNewCodeBlock KeyBindingAction = "newCodeBlock" // 新建代码块 + ActionDeleteCodeBlock KeyBindingAction = "deleteCodeBlock" // 删除代码块 + ActionSelectCodeBlock KeyBindingAction = "selectCodeBlock" // 选择代码块 + ActionFormatCode KeyBindingAction = "formatCode" // 格式化代码 + ActionChangeLanguage KeyBindingAction = "changeLanguage" // 更改语言 + + // 导航相关 + ActionGoToLine KeyBindingAction = "goToLine" // 跳转到行 + ActionFoldAll KeyBindingAction = "foldAll" // 折叠所有 + ActionUnfoldAll KeyBindingAction = "unfoldAll" // 展开所有 + ActionToggleFold KeyBindingAction = "toggleFold" // 切换折叠 + + // 视图相关 + ActionZoomIn KeyBindingAction = "zoomIn" // 放大 + ActionZoomOut KeyBindingAction = "zoomOut" // 缩小 + ActionResetZoom KeyBindingAction = "resetZoom" // 重置缩放 + ActionToggleMinimap KeyBindingAction = "toggleMinimap" // 切换小地图 + ActionToggleLineNumbers KeyBindingAction = "toggleLineNumbers" // 切换行号 + + // 文件相关 + ActionSave KeyBindingAction = "save" // 保存 +) + +// KeyBindingMetadata 快捷键配置元数据 +type KeyBindingMetadata struct { + LastUpdated string `json:"lastUpdated"` // 最后更新时间 +} + +// KeyBindingConfig 快捷键配置 +type KeyBindingConfig struct { + KeyBindings []KeyBinding `json:"keyBindings"` // 快捷键列表 + Metadata KeyBindingMetadata `json:"metadata"` // 配置元数据 +} + +// NewDefaultKeyBindingConfig 创建默认快捷键配置 +func NewDefaultKeyBindingConfig() *KeyBindingConfig { + return &KeyBindingConfig{ + KeyBindings: NewDefaultKeyBindings(), + Metadata: KeyBindingMetadata{ + LastUpdated: time.Now().Format(time.RFC3339), + }, + } +} + +// NewDefaultKeyBindings 创建默认快捷键配置 +func NewDefaultKeyBindings() []KeyBinding { + return []KeyBinding{ + // 搜索相关快捷键 + { + ID: "search.show", + Action: ActionShowSearch, + Category: CategorySearch, + Scope: ScopeGlobal, + Key: "Mod-f", + Enabled: true, + IsDefault: true, + }, + { + ID: "search.hide", + Action: ActionHideSearch, + Category: CategorySearch, + Scope: ScopeSearch, + Key: "Escape", + Enabled: true, + IsDefault: true, + }, + { + ID: "search.findNext", + Action: ActionFindNext, + Category: CategorySearch, + Scope: ScopeSearch, + Key: "Enter", + Enabled: true, + IsDefault: true, + }, + { + ID: "search.findPrevious", + Action: ActionFindPrevious, + Category: CategorySearch, + Scope: ScopeSearch, + Key: "Shift-Enter", + Enabled: true, + IsDefault: true, + }, + { + ID: "search.showReplace", + Action: ActionShowReplace, + Category: CategorySearch, + Scope: ScopeSearch, + Key: "Mod-h", + Enabled: true, + IsDefault: true, + }, + { + ID: "search.replaceAll", + Action: ActionReplaceAll, + Category: CategorySearch, + Scope: ScopeSearch, + Key: "Mod-Alt-Enter", + Enabled: true, + IsDefault: true, + }, + { + ID: "search.toggleCase", + Action: ActionToggleCase, + Category: CategorySearch, + Scope: ScopeSearch, + Key: "Alt-c", + Enabled: true, + IsDefault: true, + }, + { + ID: "search.toggleWholeWord", + Action: ActionToggleWholeWord, + Category: CategorySearch, + Scope: ScopeSearch, + Key: "Alt-w", + Enabled: true, + IsDefault: true, + }, + { + ID: "search.toggleRegex", + Action: ActionToggleRegex, + Category: CategorySearch, + Scope: ScopeSearch, + Key: "Alt-r", + Enabled: true, + IsDefault: true, + }, + + // 编辑相关快捷键 + { + ID: "edit.selectAll", + Action: ActionSelectAll, + Category: CategoryEdit, + Scope: ScopeEditor, + Key: "Mod-a", + Enabled: true, + IsDefault: true, + }, + { + ID: "edit.copy", + Action: ActionCopy, + Category: CategoryEdit, + Scope: ScopeEditor, + Key: "Mod-c", + Enabled: true, + IsDefault: true, + }, + { + ID: "edit.cut", + Action: ActionCut, + Category: CategoryEdit, + Scope: ScopeEditor, + Key: "Mod-x", + Enabled: true, + IsDefault: true, + }, + { + ID: "edit.paste", + Action: ActionPaste, + Category: CategoryEdit, + Scope: ScopeEditor, + Key: "Mod-v", + Enabled: true, + IsDefault: true, + }, + { + ID: "edit.undo", + Action: ActionUndo, + Category: CategoryEdit, + Scope: ScopeEditor, + Key: "Mod-z", + Enabled: true, + IsDefault: true, + }, + { + ID: "edit.redo", + Action: ActionRedo, + Category: CategoryEdit, + Scope: ScopeEditor, + Key: "Mod-y", + Enabled: true, + IsDefault: true, + }, + { + ID: "edit.duplicateLine", + Action: ActionDuplicateLine, + Category: CategoryEdit, + Scope: ScopeEditor, + Key: "Mod-d", + Enabled: true, + IsDefault: true, + }, + { + ID: "edit.deleteLine", + Action: ActionDeleteLine, + Category: CategoryEdit, + Scope: ScopeEditor, + Key: "Mod-Shift-k", + Enabled: true, + IsDefault: true, + }, + { + ID: "edit.moveLineUp", + Action: ActionMoveLineUp, + Category: CategoryEdit, + Scope: ScopeEditor, + Key: "Alt-ArrowUp", + Enabled: true, + IsDefault: true, + }, + { + ID: "edit.moveLineDown", + Action: ActionMoveLineDown, + Category: CategoryEdit, + Scope: ScopeEditor, + Key: "Alt-ArrowDown", + Enabled: true, + IsDefault: true, + }, + { + ID: "edit.toggleComment", + Action: ActionToggleComment, + Category: CategoryEdit, + Scope: ScopeEditor, + Key: "Mod-/", + Enabled: true, + IsDefault: true, + }, + { + ID: "edit.indent", + Action: ActionIndent, + Category: CategoryEdit, + Scope: ScopeEditor, + Key: "Tab", + Enabled: true, + IsDefault: true, + }, + { + ID: "edit.outdent", + Action: ActionOutdent, + Category: CategoryEdit, + Scope: ScopeEditor, + Key: "Shift-Tab", + Enabled: true, + IsDefault: true, + }, + + // 代码块相关快捷键 + { + ID: "codeblock.new", + Action: ActionNewCodeBlock, + Category: CategoryCodeBlock, + Scope: ScopeEditor, + Key: "Mod-Alt-n", + Enabled: true, + IsDefault: true, + }, + { + ID: "codeblock.delete", + Action: ActionDeleteCodeBlock, + Category: CategoryCodeBlock, + Scope: ScopeEditor, + Key: "Mod-Alt-d", + Enabled: true, + IsDefault: true, + }, + { + ID: "codeblock.select", + Action: ActionSelectCodeBlock, + Category: CategoryCodeBlock, + Scope: ScopeEditor, + Key: "Mod-Alt-a", + Enabled: true, + IsDefault: true, + }, + { + ID: "codeblock.format", + Action: ActionFormatCode, + Category: CategoryCodeBlock, + Scope: ScopeEditor, + Key: "Mod-Alt-f", + Enabled: true, + IsDefault: true, + }, + { + ID: "codeblock.changeLanguage", + Action: ActionChangeLanguage, + Category: CategoryCodeBlock, + Scope: ScopeEditor, + Key: "Mod-Alt-l", + Enabled: true, + IsDefault: true, + }, + + // 导航相关快捷键 + { + ID: "navigation.goToLine", + Action: ActionGoToLine, + Category: CategoryNavigation, + Scope: ScopeEditor, + Key: "Mod-g", + Enabled: true, + IsDefault: true, + }, + { + ID: "navigation.foldAll", + Action: ActionFoldAll, + Category: CategoryNavigation, + Scope: ScopeEditor, + Key: "Mod-k Mod-0", + Enabled: true, + IsDefault: true, + }, + { + ID: "navigation.unfoldAll", + Action: ActionUnfoldAll, + Category: CategoryNavigation, + Scope: ScopeEditor, + Key: "Mod-k Mod-j", + Enabled: true, + IsDefault: true, + }, + { + ID: "navigation.toggleFold", + Action: ActionToggleFold, + Category: CategoryNavigation, + Scope: ScopeEditor, + Key: "Mod-k Mod-l", + Enabled: true, + IsDefault: true, + }, + + // 视图相关快捷键 + { + ID: "view.zoomIn", + Action: ActionZoomIn, + Category: CategoryView, + Scope: ScopeGlobal, + Key: "Mod-=", + Enabled: true, + IsDefault: true, + }, + { + ID: "view.zoomOut", + Action: ActionZoomOut, + Category: CategoryView, + Scope: ScopeGlobal, + Key: "Mod--", + Enabled: true, + IsDefault: true, + }, + { + ID: "view.resetZoom", + Action: ActionResetZoom, + Category: CategoryView, + Scope: ScopeGlobal, + Key: "Mod-0", + Enabled: true, + IsDefault: true, + }, + { + ID: "view.toggleMinimap", + Action: ActionToggleMinimap, + Category: CategoryView, + Scope: ScopeGlobal, + Key: "Mod-m", + Enabled: true, + IsDefault: true, + }, + { + ID: "view.toggleLineNumbers", + Action: ActionToggleLineNumbers, + Category: CategoryView, + Scope: ScopeGlobal, + Key: "Mod-l", + Enabled: true, + IsDefault: true, + }, + + // 文件相关快捷键 + { + ID: "file.save", + Action: ActionSave, + Category: CategoryFile, + Scope: ScopeGlobal, + Key: "Mod-s", + Enabled: true, + IsDefault: true, + }, + } +} diff --git a/internal/services/config_service.go b/internal/services/config_service.go index 0e7bf0e..1f9828e 100644 --- a/internal/services/config_service.go +++ b/internal/services/config_service.go @@ -1,6 +1,7 @@ package services import ( + "encoding/json" "errors" "fmt" "os" @@ -62,14 +63,14 @@ func NewConfigService(logger *log.LoggerService) *ConfigService { // 固定配置路径和文件名 configPath := filepath.Join(currentDir, "config") - configName := "config" + configName := "settings" // 创建 Viper 实例 v := viper.New() // 配置 Viper v.SetConfigName(configName) - v.SetConfigType("yaml") + v.SetConfigType("json") v.AddConfigPath(configPath) // 设置环境变量前缀 @@ -104,33 +105,32 @@ func setDefaults(v *viper.Viper) { defaultConfig := models.NewDefaultAppConfig() // 通用设置默认值 - v.SetDefault("general.always_on_top", defaultConfig.General.AlwaysOnTop) - v.SetDefault("general.data_path", defaultConfig.General.DataPath) - v.SetDefault("general.enable_system_tray", defaultConfig.General.EnableSystemTray) - v.SetDefault("general.enable_global_hotkey", defaultConfig.General.EnableGlobalHotkey) - v.SetDefault("general.global_hotkey.ctrl", defaultConfig.General.GlobalHotkey.Ctrl) - v.SetDefault("general.global_hotkey.shift", defaultConfig.General.GlobalHotkey.Shift) - v.SetDefault("general.global_hotkey.alt", defaultConfig.General.GlobalHotkey.Alt) - v.SetDefault("general.global_hotkey.win", defaultConfig.General.GlobalHotkey.Win) - v.SetDefault("general.global_hotkey.key", defaultConfig.General.GlobalHotkey.Key) + v.SetDefault("general.alwaysOnTop", defaultConfig.General.AlwaysOnTop) + v.SetDefault("general.dataPath", defaultConfig.General.DataPath) + v.SetDefault("general.enableSystemTray", defaultConfig.General.EnableSystemTray) + v.SetDefault("general.enableGlobalHotkey", defaultConfig.General.EnableGlobalHotkey) + v.SetDefault("general.globalHotkey.ctrl", defaultConfig.General.GlobalHotkey.Ctrl) + v.SetDefault("general.globalHotkey.shift", defaultConfig.General.GlobalHotkey.Shift) + v.SetDefault("general.globalHotkey.alt", defaultConfig.General.GlobalHotkey.Alt) + v.SetDefault("general.globalHotkey.win", defaultConfig.General.GlobalHotkey.Win) + v.SetDefault("general.globalHotkey.key", defaultConfig.General.GlobalHotkey.Key) // 编辑设置默认值 - v.SetDefault("editing.font_size", defaultConfig.Editing.FontSize) - v.SetDefault("editing.font_family", defaultConfig.Editing.FontFamily) - v.SetDefault("editing.font_weight", defaultConfig.Editing.FontWeight) - v.SetDefault("editing.line_height", defaultConfig.Editing.LineHeight) - v.SetDefault("editing.enable_tab_indent", defaultConfig.Editing.EnableTabIndent) - v.SetDefault("editing.tab_size", defaultConfig.Editing.TabSize) - v.SetDefault("editing.tab_type", defaultConfig.Editing.TabType) - v.SetDefault("editing.auto_save_delay", defaultConfig.Editing.AutoSaveDelay) + v.SetDefault("editing.fontSize", defaultConfig.Editing.FontSize) + v.SetDefault("editing.fontFamily", defaultConfig.Editing.FontFamily) + v.SetDefault("editing.fontWeight", defaultConfig.Editing.FontWeight) + v.SetDefault("editing.lineHeight", defaultConfig.Editing.LineHeight) + v.SetDefault("editing.enableTabIndent", defaultConfig.Editing.EnableTabIndent) + v.SetDefault("editing.tabSize", defaultConfig.Editing.TabSize) + v.SetDefault("editing.tabType", defaultConfig.Editing.TabType) + v.SetDefault("editing.autoSaveDelay", defaultConfig.Editing.AutoSaveDelay) // 外观设置默认值 v.SetDefault("appearance.language", defaultConfig.Appearance.Language) - v.SetDefault("appearance.system_theme", defaultConfig.Appearance.SystemTheme) + v.SetDefault("appearance.systemTheme", defaultConfig.Appearance.SystemTheme) // 元数据默认值 - v.SetDefault("metadata.version", defaultConfig.Metadata.Version) - v.SetDefault("metadata.last_updated", defaultConfig.Metadata.LastUpdated) + v.SetDefault("metadata.lastUpdated", defaultConfig.Metadata.LastUpdated) } // initConfig 初始化配置 @@ -162,21 +162,32 @@ func (cs *ConfigService) createDefaultConfig() error { currentDir = "." } configDir := filepath.Join(currentDir, "config") - configPath := filepath.Join(configDir, "config.yaml") + configPath := filepath.Join(configDir, "settings.json") // 确保配置目录存在 if err := os.MkdirAll(configDir, 0755); err != nil { return &ConfigError{Operation: "create_config_dir", Err: err} } - // 设置当前时间为最后更新时间 - cs.viper.Set("metadata.last_updated", time.Now()) + // 获取默认配置 + defaultConfig := models.NewDefaultAppConfig() - // 使用 SafeWriteConfigAs 写入配置文件(如果文件不存在则创建) - if err := cs.viper.SafeWriteConfigAs(configPath); err != nil { + // 使用 JSON marshal 方式设置完整的默认配置 + configBytes, err := json.MarshalIndent(defaultConfig, "", " ") + if err != nil { + return &ConfigError{Operation: "marshal_default_config", Err: err} + } + + // 写入配置文件 + if err := os.WriteFile(configPath, configBytes, 0644); err != nil { return &ConfigError{Operation: "write_default_config", Err: err} } + // 重新读取配置文件到viper + if err := cs.viper.ReadInConfig(); err != nil { + return &ConfigError{Operation: "read_created_config", Err: err} + } + cs.logger.Info("Config: Created default config file", "path", configPath) return nil } @@ -213,11 +224,21 @@ func (cs *ConfigService) GetConfig() (*models.AppConfig, error) { func (cs *ConfigService) Set(key string, value interface{}) error { cs.mu.Lock() defer cs.mu.Unlock() - // 设置验证后的值 + + // 设置值到viper cs.viper.Set(key, value) - // 使用 WriteConfig 写入配置文件(会触发文件监听) - if err := cs.viper.WriteConfig(); err != nil { + // 直接从viper获取配置并构建AppConfig结构 + var config models.AppConfig + if err := cs.viper.Unmarshal(&config); err != nil { + return &ConfigError{Operation: "unmarshal_config_for_set", Err: err} + } + + // 更新时间戳 + config.Metadata.LastUpdated = time.Now().Format(time.RFC3339) + + // 直接写入JSON文件 + if err := cs.writeConfigToFile(&config); err != nil { return &ConfigError{Operation: "set_config", Err: err} } @@ -238,43 +259,49 @@ func (cs *ConfigService) ResetConfig() { defaultConfig := models.NewDefaultAppConfig() - // 通用设置 - 批量设置到viper中 - cs.viper.Set("general.always_on_top", defaultConfig.General.AlwaysOnTop) - cs.viper.Set("general.data_path", defaultConfig.General.DataPath) - cs.viper.Set("general.enable_system_tray", defaultConfig.General.EnableSystemTray) - cs.viper.Set("general.enable_global_hotkey", defaultConfig.General.EnableGlobalHotkey) - cs.viper.Set("general.global_hotkey.ctrl", defaultConfig.General.GlobalHotkey.Ctrl) - cs.viper.Set("general.global_hotkey.shift", defaultConfig.General.GlobalHotkey.Shift) - cs.viper.Set("general.global_hotkey.alt", defaultConfig.General.GlobalHotkey.Alt) - cs.viper.Set("general.global_hotkey.win", defaultConfig.General.GlobalHotkey.Win) - cs.viper.Set("general.global_hotkey.key", defaultConfig.General.GlobalHotkey.Key) - - // 编辑设置 - 批量设置到viper中 - cs.viper.Set("editing.font_size", defaultConfig.Editing.FontSize) - cs.viper.Set("editing.font_family", defaultConfig.Editing.FontFamily) - cs.viper.Set("editing.font_weight", defaultConfig.Editing.FontWeight) - cs.viper.Set("editing.line_height", defaultConfig.Editing.LineHeight) - cs.viper.Set("editing.enable_tab_indent", defaultConfig.Editing.EnableTabIndent) - cs.viper.Set("editing.tab_size", defaultConfig.Editing.TabSize) - cs.viper.Set("editing.tab_type", defaultConfig.Editing.TabType) - cs.viper.Set("editing.auto_save_delay", defaultConfig.Editing.AutoSaveDelay) - - // 外观设置 - 批量设置到viper中 - cs.viper.Set("appearance.language", defaultConfig.Appearance.Language) - cs.viper.Set("appearance.system_theme", defaultConfig.Appearance.SystemTheme) - - // 元数据 - 批量设置到viper中 - cs.viper.Set("metadata.version", defaultConfig.Metadata.Version) - cs.viper.Set("metadata.last_updated", time.Now()) - - // 一次性写入配置文件,触发配置变更通知 - if err := cs.viper.WriteConfig(); err != nil { + // 直接写入JSON文件 + if err := cs.writeConfigToFile(defaultConfig); err != nil { cs.logger.Error("Config: Failed to write config during reset", "error", err) - } else { - cs.logger.Info("Config: All settings have been reset to defaults") - // 手动触发配置变更检查,确保通知系统能感知到变更 - cs.notificationService.CheckConfigChanges() + return } + + // 重新读取配置文件到viper + if err := cs.viper.ReadInConfig(); err != nil { + cs.logger.Error("Config: Failed to reload config after reset", "error", err) + return + } + + cs.logger.Info("Config: All settings have been reset to defaults") + // 手动触发配置变更检查,确保通知系统能感知到变更 + cs.notificationService.CheckConfigChanges() +} + +// writeConfigToFile 直接写入配置到JSON文件 +func (cs *ConfigService) writeConfigToFile(config *models.AppConfig) error { + // 获取配置文件路径 + currentDir, err := os.Getwd() + if err != nil { + currentDir = "." + } + configPath := filepath.Join(currentDir, "config", "settings.json") + + // 序列化为JSON + configBytes, err := json.MarshalIndent(config, "", " ") + if err != nil { + return fmt.Errorf("failed to marshal config: %v", err) + } + + // 写入文件 + if err := os.WriteFile(configPath, configBytes, 0644); err != nil { + return fmt.Errorf("failed to write config file: %v", err) + } + + // 重新读取到viper中 + if err := cs.viper.ReadInConfig(); err != nil { + return fmt.Errorf("failed to reload config: %v", err) + } + + return nil } // SetHotkeyChangeCallback 设置热键配置变更回调 diff --git a/internal/services/keybinding_service.go b/internal/services/keybinding_service.go new file mode 100644 index 0000000..9063bef --- /dev/null +++ b/internal/services/keybinding_service.go @@ -0,0 +1,631 @@ +package services + +import ( + "encoding/json" + "errors" + "fmt" + "os" + "path/filepath" + "strings" + "sync" + "time" + "voidraft/internal/models" + + "github.com/fsnotify/fsnotify" + "github.com/spf13/viper" + "github.com/wailsapp/wails/v3/pkg/services/log" +) + +// KeyBindingService 快捷键管理服务 +type KeyBindingService struct { + viper *viper.Viper // Viper 实例 + logger *log.LoggerService // 日志服务 + mu sync.RWMutex // 读写锁 +} + +// KeyBindingError 快捷键错误 +type KeyBindingError struct { + Operation string // 操作名称 + KeyID string // 快捷键ID + Err error // 原始错误 +} + +// Error 实现error接口 +func (e *KeyBindingError) Error() string { + if e.KeyID != "" { + return fmt.Sprintf("keybinding error during %s for key %s: %v", e.Operation, e.KeyID, e.Err) + } + return fmt.Sprintf("keybinding error during %s: %v", e.Operation, e.Err) +} + +// Unwrap 获取原始错误 +func (e *KeyBindingError) Unwrap() error { + return e.Err +} + +// Is 实现错误匹配 +func (e *KeyBindingError) Is(target error) bool { + var keyBindingError *KeyBindingError + ok := errors.As(target, &keyBindingError) + return ok +} + +// NewKeyBindingService 创建新的快捷键服务实例 +func NewKeyBindingService(logger *log.LoggerService) *KeyBindingService { + // 设置日志服务 + if logger == nil { + logger = log.New() + } + + // 获取当前工作目录 + currentDir, err := os.Getwd() + if err != nil { + currentDir = "." + } + + // 固定配置路径和文件名 + configPath := filepath.Join(currentDir, "config") + configName := "keybindings" + + // 创建 Viper 实例 + v := viper.New() + + // 配置 Viper + v.SetConfigName(configName) + v.SetConfigType("json") + v.AddConfigPath(configPath) + + // 设置环境变量前缀 + v.SetEnvPrefix("VOIDRAFT_KEYBINDING") + v.AutomaticEnv() + + // 设置默认值 + setKeyBindingDefaults(v) + + // 构造快捷键服务实例 + service := &KeyBindingService{ + viper: v, + logger: logger, + } + + // 初始化配置 + if err := service.initConfig(); err != nil { + service.logger.Error("KeyBinding: Failed to initialize keybinding config", "error", err) + } + + // 启动配置文件监听 + service.startWatching() + + return service +} + +// setKeyBindingDefaults 设置默认快捷键配置值 +func setKeyBindingDefaults(v *viper.Viper) { + defaultConfig := models.NewDefaultKeyBindingConfig() + + // 快捷键列表默认值 + v.SetDefault("keyBindings", defaultConfig.KeyBindings) + + // 元数据默认值 + v.SetDefault("metadata.lastUpdated", defaultConfig.Metadata.LastUpdated) +} + +// initConfig 初始化配置 +func (kbs *KeyBindingService) initConfig() error { + kbs.mu.Lock() + defer kbs.mu.Unlock() + + // 尝试读取配置文件 + if err := kbs.viper.ReadInConfig(); err != nil { + var configFileNotFoundError viper.ConfigFileNotFoundError + if errors.As(err, &configFileNotFoundError) { + // 配置文件不存在,创建默认配置文件 + kbs.logger.Info("KeyBinding: Config file not found, creating default keybinding config") + return kbs.createDefaultConfig() + } + // 配置文件存在但读取失败 + return &KeyBindingError{Operation: "read_keybinding_config", Err: err} + } + + kbs.logger.Info("KeyBinding: Successfully loaded keybinding config file", "file", kbs.viper.ConfigFileUsed()) + return nil +} + +// createDefaultConfig 创建默认配置文件 +func (kbs *KeyBindingService) createDefaultConfig() error { + // 获取配置目录路径 + currentDir, err := os.Getwd() + if err != nil { + currentDir = "." + } + configDir := filepath.Join(currentDir, "config") + configPath := filepath.Join(configDir, "keybindings.json") + + // 确保配置目录存在 + if err := os.MkdirAll(configDir, 0755); err != nil { + return &KeyBindingError{Operation: "create_keybinding_config_dir", Err: err} + } + + // 获取默认配置 + defaultConfig := models.NewDefaultKeyBindingConfig() + configBytes, err := json.MarshalIndent(defaultConfig, "", " ") + if err != nil { + return &KeyBindingError{Operation: "marshal_default_keybinding_config", Err: err} + } + + // 写入配置文件 + if err := os.WriteFile(configPath, configBytes, 0644); err != nil { + return &KeyBindingError{Operation: "write_default_keybinding_config", Err: err} + } + + // 重新读取配置文件到viper + if err := kbs.viper.ReadInConfig(); err != nil { + return &KeyBindingError{Operation: "read_created_keybinding_config", Err: err} + } + + kbs.logger.Info("KeyBinding: Created default keybinding config file", "path", configPath) + return nil +} + +// startWatching 启动配置文件监听 +func (kbs *KeyBindingService) startWatching() { + // 设置配置变化回调 + kbs.viper.OnConfigChange(func(e fsnotify.Event) { + kbs.logger.Info("KeyBinding: Config file changed", "file", e.Name, "operation", e.Op.String()) + }) + + // 启动配置文件监听 + kbs.viper.WatchConfig() + kbs.logger.Info("KeyBinding: Started watching keybinding config file for changes") +} + +// GetKeyBindingConfig 获取完整快捷键配置 +func (kbs *KeyBindingService) GetKeyBindingConfig() (*models.KeyBindingConfig, error) { + kbs.mu.RLock() + defer kbs.mu.RUnlock() + + var config models.KeyBindingConfig + if err := kbs.viper.Unmarshal(&config); err != nil { + return nil, &KeyBindingError{Operation: "unmarshal_keybinding_config", Err: err} + } + + return &config, nil +} + +// GetAllKeyBindings 获取所有快捷键配置 +func (kbs *KeyBindingService) GetAllKeyBindings() ([]models.KeyBinding, error) { + kbs.mu.RLock() + defer kbs.mu.RUnlock() + + config, err := kbs.GetKeyBindingConfig() + if err != nil { + return nil, &KeyBindingError{Operation: "get_all_keybindings", Err: err} + } + + return config.KeyBindings, nil +} + +// GetKeyBindingsByCategory 根据分类获取快捷键 +func (kbs *KeyBindingService) GetKeyBindingsByCategory(category models.KeyBindingCategory) ([]models.KeyBinding, error) { + kbs.mu.RLock() + defer kbs.mu.RUnlock() + + allKeyBindings, err := kbs.GetAllKeyBindings() + if err != nil { + return nil, err + } + + var result []models.KeyBinding + for _, kb := range allKeyBindings { + if kb.Category == category { + result = append(result, kb) + } + } + + return result, nil +} + +// GetKeyBindingsByScope 根据作用域获取快捷键 +func (kbs *KeyBindingService) GetKeyBindingsByScope(scope models.KeyBindingScope) ([]models.KeyBinding, error) { + kbs.mu.RLock() + defer kbs.mu.RUnlock() + + allKeyBindings, err := kbs.GetAllKeyBindings() + if err != nil { + return nil, err + } + + var result []models.KeyBinding + for _, kb := range allKeyBindings { + if kb.Scope == scope && kb.Enabled { + result = append(result, kb) + } + } + + return result, nil +} + +// GetKeyBindingByID 根据ID获取快捷键 +func (kbs *KeyBindingService) GetKeyBindingByID(id string) (*models.KeyBinding, error) { + kbs.mu.RLock() + defer kbs.mu.RUnlock() + + allKeyBindings, err := kbs.GetAllKeyBindings() + if err != nil { + return nil, err + } + + for _, kb := range allKeyBindings { + if kb.ID == id { + return &kb, nil + } + } + + return nil, &KeyBindingError{ + Operation: "get_keybinding_by_id", + KeyID: id, + Err: errors.New("keybinding not found"), + } +} + +// GetKeyBindingByAction 根据动作获取快捷键 +func (kbs *KeyBindingService) GetKeyBindingByAction(action models.KeyBindingAction) (*models.KeyBinding, error) { + kbs.mu.RLock() + defer kbs.mu.RUnlock() + + allKeyBindings, err := kbs.GetAllKeyBindings() + if err != nil { + return nil, err + } + + for _, kb := range allKeyBindings { + if kb.Action == action && kb.Enabled { + return &kb, nil + } + } + + return nil, &KeyBindingError{ + Operation: "get_keybinding_by_action", + Err: fmt.Errorf("keybinding for action %s not found", action), + } +} + +// UpdateKeyBinding 更新快捷键 +func (kbs *KeyBindingService) UpdateKeyBinding(id string, newKey string) error { + kbs.mu.Lock() + defer kbs.mu.Unlock() + + // 验证新的快捷键格式 + if err := kbs.validateKeyFormat(newKey); err != nil { + return &KeyBindingError{ + Operation: "update_keybinding", + KeyID: id, + Err: fmt.Errorf("invalid key format: %v", err), + } + } + + // 检查快捷键冲突 + if err := kbs.checkKeyConflict(id, newKey); err != nil { + return &KeyBindingError{ + Operation: "update_keybinding", + KeyID: id, + Err: fmt.Errorf("key conflict: %v", err), + } + } + + // 获取当前配置 + config, err := kbs.GetKeyBindingConfig() + if err != nil { + return &KeyBindingError{Operation: "update_keybinding", KeyID: id, Err: err} + } + + // 查找并更新快捷键 + found := false + for i, kb := range config.KeyBindings { + if kb.ID == id { + config.KeyBindings[i].Key = newKey + config.KeyBindings[i].IsDefault = false // 标记为非默认 + found = true + break + } + } + + if !found { + return &KeyBindingError{ + Operation: "update_keybinding", + KeyID: id, + Err: errors.New("keybinding not found"), + } + } + + // 更新时间戳 + config.Metadata.LastUpdated = time.Now().Format(time.RFC3339) + + // 保存配置 + if err := kbs.saveConfig(config); err != nil { + return &KeyBindingError{Operation: "update_keybinding", KeyID: id, Err: err} + } + + kbs.logger.Info("KeyBinding: Updated keybinding", "id", id, "newKey", newKey) + return nil +} + +// EnableKeyBinding 启用快捷键 +func (kbs *KeyBindingService) EnableKeyBinding(id string) error { + return kbs.setKeyBindingEnabled(id, true) +} + +// DisableKeyBinding 禁用快捷键 +func (kbs *KeyBindingService) DisableKeyBinding(id string) error { + return kbs.setKeyBindingEnabled(id, false) +} + +// setKeyBindingEnabled 设置快捷键启用状态 +func (kbs *KeyBindingService) setKeyBindingEnabled(id string, enabled bool) error { + kbs.mu.Lock() + defer kbs.mu.Unlock() + + // 获取当前配置 + config, err := kbs.GetKeyBindingConfig() + if err != nil { + return &KeyBindingError{Operation: "set_keybinding_enabled", KeyID: id, Err: err} + } + + // 查找并更新快捷键 + found := false + for i, kb := range config.KeyBindings { + if kb.ID == id { + config.KeyBindings[i].Enabled = enabled + found = true + break + } + } + + if !found { + return &KeyBindingError{ + Operation: "set_keybinding_enabled", + KeyID: id, + Err: errors.New("keybinding not found"), + } + } + + // 更新时间戳 + config.Metadata.LastUpdated = time.Now().Format(time.RFC3339) + + // 保存配置 + if err := kbs.saveConfig(config); err != nil { + return &KeyBindingError{Operation: "set_keybinding_enabled", KeyID: id, Err: err} + } + + action := "enabled" + if !enabled { + action = "disabled" + } + kbs.logger.Info("KeyBinding: "+action+" keybinding", "id", id) + return nil +} + +// ResetKeyBinding 重置快捷键到默认值 +func (kbs *KeyBindingService) ResetKeyBinding(id string) error { + kbs.mu.Lock() + defer kbs.mu.Unlock() + + // 获取默认配置 + defaultKeyBindings := models.NewDefaultKeyBindings() + var defaultKeyBinding *models.KeyBinding + for _, kb := range defaultKeyBindings { + if kb.ID == id { + defaultKeyBinding = &kb + break + } + } + + if defaultKeyBinding == nil { + return &KeyBindingError{ + Operation: "reset_keybinding", + KeyID: id, + Err: errors.New("default keybinding not found"), + } + } + + // 获取当前配置 + config, err := kbs.GetKeyBindingConfig() + if err != nil { + return &KeyBindingError{Operation: "reset_keybinding", KeyID: id, Err: err} + } + + // 查找并重置快捷键 + found := false + for i, kb := range config.KeyBindings { + if kb.ID == id { + config.KeyBindings[i].Key = defaultKeyBinding.Key + config.KeyBindings[i].Enabled = defaultKeyBinding.Enabled + config.KeyBindings[i].IsDefault = true + found = true + break + } + } + + if !found { + return &KeyBindingError{ + Operation: "reset_keybinding", + KeyID: id, + Err: errors.New("keybinding not found"), + } + } + + // 更新时间戳 + config.Metadata.LastUpdated = time.Now().Format(time.RFC3339) + + // 保存配置 + if err := kbs.saveConfig(config); err != nil { + return &KeyBindingError{Operation: "reset_keybinding", KeyID: id, Err: err} + } + + kbs.logger.Info("KeyBinding: Reset keybinding to default", "id", id, "key", defaultKeyBinding.Key) + return nil +} + +// ResetAllKeyBindings 重置所有快捷键到默认值 +func (kbs *KeyBindingService) ResetAllKeyBindings() error { + kbs.mu.Lock() + defer kbs.mu.Unlock() + + // 获取默认配置 + defaultConfig := models.NewDefaultKeyBindingConfig() + + // 保存配置 + if err := kbs.saveConfig(defaultConfig); err != nil { + return &KeyBindingError{Operation: "reset_all_keybindings", Err: err} + } + + kbs.logger.Info("KeyBinding: Reset all keybindings to default") + return nil +} + +// saveConfig 保存配置到文件 +func (kbs *KeyBindingService) saveConfig(config *models.KeyBindingConfig) error { + // 设置快捷键列表到viper + kbs.viper.Set("keyBindings", config.KeyBindings) + kbs.viper.Set("metadata.lastUpdated", config.Metadata.LastUpdated) + + // 写入配置文件 + if err := kbs.viper.WriteConfig(); err != nil { + return fmt.Errorf("failed to write keybinding config: %v", err) + } + + return nil +} + +// validateKeyFormat 验证快捷键格式 +func (kbs *KeyBindingService) validateKeyFormat(key string) error { + if key == "" { + return errors.New("key cannot be empty") + } + + // 基本格式验证 + // 支持的修饰符: Mod, Ctrl, Shift, Alt, Win + // 支持的组合: Mod-f, Ctrl-Shift-p, Alt-ArrowUp 等 + validModifiers := []string{"Mod", "Ctrl", "Shift", "Alt", "Win"} + parts := strings.Split(key, "-") + + if len(parts) == 0 { + return errors.New("invalid key format") + } + + // 检查修饰符 + for i := 0; i < len(parts)-1; i++ { + modifier := parts[i] + valid := false + for _, validMod := range validModifiers { + if modifier == validMod { + valid = true + break + } + } + if !valid { + return fmt.Errorf("invalid modifier: %s", modifier) + } + } + + // 最后一部分应该是主键 + mainKey := parts[len(parts)-1] + if mainKey == "" { + return errors.New("main key cannot be empty") + } + + return nil +} + +// checkKeyConflict 检查快捷键冲突 +func (kbs *KeyBindingService) checkKeyConflict(excludeID, key string) error { + allKeyBindings, err := kbs.GetAllKeyBindings() + if err != nil { + return err + } + + for _, kb := range allKeyBindings { + if kb.ID != excludeID && kb.Key == key && kb.Enabled { + return fmt.Errorf("key %s is already used by %s", key, kb.ID) + } + } + + return nil +} + +// GetKeyBindingCategories 获取所有快捷键分类 +func (kbs *KeyBindingService) GetKeyBindingCategories() []models.KeyBindingCategory { + return []models.KeyBindingCategory{ + models.CategorySearch, + models.CategoryEdit, + models.CategoryCodeBlock, + models.CategoryNavigation, + models.CategoryView, + models.CategoryFile, + models.CategoryApp, + } +} + +// GetKeyBindingScopes 获取所有快捷键作用域 +func (kbs *KeyBindingService) GetKeyBindingScopes() []models.KeyBindingScope { + return []models.KeyBindingScope{ + models.ScopeGlobal, + models.ScopeEditor, + models.ScopeSearch, + } +} + +// ExportKeyBindings 导出快捷键配置 +func (kbs *KeyBindingService) ExportKeyBindings() ([]models.KeyBinding, error) { + kbs.mu.RLock() + defer kbs.mu.RUnlock() + + return kbs.GetAllKeyBindings() +} + +// ImportKeyBindings 导入快捷键配置 +func (kbs *KeyBindingService) ImportKeyBindings(keyBindings []models.KeyBinding) error { + kbs.mu.Lock() + defer kbs.mu.Unlock() + + // 验证导入的快捷键 + for _, kb := range keyBindings { + if err := kbs.validateKeyFormat(kb.Key); err != nil { + return &KeyBindingError{ + Operation: "import_keybindings", + KeyID: kb.ID, + Err: fmt.Errorf("invalid key format for %s: %v", kb.ID, err), + } + } + } + + // 检查重复的快捷键 + keyMap := make(map[string]string) + for _, kb := range keyBindings { + if kb.Enabled { + if existingID, exists := keyMap[kb.Key]; exists { + return &KeyBindingError{ + Operation: "import_keybindings", + Err: fmt.Errorf("duplicate key %s found in %s and %s", kb.Key, existingID, kb.ID), + } + } + keyMap[kb.Key] = kb.ID + } + } + + // 创建新的配置 + config := &models.KeyBindingConfig{ + KeyBindings: keyBindings, + Metadata: models.KeyBindingMetadata{ + LastUpdated: time.Now().Format(time.RFC3339), + }, + } + + // 保存配置 + if err := kbs.saveConfig(config); err != nil { + return &KeyBindingError{Operation: "import_keybindings", Err: err} + } + + kbs.logger.Info("KeyBinding: Imported keybindings", "count", len(keyBindings)) + return nil +} diff --git a/internal/services/service_manager.go b/internal/services/service_manager.go index efeccaa..d070239 100644 --- a/internal/services/service_manager.go +++ b/internal/services/service_manager.go @@ -9,14 +9,15 @@ import ( // ServiceManager 服务管理器,负责协调各个服务 type ServiceManager struct { - configService *ConfigService - documentService *DocumentService - migrationService *MigrationService - systemService *SystemService - hotkeyService *HotkeyService - dialogService *DialogService - trayService *TrayService - logger *log.LoggerService + configService *ConfigService + documentService *DocumentService + migrationService *MigrationService + systemService *SystemService + hotkeyService *HotkeyService + dialogService *DialogService + trayService *TrayService + keyBindingService *KeyBindingService + logger *log.LoggerService } // NewServiceManager 创建新的服务管理器实例 @@ -45,6 +46,9 @@ func NewServiceManager() *ServiceManager { // 初始化托盘服务 trayService := NewTrayService(logger, configService) + // 初始化快捷键服务 + keyBindingService := NewKeyBindingService(logger) + // 使用新的配置通知系统设置热键配置变更监听 err := configService.SetHotkeyChangeCallback(func(enable bool, hotkey *models.HotkeyCombo) error { return hotkeyService.UpdateHotkey(enable, hotkey) @@ -71,14 +75,15 @@ func NewServiceManager() *ServiceManager { } return &ServiceManager{ - configService: configService, - documentService: documentService, - migrationService: migrationService, - systemService: systemService, - hotkeyService: hotkeyService, - dialogService: dialogService, - trayService: trayService, - logger: logger, + configService: configService, + documentService: documentService, + migrationService: migrationService, + systemService: systemService, + hotkeyService: hotkeyService, + dialogService: dialogService, + trayService: trayService, + keyBindingService: keyBindingService, + logger: logger, } } @@ -92,6 +97,7 @@ func (sm *ServiceManager) GetServices() []application.Service { application.NewService(sm.hotkeyService), application.NewService(sm.dialogService), application.NewService(sm.trayService), + application.NewService(sm.keyBindingService), } } @@ -119,3 +125,8 @@ func (sm *ServiceManager) GetConfigService() *ConfigService { func (sm *ServiceManager) GetTrayService() *TrayService { return sm.trayService } + +// GetKeyBindingService 获取快捷键服务实例 +func (sm *ServiceManager) GetKeyBindingService() *KeyBindingService { + return sm.keyBindingService +}