# Selection and captured intent

Use logical source coordinates and prevent asynchronous actions from writing to stale ranges.

## Coordinates belong to source

MSFE uses UTF-16 offsets in CodeMirror's logical text and half-open `[from, to)` ranges. A preview has no independent coordinate system. An image or task control still owns a source range. Emoji may occupy two UTF-16 units; character counts and DOM offsets are not substitutes.

`getMarkdown()` may restore file line endings. Calculate edits against `editor.state.doc`, not byte offsets in serialized CRLF output. `selection.main.from/to` orders endpoints; `anchor/head` preserves direction.

~~~ts
import { EditorSelection } from '@quirekit/cm-state'

editor.setSelection(EditorSelection.single(10, 18))
editor.scrollTo(10)
editor.focus()
~~~

`EditorSelection.cursor()` returns one range, while `setSelection()` expects a complete selection. Use `single()` or `create()`. Define whether a command supports every selection or rejects multiple ranges; never silently modify only the first.

## Short menus: capture an exact target

~~~ts
const target = editor.captureTarget()
// The host displays a menu; source may change before the user confirms.
const applied = editor.chain(target).insertText('approved').run()
if (!applied) console.warn(editor.lastCommandResult)
~~~

Targets include instance, document, revision and directed selection. Source changes may invalidate them. Retrying without the target after failure would redirect the action to a different selection.

## Dialogs: map the original range

`captureAnchor()` maps across unrelated edits while verifying that the captured text remains unchanged. This complete helper suits asset or text selection dialogs. The host dialog must honor cancellation.

### recipes/intent.ts

```ts
import type { MsfeEditor } from "@quirekit/msfe-core";
/** @param editor - Current document. @param choose - Host dialog, observing cancellation. @returns Whether a still-current range was updated. */
export async function replaceSelectionFromDialog(editor: MsfeEditor, choose: (signal: AbortSignal) => Promise<string | null>): Promise<boolean> {
  const range = editor.state.selection.main;
  const scope = editor.documentScope.fork();
  const anchor = editor.captureAnchor({ from: range.from, to: range.to });
  scope.own(() => anchor.release());
  try {
    const value = await choose(scope.signal);
    if (scope.disposed || value === null) return false;
    const target = anchor.target();
    // Unrelated edits may move the interval; changes to its own text invalidate it.
    return target ? editor.chain(target).insertText(value).run() : false;
  } finally { scope.dispose(); }
}

```

Changing or deleting captured content, replacing the document or releasing the anchor invalidates its target. Document replacement invalidates old anchors even if the host reuses `documentId`. On `false`, ask the user to select again rather than forcing old offsets.

## Mapping policy

By default, `inclusiveStart` and `inclusiveEnd` are false, excluding insertions exactly at either boundary. `onDelete: 'drop'` removes a fully deleted range; `keep` can retain metadata as a point. Comments, decorations and insertion intents may require different policies.

`SourceRangeSet` suits larger collections of range metadata. Prefer `captureAnchor()` for one asynchronous user intent instead of rebuilding mapping infrastructure.

## Reveal, search and readonly

A native selection entering an ordinary replacement reveals source. A diagram's edit action therefore selects the appropriate source position. Atomic disclosure needs explicit state and `interaction: 'atomic'`; it must not permanently hide searchable content.

Readonly still permits selection and copying. Commands perform the final permission check: capturing an anchor never grants write permission. See [Command API](/en/api/commands) and [Rendering API](/en/api/rendering).
