🐛 Fixed the issue of text highlighting expansion

This commit is contained in:
2025-06-25 22:53:00 +08:00
parent 69957a16cf
commit a9b967aba4
10 changed files with 205 additions and 349 deletions

View File

@@ -433,11 +433,6 @@ export enum ExtensionID {
*/ */
ExtensionSearch = "search", ExtensionSearch = "search",
/**
* 代码块
*/
ExtensionCodeBlock = "codeBlock",
/** /**
* 核心扩展 * 核心扩展
* 编辑器核心功能 * 编辑器核心功能
@@ -918,6 +913,12 @@ export enum KeyBindingCommand {
* 重做选择 * 重做选择
*/ */
HistoryRedoSelectionCommand = "historyRedoSelection", HistoryRedoSelectionCommand = "historyRedoSelection",
/**
* 文本高亮扩展相关
* 切换文本高亮
*/
TextHighlightToggleCommand = "textHighlightToggle",
}; };
/** /**

View File

@@ -89,6 +89,7 @@ export default {
deleteCharForward: 'Delete character forward', deleteCharForward: 'Delete character forward',
deleteGroupBackward: 'Delete group backward', deleteGroupBackward: 'Delete group backward',
deleteGroupForward: 'Delete group forward', deleteGroupForward: 'Delete group forward',
textHighlightToggle: 'Toggle text highlight',
} }
}, },
settings: { settings: {
@@ -182,17 +183,16 @@ export default {
name: 'Search', name: 'Search',
description: 'Text search and replace functionality' description: 'Text search and replace functionality'
}, },
codeBlock: {
name: 'Code Block',
description: 'Code block syntax highlighting and formatting'
},
fold: { fold: {
name: 'Code Folding', name: 'Code Folding',
description: 'Collapse and expand code sections for better readability' description: 'Collapse and expand code sections for better readability'
}, },
textHighlight: { textHighlight: {
name: 'Text Highlight', name: 'Text Highlight',
description: 'Highlight text selections and search matches' description: 'Highlight selected text content (Ctrl+Shift+H to toggle highlight)',
backgroundColor: 'Background Color',
opacity: 'Opacity'
} }
} }
}; };

View File

@@ -89,6 +89,7 @@ export default {
deleteCharForward: '向前删除字符', deleteCharForward: '向前删除字符',
deleteGroupBackward: '向后删除组', deleteGroupBackward: '向后删除组',
deleteGroupForward: '向前删除组', deleteGroupForward: '向前删除组',
textHighlightToggle: '切换文本高亮',
} }
}, },
settings: { settings: {
@@ -182,17 +183,16 @@ export default {
name: '搜索功能', name: '搜索功能',
description: '文本搜索和替换功能' description: '文本搜索和替换功能'
}, },
codeBlock: {
name: '代码块',
description: '代码块语法高亮和格式化'
},
fold: { fold: {
name: '代码折叠', name: '代码折叠',
description: '折叠和展开代码段以提高代码可读性' description: '折叠和展开代码段以提高代码可读性'
}, },
textHighlight: { textHighlight: {
name: '文本高亮', name: '文本高亮',
description: '高亮显示文本选择和搜索匹配项' description: '高亮选中的文本内容 (Ctrl+Shift+H 切换高亮)',
backgroundColor: '背景颜色',
opacity: '透明度'
} }
} }
}; };

View File

@@ -18,6 +18,7 @@ import {createAutoSavePlugin, createSaveShortcutPlugin} from '@/views/editor/bas
import {createDynamicKeymapExtension, updateKeymapExtension} from '@/views/editor/keymap'; import {createDynamicKeymapExtension, updateKeymapExtension} from '@/views/editor/keymap';
import {createDynamicExtensions, getExtensionManager, setExtensionManagerView} from '@/views/editor/manager'; import {createDynamicExtensions, getExtensionManager, setExtensionManagerView} from '@/views/editor/manager';
import {useExtensionStore} from './extensionStore'; import {useExtensionStore} from './extensionStore';
import createCodeBlockExtension from "@/views/editor/extensions/codeblock";
export interface DocumentStats { export interface DocumentStats {
lines: number; lines: number;
@@ -142,6 +143,11 @@ export const useEditorStore = defineStore('editor', () => {
} }
} }
}); });
// 代码块功能
const codeBlockExtension = createCodeBlockExtension({
showBackground: true,
enableAutoDetection: true
});
// 创建动态快捷键扩展 // 创建动态快捷键扩展
const keymapExtension = await createDynamicKeymapExtension(); const keymapExtension = await createDynamicKeymapExtension();
@@ -159,6 +165,7 @@ export const useEditorStore = defineStore('editor', () => {
statsExtension, statsExtension,
saveShortcutExtension, saveShortcutExtension,
autoSaveExtension, autoSaveExtension,
codeBlockExtension,
...dynamicExtensions ...dynamicExtensions
]; ];

View File

@@ -1,120 +1,108 @@
import { EditorState, StateEffect, StateField, Transaction, Range } from "@codemirror/state"; import { EditorState, StateEffect, StateField, Facet } from "@codemirror/state";
import { Decoration, DecorationSet, EditorView, ViewPlugin, ViewUpdate } from "@codemirror/view"; import { Decoration, DecorationSet, EditorView } from "@codemirror/view";
import { keymap } from "@codemirror/view";
// 全局高亮存储 - 以文档ID为键高亮范围数组为值 // 高亮配置接口
interface HighlightInfo { export interface TextHighlightConfig {
from: number; backgroundColor?: string;
to: number; opacity?: number;
} }
class GlobalHighlightStore { // 默认配置
private static instance: GlobalHighlightStore; const DEFAULT_CONFIG: Required<TextHighlightConfig> = {
private highlightMap: Map<string, HighlightInfo[]> = new Map(); backgroundColor: '#FFD700', // 金黄色
opacity: 0.3
private constructor() {} };
public static getInstance(): GlobalHighlightStore {
if (!GlobalHighlightStore.instance) {
GlobalHighlightStore.instance = new GlobalHighlightStore();
}
return GlobalHighlightStore.instance;
}
// 保存文档的高亮
saveHighlights(documentId: string, highlights: HighlightInfo[]): void {
this.highlightMap.set(documentId, [...highlights]);
}
// 获取文档的高亮
getHighlights(documentId: string): HighlightInfo[] {
return this.highlightMap.get(documentId) || [];
}
// 添加高亮
addHighlight(documentId: string, highlight: HighlightInfo): void {
const highlights = this.getHighlights(documentId);
highlights.push(highlight);
this.saveHighlights(documentId, highlights);
}
// 移除高亮
removeHighlights(documentId: string, from: number, to: number): void {
const highlights = this.getHighlights(documentId);
const filtered = highlights.filter(h => !(h.from < to && h.to > from));
this.saveHighlights(documentId, filtered);
}
// 清除文档的所有高亮
clearHighlights(documentId: string): void {
this.highlightMap.delete(documentId);
}
}
// 获取全局高亮存储实例
const highlightStore = GlobalHighlightStore.getInstance();
// 定义添加和移除高亮的状态效果 // 定义添加和移除高亮的状态效果
const addHighlight = StateEffect.define<{from: number, to: number, documentId: string}>({ const addHighlight = StateEffect.define<{from: number, to: number}>({
map: ({from, to, documentId}, change) => ({ map: ({from, to}, change) => ({
from: change.mapPos(from), from: change.mapPos(from),
to: change.mapPos(to), to: change.mapPos(to)
documentId
}) })
}); });
const removeHighlight = StateEffect.define<{from: number, to: number, documentId: string}>({ const removeHighlight = StateEffect.define<{from: number, to: number}>({
map: ({from, to, documentId}, change) => ({ map: ({from, to}, change) => ({
from: change.mapPos(from), from: change.mapPos(from),
to: change.mapPos(to), to: change.mapPos(to)
documentId
}) })
}); });
// 初始化高亮效果 - 用于页面加载时恢复高亮
const initHighlights = StateEffect.define<{highlights: HighlightInfo[], documentId: string}>();
// 高亮样式
const highlightMark = Decoration.mark({ // 配置facet
attributes: {style: `background-color: rgba(255, 215, 0, 0.3)`} const highlightConfigFacet = Facet.define<TextHighlightConfig, Required<TextHighlightConfig>>({
combine: (configs) => {
let result = { ...DEFAULT_CONFIG };
for (const config of configs) {
if (config.backgroundColor !== undefined) {
result.backgroundColor = config.backgroundColor;
}
if (config.opacity !== undefined) {
result.opacity = config.opacity;
}
}
return result;
}
}); });
// 存储高亮范围的状态字段 // 创建高亮装饰
function createHighlightMark(config: Required<TextHighlightConfig>): Decoration {
const { backgroundColor, opacity } = config;
const rgbaColor = hexToRgba(backgroundColor, opacity);
return Decoration.mark({
attributes: {
style: `background-color: ${rgbaColor}; border-radius: 2px;`
}
});
}
// 将十六进制颜色转换为RGBA
function hexToRgba(hex: string, opacity: number): string {
// 移除 # 符号
hex = hex.replace('#', '');
// 处理短格式 (如 #FFF -> #FFFFFF)
if (hex.length === 3) {
hex = hex.split('').map(char => char + char).join('');
}
const r = parseInt(hex.substr(0, 2), 16);
const g = parseInt(hex.substr(2, 2), 16);
const b = parseInt(hex.substr(4, 2), 16);
return `rgba(${r}, ${g}, ${b}, ${opacity})`;
}
// 存储高亮范围的状态字段 - 支持撤销
const highlightState = StateField.define<DecorationSet>({ const highlightState = StateField.define<DecorationSet>({
create() { create() {
return Decoration.none; return Decoration.none;
}, },
update(decorations, tr) { update(decorations, tr) {
// 映射现有装饰以适应文档变化 // 映射现有装饰以适应文档变化
decorations = decorations.map(tr.changes); decorations = decorations.map(tr.changes);
// 处理添加和移除高亮的效果 // 处理效果
for (const effect of tr.effects) { for (const effect of tr.effects) {
if (effect.is(addHighlight)) { if (effect.is(addHighlight)) {
const { from, to, documentId } = effect.value; const { from, to } = effect.value;
const config = tr.state.facet(highlightConfigFacet);
const highlightMark = createHighlightMark(config);
decorations = decorations.update({ decorations = decorations.update({
add: [highlightMark.range(from, to)] add: [highlightMark.range(from, to)]
}); });
// 同步到全局存储
highlightStore.addHighlight(documentId, { from, to });
} }
else if (effect.is(removeHighlight)) { else if (effect.is(removeHighlight)) {
const { from, to, documentId } = effect.value; const { from, to } = effect.value;
decorations = decorations.update({ decorations = decorations.update({
filter: (rangeFrom, rangeTo) => { filter: (rangeFrom, rangeTo) => {
// 移除与指定范围重叠的装饰 // 移除与指定范围重叠的装饰
return !(rangeFrom < to && rangeTo > from); return !(rangeFrom < to && rangeTo > from);
} }
}); });
// 同步到全局存储
highlightStore.removeHighlights(documentId, from, to);
}
else if (effect.is(initHighlights)) {
const { highlights } = effect.value;
const ranges = highlights.map(h => highlightMark.range(h.from, h.to));
if (ranges.length > 0) {
decorations = decorations.update({ add: ranges });
}
} }
} }
@@ -123,51 +111,32 @@ const highlightState = StateField.define<DecorationSet>({
provide: field => EditorView.decorations.from(field) provide: field => EditorView.decorations.from(field)
}); });
// 定义高亮范围接口
interface HighlightRange {
from: number;
to: number;
decoration: Decoration;
}
// 查找指定位置包含的高亮
function findHighlightsAt(state: EditorState, pos: number): HighlightRange[] {
const highlights: HighlightRange[] = [];
state.field(highlightState).between(pos, pos, (from, to, deco) => {
highlights.push({ from, to, decoration: deco });
});
return highlights;
}
// 查找与给定范围重叠的所有高亮 // 查找与给定范围重叠的所有高亮
function findHighlightsInRange(state: EditorState, from: number, to: number): HighlightRange[] { function findHighlightsInRange(state: EditorState, from: number, to: number): Array<{from: number, to: number}> {
const highlights: HighlightRange[] = []; const highlights: Array<{from: number, to: number}> = [];
state.field(highlightState).between(from, to, (rangeFrom, rangeTo, deco) => { state.field(highlightState).between(from, to, (rangeFrom, rangeTo) => {
// 只添加与指定范围有重叠的高亮
if (rangeFrom < to && rangeTo > from) { if (rangeFrom < to && rangeTo > from) {
highlights.push({ from: rangeFrom, to: rangeTo, decoration: deco }); highlights.push({ from: rangeFrom, to: rangeTo });
} }
}); });
return highlights; return highlights;
} }
// 收集当前所有高亮信息 // 查找指定位置包含的高亮
function collectAllHighlights(state: EditorState): HighlightInfo[] { function findHighlightsAt(state: EditorState, pos: number): Array<{from: number, to: number}> {
const highlights: HighlightInfo[] = []; const highlights: Array<{from: number, to: number}> = [];
state.field(highlightState).between(0, state.doc.length, (from, to) => { state.field(highlightState).between(pos, pos, (from, to) => {
highlights.push({ from, to }); highlights.push({ from, to });
}); });
return highlights; return highlights;
} }
// 添加高亮 // 添加高亮范围
function addHighlightRange(view: EditorView, from: number, to: number, documentId: string): boolean { function addHighlightRange(view: EditorView, from: number, to: number): boolean {
if (from === to) return false; // 不高亮空选择 if (from === to) return false; // 不高亮空选择
// 检查是否已经完全高亮 // 检查是否已经完全高亮
@@ -179,27 +148,27 @@ function addHighlightRange(view: EditorView, from: number, to: number, documentI
if (isFullyHighlighted) return false; if (isFullyHighlighted) return false;
view.dispatch({ view.dispatch({
effects: addHighlight.of({from, to, documentId}) effects: addHighlight.of({from, to})
}); });
return true; return true;
} }
// 移除高亮 // 移除高亮范围
function removeHighlightRange(view: EditorView, from: number, to: number, documentId: string): boolean { function removeHighlightRange(view: EditorView, from: number, to: number): boolean {
const highlights = findHighlightsInRange(view.state, from, to); const highlights = findHighlightsInRange(view.state, from, to);
if (highlights.length === 0) return false; if (highlights.length === 0) return false;
view.dispatch({ view.dispatch({
effects: removeHighlight.of({from, to, documentId}) effects: removeHighlight.of({from, to})
}); });
return true; return true;
} }
// 切换高亮状态 // 切换高亮状态
function toggleHighlight(view: EditorView, documentId: string): boolean { function toggleHighlight(view: EditorView): boolean {
const selection = view.state.selection.main; const selection = view.state.selection.main;
// 如果有选择文本 // 如果有选择文本
@@ -211,10 +180,10 @@ function toggleHighlight(view: EditorView, documentId: string): boolean {
if (highlights.length > 0) { if (highlights.length > 0) {
// 如果已有高亮,则移除 // 如果已有高亮,则移除
return removeHighlightRange(view, from, to, documentId); return removeHighlightRange(view, from, to);
} else { } else {
// 如果没有高亮,则添加 // 如果没有高亮,则添加
return addHighlightRange(view, from, to, documentId); return addHighlightRange(view, from, to);
} }
} }
// 如果是光标 // 如果是光标
@@ -225,135 +194,20 @@ function toggleHighlight(view: EditorView, documentId: string): boolean {
if (highlightsAtCursor.length > 0) { if (highlightsAtCursor.length > 0) {
// 移除光标位置的高亮 // 移除光标位置的高亮
const highlight = highlightsAtCursor[0]; const highlight = highlightsAtCursor[0];
return removeHighlightRange(view, highlight.from, highlight.to, documentId); return removeHighlightRange(view, highlight.from, highlight.to);
} }
} }
return false; return false;
} }
// 创建高亮快捷键需要文档ID // 导出文本高亮切换命令,供快捷键系统使用
function createHighlightKeymap(documentId: string) { export const textHighlightToggleCommand = toggleHighlight;
return keymap.of([
{key: "Mod-h", run: (view) => toggleHighlight(view, documentId)}
]);
}
// 高亮刷新管理器类 // 创建文本高亮扩展
class HighlightRefreshManager { export function createTextHighlighter(config: TextHighlightConfig = {}) {
private view: EditorView;
private refreshPending = false;
private initialSetupDone = false;
private rafId: number | null = null;
private documentId: string;
constructor(view: EditorView, documentId: string) {
this.view = view;
this.documentId = documentId;
}
/**
* 使用requestAnimationFrame安排视图更新
*/
scheduleRefresh(): void {
if (this.refreshPending) return;
this.refreshPending = true;
this.rafId = requestAnimationFrame(() => {
this.executeRefresh();
});
}
/**
* 执行视图更新
*/
private executeRefresh(): void {
this.refreshPending = false;
this.rafId = null;
if (!this.view.state) return;
try {
// 触发一个空的更新,确保视图刷新
this.view.dispatch({});
} catch (e) {
console.debug("highlight refresh error:", e);
}
}
/**
* 初始化高亮 - 应用保存的高亮
*/
initHighlights(): void {
const savedHighlights = highlightStore.getHighlights(this.documentId);
if (savedHighlights.length > 0) {
this.view.dispatch({
effects: initHighlights.of({
highlights: savedHighlights,
documentId: this.documentId
})
});
}
}
/**
* 执行初始化设置
*/
performInitialSetup(): void {
if (this.initialSetupDone) return;
Promise.resolve().then(() => {
this.initHighlights();
this.scheduleRefresh();
});
this.initialSetupDone = true;
}
/**
* 清理资源
*/
dispose(): void {
if (this.rafId !== null) {
cancelAnimationFrame(this.rafId);
}
}
}
// 创建高亮扩展
export function createTextHighlighter(documentId: string) {
// 视图插件
const highlightSetupPlugin = ViewPlugin.define((view) => {
// 创建刷新管理器实例
const refreshManager = new HighlightRefreshManager(view, documentId);
// 执行初始化设置
refreshManager.performInitialSetup();
return {
update(update: ViewUpdate) {
// 页面有内容变化时,保存最新的高亮状态
if (update.docChanged || update.transactions.some(tr =>
tr.effects.some(e => e.is(addHighlight) || e.is(removeHighlight))
)) {
// 延迟收集高亮信息,确保所有效果都已应用
setTimeout(() => {
const allHighlights = collectAllHighlights(view.state);
highlightStore.saveHighlights(documentId, allHighlights);
}, 0);
}
},
destroy() {
// 清理资源
refreshManager.dispose();
}
};
});
return [ return [
highlightState, highlightConfigFacet.of(config),
createHighlightKeymap(documentId), highlightState
highlightSetupPlugin
]; ];
} }

