@vscode/markdown-editor 0.0.2-117 → 0.0.2-119

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -75,11 +75,20 @@ character is typed. These narrow recognition deviations avoid structural jumps
75
75
  while typing or deleting, without depending on editing history. The reading
76
76
  preset leaves these extensions off.
77
77
 
78
+ Editing presentation recognizes empty task checkboxes (`[ ]`, `[x]`, and `[]`)
79
+ at end-of-file as well as before whitespace or a newline. Enter exits an empty
80
+ task just like an empty list item; a nested empty task first outdents.
81
+
78
82
  In editing presentation, Enter continues complete list items and exits/outdents
79
83
  empty ones. Elsewhere it inserts a single newline without creating a virtual
80
84
  paragraph. Arrow Down also stays within
81
85
  the existing source instead of creating a virtual paragraph.
82
86
 
87
+ At a soft wrap, the end of one visual line and start of the next share a source
88
+ offset. Mouse placement and visual-line navigation retain upstream caret
89
+ affinity when targeting the preceding line, without changing source offsets.
90
+ Explicit newlines and programmatic selections keep their existing behavior.
91
+
83
92
  See [Editing property tests](docs/editing-properties.md) for the interactive
84
93
  assertion fixtures, CI contracts, deterministic editing corpus, and the
85
94
  red/green/visual evidence required for layout fixes.
@@ -93,6 +102,12 @@ GitHub issue URLs can receive VS Code rich presentations without `[label](url)`
93
102
  syntax. Active links retain their exact editable source. Code spans and fenced
94
103
  code do not auto-link; trailing punctuation is excluded from link targets.
95
104
 
105
+ Pasting a single HTTP(S) URL over selected inline text turns that selection into
106
+ a Markdown link, preserving inline formatting. Pasting without a selection,
107
+ inside code or an existing link, or across block boundaries keeps ordinary text
108
+ replacement. Ctrl/Cmd+Shift+V bypasses link creation with the async clipboard
109
+ strategy (including VS Code). Typed URLs do not wrap selections.
110
+
96
111
  ### Fixture reference rendering
97
112
 
98
113
  The real-editor examples expose **Interactive mode** in the fixture toolbar.
@@ -101,7 +116,11 @@ editor, selection, or recording; it defaults off for deterministic screenshots.
101
116
 
102
117
  In the sidebar, **showEditModeToggle** restores the editor's lock button.
103
118
  **readonly**, **presentation** (`reading` / `editing`), and **renderMarkupText**
104
- (`none` / `selection` / `all`) control the three independent policies. These controls are
119
+ (`none` / `selection` / `all`) control permission, presentation, and markup policy.
120
+ In read-only mode, `selection` keeps Markdown rendered even when text is selected;
121
+ unlocking restores selection-driven source markers. Explicit `all` still exposes
122
+ source for inspection, and diff/find diagnostics retain their explicit source reveals.
123
+ These controls are
105
124
  live in the examples, main demos, content/selection fixtures, and Mermaid
106
125
  fixtures: edits and selection survive changes. The dropdown follows the editor
107
126
  button and replayed state. Legacy recordings with `sourceEditing` and
@@ -181,9 +200,10 @@ source markers. The source-selection status uses a fixed-height, ellipsized row.
181
200
 
182
201
  These are coverage seeds, not a claim that every combination is complete.
183
202
  Concrete feedback must receive a named fixture plus behavioral/geometry
184
- assertions; screenshot review alone does not close a regression. Production
185
- margin optimization and removal of alternative hit-testing remain separate
186
- follow-ups after reviewing these fixtures.
203
+ assertions; screenshot review alone does not close a regression.
204
+ Point-to-source resolution always uses the editor's geometric hit test. The
205
+ demo's `caretDomPosArea` visualization samples that same path; the browser
206
+ caret hit-test alternative and its `caretDomPos`/`ownHitTest` switches are removed.
187
207
 
188
208
  The list-marker review deliberately distinguishes presentation policies.
189
209
  Editing presentation treats a bare `*`, `-`, `+`, or ordered marker as paragraph
@@ -213,21 +233,83 @@ normal font sidebearing; the selection spans those exact caret endpoints.
213
233
  Source-only spaces that do not create a nested item extend
214
234
  leftward into the gutter instead of moving the logical bullet or text column.
215
235
  Their display width uses the parsed marker prefix, not a fixed two-space rule.
