Skip to content

Commit 277ae65

Browse files
feat: add basic support for org-indent-mode like behavior
## Details Request: #134 Adds a new top level configuration for `indent` which is disabled by default. The effect of enabling it is to mimic org-indent-mode by adding `per_level` * (level - 1) spaces to the start of each line. This is accomplished by using the section nodes which nicely contain everything within a header including nested headers. Since the sections themselves are nested we don't even do the math for creating the padding we simply add `per_level` for each line of each section and the fact they are nested means the paddings stack. We need to use inline virtual text to accomplish this since that is the only way to shift text, meaining this only works for neovim >= 0.10.0. Since virtual_lines are not impacted by inline text we need to manaully pad them based on the level of the current object. There are probably many edge cases that look weird or cause all sorts of alignment problems. I'll work on them as they come up if they're not too bad to handle, otherwise I will enumerate any non functional configurations in the README.
1 parent 952b1c0 commit 277ae65

File tree

10 files changed

+185
-37
lines changed

10 files changed

+185
-37
lines changed

README.md

+28-1
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,8 @@ require('render-markdown').setup({
158158
preset = 'none',
159159
-- Capture groups that get pulled from markdown
160160
markdown_query = [[
161+
(section) @section
162+
161163
(atx_heading [
162164
(atx_h1_marker)
163165
(atx_h2_marker)
@@ -489,6 +491,14 @@ require('render-markdown').setup({
489491
-- Applies to background of sign text
490492
highlight = 'RenderMarkdownSign',
491493
},
494+
-- Mimic org-indent-mode behavior by indenting everything under a heading based on the
495+
-- level of the heading. Indenting starts from level 2 headings onward.
496+
indent = {
497+
-- Turn on / off org-indent-mode
498+
enabled = false,
499+
-- Amount of additional padding added for each heading level
500+
per_level = 2,
501+
},
492502
-- Window options to use that change between rendered and raw view
493503
win_options = {
494504
-- See :h 'conceallevel'
@@ -510,7 +520,7 @@ require('render-markdown').setup({
510520
-- to have their own behavior. Values default to the top level configuration
511521
-- if no override is provided. Supports the following fields:
512522
-- enabled, max_file_size, debounce, render_modes, anti_conceal, heading, code,
513-
-- dash, bullet, checkbox, quote, pipe_table, callout, link, sign, win_options
523+
-- dash, bullet, checkbox, quote, pipe_table, callout, link, sign, indent, win_options
514524
overrides = {
515525
-- Overrides for different buftypes, see :h 'buftype'
516526
buftype = {
@@ -884,6 +894,23 @@ require('render-markdown').setup({
884894
})
885895
```
886896

897+
## Indent
898+
899+
[Wiki Page](https://github.com/MeanderingProgrammer/render-markdown.nvim/wiki/Indent)
900+
901+
```lua
902+
require('render-markdown').setup({
903+
-- Mimic org-indent-mode behavior by indenting everything under a heading based on the
904+
-- level of the heading. Indenting starts from level 2 headings onward.
905+
indent = {
906+
-- Turn on / off org-indent-mode
907+
enabled = false,
908+
-- Amount of additional padding added for each heading level
909+
per_level = 2,
910+
},
911+
})
912+
```
913+
887914
# Colors
888915

889916
The table below shows all the highlight groups with their default link

doc/render-markdown.txt

+31-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Table of Contents *render-markdown-table-of-contents*
2222
- Callouts |render-markdown-setup-callouts|
2323
- Links |render-markdown-setup-links|
2424
- Signs |render-markdown-setup-signs|
25+
- Indent |render-markdown-setup-indent|
2526
7. Colors |render-markdown-colors|
2627
8. Info |render-markdown-info|
2728
- vimwiki |render-markdown-info-vimwiki|
@@ -188,6 +189,8 @@ Full Default Configuration ~
188189
preset = 'none',
189190
-- Capture groups that get pulled from markdown
190191
markdown_query = [[
192+
(section) @section
193+
191194
(atx_heading [
192195
(atx_h1_marker)
193196
(atx_h2_marker)
@@ -519,6 +522,14 @@ Full Default Configuration ~
519522
-- Applies to background of sign text
520523
highlight = 'RenderMarkdownSign',
521524
},
525+
-- Mimic org-indent-mode behavior by indenting everything under a heading based on the
526+
-- level of the heading. Indenting starts from level 2 headings onward.
527+
indent = {
528+
-- Turn on / off org-indent-mode
529+
enabled = false,
530+
-- Amount of additional padding added for each heading level
531+
per_level = 2,
532+
},
522533
-- Window options to use that change between rendered and raw view
523534
win_options = {
524535
-- See :h 'conceallevel'
@@ -540,7 +551,7 @@ Full Default Configuration ~
540551
-- to have their own behavior. Values default to the top level configuration
541552
-- if no override is provided. Supports the following fields:
542553
-- enabled, max_file_size, debounce, render_modes, anti_conceal, heading, code,
543-
-- dash, bullet, checkbox, quote, pipe_table, callout, link, sign, win_options
554+
-- dash, bullet, checkbox, quote, pipe_table, callout, link, sign, indent, win_options
544555
overrides = {
545556
-- Overrides for different buftypes, see :h 'buftype'
546557
buftype = {
@@ -933,6 +944,25 @@ Wiki Page
933944
<
934945

935946

947+
INDENT *render-markdown-setup-indent*
948+
949+
Wiki Page
950+
<https://github.com/MeanderingProgrammer/render-markdown.nvim/wiki/Indent>
951+
952+
>lua
953+
require('render-markdown').setup({
954+
-- Mimic org-indent-mode behavior by indenting everything under a heading based on the
955+
-- level of the heading. Indenting starts from level 2 headings onward.
956+
indent = {
957+
-- Turn on / off org-indent-mode
958+
enabled = false,
959+
-- Amount of additional padding added for each heading level
960+
per_level = 2,
961+
},
962+
})
963+
<
964+
965+
936966
==============================================================================
937967
7. Colors *render-markdown-colors*
938968

lua/render-markdown/handler/markdown.lua

+56-7
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ function Handler:parse(root)
3737
self.context:query(root, state.markdown_query, function(capture, node)
3838
local info = NodeInfo.new(self.buf, node)
3939
logger.debug_node_info(capture, info)
40-
if capture == 'heading' then
40+
if capture == 'section' then
41+
self:section(info)
42+
elseif capture == 'heading' then
4143
self:heading(info)
4244
elseif capture == 'dash' then
4345
self:dash(info)
@@ -78,6 +80,31 @@ function Handler:add(conceal, start_row, start_col, opts)
7880
return list.add_mark(self.marks, conceal, start_row, start_col, opts)
7981
end
8082

83+
---@private
84+
---@param info render.md.NodeInfo
85+
function Handler:section(info)
86+
local indent = self.config.indent
87+
if not indent.enabled then
88+
return
89+
end
90+
91+
-- Do not add any indentation on unknown or first level
92+
local heading = info:child('atx_heading')
93+
if heading == nil or heading:child('atx_h1_marker') ~= nil then
94+
return
95+
end
96+
97+
-- Each level stacks inline marks so we do not need to multiply spaces
98+
-- However skipping a level, i.e. 2 -> 5, will only add one level of spaces
99+
for row = info.start_row, info.end_row - 1 do
100+
self:add(false, row, 0, {
101+
priority = 0,
102+
virt_text = { { str.spaces(indent.per_level), 'Normal' } },
103+
virt_text_pos = 'inline',
104+
})
105+
end
106+
end
107+
81108
---@private
82109
---@param info render.md.NodeInfo
83110
function Handler:heading(info)
@@ -209,7 +236,7 @@ function Handler:heading_border(info, level, foreground, background, width)
209236
})
210237
else
211238
self:add(false, info.start_row, 0, {
212-
virt_lines = { line_above },
239+
virt_lines = { self:indent_virt_line(info, line_above) },
213240
virt_lines_above = true,
214241
})
215242
end
@@ -227,7 +254,7 @@ function Handler:heading_border(info, level, foreground, background, width)
227254
self.last_heading_border = info.end_row + 1
228255
else
229256
self:add(false, info.end_row, 0, {
230-
virt_lines = { line_below },
257+
virt_lines = { self:indent_virt_line(info, line_below) },
231258
})
232259
end
233260
end
@@ -653,19 +680,41 @@ function Handler:table_full(parsed_table)
653680
return border[11]:rep(column.width)
654681
end, delim.columns)
655682

656-
local line_above = border[1] .. table.concat(sections, border[2]) .. border[3]
683+
local line_above = {
684+
{ border[1] .. table.concat(sections, border[2]) .. border[3], pipe_table.head },
685+
}
657686
self:add(false, first.info.start_row, first.info.start_col, {
658687
virt_lines_above = true,
659-
virt_lines = { { { line_above, pipe_table.head } } },
688+
virt_lines = { self:indent_virt_line(parsed_table.info, line_above) },
660689
})
661690

662-
local line_below = border[7] .. table.concat(sections, border[8]) .. border[9]
691+
local line_below = {
692+
{ border[7] .. table.concat(sections, border[8]) .. border[9], pipe_table.row },
693+
}
663694
self:add(false, last.info.start_row, last.info.start_col, {
664695
virt_lines_above = false,
665-
virt_lines = { { { line_below, pipe_table.row } } },
696+
virt_lines = { self:indent_virt_line(parsed_table.info, line_below) },
666697
})
667698
end
668699

700+
---@private
701+
---@param info render.md.NodeInfo
702+
---@param line { [1]: string, [2]: string }[]
703+
---@return { [1]: string, [2]: string }[]
704+
function Handler:indent_virt_line(info, line)
705+
local indent = self.config.indent
706+
if not indent.enabled then
707+
return line
708+
end
709+
local level = info:level() - 1
710+
if level <= 0 then
711+
return line
712+
end
713+
local indent_line = { str.spaces(indent.per_level * level), 'Normal' }
714+
table.insert(line, 1, indent_line)
715+
return line
716+
end
717+
669718
---@class render.md.handler.Markdown: render.md.Handler
670719
local M = {}
671720

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.1.11'
8+
M.version = '6.1.12'
99

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

lua/render-markdown/init.lua

+16-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ local M = {}
2222
---@field public default? number|string|boolean
2323
---@field public rendered? number|string|boolean
2424

25+
---@class (exact) render.md.UserIndent
26+
---@field public enabled? boolean
27+
---@field public per_level? integer
28+
2529
---@class (exact) render.md.UserSign
2630
---@field public enabled? boolean
2731
---@field public highlight? string
@@ -157,6 +161,7 @@ local M = {}
157161
---@field public callout? table<string, render.md.UserCustomComponent>
158162
---@field public link? render.md.UserLink
159163
---@field public sign? render.md.UserSign
164+
---@field public indent? render.md.UserIndent
160165
---@field public win_options? table<string, render.md.UserWindowOption>
161166

162167
---@alias render.md.config.Preset 'none'|'lazy'|'obsidian'
@@ -193,6 +198,8 @@ M.default_config = {
193198
preset = 'none',
194199
-- Capture groups that get pulled from markdown
195200
markdown_query = [[
201+
(section) @section
202+
196203
(atx_heading [
197204
(atx_h1_marker)
198205
(atx_h2_marker)
@@ -524,6 +531,14 @@ M.default_config = {
524531
-- Applies to background of sign text
525532
highlight = 'RenderMarkdownSign',
526533
},
534+
-- Mimic org-indent-mode behavior by indenting everything under a heading based on the
535+
-- level of the heading. Indenting starts from level 2 headings onward.
536+
indent = {
537+
-- Turn on / off org-indent-mode
538+
enabled = false,
539+
-- Amount of additional padding added for each heading level
540+
per_level = 2,
541+
},
527542
-- Window options to use that change between rendered and raw view
528543
win_options = {
529544
-- See :h 'conceallevel'
@@ -545,7 +560,7 @@ M.default_config = {
545560
-- to have their own behavior. Values default to the top level configuration
546561
-- if no override is provided. Supports the following fields:
547562
-- enabled, max_file_size, debounce, render_modes, anti_conceal, heading, code,
548-
-- dash, bullet, checkbox, quote, pipe_table, callout, link, sign, win_options
563+
-- dash, bullet, checkbox, quote, pipe_table, callout, link, sign, indent, win_options
549564
overrides = {
550565
-- Overrides for different buftypes, see :h 'buftype'
551566
buftype = {

lua/render-markdown/node_info.lua

+13
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,19 @@ function NodeInfo.sort_inplace(infos)
4545
end)
4646
end
4747

48+
---@return integer
49+
function NodeInfo:level()
50+
local level = 0
51+
local parent = self.node:parent()
52+
while parent ~= nil and parent:type() ~= 'document' do
53+
if parent:type() == 'section' then
54+
level = level + 1
55+
end
56+
parent = parent:parent()
57+
end
58+
return level
59+
end
60+
4861
---Walk through parent nodes, count the number of target nodes
4962
---@param target string
5063
---@return integer

lua/render-markdown/parser/pipe_table.lua

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ local str = require('render-markdown.str')
2222
---@field columns render.md.parsed.table.Column[]
2323

2424
---@class render.md.parsed.PipeTable
25+
---@field info render.md.NodeInfo
2526
---@field delim render.md.parsed.table.DelimRow
2627
---@field rows render.md.parsed.table.Row[]
2728

@@ -72,7 +73,7 @@ function M.parse(context, info)
7273
end
7374

7475
---@type render.md.parsed.PipeTable
75-
return { delim = delim, rows = rows }
76+
return { info = info, delim = delim, rows = rows }
7677
end
7778

7879
---@private

lua/render-markdown/state.lua

+14-2
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ end
8585
function M.default_buffer_config()
8686
local config = M.config
8787
---@type render.md.BufferConfig
88-
return vim.deepcopy({
88+
local buffer_config = {
8989
enabled = true,
9090
max_file_size = config.max_file_size,
9191
debounce = config.debounce,
@@ -101,8 +101,10 @@ function M.default_buffer_config()
101101
callout = config.callout,
102102
link = config.link,
103103
sign = config.sign,
104+
indent = config.indent,
104105
win_options = config.win_options,
105-
})
106+
}
107+
return vim.deepcopy(buffer_config)
106108
end
107109

108110
---@return string[]
@@ -374,6 +376,14 @@ function M.validate()
374376
})
375377
end
376378

379+
local indent = config.indent
380+
if indent ~= nil then
381+
append_errors(path .. '.indent', indent, {
382+
enabled = { indent.enabled, 'boolean', nilable },
383+
per_level = { indent.per_level, 'number', nilable },
384+
})
385+
end
386+
377387
if config.win_options ~= nil then
378388
for name, win_option in pairs(config.win_options) do
379389
append_errors(path .. '.win_options.' .. name, win_option, {
@@ -401,6 +411,7 @@ function M.validate()
401411
callout = { config.callout, 'table' },
402412
link = { config.link, 'table' },
403413
sign = { config.sign, 'table' },
414+
indent = { config.indent, 'table' },
404415
win_options = { config.win_options, 'table' },
405416
preset = one_of(config.preset, { 'none', 'lazy', 'obsidian' }, {}, false),
406417
markdown_query = { config.markdown_query, 'string' },
@@ -449,6 +460,7 @@ function M.validate()
449460
callout = { override.callout, 'table', true },
450461
link = { override.link, 'table', true },
451462
sign = { override.sign, 'table', true },
463+
indent = { override.indent, 'table', true },
452464
win_options = { override.win_options, 'table', true },
453465
})
454466
validate_buffer_config(path, override, true)

lua/render-markdown/types.lua

+5
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111
---@field public default number|string|boolean
1212
---@field public rendered number|string|boolean
1313

14+
---@class (exact) render.md.Indent
15+
---@field public enabled boolean
16+
---@field public per_level integer
17+
1418
---@class (exact) render.md.Sign
1519
---@field public enabled boolean
1620
---@field public highlight string
@@ -132,6 +136,7 @@
132136
---@field public callout table<string, render.md.CustomComponent>
133137
---@field public link render.md.Link
134138
---@field public sign render.md.Sign
139+
---@field public indent render.md.Indent
135140
---@field public win_options table<string, render.md.WindowOption>
136141

137142
---@class (exact) render.md.Config: render.md.BufferConfig

0 commit comments

Comments
 (0)