Skip to content

Commit 3254863

Browse files
feat: improve completions
## Details Rather than handling the entire text of a list item (which includes any sub lists), handle only the first line. Specifically handle the first line of text starting from the end of the marker node to the current cursor column. This allows us to: - provide completions when editing existing list items: `- |text` - handle items at any position and nesting level - avoid adding an extra space after items when unneeded Generally the behavior is a lot more predictable and more likely to align with user expectations.
1 parent 1128bc3 commit 3254863

File tree

5 files changed

+93
-81
lines changed

5 files changed

+93
-81
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@
66

77
- configurable indent extmark priority [1327150](https://github.com/MeanderingProgrammer/render-markdown.nvim/commit/1327150da9b272863355f28e523050ab5450412f)
88
- more anti_conceal.ignore elements [b8c3d47](https://github.com/MeanderingProgrammer/render-markdown.nvim/commit/b8c3d474d4da14fa1a5f9a0a18955c6e7db88f65)
9+
- allow users to disable code block delimiter concealing [7620d4e](https://github.com/MeanderingProgrammer/render-markdown.nvim/commit/7620d4e96eb5cf8d12e76c6fcaf1267628564ba9)
10+
- improve completions [#474](https://github.com/MeanderingProgrammer/render-markdown.nvim/pull/474)
11+
12+
### Collaborator Shoutouts
13+
14+
- @Anaritus
915

1016
## 8.6.0 (2025-07-09)
1117

doc/render-markdown.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
*render-markdown.txt* For NVIM v0.11.3 Last change: 2025 July 17
1+
*render-markdown.txt* For NVIM v0.11.3 Last change: 2025 July 20
22

33
==============================================================================
44
Table of Contents *render-markdown-table-of-contents*

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

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

lua/render-markdown/integ/source.lua

Lines changed: 51 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1+
local Node = require('render-markdown.lib.node')
12
local env = require('render-markdown.lib.env')
23
local manager = require('render-markdown.core.manager')
34
local state = require('render-markdown.state')
4-
local str = require('render-markdown.lib.str')
55

66
local markers = {
77
list_marker_minus = '-',
@@ -10,6 +10,15 @@ local markers = {
1010
block_quote_marker = '>',
1111
}
1212

13+
local children = vim.list_extend(vim.tbl_keys(markers), {
14+
'block_continuation',
15+
'inline',
16+
'task_list_marker_checked',
17+
'task_list_marker_unchecked',
18+
})
19+
20+
local siblings = { 'paragraph' }
21+
1322
---@class render.md.source.Config
1423
---@field completions render.md.completions.Config
1524

@@ -40,41 +49,43 @@ end
4049
function M.items(buf, row, col)
4150
buf = buf == 0 and env.buf.current() or buf
4251
local node = M.node(buf, row, col, 'markdown')
43-
if not node or node:range() ~= row then
52+
if not node or node.start_row ~= row then
4453
return nil
4554
end
46-
47-
local marker_node = node:named_child(0)
48-
if not marker_node then
55+
local marker = node:child_at(0)
56+
if not marker then
4957
return nil
5058
end
51-
52-
local marker = vim.treesitter.get_node_text(marker_node, buf)
53-
local text = vim.treesitter.get_node_text(node, buf):gsub('\n%s*$', '')
54-
if M.ignore(marker, text) then
59+
local _, line = node:line('first', 0)
60+
if not line then
61+
return nil
62+
end
63+
local before, after = math.min(marker.end_col, col), col + 1
64+
local text = line:sub(before + 1, after - 1)
65+
if M.ignore(text) then
5566
return nil
5667
end
5768

58-
local items = {}
69+
local items = {} ---@type lsp.CompletionItem[]
5970
local config = state.get(buf)
6071
local filter = M.config.completions.filter
61-
local prefix = str.spaces('end', marker) == 0 and ' ' or ''
62-
if node:type() == 'block_quote' then
72+
local prefix = line:sub(before, before) == ' ' and '' or ' '
73+
if node.type == 'block_quote' then
74+
local suffix = ''
6375
for _, value in pairs(config.callout) do
6476
if filter.callout(value) then
65-
local detail = ' ' .. value.rendered
66-
items[#items + 1] = M.item(prefix, value.raw, detail)
77+
M.append(items, prefix, suffix, value.raw, value.rendered)
6778
end
6879
end
69-
elseif node:type() == 'list_item' then
80+
elseif node.type == 'list_item' then
81+
local suffix = line:sub(after, after) == ' ' and '' or ' '
7082
local unchecked = config.checkbox.unchecked
71-
items[#items + 1] = M.item(prefix, '[ ] ', unchecked.icon, 'unchecked')
83+
M.append(items, prefix, suffix, '[ ]', unchecked.icon, 'unchecked')
7284
local checked = config.checkbox.checked
73-
items[#items + 1] = M.item(prefix, '[x] ', checked.icon, 'checked')
85+
M.append(items, prefix, suffix, '[x]', checked.icon, 'checked')
7486
for name, value in pairs(config.checkbox.custom) do
7587
if filter.checkbox(value) then
76-
local label = value.raw .. ' '
77-
items[#items + 1] = M.item(prefix, label, value.rendered, name)
88+
M.append(items, prefix, suffix, value.raw, value.rendered, name)
7889
end
7990
end
8091
end
@@ -86,48 +97,42 @@ end
8697
---@param row integer
8798
---@param col integer
8899
---@param lang string
89-
---@return TSNode?
100+
---@return render.md.Node?
90101
function M.node(buf, row, col, lang)
91102
-- parse current row to get up to date node
92103
local ok, parser = pcall(vim.treesitter.get_parser, buf, lang)
93104
if not ok or not parser then
94105
return nil
95106
end
96107
parser:parse({ row, row })
97-
98108
local node = vim.treesitter.get_node({
99109
bufnr = buf,
100110
pos = { row, col },
101111
lang = lang,
102112
})
103-
if node and node:type() == 'paragraph' then
104-
node = node:prev_sibling()
105-
end
106-
local children = vim.tbl_keys(markers)
107-
children[#children + 1] = 'block_continuation'
108-
if node and vim.tbl_contains(children, node:type()) then
109-
node = node:parent()
113+
while node do
114+
if vim.tbl_contains(children, node:type()) then
115+
node = node:parent()
116+
elseif vim.tbl_contains(siblings, node:type()) then
117+
node = node:prev_sibling()
118+
else
119+
return Node.new(buf, node)
120+
end
110121
end
111-
return node
122+
return nil
112123
end
113124

114125
---@private
115-
---@param marker string
116126
---@param text string
117127
---@return boolean
118-
function M.ignore(marker, text)
119-
local prefix = '^' .. vim.pesc(vim.trim(marker)) .. '%s+'
120-
local i, j = text:find(prefix)
121-
if not (i and j) or text:sub(i, j):find('\n') then
122-
return false
123-
end
128+
function M.ignore(text)
124129
local patterns = {} ---@type string[]
125-
-- first non-space after the marker is not '['
126-
patterns[#patterns + 1] = prefix .. '[^%[]'
130+
-- first character is not '['
131+
patterns[#patterns + 1] = '^[^%[]'
127132
-- after '[' there is another '[' or a space
128-
patterns[#patterns + 1] = prefix .. '%[.*[%[%s]'
133+
patterns[#patterns + 1] = '^%[.*[%[%s]'
129134
-- there is already text enclosed by '[' ']'
130-
patterns[#patterns + 1] = prefix .. '%[.*%]'
135+
patterns[#patterns + 1] = '^%[.*%]'
131136
for _, pattern in ipairs(patterns) do
132137
if text:find(pattern) then
133138
return true
@@ -137,21 +142,21 @@ function M.ignore(marker, text)
137142
end
138143

139144
---@private
145+
---@param items lsp.CompletionItem[]
140146
---@param prefix string
147+
---@param suffix string
141148
---@param label string
142149
---@param detail string
143150
---@param description? string
144-
---@return lsp.CompletionItem
145-
function M.item(prefix, label, detail, description)
146-
---@type lsp.CompletionItem
147-
return {
151+
function M.append(items, prefix, suffix, label, detail, description)
152+
items[#items + 1] = {
148153
kind = 12,
149154
label = label,
150-
insertText = prefix .. label,
151155
labelDetails = {
152-
detail = detail,
156+
detail = ' ' .. detail,
153157
description = description,
154158
},
159+
insertText = prefix .. label .. suffix,
155160
}
156161
end
157162

tests/comp_spec.lua

Lines changed: 34 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,21 @@ local function assert_items(row, n, col, expected)
1616
end
1717

1818
---@param prefix string
19+
---@param suffix string
1920
---@param label string
2021
---@param detail string
2122
---@param description? string
2223
---@return lsp.CompletionItem
23-
local function item(prefix, label, detail, description)
24+
local function item(prefix, suffix, label, detail, description)
2425
---@type lsp.CompletionItem
2526
return {
2627
kind = 12,
2728
label = label,
28-
insertText = prefix .. label,
2929
labelDetails = {
30-
detail = detail,
30+
detail = ' ' .. detail,
3131
description = description,
3232
},
33+
insertText = prefix .. label .. suffix,
3334
}
3435
end
3536

@@ -79,9 +80,9 @@ describe('comp.md', function()
7980
---@return lsp.CompletionItem[]
8081
local function items(prefix)
8182
return {
82-
item(prefix, '[ ] ', '󰄱 ', 'unchecked'),
83-
item(prefix, '[-] ', '󰥔 ', 'todo'),
84-
item(prefix, '[x] ', '󰱒 ', 'checked'),
83+
item(prefix, ' ', '[ ]', '󰄱 ', 'unchecked'),
84+
item(prefix, ' ', '[-]', '󰥔 ', 'todo'),
85+
item(prefix, ' ', '[x]', '󰱒 ', 'checked'),
8586
}
8687
end
8788

@@ -104,33 +105,33 @@ describe('comp.md', function()
104105
---@return lsp.CompletionItem[]
105106
local function items(prefix)
106107
return {
107-
item(prefix, '[!ABSTRACT]', ' 󰨸 Abstract'),
108-
item(prefix, '[!ATTENTION]', ' 󰀪 Attention'),
109-
item(prefix, '[!BUG]', ' 󰨰 Bug'),
110-
item(prefix, '[!CAUTION]', ' 󰳦 Caution'),
111-
item(prefix, '[!CHECK]', ' 󰄬 Check'),
112-
item(prefix, '[!CITE]', ' 󱆨 Cite'),
113-
item(prefix, '[!DANGER]', ' 󱐌 Danger'),
114-
item(prefix, '[!DONE]', ' 󰄬 Done'),
115-
item(prefix, '[!ERROR]', ' 󱐌 Error'),
116-
item(prefix, '[!EXAMPLE]', ' 󰉹 Example'),
117-
item(prefix, '[!FAILURE]', ' 󰅖 Failure'),
118-
item(prefix, '[!FAIL]', ' 󰅖 Fail'),
119-
item(prefix, '[!FAQ]', ' 󰘥 Faq'),
120-
item(prefix, '[!HELP]', ' 󰘥 Help'),
121-
item(prefix, '[!HINT]', ' 󰌶 Hint'),
122-
item(prefix, '[!IMPORTANT]', ' 󰅾 Important'),
123-
item(prefix, '[!INFO]', ' 󰋽 Info'),
124-
item(prefix, '[!MISSING]', ' 󰅖 Missing'),
125-
item(prefix, '[!NOTE]', ' 󰋽 Note'),
126-
item(prefix, '[!QUESTION]', ' 󰘥 Question'),
127-
item(prefix, '[!QUOTE]', ' 󱆨 Quote'),
128-
item(prefix, '[!SUCCESS]', ' 󰄬 Success'),
129-
item(prefix, '[!SUMMARY]', ' 󰨸 Summary'),
130-
item(prefix, '[!TIP]', ' 󰌶 Tip'),
131-
item(prefix, '[!TLDR]', ' 󰨸 Tldr'),
132-
item(prefix, '[!TODO]', ' 󰗡 Todo'),
133-
item(prefix, '[!WARNING]', ' 󰀪 Warning'),
108+
item(prefix, '', '[!ABSTRACT]', '󰨸 Abstract'),
109+
item(prefix, '', '[!ATTENTION]', '󰀪 Attention'),
110+
item(prefix, '', '[!BUG]', '󰨰 Bug'),
111+
item(prefix, '', '[!CAUTION]', '󰳦 Caution'),
112+
item(prefix, '', '[!CHECK]', '󰄬 Check'),
113+
item(prefix, '', '[!CITE]', '󱆨 Cite'),
114+
item(prefix, '', '[!DANGER]', '󱐌 Danger'),
115+
item(prefix, '', '[!DONE]', '󰄬 Done'),
116+
item(prefix, '', '[!ERROR]', '󱐌 Error'),
117+
item(prefix, '', '[!EXAMPLE]', '󰉹 Example'),
118+
item(prefix, '', '[!FAILURE]', '󰅖 Failure'),
119+
item(prefix, '', '[!FAIL]', '󰅖 Fail'),
120+
item(prefix, '', '[!FAQ]', '󰘥 Faq'),
121+
item(prefix, '', '[!HELP]', '󰘥 Help'),
122+
item(prefix, '', '[!HINT]', '󰌶 Hint'),
123+
item(prefix, '', '[!IMPORTANT]', '󰅾 Important'),
124+
item(prefix, '', '[!INFO]', '󰋽 Info'),
125+
item(prefix, '', '[!MISSING]', '󰅖 Missing'),
126+
item(prefix, '', '[!NOTE]', '󰋽 Note'),
127+
item(prefix, '', '[!QUESTION]', '󰘥 Question'),
128+
item(prefix, '', '[!QUOTE]', '󱆨 Quote'),
129+
item(prefix, '', '[!SUCCESS]', '󰄬 Success'),
130+
item(prefix, '', '[!SUMMARY]', '󰨸 Summary'),
131+
item(prefix, '', '[!TIP]', '󰌶 Tip'),
132+
item(prefix, '', '[!TLDR]', '󰨸 Tldr'),
133+
item(prefix, '', '[!TODO]', '󰗡 Todo'),
134+
item(prefix, '', '[!WARNING]', '󰀪 Warning'),
134135
}
135136
end
136137

0 commit comments

Comments
 (0)