kevinhwang91 / nvim-ufo

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

The fold text of the same buffer is displayed in two different windows #131

Closed lu5je0 closed 10 months ago

lu5je0 commented 1 year ago

Neovim version (nvim -v | head -n1)

NVIM v0.9.0

Operating system/version

macOS 13.3.1

How to reproduce the issue

cat mini.lua

-- Use Vim packages install the plugin, also work with some plugins manager such as packer.nvim
vim.o.packpath = '~/.local/share/nvim/site'
vim.cmd('packadd promise-async')
vim.cmd('packadd nvim-ufo')

-- Setting
vim.o.foldcolumn = '1'
vim.o.foldlevel = 99
vim.o.foldlevelstart = -1
vim.o.foldenable = true

local ufo = require('ufo')
ufo.setup()
vim.keymap.set('n', 'zR', ufo.openAllFolds)
vim.keymap.set('n', 'zM', ufo.closeAllFolds)

nvim --clean +'so mini.lua'

1. 2. 3. ...

Expected behavior

The fold text is only displayed in the window that closes the fold.

Actual behavior

image
kevinhwang91 commented 1 year ago

It's the limitation of ufo. To render folded lines use extmark which is buffer related. No way to make the same buffer with different extmarks.

vurentjie commented 1 year ago

I cannot help with implementation but can offer some info from my own experiments that may help, below sample code using "ephemeral" extmark.

--vim -u NONE 
--e <THIS_FILE>
--so % 
--fold should be setup
--vsp #to show buffer in another window 

vim.cmd('syntax on')

vim.treesitter.start()

vim.opt.splitright = true
vim.opt.splitbelow = true
vim.opt.foldlevel = 99
vim.opt.foldlevelstart = 99
vim.opt.foldenable = true 
vim.opt.number = true 
vim.opt.foldcolumn = '1' 
vim.opt.foldmethod = 'expr' 
vim.opt.foldexpr = 'v:lua.vim.treesitter.foldexpr()' 
vim.opt.foldtext = '' 
vim.opt.fillchars = {
  fold = ' '
}

vim.cmd([[au WinNew,BufWinEnter * exec 'normal! zx'|exec 'normal! zR']])
vim.cmd('normal! zx')
vim.cmd('normal! zR')

local ns = vim.api.nvim_create_namespace('')

local function get_or_create_virtext(bufnr, lnum)
  --can add more complex hl with treesitter and possibly cache results here and elsewhere
  local text = vim.api.nvim_buf_get_lines(bufnr, lnum - 1, lnum, false)[1] or ''
  return { {  text .. ' …', 'Folded' } }
end 

vim.api.nvim_set_decoration_provider(ns, {
  on_win = function(_, winid, bufnr, topline, botline_guess)
    vim.api.nvim_win_call(winid, function()
      local first, last = vim.fn.line('w0'), vim.fn.line('w$')
      for lnum = first,last do
        local foldline = vim.fn.foldclosed(lnum)
        --range w0 to w$ my include lines that are hidden in folds, so skip those
        if foldline == lnum then 
          local fold_virttext = get_or_create_virtext(bufnr, lnum)
          --the key thing here is "ephemeral", 
          --which make this extmark only visible on the current window
          --and redrawn each cycle
          --another window with same buffer will have different extmarks
          vim.api.nvim_buf_set_extmark(bufnr, ns, lnum - 1, 0, {
            end_row = lnum - 1,
            end_col = 0,
            virt_text = fold_virttext,
            virt_text_win_col = 0,
            priority = 100,
            hl_mode = 'combine',
            ephemeral = true,
          })
        end
      end
    end)
  end,
})

https://github.com/kevinhwang91/nvim-ufo/assets/639806/510b00be-043e-4731-b17e-a96ca46b361b

kevinhwang91 commented 1 year ago

I cannot help with implementation but can offer some info from my own experiments that may help, below sample code using "ephemeral" extmark.

--vim -u NONE 
--e <THIS_FILE>
--so % 
--fold should be setup
--vsp #to show buffer in another window 

vim.cmd('syntax on')

vim.treesitter.start()

vim.opt.splitright = true
vim.opt.splitbelow = true
vim.opt.foldlevel = 99
vim.opt.foldlevelstart = 99
vim.opt.foldenable = true 
vim.opt.number = true 
vim.opt.foldcolumn = '1' 
vim.opt.foldmethod = 'expr' 
vim.opt.foldexpr = 'v:lua.vim.treesitter.foldexpr()' 
vim.opt.foldtext = '' 
vim.opt.fillchars = {
  fold = ' '
}

vim.cmd([[au WinNew,BufWinEnter * exec 'normal! zx'|exec 'normal! zR']])
vim.cmd('normal! zx')
vim.cmd('normal! zR')

local ns = vim.api.nvim_create_namespace('')

local function get_or_create_virtext(bufnr, lnum)
  --can add more complex hl with treesitter and possibly cache results here and elsewhere
  local text = vim.api.nvim_buf_get_lines(bufnr, lnum - 1, lnum, false)[1] or ''
  return { {  text .. ' …', 'Folded' } }
end 

vim.api.nvim_set_decoration_provider(ns, {
  on_win = function(_, winid, bufnr, topline, botline_guess)
    vim.api.nvim_win_call(winid, function()
      local first, last = vim.fn.line('w0'), vim.fn.line('w$')
      for lnum = first,last do
        local foldline = vim.fn.foldclosed(lnum)
        --range w0 to w$ my include lines that are hidden in folds, so skip those
        if foldline == lnum then 
          local fold_virttext = get_or_create_virtext(bufnr, lnum)
          --the key thing here is "ephemeral", 
          --which make this extmark only visible on the current window
          --and redrawn each cycle
          --another window with same buffer will have different extmarks
          vim.api.nvim_buf_set_extmark(bufnr, ns, lnum - 1, 0, {
            end_row = lnum - 1,
            end_col = 0,
            virt_text = fold_virttext,
            virt_text_win_col = 0,
            priority = 100,
            hl_mode = 'combine',
            ephemeral = true,
          })
        end
      end
    end)
  end,
})

fold_test._rec.mp4

Thanks for the helps. Unfortunately, ufo uses non-ephemeral extmark to cache fold info and will make perf regression without cache.

vurentjie commented 1 year ago

sure perhaps you can wait for nvim_win_set_extmark to be implemented here: https://github.com/neovim/neovim/issues/19654

kevinhwang91 commented 10 months ago

nightly with the latest codebase of ufo should work.