🐛 Fixed the issue of text highlighting expansion
This commit is contained in:
@@ -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",
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -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'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
@@ -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: '透明度'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
@@ -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
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@@ -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
|
|
||||||
];
|
];
|
||||||
}
|
}
|
@@ -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
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -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',
|
||||||
|
@@ -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);
|
||||||
|
@@ -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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
// 核心扩展
|
// 核心扩展
|
||||||
{
|
{
|
||||||
|
@@ -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,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user