From 47c9f1eb7433c295aa131063583a13ab25123824 Mon Sep 17 00:00:00 2001 From: Stefan Rakel Date: Fri, 27 Oct 2023 09:43:17 +0200 Subject: [PATCH] Create checkbox plugin from script in my personal config --- plugin/checkbox.lua | 177 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 plugin/checkbox.lua diff --git a/plugin/checkbox.lua b/plugin/checkbox.lua new file mode 100644 index 0000000..3adc1e4 --- /dev/null +++ b/plugin/checkbox.lua @@ -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", "x", switchAndUpdateCheckbox)