Skip to content

Commit c0082b7

Browse files
feat: align table cells according to indicators
## Details This adds full support for rendering table cells using alignment indicators and shifting text as needed to accomplish this. This works with both the `padded` and `trimmed` cell styles. An additional property `pipe_table.padding` was also added which defaults to 1 and is used to determine the minimum padding needed within each table cell. This works even if the cell underneath has no padding as well as being shifted any amount in any direction. Centering in narrow cells can sometimes look strange as the inability to use half widths can make the off by one seem larger, but there's really no way around this and most of the time it looks great. I'm pretty happy with this one :) This also modifies the rendering of the center indicator in the delimiter row to be 2 dashes on both ends of the cell rather than one in the middle, I think this looks a little better.
1 parent e13ac2c commit c0082b7

17 files changed

+185
-166
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,14 @@
1212
[965c222](https://github.com/MeanderingProgrammer/render-markdown.nvim/commit/965c222076b2d289ed498730845d533780f3c7c7)
1313
- code language name [#205](https://github.com/MeanderingProgrammer/render-markdown.nvim/issues/205)
1414
[18c7ef7](https://github.com/MeanderingProgrammer/render-markdown.nvim/commit/18c7ef71fb4b8d83cb0160adc9127fc4d65ca42e)
15+
- anti conceal per component [#204](https://github.com/MeanderingProgrammer/render-markdown.nvim/issues/204)
16+
[fb6b3d1](https://github.com/MeanderingProgrammer/render-markdown.nvim/commit/fb6b3d145e5e12b838c0b84124354802f381b1af)
1517

1618
### Bug Fixes
1719

1820
- bullet point right padding priority [#199](https://github.com/MeanderingProgrammer/render-markdown.nvim/issues/199)
1921
[b02c8ef](https://github.com/MeanderingProgrammer/render-markdown.nvim/commit/b02c8ef72b10537a346556696b6e7fb354c8771f)
22+
- window offset bottom calculation [e13ac2c](https://github.com/MeanderingProgrammer/render-markdown.nvim/commit/e13ac2c05d2f081453db1451ec07fbd8be33ceec)
2023

2124
## 7.3.0 (2024-10-04)
2225

README.md

+6-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Plugin to improve viewing Markdown files in Neovim
66

77
| | |
88
| --------- | ------- |
9-
| ![Heading](https://github.com/user-attachments/assets/6d5ca2d3-3263-4ccd-beaf-b5795167d776) | ![Table](https://github.com/user-attachments/assets/7c7e6351-baff-4e72-b668-2e4fac76e276) |
9+
| ![Heading](https://github.com/user-attachments/assets/6d5ca2d3-3263-4ccd-beaf-b5795167d776) | ![Table](https://github.com/user-attachments/assets/b071b31f-fc17-4665-bf75-2e533eae4686) |
1010
| ![Quote](https://github.com/user-attachments/assets/dba88e99-bff5-4b33-9017-ab55c1058d21) | ![LaTeX](https://github.com/user-attachments/assets/85bac8f1-c7df-4078-9e9c-374de9b08e03) |
1111
| ![Callout](https://github.com/user-attachments/assets/bea7e1b9-d77f-4c3f-abf8-f6262b05fad2) | |
1212

@@ -33,7 +33,7 @@ Plugin to improve viewing Markdown files in Neovim
3333
- Checkboxes: icon, color, user defined states [^1]
3434
- Block quotes: icon, color, line breaks [^1]
3535
- Callouts: icon, color, user defined values, Github & Obsidian defaults
36-
- Tables: border, color, alignment indicator, auto align cells always to left [^1]
36+
- Tables: border, color, alignment indicator, auto align cells [^1]
3737
- Links [^1]: icon, color, user defined destinations
3838
- `LaTeX` blocks [^3]: renders formulas
3939
- Org indent mode [^1]: per level padding
@@ -431,6 +431,8 @@ require('render-markdown').setup({
431431
-- padded: raw + cells are padded to maximum visual width for each column
432432
-- trimmed: padded except empty space is subtracted from visual width calculation
433433
cell = 'padded',
434+
-- Amount of space to put between cell contents and border
435+
padding = 1,
434436
-- Minimum column width to use for padded or trimmed cell
435437
min_width = 0,
436438
-- Characters used to replace table border
@@ -925,6 +927,8 @@ require('render-markdown').setup({
925927
-- padded: raw + cells are padded to maximum visual width for each column
926928
-- trimmed: padded except empty space is subtracted from visual width calculation
927929
cell = 'padded',
930+
-- Amount of space to put between cell contents and border
931+
padding = 1,
928932
-- Minimum column width to use for padded or trimmed cell
929933
min_width = 0,
930934
-- Characters used to replace table border

benches/medium_table_spec.lua

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ local util = require('benches.util')
44

55
describe('medium-table.md', function()
66
it('default', function()
7-
local base_marks = 240
7+
local base_marks = 241
88
util.less_than(util.setup('temp/medium-table.md'), 110)
99
util.num_marks(base_marks)
1010

benches/readme_spec.lua

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ local util = require('benches.util')
44

55
describe('README.md', function()
66
it('default', function()
7-
local base_marks = 61
7+
local base_marks = 66
88
util.less_than(util.setup('README.md'), 50)
99
util.num_marks(base_marks)
1010

demo/run.py

+13-5
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,25 @@
22
from argparse import ArgumentParser
33
from pathlib import Path
44

5+
INFO: dict[str, tuple[int, str]] = dict(
6+
heading_code=(550, "## Heading 2"),
7+
list_table=(550, ""),
8+
box_dash_quote=(250, ""),
9+
latex=(250, ""),
10+
callout=(750, ""),
11+
)
512

6-
def main(name: str, height: int, content: str):
13+
14+
def main(name: str) -> None:
715
in_file = Path(f"demo/{name}.md")
816
assert in_file.exists()
917

1018
out_file = Path(f"demo/{name}.gif")
1119
if out_file.exists():
1220
out_file.unlink()
1321

22+
height, content = INFO[name]
23+
1424
tape = Path("demo/demo.tape")
1525
tape.write_text(tape_content(in_file, out_file, height, content))
1626
result = subprocess.run(["vhs", tape])
@@ -52,8 +62,6 @@ def get_move(in_file: Path) -> str:
5262

5363
if __name__ == "__main__":
5464
parser = ArgumentParser(description="Generate a demo recording using vhs")
55-
parser.add_argument("--name", type=str, required=True)
56-
parser.add_argument("--height", type=int, required=True)
57-
parser.add_argument("--content", type=str, required=True)
65+
parser.add_argument("--name", type=str, required=True, choices=INFO.keys())
5866
args = parser.parse_args()
59-
main(args.name, args.height, args.content)
67+
main(args.name)

doc/render-markdown.txt

+5-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ Plugin to improve viewing Markdown files in Neovim
5959
- Checkboxes: icon, color, user defined states
6060
- Block quotes: icon, color, line breaks
6161
- Callouts: icon, color, user defined values, Github & Obsidian defaults
62-
- Tables: border, color, alignment indicator, auto align cells always to left
62+
- Tables: border, color, alignment indicator, auto align cells
6363
- Links : icon, color, user defined destinations
6464
- `LaTeX` blocks : renders formulas
6565
- Org indent mode : per level padding
@@ -480,6 +480,8 @@ Default Configuration ~
480480
-- padded: raw + cells are padded to maximum visual width for each column
481481
-- trimmed: padded except empty space is subtracted from visual width calculation
482482
cell = 'padded',
483+
-- Amount of space to put between cell contents and border
484+
padding = 1,
483485
-- Minimum column width to use for padded or trimmed cell
484486
min_width = 0,
485487
-- Characters used to replace table border
@@ -958,6 +960,8 @@ Table Configuration ~
958960
-- padded: raw + cells are padded to maximum visual width for each column
959961
-- trimmed: padded except empty space is subtracted from visual width calculation
960962
cell = 'padded',
963+
-- Amount of space to put between cell contents and border
964+
padding = 1,
961965
-- Minimum column width to use for padded or trimmed cell
962966
min_width = 0,
963967
-- Characters used to replace table border

justfile

+5-20
Original file line numberDiff line numberDiff line change
@@ -36,31 +36,16 @@ cat-log:
3636
demo: demo-heading demo-list demo-box demo-latex demo-callout
3737

3838
demo-heading:
39-
python demo/run.py \
40-
--name "heading_code" \
41-
--height "550" \
42-
--content "## Heading 2"
39+
python demo/run.py --name "heading_code"
4340

4441
demo-list:
45-
python demo/run.py \
46-
--name "list_table" \
47-
--height "550" \
48-
--content ""
42+
python demo/run.py --name "list_table"
4943

5044
demo-box:
51-
python demo/run.py \
52-
--name "box_dash_quote" \
53-
--height "250" \
54-
--content ""
45+
python demo/run.py --name "box_dash_quote"
5546

5647
demo-latex:
57-
python demo/run.py \
58-
--name "latex" \
59-
--height "250" \
60-
--content ""
48+
python demo/run.py --name "latex"
6149

6250
demo-callout:
63-
python demo/run.py \
64-
--name "callout" \
65-
--height "750" \
66-
--content ""
51+
python demo/run.py --name "callout"

lua/render-markdown/health.lua

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ local state = require('render-markdown.state')
44
local M = {}
55

66
---@private
7-
M.version = '7.3.8'
7+
M.version = '7.3.9'
88

99
function M.check()
1010
M.start('version')

lua/render-markdown/init.lua

+3
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ local M = {}
7474
---@field public preset? render.md.table.Preset
7575
---@field public style? render.md.table.Style
7676
---@field public cell? render.md.table.Cell
77+
---@field public padding? integer
7778
---@field public min_width? integer
7879
---@field public border? string[]
7980
---@field public alignment_indicator? string
@@ -542,6 +543,8 @@ M.default_config = {
542543
-- padded: raw + cells are padded to maximum visual width for each column
543544
-- trimmed: padded except empty space is subtracted from visual width calculation
544545
cell = 'padded',
546+
-- Amount of space to put between cell contents and border
547+
padding = 1,
545548
-- Minimum column width to use for padded or trimmed cell
546549
min_width = 0,
547550
-- Characters used to replace table border

lua/render-markdown/render/table.lua

+68-37
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ local str = require('render-markdown.core.str')
55

66
---@class render.md.table.Column
77
---@field row integer
8-
---@field col integer
8+
---@field start_col integer
9+
---@field end_col integer
910
---@field width integer
10-
---@field free integer
11+
---@field space { left: integer, right: integer }
1112

1213
---@class render.md.table.Row
1314
---@field info render.md.NodeInfo
@@ -80,9 +81,13 @@ function Render:setup()
8081
end
8182
-- Store the max width information in the delimiter
8283
for _, row in ipairs(rows) do
83-
for i, r_column in ipairs(row.columns) do
84-
local d_column = delim.columns[i]
85-
d_column.width = math.max(d_column.width, r_column.width)
84+
for i, column in ipairs(row.columns) do
85+
local delim_column, width = delim.columns[i], column.width
86+
if self.table.cell == 'trimmed' then
87+
local space_available = column.space.left + column.space.right - (2 * self.table.padding)
88+
width = width - math.max(space_available, 0)
89+
end
90+
delim_column.width = math.max(delim_column.width, width)
8691
end
8792
end
8893

@@ -143,19 +148,26 @@ function Render:parse_row(row, num_columns)
143148
end
144149
local columns = {}
145150
for i, cell in ipairs(cells) do
146-
local width = pipes[i + 1].start_col - pipes[i].end_col
147-
-- Account for double width glyphs by replacing cell spacing with width
151+
local start_col, end_col = pipes[i].end_col, pipes[i + 1].start_col
152+
-- Account for double width glyphs by replacing cell range with width
153+
local width = end_col - start_col
148154
width = width - (cell.end_col - cell.start_col) + self.context:width(cell)
149-
local free = 0
150-
if self.table.cell == 'trimmed' then
151-
-- Remove trailing spaces from width calculation except for 1
152-
free = math.max(str.spaces('end', cell.text) - 1, 0)
153-
width = width - free
154-
end
155+
local space = {
156+
-- Left space comes from the gap between the node start and the pipe start
157+
left = math.max(cell.start_col - start_col, 0),
158+
-- Right space is attached to the node itself
159+
right = math.max(str.spaces('end', cell.text), 0),
160+
}
155161
if width < 0 then
156162
return nil
157163
end
158-
table.insert(columns, { row = cell.start_row, col = cell.end_col, width = width, free = free })
164+
table.insert(columns, {
165+
row = cell.start_row,
166+
start_col = cell.start_col,
167+
end_col = cell.end_col,
168+
width = width,
169+
space = space,
170+
})
159171
end
160172
return { info = row, pipes = pipes, columns = columns }
161173
end
@@ -198,22 +210,19 @@ function Render:delimiter()
198210
local delim, border = self.data.delim, self.table.border
199211

200212
local sections = Iter.list.map(delim.columns, function(column)
201-
local indicator = self.table.alignment_indicator
213+
local indicator, box = self.table.alignment_indicator, border[11]
202214
-- If column is small there's no good place to put the alignment indicator
203215
-- Alignment indicator must be exactly one character wide
204216
-- Do not put an indicator for default alignment
205-
if column.width < 4 or str.width(indicator) ~= 1 or column.alignment == 'default' then
206-
return border[11]:rep(column.width)
217+
if column.width < 3 or str.width(indicator) ~= 1 or column.alignment == 'default' then
218+
return box:rep(column.width)
207219
end
208-
-- Handle the various alignmnet possibilities
209-
local left = border[11]:rep(math.floor(column.width / 2))
210-
local right = border[11]:rep(math.ceil(column.width / 2) - 1)
211220
if column.alignment == 'left' then
212-
return indicator .. left .. right
221+
return indicator .. box:rep(column.width - 1)
213222
elseif column.alignment == 'right' then
214-
return left .. right .. indicator
223+
return box:rep(column.width - 1) .. indicator
215224
else
216-
return left .. indicator .. right
225+
return indicator .. box:rep(column.width - 2) .. indicator
217226
end
218227
end)
219228

@@ -246,22 +255,22 @@ function Render:row(row)
246255
end
247256
end
248257

249-
-- Use low priority to include pipe marks
250258
if vim.tbl_contains({ 'trimmed', 'padded' }, self.table.cell) then
251259
for i, column in ipairs(row.columns) do
252-
local offset = delim.columns[i].width - column.width - column.free
253-
if offset > 0 then
254-
self.marks:add(true, column.row, column.col, {
255-
priority = 0,
256-
virt_text = { { str.pad(offset), self.table.filler } },
257-
virt_text_pos = 'inline',
258-
})
259-
elseif offset < 0 then
260-
self.marks:add(true, column.row, column.col + offset, {
261-
priority = 0,
262-
end_col = column.col,
263-
conceal = '',
264-
})
260+
local delim_column = delim.columns[i]
261+
local filler = delim_column.width - column.width
262+
if delim_column.alignment == 'center' then
263+
local shift = math.floor((filler + column.space.right - column.space.left) / 2)
264+
self:shift(column, 'left', shift)
265+
self:shift(column, 'right', filler - shift)
266+
elseif delim_column.alignment == 'right' then
267+
local shift = column.space.right - self.table.padding
268+
self:shift(column, 'left', filler + shift)
269+
self:shift(column, 'right', -shift)
270+
else
271+
local shift = column.space.left - self.table.padding
272+
self:shift(column, 'left', -shift)
273+
self:shift(column, 'right', filler + shift)
265274
end
266275
end
267276
end
@@ -276,6 +285,28 @@ function Render:row(row)
276285
end
277286
end
278287

288+
---Use low priority to include pipe marks
289+
---@private
290+
---@param column render.md.table.Column
291+
---@param side 'left'|'right'
292+
---@param amount integer
293+
function Render:shift(column, side, amount)
294+
local col = side == 'left' and column.start_col or column.end_col
295+
if amount > 0 then
296+
self.marks:add(true, column.row, col, {
297+
priority = 0,
298+
virt_text = { { str.pad(amount), self.table.filler } },
299+
virt_text_pos = 'inline',
300+
})
301+
elseif amount < 0 then
302+
self.marks:add(true, column.row, col + amount, {
303+
priority = 0,
304+
end_col = col,
305+
conceal = '',
306+
})
307+
end
308+
end
309+
279310
---@private
280311
function Render:full()
281312
local delim, rows, border = self.data.delim, self.data.rows, self.table.border

lua/render-markdown/state.lua

+1-1
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ function M.validate()
210210

211211
get_spec('pipe_table')
212212
:type('enabled', 'boolean')
213-
:type('min_width', 'number')
213+
:type({ 'padding', 'min_width' }, 'number')
214214
:type({ 'alignment_indicator', 'head', 'row', 'filler' }, 'string')
215215
:list('border', 'string')
216216
:one_of('preset', { 'none', 'round', 'double', 'heavy' })

lua/render-markdown/types.lua

+1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
---@field public preset render.md.table.Preset
5858
---@field public style render.md.table.Style
5959
---@field public cell render.md.table.Cell
60+
---@field public padding integer
6061
---@field public min_width integer
6162
---@field public border string[]
6263
---@field public alignment_indicator string

tests/data/table.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Table with Inline
22

33
| Heading 1 | `Heading 2` |
4-
| --------- | ---------------------- |
4+
| --------- | ---------------------: |
55
| `Item 行` | [link](https://行.com) |
66

77
# Table no Inline

0 commit comments

Comments
 (0)