gaoDean / autolist.nvim

Automatic list continuation and formatting for neovim, powered by lua
MIT License
248 stars 6 forks source link

Create new checkbox instead of adding numbers #67

Open benbrastmckie opened 1 year ago

benbrastmckie commented 1 year ago

Suppose that I have a list like:

- [x] something done
- [ ] something undone 
  - some other point
  - something further
  - etc.

Now say that I am hovering on the "some other point" line and run the invert_entry function with the mappings provided. Currently autolist turns it into an numbered list to all lines at that indentation. Intuitively what I would expect (and want) is that it would add the empty box on just the current line so that it looks like - [x].

I should say that I like how autolist toggles through just - [ ] and - [x] with invert_entry instead of also including - with no box in the cycle. However, when invert_entry is run on - with no box, it would be great it if added a checked box so that it looks like - [x]. If one wanted to remove a box altogether (empty or otherwise), that would have to be handled by a separate function which removes the box (empty or otherwise). In practice, I mostly don't remove boxes once they are added, so this further function is not super important for me.

It would also be helpful to have a function which toggles between unnumbered dashes like - and a numbered list. For instance, consider the list (A) below:

- [x] something done
- [ ] something undone 
- something with no box

Were I to run the imagined toggle numbering function, I think the most natural result would be (B):

1. [x] something done
2. [ ] something undone 
3. something with no box

Toggling number again would then return the (A) list. To summarise, here is the feature set I'm imagining:

Hope that all makes sense, and thanks again for a great plugin!

gaoDean commented 1 year ago

Sorry for the very late reply, ive been busy lately.

Have you tried org mode? I am proposing to use a sort of org-mode way of doing lists.

There will be a function purely for cycling the list types

There will be a mapping that when pressed on top of a checkbox, completes it. (normally bound to enter in emacs)

When pressing enter on a list, the new created bullet will inherit the last line's properties (checkbox or no checkbox)

There will not be a function for removing checkboxes. this is easily doable in vim, and when creating a new bullet, not using checkboxes is as easy as deleting it. Then with the above changes, the next new bullet will not have checkboxes, as it inherits the no checkbox property.

kraxli commented 1 year ago

Suppose that I have a list like:

- [x] something done
- [ ] something undone 
  - some other point
  - something further
  - etc.

Now say that I am hovering on the "some other point" line and run the invert_entry function with the mappings provided. Currently autolist turns it into an numbered list to all lines at that indentation. Intuitively what I would expect (and want) is that it would add the empty box on just the current line so that it looks like - [x].

I should say that I like how autolist toggles through just - [ ] and - [x] with invert_entry instead of also including - with no box in the cycle. However, when invert_entry is run on - with no box, it would be great it if added a checked box so that it looks like - [x]. If one wanted to remove a box altogether (empty or otherwise), that would have to be handled by a separate function which removes the box (empty or otherwise). In practice, I mostly don't remove boxes once they are added, so this further function is not super important for me.

It would also be helpful to have a function which toggles between unnumbered dashes like - and a numbered list. For instance, consider the list (A) below:

- [x] something done
- [ ] something undone 
- something with no box

Were I to run the imagined toggle numbering function, I think the most natural result would be (B):

1. [x] something done
2. [ ] something undone 
3. something with no box

Toggling number again would then return the (A) list. To summarise, here is the feature set I'm imagining:

* toggle checkmark (adding a box if none is present)

* remove box (whether checked or not)

* toggle numbering

Hope that all makes sense, and thanks again for a great plugin!

Not exactly what you are looking for, but I added a small enhancement to add a checkbox if there is non present in a list:

function handle_checkbox()
  local config = require("autolist.config")
  local auto = require("autolist.auto")

  local checkbox_pattern = " [ ]"

  local filetype_lists = config.lists[vim.bo.filetype]
  local line = vim.fn.getline(".")

  for i, list_pattern in ipairs(filetype_lists) do
    local list_item = line:match("^%s*" .. list_pattern .. "%s*")  -- only bullet, no checkbox
    list_item = list_item:gsub("%s+", "")
    local is_list_item = list_item ~= nil -- only bullet, no checkbox
    local is_checkbox_item = line:match("^%s*" .. list_pattern .. "%s*" .. "%[.%]" .. "%s*") ~= nil -- bullet and checkbox  

    if is_list_item == true and is_checkbox_item == false then
      vim.fn.setline(".", (line:gsub(list_item, list_item .. checkbox_pattern, 1)))
      local cursor_pos = vim.api.nvim_win_get_cursor(0)
      vim.api.nvim_win_set_cursor(0, {cursor_pos[1], cursor_pos[2] + checkbox_pattern:len()})
      goto continue
    else
      auto.toggle_checkbox()
      goto continue
    end

  end
  ::continue::
end

May be it is useful for you as well.

benbrastmckie commented 1 year ago

Amazing! That's super convenient. Works great!

benbrastmckie commented 1 year ago

I've been using the handle_checkbox() function you included and noticed that it does not behave nicely if we have a list like:

  1. some
  2. thing

The behaviour that would make sense to me if I were on the first node is to return:

  1. [x] some
  2. thing

Is this something difficult to fix, or would a minor tweak to what you provided fix it. Thanks again for your help!

kraxli commented 1 year ago

I have used it only with - or *. But hopefully the following does the job for different list types (it is not the nicest code, sorry):