216
- Reading layout is unchanged.
236
+ Native bullet typography is unchanged.
237
+ Nested task prefixes use half-width indentation cells to bring their first
238
+ source-space dot closer to ordinary siblings without moving bullets, checkboxes,
239
+ or labels. Unordered-list continuation indentation reuses the source prefix's
240
+ indentation, marker, and separator cells, including an indented blank line
241
+ following a list. `lists/mixed-prefix-spaces`, `lists/blank-continuation-spaces`,
242
+ and `lists/nested-continuation-spaces` isolate these cases; browser tests verify
243
+ each cell's geometry, clicking, arrow movement, and typing at four font sizes.
217
244
  The `lists/indent-depth-2`, `lists/indent-depth-uneven`, and `lists/indent-prefix-3`
218
245
  fixtures reconstruct the nested-spacing feedback with minimal sources.
219
246
  Browser geometry assertions also cover wider ordered prefixes, tabs,
220
247
  markup visibility, typing, and undo.
221
248
 
249
+ Typing the third backtick of a new opening fence inserts a closing fence on the
250
+ next line, leaving the caret on the opener for a language name. Container
251
+ indentation is retained; pasted, inline, already-paired, and closing fences are
252
+ not completed. `formatting/fence-completion` captures the typing states. Enter
253
+ inside a fenced block preserves its indentation in editing presentation.
254
+ `lists/fenced-continuation-spaces` covers list-contained fences: opening, code,
255
+ and closing-line spaces reuse the list's source prefix cells, including with
256
+ syntax highlighting, without changing reading presentation.
257
+
258
+ The mode toggle is a sticky overlay in the right margin when there is room,
259
+ and may overlap content in narrow editors. It never changes text flow or reserves
260
+ an asymmetric rail. Left and right padding use two fixed sizes, without
261
+ intermediate scaling: 8px through 600px editor width, then 48px.
262
+ Once the document reaches its configured maximum width (900px by default),
263
+ it stays that width and additional space goes equally to the outer margins.
264
+ Disabling the width limit keeps the 48px margins on wide editors.
265
+ Top-level headings retain
266
+ symmetric 2em minimum clearance for their hanging markers, independent of heading
267
+ level. Long prefixes scale to fit that gutter instead of moving the document.
268
+ Top padding stays at 32px regardless of editor width, document content, or
269
+ scrollbar gutters. The VS Code webview uses thin scrollbars with transparent
270
+ tracks. First and later headings use identical font line boxes, preserving the
271
+ same text-to-underline spacing. Only the first block's top margin is removed;
272
+ the fixed top padding provides the optical spacing adjustment.
273
+ The `editor/readonly-toggle/` fixtures include wide, narrow, and scrolling documents.
274
+ The first block has no additional top margin in either presentation. The first
275
+ block inside a blockquote also has no top margin, preventing it from collapsing
276
+ outward and shifting the quote when a marker-only line gains content. The
277
+ paired `HeadingReading` and `HeadingEditing` fixtures and lock-toggle browser
278
+ tests guard its text position. VS Code themes map whitespace indicators to
279
+ `editorWhitespace.foreground`, including its dark-theme opacity.
280
+ The `HeadingSpacingH1` and `HeadingSpacingH2` fixtures compare first and later
281
+ headings; browser tests cover their underline spacing across themes, widths,
282
+ wrapped text, presentation modes, and active or inactive selections.
283
+
284
+ Set `EditorViewOptions.highlightActiveBlock` to `false` to disable the active
285
+ block's background and glow, or update the live `view.highlightActiveBlock`
286
+ observable. This does not change active source markers, selection, or editing
287
+ behavior. Highlighting is enabled by default.
288
+
289
+ Empty ATX headings keep their separator whitespace in the same source marker
290
+ as populated headings. A zero-source-length line anchor preserves the heading
291
+ caret's column and line box when typing or removing its first character.
292
+ The existing `headings/atx-*` empty-prefix states and `headings/prefix-caret-*`
293
+ populated states cover this transition.
294
+
295
+ Rich-link pills at the end of a rendered line may use the remaining content
296
+ width before truncating their title. Short titles retain their natural width;
297
+ pills followed by prose retain their compact title limit. The width is updated
298
+ with editor layout and metadata changes, respecting list indentation and the
299
+ mode toggle's local exclusion rectangle. `Links/Rich links/Static/Trailing titles`
300
+ provides long, short, mid-line, formatted, and session-link examples.
301
+
222
302
  `task-lists/nested-task-ownership` prevents a child checkbox from turning its
223
303
  parent into a task. `task-lists/mixed-task-labels` aligns labels of task and
224
304
  ordinary sibling items while preserving nested indentation. The ordinary
225
305
  item's real separator spaces expand to fill the extra label-column width.
226
306
  `task-lists/nested-task-empty-sibling-*` and `task-lists/mixed-task-separated-lists-*`
