Skip to content

T-UYTR-md-engine-tables

State: closed/done · Impact: high · Complexity: medium

@codemirror/lang-markdown already parses GFM Table* nodes, but live mode shows raw pipes and only read mode renders a table. This task adds the tables plugin: a rendered widget when the table is inactive, mark-based styling when active, a cell-navigation keymap, insert.table, and the table.* commands the chrome table menu runs.

LocationRole today
packages/ts/obsidian-editor/src/decorations.tsThe enter switch has no Table case; table markup renders as plain text in live mode
packages/ts/obsidian-editor/src/commands.tsNo table command
packages/ts/obsidian-editor/src/theme.tsNo table rules
packages/ts/obsidian-md/src/vue/components/TableBlock.tsRead-mode table component; the widget markup must match what read mode shows
packages/ts/obsidian-md/src/processor.tsAsync renderer only; renderToHtmlSync comes from T-RCWI-md-obsidian-md-sync-helpers
packages/ts/obsidian-editor/test/helpers.ts#makeStateHeadless state builder for the plugin tests

The plugin roster and dispatch come from T-4TE2-md-engine-plugin-contract; the command catalogue from T-EXQ7-md-engine-command-catalogue; tableAt in the tree helpers from T-MNRO-md-engine-anchors; the tables.md corpus fixture and the bench from T-0GV4-md-engine-line-endings-corpus.

packages/ts/obsidian-editor/src/plugins/tables.ts, a defineEditorPlugin with:

  • Inactive Table (no selection intersecting its range) → one block Decoration.replace widget whose DOM is renderToHtmlSync markup of the table source, filled from a cache keyed by that source so two rebuilds of an unchanged table reuse one render. A click on a rendered cell moves the cursor into that cell’s source position (plugin clicks entry).
  • Active Table → no widget; every table line gets a cm-table-line line decoration, pipes visible.
  • Keymap at prec: 'high', active only when the cursor is inside a Table: Tab → next cell (from the last cell it appends an empty row and enters its first cell), Shift-Tab → previous cell, Enter → same column in the next row, Mod-Enter → insert a row below.
  • Catalogue additions: insert.table (2×2 skeleton: header row, alignment row, one body row) and table.row.insert-above, table.row.insert-below, table.col.insert-left, table.col.insert-right, table.row.delete, table.col.delete, table.align.left, table.align.center, table.align.right. Every table.* run resolves its target through tableAt and rewrites only the lines of that table; bytes outside it are untouched.

The theme module gains rules for cm-table-line and the widget’s table styling through the existing --obsmd-* variables.

  1. Write packages/ts/obsidian-editor/test/plugins/tables.plugin.test.ts first, over a fixture with three tables: inactive table → one block widget whose markup contains the cell text; selection inside → widget gone, cm-table-line on each line; render cache hit for identical source (spy on renderToHtmlSync); each keymap binding’s cursor motion; Tab from the final cell appends a row.
  2. Write the command cases in the same suite: insert.table at an empty line inserts the skeleton; each table.* command applied to the middle table leaves the other two tables byte-identical.
  3. Implement the plugin: widget class, cache, click handler, line marks.
  4. Implement the keymap and the ten catalogue commands (runs live in the plugin module, ids registered in the catalogue).
  5. Add tables to BUILTIN_PLUGINS; add the theme rules.
  6. Extend the swap-writer expectations if any command needs a fixture position the corpus lacks; run bun test, tsc --noEmit, moon run obsidian-editor:bench.
LocationKindChange
packages/ts/obsidian-editor/src/plugins/tables.tsnewtables plugin: widget, cache, marks, keymap, command runs
packages/ts/obsidian-editor/src/plugins/index.tsnewCreated by T-4TE2-md-engine-plugin-contract; tables joins BUILTIN_PLUGINS
packages/ts/obsidian-editor/src/commands/catalogue.tsnewCreated by T-EXQ7-md-engine-command-catalogue; gains insert.table and the nine table.* ids
packages/ts/obsidian-editor/src/theme.tsmodifycm-table-line and widget table rules
packages/ts/obsidian-editor/test/plugins/tables.plugin.test.tsnewWidget, cache, keymap and command tests
  • AC-1: bun test packages/ts/obsidian-editor passes, including packages/ts/obsidian-editor/test/plugins/tables.plugin.test.ts.
  • AC-2: an inactive table renders exactly one block widget whose DOM contains a table element with the fixture’s cell text; moving the selection into it removes the widget and puts cm-table-line on each of its lines.
  • AC-3: Tab from the last cell appends one row (line count of the table grows by one) and places the cursor in the new row’s first cell; Enter moves to the same column of the next row.
  • AC-4: each of the ten ids listed in Proposed exists in the compiled command set, and each table.* command applied to the middle table of the three-table fixture leaves the other two tables byte-identical.
  • AC-5: two decoration rebuilds with an unchanged document call renderToHtmlSync once per distinct table source (spy assertion).
  • AC-6: bun test packages/ts/obsidian-editor/test/swap-writer.test.ts passes with the new commands in the matrix over packages/ts/obsidian-editor/test/fixtures/corpus/tables.md.
  • AC-7: moon run obsidian-editor:bench stays within 1.5× of the recorded baseline.

Member of milestone M-931L; engine step E6 of decision D-K3EM-markdown-editor-chrome-and-plugin-environment.

T-RCWI-md-obsidian-md-sync-helpers


← Back to Tasks