1
0

Create checkbox plugin from script in my personal config

This commit is contained in:
Stefan Rakel
2023-10-27 09:43:17 +02:00
parent 54024db83e
commit 47c9f1eb74

177
plugin/checkbox.lua Normal file
View File

@@ -0,0 +1,177 @@
-- MIT License
--
-- Copyright (c) 2023 Stefan Rakel
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-- copies of the Software, and to permit persons to whom the Software is
-- furnished to do so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in all
-- copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-- SOFTWARE.
local prefixPatterns = {"%-", "%*", "#+", "%->", "=>", "%d+%."}
local skipPatterns = {"%-", "%*", "%->", "=>", "%d+%."}
-- returns indentation level, checked status and text if the line has a checbox
-- nil otherwise
local function getCheckboxFromLine(lineNum)
local line = vim.fn.getline(lineNum)
for _, pat in ipairs(prefixPatterns) do
local pattern = "(%s*)(" .. pat .. "%s*)%[([ xX]?)%](.*)"
local whitespace, before, checked, after = string.match(line, pattern)
if whitespace then
if checked == "x" or checked == "X" then
checked = true
else
checked = false
end
whitespace = string.gsub(whitespace, "\t", " ")
local indentation = string.len(whitespace)
return {lineNum=lineNum, indentation=indentation, checked=checked, before=before, after=after}
end
end
return nil
end
local function writeCheckbox(checkbox)
local checkedString = " "
if checkbox.checked then
checkedString = "X"
end
local newLine = string.rep(" ", checkbox.indentation) .. checkbox.before .. "[" .. checkedString .. "]" .. checkbox.after
vim.fn.setline(checkbox.lineNum, newLine)
end
local function setCheckbox(checkbox, checked)
checkbox.checked = checked
writeCheckbox(checkbox)
end
local function switchOrAddCheckbox(lineNum)
local checkbox = getCheckboxFromLine(lineNum)
if checkbox then
checkbox.checked = not checkbox.checked
writeCheckbox(checkbox)
return checkbox
else
for _, pat in ipairs(prefixPatterns) do
local pattern = "(%s*" .. pat .. "%s*)(.*)"
local before, after = string.match(vim.fn.getline(lineNum), pattern)
if before then
local newLine = before .. "[ ] " .. after
vim.fn.setline(lineNum, newLine)
return getCheckboxFromLine(lineNum)
end
end
return nil
end
end
local function findParent(startCheckbox)
if not startCheckbox then
return false
end
local lineNum = startCheckbox.lineNum
while true do
lineNum = lineNum - 1
local checkbox = getCheckboxFromLine(lineNum)
if not checkbox then
--check skip patterns
local skipped = false
for _, pat in ipairs(skipPatterns) do
if string.match(vim.fn.getline(lineNum), "%s*" .. pat .. ".*") then
skipped = true
end
end
if not skipped then
return false
end
else
if checkbox.indentation < startCheckbox.indentation then
return checkbox
end
end
end
end
local function findChildren(startCheckbox)
if not startCheckbox then
return false
end
local children = {}
local lineNum = startCheckbox.lineNum
while true do
lineNum = lineNum + 1
local checkbox = getCheckboxFromLine(lineNum)
if not checkbox then
--check skip patterns
local skipped = false
for _, pat in ipairs(skipPatterns) do
if string.match(vim.fn.getline(lineNum), "%s*" .. pat .. ".*") then
skipped = true
end
end
if not skipped then
return children
end
else
if checkbox.indentation > startCheckbox.indentation then
table.insert(children, checkbox)
end
end
end
return children
end
local function updateFromChildren(checkbox)
local children = findChildren(checkbox)
if not children then
return
end
local total = 0
local checked = 0
for _, child in ipairs(children) do
if not child.checked then
setCheckbox(checkbox, false)
return
end
end
setCheckbox(checkbox, true)
end
local function updateChildren(checkbox)
local children = findChildren(checkbox)
if not children then
return
end
for _, child in ipairs(children) do
setCheckbox(child, checkbox.checked)
end
end
local function switchAndUpdateCheckbox()
local lineNum = vim.fn.line(".")
checkbox = switchOrAddCheckbox(lineNum)
if checkbox then
-- Update children
updateChildren(checkbox)
-- Traverse parents upwards and update them
local parent = findParent(checkbox)
while parent do
updateFromChildren(parent)
parent = findParent(parent)
end
end
end
vim.keymap.set("n", "<leader>x", switchAndUpdateCheckbox)