227
307
  isolate populated/empty nested tasks and mixed lists separated by a thematic break.
228
- In editing presentation, checkboxes and ordinary bullets share a gutter column;
308
+ In both presentations, checkboxes and ordinary bullets share a gutter column;
229
309
  the task's extra source bullet hangs farther left. Labels keep their position
230
310
  when markup visibility changes, except for extra source-only separator padding.
311
+ `lists/mixed-label-column` compares the same nested labels in reading and editing;
312
+ both use one shared marker/label layout rather than separate task indentation.
231
313
  The expanded ordinary separator has a visible
232
314
  whitespace dot. Browser tests click task-label characters with markup hidden,
233
315
  selection-revealed, and fully revealed, then verify exact insertion offsets.
@@ -235,6 +317,15 @@ selection-revealed, and fully revealed, then verify exact insertion offsets.
235
317
  before label text is typed in editing presentation. Reading retains standard GFM
236
318
  parsing. Typing or deleting the label must not change checkbox ownership or move
237
319
  the empty caret's text column.
320
+ The `task-lists/checkbox-separator-*` fixtures cover the caret immediately
321
+ after `]`, after its first separator, and after an extra space. In editing
322
+ presentation, the first separator reaches the label column and each additional
323
+ space advances normally. The checkbox and separator participate in inline layout,
324
+ so native text ranges, clicks, and the painted caret agree.
325
+ The `lists/extra-separator-continuation-spaces` and
326
+ `lists/extra-separator-blank-line` fixtures protect `- test` followed by a blank
327
+ line and ` test`: the four prefix cells align and the blank-line caret sits
328
+ at the active label column.
238
329
  Editing also treats `- [] test` as unchecked, preserving task layout through
239
330
  `[x]` -> `[]` -> `[ ]` edits without inserting a space into the source.
240
331
  Reading keeps `[]` as literal text. The normal separator requirement remains;
