Skip to content

Commit 21623a9

Browse files
feat: indent mode icon
## Details Request: #343 Adds `icon` & `highlight` values to `indent` configuration. By default the `icon` is set to the empty string resulting in no change. However when an `icon` is provided, for example: ```lua require('render-markdown').setup({ indent = { enabled = true, per_level = 4, icon = '▎', }, }) ``` We end up with behavior similar to `indent-blankline.nvim`, where each set of 4 spaces starts with the configured icon, giving a quick visual of how far indented the current section is. The icon is not limited to one character, but users will likely choose one that is, otherwise things get cluttered rather quickly.
1 parent 43a971e commit 21623a9

File tree

14 files changed

+126
-85
lines changed

14 files changed

+126
-85
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
[17a7746](https://github.com/MeanderingProgrammer/render-markdown.nvim/commit/17a77463f945c4b9e4f371c752efd90e3e1bf604)
1515
- update troubleshooting doc [f6c9e18](https://github.com/MeanderingProgrammer/render-markdown.nvim/commit/f6c9e1841cf644a258eb037dae587e3cf407d696)
1616
- update `lazy` preset to match `LazyVim` [4a28c13](https://github.com/MeanderingProgrammer/render-markdown.nvim/commit/4a28c135bc3548e398ba38178fec3f705cb26fe6)
17+
- latex position below [#347](https://github.com/MeanderingProgrammer/render-markdown.nvim/issues/347)
18+
[43a971e](https://github.com/MeanderingProgrammer/render-markdown.nvim/commit/43a971e7da82e5622797b36450424ebd66cc9046)
1719

1820
### Bug Fixes
1921

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -680,6 +680,10 @@ require('render-markdown').setup({
680680
skip_level = 1,
681681
-- Do not indent heading titles, only the body
682682
skip_heading = false,
683+
-- Prefix added when indenting, one per level
684+
icon = '',
685+
-- Applied to icon
686+
highlight = 'RenderMarkdownIndent',
683687
},
684688
html = {
685689
-- Turn on / off all HTML rendering
@@ -1336,6 +1340,10 @@ require('render-markdown').setup({
13361340
skip_level = 1,
13371341
-- Do not indent heading titles, only the body
13381342
skip_heading = false,
1343+
-- Prefix added when indenting, one per level
1344+
icon = '',
1345+
-- Applied to icon
1346+
highlight = 'RenderMarkdownIndent',
13391347
},
13401348
})
13411349
```
@@ -1368,6 +1376,7 @@ The table below shows all the highlight groups with their default link
13681376
| RenderMarkdownDash | LineNr | Thematic break line |
13691377
| RenderMarkdownSign | SignColumn | Sign column background |
13701378
| RenderMarkdownMath | @markup.math | LaTeX lines |
1379+
| RenderMarkdownIndent | Whitespace | Indent icon |
13711380
| RenderMarkdownHtmlComment | @comment | HTML comment inline text |
13721381
| RenderMarkdownLink | @markup.link.label.markdown_inline | Image & hyperlink icons |
13731382
| RenderMarkdownUncycloLink | RenderMarkdownLink | UncycloLink icon & text |

doc/render-markdown.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -741,6 +741,10 @@ Default Configuration ~
741741
skip_level = 1,
742742
-- Do not indent heading titles, only the body
743743
skip_heading = false,
744+
-- Prefix added when indenting, one per level
745+
icon = '',
746+
-- Applied to icon
747+
highlight = 'RenderMarkdownIndent',
744748
},
745749
html = {
746750
-- Turn on / off all HTML rendering
@@ -1373,6 +1377,10 @@ Indent Configuration ~
13731377
skip_level = 1,
13741378
-- Do not indent heading titles, only the body
13751379
skip_heading = false,
1380+
-- Prefix added when indenting, one per level
1381+
icon = '',
1382+
-- Applied to icon
1383+
highlight = 'RenderMarkdownIndent',
13761384
},
13771385
})
13781386
<
@@ -1431,6 +1439,8 @@ The table below shows all the highlight groups with their default link
14311439

14321440
RenderMarkdownMath @markup.math LaTeX lines
14331441

1442+
RenderMarkdownIndent Whitespace Indent icon
1443+
14341444
RenderMarkdownHtmlComment @comment HTML comment inline
14351445
text
14361446

lua/render-markdown/colors.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ M.colors = {
3838
Dash = 'LineNr',
3939
Sign = 'SignColumn',
4040
Math = '@markup.math',
41+
Indent = 'Whitespace',
4142
HtmlComment = '@comment',
4243
-- Links
4344
Link = '@markup.link.label.markdown_inline',

lua/render-markdown/health.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ local state = require('render-markdown.state')
55
local M = {}
66

77
---@private
8-
M.version = '8.0.8'
8+
M.version = '8.0.9'
99

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

lua/render-markdown/init.lua

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ local M = {}
6262
---@field public per_level? integer
6363
---@field public skip_level? integer
6464
---@field public skip_heading? boolean
65+
---@field public icon? string
66+
---@field public highlight? string
6567

6668
---@class (exact) render.md.UserInlineHighlight: render.md.UserBaseComponent
6769
---@field public highlight? string
@@ -814,6 +816,10 @@ M.default_config = {
814816
skip_level = 1,
815817
-- Do not indent heading titles, only the body
816818
skip_heading = false,
819+
-- Prefix added when indenting, one per level
820+
icon = '',
821+
-- Applied to icon
822+
highlight = 'RenderMarkdownIndent',
817823
},
818824
html = {
819825
-- Turn on / off all HTML rendering

lua/render-markdown/render/base.lua

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@ local Iter = require('render-markdown.lib.iter')
22
local Str = require('render-markdown.lib.str')
33
local colors = require('render-markdown.colors')
44

5+
---@class render.md.line.Text
6+
---@field [1] string text
7+
---@field [2] string|string[] highlights
8+
9+
---@alias render.md.Line render.md.line.Text[]
10+
511
---@class render.md.Renderer
612
---@field protected marks render.md.Marks
713
---@field protected config render.md.buffer.Config
@@ -75,42 +81,64 @@ function Base:from_destination(icon, highlight, destination)
7581
end
7682

7783
---@protected
78-
---@param line { [1]: string, [2]: string }[]
84+
---@param virtual boolean
7985
---@param level? integer
80-
---@return { [1]: string, [2]: string }[]
81-
function Base:indent_virt_line(line, level)
82-
local amount = self:indent(level)
83-
if amount > 0 then
84-
table.insert(line, 1, self:padding_text(amount))
86+
---@return render.md.Line
87+
function Base:indent_line(virtual, level)
88+
if virtual then
89+
level = self:indent_level(level)
90+
else
91+
assert(level ~= nil, 'Level must be known for real lines')
92+
end
93+
local line = {}
94+
if level > 0 then
95+
local indent = self.config.indent
96+
local icon_width = Str.width(indent.icon)
97+
if icon_width == 0 then
98+
table.insert(line, self:padding_text(indent.per_level * level))
99+
else
100+
for _ = 1, level do
101+
table.insert(line, { indent.icon, indent.highlight })
102+
table.insert(line, self:padding_text(indent.per_level - icon_width))
103+
end
104+
end
85105
end
86106
return line
87107
end
88108

89109
---@protected
90110
---@param level? integer
91111
---@return integer
92-
function Base:indent(level)
112+
function Base:indent_size(level)
113+
return self.config.indent.per_level * self:indent_level(level)
114+
end
115+
116+
---@private
117+
---@param level? integer
118+
---@return integer
119+
function Base:indent_level(level)
93120
local indent = self.config.indent
94121
if self.context:skip(indent) then
95122
return 0
96123
end
97124
if level == nil then
125+
-- Level is not known, get it from the closest parent section
98126
level = self.node:level(true)
99-
elseif indent.skip_heading then
100-
local parent = self.node:parent('section')
101-
level = parent ~= nil and parent:level(true) or 0
102-
end
103-
level = level - indent.skip_level
104-
if level <= 0 then
105-
return 0
127+
else
128+
-- Level is known, must be a heading
129+
if indent.skip_heading then
130+
-- Account for ability to skip headings
131+
local parent = self.node:parent('section')
132+
level = parent ~= nil and parent:level(true) or 0
133+
end
106134
end
107-
return indent.per_level * level
135+
return math.max(level - indent.skip_level, 0)
108136
end
109137

110138
---@protected
111139
---@param width integer
112140
---@param highlight? string
113-
---@return { [1]: string, [2]: string }
141+
---@return render.md.line.Text
114142
function Base:padding_text(width, highlight)
115143
return { Str.pad(width), highlight or self.config.padding.highlight }
116144
end

lua/render-markdown/render/code.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ function Render:setup()
6464
padding = left_padding,
6565
max_width = max_width,
6666
empty_rows = empty_rows,
67-
indent = self:indent(),
67+
indent = self:indent_size(),
6868
}
6969

7070
return true

lua/render-markdown/render/heading.lua

Lines changed: 23 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,8 @@ function Render:render()
9999
self:background(width)
100100
self:left_pad(width)
101101
if self.data.atx then
102-
self:border(width)
102+
self:border(width, 'above', self.heading.above, self.node.start_row - 1)
103+
self:border(width, 'below', self.heading.below, self.node.end_row)
103104
else
104105
self:conceal_underline()
105106
end
@@ -205,7 +206,7 @@ function Render:background(width)
205206
end
206207
local win_col, padding = 0, {}
207208
if self.data.width == 'block' then
208-
win_col = width.margin + width.content + self:indent(self.data.level)
209+
win_col = width.margin + width.content + self:indent_size(self.data.level)
209210
table.insert(padding, self:padding_text(vim.o.columns * 2))
210211
end
211212
for row = self.node.start_row, self.node.end_row - 1 do
@@ -227,73 +228,44 @@ end
227228

228229
---@private
229230
---@param width render.md.width.Heading
230-
function Render:border(width)
231+
---@param position 'above'|'below'
232+
---@param icon string
233+
---@param row integer
234+
function Render:border(width, position, icon, row)
231235
if not self.data.border then
232236
return
233237
end
234238

235239
local foreground = self.data.foreground
236240
local background = self.data.background and colors.bg_to_fg(self.data.background)
241+
local total_width = self.data.width == 'block' and width.content or vim.o.columns
237242
local prefix = self.heading.border_prefix and self.data.level or 0
238-
local virtual = self.heading.border_virtual
239243

240-
---@param icon string
241-
---@return { [1]: string, [2]: string }[]
242-
local function line(icon)
243-
---@param size integer
244-
---@param highlight? string
245-
---@return { [1]: string, [2]: string }
246-
local function section(size, highlight)
247-
if highlight ~= nil then
248-
return { icon:rep(size), highlight }
249-
else
250-
return self:padding_text(size)
251-
end
252-
end
253-
local content_width = self.data.width == 'block' and width.content or vim.o.columns
254-
return {
255-
section(width.margin, nil),
256-
section(width.padding, background),
257-
section(prefix, foreground),
258-
section(content_width - width.padding - prefix, background),
259-
}
260-
end
244+
local line = {
245+
self:padding_text(width.margin),
246+
{ icon:rep(width.padding), background },
247+
{ icon:rep(prefix), foreground },
248+
{ icon:rep(total_width - width.padding - prefix), background },
249+
}
261250

262-
local line_above = line(self.heading.above)
263-
if not virtual and self:empty_line('above') and self.node.start_row - 1 ~= self.context.last_heading then
264-
self.marks:add('head_border', self.node.start_row - 1, 0, {
265-
virt_text = line_above,
266-
virt_text_pos = 'overlay',
267-
})
268-
else
269-
self.marks:add(false, self.node.start_row, 0, {
270-
virt_lines = { self:indent_virt_line(line_above, self.data.level) },
271-
virt_lines_above = true,
272-
})
273-
end
251+
local virtual = self.heading.border_virtual
252+
local target_line = self.node:line(position, 1)
253+
local line_available = target_line ~= nil and Str.width(target_line) == 0
274254

275-
local line_below = line(self.heading.below)
276-
if not virtual and self:empty_line('below') then
277-
self.marks:add('head_border', self.node.end_row, 0, {
278-
virt_text = line_below,
255+
if not virtual and line_available and row ~= self.context.last_heading then
256+
self.marks:add('head_border', row, 0, {
257+
virt_text = line,
279258
virt_text_pos = 'overlay',
280259
})
281-
self.context.last_heading = self.node.end_row
260+
self.context.last_heading = row
282261
else
283262
self.marks:add(false, self.node.start_row, 0, {
284-
virt_lines = { self:indent_virt_line(line_below, self.data.level) },
263+
virt_lines = { vim.list_extend(self:indent_line(true, self.data.level), line) },
264+
virt_lines_above = position == 'above',
285265
})
286266
end
287267
end
288268

289-
---@private
290-
---@param position 'above'|'below'
291-
---@return boolean
292-
function Render:empty_line(position)
293-
local line = self.node:line(position, 1)
294-
return line ~= nil and Str.width(line) == 0
295-
end
296-
297269
---@private
298270
---@param width render.md.width.Heading
299271
function Render:left_pad(width)

lua/render-markdown/render/section.lua

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,38 +27,48 @@ function Render:setup()
2727
end
2828

2929
function Render:render()
30-
local start_row = nil
30+
local start_row = self:get_start_row()
31+
local end_row = self:get_end_row()
32+
-- Each level stacks inline marks so we only need to process change in level
33+
local virt_text = self:indent_line(false, self.level_change)
34+
for row = start_row, end_row do
35+
self.marks:add(false, row, 0, {
36+
priority = 0,
37+
virt_text = virt_text,
38+
virt_text_pos = 'inline',
39+
})
40+
end
41+
end
42+
43+
---@private
44+
---@return integer
45+
function Render:get_start_row()
3146
if self.indent.skip_heading then
3247
-- Exclude any lines potentially used by section heading
3348
local second = self.node:line('first', 1)
3449
local start_offset = Str.width(second) == 0 and 1 or 0
35-
start_row = self.node.start_row + 1 + start_offset
50+
return self.node.start_row + 1 + start_offset
3651
else
3752
-- Include last empty line in previous section
3853
-- Exclude if it is the only empty line in that section
3954
local above, two_above = self.node:line('above', 1), self.node:line('above', 2)
4055
local above_is_empty = Str.width(above) == 0
4156
local two_above_is_section = two_above ~= nil and vim.startswith(two_above, '#')
4257
local start_offset = (above_is_empty and not two_above_is_section) and 1 or 0
43-
start_row = math.max(self.node.start_row - start_offset, 0)
58+
return math.max(self.node.start_row - start_offset, 0)
4459
end
60+
end
4561

62+
---@private
63+
---@return integer
64+
function Render:get_end_row()
4665
-- Exclude last empty line in current section
4766
-- Include if it is the only empty line of the last subsection
4867
local last, second_last = self.node:line('last', 0), self.node:line('last', 1)
4968
local last_is_empty = Str.width(last) == 0
5069
local second_last_is_section = second_last ~= nil and vim.startswith(second_last, '#')
5170
local end_offset = (last_is_empty and not second_last_is_section) and 1 or 0
52-
local end_row = self.node.end_row - 1 - end_offset
53-
54-
-- Each level stacks inline marks so we only need to multiply based on any skipped levels
55-
for row = start_row, end_row do
56-
self.marks:add(false, row, 0, {
57-
priority = 0,
58-
virt_text = { self:padding_text(self.indent.per_level * self.level_change) },
59-
virt_text_pos = 'inline',
60-
})
61-
end
71+
return self.node.end_row - 1 - end_offset
6272
end
6373

6474
return Render

lua/render-markdown/render/table.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,8 +379,8 @@ function Render:full()
379379
local highlight = above and self.table.head or self.table.row
380380
table.insert(line, { chars[1] .. table.concat(sections, chars[2]) .. chars[3], highlight })
381381
self.marks:add_start(false, node, {
382+
virt_lines = { vim.list_extend(self:indent_line(true), line) },
382383
virt_lines_above = above,
383-
virt_lines = { self:indent_virt_line(line) },
384384
})
385385
end
386386

0 commit comments

Comments
 (0)