From f5ef43b102233c6ec38e0a0adab1ad920c2239c6 Mon Sep 17 00:00:00 2001 From: Sayre Blades Date: Sat, 1 Aug 2026 18:14:39 -0400 Subject: [PATCH] fix(widths): guarantee minimum column width of 1 in fits path markdown-table-wrap-distribute-widths had two paths with different minimum-width contracts: the shrinking path floored every column at 1, but the fits path returned raw natural widths, which are 0 for an all-empty column. Returning 0 misaligns renderers: the separator draws a dash (via its own max-1 floor) where the row draws nothing, so every line after the separator drifts by one character per empty column. Apply max 1 in the fits path too, so both paths agree that column widths are always >= 1. The existing test markdown-table-wrap-test-widths-fit-naturally already asserted widths >= 1; this makes the fits path actually honor that contract for empty columns. Adds two regression tests: an all-empty column computes to width 1 (not 0), and a rendered table with an all-empty column has rows and separator of equal width (no drift). --- markdown-table-wrap.el | 8 +++++++- test/markdown-table-wrap-test.el | 28 ++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/markdown-table-wrap.el b/markdown-table-wrap.el index 29fcb08..24b7d35 100644 --- a/markdown-table-wrap.el +++ b/markdown-table-wrap.el @@ -832,7 +832,13 @@ monotonic — widening the terminal never shrinks any column." (total-natural (+ (markdown-table-wrap--vsum natural-widths) border-overhead))) (if (<= total-natural available-width) - (append natural-widths nil) + ;; Guarantee each column at least 1, matching the shrinking + ;; path's `max 1' floor below. An all-empty column has a + ;; natural width of 0; returning 0 misaligns renderers (the + ;; separator draws a dash where the row draws nothing), since + ;; the separator's `max 1' and the row's raw width disagree. + (mapcar (lambda (w) (max 1 w)) + (append natural-widths nil)) (let* ((col-w (make-vector num-cols 0)) ;; Precompute sqrt weights. (weights (vconcat diff --git a/test/markdown-table-wrap-test.el b/test/markdown-table-wrap-test.el index fedea5b..664d214 100644 --- a/test/markdown-table-wrap-test.el +++ b/test/markdown-table-wrap-test.el @@ -759,6 +759,34 @@ subtract marker overhead (which is invisible)." (should (>= (nth 0 widths) 1)) (should (>= (nth 1 widths) 1)))) +(ert-deftest markdown-table-wrap-test-widths-empty-column-min-1 () + "An all-empty column gets width at least 1, not 0. +A column whose header and every cell are empty has natural width 0. +Returning 0 misaligns renderers: the separator draws a dash (via its +own `max 1' floor) where the row draws nothing, so every line after +the separator drifts by one character per empty column. The shrinking +path already floors at 1; this test pins the fits path to the same +contract (see `markdown-table-wrap-test-widths-fit-naturally')." + (let ((widths (markdown-table-wrap-compute-widths + '("A" "" "B") '(("x" "" "y")) 20 3))) + (should (= (length widths) 3)) + (should (>= (nth 0 widths) 1)) + (should (>= (nth 1 widths) 1)) + (should (>= (nth 2 widths) 1)) + ;; The empty middle column is exactly 1, not 0. + (should (= (nth 1 widths) 1)))) + +(ert-deftest markdown-table-wrap-test-empty-column-row-separator-align () + "Rendering a table with an all-empty column keeps rows and separator aligned. +Before the fix, the separator was one char wider than each row per +empty column, so columns after an empty column drifted right." + (let* ((text "| a | | b |\n|---|---|---|\n| 1 | | 2 |\n") + (out (markdown-table-wrap text 40)) + (lines (split-string out "\n" t "[ \t\r]*")) + (widths (mapcar #'length lines))) + ;; Every rendered line (header, separator, row) has the same width. + (should (cl-every (lambda (w) (= w (car widths))) widths)))) + (ert-deftest markdown-table-wrap-test-widths-shrinking () "Columns shrink proportionally when table exceeds available width." (let ((widths (markdown-table-wrap-compute-widths