Skip to content

Commit 27d72d7

Browse files
feat: setext heading
## Details Adds support for the alternative markdown setext headings. Uses the same settings from the existing heading configuration. Some refactoring needed to happen to do this effectively since these headings can be any number of lines so per row for loops were added in a few places. Supports all the same features and behaves in the same way as atx headings with 2 exceptions: - borders are not supported: this seems redundant to do since setext headings always have a bottom border effectively - bottom text is concealed: the bottom line of these headings always has either equals signs or dashes, to make things look prettier this line gets concealed, can be replaced in the future with something else if users ask
1 parent 8c67dbc commit 27d72d7

File tree

10 files changed

+176
-54
lines changed

10 files changed

+176
-54
lines changed

Diff for: README.md

+1
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ require('render-markdown').setup({
166166
(atx_h5_marker)
167167
(atx_h6_marker)
168168
] @heading)
169+
(setext_heading) @heading
169170
170171
(thematic_break) @dash
171172

Diff for: doc/render-markdown.txt

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
*render-markdown.txt* For 0.10.0 Last change: 2024 August 28
1+
*render-markdown.txt* For 0.10.0 Last change: 2024 August 29
22

33
==============================================================================
44
Table of Contents *render-markdown-table-of-contents*
@@ -195,6 +195,7 @@ Full Default Configuration ~
195195
(atx_h5_marker)
196196
(atx_h6_marker)
197197
] @heading)
198+
(setext_heading) @heading
198199

199200
(thematic_break) @dash
200201

Diff for: lua/render-markdown/core/context.lua

+12-6
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,15 @@ function Context:compute_bottom(top, offset)
4848
return bottom
4949
end
5050

51+
---@param info? render.md.NodeInfo
52+
---@return integer
53+
function Context:width(info)
54+
if info == nil then
55+
return 0
56+
end
57+
return str.width(info.text) + self:get_offset(info) - self:concealed(info)
58+
end
59+
5160
---@param info render.md.NodeInfo
5261
---@param amount integer
5362
function Context:add_offset(info, amount)
@@ -58,6 +67,7 @@ function Context:add_offset(info, amount)
5867
table.insert(self.links[row], { info.start_col, info.end_col, amount })
5968
end
6069

70+
---@private
6171
---@param info render.md.NodeInfo
6272
---@return integer
6373
function Context:get_offset(info)
@@ -114,10 +124,7 @@ end
114124
---@param info? render.md.NodeInfo
115125
---@return boolean
116126
function Context:hidden(info)
117-
if info == nil then
118-
return true
119-
end
120-
return str.width(info.text) == self:concealed(info)
127+
return info == nil or str.width(info.text) == self:concealed(info)
121128
end
122129

123130
---@param info render.md.NodeInfo
@@ -127,8 +134,7 @@ function Context:concealed(info)
127134
if #ranges == 0 then
128135
return 0
129136
end
130-
local result = 0
131-
local col = info.start_col
137+
local result, col = 0, info.start_col
132138
for _, index in ipairs(vim.fn.str2list(info.text)) do
133139
local ch = vim.fn.nr2char(index)
134140
for _, range in ipairs(ranges) do

Diff for: lua/render-markdown/health.lua

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ local M = {}
55

66
---@private
77
---@type string
8-
M.version = '6.2.11'
8+
M.version = '6.2.12'
99

1010
function M.check()
1111
vim.health.start('render-markdown.nvim [version]')

Diff for: lua/render-markdown/init.lua

+1
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ M.default_config = {
214214
(atx_h5_marker)
215215
(atx_h6_marker)
216216
] @heading)
217+
(setext_heading) @heading
217218
218219
(thematic_break) @dash
219220

Diff for: lua/render-markdown/render/code.lua

+2-2
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ function Render:render()
7171
local disabled_language = vim.tbl_contains(self.code.disable_background, self.data.language)
7272
local add_background = vim.tbl_contains({ 'normal', 'full' }, self.code.style) and not disabled_language
7373

74-
local icon_added = self:language_hint(add_background)
74+
local icon_added = self:language(add_background)
7575
if add_background then
7676
self:background(icon_added)
7777
end
@@ -84,7 +84,7 @@ end
8484
---@private
8585
---@param add_background boolean
8686
---@return boolean
87-
function Render:language_hint(add_background)
87+
function Render:language(add_background)
8888
if not vim.tbl_contains({ 'language', 'full' }, self.code.style) then
8989
return false
9090
end