View File

@@ -26,6 +26,7 @@ import {deleteLineCommand} from '../extensions/codeblock/deleteLine'
import {moveLineDown, moveLineUp} from '../extensions/codeblock/moveLines' import {moveLineDown, moveLineUp} from '../extensions/codeblock/moveLines'
import {transposeChars} from '../extensions/codeblock' import {transposeChars} from '../extensions/codeblock'
import {copyCommand, cutCommand, pasteCommand} from '../extensions/codeblock/copyPaste' import {copyCommand, cutCommand, pasteCommand} from '../extensions/codeblock/copyPaste'
import {textHighlightToggleCommand} from '../extensions/textHighlight/textHighlightExtension'
import { import {
copyLineDown, copyLineDown,
copyLineUp, copyLineUp,
@@ -284,6 +285,12 @@ export const commandRegistry = {
handler: deleteGroupForward, handler: deleteGroupForward,
descriptionKey: 'keybindings.commands.deleteGroupForward' descriptionKey: 'keybindings.commands.deleteGroupForward'
}, },
// 文本高亮扩展命令
[KeyBindingCommand.TextHighlightToggleCommand]: {
handler: textHighlightToggleCommand,
descriptionKey: 'keybindings.commands.textHighlightToggle'
},
} as const } as const
/** /**

View File

@@ -10,7 +10,7 @@ import {color} from '../extensions/colorSelector'
import {hyperLink} from '../extensions/hyperlink' import {hyperLink} from '../extensions/hyperlink'
import {minimap} from '../extensions/minimap' import {minimap} from '../extensions/minimap'
import {vscodeSearch} from '../extensions/vscodeSearch' import {vscodeSearch} from '../extensions/vscodeSearch'
import {createCodeBlockExtension} from '../extensions/codeblock'
import {foldingOnIndent} from '../extensions/fold/foldExtension' import {foldingOnIndent} from '../extensions/fold/foldExtension'
/** /**
@@ -33,16 +33,21 @@ export const rainbowBracketsFactory: ExtensionFactory = {
*/ */
export const textHighlightFactory: ExtensionFactory = { export const textHighlightFactory: ExtensionFactory = {
create(config: any) { create(config: any) {
return createTextHighlighter('default') return createTextHighlighter({
backgroundColor: config.backgroundColor || '#FFD700',
opacity: config.opacity || 0.3
})
}, },
getDefaultConfig() { getDefaultConfig() {
return { return {
highlightClass: 'hl' backgroundColor: '#FFD700', // 金黄色
opacity: 0.3 // 透明度
} }
}, },
validateConfig(config: any) { validateConfig(config: any) {
return typeof config === 'object' && return typeof config === 'object' &&
(!config.highlightClass || typeof config.highlightClass === 'string') (!config.backgroundColor || typeof config.backgroundColor === 'string') &&
(!config.opacity || (typeof config.opacity === 'number' && config.opacity >= 0 && config.opacity <= 1))
} }
} }
@@ -120,29 +125,7 @@ export const searchFactory: ExtensionFactory = {
} }
} }
/**
* 代码块扩展工厂
*/
export const codeBlockFactory: ExtensionFactory = {
create(config: any) {
const options = {
showBackground: config.showBackground !== false,
enableAutoDetection: config.enableAutoDetection !== false
}
return createCodeBlockExtension(options)
},
getDefaultConfig() {
return {
showBackground: true,
enableAutoDetection: true
}
},
validateConfig(config: any) {
return typeof config === 'object' &&
(!config.showBackground || typeof config.showBackground === 'boolean') &&
(!config.enableAutoDetection || typeof config.enableAutoDetection === 'boolean')
}
}
export const foldFactory: ExtensionFactory = { export const foldFactory: ExtensionFactory = {
create(config: any) { create(config: any) {
@@ -193,11 +176,7 @@ const EXTENSION_CONFIGS = {
displayNameKey: 'extensions.search.name', displayNameKey: 'extensions.search.name',
descriptionKey: 'extensions.search.description' descriptionKey: 'extensions.search.description'
}, },
[ExtensionID.ExtensionCodeBlock]: {
factory: codeBlockFactory,
displayNameKey: 'extensions.codeBlock.name',
descriptionKey: 'extensions.codeBlock.description'
},
[ExtensionID.ExtensionFold]: { [ExtensionID.ExtensionFold]: {
factory: foldFactory, factory: foldFactory,
displayNameKey: 'extensions.fold.name', displayNameKey: 'extensions.fold.name',

View File

@@ -7,11 +7,11 @@ import {ExtensionService} from '@/../bindings/voidraft/internal/services'
import {ExtensionID} from '@/../bindings/voidraft/internal/models/models' import {ExtensionID} from '@/../bindings/voidraft/internal/models/models'
import {getExtensionManager} from '@/views/editor/manager' import {getExtensionManager} from '@/views/editor/manager'
import { import {
getAllExtensionIds, getAllExtensionIds,
getExtensionDescription, getExtensionDefaultConfig,
getExtensionDescription,
getExtensionDisplayName, getExtensionDisplayName,
hasExtensionConfig, hasExtensionConfig
getExtensionDefaultConfig
} from '@/views/editor/manager/factories' } from '@/views/editor/manager/factories'
import SettingSection from '../components/SettingSection.vue' import SettingSection from '../components/SettingSection.vue'
import SettingItem from '../components/SettingItem.vue' import SettingItem from '../components/SettingItem.vue'
@@ -67,10 +67,10 @@ const updateExtensionConfig = async (extensionId: ExtensionID, configKey: string
if (!extension) return if (!extension) return
// 更新配置 // 更新配置
const updatedConfig = { ...extension.config, [configKey]: value } const updatedConfig = {...extension.config, [configKey]: value}
await editorStore.updateExtension(extensionId, extension.enabled, updatedConfig) await editorStore.updateExtension(extensionId, extension.enabled, updatedConfig)
} catch (error) { } catch (error) {
console.error('Failed to update extension config:', error) console.error('Failed to update extension config:', error)
} }
@@ -80,7 +80,7 @@ const updateExtensionConfig = async (extensionId: ExtensionID, configKey: string
const resetExtension = async (extensionId: ExtensionID) => { const resetExtension = async (extensionId: ExtensionID) => {
try { try {
await ExtensionService.ResetExtensionToDefault(extensionId) await ExtensionService.ResetExtensionToDefault(extensionId)
// 重新加载扩展状态以获取最新配置 // 重新加载扩展状态以获取最新配置
await extensionStore.loadExtensions() await extensionStore.loadExtensions()
@@ -114,25 +114,23 @@ const extensionConfigMeta: Partial<Record<ExtensionID, Record<string, ConfigItem
displayText: { displayText: {
type: 'select', type: 'select',
options: [ options: [
{ value: 'characters', label: 'Characters' }, {value: 'characters', label: 'Characters'},
{ value: 'blocks', label: 'Blocks' } {value: 'blocks', label: 'Blocks'}
] ]
}, },
showOverlay: { showOverlay: {
type: 'select', type: 'select',
options: [ options: [
{ value: 'always', label: 'Always' }, {value: 'always', label: 'Always'},
{ value: 'mouse-over', label: 'Mouse Over' } {value: 'mouse-over', label: 'Mouse Over'}
] ]
}, },
autohide: { type: 'toggle' } autohide: {type: 'toggle'}
},
[ExtensionID.ExtensionCodeBlock]: {
showBackground: { type: 'toggle' },
enableAutoDetection: { type: 'toggle' }
}, },
[ExtensionID.ExtensionTextHighlight]: { [ExtensionID.ExtensionTextHighlight]: {
highlightClass: { type: 'text' } backgroundColor: {type: 'text'},
opacity: {type: 'number'}
} }
} }
@@ -142,7 +140,7 @@ const getConfigItemType = (extensionId: ExtensionID, configKey: string, defaultV
if (meta?.type) { if (meta?.type) {
return meta.type return meta.type
} }
// 根据默认值类型自动推断 // 根据默认值类型自动推断
if (typeof defaultValue === 'boolean') return 'toggle' if (typeof defaultValue === 'boolean') return 'toggle'
if (typeof defaultValue === 'number') return 'number' if (typeof defaultValue === 'number') return 'number'
@@ -180,8 +178,10 @@ const getSelectOptions = (extensionId: ExtensionID, configKey: string) => {
:class="{ expanded: expandedExtensions.has(extension.id) }" :class="{ expanded: expandedExtensions.has(extension.id) }"
:title="t('settings.extensionsPage.configuration')" :title="t('settings.extensionsPage.configuration')"
> >
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
<path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/> stroke-linecap="round" stroke-linejoin="round">
<path
d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/>
<circle cx="12" cy="12" r="3"/> <circle cx="12" cy="12" r="3"/>
</svg> </svg>
</button> </button>
@@ -194,7 +194,7 @@ const getSelectOptions = (extensionId: ExtensionID, configKey: string) => {
</SettingItem> </SettingItem>
<!-- 可展开的配置区域 --> <!-- 可展开的配置区域 -->
<div <div
v-if="extension.hasConfig && expandedExtensions.has(extension.id)" v-if="extension.hasConfig && expandedExtensions.has(extension.id)"
class="extension-config" class="extension-config"
> >
@@ -209,8 +209,8 @@ const getSelectOptions = (extensionId: ExtensionID, configKey: string) => {
{{ t('settings.reset') }} {{ t('settings.reset') }}
</button> </button>
</div> </div>
<div <div
v-for="[configKey, configValue] in Object.entries(extension.defaultConfig)" v-for="[configKey, configValue] in Object.entries(extension.defaultConfig)"
:key="configKey" :key="configKey"
class="config-item" class="config-item"
@@ -224,16 +224,19 @@ const getSelectOptions = (extensionId: ExtensionID, configKey: string) => {
:model-value="extension.config[configKey] ?? configValue" :model-value="extension.config[configKey] ?? configValue"
@update:model-value="updateExtensionConfig(extension.id, configKey, $event)" @update:model-value="updateExtensionConfig(extension.id, configKey, $event)"
/> />
<!-- 数字输入框 --> <!-- 数字输入框 -->
<input <input
v-else-if="getConfigItemType(extension.id, configKey, configValue) === 'number'" v-else-if="getConfigItemType(extension.id, configKey, configValue) === 'number'"
type="number" type="number"
class="config-input" class="config-input"
:value="extension.config[configKey] ?? configValue" :value="extension.config[configKey] ?? configValue"
@input="updateExtensionConfig(extension.id, configKey, parseInt(($event.target as HTMLInputElement).value))" :min="configKey === 'opacity' ? 0 : undefined"
:max="configKey === 'opacity' ? 1 : undefined"
:step="configKey === 'opacity' ? 0.1 : 1"
@input="updateExtensionConfig(extension.id, configKey, parseFloat(($event.target as HTMLInputElement).value))"
/> />
<!-- 选择框 --> <!-- 选择框 -->
<select <select
v-else-if="getConfigItemType(extension.id, configKey, configValue) === 'select'" v-else-if="getConfigItemType(extension.id, configKey, configValue) === 'select'"
@@ -249,7 +252,7 @@ const getSelectOptions = (extensionId: ExtensionID, configKey: string) => {
{{ option.label }} {{ option.label }}
</option> </option>
</select> </select>
<!-- 文本输入框 --> <!-- 文本输入框 -->
<input <input
v-else v-else
@@ -273,7 +276,7 @@ const getSelectOptions = (extensionId: ExtensionID, configKey: string) => {
.extension-item { .extension-item {
border-bottom: 1px solid var(--settings-input-border); border-bottom: 1px solid var(--settings-input-border);
&:last-child { &:last-child {
border-bottom: none; border-bottom: none;
} }
@@ -301,17 +304,17 @@ const getSelectOptions = (extensionId: ExtensionID, configKey: string) => {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
&:hover { &:hover {
background-color: var(--settings-hover); background-color: var(--settings-hover);
color: var(--settings-text); color: var(--settings-text);
} }
&.expanded { &.expanded {
color: var(--settings-accent); color: var(--settings-accent);
background-color: var(--settings-hover); background-color: var(--settings-hover);
} }
svg { svg {
transition: all 0.2s ease; transition: all 0.2s ease;
} }
@@ -368,12 +371,12 @@ const getSelectOptions = (extensionId: ExtensionID, configKey: string) => {
&:not(:last-child) { &:not(:last-child) {
margin-bottom: 12px; margin-bottom: 12px;
} }
/* 配置项标题和描述字体大小 */ /* 配置项标题和描述字体大小 */
:deep(.setting-item-title) { :deep(.setting-item-title) {
font-size: 12px; font-size: 12px;
} }
:deep(.setting-item-description) { :deep(.setting-item-description) {
font-size: 11px; font-size: 11px;
} }
@@ -387,7 +390,7 @@ const getSelectOptions = (extensionId: ExtensionID, configKey: string) => {
background-color: var(--settings-input-bg); background-color: var(--settings-input-bg);
color: var(--settings-text); color: var(--settings-text);
font-size: 11px; /* 调整字体大小 */ font-size: 11px; /* 调整字体大小 */
&:focus { &:focus {
outline: none; outline: none;
border-color: var(--settings-accent); border-color: var(--settings-accent);

View File

@@ -25,8 +25,7 @@ const (
ExtensionMinimap ExtensionID = "minimap" // 小地图 ExtensionMinimap ExtensionID = "minimap" // 小地图
// 工具扩展 // 工具扩展
ExtensionSearch ExtensionID = "search" // 搜索功能 ExtensionSearch ExtensionID = "search" // 搜索功能
ExtensionCodeBlock ExtensionID = "codeBlock" // 代码块
// 核心扩展 // 核心扩展
ExtensionEditor ExtensionID = "editor" // 编辑器核心功能 ExtensionEditor ExtensionID = "editor" // 编辑器核心功能
@@ -90,7 +89,10 @@ func NewDefaultExtensions() []Extension {
ID: ExtensionTextHighlight, ID: ExtensionTextHighlight,
Enabled: true, Enabled: true,
IsDefault: true, IsDefault: true,
Config: ExtensionConfig{}, Config: ExtensionConfig{
"backgroundColor": "#FFD700",
"opacity": 0.3,
},
}, },
// UI增强扩展 // UI增强扩展
@@ -112,15 +114,6 @@ func NewDefaultExtensions() []Extension {
IsDefault: true, IsDefault: true,
Config: ExtensionConfig{}, Config: ExtensionConfig{},
}, },
{
ID: ExtensionCodeBlock,
Enabled: true,
IsDefault: true,
Config: ExtensionConfig{
"showBackground": true,
"enableAutoDetection": true,
},
},
// 核心扩展 // 核心扩展
{ {

View File

@@ -78,6 +78,9 @@ const (
HistoryRedoCommand KeyBindingCommand = "historyRedo" // 重做 HistoryRedoCommand KeyBindingCommand = "historyRedo" // 重做
HistoryUndoSelectionCommand KeyBindingCommand = "historyUndoSelection" // 撤销选择 HistoryUndoSelectionCommand KeyBindingCommand = "historyUndoSelection" // 撤销选择
HistoryRedoSelectionCommand KeyBindingCommand = "historyRedoSelection" // 重做选择 HistoryRedoSelectionCommand KeyBindingCommand = "historyRedoSelection" // 重做选择
// 文本高亮扩展相关
TextHighlightToggleCommand KeyBindingCommand = "textHighlightToggle" // 切换文本高亮
) )
// KeyBindingMetadata 快捷键配置元数据 // KeyBindingMetadata 快捷键配置元数据
@@ -157,115 +160,115 @@ func NewDefaultKeyBindings() []KeyBinding {
IsDefault: true, IsDefault: true,
}, },
// 代码块扩展快捷键 // 代码块核心功能快捷键
{ {
Command: BlockSelectAllCommand, Command: BlockSelectAllCommand,
Extension: ExtensionCodeBlock, Extension: ExtensionEditor,
Key: "Mod-a", Key: "Mod-a",
Enabled: true, Enabled: true,
IsDefault: true, IsDefault: true,
}, },
{ {
Command: BlockAddAfterCurrentCommand, Command: BlockAddAfterCurrentCommand,
Extension: ExtensionCodeBlock, Extension: ExtensionEditor,
Key: "Mod-Enter", Key: "Mod-Enter",
Enabled: true, Enabled: true,
IsDefault: true, IsDefault: true,
}, },
{ {
Command: BlockAddAfterLastCommand, Command: BlockAddAfterLastCommand,
Extension: ExtensionCodeBlock, Extension: ExtensionEditor,
Key: "Mod-Shift-Enter", Key: "Mod-Shift-Enter",
Enabled: true, Enabled: true,
IsDefault: true, IsDefault: true,
}, },
{ {
Command: BlockAddBeforeCurrentCommand, Command: BlockAddBeforeCurrentCommand,
Extension: ExtensionCodeBlock, Extension: ExtensionEditor,
Key: "Alt-Enter", Key: "Alt-Enter",
Enabled: true, Enabled: true,
IsDefault: true, IsDefault: true,
}, },
{ {
Command: BlockGotoPreviousCommand, Command: BlockGotoPreviousCommand,
Extension: ExtensionCodeBlock, Extension: ExtensionEditor,
Key: "Mod-ArrowUp", Key: "Mod-ArrowUp",
Enabled: true, Enabled: true,
IsDefault: true, IsDefault: true,
}, },
{ {
Command: BlockGotoNextCommand, Command: BlockGotoNextCommand,
Extension: ExtensionCodeBlock, Extension: ExtensionEditor,
Key: "Mod-ArrowDown", Key: "Mod-ArrowDown",
Enabled: true, Enabled: true,
IsDefault: true, IsDefault: true,
}, },
{ {
Command: BlockSelectPreviousCommand, Command: BlockSelectPreviousCommand,
Extension: ExtensionCodeBlock, Extension: ExtensionEditor,
Key: "Mod-Shift-ArrowUp", Key: "Mod-Shift-ArrowUp",
Enabled: true, Enabled: true,
IsDefault: true, IsDefault: true,
}, },
{ {
Command: BlockSelectNextCommand, Command: BlockSelectNextCommand,
Extension: ExtensionCodeBlock, Extension: ExtensionEditor,
Key: "Mod-Shift-ArrowDown", Key: "Mod-Shift-ArrowDown",
Enabled: true, Enabled: true,
IsDefault: true, IsDefault: true,
}, },
{ {
Command: BlockDeleteCommand, Command: BlockDeleteCommand,
Extension: ExtensionCodeBlock, Extension: ExtensionEditor,
Key: "Mod-Shift-d", Key: "Mod-Shift-d",
Enabled: true, Enabled: true,
IsDefault: true, IsDefault: true,
}, },
{ {
Command: BlockMoveUpCommand, Command: BlockMoveUpCommand,
Extension: ExtensionCodeBlock, Extension: ExtensionEditor,
Key: "Alt-Mod-ArrowUp", Key: "Alt-Mod-ArrowUp",
Enabled: true, Enabled: true,
IsDefault: true, IsDefault: true,
}, },
{ {
Command: BlockMoveDownCommand, Command: BlockMoveDownCommand,
Extension: ExtensionCodeBlock, Extension: ExtensionEditor,
Key: "Alt-Mod-ArrowDown", Key: "Alt-Mod-ArrowDown",
Enabled: true, Enabled: true,
IsDefault: true, IsDefault: true,
}, },
{ {
Command: BlockDeleteLineCommand, Command: BlockDeleteLineCommand,
Extension: ExtensionCodeBlock, Extension: ExtensionEditor,
Key: "Mod-Shift-k", Key: "Mod-Shift-k",
Enabled: true, Enabled: true,
IsDefault: true, IsDefault: true,
}, },
{ {
Command: BlockMoveLineUpCommand, Command: BlockMoveLineUpCommand,
Extension: ExtensionCodeBlock, Extension: ExtensionEditor,
Key: "Alt-ArrowUp", Key: "Alt-ArrowUp",
Enabled: true, Enabled: true,
IsDefault: true, IsDefault: true,
}, },
{ {
Command: BlockMoveLineDownCommand, Command: BlockMoveLineDownCommand,
Extension: ExtensionCodeBlock, Extension: ExtensionEditor,
Key: "Alt-ArrowDown", Key: "Alt-ArrowDown",
Enabled: true, Enabled: true,
IsDefault: true, IsDefault: true,
}, },
{ {
Command: BlockTransposeCharsCommand, Command: BlockTransposeCharsCommand,
Extension: ExtensionCodeBlock, Extension: ExtensionEditor,
Key: "Ctrl-t", Key: "Ctrl-t",
Enabled: true, Enabled: true,
IsDefault: true, IsDefault: true,
}, },
{ {
Command: BlockFormatCommand, Command: BlockFormatCommand,
Extension: ExtensionCodeBlock, Extension: ExtensionEditor,
Key: "Mod-Shift-f", Key: "Mod-Shift-f",
Enabled: true, Enabled: true,
IsDefault: true, IsDefault: true,
@@ -493,6 +496,15 @@ func NewDefaultKeyBindings() []KeyBinding {
Enabled: true, Enabled: true,
IsDefault: true, IsDefault: true,
}, },
// 文本高亮扩展快捷键
{
Command: TextHighlightToggleCommand,
Extension: ExtensionTextHighlight,
Key: "Mod-Shift-h",
Enabled: true,
IsDefault: true,
},
} }
} }