summaryrefslogtreecommitdiffstats
path: root/.config/nvim/after/ftplugin/markdown.lua
blob: 1de3a374673284a7f8460951242e2da91c8ded82 (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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
local function format()
	local view = vim.fn.winsaveview()
	local buf_str = table.concat(vim.api.nvim_buf_get_lines(0, 0, -1, false), "\n") .. "\n"
	local tempname = vim.fn.tempname()
	local tempfile = assert(io.open(tempname, "w"), "Could not open temporary file")
	tempfile:write(buf_str)
	tempfile:close()

	local r_check = vim.system({
		"mdformat",
		"--number",
		"--extensions",
		"tables",
		"--extensions",
		"frontmatter",
		"--extensions",
		"wikilink",
		"--wrap",
		tostring(vim.bo.textwidth),
		"--check",
		tempname,
	}):wait()
	if r_check.code == 0 then
		return
	elseif r_check.code ~= 1 then
		vim.notify("mdformat failed (" .. r_check.code .. "):\n" .. r_check.stderr)
	end
	local r_format = vim.system({
		"mdformat",
		"--number",
		"--extensions",
		"tables",
		"--extensions",
		"frontmatter",
		"--extensions",
		"wikilink",
		"--wrap",
		tostring(vim.bo.textwidth),
		tempname,
	}):wait()
	if r_format.code ~= 0 then
		vim.notify("mdformat failed (" .. r_format.code .. "):\n" .. r_format.stderr)
	end
	local formatted_lines = {}
	for line in io.lines(tempname) do
		formatted_lines[#formatted_lines + 1] = line
	end
	os.remove(tempname)
	vim.api.nvim_buf_set_lines(0, 0, -1, false, formatted_lines)

	vim.fn.winrestview(view)
end

vim.opt_local.tabstop = 2 -- CommonMark expects two spaces for indentation
vim.opt_local.shiftwidth = 0
vim.opt_local.softtabstop = -1
vim.opt_local.expandtab = true -- Change tabs to spaces
-- Nothing in the gutter, except sign columns if necessary
vim.opt_local.number = false
vim.opt_local.relativenumber = false
vim.opt_local.signcolumn = "auto"
vim.opt_local.foldcolumn = "0"
--
vim.opt_local.textwidth = vim.g.dotfiles.textwidth.markdown -- gw wraps at this value
-- There is a bug where folded fenced code blocks will be completely invisible
-- But (apparently) it only happen when there is a # heading (and no ## heading, no ### heading etc)
-- Probably caused by tree-sitter parsing, but it's not really a problem, since adding a ## heading fixes it
-- Just remember this and don't try to fix it again!
vim.opt_local.foldlevel = 1
vim.opt_local.complete = {
	"o", -- 'omnifunc'
	".", -- current buffer
}
-- Visible link labels
vim.api.nvim_set_hl(0, "@markup.link.label.markdown_inline", { underline = true, update = true })

vim.b.autoformat = true
vim.b.format_func = format

local function item_range()
	local cursor_row = vim.api.nvim_win_get_cursor(0)[1]
	local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false)

	-- Walk up to the task bullet. Any other bullet, or a line that's neither
	-- blank nor indented, means we're not inside a task item.
	local start_row = cursor_row
	while start_row >= 1 do
		local line = lines[start_row]
		if line:match("^%s*%- %[[ Xx]%] ") then
			break
		end
		if line:match("^%s*%- ") then
			return nil
		end
		if not (line:match("^%s*$") or line:match("^%s+%S")) then
			return nil
		end
		start_row = start_row - 1
	end
	if start_row < 1 then
		return nil
	end

	-- Walk down through continuation paragraphs. Blank lines are inside the
	-- item as long as a later line is still indented to the content column.
	local bullet_indent = #lines[start_row]:match("^(%s*)")
	local cont_min = bullet_indent + 2
	local end_row = start_row
	for r = start_row + 1, #lines do
		local line = lines[r]
		if line:match("^%s*$") then
			-- blank, keep scanning
		elseif line:match("^%s*%- ") then
			break
		elseif #line:match("^(%s*)") < cont_min then
			break
		else
			end_row = r
		end
	end

	return start_row, end_row
end

local function check_checklist_item()
	local start_row, end_row = item_range()
	if not (start_row and end_row) then
		return
	end
	local lines = vim.api.nvim_buf_get_lines(0, start_row - 1, end_row, false)
	if not lines[1]:match("^%s*%- %[ %] ") then
		return
	end
	for i, line in ipairs(lines) do
		if i == 1 then
			lines[i] = line:gsub("^(%s*%- )%[ %] (.-)(%s*\\?)$", "%1[X] ~%2~%3", 1)
		elseif not line:match("^%s*$") then
			lines[i] = line:gsub("^(%s*)(.-)(%s*\\?)$", "%1~%2~%3", 1)
		end
	end
	vim.api.nvim_buf_set_lines(0, start_row - 1, end_row, false, lines)
end

local function uncheck_checklist_item()
	local start_row, end_row = item_range()
	if not (start_row and end_row) then
		return
	end
	local lines = vim.api.nvim_buf_get_lines(0, start_row - 1, end_row, false)
	if not lines[1]:match("^%s*%- %[X%] ") then
		return
	end
	for i, line in ipairs(lines) do
		if i == 1 then
			lines[i] = line:gsub("%[X%]", "[ ]", 1):gsub("~", "", 2)
		elseif not line:match("^%s*$") then
			lines[i] = line:gsub("~", "", 2)
		end
	end
	vim.api.nvim_buf_set_lines(0, start_row - 1, end_row, false, lines)
end

vim.api.nvim_buf_create_user_command(0, "MarkdownListCheck", check_checklist_item, { desc = "Check checklist item" })
vim.api.nvim_buf_create_user_command(
	0,
	"MarkdownListUncheck",
	uncheck_checklist_item,
	{ desc = "Uncheck checklist item" }
)