Command and target API
Command drafts, rejection results, external edits and captured ranges.
Signatures come from public package exports. Descriptions are checked against current JSDoc; identifiers retain their original names.
Command context
Commands are pure and synchronous. Use props.commands for nested work; see Commands.
@quirekit/msfe-coreContext of a pure author command. All nested commands share its transaction draft.
export interface CommandProps {
readonly state: EditorState;
readonly tr: SourceTransaction;
readonly editor: MsfeEditor;
readonly commands: SingleCommands;
readonly dispatch: (() => void) | undefined;
chain(): ChainedCommands;
can(): SingleCommands;
}Source transactions
Each step uses the current draft coordinates, not stale initial offsets.
@quirekit/msfe-coreMSFE transaction builder; deliberately not a ProseMirror Transaction.
export interface SourceTransaction {
readonly state: EditorState;
insertText(text: string): boolean;
replace(from: number, to: number, insert: string): boolean;
setSelection(anchor: number, head?: number): boolean;
effect<T>(effect: Effect<T>, value: T): boolean;
setMeta<T>(metadata: Metadata<T>, value: T): boolean;
apply(spec: TransactionSpec): boolean;
}Planner adapter
Adapt a pure CodeMirror planner to the same command chain.
@quirekit/msfe-coreAdapts an existing pure CodeMirror planner into the same author-command transaction. @param planner - Backend source planner. @returns Curried command body suitable for addCommands or chain.command.
function sourceCommand(planner: MsfeCommand): CommandSemantic format commands
toggleSourceMark({ node: 'StrongEmphasis', marker: '**', markerNode: 'EmphasisMark' })
returns a pure planner for editor.run() or sourceCommand(). Features declare grammar; Core handles
paired markers, directed selections and complete shells, then verifies both the requested format and
existing paired semantics. A complete body selection may expand over hidden shells. When no lossless
plan is proved, the command returns null without changing source or history. Partial mark removal and
crossing ranges are not automatically split. Empty selections still seed paired markers for free source input.
querySourceInline(state, range) reads endpoint ancestors without parsing, scanning the document or
running a format command. Use it for structural availability. reason === null permits planning but
does not guarantee a specific combination; eligible blank prose may have no node. Explicit planning
is limited to one inline owner of at most 20,000 characters, with a 12ms parse-validation budget.
Failure to verify in that budget also refuses. Multiple ranges are planned before one atomic commit.
The low-level toggleDelimiter remains a literal source tool.
@quirekit/msfe-coreGrammar owned by a semantic paired-mark feature.
export interface SourceMarkFormat {
readonly node: string;
readonly marker: string;
readonly markerNode: string;
}@quirekit/msfe-coreOne common inline owner, or an explanation of why formatting is unavailable.
export interface SourceInlineQuery {
readonly node: SyntaxNode | null;
readonly reason: "readonly" | "unparsed" | "structure" | "literal" | "selection" | null;
}@quirekit/msfe-coreReads only source endpoints and their ancestors; never starts parsing or serializes the document. @param state - Accepted source state. @param range - One intended inline range. @returns A common inline owner or a structural refusal.
function querySourceInline(state: EditorState, range: { readonly from: number; readonly to: number; }): SourceInlineQuery@quirekit/msfe-coreToggles a parser-confirmed format without replacing Markdown with another document model. @param format - Owning feature's syntax and source spelling. @returns A pure atomic command; unsafe combinations leave source and history unchanged.
function toggleSourceMark(format: SourceMarkFormat): MsfeCommandResult branches
Named commands return booleans; inspect lastCommandResult for details.
@quirekit/msfe-coreExplicit mutation rejection instead of silently retargeting user intent.
export type MsfeResult = {
readonly ok: true;
} | {
readonly ok: false;
readonly reason: "destroyed" | "not-mounted" | "readonly" | "stale-target" | "not-applicable" | "invalid-change" | "composing" | "not-installed" | "source-provider-locked";
};External changes
baseRevision is the local source revision, not a server ETag.
@quirekit/msfe-coreVersioned external source mutation.
export interface MsfeExternalChange {
readonly documentId: string;
readonly baseRevision: number;
readonly changes: ChangeSpec;
readonly origin?: string;
}Exact targets
Suitable for short menus; source changes can invalidate the target.
@quirekit/msfe-coreCaptured toolbar target, valid only for its exact document revision.
export interface MsfeTarget {
readonly instanceId: string;
readonly documentId: string;
readonly revision: number;
readonly selection: EditorSelection;
}Mapped intent
See Selection and captured intent for the complete async helper.
@quirekit/msfe-coreDocument-generation-scoped intent with explicit position remapping.
export interface SourceAnchor {
getRange(): MsfeSourceRange | null;
target(): MsfeTarget | null;
release(): void;
}Mapping policy
Choose endpoint and deletion semantics for the feature independently of presentation.
@quirekit/msfe-coreExplicit boundary affinity and deletion policy, independent of how a range is painted.
export interface RangePolicy {
readonly inclusiveStart?: boolean;
readonly inclusiveEnd?: boolean;
readonly onDelete?: "drop" | "keep";
}Source block commands
Resolve complete syntax blocks and run commands with a captured MsfeTarget. Planners preserve the source payload, use one undo step and reject cross-flow moves.
@quirekit/msfe-coreExact source block and its sibling flow; never a second document model.
export interface SourceBlock extends MsfeSourceRange {
readonly name: string;
readonly scope: MsfeSourceRange;
}@quirekit/msfe-coreSource insertion side relative to a complete block.
export type SourceBlockSide = "before" | "after";@quirekit/msfe-coreResolves only the pointer/caret ancestor chain, without scanning sibling content. @param state - Accepted source and parser snapshot. @param position - Logical position inside a block. @param scope - Optional required sibling flow, used to lift a drop target out of nested content. @returns Nearest complete block in an admitted sibling flow.
function sourceBlockAt(state: EditorState, position: number, scope?: SourceBlock["scope"]): SourceBlock | null@quirekit/msfe-core@param state - Accepted parser. @param block - Current complete block. @param direction - Previous or next sibling. @returns Adjacent structural block without enumerating the document.
function sourceBlockSibling(state: EditorState, block: SourceBlock, direction: -1 | 1): SourceBlock | null@quirekit/msfe-core@param a - First block. @param b - Second block. @returns Whether both belong to exactly the same source flow.
function sameSourceBlockScope(a: SourceBlock, b: SourceBlock): boolean@quirekit/msfe-core@param block - Explicit adjacent block. @param side - Source boundary. @param markdown - New complete Markdown. @returns One atomic source insertion; delimiters in the payload remain unchanged.
function insertSourceBlock(block: SourceBlock, side: SourceBlockSide, markdown: string): MsfeCommandreplaceSourceBlock(block, markdown) replaces a complete block atomically, preserving required
sibling separators and validating enclosing container boundaries. Run it with
editor.run(command, capturedTarget) to reject stale menu intents.
@quirekit/msfe-core@param block - Complete captured source block. @param markdown - Canonical replacement. @returns One guarded replacement preserving sibling and container boundaries.
function replaceSourceBlock(block: SourceBlock, markdown: string): MsfeCommand@quirekit/msfe-coreAn empty physical line in an admitted block flow, without a synthetic syntax node.
export interface SourceBlankLine extends MsfeSourceRange {
readonly scope: MsfeSourceRange;
}@quirekit/msfe-core@param state - Accepted source and syntax. @param position - Pointer or caret position. @returns A blank line in an admitted flow, never whitespace owned by code or list syntax.
function sourceBlankLineAt(state: EditorState, position: number): SourceBlankLine | null@quirekit/msfe-core@param line - Captured editable blank line. @param markdown - Complete canonical payload. @returns One guarded replacement preserving sibling separators and enclosing containers.
function insertAtSourceBlankLine(line: SourceBlankLine, markdown: string): MsfeCommand@quirekit/msfe-core@param block - Complete captured block. @returns One source deletion, validated again against the command draft.
function removeSourceBlock(block: SourceBlock): MsfeCommand@quirekit/msfe-coreMoves exact source, with one accepted transaction and one undo event through editor.run. @param block - Source block captured when the gesture began. @param target - Complete sibling at the final pointer position. @param side - Before or after that sibling. @returns A planner rejecting self/descendant/cross-flow and obsolete targets.
function moveSourceBlock(block: SourceBlock, target: SourceBlock, side: SourceBlockSide): MsfeCommandEditing boundaries
Features declare semantic ranges of hidden source through addEditingBoundaries() or the
editingBoundaries descriptor field. The same option on MdEditor, createMdEditor and
createMdEditorExtensions lets the host override defaults by node. See
Caret and editing boundaries for integration.
These declarations govern navigation, selection and native input protection. Structural actions
for Enter, Shift-Enter and Mod-Enter use the addBlockInput() contract below.
Ranges and policies
All offsets belong to the current state.doc and must satisfy
0 ≤ from ≤ contentFrom ≤ contentTo ≤ to ≤ state.doc.length. Optional
selectFrom/selectTo must enclose from/to within the document. An invalid range throws
invalid-editing-boundary when the rule resolves. Return null when syntax cannot be confirmed.
kind | Meaning and typical use |
|---|---|
style | Bold, emphasis, code, links and other inline formats with editable content inside paired shells |
block | Code or containers; shell protection includes adjacent line breaks needed to retain structure |
prefix | Heading, list or quote prefixes; the caret enters the content after the prefix |
separator | Compressed structural gaps skipped by navigation; the product permits native deletion to join text |
syntax | Table pipes or alignment rows; selectFrom/selectTo can identify the complete table |
atom | Whole components or empty fences; the caret stays outside and deletion at an edge first selects the object |
navigation: 'content' is the default inline entry policy; split keeps both inside and outside
stops. Blocks can explicitly declare content to clamp native line endpoints to their content,
as table rows do for outer pipes. Horizontal arrows still skip duplicate stops, and deletion
protection is unchanged. Omitted block policies retain the default navigation across shells.
Currently navigation: 'native' opts the entire syntax rule out of the shared runtime,
including input protection. deletion: 'unwrap' removes the full style or prefix at its relevant
edge; protect retains the shell; deletion: 'native' delegates editing while keeping navigation
rules. Declare the deletion policy explicitly. A complete owner selection permits whole removal;
partial selections retain its shell.
@quirekit/msfe-coreCurrent source interval and its editable interior, never a second content model.
export interface EditingBoundary {
readonly from: number;
readonly to: number;
readonly contentFrom: number;
readonly contentTo: number;
readonly kind: EditingBoundaryKind;
readonly selectFrom?: number;
readonly selectTo?: number;
readonly navigation?: "content" | "split" | "native";
readonly deletion?: "unwrap" | "protect" | "native";
}@quirekit/msfe-coreSemantic ownership of hidden source, independent of DOM decoration shape.
export type EditingBoundaryKind = "style" | "block" | "prefix" | "separator" | "syntax" | "atom";Queries and priority
nodes names parser nodes. resolve is a pure query over current source: do not dispatch, retain
old node offsets or infer positions from visible line counts. Navigation queries endpoints and
ancestors, and editing queries syntax intersecting the actual changes. Custom resolvers should
keep their work within those same bounds.
The first rule for each node in extension-priority order wins. Host overrides precede feature
defaults. Rules replace whole declarations rather than merging fields; returning null opts
that syntax rule out without falling back to the next one. Materialized block components
independently contribute whole-object ranges, superseding syntax rules within them.
The default when: 'hidden' enables rules when presentation hides syntax globally. when: 'prose'
also queries rules in Markdown editing. To protect unrevealed code or similar structures, first
check the specific range through isSourceRevealed(state, node) and return null when revealed.
Boundary normalization is disabled in source mode and during composition.
@quirekit/msfe-coreFeature-owned facts consumed by the shared navigation and editing runtime.
export interface EditingBoundaryRule {
readonly nodes: readonly string[];
readonly when?: "hidden" | "prose";
readonly resolve: (context: EditingBoundaryContext) => EditingBoundary | null;
}@quirekit/msfe-corePure feature query at one canonical endpoint.
export interface EditingBoundaryContext {
readonly state: EditorState;
readonly node: SyntaxNode;
readonly position: number;
}Installation and lifecycle
Ordinary features install automatically through addEditingBoundaries(). Advanced CM integrations
can install editingBoundaries(rules) from addCodeMirrorExtensions(); multiple installations
reuse the same runtime. Empty rules returns an empty extension without uninstalling other
features' rules. MD Editor's editingBoundaries: [] also retains defaults. Options are read on
creation; updating Vue props does not reinstall the rules.
Input protection plans complete changes before accepting the transaction, preserving annotations and mapping effects. Moving the caret neither writes source nor adds history. Explicit structure commands, external document updates and history retain their own authority. Transactions that bypass filtering only receive selection recovery when needed, guarded by current state identity and disposal. Read-only mode does not accept text writes.
@quirekit/msfe-core@param rules - Feature-owned syntax contracts, highest precedence first. @returns Shared navigation, input protection and endpoint recovery; duplicate installations reuse one runtime.
function editingBoundaries(rules: readonly EditingBoundaryRule[]): ExtensionBlock-owned Enter
Declare syntax ownership with addBlockInput(); see Commands. planBlockInput is the pure planner entry point. A null result leaves native handling to the caller.
@quirekit/msfe-coreUnique syntax ownership; omitted intents delegate, while unregistered block nodes stop routing.
export interface BlockInputRule {
readonly nodes: readonly string[];
readonly enter?: BlockInputBehavior;
readonly lineBreak?: BlockInputBehavior;
readonly exit?: BlockInputBehavior;
}@quirekit/msfe-coreImmutable whole-selection input; handlers plan without dispatching or mutating storage.
export interface BlockInputContext {
readonly state: EditorState;
readonly intent: BlockInputIntent;
readonly targets: readonly BlockInputTarget[];
}@quirekit/msfe-coreA selection endpoint and the current syntax owner resolved by the shared router.
export interface BlockInputTarget {
readonly range: SelectionRange;
readonly position: number;
readonly node: SyntaxNode;
}@quirekit/msfe-coreSemantic gestures mapped from Enter, Shift-Enter and Mod-Enter.
export type BlockInputIntent = "enter" | "lineBreak" | "exit";@quirekit/msfe-coreOne atomic source edit, delegation to an ancestor, or native keyboard fallback.
export type BlockInputResult = TransactionSpec | "parent" | "native";@quirekit/msfe-core@param context - Validated whole-selection input. @returns A pure decision for the entire gesture.
export type BlockInputHandler = (context: BlockInputContext) => BlockInputResult;@quirekit/msfe-coreA planner or a fixed ownership decision, requiring no per-key callback.
export type BlockInputBehavior = BlockInputHandler | "parent" | "native";@quirekit/msfe-coreResolves ownership without parsing, dispatching or scanning unrelated blocks. @param state - Canonical immutable source state. @param intent - Requested keyboard gesture. @returns One whole-selection edit, or native fallback with no partial mutation.
function planBlockInput(state: EditorState, intent: BlockInputIntent): TransactionSpec | null