@vscode/markdown-editor 0.0.2-116 → 0.0.2-118

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
@@ -80,6 +80,11 @@ empty ones. Elsewhere it inserts a single newline without creating a virtual
80
80
  paragraph. Arrow Down also stays within
81
81
  the existing source instead of creating a virtual paragraph.
82
82
 
83
+ At a soft wrap, the end of one visual line and start of the next share a source
84
+ offset. Mouse placement and visual-line navigation retain upstream caret
85
+ affinity when targeting the preceding line, without changing source offsets.
86
+ Explicit newlines and programmatic selections keep their existing behavior.
87
+
83
88
  See [Editing property tests](docs/editing-properties.md) for the interactive
84
89
  assertion fixtures, CI contracts, deterministic editing corpus, and the
85
90
  red/green/visual evidence required for layout fixes.
@@ -93,6 +98,12 @@ GitHub issue URLs can receive VS Code rich presentations without `[label](url)`
93
98
  syntax. Active links retain their exact editable source. Code spans and fenced
94
99
  code do not auto-link; trailing punctuation is excluded from link targets.
95
100
 
101
+ Pasting a single HTTP(S) URL over selected inline text turns that selection into
102
+ a Markdown link, preserving inline formatting. Pasting without a selection,
103
+ inside code or an existing link, or across block boundaries keeps ordinary text
104
+ replacement. Ctrl/Cmd+Shift+V bypasses link creation with the async clipboard
105
+ strategy (including VS Code). Typed URLs do not wrap selections.
106
+
96
107
  ### Fixture reference rendering
97
108
 
98
109
  The real-editor examples expose **Interactive mode** in the fixture toolbar.
@@ -101,7 +112,11 @@ editor, selection, or recording; it defaults off for deterministic screenshots.
101
112
 
102
113
  In the sidebar, **showEditModeToggle** restores the editor's lock button.
103
114
  **readonly**, **presentation** (`reading` / `editing`), and **renderMarkupText**
104
- (`none` / `selection` / `all`) control the three independent policies. These controls are
115
+ (`none` / `selection` / `all`) control permission, presentation, and markup policy.
116
+ In read-only mode, `selection` keeps Markdown rendered even when text is selected;
117
+ unlocking restores selection-driven source markers. Explicit `all` still exposes
118
+ source for inspection, and diff/find diagnostics retain their explicit source reveals.
119
+ These controls are
105
120
  live in the examples, main demos, content/selection fixtures, and Mermaid
106
121
  fixtures: edits and selection survive changes. The dropdown follows the editor
107
122
  button and replayed state. Legacy recordings with `sourceEditing` and
@@ -181,9 +196,10 @@ source markers. The source-selection status uses a fixed-height, ellipsized row.
181
196
 
182
197
  These are coverage seeds, not a claim that every combination is complete.
183
198
  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.
199
+ assertions; screenshot review alone does not close a regression.
200
+ Point-to-source resolution always uses the editor's geometric hit test. The
201
+ demo's `caretDomPosArea` visualization samples that same path; the browser
202
+ caret hit-test alternative and its `caretDomPos`/`ownHitTest` switches are removed.
187
203
 
188
204
  The list-marker review deliberately distinguishes presentation policies.
189
205
  Editing presentation treats a bare `*`, `-`, `+`, or ordered marker as paragraph
@@ -213,21 +229,68 @@ normal font sidebearing; the selection spans those exact caret endpoints.
213
229
  Source-only spaces that do not create a nested item extend
214
230
  leftward into the gutter instead of moving the logical bullet or text column.
215
231
  Their display width uses the parsed marker prefix, not a fixed two-space rule.
216
- Reading layout is unchanged.
232
+ Native bullet typography is unchanged.
233
+ Nested task prefixes use half-width indentation cells to bring their first
234
+ source-space dot closer to ordinary siblings without moving bullets, checkboxes,
235
+ or labels. Unordered-list continuation indentation reuses the source prefix's
236
+ indentation, marker, and separator cells, including an indented blank line
237
+ following a list. `lists/mixed-prefix-spaces`, `lists/blank-continuation-spaces`,
238
+ and `lists/nested-continuation-spaces` isolate these cases; browser tests verify
239
+ each cell's geometry, clicking, arrow movement, and typing at four font sizes.
217
240
  The `lists/indent-depth-2`, `lists/indent-depth-uneven`, and `lists/indent-prefix-3`
218
241
  fixtures reconstruct the nested-spacing feedback with minimal sources.
219
242
  Browser geometry assertions also cover wider ordered prefixes, tabs,
