diff --git a/autoload/vimtex/fold.vim b/autoload/vimtex/fold.vim index 1e351c8..ec7c729 100644 --- a/autoload/vimtex/fold.vim +++ b/autoload/vimtex/fold.vim @@ -11,6 +11,7 @@ function! vimtex#fold#init_options() " {{{1 call vimtex#util#set_default('g:vimtex_fold_levelmarker', '*') call vimtex#util#set_default('g:vimtex_fold_preamble', 1) call vimtex#util#set_default('g:vimtex_fold_envs', 1) + call vimtex#util#set_default('g:vimtex_fold_markers', 1) call vimtex#util#set_default('g:vimtex_fold_parts', \ [ \ 'part', @@ -26,10 +27,17 @@ function! vimtex#fold#init_options() " {{{1 \ 'subsection', \ 'subsubsection', \ ]) - call vimtex#util#set_default('g:vimtex_fold_documentclass', 0) - call vimtex#util#set_default('g:vimtex_fold_usepackage', 1) - call vimtex#util#set_default('g:vimtex_fold_newcommands', 1) - call vimtex#util#set_default('g:vimtex_fold_markers', 1) + call vimtex#util#set_default('g:vimtex_fold_commands_default', { + \ 'hypersetup' : 'single', + \ 'tikzset' : 'single', + \ 'usepackage' : 'single_opt', + \ 'includepdf' : 'single_opt', + \ '%(re)?new%(command|environment)' : 'multi', + \ 'providecommand' : 'multi', + \ 'presetkeys' : 'multi', + \ 'Declare%(Multi|Auto)?CiteCommand' : 'multi', + \ 'Declare%(Index)?%(Field|List|Name)%(Format|Alias)' : 'multi', + \}) " Disable manual mode in vimdiff let g:vimtex_fold_manual = &diff ? 0 : g:vimtex_fold_manual @@ -42,6 +50,21 @@ function! vimtex#fold#init_script() " {{{1 let s:notbslash = '\%(\\\@1' endif - " Fold documentclass - if g:vimtex_fold_documentclass - if line =~# '^\s*\\documentclass\s*\[\s*\%($\|%\)' - let s:documentclass = 1 - return 'a1' - elseif get(s:, 'documentclass', 0) && line =~# '^\s*\]{' - let s:documentclass = 0 - return 's1' - endif - endif - " Never fold \begin{document} if line =~# '^\s*\\begin\s*{\s*document\s*}' return '0' endif - " Fold usepackages - if g:vimtex_fold_usepackage - if line =~# '^\s*\\usepackage\s*\[\s*\%($\|%\)' - let s:usepackage = 1 - return 'a1' - elseif get(s:, 'usepackage', 0) && line =~# '^\s*\]{' - let s:usepackage = 0 - return 's1' - endif - endif - - " Fold newcommands (and similar) - if g:vimtex_fold_newcommands - if line =~# '\v^\s*\\%(re)?new%(command|environment)\*?' - \ && indent(a:lnum+1) > indent(a:lnum) - let s:newcommand_indent = indent(a:lnum) - return 'a1' - elseif exists('s:newcommand_indent') - \ && indent(a:lnum) == s:newcommand_indent - \ && line =~# '^\s*}\s*$' - unlet s:newcommand_indent - return 's1' - endif - endif + " Fold commands + for l:cmd in s:cmd_types + let l:value = l:cmd.level(line, a:lnum) + if !empty(l:value) | return l:value | endif + endfor " Fold chapters and sections for [part, level] in b:vimtex_fold.parts @@ -213,9 +191,9 @@ function! vimtex#fold#level(lnum) " {{{1 endif " Fold markers - if line =~# '%.*{{{' + if line =~# '\v\%.*\{\{\{' return 'a1' - elseif line =~# '%\s*}}}' + elseif line =~# '\v\%\s*\}\}\}' return 's1' endif @@ -241,87 +219,23 @@ function! vimtex#fold#level(lnum) " {{{1 return '=' endfunction -" -" Parse current buffer to find which sections to fold and their levels. The -" patterns are predefined to optimize the folding. -" -" We ignore top level parts such as \frontmatter, \appendix, \part, and -" similar, unless there are at least two such commands in a document. -" -function! s:refresh_folded_sections() - " Only refresh if file has been changed - let l:time = getftime(expand('%')) - if l:time == get(b:vimtex_fold, 'time', 0) | return | endif - let b:vimtex_fold.time = l:time - - " Initialize - let b:vimtex_fold.parts = [] - let buffer = getline(1,'$') - - " Parse part commands (frontmatter, appendix, part, etc) - let lines = filter(copy(buffer), 'v:val =~ ''' . s:parts . '''') - for part in g:vimtex_fold_parts - let partpattern = '^\s*\%(\\\|% Fake\)' . part . ':\?\>' - for line in lines - if line =~# partpattern - call insert(b:vimtex_fold.parts, [partpattern, 1]) - break - endif - endfor - endfor - - " We want a minimum of two top level parts - if len(b:vimtex_fold.parts) >= 2 - let level = 1 - else - let level = 0 - let b:vimtex_fold.parts = [] - endif - - " Parse section commands (chapter, [sub...]section) - let lines = filter(copy(buffer), 'v:val =~ ''' . s:secs . '''') - for part in g:vimtex_fold_sections - let partpattern = '^\s*\%(\\\|% Fake\)' . part . ':\?\>' - for line in lines - if line =~# partpattern - let level += 1 - call insert(b:vimtex_fold.parts, [partpattern, level]) - break - endif - endfor - endfor -endfunction - " }}}1 function! vimtex#fold#text() " {{{1 let line = getline(v:foldstart) - " Text for usepackage - if g:vimtex_fold_usepackage && line =~# '^\s*\\usepackage' - return '\usepackage[...]{' - \ . vimtex#cmd#get_at(v:foldstart, 1).args[0].text - \ . '}' + " Text for marker folding + if line =~# '\v\%\s*\{\{\{' + return ' ' . matchstr(line, '\v\%\s*\{\{\{\s*\zs.*') + elseif line =~# '\v\%.*\{\{\{' + return ' ' . matchstr(line, '\v\%\s*\zs.*\ze\{\{\{') endif - if line =~# '%\s*{{{' - return ' ' . matchstr(line, '%\s*{{{\s*\zs.*') - elseif line =~# '%.*{{{' - return ' ' . matchstr(line, '%\s*\zs.*\ze{{{') - endif - - " Text for newcommand (and similar) - if g:vimtex_fold_newcommands - \ && line =~# '\v^\s*\\%(re)?new%(command|environment)' - return matchstr(line, - \ '\v^\s*\\%(re)?new%(command|environment)\*?\{[^}]*\}') . ' ...' - endif - - " Text for documentclass - if g:vimtex_fold_documentclass && line =~# '^\s*\\documentclass' - return '\documentclass[...]{' - \ . vimtex#cmd#get_at(v:foldstart, 1).args[0].text - \ . '}' - endif + " Text for various folded commands + for l:cmd in s:cmd_types + if line =~# l:cmd.re.start + return l:cmd.text(line) + endif + endfor let level = v:foldlevel > 1 \ ? repeat('-', v:foldlevel-2) . g:vimtex_fold_levelmarker @@ -371,7 +285,7 @@ function! vimtex#fold#text() " {{{1 let caption = s:parse_caption(line) endif - " Add paranthesis to label + " Add parenthesis to label if label !=# '' let label = substitute(strpart(label,0,nt-ne-2), '\(.*\)', '(\1)','') endif @@ -391,11 +305,172 @@ function! vimtex#fold#text() " {{{1 return substitute(text, '\s\+$', '', '') . ' ' endfunction -" -" Functions for setting fold text -" +" }}}1 -function! s:parse_label() " {{{2 +function! s:foldmethod_in_modeline() " {{{1 + let l:cursor_pos = getpos('.') + let l:fdm_modeline = 'vim:.*\%(foldmethod\|fdm\)' + + call cursor(1, 1) + let l:check_top = search(l:fdm_modeline, 'cn', &modelines) + + normal! G$ + let l:check_btm = search(l:fdm_modeline, 'b', line('$') + 1 - &modelines) + + call setpos('.', l:cursor_pos) + return l:check_top || l:check_btm +endfunction + +" }}}1 +function! s:refresh_folded_sections() " {{{1 + " + " Parse current buffer to find which sections to fold and their levels. The + " patterns are predefined to optimize the folding. + " + " We ignore top level parts such as \frontmatter, \appendix, \part, and + " similar, unless there are at least two such commands in a document. + " + + " Only refresh if file has been changed + let l:time = getftime(expand('%')) + if l:time == get(b:vimtex_fold, 'time', 0) | return | endif + let b:vimtex_fold.time = l:time + + " Initialize + let b:vimtex_fold.parts = [] + let buffer = getline(1,'$') + + " Parse part commands (frontmatter, appendix, part, etc) + let lines = filter(copy(buffer), 'v:val =~ ''' . s:parts . '''') + for part in g:vimtex_fold_parts + let partpattern = '^\s*\%(\\\|% Fake\)' . part . ':\?\>' + for line in lines + if line =~# partpattern + call insert(b:vimtex_fold.parts, [partpattern, 1]) + break + endif + endfor + endfor + + " We want a minimum of two top level parts + if len(b:vimtex_fold.parts) >= 2 + let level = 1 + else + let level = 0 + let b:vimtex_fold.parts = [] + endif + + " Parse section commands (chapter, [sub...]section) + let lines = filter(copy(buffer), 'v:val =~ ''' . s:secs . '''') + for part in g:vimtex_fold_sections + let partpattern = '^\s*\%(\\\|% Fake\)' . part . ':\?\>' + for line in lines + if line =~# partpattern + let level += 1 + call insert(b:vimtex_fold.parts, [partpattern, level]) + break + endif + endfor + endfor +endfunction + +" }}}1 + +function! s:cmd_single(cmds) " {{{1 + let l:re = '\v^\s*\\%(' . join(a:cmds, '|') . ')\*?' + + let l:fold = {} + let l:fold.re = { + \ 'start' : l:re . '\s*\{\s*%($|\%)', + \ 'end' : '^\s*}', + \ 'text' : l:re, + \} + + function! l:fold.level(line, lnum) dict + if a:line =~# self.re.start + let self.opened = 1 + return 'a1' + elseif has_key(self, 'opened') + \ && a:line =~# self.re.end + unlet self.opened + return 's1' + endif + return '' + endfunction + + function! l:fold.text(line) dict + return matchstr(a:line, self.re.text) . '{...}' + endfunction + + return l:fold +endfunction + +" }}}1 +function! s:cmd_single_opt(cmds) " {{{1 + let l:re = '\v^\s*\\%(' . join(a:cmds, '|') . ')\*?' + + let l:fold = {} + let l:fold.re = { + \ 'start' : l:re . '\s*\[\s*%($|\%)', + \ 'end' : '^\s*\]{', + \ 'text' : l:re, + \} + + function! l:fold.level(line, lnum) dict + if a:line =~# self.re.start + let self.opened = 1 + return 'a1' + elseif has_key(self, 'opened') + \ && a:line =~# self.re.end + unlet self.opened + return 's1' + endif + return '' + endfunction + + function! l:fold.text(line) dict + return matchstr(a:line, self.re.text) . '[...]{' + \ . vimtex#cmd#get_at(v:foldstart, 1).args[0].text . '}' + endfunction + + return l:fold +endfunction + +" }}}1 +function! s:cmd_multi(cmds) " {{{1 + let l:re = '\v^\s*\\%(' . join(a:cmds, '|') . ')\*?' + + let l:fold = {} + let l:fold.re = { + \ 'start' : l:re, + \ 'end' : '^\s*}\s*$', + \ 'text' : l:re . '\{[^}]*\}' + \} + + function! l:fold.level(line, lnum) dict + if a:line =~# self.re.start + \ && indent(a:lnum+1) > indent(a:lnum) + let self.indent = indent(a:lnum) + return 'a1' + elseif has_key(self, 'indent') + \ && a:line =~# self.re.end + \ && indent(a:lnum) == self.indent + unlet self.indent + return 's1' + endif + return '' + endfunction + + function! l:fold.text(line) dict + return matchstr(a:line, self.re.text) . ' ...' + endfunction + + return l:fold +endfunction + +" }}}1 + +function! s:parse_label() " {{{1 let i = v:foldend while i >= v:foldstart if getline(i) =~# '^\s*\\label' @@ -406,8 +481,8 @@ function! s:parse_label() " {{{2 return '' endfunction -" }}}2 -function! s:parse_caption(line) " {{{2 +" }}}1 +function! s:parse_caption(line) " {{{1 let i = v:foldend while i >= v:foldstart if getline(i) =~# '^\s*\\caption' @@ -421,8 +496,8 @@ function! s:parse_caption(line) " {{{2 return matchstr(a:line,'\\begin\*\?{.*}\s*%\s*\zs.*') endfunction -" }}}2 -function! s:parse_caption_table(line) " {{{2 +" }}}1 +function! s:parse_caption_table(line) " {{{1 let i = v:foldstart while i <= v:foldend if getline(i) =~# '^\s*\\caption' @@ -436,8 +511,8 @@ function! s:parse_caption_table(line) " {{{2 return matchstr(a:line,'\\begin\*\?{.*}\s*%\s*\zs.*') endfunction -" }}}2 -function! s:parse_caption_frame(line) " {{{2 +" }}}1 +function! s:parse_caption_frame(line) " {{{1 " Test simple variants first let caption1 = matchstr(a:line,'\\begin\*\?{.*}{\zs.\+\ze}') let caption2 = matchstr(a:line,'\\begin\*\?{.*}{\zs.\+') @@ -461,8 +536,8 @@ function! s:parse_caption_frame(line) " {{{2 endif endfunction -" }}}2 -function! s:parse_sec_title(string, type) " {{{2 +" }}}1 +function! s:parse_sec_title(string, type) " {{{1 let l:idx = 0 let l:length = strlen(a:string) let l:level = 1 @@ -479,8 +554,6 @@ function! s:parse_sec_title(string, type) " {{{2 return strpart(a:string, 0, l:idx) endfunction -" }}}2 - " }}}1 " vim: fdm=marker sw=2 diff --git a/doc/vimtex.txt b/doc/vimtex.txt index 32f7f5d..177eb67 100644 --- a/doc/vimtex.txt +++ b/doc/vimtex.txt @@ -441,80 +441,110 @@ Options~ Default value: '*' +*g:vimtex_fold_preamble* + Control whether or not to fold the preamble. + + Note: If |g:vimtex_fold_commands| contains `documentclass`, then this option + will be automatically disabled. + + Default value: 1 + *g:vimtex_fold_envs* Control whether or not to fold environments. Default value: 1 +*g:vimtex_fold_markers* + Use this option to disable/enable vim-style markers for folding, i.e. pairs + of `{{{` and `}}}`. + + Default value: 1 + *g:vimtex_fold_parts* List of document parts that should be folded. Default value: > let g:vimtex_fold_parts = [ - \ "appendix", - \ "frontmatter", - \ "mainmatter", - \ "backmatter", + \ 'appendix', + \ 'frontmatter', + \ 'mainmatter', + \ 'backmatter', \ ] < -*g:vimtex_fold_preamble* - Control whether or not to fold the preamble. - - Note: This option can not be used together with |g:vimtex_fold_documentclass|. - - Default value: 1 - *g:vimtex_fold_sections* List of section constructs that should be folded. Default value: > let g:vimtex_fold_sections = [ - \ "part", - \ "chapter", - \ "section", - \ "subsection", - \ "subsubsection", + \ 'part', + \ 'chapter', + \ 'section', + \ 'subsection', + \ 'subsubsection', \ ] < -*g:vimtex_fold_usepackage* - Use this option to disable/enable folding of long `\usepackage` lines. The - lines must be formatted like this for folding to work properly: > +*g:vimtex_fold_commands* +*g:vimtex_fold_commands_default* + Use this option to disable/enable folding of commands. The option is + a dictionary of key-value pairs, where the key is a regex in very magic mode + (see |\v|) that matches the desired command name(s), and the value is the + variant of command folding. There are three variants allowed (see below for + a few examples that may be clarifying): - \usepackage[ - option 1, - ..., - option n - ]{name} + `single` Useful for commands with a single long argument. + `single_opt` Useful for commands that open with a single long optional + argument, then a short "real" argument. + `multi` Useful for commands that start with a short regular argument + and continue with long optional and/or regular arguments. In + order for the folding to work as expected, this style depends + on the indentation to be correct. See the example below for + how to properly format the folded regions. + + The option combines the contents of |g:vimtex_fold_commands| and + |g:vimtex_fold_commands_default|. Entries in the former will have higher + priority than entries in the latter. This way, it is easy to add custom + entries without disturbing the defaults. For those who want to ignore the + defaults, one may simple set |g:vimtex_fold_commands_default| to an empty + dictionary. + + Examples of how command folds work for the different variants: > + + Variant Not folded Folded + ---------------------------------------------------------------------- + 'single' \hypersetup{ \hypersetup{...} + option 1, + ..., + option n + } + + 'single_opt' \usepackage[ \usepackage[...]{name} + option 1, + ..., + option n + ]{name} + + 'multi' \newcommand{\command}[3]{ \newcommand{\command} ... + Hello #1, #2, and #3. + } + ---------------------------------------------------------------------- < - Default value: 1 + Note: |g:vimtex_fold_commands| deprecates the old *g:vimtex_fold_documentclass* + option. To fold the `\documentclass` command, it may be added with the + `single_opt` variant. As before, one must disable |g:vimtex_fold_preamble|. -*g:vimtex_fold_newcommands* - Use this option to disable/enable folding of long `\[re]newcommand` and - `\[re]newenvironment` lines. The lines must be formatted like this for - folding to work properly: > - - \[re]newcommand{\command}{ - ..., - } -< - That is, the fold region starts at the command and ends when at the final `}` - at the same indent level as the start of the command. - - Default value: 1 - -*g:vimtex_fold_documentclass* - Use this option to disable/enable folding of long `\documentclass` lines. This - works similar to |g:vimtex_fold_usepackage|. - - Note: This option can not be used together with |g:vimtex_fold_preamble|. - - Default value: 0 - -*g:vimtex_fold_markers* - Use this option to disable/enable vim-style markers for folding, i.e. pairs - of `{{{` and `}}}`. - - Default value: 1 + Default value: > + let g:vimtex_fold_commands = {} + let g:vimtex_fold_commands_default = { + \ 'hypersetup' : 'single', + \ 'tikzset' : 'single', + \ 'usepackage' : 'single_opt', + \ 'includepdf' : 'single_opt', + \ '%(re)?new%(command|environment)' : 'multi', + \ 'providecommand' : 'multi', + \ 'presetkeys' : 'multi', + \ 'Declare%(Multi|Auto)?CiteCommand' : 'multi', + \ 'Declare%(Index)?%(Field|List|Name)%(Format|Alias)' : 'multi', + \} *g:vimtex_imaps_enabled* Use this option to disable/enable the insert mode mappings. @@ -1708,12 +1738,14 @@ Associated settings: |g:vimtex_fold_enabled| |g:vimtex_fold_manual| |g:vimtex_fold_comments| + |g:vimtex_fold_levelmarker| |g:vimtex_fold_preamble| + |g:vimtex_fold_envs| + |g:vimtex_fold_markers| |g:vimtex_fold_parts| |g:vimtex_fold_sections| - |g:vimtex_fold_envs| - |g:vimtex_fold_usepackage| - |g:vimtex_fold_newcommands| + |g:vimtex_fold_commands| + |g:vimtex_fold_commands_default| ============================================================================== INDENTATION *vimtex-indent* diff --git a/test/features/folding/preamble.tex b/test/features/folding/main.tex similarity index 86% rename from test/features/folding/preamble.tex rename to test/features/folding/main.tex index 204e133..bb034c4 100644 --- a/test/features/folding/preamble.tex +++ b/test/features/folding/main.tex @@ -1,4 +1,8 @@ \documentclass[% Options of scrbook + % + % Preamble fold starts here, unless 'documentclass' is added to the + % g:vimtex_fold_commands option. + % % draft, fontsize=12pt, %smallheadings, @@ -25,7 +29,18 @@ ]{scrbook} % -% Fold usepackages +% Fold commands (single) +% +\hypersetup{ + ..., +} +\tikzset{ + testing, + tested, +} + +% +% Fold commands (single_opt) % \usepackage[ ... @@ -40,7 +55,7 @@ ]{biblatex} % -% Fold newcommands and similar +% Fold commands (multi) % \renewcommand{\marginpar}{% \marginnote%