package/dist/index.d.ts CHANGED
@@ -637,16 +637,13 @@ export declare interface CursorMoveResult {
637
637
  export declare const cursorMoveRight: CursorCommand;
638
638
 
639
639
  /** The cursor's position in either source text or a source-less visual line. */
640
- export declare type CursorPosition = {
641
- readonly kind: 'source';
642
- readonly offset: SourceOffset;
643
- } | {
640
+ export declare type CursorPosition = SourceCursorPosition | {
644
641
  readonly kind: 'virtual';
645
642
  readonly line: VirtualCursorLine;
646
643
  };
647
644
 
648
645
  export declare namespace CursorPosition {
649
- export function source(offset: SourceOffset): CursorPosition;
646
+ export function source(offset: SourceOffset, affinity?: SourceCursorPosition['affinity']): SourceCursorPosition;
650
647
  export function virtual(line: VirtualCursorLine): CursorPosition;
651
648
  }
652
649
 
@@ -1275,6 +1272,7 @@ export declare class EditorView extends Disposable {
1275
1272
  readonly coordinateSpace: EditorCoordinateSpace;
1276
1273
  readonly forcedMarkerVisibleBlocks: ISettableObservable<ReadonlySet<BlockAstNode>, void>;
1277
1274
  readonly showReadonlyToggle: ISettableObservable<boolean, void>;
1275
+ readonly highlightActiveBlock: ISettableObservable<boolean, void>;
1278
1276
  /**
1279
1277
  * Inner container that holds the rendered document and the cursor/selection
1280
1278
  * overlays. The outer {@link element} spans the full width; this container
@@ -1383,9 +1381,9 @@ export declare class EditorView extends Disposable {
1383
1381
  * Renders the locked/rich/source editing mode toggle. When locked every block stays
1384
1382
  * in its clean rendered form (no markdown markers revealed) and edits are
1385
1383
  * ignored, while text selection still works. The control lives in a
1386
- * zero-height *sticky* host inside the centered content container, so the
1387
- * lock follows the content's right edge and remains pinned as the document
1388
- * scrolls. The current mode is also mirrored onto the root as `.md-readonly`
1384
+ * zero-height sticky host, using the right margin when it fits and
1385
+ * overlapping content when narrow, without changing the document's geometry.
1386
+ * The current mode is also mirrored onto the root as `.md-readonly`
1389
1387
  * for any CSS hooks.
1390
1388
  */
1391
1389
  private _setupReadonlyToggle;
@@ -1458,31 +1456,19 @@ export declare class EditorView extends Disposable {
1458
1456
  * re-check still respects any target the user has claimed in the meantime.
1459
1457
  */
1460
1458
  autoFocusOnOpen(): void;
1461
- /**
1462
- * Own point→offset resolution. When `true` (the default),
1463
- * {@link resolveOffsetFromPoint} ignores the platform DOM hit-test
1464
- * (`caretPositionFromPoint`) and snaps the point to the nearest offset purely
1465
- * from the rendered {@link VisualLineMap} geometry — picking the nearest
1466
- * visual line by `y`, then the nearest offset on it by `x`. Because a table
1467
- * row's cells share one horizontal line band, this makes the whole width of a
1468
- * row resolve into that row (rather than only the cell boxes), with no visible
1469
- * layout change. It also lets a drag keep extending toward off-viewport points
1470
- * (e.g. the pointer leaving the window), which the platform hit-test cannot
1471
- * resolve. Set to `false` to fall back to the platform DOM hit-test.
1472
- */
1473
- readonly geometricHitTest: ISettableObservable<boolean, void>;
1474
1459
  /**
1475
1460
  * Client coordinates → absolute source offset (any block). Used during
1476
1461
  * drag to keep extending the selection even when the pointer leaves the
1477
- * original block. Honours {@link geometricHitTest}.
1462
+ * original block. Snaps to the nearest visual line and source offset.
1478
1463
  */
1479
1464
  resolveOffsetFromPoint(point: Point2D): SourceOffset | undefined;
1465
+ resolveCursorPositionFromPoint(point: Point2D): SourceCursorPosition | undefined;
1480
1466
  private _resolveControlFreeVideoOffset;
1481
1467
  /**
1482
1468
  * Resolve table-cell hits that have no measurable text run. Empty cells map
1483
1469
  * from their own box instead of snapping to a neighboring cell; element-only
1484
1470
  * content (for example an inactive image) maps through the hit element's view
1485
- * node. Text-bearing cells keep the normal pixel-precise line-map/DOM path.
1471
+ * node. Text-bearing cells keep the normal pixel-precise line-map path.
1486
1472
  */
1487
1473
  private _resolveTableCellOffset;
1488
1474
  /**
@@ -1568,11 +1554,13 @@ export declare interface EditorViewOptions extends BlockViewOptions {
1568
1554
  */
1569
1555
  readonly classNames?: readonly string[];
1570
1556
  /**
1571
- * Whether to render the sticky edit/read-only toggle at the top-right edge
1557
+ * Whether to render the edit/read-only toggle at the top-right edge
1572
1558
  * of the content. Defaults to `true`; set to `false` to omit it (e.g. in
1573
1559
  * fixtures that focus on selection rendering).
1574
1560
  */
1575
1561
  readonly showReadonlyToggle?: boolean;
1562
+ /** Whether to paint the active block's background and glow. Defaults to `true`. */
1563
+ readonly highlightActiveBlock?: boolean;
1576
1564
  /**
1577
1565
  * Controls "limited width mode". The observable yields the maximum content
1578
1566
  * width in pixels, or `undefined` to let the content fill the available
@@ -1874,6 +1862,7 @@ declare class GlueViewData {
1874
1862
  */
1875
1863
  readonly decorateNewline: boolean;
1876
1864
  readonly showWhitespace: boolean;
1865
+ readonly listContinuation?: ListContinuation | undefined;
1877
1866
  readonly kind = "glue";
1878
1867
  constructor(ast: GlueAstNode, visible: boolean,
1879
1868
  /**
@@ -1882,7 +1871,7 @@ declare class GlueViewData {
1882
1871
  * the line ending hiding there; between block-level siblings (list items,
1883
1872
  * block children) the break is already visible, so no `↵` is drawn.
1884
1873
  */
1885
- decorateNewline: boolean, showWhitespace?: boolean);
1874
+ decorateNewline: boolean, showWhitespace?: boolean, listContinuation?: ListContinuation | undefined);
1886
1875
  }
1887
1876
 
1888
1877
  /**
@@ -1953,8 +1942,8 @@ export declare interface IClipboardContext {
1953
1942
  getSelectedText(): string | undefined;
1954
1943
  /** Delete the current selection (the cut half of cut). */
1955
1944
  deleteSelection(): void;
1956
- /** Insert text at the caret, replacing any selection (the paste action). */
1957
- insertText(text: string): void;
1945
+ /** Paste text, linking a suitable selection when the payload is a URL unless plain text was requested. */
1946
+ insertText(text: string, plainText?: boolean): void;
1958
1947
  }
1959
1948
 
1960
1949
  /**
@@ -2191,7 +2180,7 @@ export declare const insertSmartEnter: (ctx: CursorCommandContext, useSourceLine
2191
2180
  */
2192
2181
  export declare function insertTab(config?: IndentationConfig): EditCommand;
2193
2182
 
2194
- export declare function insertText(text: string, generatedIndentation?: OffsetRange): EditCommand;
2183
+ export declare function insertText(text: string, generatedIndentation?: OffsetRange, completeFences?: boolean): EditCommand;
2195
2184
 
2196
2185
  /**
2197
2186
  * An immutable view of one document's tokens at a point in time. It may be a
@@ -2369,6 +2358,14 @@ export declare class ListAstNode extends BlockAstNodeBase {
2369
2358
  protected _localEquals(o: this): boolean;
2370
2359
  }
2371
2360
 
2361
+ declare interface ListContinuation {
2362
+ readonly item: ListItemAstNode;
2363
+ readonly level: number;
2364
+ readonly indentation: number;
2365
+ readonly mixedTasks: boolean;
2366
+ readonly outsideList: boolean;
2367
+ }
2368
+
2372
2369
  export declare class ListItemAstNode extends AstNode {
2373
2370
  readonly marker: MarkerAstNode;
2374
2371
  readonly content: readonly (BlockAstNode | GlueAstNode)[];
@@ -2997,6 +2994,7 @@ export declare class RichLink {
2997
2994
  private constructor();
2998
2995
  update(presentation: LinkPresentation | undefined): void;
2999
2996
  private _renderUnavailable;
2997
+ private _layout;
3000
2998
  private _setDefaultOrder;
3001
2999
  }
3002
3000
 
@@ -3013,13 +3011,14 @@ export declare function selectBlock(ctx: CursorCommandContext, blockRange: Offse
3013
3011
  declare class Selection_2 {
3014
3012
  readonly anchor: SourceOffset;
3015
3013
  readonly active: SourceOffset;
3016
- static collapsed(offset: SourceOffset): Selection_2;
3017
- constructor(anchor: SourceOffset, active: SourceOffset);
3014
+ static collapsed(offset: SourceOffset, affinity?: SourceCursorPosition['affinity']): Selection_2;
3015
+ readonly activeAffinity?: SourceCursorPosition['affinity'];
3016
+ constructor(anchor: SourceOffset, active: SourceOffset, activeAffinity?: SourceCursorPosition['affinity']);
3018
3017
  get isCollapsed(): boolean;
3019
3018
  get isForward(): boolean;
3020
3019
  get range(): OffsetRange;
3021
3020
  collapseToActive(): Selection_2;
3022
- withActive(active: SourceOffset): Selection_2;
3021
+ withActive(active: SourceOffset, affinity?: SourceCursorPosition['affinity']): Selection_2;
3023
3022
  }
3024
3023
  export { Selection_2 as Selection }
3025
3024
 
@@ -3120,6 +3119,13 @@ export declare interface SnapshotTokens {
3120
3119
  readonly tokens: readonly Token[];
3121
3120
  }
3122
3121
 
3122
+ declare interface SourceCursorPosition {
3123
+ readonly kind: 'source';
3124
+ readonly offset: SourceOffset;
3125
+ /** Prefer the preceding visual line at a shared soft-wrap boundary. */
3126
+ readonly affinity?: 'upstream';
3127
+ }
3128
+
3123
3129
  /** Metadata delivered synchronously immediately before a model-owned source edit is applied. */