220
243
  markup visibility, typing, and undo.
221
244
 
245
+ Typing the third backtick of a new opening fence inserts a closing fence on the
246
+ next line, leaving the caret on the opener for a language name. Container
247
+ indentation is retained; pasted, inline, already-paired, and closing fences are
248
+ not completed. `formatting/fence-completion` captures the typing states. Enter
249
+ inside a fenced block preserves its indentation in editing presentation.
250
+ `lists/fenced-continuation-spaces` covers list-contained fences: opening, code,
251
+ and closing-line spaces reuse the list's source prefix cells, including with
252
+ syntax highlighting, without changing reading presentation.
253
+
254
+ The mode toggle is a sticky overlay in the right margin when there is room,
255
+ and may overlap content in narrow editors. It never changes text flow or reserves
256
+ an asymmetric rail. Left and right padding are equal and shrink
257
+ from 80px to a 24px minimum on narrow editors. Documents with top-level headings
258
+ retain a font-relative 3.5em minimum for hanging heading markers on both sides.
259
+ The `editor/readonly-toggle/` fixtures include wide, narrow, and scrolling documents.
260
+ The first heading has no additional top margin in either presentation; the
261
+ paired `HeadingReading` and `HeadingEditing` fixtures and lock-toggle browser
262
+ tests guard its text position. VS Code themes map whitespace indicators to
263
+ `editorWhitespace.foreground`, including its dark-theme opacity.
264
+
265
+ Set `EditorViewOptions.highlightActiveBlock` to `false` to disable the active
266
+ block's background and glow, or update the live `view.highlightActiveBlock`
267
+ observable. This does not change active source markers, selection, or editing
268
+ behavior. Highlighting is enabled by default.
269
+
270
+ Empty ATX headings keep their separator whitespace in the same source marker
271
+ as populated headings. A zero-source-length line anchor preserves the heading
272
+ caret's column and line box when typing or removing its first character.
273
+ The existing `headings/atx-*` empty-prefix states and `headings/prefix-caret-*`
274
+ populated states cover this transition.
275
+
276
+ Rich-link pills at the end of a rendered line may use the remaining content
277
+ width before truncating their title. Short titles retain their natural width;
278
+ pills followed by prose retain their compact title limit. The width is updated
279
+ with editor layout and metadata changes, respecting list indentation and the
280
+ mode toggle's local exclusion rectangle. `Links/Rich links/Static/Trailing titles`
281
+ provides long, short, mid-line, formatted, and session-link examples.
282
+
222
283
  `task-lists/nested-task-ownership` prevents a child checkbox from turning its
223
284
  parent into a task. `task-lists/mixed-task-labels` aligns labels of task and
224
285
  ordinary sibling items while preserving nested indentation. The ordinary
225
286
  item's real separator spaces expand to fill the extra label-column width.
226
287
  `task-lists/nested-task-empty-sibling-*` and `task-lists/mixed-task-separated-lists-*`
227
288
  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;
289
+ In both presentations, checkboxes and ordinary bullets share a gutter column;
229
290
  the task's extra source bullet hangs farther left. Labels keep their position
230
291
  when markup visibility changes, except for extra source-only separator padding.
292
+ `lists/mixed-label-column` compares the same nested labels in reading and editing;
293
+ both use one shared marker/label layout rather than separate task indentation.
231
294
  The expanded ordinary separator has a visible
232
295
  whitespace dot. Browser tests click task-label characters with markup hidden,
233
296
  selection-revealed, and fully revealed, then verify exact insertion offsets.
@@ -235,6 +298,15 @@ selection-revealed, and fully revealed, then verify exact insertion offsets.
235
298
  before label text is typed in editing presentation. Reading retains standard GFM
236
299
  parsing. Typing or deleting the label must not change checkbox ownership or move
237
300
  the empty caret's text column.
301
+ The `task-lists/checkbox-separator-*` fixtures cover the caret immediately
302
+ after `]`, after its first separator, and after an extra space. In editing
303
+ presentation, the first separator reaches the label column and each additional
304
+ space advances normally. The checkbox and separator participate in inline layout,
305
+ so native text ranges, clicks, and the painted caret agree.
306
+ The `lists/extra-separator-continuation-spaces` and
307
+ `lists/extra-separator-blank-line` fixtures protect `- test` followed by a blank
308
+ line and ` test`: the four prefix cells align and the blank-line caret sits
309
+ at the active label column.
238
310
  Editing also treats `- [] test` as unchecked, preserving task layout through
239
311
  `[x]` -> `[]` -> `[ ]` edits without inserting a space into the source.
240
312
  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;