function M.handle_checkbox()
  local config = require("autolist.config")
  local auto = require("autolist.auto")

  local checkbox_pattern = " [ ]"

  local filetype_list = config.lists[vim.bo.filetype]
  local line = vim.fn.getline(".")

  for i, list_pattern in ipairs(filetype_list) do
    local list_item = line:match("^%s*" .. list_pattern .. "%s*")  -- only bullet, no checkbox
    if list_item == nil then goto continue_for_loop end
    list_item = list_item:gsub("%s+", "")
    local is_list_item = list_item ~= nil -- only bullet, no checkbox
    local is_checkbox_item = line:match("^%s*" .. list_pattern .. "%s*" .. "%[.%]" .. "%s*") ~= nil -- bullet and checkbox

    if is_list_item == true and is_checkbox_item == false then
      list_item = list_item:gsub('%)', '%%)')
      vim.fn.setline(".", (line:gsub(list_item, list_item .. checkbox_pattern, 1)))

      local cursor_pos = vim.api.nvim_win_get_cursor(0)
      if cursor_pos[2] > 0 then
        vim.api.nvim_win_set_cursor(0, {cursor_pos[1], cursor_pos[2] + checkbox_pattern:len()})
      end
      goto continue
    else
      auto.toggle_checkbox()
      goto continue
    end

    ::continue_for_loop::
  end
  ::continue::
end
kraxli commented 1 year ago

I've been using the handle_checkbox() function you included and noticed that it does not behave nicely if we have a list like:

1. some

2. thing

The behaviour that would make sense to me if I were on the first node is to return:

1. [x]  some

2. thing

Is this something difficult to fix, or would a minor tweak to what you provided fix it. Thanks again for your help!

not sure if you had the chance to test the updated code above

benbrastmckie commented 9 months ago

not sure if you had the chance to test the updated code above

Thanks @kraxli I tried again and got your updated code working. I then modified it to this monstrosity (I am very much a beginner at Lua):

    function HandleCheckbox()
      local config = require("autolist.config")
      local auto = require("autolist.auto")
      local emptybox_pattern = " [ ]"
      local progbox_pattern = " [.]"
      local closebox_pattern = " [:]"
      local donebox_pattern = " [x]"
      local filetype_list = config.lists[vim.bo.filetype]
      local line = vim.fn.getline(".")

      for i, list_pattern in ipairs(filetype_list) do
        local list_item = line:match("^%s*" .. list_pattern .. "%s*")
        -- only bullet, no checkbox

        if list_item == nil then goto continue_for_loop end
        list_item = list_item:gsub("%s+", "")

        local is_list_item = list_item ~= nil
        -- only bullet, no checkbox
        local is_checkbox_item = line:match("^%s*" .. list_pattern .. "%s*" .. "%[.%]" .. "%s*") ~= nil
        -- bullet and checkbox
        local is_emptybox_item = line:match("^%s*" .. list_pattern .. "%s*" .. "%[%s%]" .. "%s*") ~= nil
        -- bullet and emptybox
        local is_progbox_item = line:match("^%s*" .. list_pattern .. "%s*" .. "%[%.%]" .. "%s*") ~= nil
        -- bullet and progbox
        local is_closebox_item = line:match("^%s*" .. list_pattern .. "%s*" .. "%[%:%]" .. "%s*") ~= nil
        -- bullet and closebox
        local is_donebox_item = line:match("^%s*" .. list_pattern .. "%s*" .. "%[x%]" .. "%s*") ~= nil
        -- bullet and closebox

        if is_list_item == true and is_checkbox_item == false then
          list_item = list_item:gsub('%)', '%%)')
          vim.fn.setline(".", (line:gsub(list_item, list_item .. emptybox_pattern, 1)))

          local cursor_pos = vim.api.nvim_win_get_cursor(0)
          if cursor_pos[2] > 0 then
            vim.api.nvim_win_set_cursor(0, { cursor_pos[1], cursor_pos[2] + emptybox_pattern:len() })
          end
          goto continue
        elseif is_list_item == true and is_emptybox_item == true then
          list_item = list_item:gsub('%)', '%%)')
          vim.fn.setline(".", (line:gsub(" %[%s%]", progbox_pattern, 1)))
          goto continue
        elseif is_list_item == true and is_progbox_item == true then
          list_item = list_item:gsub('%)', '%%)')
          vim.fn.setline(".", (line:gsub(" %[%.%]", closebox_pattern, 1)))
          goto continue
        elseif is_list_item == true and is_closebox_item == true then
          list_item = list_item:gsub('%)', '%%)')
          vim.fn.setline(".", (line:gsub(" %[%:%]", donebox_pattern, 1)))
          goto continue
        elseif is_list_item == true and is_donebox_item == true then
          list_item = list_item:gsub('%)', '%%)')
          vim.fn.setline(".", (line:gsub(" %[x%]", "", 1)))

          local cursor_pos = vim.api.nvim_win_get_cursor(0)
          -- vim.cmd("norm! 0 | f]")
          if cursor_pos[2] > donebox_pattern:len() then
            vim.api.nvim_win_set_cursor(0, { cursor_pos[1], cursor_pos[2] - donebox_pattern:len() })
          end
          goto continue
        else
          auto.toggle_checkbox()
          goto continue
        end
        ::continue_for_loop::
      end
      ::continue::
    end

It works, cycling checkboxes through the following:

- item
- [ ] undone
- [.] started
- [:] close
- [x] done
- item

And round and round. I noticed that my linter says, "Unused local 'i'" at the beginning of the for-loop. There is also some funny business about where the cursor gets moved to if it is too close to the start of a line. In any case, I'd love any help improving this if someone knows how.