🎨 Optimize code
This commit is contained in:
@@ -10,9 +10,10 @@ export const ADD_NEW_BLOCK = "codeblock-add-new-block";
|
|||||||
export const MOVE_BLOCK = "codeblock-move-block";
|
export const MOVE_BLOCK = "codeblock-move-block";
|
||||||
export const DELETE_BLOCK = "codeblock-delete-block";
|
export const DELETE_BLOCK = "codeblock-delete-block";
|
||||||
export const CURRENCIES_LOADED = "codeblock-currencies-loaded";
|
export const CURRENCIES_LOADED = "codeblock-currencies-loaded";
|
||||||
|
export const CONTENT_EDIT = "codeblock-content-edit";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 统一管理的 userEvent 常量,方便复用与检索。
|
* 统一管理的 userEvent 常量。
|
||||||
*/
|
*/
|
||||||
export const USER_EVENTS = {
|
export const USER_EVENTS = {
|
||||||
INPUT: "input",
|
INPUT: "input",
|
||||||
@@ -25,6 +26,8 @@ export const USER_EVENTS = {
|
|||||||
MOVE_LINE: "move.line",
|
MOVE_LINE: "move.line",
|
||||||
MOVE_CHARACTER: "move.character",
|
MOVE_CHARACTER: "move.character",
|
||||||
SELECT_BLOCK_BOUNDARY: "select.block-boundary",
|
SELECT_BLOCK_BOUNDARY: "select.block-boundary",
|
||||||
|
INPUT_REPLACE: "input.replace",
|
||||||
|
INPUT_REPLACE_ALL: "input.replace.all",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { EditorState, EditorSelection } from "@codemirror/state";
|
|||||||
import { EditorView } from "@codemirror/view";
|
import { EditorView } from "@codemirror/view";
|
||||||
import { Command } from "@codemirror/view";
|
import { Command } from "@codemirror/view";
|
||||||
import { LANGUAGES } from "./lang-parser/languages";
|
import { LANGUAGES } from "./lang-parser/languages";
|
||||||
import { USER_EVENTS } from "./annotation";
|
import { USER_EVENTS, codeBlockEvent, CONTENT_EDIT } from "./annotation";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建块分隔符正则表达式
|
* 构建块分隔符正则表达式
|
||||||
@@ -90,7 +90,8 @@ export const codeBlockCopyCut = EditorView.domEventHandlers({
|
|||||||
view.dispatch({
|
view.dispatch({
|
||||||
changes: ranges,
|
changes: ranges,
|
||||||
scrollIntoView: true,
|
scrollIntoView: true,
|
||||||
userEvent: USER_EVENTS.DELETE_CUT
|
userEvent: USER_EVENTS.DELETE_CUT,
|
||||||
|
annotations: [codeBlockEvent.of(CONTENT_EDIT)],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,7 +113,8 @@ const copyCut = (view: EditorView, cut: boolean): boolean => {
|
|||||||
view.dispatch({
|
view.dispatch({
|
||||||
changes: ranges,
|
changes: ranges,
|
||||||
scrollIntoView: true,
|
scrollIntoView: true,
|
||||||
userEvent: USER_EVENTS.DELETE_CUT
|
userEvent: USER_EVENTS.DELETE_CUT,
|
||||||
|
annotations: [codeBlockEvent.of(CONTENT_EDIT)],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,7 +146,8 @@ function doPaste(view: EditorView, input: string) {
|
|||||||
|
|
||||||
view.dispatch(changes, {
|
view.dispatch(changes, {
|
||||||
userEvent: USER_EVENTS.INPUT_PASTE,
|
userEvent: USER_EVENTS.INPUT_PASTE,
|
||||||
scrollIntoView: true
|
scrollIntoView: true,
|
||||||
|
annotations: [codeBlockEvent.of(CONTENT_EDIT)],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { ViewPlugin, EditorView, Decoration, WidgetType, layer, RectangleMarker } from "@codemirror/view";
|
import { ViewPlugin, EditorView, Decoration, WidgetType, layer, RectangleMarker } from "@codemirror/view";
|
||||||
import { StateField, RangeSetBuilder, EditorState } from "@codemirror/state";
|
import { StateField, RangeSetBuilder, EditorState, Transaction } from "@codemirror/state";
|
||||||
import { blockState } from "./state";
|
import { blockState } from "./state";
|
||||||
import { codeBlockEvent } from "./annotation";
|
import { codeBlockEvent, USER_EVENTS } from "./annotation";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 块开始装饰组件
|
* 块开始装饰组件
|
||||||
@@ -196,7 +196,8 @@ const preventFirstBlockFromBeingDeleted = EditorState.changeFilter.of((tr: any)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 如果是搜索替换操作,保护所有块分隔符
|
// 如果是搜索替换操作,保护所有块分隔符
|
||||||
if (tr.annotations.some((a: any) => a.value === "input.replace" || a.value === "input.replace.all")) {
|
const userEvent = tr.annotation(Transaction.userEvent);
|
||||||
|
if (userEvent && (userEvent === USER_EVENTS.INPUT_REPLACE || userEvent === USER_EVENTS.INPUT_REPLACE_ALL)) {
|
||||||
blocks?.forEach((block: any) => {
|
blocks?.forEach((block: any) => {
|
||||||
if (block.delimiter) {
|
if (block.delimiter) {
|
||||||
protect.push(block.delimiter.from, block.delimiter.to);
|
protect.push(block.delimiter.from, block.delimiter.to);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
import { EditorSelection, SelectionRange } from "@codemirror/state";
|
import { EditorSelection, SelectionRange } from "@codemirror/state";
|
||||||
import { EditorView } from "@codemirror/view";
|
import { EditorView } from "@codemirror/view";
|
||||||
import { getNoteBlockFromPos } from "./state";
|
import { getNoteBlockFromPos } from "./state";
|
||||||
|
import { codeBlockEvent, CONTENT_EDIT } from "./annotation";
|
||||||
import { USER_EVENTS } from "./annotation";
|
import { USER_EVENTS } from "./annotation";
|
||||||
|
|
||||||
interface LineBlock {
|
interface LineBlock {
|
||||||
@@ -88,7 +89,8 @@ export const deleteLine = (view: EditorView): boolean => {
|
|||||||
changes,
|
changes,
|
||||||
selection,
|
selection,
|
||||||
scrollIntoView: true,
|
scrollIntoView: true,
|
||||||
userEvent: USER_EVENTS.DELETE_LINE
|
userEvent: USER_EVENTS.DELETE_LINE,
|
||||||
|
annotations: [codeBlockEvent.of(CONTENT_EDIT)],
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -128,7 +130,8 @@ export const deleteLineCommand = ({ state, dispatch }: { state: any; dispatch: a
|
|||||||
changes,
|
changes,
|
||||||
selection,
|
selection,
|
||||||
scrollIntoView: true,
|
scrollIntoView: true,
|
||||||
userEvent: USER_EVENTS.DELETE_LINE
|
userEvent: USER_EVENTS.DELETE_LINE,
|
||||||
|
annotations: [codeBlockEvent.of(CONTENT_EDIT)],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import * as prettier from "prettier/standalone";
|
|||||||
import { getActiveNoteBlock } from "./state";
|
import { getActiveNoteBlock } from "./state";
|
||||||
import { getLanguage } from "./lang-parser/languages";
|
import { getLanguage } from "./lang-parser/languages";
|
||||||
import { SupportedLanguage } from "./types";
|
import { SupportedLanguage } from "./types";
|
||||||
import { USER_EVENTS } from "./annotation";
|
import { USER_EVENTS, codeBlockEvent, CONTENT_EDIT } from "./annotation";
|
||||||
|
|
||||||
export const formatBlockContent = (view) => {
|
export const formatBlockContent = (view) => {
|
||||||
if (!view || view.state.readOnly)
|
if (!view || view.state.readOnly)
|
||||||
@@ -88,7 +88,8 @@ export const formatBlockContent = (view) => {
|
|||||||
},
|
},
|
||||||
selection: EditorSelection.cursor(currentBlockFrom + Math.min(formattedContent.cursorOffset, formattedContent.formatted.length)),
|
selection: EditorSelection.cursor(currentBlockFrom + Math.min(formattedContent.cursorOffset, formattedContent.formatted.length)),
|
||||||
scrollIntoView: true,
|
scrollIntoView: true,
|
||||||
userEvent: USER_EVENTS.INPUT
|
userEvent: USER_EVENTS.INPUT,
|
||||||
|
annotations: [codeBlockEvent.of(CONTENT_EDIT)],
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -7,6 +7,11 @@ import { ViewPlugin, Decoration, WidgetType } from "@codemirror/view";
|
|||||||
import { RangeSetBuilder } from "@codemirror/state";
|
import { RangeSetBuilder } from "@codemirror/state";
|
||||||
import { getNoteBlockFromPos } from "./state";
|
import { getNoteBlockFromPos } from "./state";
|
||||||
import { transactionsHasAnnotation, CURRENCIES_LOADED } from "./annotation";
|
import { transactionsHasAnnotation, CURRENCIES_LOADED } from "./annotation";
|
||||||
|
|
||||||
|
type MathParserEntry = {
|
||||||
|
parser: any;
|
||||||
|
prev?: any;
|
||||||
|
};
|
||||||
// 声明全局math对象
|
// 声明全局math对象
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
@@ -63,8 +68,7 @@ class MathResult extends WidgetType {
|
|||||||
/**
|
/**
|
||||||
* 数学装饰函数
|
* 数学装饰函数
|
||||||
*/
|
*/
|
||||||
function mathDeco(view: any): any {
|
function mathDeco(view: any, parserCache: WeakMap<any, MathParserEntry>): any {
|
||||||
const mathParsers = new WeakMap();
|
|
||||||
const builder = new RangeSetBuilder();
|
const builder = new RangeSetBuilder();
|
||||||
|
|
||||||
for (const { from, to } of view.visibleRanges) {
|
for (const { from, to } of view.visibleRanges) {
|
||||||
@@ -73,17 +77,17 @@ function mathDeco(view: any): any {
|
|||||||
const block = getNoteBlockFromPos(view.state, pos);
|
const block = getNoteBlockFromPos(view.state, pos);
|
||||||
|
|
||||||
if (block && block.language.name === "math") {
|
if (block && block.language.name === "math") {
|
||||||
// get math.js parser and cache it for this block
|
let entry = parserCache.get(block);
|
||||||
let { parser, prev } = mathParsers.get(block) || {};
|
let parser = entry?.parser;
|
||||||
if (!parser) {
|
if (!parser) {
|
||||||
// 如果当前可见行不是该 math 块的第一行,为了正确累计 prev,需要从块头开始重新扫描
|
|
||||||
if (line.from > block.content.from) {
|
if (line.from > block.content.from) {
|
||||||
pos = block.content.from;
|
pos = block.content.from;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (typeof window.math !== 'undefined') {
|
if (typeof window.math !== 'undefined') {
|
||||||
parser = window.math.parser();
|
parser = window.math.parser();
|
||||||
mathParsers.set(block, { parser, prev });
|
entry = { parser, prev: undefined };
|
||||||
|
parserCache.set(block, entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,10 +95,15 @@ function mathDeco(view: any): any {
|
|||||||
let result: any;
|
let result: any;
|
||||||
try {
|
try {
|
||||||
if (parser) {
|
if (parser) {
|
||||||
parser.set("prev", prev);
|
if (entry && line.from === block.content.from && typeof parser.clear === "function") {
|
||||||
|
parser.clear();
|
||||||
|
entry.prev = undefined;
|
||||||
|
}
|
||||||
|
const prevValue = entry?.prev;
|
||||||
|
parser.set("prev", prevValue);
|
||||||
result = parser.evaluate(line.text);
|
result = parser.evaluate(line.text);
|
||||||
if (result !== undefined) {
|
if (entry && result !== undefined) {
|
||||||
mathParsers.set(block, { parser, prev: result });
|
entry.prev = result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -103,7 +112,7 @@ function mathDeco(view: any): any {
|
|||||||
|
|
||||||
// if we got a result from math.js, add the result decoration
|
// if we got a result from math.js, add the result decoration
|
||||||
if (result !== undefined) {
|
if (result !== undefined) {
|
||||||
const format = parser?.get("format");
|
const format = parser?.get?.("format");
|
||||||
|
|
||||||
let resultWidget: MathResult | undefined;
|
let resultWidget: MathResult | undefined;
|
||||||
if (typeof(result) === "string") {
|
if (typeof(result) === "string") {
|
||||||
@@ -148,19 +157,25 @@ function mathDeco(view: any): any {
|
|||||||
*/
|
*/
|
||||||
export const mathBlock = ViewPlugin.fromClass(class {
|
export const mathBlock = ViewPlugin.fromClass(class {
|
||||||
decorations: any;
|
decorations: any;
|
||||||
|
mathParsers: WeakMap<any, MathParserEntry>;
|
||||||
|
|
||||||
constructor(view: any) {
|
constructor(view: any) {
|
||||||
this.decorations = mathDeco(view);
|
this.mathParsers = new WeakMap();
|
||||||
|
this.decorations = mathDeco(view, this.mathParsers);
|
||||||
}
|
}
|
||||||
|
|
||||||
update(update: any) {
|
update(update: any) {
|
||||||
// 需要在文档/视口变化或收到 CURRENCIES_LOADED 注解时重新渲染
|
const hasCurrencyUpdate = transactionsHasAnnotation(update.transactions, CURRENCIES_LOADED);
|
||||||
|
if (update.docChanged || hasCurrencyUpdate) {
|
||||||
|
// 文档结构或汇率变化时重置解析缓存
|
||||||
|
this.mathParsers = new WeakMap();
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
update.docChanged ||
|
update.docChanged ||
|
||||||
update.viewportChanged ||
|
update.viewportChanged ||
|
||||||
transactionsHasAnnotation(update.transactions, CURRENCIES_LOADED)
|
hasCurrencyUpdate
|
||||||
) {
|
) {
|
||||||
this.decorations = mathDeco(update.view);
|
this.decorations = mathDeco(update.view, this.mathParsers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
import { EditorSelection, SelectionRange } from "@codemirror/state";
|
import { EditorSelection, SelectionRange } from "@codemirror/state";
|
||||||
import { blockState } from "./state";
|
import { blockState } from "./state";
|
||||||
import { LANGUAGES } from "./lang-parser/languages";
|
import { LANGUAGES } from "./lang-parser/languages";
|
||||||
|
import { codeBlockEvent, CONTENT_EDIT } from "./annotation";
|
||||||
import { USER_EVENTS } from "./annotation";
|
import { USER_EVENTS } from "./annotation";
|
||||||
|
|
||||||
interface LineBlock {
|
interface LineBlock {
|
||||||
@@ -132,7 +133,8 @@ function moveLine(state: any, dispatch: any, forward: boolean): boolean {
|
|||||||
changes,
|
changes,
|
||||||
scrollIntoView: true,
|
scrollIntoView: true,
|
||||||
selection: EditorSelection.create(ranges, state.selection.mainIndex),
|
selection: EditorSelection.create(ranges, state.selection.mainIndex),
|
||||||
userEvent: USER_EVENTS.MOVE_LINE
|
userEvent: USER_EVENTS.MOVE_LINE,
|
||||||
|
annotations: [codeBlockEvent.of(CONTENT_EDIT)],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { EditorState } from '@codemirror/state';
|
import { EditorState } from '@codemirror/state';
|
||||||
import { syntaxTree, syntaxTreeAvailable } from '@codemirror/language';
|
import { syntaxTree, ensureSyntaxTree } from '@codemirror/language';
|
||||||
|
import type { Tree } from '@lezer/common';
|
||||||
import { Block as BlockNode, BlockDelimiter, BlockContent, BlockLanguage } from './lang-parser/parser.terms.js';
|
import { Block as BlockNode, BlockDelimiter, BlockContent, BlockLanguage } from './lang-parser/parser.terms.js';
|
||||||
import {
|
import {
|
||||||
SupportedLanguage,
|
SupportedLanguage,
|
||||||
@@ -15,51 +16,47 @@ import {
|
|||||||
} from './types';
|
} from './types';
|
||||||
import { LANGUAGES } from './lang-parser/languages';
|
import { LANGUAGES } from './lang-parser/languages';
|
||||||
|
|
||||||
|
const DEFAULT_LANGUAGE = (LANGUAGES[0]?.token || 'text') as string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从语法树解析代码块
|
* 从语法树解析代码块
|
||||||
*/
|
*/
|
||||||
export function getBlocksFromSyntaxTree(state: EditorState): Block[] | null {
|
export function getBlocksFromSyntaxTree(state: EditorState): Block[] | null {
|
||||||
if (!syntaxTreeAvailable(state)) {
|
const tree = syntaxTree(state);
|
||||||
|
if (!tree) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
return collectBlocksFromTree(tree, state);
|
||||||
|
}
|
||||||
|
|
||||||
const tree = syntaxTree(state);
|
function collectBlocksFromTree(tree: Tree, state: EditorState): Block[] | null {
|
||||||
const blocks: Block[] = [];
|
const blocks: Block[] = [];
|
||||||
const doc = state.doc;
|
const doc = state.doc;
|
||||||
|
|
||||||
// 遍历语法树中的所有块
|
|
||||||
tree.iterate({
|
tree.iterate({
|
||||||
enter(node) {
|
enter(node) {
|
||||||
if (node.type.id === BlockNode) {
|
if (node.type.id === BlockNode) {
|
||||||
// 查找块的分隔符和内容
|
|
||||||
let delimiter: { from: number; to: number } | null = null;
|
let delimiter: { from: number; to: number } | null = null;
|
||||||
let content: { from: number; to: number } | null = null;
|
let content: { from: number; to: number } | null = null;
|
||||||
let language = 'text';
|
let language: string = DEFAULT_LANGUAGE;
|
||||||
let auto = false;
|
let auto = false;
|
||||||
|
|
||||||
// 遍历块的子节点
|
|
||||||
const blockNode = node.node;
|
const blockNode = node.node;
|
||||||
blockNode.firstChild?.cursor().iterate(child => {
|
blockNode.firstChild?.cursor().iterate(child => {
|
||||||
if (child.type.id === BlockDelimiter) {
|
if (child.type.id === BlockDelimiter) {
|
||||||
delimiter = { from: child.from, to: child.to };
|
delimiter = { from: child.from, to: child.to };
|
||||||
|
|
||||||
// 解析整个分隔符文本来获取语言和自动检测标记
|
|
||||||
const delimiterText = doc.sliceString(child.from, child.to);
|
const delimiterText = doc.sliceString(child.from, child.to);
|
||||||
|
|
||||||
// 使用正则表达式解析分隔符
|
|
||||||
const match = delimiterText.match(/∞∞∞([a-zA-Z0-9_-]+)(-a)?\n/);
|
const match = delimiterText.match(/∞∞∞([a-zA-Z0-9_-]+)(-a)?\n/);
|
||||||
if (match) {
|
if (match) {
|
||||||
language = match[1] || 'text';
|
language = match[1] || DEFAULT_LANGUAGE;
|
||||||
auto = match[2] === '-a';
|
auto = match[2] === '-a';
|
||||||
} else {
|
} else {
|
||||||
// 回退到逐个解析子节点
|
|
||||||
child.node.firstChild?.cursor().iterate(langChild => {
|
child.node.firstChild?.cursor().iterate(langChild => {
|
||||||
if (langChild.type.id === BlockLanguage) {
|
if (langChild.type.id === BlockLanguage) {
|
||||||
const langText = doc.sliceString(langChild.from, langChild.to);
|
const langText = doc.sliceString(langChild.from, langChild.to);
|
||||||
language = langText || 'text';
|
language = langText || DEFAULT_LANGUAGE;
|
||||||
}
|
}
|
||||||
// 检查是否有自动检测标记
|
if (doc.sliceString(langChild.from, langChild.to) === AUTO_DETECT_SUFFIX) {
|
||||||
if (doc.sliceString(langChild.from, langChild.to) === '-a') {
|
|
||||||
auto = true;
|
auto = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -88,7 +85,6 @@ export function getBlocksFromSyntaxTree(state: EditorState): Block[] | null {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (blocks.length > 0) {
|
if (blocks.length > 0) {
|
||||||
// 设置第一个块分隔符的大小
|
|
||||||
firstBlockDelimiterSize = blocks[0].delimiter.to;
|
firstBlockDelimiterSize = blocks[0].delimiter.to;
|
||||||
return blocks;
|
return blocks;
|
||||||
}
|
}
|
||||||
@@ -104,203 +100,78 @@ export let firstBlockDelimiterSize: number | undefined;
|
|||||||
*/
|
*/
|
||||||
export function getBlocksFromString(state: EditorState): Block[] {
|
export function getBlocksFromString(state: EditorState): Block[] {
|
||||||
const blocks: Block[] = [];
|
const blocks: Block[] = [];
|
||||||
const doc = state.doc;
|
const doc = state.doc;
|
||||||
|
|
||||||
if (doc.length === 0) {
|
if (doc.length === 0) {
|
||||||
// 如果文档为空,创建一个默认的文本块
|
return [createPlainTextBlock(0, 0)];
|
||||||
return [{
|
}
|
||||||
language: {
|
|
||||||
name: 'text',
|
|
||||||
auto: false,
|
|
||||||
},
|
|
||||||
content: {
|
|
||||||
from: 0,
|
|
||||||
to: 0,
|
|
||||||
},
|
|
||||||
delimiter: {
|
|
||||||
from: 0,
|
|
||||||
to: 0,
|
|
||||||
},
|
|
||||||
range: {
|
|
||||||
from: 0,
|
|
||||||
to: 0,
|
|
||||||
},
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
|
|
||||||
const content = doc.sliceString(0, doc.length);
|
const content = doc.sliceString(0, doc.length);
|
||||||
const delim = "\n∞∞∞";
|
const delimiter = DELIMITER_PREFIX;
|
||||||
let pos = 0;
|
const suffixLength = DELIMITER_SUFFIX.length;
|
||||||
|
|
||||||
// 检查文档是否以分隔符开始(不带前导换行符)
|
let pos = content.indexOf(delimiter);
|
||||||
if (content.startsWith("∞∞∞")) {
|
|
||||||
// 文档直接以分隔符开始,调整为标准格式
|
if (pos === -1) {
|
||||||
pos = 0;
|
firstBlockDelimiterSize = 0;
|
||||||
} else if (content.startsWith("\n∞∞∞")) {
|
return [createPlainTextBlock(0, doc.length)];
|
||||||
// 文档以换行符+分隔符开始,这是标准格式,从位置0开始解析
|
}
|
||||||
pos = 0;
|
|
||||||
} else {
|
if (pos > 0) {
|
||||||
// 如果文档不以分隔符开始,查找第一个分隔符
|
blocks.push(createPlainTextBlock(0, pos));
|
||||||
const firstDelimPos = content.indexOf(delim);
|
}
|
||||||
|
|
||||||
if (firstDelimPos === -1) {
|
while (pos !== -1 && pos < doc.length) {
|
||||||
// 如果没有找到分隔符,整个文档作为一个文本块
|
const blockStart = pos;
|
||||||
firstBlockDelimiterSize = 0;
|
const langStart = blockStart + delimiter.length;
|
||||||
return [{
|
const delimiterEnd = content.indexOf(DELIMITER_SUFFIX, langStart);
|
||||||
language: {
|
if (delimiterEnd === -1) break;
|
||||||
name: 'text',
|
|
||||||
auto: false,
|
const delimiterText = content.slice(blockStart, delimiterEnd + suffixLength);
|
||||||
},
|
const delimiterInfo = parseDelimiter(delimiterText);
|
||||||
content: {
|
if (!delimiterInfo) break;
|
||||||
from: 0,
|
|
||||||
to: doc.length,
|
const contentStart = delimiterEnd + suffixLength;
|
||||||
},
|
const nextDelimiter = content.indexOf(delimiter, contentStart);
|
||||||
delimiter: {
|
const contentEnd = nextDelimiter === -1 ? doc.length : nextDelimiter;
|
||||||
from: 0,
|
|
||||||
to: 0,
|
|
||||||
},
|
|
||||||
range: {
|
|
||||||
from: 0,
|
|
||||||
to: doc.length,
|
|
||||||
},
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建第一个块(分隔符之前的内容)
|
|
||||||
blocks.push({
|
blocks.push({
|
||||||
language: {
|
language: { name: delimiterInfo.language, auto: delimiterInfo.auto },
|
||||||
name: 'text',
|
content: { from: contentStart, to: contentEnd },
|
||||||
auto: false,
|
delimiter: { from: blockStart, to: delimiterEnd + suffixLength },
|
||||||
},
|
range: { from: blockStart, to: contentEnd },
|
||||||
content: {
|
|
||||||
from: 0,
|
|
||||||
to: firstDelimPos,
|
|
||||||
},
|
|
||||||
delimiter: {
|
|
||||||
from: 0,
|
|
||||||
to: 0,
|
|
||||||
},
|
|
||||||
range: {
|
|
||||||
from: 0,
|
|
||||||
to: firstDelimPos,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
pos = firstDelimPos;
|
pos = nextDelimiter;
|
||||||
firstBlockDelimiterSize = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
while (pos < doc.length) {
|
|
||||||
let blockStart: number;
|
|
||||||
|
|
||||||
if (pos === 0 && content.startsWith("∞∞∞")) {
|
|
||||||
// 处理文档开头直接是分隔符的情况(不带前导换行符)
|
|
||||||
blockStart = 0;
|
|
||||||
} else if (pos === 0 && content.startsWith("\n∞∞∞")) {
|
|
||||||
// 处理文档开头是换行符+分隔符的情况(标准格式)
|
|
||||||
blockStart = 0;
|
|
||||||
} else {
|
|
||||||
blockStart = content.indexOf(delim, pos);
|
|
||||||
if (blockStart !== pos) {
|
|
||||||
// 如果在当前位置没有找到分隔符,可能是文档结尾
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 确定语言开始位置
|
|
||||||
let langStart: number;
|
|
||||||
if (pos === 0 && content.startsWith("∞∞∞")) {
|
|
||||||
// 文档直接以分隔符开始,跳过 ∞∞∞
|
|
||||||
langStart = blockStart + 3;
|
|
||||||
} else {
|
|
||||||
// 标准情况,跳过 \n∞∞∞
|
|
||||||
langStart = blockStart + delim.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
const delimiterEnd = content.indexOf("\n", langStart);
|
|
||||||
if (delimiterEnd < 0) {
|
|
||||||
console.error("Error parsing blocks. Delimiter didn't end with newline");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const langFull = content.substring(langStart, delimiterEnd);
|
|
||||||
let auto = false;
|
|
||||||
let lang = langFull;
|
|
||||||
|
|
||||||
if (langFull.endsWith("-a")) {
|
|
||||||
auto = true;
|
|
||||||
lang = langFull.substring(0, langFull.length - 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
const contentFrom = delimiterEnd + 1;
|
|
||||||
let blockEnd = content.indexOf(delim, contentFrom);
|
|
||||||
if (blockEnd < 0) {
|
|
||||||
blockEnd = doc.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
const block: Block = {
|
|
||||||
language: {
|
|
||||||
name: lang || 'text',
|
|
||||||
auto: auto,
|
|
||||||
},
|
|
||||||
content: {
|
|
||||||
from: contentFrom,
|
|
||||||
to: blockEnd,
|
|
||||||
},
|
|
||||||
delimiter: {
|
|
||||||
from: blockStart,
|
|
||||||
to: delimiterEnd + 1,
|
|
||||||
},
|
|
||||||
range: {
|
|
||||||
from: blockStart,
|
|
||||||
to: blockEnd,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
blocks.push(block);
|
|
||||||
pos = blockEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果没有找到任何块,创建一个默认块
|
|
||||||
if (blocks.length === 0) {
|
if (blocks.length === 0) {
|
||||||
blocks.push({
|
blocks.push(createPlainTextBlock(0, doc.length));
|
||||||
language: {
|
|
||||||
name: 'text',
|
|
||||||
auto: false,
|
|
||||||
},
|
|
||||||
content: {
|
|
||||||
from: 0,
|
|
||||||
to: doc.length,
|
|
||||||
},
|
|
||||||
delimiter: {
|
|
||||||
from: 0,
|
|
||||||
to: 0,
|
|
||||||
},
|
|
||||||
range: {
|
|
||||||
from: 0,
|
|
||||||
to: doc.length,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
firstBlockDelimiterSize = 0;
|
firstBlockDelimiterSize = 0;
|
||||||
} else {
|
} else {
|
||||||
// 设置第一个块分隔符的大小
|
|
||||||
firstBlockDelimiterSize = blocks[0].delimiter.to;
|
firstBlockDelimiterSize = blocks[0].delimiter.to;
|
||||||
}
|
}
|
||||||
|
|
||||||
return blocks;
|
return blocks;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取文档中的所有块
|
* 获取文档中的所有块
|
||||||
*/
|
*/
|
||||||
export function getBlocks(state: EditorState): Block[] {
|
export function getBlocks(state: EditorState): Block[] {
|
||||||
// 优先使用语法树解析
|
let blocks = getBlocksFromSyntaxTree(state);
|
||||||
const syntaxTreeBlocks = getBlocksFromSyntaxTree(state);
|
if (blocks) {
|
||||||
if (syntaxTreeBlocks) {
|
return blocks;
|
||||||
return syntaxTreeBlocks;
|
}
|
||||||
|
|
||||||
|
const ensuredTree = ensureSyntaxTree(state, state.doc.length, 200);
|
||||||
|
if (ensuredTree) {
|
||||||
|
blocks = collectBlocksFromTree(ensuredTree, state);
|
||||||
|
if (blocks) {
|
||||||
|
return blocks;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果语法树不可用,回退到字符串解析
|
|
||||||
return getBlocksFromString(state);
|
return getBlocksFromString(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -396,10 +267,21 @@ export function parseDelimiter(delimiterText: string): { language: SupportedLang
|
|||||||
|
|
||||||
const validLanguage = LANGUAGES.some(lang => lang.token === languageName)
|
const validLanguage = LANGUAGES.some(lang => lang.token === languageName)
|
||||||
? languageName as SupportedLanguage
|
? languageName as SupportedLanguage
|
||||||
: 'text';
|
: DEFAULT_LANGUAGE as SupportedLanguage;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
language: validLanguage,
|
language: validLanguage,
|
||||||
auto: isAuto
|
auto: isAuto
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createPlainTextBlock(from: number, to: number): Block {
|
||||||
|
return {
|
||||||
|
language: { name: DEFAULT_LANGUAGE, auto: false },
|
||||||
|
content: { from, to },
|
||||||
|
delimiter: { from: 0, to: 0 },
|
||||||
|
range: { from, to },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { StateField, StateEffect, RangeSetBuilder, EditorSelection, EditorState,
|
|||||||
import { selectAll as defaultSelectAll } from "@codemirror/commands";
|
import { selectAll as defaultSelectAll } from "@codemirror/commands";
|
||||||
import { Command } from "@codemirror/view";
|
import { Command } from "@codemirror/view";
|
||||||
import { getActiveNoteBlock, blockState } from "./state";
|
import { getActiveNoteBlock, blockState } from "./state";
|
||||||
import { USER_EVENTS } from "./annotation";
|
import { USER_EVENTS, codeBlockEvent, CONTENT_EDIT } from "./annotation";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 当用户按下 Ctrl+A 时,我们希望首先选择整个块。但如果整个块已经被选中,
|
* 当用户按下 Ctrl+A 时,我们希望首先选择整个块。但如果整个块已经被选中,
|
||||||
@@ -116,7 +116,8 @@ export const selectAll: Command = ({ state, dispatch }) => {
|
|||||||
// 选择当前块的所有内容
|
// 选择当前块的所有内容
|
||||||
dispatch(state.update({
|
dispatch(state.update({
|
||||||
selection: { anchor: block.content.from, head: block.content.to },
|
selection: { anchor: block.content.from, head: block.content.to },
|
||||||
userEvent: USER_EVENTS.SELECT
|
userEvent: USER_EVENTS.SELECT,
|
||||||
|
annotations: [codeBlockEvent.of(CONTENT_EDIT)],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import { EditorSelection, findClusterBreak } from "@codemirror/state";
|
import { EditorSelection, findClusterBreak } from "@codemirror/state";
|
||||||
import { getNoteBlockFromPos } from "./state";
|
import { getNoteBlockFromPos } from "./state";
|
||||||
import { USER_EVENTS } from "./annotation";
|
import { USER_EVENTS, codeBlockEvent, CONTENT_EDIT } from "./annotation";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 交换光标前后的字符
|
* 交换光标前后的字符
|
||||||
@@ -47,7 +47,8 @@ export const transposeChars = ({ state, dispatch }: { state: any; dispatch: any
|
|||||||
|
|
||||||
dispatch(state.update(changes, {
|
dispatch(state.update(changes, {
|
||||||
scrollIntoView: true,
|
scrollIntoView: true,
|
||||||
userEvent: USER_EVENTS.MOVE_CHARACTER
|
userEvent: USER_EVENTS.MOVE_CHARACTER,
|
||||||
|
annotations: [codeBlockEvent.of(CONTENT_EDIT)],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
Reference in New Issue
Block a user