nvim-ufo icon indicating copy to clipboard operation
nvim-ufo copied to clipboard

implement zr and zm more like default behavior

Open fisher-j opened this issue 2 years ago • 11 comments

Feature description

Related to #62, I would like it if zr would decrease the amount of folding and zm would increase the amount of folding.

Describe the solution you'd like

Starting with no folds, zm would need to be aware of the maximum fold level currently used in a document and incrementally reduce this value. The foldlevel could possibly be tracked by a buffer local variable (vim.b.ufo_foldlevel?), similar to foldlevel.

zr would then increment vim.b.ufo_foldlevel up to the maximum currently used in the document.

Additional context

Thank you for your work on this plugin, it has made it possible for me to use folds on large markdown documents with many code chunks, where before it was impossibly slow!

fisher-j avatar Aug 24 '23 17:08 fisher-j

~~vim.b.ufo_foldlevel~~ -> vim.w.ufo_foldlevel

Have you encountered any performance issues by customizing yourself? IMO, the built-in zr and zm behaviors are not good workflow.

Maybe enhance ufo to preview the folded/unfolded level code in the future.

kevinhwang91 avatar Aug 24 '23 23:08 kevinhwang91

I agree this should be easy to do, but I don't know how to find the greatest fold level of the document.

Is there a way of querying what the largest fold level is for a document?

On Thu, Aug 24, 2023 at 4:24 PM Kevin Hwang @.***> wrote:

vim.b.ufo_foldlevel -> vim.w.ufo_foldlevel

Have you encountered any performance issues by customizing yourself? IMO, the built-in zr and zm behaviors are not good workflow.

Maybe enhance ufo to preview the folded/unfolded level code in the future.

— Reply to this email directly, view it on GitHub https://github.com/kevinhwang91/nvim-ufo/issues/150#issuecomment-1692538787, or unsubscribe https://github.com/notifications/unsubscribe-auth/APBWHQKDA2WB6BYF4CVEGOLXW7PEVANCNFSM6AAAAAA35MGRIU . You are receiving this because you authored the thread.Message ID: @.***>

fisher-j avatar Aug 24 '23 23:08 fisher-j

Have you understood the comment https://github.com/kevinhwang91/nvim-ufo/issues/62#issuecomment-1207198496 ?

kevinhwang91 avatar Aug 24 '23 23:08 kevinhwang91

Yes, I see you say that incremental folding and virtual_foldlevel tracking would be hard to implement. In that case, I'm can deal with working with 1zm 2zm...

On Thu, Aug 24, 2023 at 4:50 PM Kevin Hwang @.***> wrote:

Have you understood the comment #62 (comment) https://github.com/kevinhwang91/nvim-ufo/issues/62#issuecomment-1207198496 ?

— Reply to this email directly, view it on GitHub https://github.com/kevinhwang91/nvim-ufo/issues/150#issuecomment-1692558334, or unsubscribe https://github.com/notifications/unsubscribe-auth/APBWHQK7I7BP5V6EQBMJBF3XW7SF3ANCNFSM6AAAAAA35MGRIU . You are receiving this because you authored the thread.Message ID: @.***>

fisher-j avatar Aug 25 '23 00:08 fisher-j

Hey @fisher-j and anyone else who comes across this.

I've implemented this in my Neovim config somewhat and thus far it's been working well enough for me.

Here's the code:

-- Ensure our ufo foldlevel is set for the buffer
vim.api.nvim_create_autocmd("BufReadPre", {
    callback = function()
        vim.b.ufo_foldlevel = 0
    end
})

---@param num integer Set the fold level to this number
local set_buf_foldlevel = function(num)
    vim.b.ufo_foldlevel = num
    require("ufo").closeFoldsWith(num)
end

---@param num integer The amount to change the UFO fold level by
local change_buf_foldlevel_by = function(num)
    local foldlevel = vim.b.ufo_foldlevel or 0
    -- Ensure the foldlevel can't be set negatively
    if foldlevel + num >= 0 then
        foldlevel = foldlevel + num
    else
        foldlevel = 0
    end
    set_buf_foldlevel(foldlevel)
end

-- Keymaps
vim.keymap.set("n", "zm", function()
    local count = vim.v.count
    if count == 0 then
        count = 1
    end
    change_buf_foldlevel_by(-(count))
end, { desc = "UFO: Fold More" })

vim.keymap.set("n", "zr", function()
    local count = vim.v.count
    if count == 0 then
        count = 1
    end
    change_buf_foldlevel_by(count)
end, { desc = "UFO: Fold Less" })

-- 99% sure `zS` isn't mapped by default
vim.keymap.set("n", "zS", function()
    if vim.v.count == 0 then
        vim.notify("No foldlevel given to set!", vim.log.levels.WARN)
    else
        set_buf_foldlevel(vim.v.count)
    end
end, { desc = "UFO: Set Foldlevel" })

Mostly I've just written a wrapper around require('ufo').closeFoldsWith to keep track of a separate ufo_foldlevel buffer variable.

If you're curious, I've implemented it into my config here. I use lazy.nvim to manage my plugins so I set my keybindings through lazy's keys table. That's the only significant difference between the code above and my code in terms of the folding.

I'm sure there's problems that can be caused/extend from this, but thus far it seems to be working quite well for me. YMMV.

Hope someone else finds this helpful 🙂.

PriceHiller avatar Dec 22 '23 17:12 PriceHiller

Thanks for this code snippet @treatybreaker ! I am looking to improve it a bit, @kevinhwang91 is there a way internally to retrieve the max fold level of a buffer? I'm curious if this information is stored anywhere when setting up the folds

mehalter avatar Jan 25 '24 11:01 mehalter

