kevinhwang91 / nvim-ufo

Not UFO in the sky, but an ultra fold in Neovim.
BSD 3-Clause "New" or "Revised" License
2.16k stars 37 forks source link

implement zr and zm more like default behavior #150

Open fisher-j opened 10 months ago

fisher-j commented 10 months ago

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!

kevinhwang91 commented 10 months ago

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.

fisher-j commented 10 months ago

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: @.***>

kevinhwang91 commented 10 months ago

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

fisher-j commented 10 months ago

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: @.***>

PriceHiller commented 6 months ago

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 🙂.

mehalter commented 5 months ago

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 commented 5 months ago

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!

kevinhwang91 commented 4 months ago

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.

WieeRd commented 1 week ago

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?

kevinhwang91 commented 1 week ago

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.

WieeRd commented 1 week ago

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.