Skip to content

Commit 3114d70

Browse files
feature: Add border style to code blocks, use thin by default
## Details Request: #62 Rather than using a highlight group for the entire range of code blocks allow half height characters to be used when start and end of code block are entirely concealed. This involves using the inverse of the code highlight group and repeating the configured characters across the width. Allow using the original implementation with `{ border = 'thick' }`. When rendering the body we now need to be aware of how the language was rendered, since placing a language above the code block interferes with the half height row. To fix this we need to handle the language from the top level code block logic, instead of using 2 separate capture groups for code block & language. Add sign configuration to README as well.
1 parent 5ce3566 commit 3114d70

File tree

15 files changed

+268
-86
lines changed

15 files changed

+268
-86
lines changed

README.md

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,6 @@ require('render-markdown').setup({
120120
(thematic_break) @dash
121121
122122
(fenced_code_block) @code
123-
(fenced_code_block (info_string (language) @language))
124123
125124
[
126125
(list_marker_plus)
@@ -206,6 +205,14 @@ require('render-markdown').setup({
206205
-- language: adds language icon to sign column and icon + name above code blocks
207206
-- full: normal + language
208207
style = 'full',
208+
-- Determins how the top / bottom of code block are rendered:
209+
-- thick: use the same highlight as the code body
210+
-- thin: when lines are empty overlay the above & below icons
211+
border = 'thin',
212+
-- Used above code blocks for thin border
213+
above = '',
214+
-- Used below code blocks for thin border
215+
below = '',
209216
-- Highlight for code blocks & inline code
210217
highlight = 'ColorColumn',
211218
},
@@ -414,6 +421,14 @@ require('render-markdown').setup({
414421
-- language: adds language icon to sign column and icon + name above code blocks
415422
-- full: normal + language
416423
style = 'full',
424+
-- Determins how the top / bottom of code block are rendered:
425+
-- thick: use the same highlight as the code body
426+
-- thin: when lines are empty overlay the above & below icons
427+
border = 'thin',
428+
-- Used above code blocks for thin border
429+
above = '',
430+
-- Used below code blocks for thin border
431+
below = '',
417432
-- Highlight for code blocks & inline code
418433
highlight = 'ColorColumn',
419434
},
@@ -587,6 +602,23 @@ require('render-markdown').setup({
587602
})
588603
```
589604

605+
## Signs
606+
607+
```lua
608+
require('render-markdown').setup({
609+
sign = {
610+
-- Turn on / off sign rendering
611+
enabled = true,
612+
-- More granular mechanism, disable signs within specific buftypes
613+
exclude = {
614+
buftypes = { 'nofile' },
615+
},
616+
-- Applies to background of sign text
617+
highlight = 'SignColumn',
618+
},
619+
})
620+
```
621+
590622
# Additional Info
591623

592624
- [Limitations](doc/limitations.md): Known limitations of this plugin

demo/callout.gif

-8.25 KB
Loading

demo/heading_code.gif

19.3 KB
Loading

doc/render-markdown.txt

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
*render-markdown.txt* For 0.10.0 Last change: 2024 July 12
1+
*render-markdown.txt* For 0.10.0 Last change: 2024 July 13
22

33
==============================================================================
44
Table of Contents *render-markdown-table-of-contents*
@@ -20,6 +20,7 @@ Table of Contents *render-markdown-table-of-contents*
2020
- Tables |render-markdown-setup-tables|
2121
- Callouts |render-markdown-setup-callouts|
2222
- Links |render-markdown-setup-links|
23+
- Signs |render-markdown-setup-signs|
2324
7. Additional Info |render-markdown-additional-info|
2425

2526
==============================================================================
@@ -157,7 +158,6 @@ Full Default Configuration ~
157158
(thematic_break) @dash
158159

159160
(fenced_code_block) @code
160-
(fenced_code_block (info_string (language) @language))
161161

162162
[
163163
(list_marker_plus)
@@ -243,6 +243,14 @@ Full Default Configuration ~
243243
-- language: adds language icon to sign column and icon + name above code blocks
244244
-- full: normal + language
245245
style = 'full',
246+
-- Determins how the top / bottom of code block are rendered:
247+
-- thick: use the same highlight as the code body
248+
-- thin: when lines are empty overlay the above & below icons
249+
border = 'thin',
250+
-- Used above code blocks for thin border
251+
above = '▄',
252+
-- Used below code blocks for thin border
253+
below = '▀',
246254
-- Highlight for code blocks & inline code
247255
highlight = 'ColorColumn',
248256
},
@@ -451,6 +459,14 @@ CODE BLOCKS *render-markdown-setup-code-blocks*
451459
-- language: adds language icon to sign column and icon + name above code blocks
452460
-- full: normal + language
453461
style = 'full',
462+
-- Determins how the top / bottom of code block are rendered:
463+
-- thick: use the same highlight as the code body
464+
-- thin: when lines are empty overlay the above & below icons
465+
border = 'thin',
466+
-- Used above code blocks for thin border
467+
above = '▄',
468+
-- Used below code blocks for thin border
469+
below = '▀',
454470
-- Highlight for code blocks & inline code
455471
highlight = 'ColorColumn',
456472
},
@@ -632,6 +648,24 @@ LINKS *render-markdown-setup-links*
632648
<
633649

634650

651+
SIGNS *render-markdown-setup-signs*
652+
653+
>lua
654+
require('render-markdown').setup({
655+
sign = {
656+
-- Turn on / off sign rendering
657+
enabled = true,
658+
-- More granular mechanism, disable signs within specific buftypes
659+
exclude = {
660+
buftypes = { 'nofile' },
661+
},
662+
-- Applies to background of sign text
663+
highlight = 'SignColumn',
664+
},
665+
})
666+
<
667+
668+
635669
==============================================================================
636670
7. Additional Info *render-markdown-additional-info*
637671

lua/render-markdown/colors.lua

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,18 @@ M.combine = function(foreground, background)
2323
return name
2424
end
2525

26+
---@param highlight string
27+
---@return string
28+
M.inverse = function(highlight)
29+
local name = string.format('RenderMd_Inverse_%s', highlight)
30+
if not vim.tbl_contains(cache.highlights, name) then
31+
local hl = M.get_hl(highlight)
32+
vim.api.nvim_set_hl(0, name, { fg = hl.bg, bg = hl.fg })
33+
table.insert(cache.highlights, name)
34+
end
35+
return name
36+
end
37+
2638
---@private
2739
---@param name string
2840
---@return vim.api.keyset.hl_info

lua/render-markdown/handler/markdown.lua

Lines changed: 102 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -80,82 +80,28 @@ M.render_node = function(namespace, buf, capture, node)
8080
if not dash.enabled then
8181
return
8282
end
83-
local width = vim.api.nvim_win_get_width(util.buf_to_win(buf))
8483
vim.api.nvim_buf_set_extmark(buf, namespace, info.start_row, 0, {
85-
virt_text = { { dash.icon:rep(width), dash.highlight } },
84+
virt_text = { { dash.icon:rep(util.get_width(buf)), dash.highlight } },
8685
virt_text_pos = 'overlay',
8786
})
8887
elseif capture == 'code' then
89-
local code = state.config.code
90-
if not code.enabled then
91-
return
92-
end
93-
if not vim.tbl_contains({ 'normal', 'full' }, code.style) then
94-
return
95-
end
96-
vim.api.nvim_buf_set_extmark(buf, namespace, info.start_row, 0, {
97-
end_row = info.end_row,
98-
end_col = 0,
99-
hl_group = code.highlight,
100-
hl_eol = true,
101-
})
102-
elseif capture == 'language' then
103-
local code = state.config.code
104-
if not code.enabled then
105-
return
106-
end
107-
if not vim.tbl_contains({ 'language', 'full' }, code.style) then
108-
return
109-
end
110-
local icon, icon_highlight = icons.get(info.text)
111-
if icon == nil or icon_highlight == nil then
112-
return
113-
end
114-
M.render_sign(namespace, buf, info, icon, icon_highlight)
115-
-- Requires inline extmarks
116-
if not util.has_10 then
117-
return
118-
end
119-
120-
local icon_text = icon .. ' '
121-
if ts.concealed(buf, info) > 0 then
122-
-- Fenced code blocks will pick up varying amounts of leading white space depending on
123-
-- the context they are in. This gets lumped into the delimiter node and as a result,
124-
-- after concealing, the extmark will be left shifted. Logic below accounts for this.
125-
local padding = 0
126-
local code_block = ts.parent_in_section(info.node, 'fenced_code_block')
127-
if code_block ~= nil then
128-
padding = str.leading_spaces(ts.info(code_block, buf).text)
129-
end
130-
icon_text = str.pad(icon_text .. info.text, padding)
131-
end
132-
133-
local highlight = { icon_highlight }
134-
if code.style == 'full' then
135-
highlight = { icon_highlight, code.highlight }
136-
end
137-
138-
vim.api.nvim_buf_set_extmark(buf, namespace, info.start_row, info.start_col, {
139-
virt_text = { { icon_text, highlight } },
140-
virt_text_pos = 'inline',
141-
})
88+
M.render_code(namespace, buf, info)
14289
elseif capture == 'list_marker' then
14390
---@return boolean
14491
local function sibling_checkbox()
14592
if not state.config.checkbox.enabled then
14693
return false
14794
end
148-
if ts.sibling(info.node, 'task_list_marker_unchecked') ~= nil then
95+
if ts.sibling(buf, info, 'task_list_marker_unchecked') ~= nil then
14996
return true
15097
end
151-
if ts.sibling(info.node, 'task_list_marker_checked') ~= nil then
98+
if ts.sibling(buf, info, 'task_list_marker_checked') ~= nil then
15299
return true
153100
end
154-
local paragraph_node = ts.sibling(info.node, 'paragraph')
155-
if paragraph_node == nil then
101+
local paragraph = ts.sibling(buf, info, 'paragraph')
102+
if paragraph == nil then
156103
return false
157104
end
158-
local paragraph = ts.info(paragraph_node, buf)
159105
return component.checkbox(paragraph.text, 'starts') ~= nil
160106
end
161107

@@ -175,7 +121,7 @@ M.render_node = function(namespace, buf, capture, node)
175121
-- edge cases in the parser: https://github.com/tree-sitter-grammars/tree-sitter-markdown/issues/127
176122
-- As a result we handle leading spaces here, can remove if this gets fixed upstream
177123
local leading_spaces = str.leading_spaces(info.text)
178-
local level = ts.level_in_section(info.node, 'list')
124+
local level = ts.level_in_section(info, 'list')
179125
local icon = list.cycle(bullet.icons, level)
180126

181127
vim.api.nvim_buf_set_extmark(buf, namespace, info.start_row, info.start_col, {
@@ -196,9 +142,9 @@ M.render_node = function(namespace, buf, capture, node)
196142
return
197143
end
198144
local highlight = quote.highlight
199-
local quote_node = ts.parent_in_section(info.node, 'block_quote')
200-
if quote_node ~= nil then
201-
local callout = component.callout(ts.info(quote_node, buf).text, 'contains')
145+
local block_quote = ts.parent_in_section(buf, info, 'block_quote')
146+
if block_quote ~= nil then
147+
local callout = component.callout(block_quote.text, 'contains')
202148
if callout ~= nil then
203149
highlight = callout.highlight
204150
end
@@ -232,6 +178,98 @@ M.render_node = function(namespace, buf, capture, node)
232178
end
233179
end
234180

181+
---@param namespace integer
182+
---@param buf integer
183+
---@param info render.md.NodeInfo
184+
M.render_code = function(namespace, buf, info)
185+
local code = state.config.code
186+
if not code.enabled then
187+
return
188+
end
189+
if code.style == 'none' then
190+
return
191+
end
192+
local did_render_language = false
193+
local code_info = ts.child(buf, info, 'info_string', info.start_row)
194+
if code_info ~= nil then
195+
local language_info = ts.child(buf, code_info, 'language', code_info.start_row)
196+
if language_info ~= nil then
197+
did_render_language = M.render_language(namespace, buf, language_info, info)
198+
end
199+
end
200+
if not vim.tbl_contains({ 'normal', 'full' }, code.style) then
201+
return
202+
end
203+
local start_row = info.start_row
204+
local end_row = info.end_row
205+
-- Do not attempt to render single line code block
206+
if start_row == end_row - 1 then
207+
return
208+
end
209+
if code.border == 'thin' then
210+
local code_start = ts.child(buf, info, 'fenced_code_block_delimiter', info.start_row)
211+
local code_end = ts.child(buf, info, 'fenced_code_block_delimiter', info.end_row - 1)
212+
if not did_render_language and ts.hidden(buf, code_info) and ts.hidden(buf, code_start) then
213+
start_row = start_row + 1
214+
vim.api.nvim_buf_set_extmark(buf, namespace, info.start_row, info.start_col, {
215+
virt_text = { { code.above:rep(util.get_width(buf)), colors.inverse(code.highlight) } },
216+
virt_text_pos = 'overlay',
217+
})
218+
end
219+
if ts.hidden(buf, code_end) then
220+
end_row = end_row - 1
221+
vim.api.nvim_buf_set_extmark(buf, namespace, info.end_row - 1, info.start_col, {
222+
virt_text = { { code.below:rep(util.get_width(buf)), colors.inverse(code.highlight) } },
223+
virt_text_pos = 'overlay',
224+
})
225+
end
226+
end
227+
vim.api.nvim_buf_set_extmark(buf, namespace, start_row, 0, {
228+
end_row = end_row,
229+
end_col = 0,
230+
hl_group = code.highlight,
231+
hl_eol = true,
232+
})
233+
end
234+
235+
---@param namespace integer
236+
---@param buf integer
237+
---@param info render.md.NodeInfo
238+
---@param code_block render.md.NodeInfo
239+
---@return boolean
240+
M.render_language = function(namespace, buf, info, code_block)
241+
local code = state.config.code
242+
if not vim.tbl_contains({ 'language', 'full' }, code.style) then
243+
return false
244+
end
245+
local icon, icon_highlight = icons.get(info.text)
246+
if icon == nil or icon_highlight == nil then
247+
return false
248+
end
249+
M.render_sign(namespace, buf, info, icon, icon_highlight)
250+
-- Requires inline extmarks
251+
if not util.has_10 then
252+
return false
253+
end
254+
local icon_text = icon .. ' '
255+
if ts.hidden(buf, info) then
256+
-- Code blocks will pick up varying amounts of leading white space depending on the
257+
-- context they are in. This gets lumped into the delimiter node and as a result,
258+
-- after concealing, the extmark will be left shifted. Logic below accounts for this.
259+
local padding = str.leading_spaces(code_block.text)
260+
icon_text = str.pad(icon_text .. info.text, padding)
261+
end
262+
local highlight = { icon_highlight }
263+
if code.style == 'full' then
264+
highlight = { icon_highlight, code.highlight }
265+
end
266+
vim.api.nvim_buf_set_extmark(buf, namespace, info.start_row, info.start_col, {
267+
virt_text = { { icon_text, highlight } },
268+
virt_text_pos = 'inline',
269+
})
270+
return true
271+
end
272+
235273
---@param namespace integer
236274
---@param buf integer
237275
---@param info render.md.NodeInfo

0 commit comments

Comments
 (0)