Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for applying marker method in parallel with ufo #90

Open
msva opened this issue Oct 27, 2022 · 5 comments
Open

Support for applying marker method in parallel with ufo #90

msva opened this issue Oct 27, 2022 · 5 comments
Labels
enhancement New feature or request

Comments

@msva
Copy link

msva commented Oct 27, 2022

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 🤷

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 😀

Thanks in advance!

Additional context

No response

@msva msva added the enhancement New feature or request label Oct 27, 2022
@kevinhwang91
Copy link
Owner

kevinhwang91 commented Oct 27, 2022

It's similar to #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

      nvim-ufo/doc/example.lua

      Lines 26 to 57 in 9d59d71

      -- lsp->treesitter->indent
      local function selectProviderWithChainByDefault()
      local ftMap = {
      vim = 'indent',
      python = {'indent'},
      git = ''
      }
      ---@param bufnr number
      ---@return Promise
      local function customizeSelector(bufnr)
      local function handleFallbackException(err, providerName)
      if type(err) == 'string' and err:match('UfoFallbackException') then
      return require('ufo').getFolds(bufnr, providerName)
      else
      return require('promise').reject(err)
      end
      end
      return require('ufo').getFolds(bufnr, 'lsp'):catch(function(err)
      return handleFallbackException(err, 'treesitter')
      end):catch(function(err)
      return handleFallbackException(err, 'indent')
      end)
      end
      require('ufo').setup({
      provider_selector = function(bufnr, filetype, buftype)
      return ftMap[filetype] or customizeSelector
      end
      })
      end
      , 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
Copy link
Author

msva commented Oct 27, 2022

thanks

@danpf
Copy link

danpf commented Jan 3, 2024

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
Copy link
Owner

kevinhwang91 commented Jan 3, 2024

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
Copy link
Contributor

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

Pull request: #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: #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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

4 participants