aboutsummaryrefslogtreecommitdiffstats
path: root/plugin/50-markdown.lua
blob: 2fdf3d10a543cb79213c3ef27c7f08db98f37329 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
--[[ 50-markdown.lua — treesitter parsers, markdown rendering, and checklist commands.

Renders markdown via `render-markdown`
(https://github.com/MeanderingProgrammer/render-markdown.nvim).

User commands:
  MarkdownCheck:   check the checklist item under the cursor and strike through its text
  MarkdownUncheck: uncheck the checklist item under the cursor and remove the strikethrough
]]

----------------------------------------------------------------------------------------------------
-- Rendering ---------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------

require("nvim-treesitter").install({
	"html",
	"latex",
	"markdown",
	"markdown_inline",
	"yaml",
})

require("render-markdown").setup({
	code = {
		position = "center",
		sign = false,
		width = "block",
		min_width = 60,
	},
	win_options = { conceallevel = { rendered = 2 } },
})

----------------------------------------------------------------------------------------------------
-- Checklist ---------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------

--[[ Locate the checklist item containing line `row`, returning its first and last line (1-indexed).
Walks up from `row` to the item's `- [ ]` bullet — any other bullet, or an unindented non-blank
line, means `row` is not in a checklist item — then down through the item's continuation lines
(blank, or indented to the bullet's content column). Returns nil when `row` is outside an item. ]]
local function item_range(lines, row)
	local first = row
	while lines[first] and not lines[first]:match("^%s*%- %[[ Xx]%] ") do
		if lines[first]:match("^%s*%- ") or lines[first]:match("^%S") then
			return
		end
		first = first - 1
	end
	if not lines[first] then
		return
	end
	local content_col = #lines[first]:match("^%s*") + 2
	local last = first
	for i = first + 1, #lines do
		local line = lines[i]
		if line:match("^%s*%- ") or (line:match("%S") and #line:match("^%s*") < content_col) then
			break
		end
		if line:match("%S") then
			last = i
		end
	end
	if last < row then
		return
	end
	return first, last
end

-- Strike through the text of `line` after `prefix`, keeping a trailing hard-break `\` outside.
local function strike(line, prefix)
	return (line:gsub("^(" .. prefix .. ")(.-)(%s*\\?)$", "%1~%2~%3"))
end

-- Remove the strikethrough around the text of `line` after `prefix`, if there is one.
local function unstrike(line, prefix)
	return (line:gsub("^(" .. prefix .. ")~(.-)~(%s*\\?)$", "%1%2%3"))
end

--[[ Check or uncheck the checklist item under the cursor.
Checking flips the bullet's `[ ]` to `[X]` and strikes through each non-blank line of the item;
unchecking reverses both. Does nothing when the cursor is not in a checklist item or the item is
already in the requested state. ]]
local function set_item(checked)
	local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false)
	local first, last = item_range(lines, vim.api.nvim_win_get_cursor(0)[1])
	local marker = checked and "^%s*%- %[ %] " or "^%s*%- %[[Xx]%] "
	if not (first and last) or not lines[first]:match(marker) then
		return
	end
	local restyle = checked and strike or unstrike
	local item = {}
	for i = first, last do
		local line = lines[i]
		if i == first then
			line = restyle(line:gsub("%[.%]", checked and "[X]" or "[ ]", 1), "%s*%- %[.%] ")
		elseif line:match("%S") then
			line = restyle(line, "%s*")
		end
		item[#item + 1] = line
	end
	vim.api.nvim_buf_set_lines(0, first - 1, last, false, item)
end

----------------------------------------------------------------------------------------------------
-- Commands ----------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------

-- Check the checklist item under the cursor.
local function check_item()
	set_item(true)
end

-- Uncheck the checklist item under the cursor.
local function uncheck_item()
	set_item(false)
end

vim.api.nvim_create_user_command("MarkdownCheck", check_item, { desc = "Check checklist item" })
vim.api.nvim_create_user_command(
	"MarkdownUncheck",
	uncheck_item,
	{ desc = "Uncheck checklist item" }
)