kevinhwang91 / nvim-ufo

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

Support for applying `marker` method in parallel with ufo #90

Open msva opened 1 year ago

msva commented 1 year ago

Feature description

Hi there! Is it somehow possible to implement support to apply methods, that was selected in ufo (ts/lsp) simultaneously with marker (to support vim's default {{{/}}} at the same time)?

Describe the solution you'd like

I'm not sure, how is it supposed to look like if taking {n,}vim's internal marker method, but, probably, it can be made by writing custom function, that implements same behaviour :shrug:

I'll try to invent something myself, but can't promise anything, and would be glad to get some help/code snippets for that.

Or, if that would "internal" functionality of ufo :grinning:

Thanks in advance!

Additional context

No response

kevinhwang91 commented 1 year ago

It's similar to https://github.com/kevinhwang91/nvim-ufo/issues/22.

Two steps:

  1. Need to write a marker provider, you can refer to indent.lua. TBH, it's not hard, just scan content and parse foldmarker, use a stack data struct should be easy enough, I am not very Interested in this feature, so I leave this feature for the someone like you, you needn't to concern the performance for the time being. If there're perf issues I will solve them later.
  2. Enable marker provider, choose one of the two ways:
    1. Register a new provider to mix multiple providers, refer to https://github.com/kevinhwang91/nvim-ufo/blob/9d59d713c484d90790f53045a5b4685fb0686ff6/doc/example.lua#L26-L57 , this way is very flexible;
    2. Add a new option to enable ufo's marker mixed with main or fallback provider automatically if foldmethod == 'marker' , AFAIK, foldmethod == 'marker' is always used with modeline, this way is more elegant than first way IMO.
msva commented 1 year ago

thanks

danpf commented 6 months ago

This was my solution to this problem. it is not perfect, but it works well enough for my needs.

    {
        "kevinhwang91/nvim-ufo",
        event = { "BufRead" },
        dependencies = "kevinhwang91/promise-async",
        config = function()
            vim.o.foldcolumn = "1" -- '0' is not bad
            vim.o.foldlevel = 99 -- Using ufo provider need a large value, feel free to decrease the value
            vim.o.foldlevelstart = 99
            vim.o.foldenable = true

            -- Using ufo provider need remap `zR` and `zM`. If Neovim is 0.6.1, remap yourself
            vim.keymap.set("n", "zR", require("ufo").openAllFolds)
            vim.keymap.set("n", "zM", require("ufo").closeAllFolds)
            vim.keymap.set("n", "zK", function()
                local winid = require("ufo").peekFoldedLinesUnderCursor()
                if not winid then
                    vim.lsp.buf.hover()
                end
            end, { desc = "Peek Fold" })

            local ftMap = {
                git = "",
            }
            local foldingrange = require("ufo.model.foldingrange")
            local bufmanager = require("ufo.bufmanager")

            local CustomMarkerProvider = {}

            function CustomMarkerProvider.getFolds(bufnr)
                local buf = bufmanager:get(bufnr)
                if not buf then
                    return
                end
                local fmrOpen  = vim.opt.foldmarker:get()[1]
                local fmrClose = vim.opt.foldmarker:get()[2]
                local commentstring = vim.opt.commentstring:get()
                if commentstring == "/*%s*/" then
                    -- Hack for c++ and other // and /* */ langs
                    commentstring = "//%s"
                end
                local openRegx = commentstring .. "*.-" .. fmrOpen
                local closeRegx = commentstring .. "*.-" .. fmrClose
                local summaryRegx = openRegx .. "%s*(.*)"
                local lines = buf:lines(1, -1)

                local ranges = {}
                local stack = {}

                for lnum, line in ipairs(lines) do
                    -- Check for start marker
                    if line:match(openRegx) then
                        table.insert(stack, lnum)
                    -- Check for end marker
                    elseif line:match(closeRegx) then
                        local startLnum = table.remove(stack)
                        if startLnum then
                            local summary = lines[startLnum]:match(summaryRegx)
                            table.insert(ranges, foldingrange.new(startLnum - 1, lnum - 1, summary))
                        end
                    end
                end

                return ranges
            end

            local function customizeSelector(bufnr)
                local ranges = CustomMarkerProvider.getFolds(bufnr)
                local maybe_additional_ranges = require("ufo").getFolds(bufnr, "treesitter")
                if next(maybe_additional_ranges) ~= nil then
                    ranges = vim.list_extend(ranges, maybe_additional_ranges)
                else
                    ranges = vim.list_extend(ranges, require("ufo").getFolds(bufnr, "indent"))
                end
                return ranges
            end

            require("ufo").setup({
                provider_selector = function(bufnr, filetype, buftype)
                    return ftMap[filetype] or customizeSelector
                end,
            })
        end,
    },
kevinhwang91 commented 6 months ago

This was my solution to this problem. it is not perfect, but it works well enough for my needs.

  {
      "kevinhwang91/nvim-ufo",
      event = { "BufRead" },
      dependencies = "kevinhwang91/promise-async",
      config = function()
          vim.o.foldcolumn = "1" -- '0' is not bad
          vim.o.foldlevel = 99 -- Using ufo provider need a large value, feel free to decrease the value
          vim.o.foldlevelstart = 99
          vim.o.foldenable = true

          -- Using ufo provider need remap `zR` and `zM`. If Neovim is 0.6.1, remap yourself
          vim.keymap.set("n", "zR", require("ufo").openAllFolds)
          vim.keymap.set("n", "zM", require("ufo").closeAllFolds)
          vim.keymap.set("n", "zK", function()
              local winid = require("ufo").peekFoldedLinesUnderCursor()
              if not winid then
                  vim.lsp.buf.hover()
              end
          end, { desc = "Peek Fold" })

          local ftMap = {
              git = "",
          }
          local foldingrange = require("ufo.model.foldingrange")
          local bufmanager = require("ufo.bufmanager")

          local CustomMarkerProvider = {}

          function CustomMarkerProvider.getFolds(bufnr)
              local buf = bufmanager:get(bufnr)
              if not buf then
                  return
              end
              local fmrOpen  = vim.opt.foldmarker:get()[1]
              local fmrClose = vim.opt.foldmarker:get()[2]
              local commentstring = vim.opt.commentstring:get()
              if commentstring == "/*%s*/" then
                  -- Hack for c++ and other // and /* */ langs
                  commentstring = "//%s"
              end
              local openRegx = commentstring .. "*.-" .. fmrOpen
              local closeRegx = commentstring .. "*.-" .. fmrClose
              local summaryRegx = openRegx .. "%s*(.*)"
              local lines = buf:lines(1, -1)

              local ranges = {}
              local stack = {}

              for lnum, line in ipairs(lines) do
                  -- Check for start marker
                  if line:match(openRegx) then
                      table.insert(stack, lnum)
                  -- Check for end marker
                  elseif line:match(closeRegx) then
                      local startLnum = table.remove(stack)
                      if startLnum then
                          local summary = lines[startLnum]:match(summaryRegx)
                          table.insert(ranges, foldingrange.new(startLnum - 1, lnum - 1, summary))
                      end
                  end
              end

              return ranges
          end

          local function customizeSelector(bufnr)
              local ranges = CustomMarkerProvider.getFolds(bufnr)
              local maybe_additional_ranges = require("ufo").getFolds(bufnr, "treesitter")
              if next(maybe_additional_ranges) ~= nil then
                  ranges = vim.list_extend(ranges, maybe_additional_ranges)
              else
                  ranges = vim.list_extend(ranges, require("ufo").getFolds(bufnr, "indent"))
              end
              return ranges
          end

          require("ufo").setup({
              provider_selector = function(bufnr, filetype, buftype)
                  return ftMap[filetype] or customizeSelector
              end,
          })
      end,
  },
  1. No need to handle commentstring;
  2. Use string.find({s}, {pattern}, 0, true]]) to handle plain marker instead of regex;
  3. If use lsp provider, should use an async function. Promise.resolve(range):thenCall should be more robust.
LucasAVasco commented 2 months ago

I implemented the marker provider in a fork: https://github.com/LucasAVasco/nvim-ufo/tree/main

Pull request: https://github.com/kevinhwang91/nvim-ufo/pull/218

I also implemented a function to merge UFO providers (also can merge functions that returns Foldings): https://github.com/LucasAVasco/nvim-ufo/tree/featMergeProviders

Pull request: https://github.com/kevinhwang91/nvim-ufo/pull/219

If you want to use both (and my pull requests have not yet been accepted), you can use this branch: https://github.com/LucasAVasco/nvim-ufo/tree/markerAndMergeProviders