Diff for: lua/render-markdown/render/heading.lua

+99-40
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ local list = require('render-markdown.core.list')
44
local str = require('render-markdown.core.str')
55

66
---@class render.md.data.Heading
7+
---@field atx boolean
78
---@field level integer
89
---@field icon? string
910
---@field sign? string
1011
---@field foreground string
1112
---@field background string
1213
---@field heading_width render.md.heading.Width
14+
---@field end_row integer
1315

1416
---@class render.md.render.Heading: render.md.Renderer
1517
---@field private heading render.md.Heading
@@ -33,80 +35,77 @@ function Render:setup()
3335
return false
3436
end
3537

36-
local level = str.width(self.info.text)
38+
local atx, level = nil, nil
39+
if self.info.type == 'setext_heading' then
40+
atx, level = false, self.info:child('setext_h1_underline') ~= nil and 1 or 2
41+
else
42+
atx, level = true, str.width(self.info.text)
43+
end
44+
3745
local heading_width = self.heading.width
3846
if type(heading_width) == 'table' then
3947
heading_width = list.clamp(heading_width, level)
4048
end
49+
4150
self.data = {
51+
atx = atx,
4252
level = level,
4353
icon = list.cycle(self.heading.icons, level),
4454
sign = list.cycle(self.heading.signs, level),
4555
foreground = list.clamp(self.heading.foregrounds, level),
4656
background = list.clamp(self.heading.backgrounds, level),
4757
heading_width = heading_width,
58+
end_row = self.info.end_row + (atx and 1 or 0),
4859
}
4960

5061
return true
5162
end
5263

5364
function Render:render()
54-
local icon_width = self:start_icon()
65+
local width = self:width(self:icon())
5566
if self.heading.sign then
5667
self:sign(self.data.sign, self.data.foreground)
5768
end
69+
self:background(width)
70+
self:border(width)
71+
self:left_pad()
72+
self:conceal_underline()
73+
end
5874

