epwalsh / obsidian.nvim

Obsidian 🤝 Neovim
Apache License 2.0
3.3k stars 150 forks source link

Checkbox toggling and table insertion #631

Closed malko42 closed 1 week ago

malko42 commented 2 weeks ago

🚀 The feature, motivation and pitch

First and foremost thank you for that amazing plugin which enables me to not leave my beloved editor when writing notes! Great work!

Now, I have been using some helper functions on top on that plugin in my daily workflow and I was wondering if that would be a good idea to push them upstream or if it's out of scope since those helper functions are not Obsidian specific but rather Markdown related.

These are the functions I use:

M._add_checkbox = function(character, line_num)
    local line = vim.api.nvim_buf_get_lines(0, line_num - 1, line_num, false)[1]

    local checkbox_pattern = "^%s*- %[.] "
    local checkbox = character or " "

    if not string.match(line, checkbox_pattern) then
        local unordered_list_pattern = "^(%s*)[-*+] (.*)"
        if string.match(line, unordered_list_pattern) then
            line = string.gsub(line, unordered_list_pattern, "%1- [ ] %2")
        else
            line = string.gsub(line, "^(%s*)", "%1- [ ] ")
        end
    end
    local capturing_checkbox_pattern = "^(%s*- %[).(%] )"
    line = string.gsub(line, capturing_checkbox_pattern, "%1" .. checkbox .. "%2")

    -- 0-indexed
    vim.api.nvim_buf_set_lines(0, line_num - 1, line_num, true, { line })
end

M._remove_checkbox = function(line_num)
    local line = vim.api.nvim_buf_get_lines(0, line_num - 1, line_num, false)[1]
    local checkbox_pattern = "^%s*- %[.]. "
    local capturing_checkbox_pattern = "^(%s*-) %[.%] (.*)"
    line = string.gsub(line, capturing_checkbox_pattern, "%1 %2")
    line = string.gsub(line, checkbox_pattern, "")
    -- 0-indexed
    vim.api.nvim_buf_set_lines(0, line_num - 1, line_num, true, { line })
end

M.toggle_checkbox = function(character)
    -- Check if we are in visual line mode
    local mode = vim.api.nvim_get_mode().mode

    local toggle_or_remove = function(character, line_num)
        if (character == nil) then
            -- Remove checkbox
            M._remove_checkbox(line_num)
        else
            -- Add checkbox
            M._add_checkbox(character, line_num)
        end
    end

    if mode == 'V' or mode == 'v' then
        -- Get the range of selected lines
        vim.cmd([[execute "normal! \<ESC>"]])
        local vstart = vim.fn.getcharpos("'<")
        local vend = vim.fn.getcharpos("'>")

        local line_start = vstart[2]
        local line_end = vend[2]

        -- Iterate over each line in the range and apply the transformation
        for line_num = line_start, line_end do
            toggle_or_remove(character, line_num)
        end
    else
        -- Normal mode
        -- Allow line_num to be optional, defaulting to the current line if not provided (normal mode)
        local line_num = unpack(vim.api.nvim_win_get_cursor(0))
        toggle_or_remove(character, line_num)
    end
end

M.insert_markdown_table = function()
    local get_size = function()
        local INPUT_CANCELLED = "~~~INPUT-CANCELLED~~~"
        local input = vim.fn.input { prompt = "Size of the table - columns by rows (e.g. 2x3)", cancelreturn = INPUT_CANCELLED }
        if input == INPUT_CANCELLED then
            -- early return if the user cancels the input
            return nil, nil
        end
        local string_column, string_row = input:match("([^x]+)x([^x]+)")
        return tonumber(string_column), tonumber(string_row)
    end
    local columns, rows = get_size();
    if columns == nil or rows == nil or columns < 1 or rows < 1 then
        vim.api.nvim_err_writeln("Invalid input. Please provide a valid size (e.g. 2x3)")
        return
    end

    -- Create a row
    -- @param row number
    -- @param header boolean
    local create_row = function(length, header)
        local row = "|"
        for _ = 1, length do
            row = row .. (header and " ---" or "   ") .. " |"
        end
        return row
    end

    local rows_table = {}

    -- First row with header row
    table.insert(rows_table, create_row(columns, false))
    table.insert(rows_table, create_row(columns, true))
    for _ = 2, rows do
        table.insert(rows_table, create_row(columns, false))
    end

    vim.api.nvim_put(rows_table, "l", true, false)
end

toggle_checkbox is used with a shortcut to turn the line under cursor (or the selection) into an unordered todo item insert_markdown_table inserts at the current cursor position a table of any size.

Do you think that would make sense to include them into that plugin? Any improvement suggestions?

Alternatives

No response

Additional context

No response

epwalsh commented 1 week ago

Hey @malko42 we already have functionality for toggling checkboxes, but maybe it could be improved. I think table functionality is out of scope, but it would be great if you added that to the cookbook in case others find it useful. Thanks!

malko42 commented 1 week ago

I converted this discussion into 2 separate cookbooks discussions. Thank you for suggesting this.