Here is the code I'm trying to get working currently:

-- return the max fold level of the buffer (for now doing the opposite and folding incrementally is unbounded)
-- Also jarring if you start folding incrementally after opening all folds
local function max_level()
  -- return vim.wo.foldlevel -- find a way for this to return max fold level
  return 0
end

---Set the fold level to the provided value and store it locally to the buffer
---@param num integer the fold level to set
local function set_fold(num)
  -- vim.w.ufo_foldlevel = math.min(math.max(0, num), max_level()) -- when max_level is implemneted properly
  vim.b.ufo_foldlevel = math.max(0, num)
  require("ufo").closeFoldsWith(vim.b.ufo_foldlevel)
end

---Shift the current fold level by the provided amount
---@param dir number positive or negative number to add to the current fold level to shift it
local shift_fold = function(dir) set_fold((vim.b.ufo_foldlevel or max_level()) + dir) end

-- when max_level is implemented properly
-- vim.keymap.set("n", "zR", function() set_win_fold(max_level()) end, { desc = "Open all folds" })
vim.keymap.set("n", "zR", require("ufo").openAllFolds, { desc = "Open all folds" })

vim.keymap.set("n", "zM", function() set_fold(0) end, { desc = "Close all folds" })

vim.keymap.set("n", "zr", function() shift_fold(vim.v.count == 0 and 1 or vim.v.count) end, { desc = "Fold less" })

vim.keymap.set("n", "zm", function() shift_fold(-(vim.v.count == 0 and 1 or vim.v.count)) end, { desc = "Fold more" })

If we had a way to dynamically fetch the max fold level from nvim-ufo it would be completely seamless when moving between using zr/zm alongside zR and zM. Let me know what you think and if anyone knows of this sort of information being available!

mehalter avatar Jan 25 '24 12:01 mehalter

Here is the code I'm trying to get working currently:

-- return the max fold level of the buffer (for now doing the opposite and folding incrementally is unbounded)
-- Also jarring if you start folding incrementally after opening all folds
local function max_level()
  -- return vim.wo.foldlevel -- find a way for this to return max fold level
  return 0
end

---Set the fold level to the provided value and store it locally to the buffer
---@param num integer the fold level to set
local function set_fold(num)
  -- vim.w.ufo_foldlevel = math.min(math.max(0, num), max_level()) -- when max_level is implemneted properly
  vim.b.ufo_foldlevel = math.max(0, num)
  require("ufo").closeFoldsWith(vim.b.ufo_foldlevel)
end

---Shift the current fold level by the provided amount
---@param dir number positive or negative number to add to the current fold level to shift it
local shift_fold = function(dir) set_fold((vim.b.ufo_foldlevel or max_level()) + dir) end

-- when max_level is implemented properly
-- vim.keymap.set("n", "zR", function() set_win_fold(max_level()) end, { desc = "Open all folds" })
vim.keymap.set("n", "zR", require("ufo").openAllFolds, { desc = "Open all folds" })

vim.keymap.set("n", "zM", function() set_fold(0) end, { desc = "Close all folds" })

vim.keymap.set("n", "zr", function() shift_fold(vim.v.count == 0 and 1 or vim.v.count) end, { desc = "Fold less" })

vim.keymap.set("n", "zm", function() shift_fold(-(vim.v.count == 0 and 1 or vim.v.count)) end, { desc = "Fold more" })

If we had a way to dynamically fetch the max fold level from nvim-ufo it would be completely seamless when moving between using zr/zm alongside zR and zM. Let me know what you think and if anyone knows of this sort of information being available!

May take time to explore.

kevinhwang91 avatar Feb 16 '24 13:02 kevinhwang91

It looks like it is possible for nvim-ufo to provide the said vim.w.ufo_foldlevel variable right now, by adding some code that increments and decrements the variable inside the existing APIs. Much like the @PriceHiller's implementation https://github.com/kevinhwang91/nvim-ufo/issues/150#issuecomment-1867928299 but embedding the whole tracking code inside the API itself so that users do not need an extra wrapper.

Of course, this is not "directly" fetching the actual max fold level. (currently the only way to do that is by analyzing the overlapping ranges of ufo.getFolds()) Just carefully tracking the invocation of folds to accurately "guess" the current fold level.

In a way this could be considered not very elegant. However, it probably works and definitely does not hinder the performance. There may be edge cases that might cause the tracking variable to go out of sync with the actual fold level - but for now I cannot think of any.

So, is there a particular reason this solution cannot be integrated in the nvim-ufo's code? Are you not satisfied with the said lack of "elegance" or the potential of edge cases? Perhaps working on more clean and robust solution on your mind?

WieeRd avatar Jun 25 '24 10:06 WieeRd

So, is there a particular reason this solution cannot be integrated in the nvim-ufo's code? Are you not satisfied with the said lack of "elegance" or the potential of edge cases? Perhaps working on more clean and robust solution on your mind?

Have you seen zR doc? zR will make foldlevel become the highest fold level and ufo doesn't know the highest fold level. Even from getFolds method, ufo knows all ranges, but the user can type zf to create a new fold that makes the trace of vim.w.ufo_foldlevel invalid.

@mehalter has mentioned we need a way to get the highest level. Frankly speaking, this is a laborious task. I have no time to explore the upstream source code and port it to ufo.

kevinhwang91 avatar Jun 25 '24 14:06 kevinhwang91

I have completely forgotten about the fact that foldmethod remains "manual" because I never manually created folds while using nvim-ufo. Welp, fair enough. In my use case it'd work fine because I do not use zf but I agree this method is flawed and shouldn't be included in the plugin code.

WieeRd avatar Jun 25 '24 15:06 WieeRd