Repository: neovim
Issue: Render tabs correctly in foldtext
Labels: enhancement,folds

### Feature already in Vim?

No, Vim has this same limitation.

### Feature description

It would be extremely nice if tabs would render correctly in foldtext, honoring tabstop and list mode. Currently, making tabs show up correctly requires a lot of extra logic in a custom foldtext function.

Below is an example custom foldtext method (the one I wrote and use as my default). A survey of 3 or 4 other custom foldtext methods from plugins and blogs shows that most people do similar things, except they usually have more limitations than this code because they use regex substitutions that don't take into account the current display column, etc.

The algorithm I currently use is in the code paragraph that starts with the comment: "We can't use tabs in the fold text, so visualize tabs explicitly." As you can see, it's not fully general, and assumes list mode (which I always use) and my specific listchars. Some of this would be fixable with significantly more code, but it would be much nicer if NeoVim would simply render tabs in foldtext the same as it would render them in any other text, without the need for hacks like this. 

```vim
set foldtext=X_foldtext()
function! X_foldtext() " {{{

	" Use the first non-blank line for our starting display text.
	let start_text = getline(v:foldstart)
	if getline(v:foldstart) =~ '^\s*$'
		let line = nextnonblank(v:foldstart + 1)
		if line != 0
			let start_text = getline(line)
		endif
	endif

	" Strip trailing whitespace from the text.
	let start_text = substitute(start_text, '\s*$', "", "")

	" We can't use tabs in the fold text, so visualize tabs explicitly.
	let chars = split(start_text, '\zs')
	let start_text = ""
	for char in chars
		if char != "\t"
			" Just append non-tab characters.
			let start_text = start_text .. char
		else
			" Expand tab characters into an appropriate visual form.
			" 	* FIXME: This hard-codes the tab characters.
			" 	  It would be better to extract this from listchars.
			let column = strdisplaywidth(start_text)
			let width = strdisplaywidth("\t", column)
			let tabtext = "┊" . repeat(" ", width - 1)
			let start_text = start_text .. tabtext
		endif
	endfor

	" Use the last non-blank line for our ending display text.
	let end_text = getline(v:foldend)
	if getline(v:foldend) =~ '^\s*$'
		let line = prevnonblank(v:foldend - 1)
		if line != 0
			let end_text = getline(line)
		endif
	endif

	" Strip leading and trailing whitespace from the end text.
	let end_text = substitute(end_text, '^\s*', "", "")
	let end_text = substitute(end_text, '\s*$', "", "")

	" Truncate end text somewhat arbitrarily.
	" The goal is to just provide a little context.
	let end_text = " ⋯ " .. end_text[-10:-1] .. " "

	" Compute fold size information.
	let fold_lines   = 1 + v:foldend - v:foldstart
	let fold_percent = 100.0 * fold_lines / line("$")

	" Compute fold information field.
	let fold_info = printf(" +%d (%.1f%%)", fold_lines, fold_percent)

	" Compute the width of the editor area.
	" FIXME: this assumes there is no fold column.
	let fold_column_width = 0
	let number_column_width = 1 + float2nr(ceil(log10(line("$"))))
	let editor_width = winwidth(0) - fold_column_width - number_column_width

	" Truncate our text to make enough room for the fold info.
	let max_width = editor_width - strwidth(fold_info) - strwidth(end_text) - 2
	let start_text = start_text[0:max_width]
	"while strwidth(start_text) > max_width
	"	let start_text = start_text[:-2]
	"endwhile

	" Compute expander text.
	let expander = repeat("·", editor_width - strwidth(start_text) - strwidth(end_text) - strwidth(fold_info))
	let expander = substitute(expander, "··", "· ", "g")

	" Build the final display text.
	let fold_text = start_text .. end_text .. expander .. fold_info
	return fold_text
endf " }}}

```