Added key binding service

This commit is contained in:
2025-06-20 13:37:48 +08:00
parent 13072a00a1
commit 85544ba1e4
18 changed files with 1968 additions and 349 deletions

View File

@@ -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<AppConfig>);
}
@@ -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<ConfigMetadata> = {}) {
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<KeyBindingsConfig> = {}) {
/**
* 快捷键动作
*/
"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<KeyBinding> = {}) {
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<KeyBindingsConfig>);
return new KeyBinding($$parsedSource as Partial<KeyBinding>);
}
}
/**
* 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<KeyBindingConfig> = {}) {
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<KeyBindingConfig>);
}
}
/**
* KeyBindingMetadata 快捷键配置元数据
*/
export class KeyBindingMetadata {
/**
* 最后更新时间
*/
"lastUpdated": string;
/** Creates a new KeyBindingMetadata instance. */
constructor($$source: Partial<KeyBindingMetadata> = {}) {
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<KeyBindingMetadata>);
}
}
/**
* 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;

View File

@@ -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

View File

@@ -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<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(1594003006, id) as any;
return $resultPromise;
}
/**
* EnableKeyBinding 启用快捷键
*/
export function EnableKeyBinding(id: string): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(1462644129, id) as any;
return $resultPromise;
}
/**
* ExportKeyBindings 导出快捷键配置
*/
export function ExportKeyBindings(): Promise<models$0.KeyBinding[]> & { 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<models$0.KeyBinding[]> & { 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<models$0.KeyBinding | null> & { 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<models$0.KeyBinding | null> & { 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<models$0.KeyBindingCategory[]> & { 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<models$0.KeyBindingConfig | null> & { 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<models$0.KeyBindingScope[]> & { 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<models$0.KeyBinding[]> & { 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<models$0.KeyBinding[]> & { 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<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(642201520, keyBindings) as any;
return $resultPromise;
}
/**
* ResetAllKeyBindings 重置所有快捷键到默认值
*/
export function ResetAllKeyBindings(): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(2771372645) as any;
return $resultPromise;
}
/**
* ResetKeyBinding 重置快捷键到默认值
*/
export function ResetKeyBinding(id: string): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(3466323405, id) as any;
return $resultPromise;
}
/**
* UpdateKeyBinding 更新快捷键
*/
export function UpdateKeyBinding(id: string, newKey: string): Promise<void> & { 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);

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -20,7 +20,7 @@ defineProps<{
<style scoped lang="scss">
.setting-item {
display: flex;
padding: 14px 0;
padding: 12px 0;
border-bottom: 1px solid var(--settings-border);
transition: background-color 0.15s ease;
@@ -35,24 +35,24 @@ defineProps<{
.setting-info {
flex: 1;
padding-right: 20px;
padding-right: 16px;
.setting-title {
font-size: 14px;
font-size: 13px;
font-weight: 500;
margin-bottom: 6px;
margin-bottom: 4px;
color: var(--settings-text);
}
.setting-description {
font-size: 12px;
font-size: 11px;
color: var(--settings-text-secondary);
line-height: 1.5;
line-height: 1.4;
}
}
.setting-control {
width: 200px;
width: 180px;
display: flex;
align-items: center;
justify-content: flex-end;

View File

@@ -15,7 +15,7 @@ defineProps<{
<style scoped lang="scss">
.setting-section {
margin-bottom: 30px;
margin-bottom: 24px;
background-color: var(--settings-card-bg);
border-radius: 6px;
overflow: hidden;
@@ -23,17 +23,17 @@ defineProps<{
border: 1px solid var(--settings-border);
.section-title {
font-size: 14px;
font-size: 13px;
font-weight: 600;
margin: 0;
padding: 12px 16px;
padding: 10px 14px;
background-color: var(--settings-hover);
color: var(--settings-text);
border-bottom: 1px solid var(--settings-border);
}
.section-content {
padding: 8px 16px;
padding: 6px 14px;
}
}
</style>

View File

@@ -1,29 +1,45 @@
<script setup lang="ts">
import { nextTick } from 'vue';
const props = defineProps<{
modelValue: boolean;
disabled?: boolean;
}>();
const emit = defineEmits<{
'update:modelValue': [value: boolean]
}>();
const toggle = () => {
emit('update:modelValue', !props.modelValue);
const toggle = async () => {
if (props.disabled) return;
const newValue = !props.modelValue;
emit('update:modelValue', newValue);
// 确保DOM更新
await nextTick();
};
</script>
<template>
<div class="toggle-switch" :class="{ active: modelValue }" @click="toggle">
<div
class="toggle-switch"
:class="{
active: modelValue,
disabled: disabled
}"
@click="toggle"
>
<div class="toggle-handle"></div>
</div>
</template>
<style scoped lang="scss">
.toggle-switch {
width: 40px;
height: 20px;
width: 36px;
height: 18px;
background-color: var(--settings-input-border);
border-radius: 10px;
border-radius: 9px;
position: relative;
cursor: pointer;
transition: background-color 0.2s;
@@ -33,17 +49,30 @@ const toggle = () => {
background-color: #4a9eff;
.toggle-handle {
transform: translateX(20px);
transform: translateX(18px);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
}
}
&.disabled {
opacity: 0.5;
cursor: not-allowed;
&:hover {
background-color: var(--settings-input-border);
}
&.active:hover {
background-color: #4a9eff;
}
}
.toggle-handle {
position: absolute;
top: 2px;
left: 2px;
width: 16px;
height: 16px;
width: 14px;
height: 14px;
background-color: var(--settings-text);
border-radius: 50%;
transition: all 0.2s ease;

View File

@@ -79,19 +79,19 @@ const updateSystemTheme = async (event: Event) => {
}
.select-input {
min-width: 150px;
padding: 8px 12px;
min-width: 140px;
padding: 6px 10px;
border: 1px solid var(--settings-input-border);
border-radius: 4px;
background-color: var(--settings-input-bg);
color: var(--settings-text);
font-size: 13px;
font-size: 12px;
appearance: none;
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23666666' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: right 8px center;
background-size: 16px;
padding-right: 30px;
background-position: right 6px center;
background-size: 14px;
padding-right: 26px;
transition: border-color 0.2s ease;
&:focus {

View File

@@ -123,7 +123,12 @@ const handleToggleTabType = async () => {
// 创建双向绑定的计算属性
const enableTabIndent = computed({
get: () => configStore.config.editing.enableTabIndent,
set: (value: boolean) => configStore.setEnableTabIndent(value)
set: async (value: boolean) => {
await safeCall(
() => configStore.setEnableTabIndent(value),
'config.tabIndentToggleFailed'
);
}
});
// 保存选项处理器
@@ -138,31 +143,7 @@ const handleAutoSaveDelayChange = async (event: Event) => {
}
};
// 动态字体预览文本
const fontPreviewText = computed(() => {
const currentFont = configStore.config.editing.fontFamily;
// 根据字体类型返回不同的预览文本
if (currentFont.includes('HarmonyOS')) {
return '鸿蒙字体测试';
} else if (currentFont.includes('Microsoft YaHei')) {
return '微软雅黑测试';
} else if (currentFont.includes('PingFang')) {
return '苹方字体测试';
} else if (currentFont.includes('JetBrains')) {
return 'JetBrains Mono';
} else if (currentFont.includes('Fira Code')) {
return 'Fira Code Test';
} else if (currentFont.includes('Source Code')) {
return 'Source Code Pro';
} else if (currentFont.includes('Cascadia')) {
return 'Cascadia Code';
} else if (currentFont.includes('SF Mono') || currentFont.includes('Monaco')) {
return 'System Monospace';
} else {
return 'Font Preview';
}
});
</script>
<template>
@@ -228,20 +209,7 @@ const fontPreviewText = computed(() => {
</div>
</SettingItem>
<div class="font-preview" :style="{
fontSize: `${configStore.config.editing.fontSize}px`,
fontFamily: configStore.config.editing.fontFamily,
fontWeight: configStore.config.editing.fontWeight,
lineHeight: configStore.config.editing.lineHeight
}">
<div class="preview-label">字体预览</div>
<div class="preview-text">
<span>function example() {</span>
<span class="indent">console.log("Hello, 世界!");</span>
<span class="indent">const message = "{{ fontPreviewText }}";</span>
<span>}</span>
</div>
</div>
</SettingSection>
<SettingSection :title="t('settings.tabSettings')">
@@ -314,7 +282,7 @@ const fontPreviewText = computed(() => {
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
font-size: 12px;
transition: all 0.2s ease;
&:hover:not(:disabled) {
@@ -335,7 +303,7 @@ const fontPreviewText = computed(() => {
span {
min-width: 50px;
text-align: center;
font-size: 14px;
font-size: 12px;
color: var(--settings-text);
background-color: var(--settings-input-bg);
border: 1px solid var(--settings-input-border);
@@ -344,65 +312,7 @@ const fontPreviewText = computed(() => {
}
}
.font-size-preview {
margin: 15px 0 5px 20px;
padding: 15px;
background-color: var(--settings-card-bg);
border: 1px solid var(--settings-border);
border-radius: 4px;
font-family: 'Consolas', 'Courier New', monospace;
.preview-label {
font-size: 12px;
color: var(--text-muted);
margin-bottom: 8px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
.preview-text {
display: flex;
flex-direction: column;
span {
line-height: 1.4;
color: var(--settings-text-secondary);
}
.indent {
padding-left: 20px;
color: #4a9eff;
}
}
}
.font-preview {
margin: 15px 0 5px 20px;
padding: 15px;
background-color: var(--settings-card-bg);
border: 1px solid var(--settings-border);
border-radius: 4px;
.preview-label {
font-size: 12px;
color: var(--text-muted);
margin-bottom: 8px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
.preview-text {
display: flex;
flex-direction: column;
span {
color: var(--settings-text-secondary);
}
.indent {
padding-left: 20px;
color: #4a9eff;
}
}
}
.font-family-select,
.font-weight-select {
@@ -412,7 +322,7 @@ const fontPreviewText = computed(() => {
border-radius: 4px;
background-color: var(--settings-input-bg);
color: var(--settings-text);
font-size: 13px;
font-size: 12px;
cursor: pointer;
&:focus {
@@ -439,7 +349,7 @@ const fontPreviewText = computed(() => {
border-radius: 4px;
color: var(--settings-text);
cursor: pointer;
font-size: 13px;
font-size: 12px;
text-align: center;
transition: all 0.2s ease;
@@ -460,7 +370,7 @@ const fontPreviewText = computed(() => {
border-radius: 4px;
background-color: var(--settings-input-bg);
color: var(--settings-text);
font-size: 13px;
font-size: 12px;
&:focus {
outline: none;

View File

@@ -472,7 +472,7 @@ onUnmounted(() => {
border: 1px solid var(--settings-input-border);
border-radius: 4px;
color: var(--settings-text-secondary);
font-size: 13px;
font-size: 12px;
transition: all 0.2s ease;
&:hover {
@@ -493,10 +493,10 @@ onUnmounted(() => {
padding: 8px 12px;
background-color: var(--settings-input-bg);
border: 1px solid var(--settings-input-border);
border-radius: 4px;
color: var(--settings-text);
font-size: 13px;
appearance: none;
border-radius: 4px;
color: var(--settings-text);
font-size: 12px;
appearance: none;
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23999999' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: right 8px center;
@@ -530,17 +530,17 @@ onUnmounted(() => {
border-radius: 4px;
margin-top: 8px;
.preview-label {
font-size: 12px;
color: var(--settings-text-secondary);
}
.preview-hotkey {
font-size: 13px;
color: var(--settings-text);
font-weight: 500;
font-family: 'Consolas', 'Courier New', monospace;
}
.preview-label {
font-size: 11px;
color: var(--settings-text-secondary);
}
.preview-hotkey {
font-size: 12px;
color: var(--settings-text);
font-weight: 500;
font-family: 'Consolas', 'Courier New', monospace;
}
}
}
@@ -558,7 +558,7 @@ onUnmounted(() => {
margin-bottom: 12px;
.setting-title {
font-size: 14px;
font-size: 13px;
font-weight: 500;
color: var(--settings-text);
}
@@ -583,7 +583,7 @@ onUnmounted(() => {
border: 1px solid var(--settings-input-border);
border-radius: 4px;
color: var(--settings-text);
font-size: 13px;
font-size: 12px;
line-height: 1.2;
transition: all 0.2s ease;
cursor: pointer;

View File

@@ -132,7 +132,7 @@ const handleKeyDown = (event: KeyboardEvent, binding: KeyBinding) => {
padding: 0 0 10px 0;
border-bottom: 1px solid var(--settings-border);
color: var(--text-muted);
font-size: 13px;
font-size: 12px;
font-weight: 500;
}
@@ -159,7 +159,7 @@ const handleKeyDown = (event: KeyboardEvent, binding: KeyBinding) => {
.command-col {
flex: 1;
padding-right: 10px;
font-size: 14px;
font-size: 13px;
color: var(--settings-text);
}
@@ -176,9 +176,9 @@ const handleKeyDown = (event: KeyboardEvent, binding: KeyBinding) => {
.key-badge {
background-color: var(--settings-input-bg);
padding: 3px 8px;
padding: 2px 6px;
border-radius: 3px;
font-size: 12px;
font-size: 11px;
border: 1px solid var(--settings-input-border);
color: var(--settings-text);
}
@@ -195,7 +195,7 @@ const handleKeyDown = (event: KeyboardEvent, binding: KeyBinding) => {
border-radius: 4px;
color: var(--settings-text);
cursor: pointer;
font-size: 13px;
font-size: 12px;
transition: all 0.2s ease;
&:hover {
@@ -217,6 +217,6 @@ const handleKeyDown = (event: KeyboardEvent, binding: KeyBinding) => {
color: var(--text-muted);
text-align: center;
font-style: italic;
font-size: 14px;
font-size: 13px;
}
</style>

View File

@@ -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;

View File

@@ -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),
},
}
}

View File

@@ -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,
},
}
}

View File

@@ -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 设置热键配置变更回调

View File

@@ -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
}

View File

@@ -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
}