Skip to content

Commit

Permalink
feat: setext heading
Browse files Browse the repository at this point in the history
## 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
  • Loading branch information
MeanderingProgrammer committed Aug 29, 2024
1 parent 8c67dbc commit 27d72d7
Show file tree
Hide file tree
Showing 10 changed files with 176 additions and 54 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ require('render-markdown').setup({
(atx_h5_marker)
(atx_h6_marker)
] @heading)
(setext_heading) @heading
(thematic_break) @dash
Expand Down
3 changes: 2 additions & 1 deletion doc/render-markdown.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
*render-markdown.txt* For 0.10.0 Last change: 2024 August 28
*render-markdown.txt* For 0.10.0 Last change: 2024 August 29

==============================================================================
Table of Contents *render-markdown-table-of-contents*
Expand Down Expand Up @@ -195,6 +195,7 @@ Full Default Configuration ~
(atx_h5_marker)
(atx_h6_marker)
] @heading)
(setext_heading) @heading

(thematic_break) @dash

Expand Down
18 changes: 12 additions & 6 deletions lua/render-markdown/core/context.lua
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,15 @@ function Context:compute_bottom(top, offset)
return bottom
end

---@param info? render.md.NodeInfo
---@return integer
function Context:width(info)
if info == nil then
return 0
end
return str.width(info.text) + self:get_offset(info) - self:concealed(info)
end

---@param info render.md.NodeInfo
---@param amount integer
function Context:add_offset(info, amount)
Expand All @@ -58,6 +67,7 @@ function Context:add_offset(info, amount)
table.insert(self.links[row], { info.start_col, info.end_col, amount })
end

---@private
---@param info render.md.NodeInfo
---@return integer
function Context:get_offset(info)
Expand Down Expand Up @@ -114,10 +124,7 @@ end
---@param info? render.md.NodeInfo
---@return boolean
function Context:hidden(info)
if info == nil then
return true
end
return str.width(info.text) == self:concealed(info)
return info == nil or str.width(info.text) == self:concealed(info)
end

---@param info render.md.NodeInfo
Expand All @@ -127,8 +134,7 @@ function Context:concealed(info)
if #ranges == 0 then
return 0
end
local result = 0
local col = info.start_col
local result, col = 0, info.start_col
for _, index in ipairs(vim.fn.str2list(info.text)) do
local ch = vim.fn.nr2char(index)
for _, range in ipairs(ranges) do
Expand Down
2 changes: 1 addition & 1 deletion lua/render-markdown/health.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ local M = {}

---@private
---@type string
M.version = '6.2.11'
M.version = '6.2.12'

function M.check()
vim.health.start('render-markdown.nvim [version]')
Expand Down
1 change: 1 addition & 0 deletions lua/render-markdown/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ M.default_config = {
(atx_h5_marker)
(atx_h6_marker)
] @heading)
(setext_heading) @heading
(thematic_break) @dash
Expand Down
4 changes: 2 additions & 2 deletions lua/render-markdown/render/code.lua
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ function Render:render()
local disabled_language = vim.tbl_contains(self.code.disable_background, self.data.language)
local add_background = vim.tbl_contains({ 'normal', 'full' }, self.code.style) and not disabled_language

local icon_added = self:language_hint(add_background)
local icon_added = self:language(add_background)
if add_background then
self:background(icon_added)
end
Expand All @@ -84,7 +84,7 @@ end
---@private
---@param add_background boolean
---@return boolean
function Render:language_hint(add_background)
function Render:language(add_background)
if not vim.tbl_contains({ 'language', 'full' }, self.code.style) then
return false
end
Expand Down
139 changes: 99 additions & 40 deletions lua/render-markdown/render/heading.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ local list = require('render-markdown.core.list')
local str = require('render-markdown.core.str')

---@class render.md.data.Heading
---@field atx boolean
---@field level integer
---@field icon? string
---@field sign? string
---@field foreground string
---@field background string
---@field heading_width render.md.heading.Width
---@field end_row integer

---@class render.md.render.Heading: render.md.Renderer
---@field private heading render.md.Heading
Expand All @@ -33,80 +35,77 @@ function Render:setup()
return false
end

local level = str.width(self.info.text)
local atx, level = nil, nil
if self.info.type == 'setext_heading' then
atx, level = false, self.info:child('setext_h1_underline') ~= nil and 1 or 2
else
atx, level = true, str.width(self.info.text)
end

local heading_width = self.heading.width
if type(heading_width) == 'table' then
heading_width = list.clamp(heading_width, level)
end

self.data = {
atx = atx,
level = level,
icon = list.cycle(self.heading.icons, level),
sign = list.cycle(self.heading.signs, level),
foreground = list.clamp(self.heading.foregrounds, level),
background = list.clamp(self.heading.backgrounds, level),
heading_width = heading_width,
end_row = self.info.end_row + (atx and 1 or 0),
}

return true
end

function Render:render()
local icon_width = self:start_icon()
local width = self:width(self:icon())
if self.heading.sign then
self:sign(self.data.sign, self.data.foreground)
end
self:background(width)
self:border(width)
self:left_pad()
self:conceal_underline()
end

