Context sensitivity in commentstring
Question
When setting folds manually, it would be handy if it were possible to set the commentstring
in a context sensitive manner. Consider the case in which the language uses BCPL-style comment markers (i.e., comments begin with //
and are terminated by a newline), the first line of the visual block contains a comment, and the last line does not. Currently, if commentstring
is set to //%s
, redundant //
characters will be appended to the first line when zf
is used to create a fold.
Is it possible to set commentstring
so that the //
characters are added only if they do not already appear on the line?
La solution
According to :help fold-create-marker
, automatic folding marker insertion
does not work properly when:
- The line already contains a marker with a level number. Vim then doesn't know what to do.
- Folds nearby use a level number in their marker which gets in the way.
- The line is inside a comment,
commentstring
isn't empty and nested comments don't work. For example with C: adding/* {{{ */
inside a comment will truncate the existing comment.
Thus, it is not possible to change the default zf
behavior by modifying the
commentstring
setting.
However, it is possible to create a custom version of the zf
command that
would take into consideration that starting or ending (or both) lines of
a fold could have comments. For example, consider the following mappings, one
for marking a fold by visual selection, another for using with a motion
command.
nnoremap <silent> <leader>zf :set opfunc=CreateMarkerFold<cr>g@
vnoremap <silent> <leader>zf :<c-u>call CreateMarkerFold(visualmode(), 1)<cr>
function! CreateMarkerFold(vt, ...)
let range = map(['[<', ']>'], 'line("''".v:val[a:0])')
let mark = split(&foldmarker, ',')
let pat = escape(&commentstring, '\')
let pat = '\V' . substitute(pat, '\\\@<!%s', '\\zs\\ze\\.\\{-}', '')
for i in [1, 0]
let line = getline(range[i])
if line =~ pat
let line = substitute(line, pat, escape(mark[i], '\'), '')
else
let line .= printf(&commentstring, mark[i])
endif
call setline(range[i], line)
endfor
endfunction
Both of the mappings follow the same routine. Before adding starting and
ending folding markers, it checks separately whether the first and the last
lines of the block to fold match the commentstring
pattern. For each of the
two that do match, it inserts the corresponding marker inside the first found
comment, at the very beginning of its text. Otherwise, the marker is
decorated according to the commentstring
template and added at the end of
the line.
If in the latter case it is preferable to separate the marker on its own line, one can change the for loop as shown below.
for i in [1, 0]
let line = getline(range[i])
if line =~ pat
let line = substitute(line, pat, escape(mark[i], '\'), '')
call setline(range[i], line)
else
call append(range[i] - !i, printf(&commentstring, mark[i]))
endif
endfor
Unlike the previous version of the loop, the order of processing the two lines is important: The ending marker line should be added first, if necessary, because inserting a line for the beginning marker would shift the following lines changing their numbers.