3124
3130
  export declare interface SourceEditEvent {
3125
3131
  readonly baseSourceTextId: number;
@@ -3763,7 +3769,7 @@ export declare class VisualLineMap {
3763
3769
  * The first such trailing-boundary line is remembered as a fallback for the
3764
3770
  * document's very last offset, where no later line starts it.
3765
3771
  */
3766
- lineIndexOfOffset(offset: SourceOffset): number;
3772
+ lineIndexOfOffset(offset: SourceOffset, affinity?: SourceCursorPosition['affinity']): number;
3767
3773
  /**
3768
3774
  * x of the caret position before `offset`, on the line returned by
3769
3775
  * {@link lineIndexOfOffset}. Returns `0` when the map is empty.
@@ -3786,6 +3792,8 @@ export declare class VisualLineMap {
3786
3792
  * uses {@link offsetInLineAtX} directly to preserve desired column.
3787
3793
  */
3788
3794
  offsetAtPoint(point: Point2D): SourceOffset;
3795
+ positionAtPoint(point: Point2D): SourceCursorPosition;
3796
+ positionAtOffsetInLine(lineIndex: number, offset: SourceOffset): SourceCursorPosition;
3789
3797
  /** Snap `x` to the nearest offset on a specific line. */
3790
3798
  offsetInLineAtX(lineIndex: number, x: number): SourceOffset;
3791
3799
  positionInLineAtX(lineIndex: number, x: number): CursorPosition;