self.marks:add(true, self.info.start_row, 0, {
end_row = self.info.end_row + 1,
end_col = 0,
hl_group = self.data.background,
hl_eol = true,
})

local width = self:width(icon_width)
if self.data.heading_width == 'block' then
-- Overwrite anything beyond width with Normal
self.marks:add(true, self.info.start_row, 0, {
priority = 0,
virt_text = { { str.spaces(vim.o.columns * 2), 'Normal' } },
virt_text_win_col = width,
})
end

if self.heading.border then
self:border(width)
end

if self.heading.left_pad > 0 then
self.marks:add(false, self.info.start_row, 0, {
priority = 0,
virt_text = { { str.spaces(self.heading.left_pad), self.data.background } },
---@private
---@return integer
function Render:icon()
if not self.data.atx then
if self.data.icon == nil then
return 0
end
local added = self.marks:add(true, self.info.start_row, self.info.start_col, {
end_row = self.info.end_row,
end_col = self.info.end_col,
virt_text = { { self.data.icon, { self.data.foreground, self.data.background } } },
virt_text_pos = 'inline',
})
return added and str.width(self.data.icon) or 0
end
end

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

local padding = width - str.width(self.data.icon)
if self.heading.position == 'inline' or padding < 0 then
self.marks:add(true, self.info.start_row, self.info.start_col, {
local added = self.marks:add(true, self.info.start_row, self.info.start_col, {
end_row = self.info.end_row,
end_col = self.info.end_col,
virt_text = { { self.data.icon, { self.data.foreground, self.data.background } } },
virt_text_pos = 'inline',
conceal = '',
})
return str.width(self.data.icon)
return added and str.width(self.data.icon) or width
else
self.marks:add(true, self.info.start_row, self.info.start_col, {
end_row = self.info.end_row,
Expand All @@ -123,20 +122,50 @@ end
---@return integer
function Render:width(icon_width)
if self.data.heading_width == 'block' then
local width = self.heading.left_pad + icon_width + self.heading.right_pad
local content = self.info:sibling('inline')
if content ~= nil then
width = width + str.width(content.text) + self.context:get_offset(content) - self.context:concealed(content)
local width = nil
if self.data.atx then
width = icon_width + self.context:width(self.info:sibling('inline'))
else
-- Account for icon in first row
local widths = vim.tbl_map(str.width, self.info:lines())
widths[1] = widths[1] + icon_width
width = vim.fn.max(widths)
end
width = self.heading.left_pad + width + self.heading.right_pad
return math.max(width, self.heading.min_width)
else
return self.context:get_width()
end
end

---@private
---@param width integer
function Render:background(width)
for row = self.info.start_row, self.data.end_row - 1 do
self.marks:add(true, row, 0, {
end_row = row + 1,
hl_group = self.data.background,
hl_eol = true,
})
if self.data.heading_width == 'block' then
-- Overwrite anything beyond width with Normal
self.marks:add(true, row, 0, {
priority = 0,
virt_text = { { str.spaces(vim.o.columns * 2), 'Normal' } },
virt_text_win_col = width,
})
end
end
end

---@private
---@param width integer
function Render:border(width)
-- Only atx headings support borders
if not self.heading.border or not self.data.atx then
return
end

local background = colors.inverse(self.data.background)
local prefix = self.heading.border_prefix and self.data.level or 0

Expand Down Expand Up @@ -175,4 +204,34 @@ function Render:border(width)
end
end

---@private
function Render:left_pad()
if self.heading.left_pad <= 0 then
return
end
for row = self.info.start_row, self.data.end_row - 1 do
self.marks:add(false, row, 0, {
priority = 0,
virt_text = { { str.spaces(self.heading.left_pad), self.data.background } },
virt_text_pos = 'inline',
})
end
end

---@private
function Render:conceal_underline()
if self.data.atx then
return
end
local info = self.info:child(string.format('setext_h%d_underline', self.data.level))
if info == nil then
return
end
self.marks:add(true, info.start_row, info.start_col, {
end_row = info.end_row,
end_col = info.end_col,
conceal = '',
})
end

return Render
6 changes: 2 additions & 4 deletions lua/render-markdown/render/table.lua
Original file line number Diff line number Diff line change
Expand Up @@ -136,10 +136,8 @@ function Render:parse_row(row, num_columns)
local columns = {}
for i = 1, #cells do
local cell, width = cells[i], pipes[i + 1].start_col - pipes[i].end_col
-- Account for double width glyphs by replacing cell spacing with text width
width = width - (cell.end_col - cell.start_col) + str.width(cell.text)
-- Remove concealed and add inlined text
width = width - self.context:concealed(cell) + self.context:get_offset(cell)
-- Account for double width glyphs by replacing cell spacing with width
width = width - (cell.end_col - cell.start_col) + self.context:width(cell)
if width < 0 then
return nil
end
Expand Down
Loading

0 comments on commit 27d72d7

Please sign in to comment.