59-
self.marks:add(true, self.info.start_row, 0, {
60-
end_row = self.info.end_row + 1,
61-
end_col = 0,
62-
hl_group = self.data.background,
63-
hl_eol = true,
64-
})
65-
66-
local width = self:width(icon_width)
67-
if self.data.heading_width == 'block' then
68-
-- Overwrite anything beyond width with Normal
69-
self.marks:add(true, self.info.start_row, 0, {
70-
priority = 0,
71-
virt_text = { { str.spaces(vim.o.columns * 2), 'Normal' } },
72-
virt_text_win_col = width,
73-
})
74-
end
75-
76-
if self.heading.border then
77-
self:border(width)
78-
end
79-
80-
if self.heading.left_pad > 0 then
81-
self.marks:add(false, self.info.start_row, 0, {
82-
priority = 0,
83-
virt_text = { { str.spaces(self.heading.left_pad), self.data.background } },
75+
---@private
76+
---@return integer
77+
function Render:icon()
78+
if not self.data.atx then
79+
if self.data.icon == nil then
80+
return 0
81+
end
82+
local added = self.marks:add(true, self.info.start_row, self.info.start_col, {
83+
end_row = self.info.end_row,
84+
end_col = self.info.end_col,
85+
virt_text = { { self.data.icon, { self.data.foreground, self.data.background } } },
8486
virt_text_pos = 'inline',
8587
})
88+
return added and str.width(self.data.icon) or 0
8689
end
87-
end
8890

89-
---@private
90-
---@return integer
91-
function Render:start_icon()
92-
-- Available width is level + 1 - concealed, where level = number of `#` characters, one
93-
-- is added to account for the space after the last `#` but before the heading title,
94-
-- and concealed text is subtracted since that space is not usable
91+
-- For atx headings available width is level + 1 - concealed, where level = number of
92+
-- `#` characters, one is added to account for the space after the last `#` but before
93+
-- the heading title, and concealed text is subtracted since that space is not usable
9594
local width = self.data.level + 1 - self.context:concealed(self.info)
9695
if self.data.icon == nil then
9796
return width
9897
end
9998

10099
local padding = width - str.width(self.data.icon)
101100
if self.heading.position == 'inline' or padding < 0 then
102-
self.marks:add(true, self.info.start_row, self.info.start_col, {
101+
local added = self.marks:add(true, self.info.start_row, self.info.start_col, {
103102
end_row = self.info.end_row,
104103
end_col = self.info.end_col,
105104
virt_text = { { self.data.icon, { self.data.foreground, self.data.background } } },
106105
virt_text_pos = 'inline',
107106
conceal = '',
108107
})
109-
return str.width(self.data.icon)
108+
return added and str.width(self.data.icon) or width
110109
else
111110
self.marks:add(true, self.info.start_row, self.info.start_col, {
112111
end_row = self.info.end_row,
@@ -123,20 +122,50 @@ end
123122
---@return integer
124123
function Render:width(icon_width)
125124
if self.data.heading_width == 'block' then
126-
local width = self.heading.left_pad + icon_width + self.heading.right_pad
127-
local content = self.info:sibling('inline')
128-
if content ~= nil then
129-
width = width + str.width(content.text) + self.context:get_offset(content) - self.context:concealed(content)
125+
local width = nil
126+
if self.data.atx then
127+
width = icon_width + self.context:width(self.info:sibling('inline'))
128+
else
129+
-- Account for icon in first row
130+
local widths = vim.tbl_map(str.width, self.info:lines())
131+
widths[1] = widths[1] + icon_width
132+
width = vim.fn.max(widths)
130133
end
134+
width = self.heading.left_pad + width + self.heading.right_pad
131135
return math.max(width, self.heading.min_width)
132136
else
133137
return self.context:get_width()
134138
end
135139
end
136140

141+
---@private
142+
---@param width integer
143+
function Render:background(width)
144+
for row = self.info.start_row, self.data.end_row - 1 do
145+
self.marks:add(true, row, 0, {
146+
end_row = row + 1,
147+
hl_group = self.data.background,
148+
hl_eol = true,
149+
})
150+
if self.data.heading_width == 'block' then
151+
-- Overwrite anything beyond width with Normal
152+
self.marks:add(true, row, 0, {
153+
priority = 0,
154+
virt_text = { { str.spaces(vim.o.columns * 2), 'Normal' } },
155+
virt_text_win_col = width,
156+
})
157+
end
158+
end
159+
end
160+
137161
---@private
138162
---@param width integer
139163
function Render:border(width)
164+
-- Only atx headings support borders
165+
if not self.heading.border or not self.data.atx then
166+
return
167+
end
168+
140169
local background = colors.inverse(self.data.background)
141170
local prefix = self.heading.border_prefix and self.data.level or 0
142171

@@ -175,4 +204,34 @@ function Render:border(width)
175204
end
176205
end
177206

207+
---@private
208+
function Render:left_pad()
209+
if self.heading.left_pad <= 0 then
210+
return
211+
end
212+
for row = self.info.start_row, self.data.end_row - 1 do
213+
self.marks:add(false, row, 0, {
214+
priority = 0,
215+
virt_text = { { str.spaces(self.heading.left_pad), self.data.background } },
216+
virt_text_pos = 'inline',
217+
})
218+
end
219+
end
220+
221+
---@private
222+
function Render:conceal_underline()
223+
if self.data.atx then
224+
return
225+
end
226+
local info = self.info:child(string.format('setext_h%d_underline', self.data.level))
227+
if info == nil then
228+
return
229+
end
230+
self.marks:add(true, info.start_row, info.start_col, {
231+
end_row = info.end_row,
232+
end_col = info.end_col,
233+
conceal = '',
234+
})
235+
end
236+
178237
return Render

Diff for: lua/render-markdown/render/table.lua

+2-4
Original file line numberDiff line numberDiff line change
@@ -136,10 +136,8 @@ function Render:parse_row(row, num_columns)
136136
local columns = {}
137137
for i = 1, #cells do
138138
local cell, width = cells[i], pipes[i + 1].start_col - pipes[i].end_col
139-
-- Account for double width glyphs by replacing cell spacing with text width
140-
width = width - (cell.end_col - cell.start_col) + str.width(cell.text)
141-
-- Remove concealed and add inlined text
142-
width = width - self.context:concealed(cell) + self.context:get_offset(cell)
139+
-- Account for double width glyphs by replacing cell spacing with width
140+
width = width - (cell.end_col - cell.start_col) + self.context:width(cell)
143141
if width < 0 then
144142
return nil
145143
end

0 commit comments

Comments
 (0)