summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md2
-rw-r--r--after/ftplugin/tex.vim20
-rw-r--r--after/syntax/tex.vim14
-rw-r--r--autoload/health/vimtex.vim164
-rw-r--r--autoload/unite/sources/vimtex.vim87
-rw-r--r--autoload/vimtex.vim707
-rw-r--r--autoload/vimtex/cache.vim179
-rw-r--r--autoload/vimtex/cmd.vim718
-rw-r--r--autoload/vimtex/compiler.vim334
-rw-r--r--autoload/vimtex/compiler/arara.vim218
-rw-r--r--autoload/vimtex/compiler/latexmk.vim700
-rw-r--r--autoload/vimtex/compiler/latexrun.vim250
-rw-r--r--autoload/vimtex/compiler/tectonic.vim255
-rw-r--r--autoload/vimtex/complete.vim1089
-rw-r--r--autoload/vimtex/debug.vim114
-rw-r--r--autoload/vimtex/delim.vim1096
-rw-r--r--autoload/vimtex/doc.vim251
-rw-r--r--autoload/vimtex/echo.vim121
-rw-r--r--autoload/vimtex/env.vim202
-rw-r--r--autoload/vimtex/fold.vim143
-rw-r--r--autoload/vimtex/fold/cmd_addplot.vim51
-rw-r--r--autoload/vimtex/fold/cmd_multi.vim51
-rw-r--r--autoload/vimtex/fold/cmd_single.vim52
-rw-r--r--autoload/vimtex/fold/cmd_single_opt.vim53
-rw-r--r--autoload/vimtex/fold/comments.vim46
-rw-r--r--autoload/vimtex/fold/env_options.vim53
-rw-r--r--autoload/vimtex/fold/envs.vim188
-rw-r--r--autoload/vimtex/fold/markers.vim60
-rw-r--r--autoload/vimtex/fold/preamble.vim36
-rw-r--r--autoload/vimtex/fold/sections.vim180
-rw-r--r--autoload/vimtex/format.vim217
-rw-r--r--autoload/vimtex/fzf.vim114
-rw-r--r--autoload/vimtex/imaps.vim192
-rw-r--r--autoload/vimtex/include.vim147
-rw-r--r--autoload/vimtex/info.vim222
-rw-r--r--autoload/vimtex/kpsewhich.vim53
-rw-r--r--autoload/vimtex/log.vim137
-rw-r--r--autoload/vimtex/matchparen.vim111
-rw-r--r--autoload/vimtex/misc.vim158
-rw-r--r--autoload/vimtex/motion.vim207
-rw-r--r--autoload/vimtex/parser.vim144
-rw-r--r--autoload/vimtex/parser/auxiliary.vim58
-rw-r--r--autoload/vimtex/parser/bib.vim370
-rw-r--r--autoload/vimtex/parser/fls.vim19
-rw-r--r--autoload/vimtex/parser/tex.vim205
-rw-r--r--autoload/vimtex/parser/toc.vim778
-rw-r--r--autoload/vimtex/paths.vim91
-rw-r--r--autoload/vimtex/pos.vim97
-rw-r--r--autoload/vimtex/process.vim233
-rw-r--r--autoload/vimtex/profile.vim125
-rw-r--r--autoload/vimtex/qf.vim245
-rw-r--r--autoload/vimtex/qf/biblatex.vim250
-rw-r--r--autoload/vimtex/qf/bibtex.vim187
-rw-r--r--autoload/vimtex/qf/latexlog.vim209
-rw-r--r--autoload/vimtex/qf/pplatex.vim98
-rw-r--r--autoload/vimtex/qf/pulp.vim67
-rw-r--r--autoload/vimtex/re.vim110
-rw-r--r--autoload/vimtex/scratch.vim72
-rw-r--r--autoload/vimtex/state.vim745
-rw-r--r--autoload/vimtex/syntax.vim66
-rw-r--r--autoload/vimtex/syntax/load.vim82
-rw-r--r--autoload/vimtex/syntax/misc.vim92
-rw-r--r--autoload/vimtex/syntax/p/amsmath.vim47
-rw-r--r--autoload/vimtex/syntax/p/array.vim35
-rw-r--r--autoload/vimtex/syntax/p/asymptote.vim34
-rw-r--r--autoload/vimtex/syntax/p/beamer.vim32
-rw-r--r--autoload/vimtex/syntax/p/biblatex.vim84
-rw-r--r--autoload/vimtex/syntax/p/breqn.vim23
-rw-r--r--autoload/vimtex/syntax/p/cases.vim20
-rw-r--r--autoload/vimtex/syntax/p/cleveref.vim44
-rw-r--r--autoload/vimtex/syntax/p/csquotes.vim18
-rw-r--r--autoload/vimtex/syntax/p/dot2texi.vim25
-rw-r--r--autoload/vimtex/syntax/p/glossaries.vim20
-rw-r--r--autoload/vimtex/syntax/p/glossaries_extra.vim21
-rw-r--r--autoload/vimtex/syntax/p/gnuplottex.vim25
-rw-r--r--autoload/vimtex/syntax/p/hyperref.vim35
-rw-r--r--autoload/vimtex/syntax/p/listings.vim75
-rw-r--r--autoload/vimtex/syntax/p/luacode.vim31
-rw-r--r--autoload/vimtex/syntax/p/markdown.vim43
-rw-r--r--autoload/vimtex/syntax/p/mathtools.vim21
-rw-r--r--autoload/vimtex/syntax/p/minted.vim256
-rw-r--r--autoload/vimtex/syntax/p/moreverb.vim26
-rw-r--r--autoload/vimtex/syntax/p/natbib.vim18
-rw-r--r--autoload/vimtex/syntax/p/pdfpages.vim33
-rw-r--r--autoload/vimtex/syntax/p/pgfplots.vim38
-rw-r--r--autoload/vimtex/syntax/p/pythontex.vim40
-rw-r--r--autoload/vimtex/syntax/p/subfile.vim19
-rw-r--r--autoload/vimtex/syntax/p/tabularx.vim77
-rw-r--r--autoload/vimtex/syntax/p/tikz.vim47
-rw-r--r--autoload/vimtex/syntax/p/url.vim18
-rw-r--r--autoload/vimtex/syntax/p/varioref.vim25
-rw-r--r--autoload/vimtex/syntax/p/wiki.vim26
-rw-r--r--autoload/vimtex/test.vim98
-rw-r--r--autoload/vimtex/text_obj.vim447
-rw-r--r--autoload/vimtex/text_obj/cmdtargets.vim85
-rw-r--r--autoload/vimtex/text_obj/envtargets.vim110
-rw-r--r--autoload/vimtex/text_obj/targets.vim49
-rw-r--r--autoload/vimtex/toc.vim801
-rw-r--r--autoload/vimtex/util.vim273
-rw-r--r--autoload/vimtex/view.vim115
-rw-r--r--autoload/vimtex/view/common.vim211
-rw-r--r--autoload/vimtex/view/general.vim111
-rw-r--r--autoload/vimtex/view/mupdf.vim186
-rw-r--r--autoload/vimtex/view/skim.vim114
-rw-r--r--autoload/vimtex/view/zathura.vim155
-rwxr-xr-xbuild2
-rw-r--r--compiler/bibertool.vim26
-rw-r--r--compiler/chktex.vim20
-rw-r--r--compiler/lacheck.vim20
-rw-r--r--compiler/style-check.vim24
-rw-r--r--compiler/textidote.vim38
-rw-r--r--ftplugin/bib.vim28
-rw-r--r--ftplugin/latex-box/common.vim417
-rw-r--r--ftplugin/latex-box/complete.vim936
-rw-r--r--ftplugin/latex-box/findmain.vim66
-rw-r--r--ftplugin/latex-box/folding.vim382
-rw-r--r--ftplugin/latex-box/latexmk.vim558
-rw-r--r--ftplugin/latex-box/mappings.vim110
-rw-r--r--ftplugin/latex-box/motion.vim548
-rw-r--r--ftplugin/latextoc.vim206
-rw-r--r--ftplugin/tex.vim29
-rw-r--r--ftplugin/tex_LatexBox.vim37
-rw-r--r--indent/bib.vim85
-rw-r--r--indent/tex.vim402
-rw-r--r--syntax/latextoc.vim13
125 files changed, 17908 insertions, 3385 deletions
diff --git a/README.md b/README.md
index 852fdd1f..37c8776b 100644
--- a/README.md
+++ b/README.md
@@ -109,7 +109,7 @@ If you need full functionality of any plugin, please use it directly with your p
- [jsx](https://github.com/MaxMEllon/vim-jsx-pretty) (autoload, after)
- [julia](https://github.com/JuliaEditorSupport/julia-vim) (syntax, indent, autoload, ftplugin)
- [kotlin](https://github.com/udalov/kotlin-vim) (syntax, indent, ftplugin)
-- [latex](https://github.com/LaTeX-Box-Team/LaTeX-Box) (syntax, indent, ftplugin)
+- [latex](https://github.com/lervag/vimtex) (indent, compiler, autoload, ftplugin, syntax)
- [less](https://github.com/groenewege/vim-less) (syntax, indent, ftplugin)
- [lilypond](https://github.com/anowlcalledjosh/vim-lilypond) (syntax, indent, compiler, ftplugin)
- [livescript](https://github.com/gkz/vim-ls) (syntax, indent, compiler, ftplugin)
diff --git a/after/ftplugin/tex.vim b/after/ftplugin/tex.vim
new file mode 100644
index 00000000..777ed5c2
--- /dev/null
+++ b/after/ftplugin/tex.vim
@@ -0,0 +1,20 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+if !get(g:, 'vimtex_enabled', 1)
+ finish
+endif
+
+if exists('b:did_ftplugin_vimtex')
+ finish
+endif
+let b:did_ftplugin_vimtex = 1
+
+call vimtex#check_plugin_clash()
+
+endif
diff --git a/after/syntax/tex.vim b/after/syntax/tex.vim
index f549de30..6e01749f 100644
--- a/after/syntax/tex.vim
+++ b/after/syntax/tex.vim
@@ -1,13 +1,11 @@
if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
-" adds support for cleverref package
-" \Cref, \cref, \cpageref, \labelcref, \labelcpageref
-syn region texRefZone matchgroup=texStatement start="\\Cref{" end="}\|%stopzone\>" contains=@texRefGroup
-syn region texRefZone matchgroup=texStatement start="\\\(label\|\)c\(page\|\)ref{" end="}\|%stopzone\>" contains=@texRefGroup
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
-" adds support for listings package
-syn region texZone start="\\begin{lstlisting}" end="\\end{lstlisting}\|%stopzone\>"
-syn match texInputFile "\\lstinputlisting\s*\(\[.*\]\)\={.\{-}}" contains=texStatement,texInputCurlies,texInputFileOpt
-syn match texZone "\\lstinline\s*\(\[.*\]\)\={.\{-}}"
+call vimtex#syntax#init()
endif
diff --git a/autoload/health/vimtex.vim b/autoload/health/vimtex.vim
new file mode 100644
index 00000000..1af2c78f
--- /dev/null
+++ b/autoload/health/vimtex.vim
@@ -0,0 +1,164 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+function! health#vimtex#check() abort
+ call vimtex#init_options()
+
+ call health#report_start('vimtex')
+
+ call s:check_general()
+ call s:check_plugin_clash()
+ call s:check_view()
+ call s:check_compiler()
+endfunction
+
+function! s:check_general() abort " {{{1
+ if !has('nvim') || v:version < 800
+ call health#report_warn('vimtex works best with Vim 8 or neovim')
+ else
+ call health#report_ok('Vim version should have full support!')
+ endif
+
+ if !executable('bibtex')
+ call health#report_warn('bibtex is not executable, so bibtex completions are disabled.')
+ endif
+endfunction
+
+" }}}1
+
+function! s:check_compiler() abort " {{{1
+ if !g:vimtex_compiler_enabled | return | endif
+
+ if !executable(g:vimtex_compiler_method)
+ let l:ind = ' '
+ call health#report_error(printf(
+ \ '|g:vimtex_compiler_method| (`%s`) is not executable!',
+ \ g:vimtex_compiler_method))
+ return
+ endif
+
+ let l:ok = 1
+ if !executable(g:vimtex_compiler_progname)
+ call health#report_warn(printf(
+ \ '|g:vimtex_compiler_progname| (`%s`) is not executable!',
+ \ g:vimtex_compiler_progname))
+ let l:ok = 0
+ endif
+
+ if has('nvim')
+ \ && fnamemodify(g:vimtex_compiler_progname, ':t') !=# 'nvr'
+ call health#report_warn('Compiler callbacks will not work!', [
+ \ '`neovim-remote` / `nvr` is required for callbacks to work with neovim',
+ \ "Please also set |g:vimtex_compiler_progname| = 'nvr'",
+ \ 'For more info, see :help |vimtex-faq-neovim|',
+ \])
+ let l:ok = 0
+ endif
+
+ if l:ok
+ call health#report_ok('Compiler should work!')
+ endif
+endfunction
+
+" }}}1
+
+function! s:check_plugin_clash() abort " {{{1
+ let l:scriptnames = split(execute('scriptnames'), "\n")
+
+ let l:latexbox = !empty(filter(copy(l:scriptnames), "v:val =~# 'latex-box'"))
+ if l:latexbox
+ call health#report_warn('Conflicting plugin detected: LaTeX-Box')
+ call health#report_info('vimtex does not work as expected when LaTeX-Box is installed!')
+ call health#report_info('Please disable or remove it to use vimtex!')
+
+ let l:polyglot = !empty(filter(copy(l:scriptnames), "v:val =~# 'polyglot'"))
+ if l:polyglot
+ call health#report_info('LaTeX-Box is included with vim-polyglot and may be disabled with:')
+ call health#report_info('let g:polyglot_disabled = [''latex'']')
+ endif
+ endif
+endfunction
+
+" }}}1
+
+function! s:check_view() abort " {{{1
+ call s:check_view_{g:vimtex_view_method}()
+
+ if executable('xdotool') && !executable('pstree')
+ call health#report_warn('pstree is not available',
+ \ 'vimtex#view#reverse_goto is better if pstree is available.')
+ endif
+endfunction
+
+" }}}1
+function! s:check_view_general() abort " {{{1
+ if executable(g:vimtex_view_general_viewer)
+ call health#report_ok('General viewer should work properly!')
+ else
+ call health#report_error(
+ \ 'Selected viewer is not executable!',
+ \ '- Selection: ' . g:vimtex_view_general_viewer,
+ \ '- Please see :h g:vimtex_view_general_viewer')
+ endif
+endfunction
+
+" }}}1
+function! s:check_view_zathura() abort " {{{1
+ let l:ok = 1
+
+ if !executable('zathura')
+ call health#report_error('Zathura is not executable!')
+ let l:ok = 0
+ endif
+
+ if !executable('xdotool')
+ call health#report_warn('Zathura requires xdotool for forward search!')
+ let l:ok = 0
+ endif
+
+ if l:ok
+ call health#report_ok('Zathura should work properly!')
+ endif
+endfunction
+
+" }}}1
+function! s:check_view_mupdf() abort " {{{1
+ let l:ok = 1
+
+ if !executable('mupdf')
+ call health#report_error('MuPDF is not executable!')
+ let l:ok = 0
+ endif
+
+ if !executable('xdotool')
+ call health#report_warn('MuPDF requires xdotool for forward search!')
+ let l:ok = 0
+ endif
+
+ if !executable('synctex')
+ call health#report_warn('MuPDF requires synctex for forward search!')
+ let l:ok = 0
+ endif
+
+ if l:ok
+ call health#report_ok('MuPDF should work properly!')
+ endif
+endfunction
+
+" }}}1
+function! s:check_view_skim() abort " {{{1
+ let l:cmd = join([
+ \ 'osascript -e ',
+ \ '''tell application "Finder" to POSIX path of ',
+ \ '(get application file id (id of application "Skim") as alias)''',
+ \])
+
+ if system(l:cmd)
+ call health#report_error('Skim is not installed!')
+ else
+ call health#report_ok('Skim viewer should work!')
+ endif
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/unite/sources/vimtex.vim b/autoload/unite/sources/vimtex.vim
new file mode 100644
index 00000000..af13a6e4
--- /dev/null
+++ b/autoload/unite/sources/vimtex.vim
@@ -0,0 +1,87 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+let s:save_cpo = &cpo
+set cpo&vim
+
+let s:source = {
+ \ 'name' : 'vimtex',
+ \ 'sorters' : 'sorter_nothing',
+ \ 'default_kind' : 'jump_list',
+ \ 'syntax' : 'uniteSource__vimtex',
+ \ 'entries' : [],
+ \ 'maxlevel' : 1,
+ \ 'hooks' : {},
+ \}
+
+function! s:source.gather_candidates(args, context) abort " {{{1
+ if exists('b:vimtex')
+ let s:source.entries = vimtex#parser#toc()
+ let s:source.maxlevel = max(map(copy(s:source.entries), 'v:val.level'))
+ endif
+ return map(copy(s:source.entries),
+ \ 's:create_candidate(v:val, s:source.maxlevel)')
+endfunction
+
+" }}}1
+function! s:source.hooks.on_syntax(args, context) abort " {{{1
+ syntax match VimtexTocSecs /.* @\d$/
+ \ contains=VimtexTocNum,VimtexTocTag,@Tex
+ \ contained containedin=uniteSource__vimtex
+ syntax match VimtexTocSec0 /.* @0$/
+ \ contains=VimtexTocNum,VimtexTocTag,@Tex
+ \ contained containedin=uniteSource__vimtex
+ syntax match VimtexTocSec1 /.* @1$/
+ \ contains=VimtexTocNum,VimtexTocTag,@Tex
+ \ contained containedin=uniteSource__vimtex
+ syntax match VimtexTocSec2 /.* @2$/
+ \ contains=VimtexTocNum,VimtexTocTag,@Tex
+ \ contained containedin=uniteSource__vimtex
+ syntax match VimtexTocSec3 /.* @3$/
+ \ contains=VimtexTocNum,VimtexTocTag,@Tex
+ \ contained containedin=uniteSource__vimtex
+ syntax match VimtexTocSec4 /.* @4$/
+ \ contains=VimtexTocNum,VimtexTocTag,@Tex
+ \ contained containedin=uniteSource__vimtex
+ syntax match VimtexTocNum
+ \ /\%69v\%(\%([A-Z]\+\>\|\d\+\)\%(\.\d\+\)*\)\?\s*@\d$/
+ \ contains=VimtexTocLevel
+ \ contained containedin=VimtexTocSec[0-9*]
+ syntax match VimtexTocTag
+ \ /\[.\]\s*@\d$/
+ \ contains=VimtexTocLevel
+ \ contained containedin=VimtexTocSec[0-9*]
+ syntax match VimtexTocLevel
+ \ /@\d$/ conceal
+ \ contained containedin=VimtexTocNum,VimtexTocTag
+endfunction
+
+" }}}1
+
+function! s:create_candidate(entry, maxlevel) abort " {{{1
+ let level = a:maxlevel - a:entry.level
+ let title = printf('%-65S%-10s',
+ \ strpart(repeat(' ', 2*level) . a:entry.title, 0, 60),
+ \ b:vimtex.toc.print_number(a:entry.number))
+ return {
+ \ 'word' : title,
+ \ 'abbr' : title . ' @' . level,
+ \ 'action__path' : a:entry.file,
+ \ 'action__line' : get(a:entry, 'line', 0),
+ \ }
+endfunction
+
+" }}}1
+
+function! unite#sources#vimtex#define() abort
+ return s:source
+endfunction
+
+let &cpo = s:save_cpo
+
+endif
diff --git a/autoload/vimtex.vim b/autoload/vimtex.vim
new file mode 100644
index 00000000..abd5adc1
--- /dev/null
+++ b/autoload/vimtex.vim
@@ -0,0 +1,707 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#init() abort " {{{1
+ call vimtex#init_options()
+
+ call s:init_highlights()
+ call s:init_state()
+ call s:init_buffer()
+ call s:init_default_mappings()
+
+ if exists('#User#VimtexEventInitPost')
+ doautocmd <nomodeline> User VimtexEventInitPost
+ endif
+
+ augroup vimtex_main
+ autocmd!
+ autocmd VimLeave * call s:quit()
+ augroup END
+endfunction
+
+" }}}1
+function! vimtex#init_options() abort " {{{1
+ call s:init_option('vimtex_compiler_enabled', 1)
+ call s:init_option('vimtex_compiler_method', 'latexmk')
+ call s:init_option('vimtex_compiler_progname',
+ \ has('nvim') && executable('nvr')
+ \ ? 'nvr'
+ \ : get(v:, 'progpath', get(v:, 'progname', '')))
+ call s:init_option('vimtex_compiler_callback_hooks', [])
+ call s:init_option('vimtex_compiler_latexmk_engines', {})
+ call s:init_option('vimtex_compiler_latexrun_engines', {})
+
+ call s:init_option('vimtex_complete_enabled', 1)
+ call s:init_option('vimtex_complete_close_braces', 0)
+ call s:init_option('vimtex_complete_ignore_case', &ignorecase)
+ call s:init_option('vimtex_complete_smart_case', &smartcase)
+ call s:init_option('vimtex_complete_bib', {
+ \ 'simple': 0,
+ \ 'menu_fmt': '[@type] @author_short (@year), "@title"',
+ \ 'abbr_fmt': '',
+ \ 'custom_patterns': [],
+ \})
+ call s:init_option('vimtex_complete_ref', {
+ \ 'custom_patterns': [],
+ \})
+
+ call s:init_option('vimtex_delim_timeout', 300)
+ call s:init_option('vimtex_delim_insert_timeout', 60)
+ call s:init_option('vimtex_delim_stopline', 500)
+
+ call s:init_option('vimtex_include_search_enabled', 1)
+
+ call s:init_option('vimtex_doc_enabled', 1)
+ call s:init_option('vimtex_doc_handlers', [])
+
+ call s:init_option('vimtex_echo_verbose_input', 1)
+
+ call s:init_option('vimtex_env_change_autofill', 0)
+
+ if &diff
+ let g:vimtex_fold_enabled = 0
+ else
+ call s:init_option('vimtex_fold_enabled', 0)
+ endif
+ call s:init_option('vimtex_fold_manual', 0)
+ call s:init_option('vimtex_fold_levelmarker', '*')
+ call s:init_option('vimtex_fold_types', {})
+ call s:init_option('vimtex_fold_types_defaults', {
+ \ 'preamble' : {},
+ \ 'comments' : { 'enabled' : 0 },
+ \ 'envs' : {
+ \ 'blacklist' : [],
+ \ 'whitelist' : [],
+ \ },
+ \ 'env_options' : {},
+ \ 'markers' : {},
+ \ 'sections' : {
+ \ 'parse_levels' : 0,
+ \ 'sections' : [
+ \ 'part',
+ \ 'chapter',
+ \ 'section',
+ \ 'subsection',
+ \ 'subsubsection',
+ \ ],
+ \ 'parts' : [
+ \ 'appendix',
+ \ 'frontmatter',
+ \ 'mainmatter',
+ \ 'backmatter',
+ \ ],
+ \ },
+ \ 'cmd_single' : {
+ \ 'cmds' : [
+ \ 'hypersetup',
+ \ 'tikzset',
+ \ 'pgfplotstableread',
+ \ 'lstset',
+ \ ],
+ \ },
+ \ 'cmd_single_opt' : {
+ \ 'cmds' : [
+ \ 'usepackage',
+ \ 'includepdf',
+ \ ],
+ \ },
+ \ 'cmd_multi' : {
+ \ 'cmds' : [
+ \ '%(re)?new%(command|environment)',
+ \ 'providecommand',
+ \ 'presetkeys',
+ \ 'Declare%(Multi|Auto)?CiteCommand',
+ \ 'Declare%(Index)?%(Field|List|Name)%(Format|Alias)',
+ \ ],
+ \ },
+ \ 'cmd_addplot' : {
+ \ 'cmds' : [
+ \ 'addplot[+3]?',
+ \ ],
+ \ },
+ \})
+
+ call s:init_option('vimtex_format_enabled', 0)
+
+ call s:init_option('vimtex_imaps_enabled', 1)
+ call s:init_option('vimtex_imaps_disabled', [])
+ call s:init_option('vimtex_imaps_list', [
+ \ { 'lhs' : '0', 'rhs' : '\emptyset' },
+ \ { 'lhs' : '6', 'rhs' : '\partial' },
+ \ { 'lhs' : '8', 'rhs' : '\infty' },
+ \ { 'lhs' : '=', 'rhs' : '\equiv' },
+ \ { 'lhs' : '\', 'rhs' : '\setminus' },
+ \ { 'lhs' : '.', 'rhs' : '\cdot' },
+ \ { 'lhs' : '*', 'rhs' : '\times' },
+ \ { 'lhs' : '<', 'rhs' : '\langle' },
+ \ { 'lhs' : '>', 'rhs' : '\rangle' },
+ \ { 'lhs' : 'H', 'rhs' : '\hbar' },
+ \ { 'lhs' : '+', 'rhs' : '\dagger' },
+ \ { 'lhs' : '[', 'rhs' : '\subseteq' },
+ \ { 'lhs' : ']', 'rhs' : '\supseteq' },
+ \ { 'lhs' : '(', 'rhs' : '\subset' },
+ \ { 'lhs' : ')', 'rhs' : '\supset' },
+ \ { 'lhs' : 'A', 'rhs' : '\forall' },
+ \ { 'lhs' : 'E', 'rhs' : '\exists' },
+ \ { 'lhs' : 'jj', 'rhs' : '\downarrow' },
+ \ { 'lhs' : 'jJ', 'rhs' : '\Downarrow' },
+ \ { 'lhs' : 'jk', 'rhs' : '\uparrow' },
+ \ { 'lhs' : 'jK', 'rhs' : '\Uparrow' },
+ \ { 'lhs' : 'jh', 'rhs' : '\leftarrow' },
+ \ { 'lhs' : 'jH', 'rhs' : '\Leftarrow' },
+ \ { 'lhs' : 'jl', 'rhs' : '\rightarrow' },
+ \ { 'lhs' : 'jL', 'rhs' : '\Rightarrow' },
+ \ { 'lhs' : 'a', 'rhs' : '\alpha' },
+ \ { 'lhs' : 'b', 'rhs' : '\beta' },
+ \ { 'lhs' : 'c', 'rhs' : '\chi' },
+ \ { 'lhs' : 'd', 'rhs' : '\delta' },
+ \ { 'lhs' : 'e', 'rhs' : '\epsilon' },
+ \ { 'lhs' : 'f', 'rhs' : '\phi' },
+ \ { 'lhs' : 'g', 'rhs' : '\gamma' },
+ \ { 'lhs' : 'h', 'rhs' : '\eta' },
+ \ { 'lhs' : 'i', 'rhs' : '\iota' },
+ \ { 'lhs' : 'k', 'rhs' : '\kappa' },
+ \ { 'lhs' : 'l', 'rhs' : '\lambda' },
+ \ { 'lhs' : 'm', 'rhs' : '\mu' },
+ \ { 'lhs' : 'n', 'rhs' : '\nu' },
+ \ { 'lhs' : 'p', 'rhs' : '\pi' },
+ \ { 'lhs' : 'q', 'rhs' : '\theta' },
+ \ { 'lhs' : 'r', 'rhs' : '\rho' },
+ \ { 'lhs' : 's', 'rhs' : '\sigma' },
+ \ { 'lhs' : 't', 'rhs' : '\tau' },
+ \ { 'lhs' : 'y', 'rhs' : '\psi' },
+ \ { 'lhs' : 'u', 'rhs' : '\upsilon' },
+ \ { 'lhs' : 'w', 'rhs' : '\omega' },
+ \ { 'lhs' : 'z', 'rhs' : '\zeta' },
+ \ { 'lhs' : 'x', 'rhs' : '\xi' },
+ \ { 'lhs' : 'G', 'rhs' : '\Gamma' },
+ \ { 'lhs' : 'D', 'rhs' : '\Delta' },
+ \ { 'lhs' : 'F', 'rhs' : '\Phi' },
+ \ { 'lhs' : 'G', 'rhs' : '\Gamma' },
+ \ { 'lhs' : 'L', 'rhs' : '\Lambda' },
+ \ { 'lhs' : 'P', 'rhs' : '\Pi' },
+ \ { 'lhs' : 'Q', 'rhs' : '\Theta' },
+ \ { 'lhs' : 'S', 'rhs' : '\Sigma' },
+ \ { 'lhs' : 'U', 'rhs' : '\Upsilon' },
+ \ { 'lhs' : 'W', 'rhs' : '\Omega' },
+ \ { 'lhs' : 'X', 'rhs' : '\Xi' },
+ \ { 'lhs' : 'Y', 'rhs' : '\Psi' },
+ \ { 'lhs' : 've', 'rhs' : '\varepsilon' },
+ \ { 'lhs' : 'vf', 'rhs' : '\varphi' },
+ \ { 'lhs' : 'vk', 'rhs' : '\varkappa' },
+ \ { 'lhs' : 'vq', 'rhs' : '\vartheta' },
+ \ { 'lhs' : 'vr', 'rhs' : '\varrho' },
+ \ { 'lhs' : '/', 'rhs' : 'vimtex#imaps#style_math("slashed")', 'expr' : 1, 'leader' : '#'},
+ \ { 'lhs' : 'b', 'rhs' : 'vimtex#imaps#style_math("mathbf")', 'expr' : 1, 'leader' : '#'},
+ \ { 'lhs' : 'f', 'rhs' : 'vimtex#imaps#style_math("mathfrak")', 'expr' : 1, 'leader' : '#'},
+ \ { 'lhs' : 'c', 'rhs' : 'vimtex#imaps#style_math("mathcal")', 'expr' : 1, 'leader' : '#'},
+ \ { 'lhs' : '-', 'rhs' : 'vimtex#imaps#style_math("overline")', 'expr' : 1, 'leader' : '#'},
+ \ { 'lhs' : 'B', 'rhs' : 'vimtex#imaps#style_math("mathbb")', 'expr' : 1, 'leader' : '#'},
+ \])
+
+ call s:init_option('vimtex_mappings_enabled', 1)
+ call s:init_option('vimtex_mappings_disable', {})
+
+ call s:init_option('vimtex_matchparen_enabled', 1)
+ call s:init_option('vimtex_motion_enabled', 1)
+
+ call s:init_option('vimtex_labels_enabled', 1)
+ call s:init_option('vimtex_labels_refresh_always', 1)
+
+ call s:init_option('vimtex_parser_bib_backend', 'bibtex')
+
+ call s:init_option('vimtex_quickfix_enabled', 1)
+ call s:init_option('vimtex_quickfix_method', 'latexlog')
+ call s:init_option('vimtex_quickfix_autojump', '0')
+ call s:init_option('vimtex_quickfix_ignore_filters', [])
+ call s:init_option('vimtex_quickfix_mode', '2')
+ call s:init_option('vimtex_quickfix_open_on_warning', '1')
+ call s:init_option('vimtex_quickfix_blgparser', {})
+ call s:init_option('vimtex_quickfix_autoclose_after_keystrokes', '0')
+
+ call s:init_option('vimtex_syntax_enabled', 1)
+ call s:init_option('vimtex_syntax_nested', {
+ \ 'aliases' : {
+ \ 'C' : 'c',
+ \ 'csharp' : 'cs',
+ \ },
+ \ 'ignored' : {
+ \ 'cs' : [
+ \ 'csBraces',
+ \ ],
+ \ 'python' : [
+ \ 'pythonEscape',
+ \ 'pythonBEscape',
+ \ 'pythonBytesEscape',
+ \ ],
+ \ 'java' : [
+ \ 'javaError',
+ \ ],
+ \ 'haskell' : [
+ \ 'hsVarSym',
+ \ ],
+ \ }
+ \})
+
+ call s:init_option('vimtex_texcount_custom_arg', '')
+
+ call s:init_option('vimtex_text_obj_enabled', 1)
+ call s:init_option('vimtex_text_obj_variant', 'auto')
+ call s:init_option('vimtex_text_obj_linewise_operators', ['d', 'y'])
+
+ call s:init_option('vimtex_toc_enabled', 1)
+ call s:init_option('vimtex_toc_custom_matchers', [])
+ call s:init_option('vimtex_toc_show_preamble', 1)
+ call s:init_option('vimtex_toc_todo_keywords', ['TODO', 'FIXME'])
+ call s:init_option('vimtex_toc_config', {
+ \ 'name' : 'Table of contents (vimtex)',
+ \ 'mode' : 1,
+ \ 'fold_enable' : 0,
+ \ 'fold_level_start' : -1,
+ \ 'hide_line_numbers' : 1,
+ \ 'hotkeys_enabled' : 0,
+ \ 'hotkeys' : 'abcdeilmnopuvxyz',
+ \ 'hotkeys_leader' : ';',
+ \ 'layer_status' : {
+ \ 'content': 1,
+ \ 'label': 1,
+ \ 'todo': 1,
+ \ 'include': 1,
+ \ },
+ \ 'layer_keys' : {
+ \ 'content': 'C',
+ \ 'label': 'L',
+ \ 'todo': 'T',
+ \ 'include': 'I',
+ \ },
+ \ 'resize' : 0,
+ \ 'refresh_always' : 1,
+ \ 'show_help' : 1,
+ \ 'show_numbers' : 1,
+ \ 'split_pos' : 'vert leftabove',
+ \ 'split_width' : 30,
+ \ 'tocdepth' : 3,
+ \ 'todo_sorted' : 1,
+ \})
+
+ call s:init_option('vimtex_view_enabled', 1)
+ call s:init_option('vimtex_view_automatic', 1)
+ call s:init_option('vimtex_view_method', 'general')
+ call s:init_option('vimtex_view_use_temp_files', 0)
+ call s:init_option('vimtex_view_forward_search_on_start', 1)
+
+ " OS dependent defaults
+ let l:os = vimtex#util#get_os()
+ if l:os ==# 'win'
+ if executable('SumatraPDF')
+ call s:init_option('vimtex_view_general_viewer', 'SumatraPDF')
+ call s:init_option('vimtex_view_general_options',
+ \ '-reuse-instance -forward-search @tex @line @pdf')
+ call s:init_option('vimtex_view_general_options_latexmk',
+ \ 'reuse-instance')
+ elseif executable('mupdf')
+ call s:init_option('vimtex_view_general_viewer', 'mupdf')
+ else
+ call s:init_option('vimtex_view_general_viewer', '')
+ endif
+ else
+ call s:init_option('vimtex_view_general_viewer', get({
+ \ 'linux' : 'xdg-open',
+ \ 'mac' : 'open',
+ \ 'win' : 'start',
+ \}, l:os, ''))
+ call s:init_option('vimtex_view_general_options', '@pdf')
+ call s:init_option('vimtex_view_general_options_latexmk', '')
+ endif
+
+ call s:init_option('vimtex_view_mupdf_options', '')
+ call s:init_option('vimtex_view_mupdf_send_keys', '')
+ call s:init_option('vimtex_view_skim_activate', 0)
+ call s:init_option('vimtex_view_skim_reading_bar', 1)
+ call s:init_option('vimtex_view_zathura_options', '')
+endfunction
+
+" }}}1
+function! vimtex#check_plugin_clash() abort " {{{1
+ let l:scriptnames = vimtex#util#command('scriptnames')
+
+ let l:latexbox = !empty(filter(copy(l:scriptnames), "v:val =~# 'latex-box'"))
+ if l:latexbox
+ let l:polyglot = !empty(filter(copy(l:scriptnames), "v:val =~# 'polyglot'"))
+ call vimtex#log#warning([
+ \ 'Conflicting plugin detected: LaTeX-Box',
+ \ 'vimtex does not work as expected when LaTeX-Box is installed!',
+ \ 'Please disable or remove it to use vimtex!',
+ \])
+ if l:polyglot
+ call vimtex#log#warning([
+ \ 'LaTeX-Box is included with vim-polyglot and may be disabled with:',
+ \ 'let g:polyglot_disabled = [''latex'']',
+ \])
+ endif
+ endif
+endfunction
+
+" }}}1
+
+function! s:init_option(option, default) abort " {{{1
+ let l:option = 'g:' . a:option
+ if !exists(l:option)
+ let {l:option} = a:default
+ elseif type(a:default) == type({})
+ call vimtex#util#extend_recursive({l:option}, a:default, 'keep')
+ endif
+endfunction
+
+" }}}1
+function! s:init_highlights() abort " {{{1
+ for [l:name, l:target] in [
+ \ ['VimtexImapsArrow', 'Comment'],
+ \ ['VimtexImapsLhs', 'ModeMsg'],
+ \ ['VimtexImapsRhs', 'ModeMsg'],
+ \ ['VimtexImapsWrapper', 'Type'],
+ \ ['VimtexInfo', 'Question'],
+ \ ['VimtexInfoTitle', 'PreProc'],
+ \ ['VimtexInfoKey', 'PreProc'],
+ \ ['VimtexInfoValue', 'Statement'],
+ \ ['VimtexInfoOther', 'Normal'],
+ \ ['VimtexMsg', 'ModeMsg'],
+ \ ['VimtexSuccess', 'Statement'],
+ \ ['VimtexTocHelp', 'helpVim'],
+ \ ['VimtexTocHelpKey', 'ModeMsg'],
+ \ ['VimtexTocHelpLayerOn', 'Statement'],
+ \ ['VimtexTocHelpLayerOff', 'Comment'],
+ \ ['VimtexTocTodo', 'Todo'],
+ \ ['VimtexTocNum', 'Number'],
+ \ ['VimtexTocSec0', 'Title'],
+ \ ['VimtexTocSec1', 'Normal'],
+ \ ['VimtexTocSec2', 'helpVim'],
+ \ ['VimtexTocSec3', 'NonText'],
+ \ ['VimtexTocSec4', 'Comment'],
+ \ ['VimtexTocHotkey', 'Comment'],
+ \ ['VimtexTocLabelsSecs', 'Statement'],
+ \ ['VimtexTocLabelsEq', 'PreProc'],
+ \ ['VimtexTocLabelsFig', 'Identifier'],
+ \ ['VimtexTocLabelsTab', 'String'],
+ \ ['VimtexTocIncl', 'Number'],
+ \ ['VimtexTocInclPath', 'Normal'],
+ \ ['VimtexWarning', 'WarningMsg'],
+ \ ['VimtexError', 'ErrorMsg'],
+ \]
+ if !hlexists(l:name)
+ silent execute 'highlight default link' l:name l:target
+ endif
+ endfor
+endfunction
+
+" }}}1
+function! s:init_state() abort " {{{1
+ call vimtex#state#init()
+ call vimtex#state#init_local()
+endfunction
+
+" }}}1
+function! s:init_buffer() abort " {{{1
+ " Set Vim options
+ for l:suf in [
+ \ '.sty',
+ \ '.cls',
+ \ '.log',
+ \ '.aux',
+ \ '.bbl',
+ \ '.out',
+ \ '.blg',
+ \ '.brf',
+ \ '.cb',
+ \ '.dvi',
+ \ '.fdb_latexmk',
+ \ '.fls',
+ \ '.idx',
+ \ '.ilg',
+ \ '.ind',
+ \ '.inx',
+ \ '.pdf',
+ \ '.synctex.gz',
+ \ '.toc',
+ \ ]
+ execute 'set suffixes+=' . l:suf
+ endfor
+ setlocal suffixesadd=.sty,.tex,.cls
+ setlocal comments=sO:%\ -,mO:%\ \ ,eO:%%,:%
+ setlocal commentstring=%%s
+ setlocal iskeyword+=:
+ setlocal includeexpr=vimtex#include#expr()
+ let &l:include = g:vimtex#re#tex_include
+ let &l:define = '\\\([egx]\|char\|mathchar\|count\|dimen\|muskip\|skip'
+ let &l:define .= '\|toks\)\=def\|\\font\|\\\(future\)\=let'
+ let &l:define .= '\|\\new\(count\|dimen\|skip'
+ let &l:define .= '\|muskip\|box\|toks\|read\|write\|fam\|insert\)'
+ let &l:define .= '\|\\\(re\)\=new\(boolean\|command\|counter\|environment'
+ let &l:define .= '\|font\|if\|length\|savebox'
+ let &l:define .= '\|theorem\(style\)\=\)\s*\*\=\s*{\='
+ let &l:define .= '\|DeclareMathOperator\s*{\=\s*'
+
+ " Define autocommands
+ augroup vimtex_buffers
+ autocmd! * <buffer>
+ autocmd BufFilePre <buffer> call s:filename_changed_pre()
+ autocmd BufFilePost <buffer> call s:filename_changed_post()
+ autocmd BufUnload <buffer> call s:buffer_deleted('unload')
+ autocmd BufWipeout <buffer> call s:buffer_deleted('wipe')
+ augroup END
+
+ " Initialize buffer settings for sub modules
+ for l:mod in s:modules
+ if index(get(b:vimtex, 'disabled_modules', []), l:mod) >= 0 | continue | endif
+
+ try
+ call vimtex#{l:mod}#init_buffer()
+ catch /E117.*#init_/
+ catch /E127.*vimtex#profile#/
+ endtry
+ endfor
+endfunction
+
+" }}}1
+function! s:init_default_mappings() abort " {{{1
+ if !g:vimtex_mappings_enabled | return | endif
+
+ function! s:map(mode, lhs, rhs, ...) abort
+ if !hasmapto(a:rhs, a:mode)
+ \ && index(get(g:vimtex_mappings_disable, a:mode, []), a:lhs) < 0
+ \ && (empty(maparg(a:lhs, a:mode)) || a:0 > 0)
+ silent execute a:mode . 'map <silent><nowait><buffer>' a:lhs a:rhs
+ endif
+ endfunction
+
+ call s:map('n', '<localleader>li', '<plug>(vimtex-info)')
+ call s:map('n', '<localleader>lI', '<plug>(vimtex-info-full)')
+ call s:map('n', '<localleader>lx', '<plug>(vimtex-reload)')
+ call s:map('n', '<localleader>lX', '<plug>(vimtex-reload-state)')
+ call s:map('n', '<localleader>ls', '<plug>(vimtex-toggle-main)')
+ call s:map('n', '<localleader>lq', '<plug>(vimtex-log)')
+
+ call s:map('n', 'ds$', '<plug>(vimtex-env-delete-math)')
+ call s:map('n', 'cs$', '<plug>(vimtex-env-change-math)')
+ call s:map('n', 'dse', '<plug>(vimtex-env-delete)')
+ call s:map('n', 'cse', '<plug>(vimtex-env-change)')
+ call s:map('n', 'tse', '<plug>(vimtex-env-toggle-star)')
+
+ call s:map('n', 'dsc', '<plug>(vimtex-cmd-delete)')
+ call s:map('n', 'csc', '<plug>(vimtex-cmd-change)')
+ call s:map('n', 'tsc', '<plug>(vimtex-cmd-toggle-star)')
+ call s:map('n', 'tsf', '<plug>(vimtex-cmd-toggle-frac)')
+ call s:map('x', 'tsf', '<plug>(vimtex-cmd-toggle-frac)')
+ call s:map('i', '<F7>', '<plug>(vimtex-cmd-create)')
+ call s:map('n', '<F7>', '<plug>(vimtex-cmd-create)')
+ call s:map('x', '<F7>', '<plug>(vimtex-cmd-create)')
+
+ call s:map('n', 'dsd', '<plug>(vimtex-delim-delete)')
+ call s:map('n', 'csd', '<plug>(vimtex-delim-change-math)')
+ call s:map('n', 'tsd', '<plug>(vimtex-delim-toggle-modifier)')
+ call s:map('x', 'tsd', '<plug>(vimtex-delim-toggle-modifier)')
+ call s:map('n', 'tsD', '<plug>(vimtex-delim-toggle-modifier-reverse)')
+ call s:map('x', 'tsD', '<plug>(vimtex-delim-toggle-modifier-reverse)')
+ call s:map('i', ']]', '<plug>(vimtex-delim-close)')
+
+ if g:vimtex_compiler_enabled
+ call s:map('n', '<localleader>ll', '<plug>(vimtex-compile)')
+ call s:map('n', '<localleader>lo', '<plug>(vimtex-compile-output)')
+ call s:map('n', '<localleader>lL', '<plug>(vimtex-compile-selected)')
+ call s:map('x', '<localleader>lL', '<plug>(vimtex-compile-selected)')
+ call s:map('n', '<localleader>lk', '<plug>(vimtex-stop)')
+ call s:map('n', '<localleader>lK', '<plug>(vimtex-stop-all)')
+ call s:map('n', '<localleader>le', '<plug>(vimtex-errors)')
+ call s:map('n', '<localleader>lc', '<plug>(vimtex-clean)')
+ call s:map('n', '<localleader>lC', '<plug>(vimtex-clean-full)')
+ call s:map('n', '<localleader>lg', '<plug>(vimtex-status)')
+ call s:map('n', '<localleader>lG', '<plug>(vimtex-status-all)')
+ endif
+
+ if g:vimtex_motion_enabled
+ " These are forced in order to overwrite matchit mappings
+ call s:map('n', '%', '<plug>(vimtex-%)', 1)
+ call s:map('x', '%', '<plug>(vimtex-%)', 1)
+ call s:map('o', '%', '<plug>(vimtex-%)', 1)
+
+ call s:map('n', ']]', '<plug>(vimtex-]])')
+ call s:map('n', '][', '<plug>(vimtex-][)')
+ call s:map('n', '[]', '<plug>(vimtex-[])')
+ call s:map('n', '[[', '<plug>(vimtex-[[)')
+ call s:map('x', ']]', '<plug>(vimtex-]])')
+ call s:map('x', '][', '<plug>(vimtex-][)')
+ call s:map('x', '[]', '<plug>(vimtex-[])')
+ call s:map('x', '[[', '<plug>(vimtex-[[)')
+ call s:map('o', ']]', '<plug>(vimtex-]])')
+ call s:map('o', '][', '<plug>(vimtex-][)')
+ call s:map('o', '[]', '<plug>(vimtex-[])')
+ call s:map('o', '[[', '<plug>(vimtex-[[)')
+
+ call s:map('n', ']M', '<plug>(vimtex-]M)')
+ call s:map('n', ']m', '<plug>(vimtex-]m)')
+ call s:map('n', '[M', '<plug>(vimtex-[M)')
+ call s:map('n', '[m', '<plug>(vimtex-[m)')
+ call s:map('x', ']M', '<plug>(vimtex-]M)')
+ call s:map('x', ']m', '<plug>(vimtex-]m)')
+ call s:map('x', '[M', '<plug>(vimtex-[M)')
+ call s:map('x', '[m', '<plug>(vimtex-[m)')
+ call s:map('o', ']M', '<plug>(vimtex-]M)')
+ call s:map('o', ']m', '<plug>(vimtex-]m)')
+ call s:map('o', '[M', '<plug>(vimtex-[M)')
+ call s:map('o', '[m', '<plug>(vimtex-[m)')
+
+ call s:map('n', ']/', '<plug>(vimtex-]/)')
+ call s:map('n', ']*', '<plug>(vimtex-]*)')
+ call s:map('n', '[/', '<plug>(vimtex-[/)')
+ call s:map('n', '[*', '<plug>(vimtex-[*)')
+ call s:map('x', ']/', '<plug>(vimtex-]/)')
+ call s:map('x', ']*', '<plug>(vimtex-]*)')
+ call s:map('x', '[/', '<plug>(vimtex-[/)')
+ call s:map('x', '[*', '<plug>(vimtex-[*)')
+ call s:map('o', ']/', '<plug>(vimtex-]/)')
+ call s:map('o', ']*', '<plug>(vimtex-]*)')
+ call s:map('o', '[/', '<plug>(vimtex-[/)')
+ call s:map('o', '[*', '<plug>(vimtex-[*)')
+ endif
+
+ if g:vimtex_text_obj_enabled
+ call s:map('x', 'id', '<plug>(vimtex-id)')
+ call s:map('x', 'ad', '<plug>(vimtex-ad)')
+ call s:map('o', 'id', '<plug>(vimtex-id)')
+ call s:map('o', 'ad', '<plug>(vimtex-ad)')
+ call s:map('x', 'i$', '<plug>(vimtex-i$)')
+ call s:map('x', 'a$', '<plug>(vimtex-a$)')
+ call s:map('o', 'i$', '<plug>(vimtex-i$)')
+ call s:map('o', 'a$', '<plug>(vimtex-a$)')
+ call s:map('x', 'iP', '<plug>(vimtex-iP)')
+ call s:map('x', 'aP', '<plug>(vimtex-aP)')
+ call s:map('o', 'iP', '<plug>(vimtex-iP)')
+ call s:map('o', 'aP', '<plug>(vimtex-aP)')
+ call s:map('x', 'im', '<plug>(vimtex-im)')
+ call s:map('x', 'am', '<plug>(vimtex-am)')
+ call s:map('o', 'im', '<plug>(vimtex-im)')
+ call s:map('o', 'am', '<plug>(vimtex-am)')
+
+ if vimtex#text_obj#targets#enabled()
+ call vimtex#text_obj#targets#init()
+
+ " These are handled explicitly to avoid conflict with gitgutter
+ call s:map('x', 'ic', '<plug>(vimtex-targets-i)c')
+ call s:map('x', 'ac', '<plug>(vimtex-targets-a)c')
+ call s:map('o', 'ic', '<plug>(vimtex-targets-i)c')
+ call s:map('o', 'ac', '<plug>(vimtex-targets-a)c')
+ else
+ if g:vimtex_text_obj_variant ==# 'targets'
+ call vimtex#log#warning(
+ \ "Ignoring g:vimtex_text_obj_variant = 'targets'"
+ \ . " because 'g:loaded_targets' does not exist or is 0.")
+ endif
+ let g:vimtex_text_obj_variant = 'vimtex'
+
+ call s:map('x', 'ie', '<plug>(vimtex-ie)')
+ call s:map('x', 'ae', '<plug>(vimtex-ae)')
+ call s:map('o', 'ie', '<plug>(vimtex-ie)')
+ call s:map('o', 'ae', '<plug>(vimtex-ae)')
+ call s:map('x', 'ic', '<plug>(vimtex-ic)')
+ call s:map('x', 'ac', '<plug>(vimtex-ac)')
+ call s:map('o', 'ic', '<plug>(vimtex-ic)')
+ call s:map('o', 'ac', '<plug>(vimtex-ac)')
+ endif
+ endif
+
+ if g:vimtex_toc_enabled
+ call s:map('n', '<localleader>lt', '<plug>(vimtex-toc-open)')
+ call s:map('n', '<localleader>lT', '<plug>(vimtex-toc-toggle)')
+ endif
+
+ if has_key(b:vimtex, 'viewer')
+ call s:map('n', '<localleader>lv', '<plug>(vimtex-view)')
+ if has_key(b:vimtex.viewer, 'reverse_search')
+ call s:map('n', '<localleader>lr', '<plug>(vimtex-reverse-search)')
+ endif
+ endif
+
+ if g:vimtex_imaps_enabled
+ call s:map('n', '<localleader>lm', '<plug>(vimtex-imaps-list)')
+ endif
+
+ if g:vimtex_doc_enabled
+ call s:map('n', 'K', '<plug>(vimtex-doc-package)')
+ endif
+endfunction
+
+" }}}1
+
+function! s:filename_changed_pre() abort " {{{1
+ let s:filename_changed = expand('%:p') ==# b:vimtex.tex
+endfunction
+
+" }}}1
+function! s:filename_changed_post() abort " {{{1
+ if s:filename_changed
+ let l:base_old = b:vimtex.base
+ let b:vimtex.tex = fnamemodify(expand('%'), ':p')
+ let b:vimtex.base = fnamemodify(b:vimtex.tex, ':t')
+ let b:vimtex.name = fnamemodify(b:vimtex.tex, ':t:r')
+
+ call vimtex#log#warning('Filename change detected')
+ call vimtex#log#info('Old filename: ' . l:base_old)
+ call vimtex#log#info('New filename: ' . b:vimtex.base)
+
+ if has_key(b:vimtex, 'compiler')
+ if b:vimtex.compiler.is_running()
+ call vimtex#log#warning('Compilation stopped!')
+ call vimtex#compiler#stop()
+ endif
+ let b:vimtex.compiler.target = b:vimtex.base
+ let b:vimtex.compiler.target_path = b:vimtex.tex
+ endif
+ endif
+endfunction
+
+" }}}1
+function! s:buffer_deleted(reason) abort " {{{1
+ "
+ " We need a simple cache of buffer ids because a buffer unload might clear
+ " buffer variables, so that a subsequent buffer wipe will not trigger a full
+ " cleanup. By caching the buffer id, we should avoid this issue.
+ "
+ let s:buffer_cache = get(s:, 'buffer_cache', {})
+ let l:file = expand('<afile>')
+
+ if !has_key(s:buffer_cache, l:file)
+ let s:buffer_cache[l:file] = getbufvar(l:file, 'vimtex_id', -1)
+ endif
+
+ if a:reason ==# 'wipe'
+ call vimtex#state#cleanup(s:buffer_cache[l:file])
+ call remove(s:buffer_cache, l:file)
+ endif
+endfunction
+
+" }}}1
+function! s:quit() abort " {{{1
+ for l:state in vimtex#state#list_all()
+ call l:state.cleanup()
+ endfor
+
+ call vimtex#cache#write_all()
+endfunction
+
+" }}}1
+
+
+" {{{1 Initialize module
+
+let s:modules = map(
+ \ glob(fnamemodify(expand('<sfile>'), ':r') . '/*.vim', 0, 1),
+ \ 'fnamemodify(v:val, '':t:r'')')
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/cache.vim b/autoload/vimtex/cache.vim
new file mode 100644
index 00000000..a9ab8be1
--- /dev/null
+++ b/autoload/vimtex/cache.vim
@@ -0,0 +1,179 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#cache#open(name, ...) abort " {{{1
+ let l:opts = a:0 > 0 ? a:1 : {}
+ let l:name = get(l:opts, 'local') ? s:local_name(a:name) : a:name
+
+ let s:caches = get(s:, 'caches', {})
+ if has_key(s:caches, l:name)
+ return s:caches[l:name]
+ endif
+
+ let s:caches[l:name] = s:cache.init(l:name, l:opts)
+ return s:caches[l:name]
+endfunction
+
+" }}}1
+function! vimtex#cache#close(name) abort " {{{1
+ let s:caches = get(s:, 'caches', {})
+
+ " Try global name first, then local name
+ let l:name = a:name
+ if !has_key(s:caches, l:name)
+ let l:name = s:local_name(l:name)
+ endif
+ if !has_key(s:caches, l:name) | return | endif
+
+ let l:cache = s:caches[l:name]
+ call l:cache.write()
+ unlet s:caches[l:name]
+endfunction
+
+" }}}1
+function! vimtex#cache#wrap(Func, name, ...) abort " {{{1
+ if !has('lambda')
+ throw 'error: vimtex#cache#wrap requires +lambda'
+ endif
+
+ let l:opts = a:0 > 0 ? a:1 : {}
+ let l:cache = vimtex#cache#open(a:name, l:opts)
+
+ function! CachedFunc(key) closure
+ if l:cache.has(a:key)
+ return l:cache.get(a:key)
+ else
+ return l:cache.set(a:key, a:Func(a:key))
+ endif
+ endfunction
+
+ return function('CachedFunc')
+endfunction
+
+" }}}1
+function! vimtex#cache#clear(name, local) abort " {{{1
+ let l:cache = vimtex#cache#open(a:name, {'local': a:local})
+
+ call l:cache.read()
+ if !empty(l:cache.data)
+ let l:cache.data = {}
+ call l:cache.write()
+ endif
+endfunction
+
+" }}}1
+function! vimtex#cache#write_all() abort " {{{1
+ for l:cache in values(get(s:, 'caches', {}))
+ call l:cache.write()
+ endfor
+endfunction
+
+" }}}1
+
+let s:cache = {}
+
+function! s:cache.init(name, opts) dict abort " {{{1
+ let new = deepcopy(self)
+ unlet new.init
+
+ let l:root = get(g:, 'vimtex_cache_root', $HOME . '/.cache/vimtex')
+ if !isdirectory(l:root)
+ call mkdir(l:root, 'p')
+ endif
+
+ let new.name = a:name
+ let new.path = l:root . '/' . a:name . '.json'
+ let new.local = get(a:opts, 'local')
+ let new.persistent = get(a:opts, 'persistent',
+ \ get(g:, 'vimtex_cache_persistent', 1))
+
+ if has_key(a:opts, 'default')
+ let new.default = a:opts.default
+ endif
+
+ let new.data = {}
+ let new.ftime = -1
+ let new.modified = 0
+
+ return new
+endfunction
+
+" }}}1
+function! s:cache.get(key) dict abort " {{{1
+ call self.read()
+
+ if has_key(self, 'default') && !has_key(self.data, a:key)
+ let self.data[a:key] = deepcopy(self.default)
+ endif
+
+ return get(self.data, a:key)
+endfunction
+
+" }}}1
+function! s:cache.has(key) dict abort " {{{1
+ call self.read()
+
+ return has_key(self.data, a:key)
+endfunction
+
+" }}}1
+function! s:cache.set(key, value) dict abort " {{{1
+ call self.read()
+
+ let self.data[a:key] = a:value
+ let self.modified = 1
+ call self.write()
+
+ return a:value
+endfunction
+
+" }}}1
+function! s:cache.write() dict abort " {{{1
+ if !self.persistent
+ let self.modified = 0
+ return
+ endif
+
+ if !self.modified | return | endif
+
+ call self.read()
+ call writefile([json_encode(self.data)], self.path)
+ let self.ftime = getftime(self.path)
+ let self.modified = 0
+endfunction
+
+" }}}1
+function! s:cache.read() dict abort " {{{1
+ if !self.persistent | return | endif
+
+ if getftime(self.path) > self.ftime
+ let self.ftime = getftime(self.path)
+ call extend(self.data,
+ \ json_decode(join(readfile(self.path))), 'keep')
+ endif
+endfunction
+
+" }}}1
+
+"
+" Utility functions
+"
+function! s:local_name(name) abort " {{{1
+ let l:filename = exists('b:vimtex.tex')
+ \ ? fnamemodify(b:vimtex.tex, ':r')
+ \ : expand('%:p:r')
+ let l:filename = substitute(l:filename, '\s\+', '_', 'g')
+ let l:filename = substitute(l:filename, '\/', '%', 'g')
+ let l:filename = substitute(l:filename, '\\', '%', 'g')
+ let l:filename = substitute(l:filename, ':', '%', 'g')
+ return a:name . l:filename
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/cmd.vim b/autoload/vimtex/cmd.vim
new file mode 100644
index 00000000..06b5e14c
--- /dev/null
+++ b/autoload/vimtex/cmd.vim
@@ -0,0 +1,718 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#cmd#init_buffer() abort " {{{1
+ nnoremap <silent><buffer> <plug>(vimtex-cmd-delete)
+ \ :<c-u>call <sid>operator_setup('delete')<bar>normal! g@l<cr>
+
+ nnoremap <silent><buffer> <plug>(vimtex-cmd-change)
+ \ :<c-u>call <sid>operator_setup('change')<bar>normal! g@l<cr>
+
+ inoremap <silent><buffer> <plug>(vimtex-cmd-create)
+ \ <c-r>=vimtex#cmd#create_insert()<cr>
+
+ nnoremap <silent><buffer> <plug>(vimtex-cmd-create)
+ \ :<c-u>call <sid>operator_setup('create')<bar>normal! g@l<cr>
+
+ xnoremap <silent><buffer> <plug>(vimtex-cmd-create)
+ \ :<c-u>call vimtex#cmd#create_visual()<cr>
+
+ nnoremap <silent><buffer> <plug>(vimtex-cmd-toggle-star)
+ \ :<c-u>call <sid>operator_setup('toggle_star')<bar>normal! g@l<cr>
+
+ nnoremap <silent><buffer> <plug>(vimtex-cmd-toggle-frac)
+ \ :<c-u>call <sid>operator_setup('toggle_frac')<bar>normal! g@l<cr>
+
+ xnoremap <silent><buffer> <plug>(vimtex-cmd-toggle-frac)
+ \ :<c-u>call vimtex#cmd#toggle_frac_visual()<cr>
+endfunction
+
+" }}}1
+
+function! vimtex#cmd#change(new_name) abort " {{{1
+ let l:cmd = vimtex#cmd#get_current()
+ if empty(l:cmd) | return | endif
+
+ let l:old_name = l:cmd.name
+ let l:lnum = l:cmd.pos_start.lnum
+ let l:cnum = l:cmd.pos_start.cnum
+
+ " Get new command name
+ let l:new_name = substitute(a:new_name, '^\\', '', '')
+ if empty(l:new_name) | return | endif
+
+ " Update current position
+ let l:save_pos = vimtex#pos#get_cursor()
+ if strlen(l:new_name) < strlen(l:old_name)
+ let l:col = searchpos('\\\k', 'bcnW')[1] + strlen(l:new_name)
+ if l:col < l:save_pos[2]
+ let l:save_pos[2] = l:col
+ endif
+ endif
+
+ " Perform the change
+ let l:line = getline(l:lnum)
+ call setline(l:lnum,
+ \ strpart(l:line, 0, l:cnum)
+ \ . l:new_name
+ \ . strpart(l:line, l:cnum + strlen(l:old_name) - 1))
+
+ " Restore cursor position
+ cal vimtex#pos#set_cursor(l:save_pos)
+endfunction
+
+function! vimtex#cmd#delete(...) abort " {{{1
+ if a:0 > 0
+ let l:cmd = call('vimtex#cmd#get_at', a:000)
+ else
+ let l:cmd = vimtex#cmd#get_current()
+ endif
+ if empty(l:cmd) | return | endif
+
+ " Save current position
+ let l:save_pos = vimtex#pos#get_cursor()
+ let l:lnum_cur = l:save_pos[1]
+ let l:cnum_cur = l:save_pos[2]
+
+ " Remove closing bracket (if exactly one argument)
+ if len(l:cmd.args) == 1
+ let l:lnum = l:cmd.args[0].close.lnum
+ let l:cnum = l:cmd.args[0].close.cnum
+ let l:line = getline(l:lnum)
+ call setline(l:lnum,
+ \ strpart(l:line, 0, l:cnum - 1)
+ \ . strpart(l:line, l:cnum))
+
+ let l:cnum2 = l:cmd.args[0].open.cnum
+ endif
+
+ " Remove command (and possibly the opening bracket)
+ let l:lnum = l:cmd.pos_start.lnum
+ let l:cnum = l:cmd.pos_start.cnum
+ let l:cnum2 = get(l:, 'cnum2', l:cnum + strlen(l:cmd.name) - 1)
+ let l:line = getline(l:lnum)
+ call setline(l:lnum,
+ \ strpart(l:line, 0, l:cnum - 1)
+ \ . strpart(l:line, l:cnum2))
+
+ " Restore appropriate cursor position
+ if l:lnum_cur == l:lnum
+ if l:cnum_cur > l:cnum2
+ let l:save_pos[2] -= l:cnum2 - l:cnum + 1
+ else
+ let l:save_pos[2] -= l:cnum_cur - l:cnum
+ endif
+ endif
+ cal vimtex#pos#set_cursor(l:save_pos)
+endfunction
+
+function! vimtex#cmd#delete_all(...) abort " {{{1
+ if a:0 > 0
+ let l:cmd = call('vimtex#cmd#get_at', a:000)
+ else
+ let l:cmd = vimtex#cmd#get_current()
+ endif
+ if empty(l:cmd) | return | endif
+
+ call vimtex#pos#set_cursor(l:cmd.pos_start)
+ normal! v
+ call vimtex#pos#set_cursor(l:cmd.pos_end)
+ normal! d
+endfunction
+
+function! vimtex#cmd#create_insert() abort " {{{1
+ if mode() !=# 'i' | return | endif
+
+ let l:re = '\v%(^|\A)\zs\a+\ze%(\A|$)'
+ let l:c0 = col('.') - 1
+
+ let [l:l1, l:c1] = searchpos(l:re, 'bcn', line('.'))
+ let l:c1 -= 1
+ let l:line = getline(l:l1)
+ let l:match = matchstr(l:line, l:re, l:c1)
+ let l:c2 = l:c1 + strlen(l:match)
+
+ if l:c0 > l:c2
+ call vimtex#log#warning('Could not create command')
+ return ''
+ endif
+
+ let l:strpart1 = strpart(l:line, 0, l:c1)
+ let l:strpart2 = '\' . strpart(l:match, 0, l:c0 - l:c1) . '{'
+ let l:strpart3 = strpart(l:line, l:c0)
+ call setline(l:l1, l:strpart1 . l:strpart2 . l:strpart3)
+
+ call vimtex#pos#set_cursor(l:l1, l:c2+3)
+ return ''
+endfunction
+
+" }}}1
+function! vimtex#cmd#create(cmd, visualmode) abort " {{{1
+ if empty(a:cmd) | return | endif
+
+ " Avoid autoindent (disable indentkeys)
+ let l:save_indentkeys = &l:indentkeys
+ setlocal indentkeys=
+
+ if a:visualmode
+ let l:pos_start = getpos("'<")
+ let l:pos_end = getpos("'>")
+
+ if visualmode() ==# ''
+ normal! gvA}
+ execute 'normal! gvI\' . a:cmd . '{'
+
+ let l:pos_end[2] += strlen(a:cmd) + 3
+ else
+ normal! `>a}
+ normal! `<
+ execute 'normal! i\' . a:cmd . '{'
+
+ let l:pos_end[2] +=
+ \ l:pos_end[1] == l:pos_start[1] ? strlen(a:cmd) + 3 : 1
+ endif
+
+ call vimtex#pos#set_cursor(l:pos_end)
+ else
+ let l:pos = vimtex#pos#get_cursor()
+ let l:save_reg = getreg('"')
+ let l:pos[2] += strlen(a:cmd) + 2
+ execute 'normal! ciw\' . a:cmd . '{"}'
+ call setreg('"', l:save_reg)
+ call vimtex#pos#set_cursor(l:pos)
+ endif
+
+ " Restore indentkeys setting
+ let &l:indentkeys = l:save_indentkeys
+endfunction
+
+" }}}1
+function! vimtex#cmd#create_visual() abort " {{{1
+ let l:cmd = vimtex#echo#input({
+ \ 'info' :
+ \ ['Create command: ', ['VimtexWarning', '(empty to cancel)']],
+ \})
+ let l:cmd = substitute(l:cmd, '^\\', '', '')
+ call vimtex#cmd#create(l:cmd, 1)
+endfunction
+
+" }}}1
+function! vimtex#cmd#toggle_star() abort " {{{1
+ let l:cmd = vimtex#cmd#get_current()
+ if empty(l:cmd) | return | endif
+
+ let l:old_name = l:cmd.name
+ let l:lnum = l:cmd.pos_start.lnum
+ let l:cnum = l:cmd.pos_start.cnum
+
+ " Set new command name
+ if match(l:old_name, '\*$') == -1
+ let l:new_name = l:old_name.'*'
+ else
+ let l:new_name = strpart(l:old_name, 0, strlen(l:old_name)-1)
+ endif
+ let l:new_name = substitute(l:new_name, '^\\', '', '')
+ if empty(l:new_name) | return | endif
+
+ " Update current position
+ let l:save_pos = vimtex#pos#get_cursor()
+ let l:save_pos[2] += strlen(l:new_name) - strlen(l:old_name) + 1
+
+ " Perform the change
+ let l:line = getline(l:lnum)
+ call setline(l:lnum,
+ \ strpart(l:line, 0, l:cnum)
+ \ . l:new_name
+ \ . strpart(l:line, l:cnum + strlen(l:old_name) - 1))
+
+ " Restore cursor position
+ cal vimtex#pos#set_cursor(l:save_pos)
+endfunction
+
+" }}}1
+function! vimtex#cmd#toggle_frac() abort " {{{1
+ let l:frac = s:get_frac_cmd()
+ if empty(l:frac)
+ let l:frac = s:get_frac_inline()
+ endif
+ if empty(l:frac) | return | endif
+
+ let l:lnum = line('.')
+ let l:line = getline(l:lnum)
+ call setline(l:lnum,
+ \ strpart(l:line, 0, l:frac.col_start)
+ \ . l:frac.text_toggled
+ \ . strpart(l:line, l:frac.col_end+1))
+endfunction
+
+" }}}1
+function! vimtex#cmd#toggle_frac_visual() abort " {{{1
+ let l:save_reg = getreg('a')
+ normal! gv"ay
+ let l:selected = substitute(getreg('a'), '\n\s*', ' ', '')
+ call setreg('a', l:save_reg)
+
+ let l:frac = s:get_frac_inline_visual(l:selected)
+ if empty(l:frac)
+ let l:frac = s:get_frac_cmd_visual(l:selected)
+ endif
+
+ if empty(l:frac) | return | endif
+
+ let l:save_reg = getreg('a')
+ call setreg('a', l:frac.text_toggled)
+ normal! gv"ap
+ call setreg('a', l:save_reg)
+endfunction
+
+" }}}1
+
+function! s:get_frac_cmd() abort " {{{1
+ let l:save_pos = vimtex#pos#get_cursor()
+ while v:true
+ let l:cmd = s:get_cmd('prev')
+ if empty(l:cmd) || l:cmd.pos_start.lnum < line('.')
+ call vimtex#pos#set_cursor(l:save_pos)
+ return {}
+ endif
+
+ if l:cmd.name ==# '\frac'
+ break
+ endif
+
+ call vimtex#pos#set_cursor(vimtex#pos#prev(l:cmd.pos_start))
+ endwhile
+ call vimtex#pos#set_cursor(l:save_pos)
+
+ let l:frac = {
+ \ 'type': 'cmd',
+ \ 'col_start': l:cmd.pos_start.cnum - 1,
+ \ 'col_end': l:cmd.pos_end.cnum - 1,
+ \}
+
+ if len(l:cmd.args) >= 2
+ let l:consume = []
+ let l:frac.denominator = l:cmd.args[0].text
+ let l:frac.numerator = l:cmd.args[1].text
+ elseif len(l:cmd.args) == 1
+ let l:consume = ['numerator']
+ let l:frac.denominator = l:cmd.args[0].text
+ let l:frac.numerator = ''
+ else
+ let l:consume = ['denominator', 'numerator']
+ let l:frac.denominator = ''
+ let l:frac.numerator = ''
+ endif
+
+ " Handle unfinished cases
+ let l:line = getline('.')
+ let l:pos = l:frac.col_end + 1
+ for l:key in l:consume
+ let l:part = strpart(l:line, l:frac.col_end + 1)
+
+ let l:blurp = matchstr(l:part, '^\s*{[^}]*}')
+ if !empty(l:blurp)
+ let l:frac[l:key] = vimtex#util#trim(l:blurp)[1:-2]
+ let l:frac.col_end += len(l:blurp)
+ continue
+ endif
+
+ let l:blurp = matchstr(l:part, '^\s*\w')
+ if !empty(l:blurp)
+ let l:frac[l:key] = vimtex#util#trim(l:blurp)
+ let l:frac.col_end += len(l:blurp)
+ endif
+ endfor
+
+ " Abort if \frac region does not cover cursor
+ if l:frac.col_end < col('.') | return {} | endif
+
+ let l:frac.text = strpart(getline('.'),
+ \ l:frac.col_start, l:frac.col_end - l:frac.col_start + 1)
+
+ return s:get_frac_cmd_aux(l:frac)
+endfunction
+
+" }}}1
+function! s:get_frac_cmd_visual(selected) abort " {{{1
+ let l:matches = matchlist(a:selected, '^\s*\\frac\s*{\(.*\)}\s*{\(.*\)}\s*$')
+ if empty(l:matches) | return {} | endif
+
+ let l:frac = {
+ \ 'type': 'cmd',
+ \ 'text': a:selected,
+ \ 'denominator': l:matches[1],
+ \ 'numerator': l:matches[2],
+ \}
+
+ return s:get_frac_cmd_aux(l:frac)
+endfunction
+
+" }}}1
+function! s:get_frac_cmd_aux(frac) abort " {{{1
+ let l:denominator = (a:frac.denominator =~# '^\\\?\w*$')
+ \ ? a:frac.denominator
+ \ : '(' . a:frac.denominator . ')'
+
+ let l:numerator = (a:frac.numerator =~# '^\\\?\w*$')
+ \ ? a:frac.numerator
+ \ : '(' . a:frac.numerator . ')'
+
+ let a:frac.text_toggled = l:denominator . '/' . l:numerator
+
+ return a:frac
+endfunction
+
+" }}}1
+function! s:get_frac_inline() abort " {{{1
+ let l:line = getline('.')
+ let l:col = col('.') - 1
+
+ let l:pos_after = -1
+ let l:pos_before = -1
+ while v:true
+ let l:pos_before = l:pos_after
+ let l:pos_after = match(l:line, '\/', l:pos_after+1)
+ if l:pos_after < 0 || l:pos_after >= l:col | break | endif
+ endwhile
+
+ if l:pos_after == -1 && l:pos_before == -1
+ return {}
+ endif
+
+ let l:positions = []
+ if l:pos_before > 0
+ let l:positions += [l:pos_before]
+ endif
+ if l:pos_after > 0
+ let l:positions += [l:pos_after]
+ endif
+
+ for l:pos in l:positions
+ let l:frac = {'type': 'inline'}
+
+ "
+ " Parse numerator
+ "
+ let l:before = strpart(l:line, 0, l:pos)
+ if l:before =~# ')\s*$'
+ let l:pos_before = s:get_inline_limit(l:before, -1) - 1
+ let l:parens = strpart(l:before, l:pos_before)
+ else
+ let l:pos_before = match(l:before, '\s*$')
+ let l:parens = ''
+ endif
+
+ let l:before = strpart(l:line, 0, l:pos_before)
+ let l:atoms = matchstr(l:before, '\(\\(\)\?\zs[^-$(){} ]*$')
+ let l:pos_before = l:pos_before - strlen(l:atoms)
+ let l:frac.numerator = s:get_inline_trim(l:atoms . l:parens)
+ let l:frac.col_start = l:pos_before
+
+ "
+ " Parse denominator
+ "
+ let l:after = strpart(l:line, l:pos+1)
+ let l:atoms = l:after =~# '^\s*[^$()} ]*\\)'
+ \ ? matchstr(l:after, '^\s*[^$()} ]*\ze\\)')
+ \ : matchstr(l:after, '^\s*[^$()} ]*')
+ let l:pos_after = l:pos + strlen(l:atoms)
+ let l:after = strpart(l:line, l:pos_after+1)
+ if l:after =~# '^('
+ let l:index = s:get_inline_limit(l:after, 1)
+ let l:pos_after = l:pos_after + l:index + 1
+ let l:parens = strpart(l:after, 0, l:index+1)
+ else
+ let l:parens = ''
+ endif
+ let l:frac.denominator = s:get_inline_trim(l:atoms . l:parens)
+ let l:frac.col_end = l:pos_after
+
+ "
+ " Combine/Parse inline and frac expressions
+ "
+ let l:frac.text = strpart(l:line,
+ \ l:frac.col_start,
+ \ l:frac.col_end - l:frac.col_start + 1)
+ let l:frac.text_toggled = printf('\frac{%s}{%s}',
+ \ l:frac.numerator, l:frac.denominator)
+
+ "
+ " Accept result if the range contains the cursor column
+ "
+ if l:col >= l:frac.col_start && l:col <= l:frac.col_end
+ return l:frac
+ endif
+ endfor
+
+ return {}
+endfunction
+
+" }}}1
+function! s:get_frac_inline_visual(selected) abort " {{{1
+ let l:parts = split(a:selected, '/')
+ if len(l:parts) != 2 | return {} | endif
+
+ let l:frac = {
+ \ 'type': 'inline',
+ \ 'text': a:selected,
+ \ 'numerator': s:get_inline_trim(l:parts[0]),
+ \ 'denominator': s:get_inline_trim(l:parts[1]),
+ \}
+
+ let l:frac.text_toggled = printf('\frac{%s}{%s}',
+ \ l:frac.numerator, l:frac.denominator)
+
+ return l:frac
+endfunction
+
+" }}}1
+function! s:get_inline_limit(str, dir) abort " {{{1
+ if a:dir > 0
+ let l:open = '('
+ let l:string = a:str
+ else
+ let l:open = ')'
+ let l:string = join(reverse(split(a:str, '\zs')), '')
+ endif
+
+ let idx = -1
+ let depth = 0
+
+ while idx < len(l:string)
+ let idx = match(l:string, '[()]', idx + 1)
+ if idx < 0
+ let idx = len(l:string)
+ endif
+ if idx >= len(l:string) || l:string[idx] ==# l:open
+ let depth += 1
+ else
+ let depth -= 1
+ if depth == 0
+ return a:dir < 0 ? len(a:str) - idx : idx
+ endif
+ endif
+ endwhile
+
+ return -1
+endfunction
+
+" }}}1
+function! s:get_inline_trim(str) abort " {{{1
+ let l:str = vimtex#util#trim(a:str)
+ return substitute(l:str, '^(\(.*\))$', '\1', '')
+endfunction
+
+" }}}1
+
+function! vimtex#cmd#get_next() abort " {{{1
+ return s:get_cmd('next')
+endfunction
+
+" }}}1
+function! vimtex#cmd#get_prev() abort " {{{1
+ return s:get_cmd('prev')
+endfunction
+
+" }}}1
+function! vimtex#cmd#get_current() abort " {{{1
+ let l:save_pos = vimtex#pos#get_cursor()
+ let l:pos_val_cursor = vimtex#pos#val(l:save_pos)
+
+ let l:depth = 3
+ while l:depth > 0
+ let l:depth -= 1
+ let l:cmd = s:get_cmd('prev')
+ if empty(l:cmd) | break | endif
+
+ let l:pos_val = vimtex#pos#val(l:cmd.pos_end)
+ if l:pos_val >= l:pos_val_cursor
+ call vimtex#pos#set_cursor(l:save_pos)
+ return l:cmd
+ else
+ call vimtex#pos#set_cursor(vimtex#pos#prev(l:cmd.pos_start))
+ endif
+ endwhile
+
+ call vimtex#pos#set_cursor(l:save_pos)
+
+ return {}
+endfunction
+
+" }}}1
+function! vimtex#cmd#get_at(...) abort " {{{1
+ let l:pos_saved = vimtex#pos#get_cursor()
+ call call('vimtex#pos#set_cursor', a:000)
+ let l:cmd = vimtex#cmd#get_current()
+ call vimtex#pos#set_cursor(l:pos_saved)
+ return l:cmd
+endfunction
+
+" }}}1
+
+function! s:operator_setup(operator) abort " {{{1
+ let s:operator = a:operator
+ let &opfunc = s:snr() . 'operator_function'
+
+ " Ask for user input if necessary/relevant
+ if s:operator ==# 'change'
+ let l:current = vimtex#cmd#get_current()
+ if empty(l:current) | return | endif
+
+ let s:operator_cmd_name = substitute(vimtex#echo#input({
+ \ 'info' : ['Change command: ', ['VimtexWarning', l:current.name]],
+ \}), '^\\', '', '')
+ elseif s:operator ==# 'create'
+ let s:operator_cmd_name = substitute(vimtex#echo#input({
+ \ 'info' : ['Create command: ', ['VimtexWarning', '(empty to cancel)']],
+ \}), '^\\', '', '')
+ endif
+endfunction
+
+" }}}1
+function! s:operator_function(_) abort " {{{1
+ let l:name = get(s:, 'operator_cmd_name', '')
+
+ execute 'call vimtex#cmd#' . {
+ \ 'change': 'change(l:name)',
+ \ 'create': 'create(l:name, 0)',
+ \ 'delete': 'delete()',
+ \ 'toggle_star': 'toggle_star()',
+ \ 'toggle_frac': 'toggle_frac()',
+ \ }[s:operator]
+endfunction
+
+" }}}1
+function! s:snr() abort " {{{1
+ return matchstr(expand('<sfile>'), '<SNR>\d\+_')
+endfunction
+
+" }}}1
+
+function! s:get_cmd(direction) abort " {{{1
+ let [lnum, cnum, match] = s:get_cmd_name(a:direction ==# 'next')
+ if lnum == 0 | return {} | endif
+
+ let res = {
+ \ 'name' : match,
+ \ 'text' : '',
+ \ 'pos_start' : { 'lnum' : lnum, 'cnum' : cnum },
+ \ 'pos_end' : { 'lnum' : lnum, 'cnum' : cnum + strlen(match) - 1 },
+ \ 'args' : [],
+ \}
+
+ " Environments always start with environment name and allows option
+ " afterwords
+ if res.name ==# '\begin'
+ let arg = s:get_cmd_part('{', res.pos_end)
+ if empty(arg) | return res | endif
+
+ call add(res.args, arg)
+ let res.pos_end.lnum = arg.close.lnum
+ let res.pos_end.cnum = arg.close.cnum
+ endif
+
+ " Get overlay specification
+ let res.overlay = s:get_cmd_overlay(res.pos_end.lnum, res.pos_end.cnum)
+ if !empty(res.overlay)
+ let res.pos_end.lnum = res.overlay.close.lnum
+ let res.pos_end.cnum = res.overlay.close.cnum
+ endif
+
+ " Get options
+ let res.opt = s:get_cmd_part('[', res.pos_end)
+ if !empty(res.opt)
+ let res.pos_end.lnum = res.opt.close.lnum
+ let res.pos_end.cnum = res.opt.close.cnum
+ endif
+
+ " Get arguments
+ let arg = s:get_cmd_part('{', res.pos_end)
+ while !empty(arg)
+ call add(res.args, arg)
+ let res.pos_end.lnum = arg.close.lnum
+ let res.pos_end.cnum = arg.close.cnum
+ let arg = s:get_cmd_part('{', res.pos_end)
+ endwhile
+
+ " Include entire cmd text
+ let res.text = s:text_between(res.pos_start, res.pos_end, 1)
+
+ return res
+endfunction
+
+" }}}1
+function! s:get_cmd_name(next) abort " {{{1
+ let [l:lnum, l:cnum] = searchpos('\v\\\a+\*?', a:next ? 'nW' : 'cbnW')
+ let l:match = matchstr(getline(l:lnum), '^\v\\\a*\*?', l:cnum-1)
+ return [l:lnum, l:cnum, l:match]
+endfunction
+
+" }}}1
+function! s:get_cmd_part(part, start_pos) abort " {{{1
+ let l:save_pos = vimtex#pos#get_cursor()
+ call vimtex#pos#set_cursor(a:start_pos)
+ let l:open = vimtex#delim#get_next('delim_tex', 'open')
+ call vimtex#pos#set_cursor(l:save_pos)
+
+ "
+ " Ensure that the delimiter
+ " 1) exists,
+ " 2) is of the right type,
+ " 3) and is the next non-whitespace character.
+ "
+ if empty(l:open)
+ \ || l:open.match !=# a:part
+ \ || strlen(substitute(
+ \ s:text_between(a:start_pos, l:open), '\_s', '', 'g')) != 0
+ return {}
+ endif
+
+ let l:close = vimtex#delim#get_matching(l:open)
+ if empty(l:close)
+ return {}
+ endif
+
+ return {
+ \ 'open' : l:open,
+ \ 'close' : l:close,
+ \ 'text' : s:text_between(l:open, l:close),
+ \}
+endfunction
+
+" }}}1
+function! s:get_cmd_overlay(lnum, cnum) abort " {{{1
+ let l:match = matchstr(getline(a:lnum), '^\s*[^>]*>', a:cnum)
+
+ return empty(l:match)
+ \ ? {}
+ \ : {
+ \ 'open' : {'lnum' : a:lnum, 'cnum' : a:cnum + 1},
+ \ 'close' : {'lnum' : a:lnum, 'cnum' : a:cnum + strlen(l:match)},
+ \ 'text' : l:match
+ \ }
+endfunction
+
+" }}}1
+
+function! s:text_between(p1, p2, ...) abort " {{{1
+ let [l1, c1] = [a:p1.lnum, a:p1.cnum - (a:0 > 0)]
+ let [l2, c2] = [a:p2.lnum, a:p2.cnum - (a:0 <= 0)]
+
+ let lines = getline(l1, l2)
+ if !empty(lines)
+ let lines[0] = strpart(lines[0], c1)
+ let lines[-1] = strpart(lines[-1], 0,
+ \ l1 == l2 ? c2 - c1 : c2)
+ endif
+ return join(lines, "\n")
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/compiler.vim b/autoload/vimtex/compiler.vim
new file mode 100644
index 00000000..5ecf260b
--- /dev/null
+++ b/autoload/vimtex/compiler.vim
@@ -0,0 +1,334 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#compiler#init_buffer() abort " {{{1
+ if !g:vimtex_compiler_enabled | return | endif
+
+ " Define commands
+ command! -buffer VimtexCompile call vimtex#compiler#compile()
+ command! -buffer -bang VimtexCompileSS call vimtex#compiler#compile_ss()
+ command! -buffer -range VimtexCompileSelected <line1>,<line2>call vimtex#compiler#compile_selected('cmd')
+ command! -buffer VimtexCompileOutput call vimtex#compiler#output()
+ command! -buffer VimtexStop call vimtex#compiler#stop()
+ command! -buffer VimtexStopAll call vimtex#compiler#stop_all()
+ command! -buffer -bang VimtexClean call vimtex#compiler#clean(<q-bang> == "!")
+ command! -buffer -bang VimtexStatus call vimtex#compiler#status(<q-bang> == "!")
+
+ " Define mappings
+ nnoremap <buffer> <plug>(vimtex-compile) :call vimtex#compiler#compile()<cr>
+ nnoremap <buffer> <plug>(vimtex-compile-ss) :call vimtex#compiler#compile_ss()<cr>
+ nnoremap <buffer> <plug>(vimtex-compile-selected) :set opfunc=vimtex#compiler#compile_selected<cr>g@
+ xnoremap <buffer> <plug>(vimtex-compile-selected) :<c-u>call vimtex#compiler#compile_selected('visual')<cr>
+ nnoremap <buffer> <plug>(vimtex-compile-output) :call vimtex#compiler#output()<cr>
+ nnoremap <buffer> <plug>(vimtex-stop) :call vimtex#compiler#stop()<cr>
+ nnoremap <buffer> <plug>(vimtex-stop-all) :call vimtex#compiler#stop_all()<cr>
+ nnoremap <buffer> <plug>(vimtex-clean) :call vimtex#compiler#clean(0)<cr>
+ nnoremap <buffer> <plug>(vimtex-clean-full) :call vimtex#compiler#clean(1)<cr>
+ nnoremap <buffer> <plug>(vimtex-status) :call vimtex#compiler#status(0)<cr>
+ nnoremap <buffer> <plug>(vimtex-status-all) :call vimtex#compiler#status(1)<cr>
+endfunction
+
+" }}}1
+function! vimtex#compiler#init_state(state) abort " {{{1
+ if !g:vimtex_compiler_enabled | return | endif
+
+ try
+ let l:options = {
+ \ 'root': a:state.root,
+ \ 'target' : a:state.base,
+ \ 'target_path' : a:state.tex,
+ \ 'tex_program' : a:state.tex_program,
+ \}
+ let a:state.compiler
+ \ = vimtex#compiler#{g:vimtex_compiler_method}#init(l:options)
+ catch /vimtex: Requirements not met/
+ call vimtex#log#error('Compiler was not initialized!')
+ catch /E117/
+ call vimtex#log#error(
+ \ 'Invalid compiler: ' . g:vimtex_compiler_method,
+ \ 'Please see :h g:vimtex_compiler_method')
+ endtry
+endfunction
+
+" }}}1
+
+function! vimtex#compiler#callback(status) abort " {{{1
+ if exists('b:vimtex') && get(b:vimtex.compiler, 'silence_next_callback')
+ let b:vimtex.compiler.silence_next_callback = 0
+ return
+ endif
+
+ call vimtex#qf#open(0)
+ redraw
+
+ if exists('s:output')
+ call s:output.update()
+ endif
+
+ if a:status
+ call vimtex#log#info('Compilation completed')
+ else
+ call vimtex#log#warning('Compilation failed!')
+ endif
+
+ if a:status && exists('b:vimtex')
+ call b:vimtex.parse_packages()
+ call vimtex#syntax#load#packages()
+ endif
+
+ for l:hook in g:vimtex_compiler_callback_hooks
+ if exists('*' . l:hook)
+ execute 'call' l:hook . '(' . a:status . ')'
+ endif
+ endfor
+
+ return ''
+endfunction
+
+" }}}1
+
+function! vimtex#compiler#compile() abort " {{{1
+ if get(b:vimtex.compiler, 'continuous')
+ if b:vimtex.compiler.is_running()
+ call vimtex#compiler#stop()
+ else
+ call b:vimtex.compiler.start()
+ let b:vimtex.compiler.check_timer = s:check_if_running_start()
+ endif
+ else
+ call b:vimtex.compiler.start_single()
+ endif
+endfunction
+
+" }}}1
+function! vimtex#compiler#compile_ss() abort " {{{1
+ call b:vimtex.compiler.start_single()
+endfunction
+
+" }}}1
+function! vimtex#compiler#compile_selected(type) abort range " {{{1
+ let l:file = vimtex#parser#selection_to_texfile(a:type)
+ if empty(l:file) | return | endif
+
+ " Create and initialize temporary compiler
+ let l:options = {
+ \ 'root' : l:file.root,
+ \ 'target' : l:file.base,
+ \ 'target_path' : l:file.tex,
+ \ 'backend' : 'process',
+ \ 'tex_program' : b:vimtex.tex_program,
+ \ 'background' : 1,
+ \ 'continuous' : 0,
+ \ 'callback' : 0,
+ \}
+ let l:compiler = vimtex#compiler#{g:vimtex_compiler_method}#init(l:options)
+
+ call vimtex#log#toggle_verbose()
+ call l:compiler.start()
+
+ " Check if successful
+ if vimtex#qf#inquire(l:file.base)
+ call vimtex#log#toggle_verbose()
+ call vimtex#log#warning('Compiling selected lines ... failed!')
+ botright cwindow
+ return
+ else
+ call l:compiler.clean(0)
+ call b:vimtex.viewer.view(l:file.pdf)
+ call vimtex#log#toggle_verbose()
+ call vimtex#log#info('Compiling selected lines ... done')
+ endif
+endfunction
+
+" }}}1
+function! vimtex#compiler#output() abort " {{{1
+ let l:file = get(b:vimtex.compiler, 'output', '')
+ if empty(l:file)
+ call vimtex#log#warning('No output exists!')
+ return
+ endif
+
+ " If window already open, then go there
+ if exists('s:output')
+ if bufwinnr(l:file) == s:output.winnr
+ execute s:output.winnr . 'wincmd w'
+ return
+ else
+ call s:output.destroy()
+ endif
+ endif
+
+ " Create new output window
+ silent execute 'split' l:file
+
+ " Create the output object
+ let s:output = {}
+ let s:output.name = l:file
+ let s:output.bufnr = bufnr('%')
+ let s:output.winnr = bufwinnr('%')
+ function! s:output.update() dict abort
+ if bufwinnr(self.name) != self.winnr
+ return
+ endif
+
+ if mode() ==? 'v' || mode() ==# "\<c-v>"
+ return
+ endif
+
+ " Go to last line of file if it is not the current window
+ if bufwinnr('%') != self.winnr
+ let l:return = bufwinnr('%')
+ execute 'keepalt' self.winnr . 'wincmd w'
+ edit
+ normal! Gzb
+ execute 'keepalt' l:return . 'wincmd w'
+ redraw
+ endif
+ endfunction
+ function! s:output.destroy() dict abort
+ autocmd! vimtex_output_window
+ augroup! vimtex_output_window
+ unlet s:output
+ endfunction
+
+ " Better automatic update
+ augroup vimtex_output_window
+ autocmd!
+ autocmd BufDelete <buffer> call s:output.destroy()
+ autocmd BufEnter * call s:output.update()
+ autocmd FocusGained * call s:output.update()
+ autocmd CursorHold * call s:output.update()
+ autocmd CursorHoldI * call s:output.update()
+ autocmd CursorMoved * call s:output.update()
+ autocmd CursorMovedI * call s:output.update()
+ augroup END
+
+ " Set some mappings
+ nnoremap <silent><nowait><buffer> q :bwipeout<cr>
+ if has('nvim') || has('gui_running')
+ nnoremap <silent><nowait><buffer> <esc> :bwipeout<cr>
+ endif
+
+ " Set some buffer options
+ setlocal autoread
+ setlocal nomodifiable
+ setlocal bufhidden=wipe
+endfunction
+
+" }}}1
+function! vimtex#compiler#stop() abort " {{{1
+ call b:vimtex.compiler.stop()
+ silent! call timer_stop(b:vimtex.compiler.check_timer)
+endfunction
+
+" }}}1
+function! vimtex#compiler#stop_all() abort " {{{1
+ for l:state in vimtex#state#list_all()
+ if exists('l:state.compiler.is_running')
+ \ && l:state.compiler.is_running()
+ call l:state.compiler.stop()
+ endif
+ endfor
+endfunction
+
+" }}}1
+function! vimtex#compiler#clean(full) abort " {{{1
+ call b:vimtex.compiler.clean(a:full)
+
+ if empty(b:vimtex.compiler.build_dir) | return | endif
+ sleep 100m
+
+ " Remove auxilliary output directories if they are empty
+ let l:build_dir = (vimtex#paths#is_abs(b:vimtex.compiler.build_dir)
+ \ ? '' : b:vimtex.root . '/')
+ \ . b:vimtex.compiler.build_dir
+ let l:tree = glob(l:build_dir . '/**/*', 0, 1)
+ let l:files = filter(copy(l:tree), 'filereadable(v:val)')
+ if !empty(l:files) | return | endif
+
+ for l:dir in sort(l:tree) + [l:build_dir]
+ call delete(l:dir, 'd')
+ endfor
+endfunction
+
+" }}}1
+function! vimtex#compiler#status(detailed) abort " {{{1
+ if a:detailed
+ let l:running = []
+ for l:data in vimtex#state#list_all()
+ if l:data.compiler.is_running()
+ let l:name = l:data.tex
+ if len(l:name) >= winwidth('.') - 20
+ let l:name = '...' . l:name[-winwidth('.')+23:]
+ endif
+ call add(l:running, printf('%-6s %s',
+ \ string(l:data.compiler.get_pid()) . ':', l:name))
+ endif
+ endfor
+
+ if empty(l:running)
+ call vimtex#log#warning('Compiler is not running!')
+ else
+ call vimtex#log#info('Compiler is running', l:running)
+ endif
+ else
+ if b:vimtex.compiler.is_running()
+ call vimtex#log#info('Compiler is running')
+ else
+ call vimtex#log#warning('Compiler is not running!')
+ endif
+ endif
+endfunction
+
+" }}}1
+
+
+let s:check_timers = {}
+function! s:check_if_running_start() abort " {{{1
+ if !exists('*timer_start') | return -1 | endif
+
+ let l:timer = timer_start(50, function('s:check_if_running'), {'repeat': 20})
+
+ let s:check_timers[l:timer] = {
+ \ 'compiler' : b:vimtex.compiler,
+ \ 'vimtex_id' : b:vimtex_id,
+ \}
+
+ return l:timer
+endfunction
+
+" }}}1
+function! s:check_if_running(timer) abort " {{{1
+ if s:check_timers[a:timer].compiler.is_running() | return | endif
+
+ call timer_stop(a:timer)
+
+ if get(b:, 'vimtex_id', -1) == s:check_timers[a:timer].vimtex_id
+ call vimtex#compiler#output()
+ endif
+ call vimtex#log#error('Compiler did not start successfully!')
+
+ unlet s:check_timers[a:timer].compiler.check_timer
+ unlet s:check_timers[a:timer]
+endfunction
+
+" }}}1
+
+
+" {{{1 Initialize module
+
+if !g:vimtex_compiler_enabled | finish | endif
+
+augroup vimtex_compiler
+ autocmd!
+ autocmd VimLeave * call vimtex#compiler#stop_all()
+augroup END
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/compiler/arara.vim b/autoload/vimtex/compiler/arara.vim
new file mode 100644
index 00000000..8234b77d
--- /dev/null
+++ b/autoload/vimtex/compiler/arara.vim
@@ -0,0 +1,218 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#compiler#arara#init(options) abort " {{{1
+ let l:compiler = deepcopy(s:compiler)
+
+ call l:compiler.init(extend(a:options,
+ \ get(g:, 'vimtex_compiler_arara', {}), 'keep'))
+
+ return l:compiler
+endfunction
+
+" }}}1
+
+let s:compiler = {
+ \ 'name' : 'arara',
+ \ 'backend' : has('nvim') ? 'nvim'
+ \ : v:version >= 800 ? 'jobs' : 'process',
+ \ 'root' : '',
+ \ 'target' : '',
+ \ 'target_path' : '',
+ \ 'background' : 1,
+ \ 'output' : tempname(),
+ \ 'options' : ['--log'],
+ \}
+
+function! s:compiler.init(options) abort dict " {{{1
+ call extend(self, a:options)
+
+ if !executable('arara')
+ call vimtex#log#warning('arara is not executable!')
+ throw 'vimtex: Requirements not met'
+ endif
+
+ call extend(self, deepcopy(s:compiler_{self.backend}))
+
+ " Processes run with the new jobs api will not run in the foreground
+ if self.backend !=# 'process'
+ let self.background = 1
+ endif
+endfunction
+
+" }}}1
+
+function! s:compiler.build_cmd() abort dict " {{{1
+ let l:cmd = 'arara'
+
+ for l:opt in self.options
+ let l:cmd .= ' ' . l:opt
+ endfor
+
+ return l:cmd . ' ' . vimtex#util#shellescape(self.target)
+endfunction
+
+" }}}1
+function! s:compiler.cleanup() abort dict " {{{1
+ " Pass
+endfunction
+
+" }}}1
+function! s:compiler.pprint_items() abort dict " {{{1
+ let l:configuration = []
+
+ if self.backend ==# 'process'
+ call add(l:configuration, ['background', self.background])
+ endif
+
+ call add(l:configuration, ['arara options', self.options])
+
+ let l:list = []
+ call add(l:list, ['backend', self.backend])
+ if self.background
+ call add(l:list, ['output', self.output])
+ endif
+
+ if self.target_path !=# b:vimtex.tex
+ call add(l:list, ['root', self.root])
+ call add(l:list, ['target', self.target_path])
+ endif
+
+ call add(l:list, ['configuration', l:configuration])
+
+ if has_key(self, 'process')
+ call add(l:list, ['process', self.process])
+ endif
+
+ if has_key(self, 'job')
+ call add(l:list, ['cmd', self.cmd])
+ endif
+
+ return l:list
+endfunction
+
+" }}}1
+
+function! s:compiler.clean(...) abort dict " {{{1
+ call vimtex#log#warning('Clean not implemented for arara')
+endfunction
+
+" }}}1
+function! s:compiler.start(...) abort dict " {{{1
+ call self.exec()
+
+ if self.background
+ call vimtex#log#info('Compiler started in background')
+ else
+ call vimtex#compiler#callback(!vimtex#qf#inquire(self.target))
+ endif
+endfunction
+
+" }}}1
+function! s:compiler.start_single() abort dict " {{{1
+ call self.start()
+endfunction
+
+" }}}1
+function! s:compiler.stop() abort dict " {{{1
+ " Pass
+endfunction
+
+" }}}1
+function! s:compiler.is_running() abort dict " {{{1
+ return 0
+endfunction
+
+" }}}1
+function! s:compiler.kill() abort dict " {{{1
+ " Pass
+endfunction
+
+" }}}1
+function! s:compiler.get_pid() abort dict " {{{1
+ return 0
+endfunction
+
+" }}}1
+
+let s:compiler_process = {}
+function! s:compiler_process.exec() abort dict " {{{1
+ let self.process = vimtex#process#new()
+ let self.process.name = 'arara'
+ let self.process.background = self.background
+ let self.process.workdir = self.root
+ let self.process.output = self.output
+ let self.process.cmd = self.build_cmd()
+ call self.process.run()
+endfunction
+
+" }}}1
+
+let s:compiler_jobs = {}
+function! s:compiler_jobs.exec() abort dict " {{{1
+ let self.cmd = self.build_cmd()
+ let l:cmd = has('win32')
+ \ ? 'cmd /s /c "' . self.cmd . '"'
+ \ : ['sh', '-c', self.cmd]
+ let l:options = {
+ \ 'out_io' : 'file',
+ \ 'err_io' : 'file',
+ \ 'out_name' : self.output,
+ \ 'err_name' : self.output,
+ \}
+
+ let s:cb_target = self.target_path !=# b:vimtex.tex ? self.target_path : ''
+ let l:options.exit_cb = function('s:callback')
+
+ call vimtex#paths#pushd(self.root)
+ let self.job = job_start(l:cmd, l:options)
+ call vimtex#paths#popd()
+endfunction
+
+" }}}1
+function! s:callback(ch, msg) abort " {{{1
+ call vimtex#compiler#callback(!vimtex#qf#inquire(s:cb_target))
+endfunction
+
+" }}}1
+
+let s:compiler_nvim = {}
+function! s:compiler_nvim.exec() abort dict " {{{1
+ let self.cmd = self.build_cmd()
+ let l:cmd = has('win32')
+ \ ? 'cmd /s /c "' . self.cmd . '"'
+ \ : ['sh', '-c', self.cmd]
+
+ let l:shell = {
+ \ 'on_stdout' : function('s:callback_nvim_output'),
+ \ 'on_stderr' : function('s:callback_nvim_output'),
+ \ 'on_exit' : function('s:callback_nvim_exit'),
+ \ 'cwd' : self.root,
+ \ 'target' : self.target_path,
+ \ 'output' : self.output,
+ \}
+
+ let self.job = jobstart(l:cmd, l:shell)
+endfunction
+
+" }}}1
+function! s:callback_nvim_output(id, data, event) abort dict " {{{1
+ if !empty(a:data)
+ call writefile(filter(a:data, '!empty(v:val)'), self.output, 'a')
+ endif
+endfunction
+
+" }}}1
+function! s:callback_nvim_exit(id, data, event) abort dict " {{{1
+ let l:target = self.target !=# b:vimtex.tex ? self.target : ''
+ call vimtex#compiler#callback(!vimtex#qf#inquire(l:target))
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/compiler/latexmk.vim b/autoload/vimtex/compiler/latexmk.vim
new file mode 100644
index 00000000..d7a36708
--- /dev/null
+++ b/autoload/vimtex/compiler/latexmk.vim
@@ -0,0 +1,700 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#compiler#latexmk#init(options) abort " {{{1
+ let l:compiler = deepcopy(s:compiler)
+
+ call l:compiler.init(extend(a:options,
+ \ get(g:, 'vimtex_compiler_latexmk', {}), 'keep'))
+
+ return l:compiler
+endfunction
+
+" }}}1
+function! vimtex#compiler#latexmk#wrap_option(name, value) abort " {{{1
+ return has('win32')
+ \ ? ' -e "$' . a:name . ' = ''' . a:value . '''"'
+ \ : ' -e ''$' . a:name . ' = "' . a:value . '"'''
+endfunction
+
+"}}}1
+
+function! vimtex#compiler#latexmk#get_rc_opt(root, opt, type, default) abort " {{{1
+ "
+ " Parse option from .latexmkrc.
+ "
+ " Arguments:
+ " root Root of LaTeX project
+ " opt Name of options
+ " type 0 if string, 1 if integer, 2 if list
+ " default Value to return if option not found in latexmkrc file
+ "
+ " Output:
+ " [value, location]
+ "
+ " value Option value (integer or string)
+ " location An integer that indicates where option was found
+ " -1: not found (default value returned)
+ " 0: global latexmkrc file
+ " 1: local latexmkrc file
+ "
+
+ if a:type == 0
+ let l:pattern = '^\s*\$' . a:opt . '\s*=\s*[''"]\(.\+\)[''"]'
+ elseif a:type == 1
+ let l:pattern = '^\s*\$' . a:opt . '\s*=\s*\(\d\+\)'
+ elseif a:type == 2
+ let l:pattern = '^\s*@' . a:opt . '\s*=\s*(\(.*\))'
+ else
+ throw 'vimtex: argument error'
+ endif
+
+ " Candidate files
+ " - each element is a pair [path_to_file, is_local_rc_file].
+ let l:files = [
+ \ [a:root . '/latexmkrc', 1],
+ \ [a:root . '/.latexmkrc', 1],
+ \ [fnamemodify('~/.latexmkrc', ':p'), 0],
+ \]
+ if !empty($XDG_CONFIG_HOME)
+ call add(l:files, [$XDG_CONFIG_HOME . '/latexmk/latexmkrc', 0])
+ endif
+
+ let l:result = [a:default, -1]
+
+ for [l:file, l:is_local] in l:files
+ if filereadable(l:file)
+ let l:match = matchlist(readfile(l:file), l:pattern)
+ if len(l:match) > 1
+ let l:result = [l:match[1], l:is_local]
+ break
+ end
+ endif
+ endfor
+
+ " Parse the list
+ if a:type == 2 && l:result[1] > -1
+ let l:array = split(l:result[0], ',')
+ let l:result[0] = []
+ for l:x in l:array
+ let l:x = substitute(l:x, "^'", '', '')
+ let l:x = substitute(l:x, "'$", '', '')
+ let l:result[0] += [l:x]
+ endfor
+ endif
+
+ return l:result
+endfunction
+
+" }}}1
+
+let s:compiler = {
+ \ 'name' : 'latexmk',
+ \ 'executable' : 'latexmk',
+ \ 'backend' : has('nvim') ? 'nvim'
+ \ : v:version >= 800 ? 'jobs' : 'process',
+ \ 'root' : '',
+ \ 'target' : '',
+ \ 'target_path' : '',
+ \ 'background' : 1,
+ \ 'build_dir' : '',
+ \ 'callback' : 1,
+ \ 'continuous' : 1,
+ \ 'output' : tempname(),
+ \ 'options' : [
+ \ '-verbose',
+ \ '-file-line-error',
+ \ '-synctex=1',
+ \ '-interaction=nonstopmode',
+ \ ],
+ \ 'hooks' : [],
+ \ 'shell' : fnamemodify(&shell, ':t'),
+ \}
+
+function! s:compiler.init(options) abort dict " {{{1
+ call extend(self, a:options)
+
+ call self.init_check_requirements()
+ call self.init_build_dir_option()
+ call self.init_pdf_mode_option()
+
+ call extend(self, deepcopy(s:compiler_{self.backend}))
+
+ " Continuous processes can't run in foreground, neither can processes run
+ " with the new jobs api
+ if self.continuous || self.backend !=# 'process'
+ let self.background = 1
+ endif
+
+ if self.backend !=# 'process'
+ let self.shell = 'sh'
+ endif
+endfunction
+
+" }}}1
+function! s:compiler.init_build_dir_option() abort dict " {{{1
+ "
+ " Check if .latexmkrc sets the build_dir - if so this should be respected
+ "
+ let l:out_dir =
+ \ vimtex#compiler#latexmk#get_rc_opt(self.root, 'out_dir', 0, '')[0]
+
+ if !empty(l:out_dir)
+ if !empty(self.build_dir) && (self.build_dir !=# l:out_dir)
+ call vimtex#log#warning(
+ \ 'Setting out_dir from latexmkrc overrides build_dir!',
+ \ 'Changed build_dir from: ' . self.build_dir,
+ \ 'Changed build_dir to: ' . l:out_dir)
+ endif
+ let self.build_dir = l:out_dir
+ endif
+endfunction
+
+" }}}1
+function! s:compiler.init_pdf_mode_option() abort dict " {{{1
+ " If the TeX program directive was not set, and if the pdf_mode is set in
+ " a .latexmkrc file, then deduce the compiler engine from the value of
+ " pdf_mode.
+
+ " Parse the pdf_mode option. If not found, it is set to -1.
+ let [l:pdf_mode, l:is_local] =
+ \ vimtex#compiler#latexmk#get_rc_opt(self.root, 'pdf_mode', 1, -1)
+
+ " If pdf_mode has a supported value (1: pdflatex, 4: lualatex, 5: xelatex),
+ " override the value of self.tex_program.
+ if l:pdf_mode == 1
+ let l:tex_program = 'pdflatex'
+ elseif l:pdf_mode == 3
+ let l:tex_program = 'pdfdvi'
+ elseif l:pdf_mode == 4
+ let l:tex_program = 'lualatex'
+ elseif l:pdf_mode == 5
+ let l:tex_program = 'xelatex'
+ else
+ return
+ endif
+
+ if self.tex_program ==# '_'
+ " The TeX program directive was not specified
+ let self.tex_program = l:tex_program
+ elseif l:is_local && self.tex_program !=# l:tex_program
+ call vimtex#log#warning(
+ \ 'Value of pdf_mode from latexmkrc is inconsistent with ' .
+ \ 'TeX program directive!',
+ \ 'TeX program: ' . self.tex_program,
+ \ 'pdf_mode: ' . l:tex_program,
+ \ 'The value of pdf_mode will be ignored.')
+ endif
+endfunction
+
+" }}}1
+function! s:compiler.init_check_requirements() abort dict " {{{1
+ " Check option validity
+ if self.callback
+ if !(has('clientserver') || has('nvim') || has('job'))
+ let self.callback = 0
+ call vimtex#log#warning(
+ \ 'Can''t use callbacks without +job, +nvim, or +clientserver',
+ \ 'Callback option has been disabled.')
+ endif
+ endif
+
+ " Check for required executables
+ let l:required = [self.executable]
+ if self.continuous && !(has('win32') || has('win32unix'))
+ let l:required += ['pgrep']
+ endif
+ let l:missing = filter(l:required, '!executable(v:val)')
+
+ " Disable latexmk if required programs are missing
+ if len(l:missing) > 0
+ for l:cmd in l:missing
+ call vimtex#log#warning(l:cmd . ' is not executable')
+ endfor
+ throw 'vimtex: Requirements not met'
+ endif
+endfunction
+
+" }}}1
+
+function! s:compiler.build_cmd() abort dict " {{{1
+ if has('win32')
+ let l:cmd = 'set max_print_line=2000 & ' . self.executable
+ else
+ if self.shell ==# 'fish'
+ let l:cmd = 'set max_print_line 2000; and ' . self.executable
+ else
+ let l:cmd = 'max_print_line=2000 ' . self.executable
+ endif
+ endif
+
+ for l:opt in self.options
+ let l:cmd .= ' ' . l:opt
+ endfor
+
+ let l:cmd .= ' ' . self.get_engine()
+
+ if !empty(self.build_dir)
+ let l:cmd .= ' -outdir=' . fnameescape(self.build_dir)
+ endif
+
+ if self.continuous
+ let l:cmd .= ' -pvc'
+
+ " Set viewer options
+ if !get(g:, 'vimtex_view_automatic', 1)
+ \ || get(get(b:vimtex, 'viewer', {}), 'xwin_id') > 0
+ \ || get(s:, 'silence_next_callback', 0)
+ let l:cmd .= ' -view=none'
+ elseif g:vimtex_view_enabled
+ \ && has_key(b:vimtex.viewer, 'latexmk_append_argument')
+ let l:cmd .= b:vimtex.viewer.latexmk_append_argument()
+ endif
+
+ if self.callback
+ if has('job') || has('nvim')
+ for [l:opt, l:val] in items({
+ \ 'success_cmd' : 'vimtex_compiler_callback_success',
+ \ 'failure_cmd' : 'vimtex_compiler_callback_failure',
+ \})
+ let l:func = 'echo ' . l:val
+ let l:cmd .= vimtex#compiler#latexmk#wrap_option(l:opt, l:func)
+ endfor
+ elseif empty(v:servername)
+ call vimtex#log#warning('Can''t use callbacks with empty v:servername')
+ else
+ " Some notes:
+ " - We excape the v:servername because this seems necessary on Windows
+ " for neovim, see e.g. Github Issue #877
+ for [l:opt, l:val] in items({'success_cmd' : 1, 'failure_cmd' : 0})
+ let l:callback = has('win32')
+ \ ? '"vimtex#compiler#callback(' . l:val . ')"'
+ \ : '\"vimtex\#compiler\#callback(' . l:val . ')\"'
+ let l:func = vimtex#util#shellescape('""')
+ \ . g:vimtex_compiler_progname
+ \ . vimtex#util#shellescape('""')
+ \ . ' --servername ' . vimtex#util#shellescape(v:servername)
+ \ . ' --remote-expr ' . l:callback
+ let l:cmd .= vimtex#compiler#latexmk#wrap_option(l:opt, l:func)
+ endfor
+ endif
+ endif
+ endif
+
+ return l:cmd . ' ' . vimtex#util#shellescape(self.target)
+endfunction
+
+" }}}1
+function! s:compiler.get_engine() abort dict " {{{1
+ return get(extend(g:vimtex_compiler_latexmk_engines,
+ \ {
+ \ 'pdfdvi' : '-pdfdvi',
+ \ 'pdflatex' : '-pdf',
+ \ 'luatex' : '-lualatex',
+ \ 'lualatex' : '-lualatex',
+ \ 'xelatex' : '-xelatex',
+ \ 'context (pdftex)' : '-pdf -pdflatex=texexec',
+ \ 'context (luatex)' : '-pdf -pdflatex=context',
+ \ 'context (xetex)' : '-pdf -pdflatex=''texexec --xtx''',
+ \ }, 'keep'), self.tex_program, '-pdf')
+endfunction
+
+" }}}1
+function! s:compiler.cleanup() abort dict " {{{1
+ if self.is_running()
+ call self.kill()
+ endif
+endfunction
+
+" }}}1
+function! s:compiler.pprint_items() abort dict " {{{1
+ let l:configuration = [
+ \ ['continuous', self.continuous],
+ \ ['callback', self.callback],
+ \]
+
+ if self.backend ==# 'process' && !self.continuous
+ call add(l:configuration, ['background', self.background])
+ endif
+
+ if !empty(self.build_dir)
+ call add(l:configuration, ['build_dir', self.build_dir])
+ endif
+ call add(l:configuration, ['latexmk options', self.options])
+ call add(l:configuration, ['latexmk engine', self.get_engine()])
+
+ let l:list = []
+ call add(l:list, ['backend', self.backend])
+ if self.executable !=# s:compiler.executable
+ call add(l:list, ['latexmk executable', self.executable])
+ endif
+ if self.background
+ call add(l:list, ['output', self.output])
+ endif
+
+ if self.target_path !=# b:vimtex.tex
+ call add(l:list, ['root', self.root])
+ call add(l:list, ['target', self.target_path])
+ endif
+
+ call add(l:list, ['configuration', l:configuration])
+
+ if has_key(self, 'process')
+ call add(l:list, ['process', self.process])
+ endif
+
+ if has_key(self, 'job')
+ if self.continuous
+ if self.backend ==# 'jobs'
+ call add(l:list, ['job', self.job])
+ else
+ call add(l:list, ['pid', self.get_pid()])
+ endif
+ endif
+ call add(l:list, ['cmd', self.cmd])
+ endif
+
+ return l:list
+endfunction
+
+" }}}1
+
+function! s:compiler.clean(full) abort dict " {{{1
+ let l:restart = self.is_running()
+ if l:restart
+ call self.stop()
+ endif
+
+ " Define and run the latexmk clean cmd
+ let l:cmd = (has('win32')
+ \ ? 'cd /D "' . self.root . '" & '
+ \ : 'cd ' . vimtex#util#shellescape(self.root) . '; ')
+ \ . self.executable . ' ' . (a:full ? '-C ' : '-c ')
+ if !empty(self.build_dir)
+ let l:cmd .= printf(' -outdir=%s ', fnameescape(self.build_dir))
+ endif
+ let l:cmd .= vimtex#util#shellescape(self.target)
+ call vimtex#process#run(l:cmd)
+
+ call vimtex#log#info('Compiler clean finished' . (a:full ? ' (full)' : ''))
+
+ if l:restart
+ let self.silent_next_callback = 1
+ silent call self.start()
+ endif
+endfunction
+
+" }}}1
+function! s:compiler.start(...) abort dict " {{{1
+ if self.is_running()
+ call vimtex#log#warning(
+ \ 'Compiler is already running for `' . self.target . "'")
+ return
+ endif
+
+ "
+ " Create build dir if it does not exist
+ "
+ if !empty(self.build_dir)
+ let l:dirs = split(glob(self.root . '/**/*.tex'), '\n')
+ call map(l:dirs, 'fnamemodify(v:val, '':h'')')
+ call map(l:dirs, 'strpart(v:val, strlen(self.root) + 1)')
+ call vimtex#util#uniq(sort(filter(l:dirs, "v:val !=# ''")))
+ call map(l:dirs,
+ \ (vimtex#paths#is_abs(self.build_dir) ? '' : "self.root . '/' . ")
+ \ . "self.build_dir . '/' . v:val")
+ call filter(l:dirs, '!isdirectory(v:val)')
+
+ " Create the non-existing directories
+ for l:dir in l:dirs
+ call mkdir(l:dir, 'p')
+ endfor
+ endif
+
+ call self.exec()
+
+ if self.continuous
+ call vimtex#log#info('Compiler started in continuous mode'
+ \ . (a:0 > 0 ? ' (single shot)' : ''))
+ if exists('#User#VimtexEventCompileStarted')
+ doautocmd <nomodeline> User VimtexEventCompileStarted
+ endif
+ else
+ if self.background
+ call vimtex#log#info('Compiler started in background!')
+ else
+ call vimtex#compiler#callback(!vimtex#qf#inquire(self.target))
+ endif
+ endif
+endfunction
+
+" }}}1
+function! s:compiler.stop() abort dict " {{{1
+ if self.is_running()
+ call self.kill()
+ call vimtex#log#info('Compiler stopped (' . self.target . ')')
+ if exists('#User#VimtexEventCompileStopped')
+ doautocmd <nomodeline> User VimtexEventCompileStopped
+ endif
+ else
+ call vimtex#log#warning(
+ \ 'There is no process to stop (' . self.target . ')')
+ endif
+endfunction
+
+" }}}1
+
+let s:compiler_process = {}
+function! s:compiler_process.exec() abort dict " {{{1
+ let l:process = vimtex#process#new()
+ let l:process.name = 'latexmk'
+ let l:process.continuous = self.continuous
+ let l:process.background = self.background
+ let l:process.workdir = self.root
+ let l:process.output = self.output
+ let l:process.cmd = self.build_cmd()
+
+ if l:process.continuous
+ if (has('win32') || has('win32unix'))
+ " Not implemented
+ else
+ for l:pid in split(system(
+ \ 'pgrep -f "^[^ ]*perl.*latexmk.*' . self.target . '"'), "\n")
+ let l:path = resolve('/proc/' . l:pid . '/cwd') . '/' . self.target
+ if l:path ==# self.target_path
+ let l:process.pid = str2nr(l:pid)
+ break
+ endif
+ endfor
+ endif
+ endif
+
+ function! l:process.set_pid() abort dict " {{{2
+ if (has('win32') || has('win32unix'))
+ let pidcmd = 'tasklist /fi "imagename eq latexmk.exe"'
+ let pidinfo = vimtex#process#capture(pidcmd)[-1]
+ let self.pid = str2nr(split(pidinfo,'\s\+')[1])
+ else
+ let self.pid = str2nr(system('pgrep -nf "^[^ ]*perl.*latexmk"')[:-2])
+ endif
+
+ return self.pid
+ endfunction
+
+ " }}}2
+
+ let self.process = l:process
+ call self.process.run()
+endfunction
+
+" }}}1
+function! s:compiler_process.start_single() abort dict " {{{1
+ let l:continuous = self.continuous
+ let self.continuous = self.background && self.callback && !empty(v:servername)
+
+ if self.continuous
+ let g:vimtex_compiler_callback_hooks += ['VimtexSSCallback']
+ function! VimtexSSCallback(status) abort
+ silent call vimtex#compiler#stop()
+ call remove(g:vimtex_compiler_callback_hooks, 'VimtexSSCallback')
+ endfunction
+ endif
+
+ call self.start(1)
+ let self.continuous = l:continuous
+endfunction
+
+" }}}1
+function! s:compiler_process.is_running() abort dict " {{{1
+ return exists('self.process.pid') && self.process.pid > 0
+endfunction
+
+" }}}1
+function! s:compiler_process.kill() abort dict " {{{1
+ call self.process.stop()
+endfunction
+
+" }}}1
+function! s:compiler_process.get_pid() abort dict " {{{1
+ return has_key(self, 'process') ? self.process.pid : 0
+endfunction
+
+" }}}1
+
+let s:compiler_jobs = {}
+function! s:compiler_jobs.exec() abort dict " {{{1
+ let self.cmd = self.build_cmd()
+ let l:cmd = has('win32')
+ \ ? 'cmd /s /c "' . self.cmd . '"'
+ \ : ['sh', '-c', self.cmd]
+
+ let l:options = {
+ \ 'out_io' : 'file',
+ \ 'err_io' : 'file',
+ \ 'out_name' : self.output,
+ \ 'err_name' : self.output,
+ \}
+ if self.continuous
+ let l:options.out_io = 'pipe'
+ let l:options.err_io = 'pipe'
+ let l:options.out_cb = function('s:callback_continuous_output')
+ let l:options.err_cb = function('s:callback_continuous_output')
+ call writefile([], self.output, 'a')
+ else
+ let s:cb_target = self.target_path !=# b:vimtex.tex
+ \ ? self.target_path : ''
+ let l:options.exit_cb = function('s:callback')
+ endif
+
+ call vimtex#paths#pushd(self.root)
+ let self.job = job_start(l:cmd, l:options)
+ call vimtex#paths#popd()
+endfunction
+
+" }}}1
+function! s:compiler_jobs.start_single() abort dict " {{{1
+ let l:continuous = self.continuous
+ let self.continuous = 0
+ call self.start()
+ let self.continuous = l:continuous
+endfunction
+
+" }}}1
+function! s:compiler_jobs.kill() abort dict " {{{1
+ call job_stop(self.job)
+endfunction
+
+" }}}1
+function! s:compiler_jobs.is_running() abort dict " {{{1
+ return has_key(self, 'job') && job_status(self.job) ==# 'run'
+endfunction
+
+" }}}1
+function! s:compiler_jobs.get_pid() abort dict " {{{1
+ return has_key(self, 'job')
+ \ ? get(job_info(self.job), 'process') : 0
+endfunction
+
+" }}}1
+function! s:callback(ch, msg) abort " {{{1
+ call vimtex#compiler#callback(!vimtex#qf#inquire(s:cb_target))
+endfunction
+
+" }}}1
+function! s:callback_continuous_output(channel, msg) abort " {{{1
+ if exists('b:vimtex') && filewritable(b:vimtex.compiler.output)
+ call writefile([a:msg], b:vimtex.compiler.output, 'a')
+ endif
+
+ if a:msg ==# 'vimtex_compiler_callback_success'
+ call vimtex#compiler#callback(1)
+ elseif a:msg ==# 'vimtex_compiler_callback_failure'
+ call vimtex#compiler#callback(0)
+ endif
+
+ try
+ for l:Hook in get(get(get(b:, 'vimtex', {}), 'compiler', {}), 'hooks', [])
+ call l:Hook(a:msg)
+ endfor
+ catch /E716/
+ endtry
+endfunction
+
+" }}}1
+
+let s:compiler_nvim = {}
+function! s:compiler_nvim.exec() abort dict " {{{1
+ let self.cmd = self.build_cmd()
+ let l:cmd = has('win32')
+ \ ? 'cmd /s /c "' . self.cmd . '"'
+ \ : ['sh', '-c', self.cmd]
+
+ let l:shell = {
+ \ 'on_stdout' : function('s:callback_nvim_output'),
+ \ 'on_stderr' : function('s:callback_nvim_output'),
+ \ 'cwd' : self.root,
+ \ 'target' : self.target_path,
+ \ 'output' : self.output,
+ \}
+
+ if !self.continuous
+ let l:shell.on_exit = function('s:callback_nvim_exit')
+ endif
+
+ " Initialize output file
+ try
+ call writefile([], self.output)
+ endtry
+
+ let self.job = jobstart(l:cmd, l:shell)
+endfunction
+
+" }}}1
+function! s:compiler_nvim.start_single() abort dict " {{{1
+ let l:continuous = self.continuous
+ let self.continuous = 0
+ call self.start()
+ let self.continuous = l:continuous
+endfunction
+
+" }}}1
+function! s:compiler_nvim.kill() abort dict " {{{1
+ call jobstop(self.job)
+endfunction
+
+" }}}1
+function! s:compiler_nvim.is_running() abort dict " {{{1
+ try
+ let pid = jobpid(self.job)
+ return 1
+ catch
+ return 0
+ endtry
+endfunction
+
+" }}}1
+function! s:compiler_nvim.get_pid() abort dict " {{{1
+ try
+ return jobpid(self.job)
+ catch
+ return 0
+ endtry
+endfunction
+
+" }}}1
+function! s:callback_nvim_output(id, data, event) abort dict " {{{1
+ " Filter out unwanted newlines
+ let l:data = split(substitute(join(a:data, 'QQ'), '^QQ\|QQ$', '', ''), 'QQ')
+
+ if !empty(l:data) && filewritable(self.output)
+ call writefile(l:data, self.output, 'a')
+ endif
+
+ if match(a:data, 'vimtex_compiler_callback_success') != -1
+ call vimtex#compiler#callback(!vimtex#qf#inquire(self.target))
+ elseif match(a:data, 'vimtex_compiler_callback_failure') != -1
+ call vimtex#compiler#callback(0)
+ endif
+
+ try
+ for l:Hook in get(get(get(b:, 'vimtex', {}), 'compiler', {}), 'hooks', [])
+ call l:Hook(join(a:data, "\n"))
+ endfor
+ catch /E716/
+ endtry
+endfunction
+
+" }}}1
+function! s:callback_nvim_exit(id, data, event) abort dict " {{{1
+ let l:target = self.target !=# b:vimtex.tex ? self.target : ''
+ call vimtex#compiler#callback(!vimtex#qf#inquire(l:target))
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/compiler/latexrun.vim b/autoload/vimtex/compiler/latexrun.vim
new file mode 100644
index 00000000..01ebc02f
--- /dev/null
+++ b/autoload/vimtex/compiler/latexrun.vim
@@ -0,0 +1,250 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#compiler#latexrun#init(options) abort " {{{1
+ let l:compiler = deepcopy(s:compiler)
+
+ call l:compiler.init(extend(a:options,
+ \ get(g:, 'vimtex_compiler_latexrun', {}), 'keep'))
+
+ return l:compiler
+endfunction
+
+" }}}1
+
+let s:compiler = {
+ \ 'name' : 'latexrun',
+ \ 'backend' : has('nvim') ? 'nvim'
+ \ : v:version >= 800 ? 'jobs' : 'process',
+ \ 'root' : '',
+ \ 'target' : '',
+ \ 'target_path' : '',
+ \ 'background' : 1,
+ \ 'build_dir' : '',
+ \ 'output' : tempname(),
+ \ 'options' : [
+ \ '--verbose-cmds',
+ \ '--latex-args="-synctex=1"',
+ \ ],
+ \}
+
+function! s:compiler.init(options) abort dict " {{{1
+ call extend(self, a:options)
+
+ if !executable('latexrun')
+ call vimtex#log#warning('latexrun is not executable!')
+ throw 'vimtex: Requirements not met'
+ endif
+
+ call extend(self, deepcopy(s:compiler_{self.backend}))
+
+ " Processes run with the new jobs api will not run in the foreground
+ if self.backend !=# 'process'
+ let self.background = 1
+ endif
+endfunction
+
+" }}}1
+
+function! s:compiler.build_cmd() abort dict " {{{1
+ let l:cmd = 'latexrun'
+
+ for l:opt in self.options
+ let l:cmd .= ' ' . l:opt
+ endfor
+
+ let l:cmd .= ' --latex-cmd ' . self.get_engine()
+
+ let l:cmd .= ' -O '
+ \ . (empty(self.build_dir) ? '.' : fnameescape(self.build_dir))
+
+ return l:cmd . ' ' . vimtex#util#shellescape(self.target)
+endfunction
+
+" }}}1
+function! s:compiler.get_engine() abort dict " {{{1
+ return get(extend(g:vimtex_compiler_latexrun_engines,
+ \ {
+ \ '_' : 'pdflatex',
+ \ 'pdflatex' : 'pdflatex',
+ \ 'lualatex' : 'lualatex',
+ \ 'xelatex' : 'xelatex',
+ \ }, 'keep'), self.tex_program, '_')
+endfunction
+
+" }}}1
+function! s:compiler.cleanup() abort dict " {{{1
+ " Pass
+endfunction
+
+" }}}1
+function! s:compiler.pprint_items() abort dict " {{{1
+ let l:configuration = []
+
+ if self.backend ==# 'process'
+ call add(l:configuration, ['background', self.background])
+ endif
+
+ if !empty(self.build_dir)
+ call add(l:configuration, ['build_dir', self.build_dir])
+ endif
+ call add(l:configuration, ['latexrun options', self.options])
+ call add(l:configuration, ['latexrun engine', self.get_engine()])
+
+ let l:list = []
+ call add(l:list, ['backend', self.backend])
+ if self.background
+ call add(l:list, ['output', self.output])
+ endif
+
+ if self.target_path !=# b:vimtex.tex
+ call add(l:list, ['root', self.root])
+ call add(l:list, ['target', self.target_path])
+ endif
+
+ call add(l:list, ['configuration', l:configuration])
+
+ if has_key(self, 'process')
+ call add(l:list, ['process', self.process])
+ endif
+
+ if has_key(self, 'job')
+ call add(l:list, ['cmd', self.cmd])
+ endif
+
+ return l:list
+endfunction
+
+" }}}1
+
+function! s:compiler.clean(...) abort dict " {{{1
+ let l:cmd = (has('win32')
+ \ ? 'cd /D "' . self.root . '" & '
+ \ : 'cd ' . vimtex#util#shellescape(self.root) . '; ')
+ \ . 'latexrun --clean-all'
+ \ . ' -O '
+ \ . (empty(self.build_dir) ? '.' : fnameescape(self.build_dir))
+ call vimtex#process#run(l:cmd)
+
+ call vimtex#log#info('Compiler clean finished')
+endfunction
+
+" }}}1
+function! s:compiler.start(...) abort dict " {{{1
+ call self.exec()
+
+ if self.background
+ call vimtex#log#info('Compiler started in background')
+ else
+ call vimtex#compiler#callback(!vimtex#qf#inquire(self.target))
+ endif
+endfunction
+
+" }}}1
+function! s:compiler.start_single() abort dict " {{{1
+ call self.start()
+endfunction
+
+" }}}1
+function! s:compiler.stop() abort dict " {{{1
+ " Pass
+endfunction
+
+" }}}1
+function! s:compiler.is_running() abort dict " {{{1
+ return 0
+endfunction
+
+" }}}1
+function! s:compiler.kill() abort dict " {{{1
+ " Pass
+endfunction
+
+" }}}1
+function! s:compiler.get_pid() abort dict " {{{1
+ return 0
+endfunction
+
+" }}}1
+
+let s:compiler_process = {}
+function! s:compiler_process.exec() abort dict " {{{1
+ let self.process = vimtex#process#new()
+ let self.process.name = 'latexrun'
+ let self.process.background = self.background
+ let self.process.workdir = self.root
+ let self.process.output = self.output
+ let self.process.cmd = self.build_cmd()
+ call self.process.run()
+endfunction
+
+" }}}1
+
+let s:compiler_jobs = {}
+function! s:compiler_jobs.exec() abort dict " {{{1
+ let self.cmd = self.build_cmd()
+ let l:cmd = has('win32')
+ \ ? 'cmd /s /c "' . self.cmd . '"'
+ \ : ['sh', '-c', self.cmd]
+ let l:options = {
+ \ 'out_io' : 'file',
+ \ 'err_io' : 'file',
+ \ 'out_name' : self.output,
+ \ 'err_name' : self.output,
+ \}
+
+ let s:cb_target = self.target_path !=# b:vimtex.tex ? self.target_path : ''
+ let l:options.exit_cb = function('s:callback')
+
+ call vimtex#paths#pushd(self.root)
+ let self.job = job_start(l:cmd, l:options)
+ call vimtex#paths#popd()
+endfunction
+
+" }}}1
+function! s:callback(ch, msg) abort " {{{1
+ call vimtex#compiler#callback(!vimtex#qf#inquire(s:cb_target))
+endfunction
+
+" }}}1
+
+let s:compiler_nvim = {}
+function! s:compiler_nvim.exec() abort dict " {{{1
+ let self.cmd = self.build_cmd()
+ let l:cmd = has('win32')
+ \ ? 'cmd /s /c "' . self.cmd . '"'
+ \ : ['sh', '-c', self.cmd]
+
+ let l:shell = {
+ \ 'on_stdout' : function('s:callback_nvim_output'),
+ \ 'on_stderr' : function('s:callback_nvim_output'),
+ \ 'on_exit' : function('s:callback_nvim_exit'),
+ \ 'cwd' : self.root,
+ \ 'target' : self.target_path,
+ \ 'output' : self.output,
+ \}
+
+ let self.job = jobstart(l:cmd, l:shell)
+endfunction
+
+" }}}1
+function! s:callback_nvim_output(id, data, event) abort dict " {{{1
+ if !empty(a:data)
+ call writefile(filter(a:data, '!empty(v:val)'), self.output, 'a')
+ endif
+endfunction
+
+" }}}1
+function! s:callback_nvim_exit(id, data, event) abort dict " {{{1
+ let l:target = self.target !=# b:vimtex.tex ? self.target : ''
+ call vimtex#compiler#callback(!vimtex#qf#inquire(l:target))
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/compiler/tectonic.vim b/autoload/vimtex/compiler/tectonic.vim
new file mode 100644
index 00000000..3b9e8139
--- /dev/null
+++ b/autoload/vimtex/compiler/tectonic.vim
@@ -0,0 +1,255 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#compiler#tectonic#init(options) abort " {{{1
+ let l:compiler = deepcopy(s:compiler)
+
+ call l:compiler.init(extend(a:options,
+ \ get(g:, 'vimtex_compiler_tectonic', {}), 'keep'))
+
+ return l:compiler
+endfunction
+
+" }}}1
+
+let s:compiler = {
+ \ 'name' : 'tectonic',
+ \ 'backend' : has('nvim') ? 'nvim'
+ \ : v:version >= 800 ? 'jobs' : 'process',
+ \ 'root' : '',
+ \ 'target' : '',
+ \ 'target_path' : '',
+ \ 'background' : 1,
+ \ 'build_dir' : '',
+ \ 'output' : tempname(),
+ \ 'options' : [
+ \ '--keep-logs',
+ \ '--synctex'
+ \ ],
+ \}
+
+function! s:compiler.init(options) abort dict " {{{1
+ call extend(self, a:options)
+
+ if !executable('tectonic')
+ call vimtex#log#warning('tectonic is not executable!')
+ throw 'vimtex: Requirements not met'
+ endif
+
+ call extend(self, deepcopy(s:compiler_{self.backend}))
+
+ " Processes run with the new jobs api will not run in the foreground
+ if self.backend !=# 'process'
+ let self.background = 1
+ endif
+endfunction
+
+" }}}1
+
+function! s:compiler.build_cmd() abort dict " {{{1
+ let l:cmd = 'tectonic'
+
+ for l:opt in self.options
+ if l:opt =~# '^-\%(o\|-outdir\)'
+ call vimtex#log#warning("Don't use --outdir or -o in compiler options,"
+ \ . ' use build_dir instead, see :help g:vimtex_compiler_tectonic'
+ \ . ' for more details')
+ continue
+ endif
+
+ let l:cmd .= ' ' . l:opt
+ endfor
+
+ if empty(self.build_dir)
+ let self.build_dir = fnamemodify(self.target_path, ':p:h')
+ elseif !isdirectory(self.build_dir)
+ call vimtex#log#warning(
+ \ "build_dir doesn't exist, it will be created: " . self.build_dir)
+ call mkdir(self.build_dir, 'p')
+ endif
+
+ return l:cmd
+ \ . ' --outdir=' . self.build_dir
+ \ . ' ' . vimtex#util#shellescape(self.target)
+endfunction
+
+" }}}1
+function! s:compiler.cleanup() abort dict " {{{1
+ " Pass
+endfunction
+
+" }}}1
+function! s:compiler.pprint_items() abort dict " {{{1
+ let l:configuration = []
+
+ if self.backend ==# 'process'
+ call add(l:configuration, ['background', self.background])
+ endif
+
+ call add(l:configuration, ['tectonic options', self.options])
+
+ let l:list = []
+ call add(l:list, ['backend', self.backend])
+ if self.background
+ call add(l:list, ['output', self.output])
+ endif
+
+ if self.target_path !=# b:vimtex.tex
+ call add(l:list, ['root', self.root])
+ call add(l:list, ['target', self.target_path])
+ endif
+
+ call add(l:list, ['configuration', l:configuration])
+
+ if has_key(self, 'process')
+ call add(l:list, ['process', self.process])
+ endif
+
+ if has_key(self, 'job')
+ call add(l:list, ['cmd', self.cmd])
+ endif
+
+ return l:list
+endfunction
+
+" }}}1
+
+function! s:compiler.clean(...) abort dict " {{{1
+ let l:files = ['synctex.gz', 'toc', 'out', 'aux', 'log']
+
+ " If a full clean is required
+ if a:0 > 0 && a:1
+ call extend(l:intermediate, ['pdf'])
+ endif
+
+ let l:basename = self.build_dir . '/' . fnamemodify(self.target_path, ':t:r')
+ call map(l:files, 'l:basename . v:val')
+
+ call vimtex#process#run('rm -f ' . join(l:files))
+ call vimtex#log#info('Compiler clean finished')
+endfunction
+
+" }}}1
+function! s:compiler.start(...) abort dict " {{{1
+ call self.exec()
+
+ if self.background
+ call vimtex#log#info('Compiler started in background')
+ else
+ call vimtex#compiler#callback(!vimtex#qf#inquire(self.target))
+ endif
+endfunction
+
+" }}}1
+function! s:compiler.start_single() abort dict " {{{1
+ call self.start()
+endfunction
+
+" }}}1
+function! s:compiler.stop() abort dict " {{{1
+ " Pass
+endfunction
+
+" }}}1
+function! s:compiler.is_running() abort dict " {{{1
+ return 0
+endfunction
+
+" }}}1
+function! s:compiler.kill() abort dict " {{{1
+ " Pass
+endfunction
+
+" }}}1
+function! s:compiler.get_pid() abort dict " {{{1
+ return 0
+endfunction
+
+" }}}1
+
+let s:compiler_process = {}
+function! s:compiler_process.exec() abort dict " {{{1
+ let self.process = vimtex#process#new()
+ let self.process.name = 'tectonic'
+ let self.process.background = self.background
+ let self.process.workdir = self.root
+ let self.process.output = self.output
+ let self.process.cmd = self.build_cmd()
+ call self.process.run()
+endfunction
+
+" }}}1
+
+let s:compiler_jobs = {}
+function! s:compiler_jobs.exec() abort dict " {{{1
+ let self.cmd = self.build_cmd()
+ let l:cmd = has('win32')
+ \ ? 'cmd /s /c "' . self.cmd . '"'
+ \ : ['sh', '-c', self.cmd]
+ let l:options = {
+ \ 'out_io' : 'file',
+ \ 'err_io' : 'file',
+ \ 'out_name' : self.output,
+ \ 'err_name' : self.output,
+ \}
+
+ let s:cb_target = self.target_path !=# b:vimtex.tex ? self.target_path : ''
+ let l:options.exit_cb = function('s:callback')
+
+ if !empty(self.root)
+ let l:save_pwd = getcwd()
+ execute 'lcd' fnameescape(self.root)
+ endif
+ let self.job = job_start(l:cmd, l:options)
+ if !empty(self.root)
+ execute 'lcd' fnameescape(l:save_pwd)
+ endif
+endfunction
+
+" }}}1
+function! s:callback(ch, msg) abort " {{{1
+ call vimtex#compiler#callback(!vimtex#qf#inquire(s:cb_target))
+endfunction
+
+" }}}1
+
+let s:compiler_nvim = {}
+function! s:compiler_nvim.exec() abort dict " {{{1
+ let self.cmd = self.build_cmd()
+ let l:cmd = has('win32')
+ \ ? 'cmd /s /c "' . self.cmd . '"'
+ \ : ['sh', '-c', self.cmd]
+
+ let l:shell = {
+ \ 'on_stdout' : function('s:callback_nvim_output'),
+ \ 'on_stderr' : function('s:callback_nvim_output'),
+ \ 'on_exit' : function('s:callback_nvim_exit'),
+ \ 'cwd' : self.root,
+ \ 'target' : self.target_path,
+ \ 'output' : self.output,
+ \}
+
+ let self.job = jobstart(l:cmd, l:shell)
+endfunction
+
+" }}}1
+function! s:callback_nvim_output(id, data, event) abort dict " {{{1
+ if !empty(a:data)
+ call writefile(filter(a:data, '!empty(v:val)'), self.output, 'a')
+ endif
+endfunction
+
+" }}}1
+function! s:callback_nvim_exit(id, data, event) abort dict " {{{1
+ let l:target = self.target !=# b:vimtex.tex ? self.target : ''
+ call vimtex#compiler#callback(!vimtex#qf#inquire(l:target))
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/complete.vim b/autoload/vimtex/complete.vim
new file mode 100644
index 00000000..b23bc0de
--- /dev/null
+++ b/autoload/vimtex/complete.vim
@@ -0,0 +1,1089 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#complete#init_buffer() abort " {{{1
+ if !g:vimtex_complete_enabled | return | endif
+
+ if !has_key(b:vimtex, 'complete')
+ let b:vimtex.complete = {}
+ endif
+
+ for l:completer in s:completers
+ if has_key(l:completer, 'init')
+ call l:completer.init()
+ endif
+ endfor
+
+ setlocal omnifunc=vimtex#complete#omnifunc
+endfunction
+
+" }}}1
+
+function! vimtex#complete#omnifunc(findstart, base) abort " {{{1
+ if a:findstart
+ if exists('s:completer') | unlet s:completer | endif
+
+ let l:pos = col('.') - 1
+ let l:line = getline('.')[:l:pos-1]
+ for l:completer in s:completers
+ if !get(l:completer, 'enabled', 1) | continue | endif
+
+ for l:pattern in l:completer.patterns
+ if l:line =~# l:pattern
+ let s:completer = l:completer
+ while l:pos > 0
+ if l:line[l:pos - 1] =~# '{\|,\|\[\|\\'
+ \ || l:line[l:pos-2:l:pos-1] ==# ', '
+ let s:completer.context = matchstr(l:line,
+ \ get(s:completer, 're_context', '\S*$'))
+ return l:pos
+ else
+ let l:pos -= 1
+ endif
+ endwhile
+ return -2
+ endif
+ endfor
+ endfor
+ return -3
+ else
+ if !exists('s:completer') | return [] | endif
+
+ return g:vimtex_complete_close_braces && get(s:completer, 'inside_braces', 1)
+ \ ? s:close_braces(s:completer.complete(a:base))
+ \ : s:completer.complete(a:base)
+ endif
+endfunction
+
+" }}}1
+function! vimtex#complete#complete(type, input, context) abort " {{{1
+ try
+ let s:completer = s:completer_{a:type}
+ let s:completer.context = a:context
+ return s:completer.complete(a:input)
+ catch /E121/
+ return []
+ endtry
+endfunction
+
+" }}}1
+
+"
+" Completers
+"
+" {{{1 Bibtex
+
+let s:completer_bib = {
+ \ 'patterns' : [
+ \ '\v\\\a*cite\a*%(\s*\[[^]]*\]){0,2}\s*\{[^}]*$',
+ \ '\v\\bibentry\s*\{[^}]*$',
+ \ '\v\\%(text|block)cquote\*?%(\s*\[[^]]*\]){0,2}\{[^}]*$',
+ \ '\v\\%(for|hy)\w+cquote\*?\{[^}]*\}%(\s*\[[^]]*\]){0,2}\{[^}]*$',
+ \ ],
+ \ 'bibs' : '''\v%(%(\\@<!%(\\\\)*)@<=\%.*)@<!'
+ \ . '\\(%(no)?bibliography|add(bibresource|globalbib|sectionbib))'
+ \ . '\m\s*{\zs[^}]\+\ze}''',
+ \ 'initialized' : 0,
+ \}
+
+function! s:completer_bib.init() dict abort " {{{2
+ if self.initialized | return | endif
+ let self.initialized = 1
+
+ let self.patterns += g:vimtex_complete_bib.custom_patterns
+endfunction
+
+function! s:completer_bib.complete(regex) dict abort " {{{2
+ let self.candidates = self.gather_candidates()
+
+ if g:vimtex_complete_bib.simple
+ call s:filter_with_options(self.candidates, a:regex)
+ else
+ call s:filter_with_options(self.candidates, a:regex, {
+ \ 'anchor': 0,
+ \ 'filter_by_menu': 1,
+ \})
+ endif
+
+ return self.candidates
+endfunction
+
+function! s:completer_bib.gather_candidates() dict abort " {{{2
+ let l:entries = []
+
+ let l:cache = vimtex#cache#open('bibcomplete', {
+ \ 'local': 1,
+ \ 'default': {'result': [], 'ftime': -1}
+ \})
+
+ "
+ " Find data from external bib files
+ "
+
+ " Note: bibtex seems to require that we are in the project root
+ call vimtex#paths#pushd(b:vimtex.root)
+ for l:file in self.find_bibs()
+ if empty(l:file) | continue | endif
+
+ let l:filename = substitute(l:file, '\%(\.bib\)\?$', '.bib', '')
+ if !filereadable(l:filename)
+ let l:filename = vimtex#kpsewhich#find(l:filename)
+ endif
+ if !filereadable(l:filename) | continue | endif
+
+ let l:current = l:cache.get(l:filename)
+ let l:ftime = getftime(l:filename)
+ if l:ftime > l:current.ftime
+ let l:current.ftime = l:ftime
+ let l:current.result = map(
+ \ vimtex#parser#bib(l:filename),
+ \ 'self.convert(v:val)')
+ let l:cache.modified = 1
+ endif
+ let l:entries += l:current.result
+ endfor
+
+ call vimtex#paths#popd()
+
+ "
+ " Find data from 'thebibliography' environments
+ "
+ let l:ftime = b:vimtex.getftime()
+ if l:ftime > 0
+ let l:current = l:cache.get(sha256(b:vimtex.tex))
+
+ if l:ftime > l:current.ftime
+ let l:current.ftime = l:ftime
+ let l:current.result = []
+
+ let l:lines = vimtex#parser#tex(b:vimtex.tex, {'detailed' : 0})
+ if match(l:lines, '\C\\begin{thebibliography}') >= 0
+ call filter(l:lines, 'v:val =~# ''\C\\bibitem''')
+
+ for l:line in l:lines
+ let l:matches = matchlist(l:line, '\\bibitem\(\[[^]]\]\)\?{\([^}]*\)')
+ if len(l:matches) > 1
+ call add(l:current.result, self.convert({
+ \ 'key': l:matches[2],
+ \ 'type': 'thebibliography',
+ \ 'author': '',
+ \ 'year': '',
+ \ 'title': l:matches[2],
+ \ }))
+ endif
+ endfor
+ endif
+ endif
+
+ let l:entries += l:current.result
+ endif
+
+ " Write cache to file
+ call l:cache.write()
+
+ return l:entries
+endfunction
+
+function! s:completer_bib.find_bibs() dict abort " {{{2
+ "
+ " Search for added bibliographies
+ " * Parse commands such as \bibliography{file1,file2.bib,...}
+ " * This also removes the .bib extensions
+ "
+
+ let l:cache = vimtex#cache#open('bibfiles', {
+ \ 'local': 1,
+ \ 'default': {'files': [], 'ftime': -1}
+ \})
+
+ " Handle local file editing (e.g. subfiles package)
+ let l:id = get(get(b:, 'vimtex_local', {'main_id' : b:vimtex_id}), 'main_id')
+ let l:vimtex = vimtex#state#get(l:id)
+
+ let l:bibfiles = []
+ for l:file in map(copy(l:vimtex.sources), 'l:vimtex.root . ''/'' . v:val')
+ let l:current = l:cache.get(l:file)
+
+ let l:ftime = getftime(l:file)
+ if l:ftime > l:current.ftime
+ let l:cache.modified = 1
+ let l:current.ftime = l:ftime
+ let l:current.files = []
+ for l:entry in map(
+ \ filter(readfile(l:file), 'v:val =~ ' . self.bibs),
+ \ 'matchstr(v:val, ' . self.bibs . ')')
+ let l:files = []
+ let l:entry = substitute(l:entry, '\\jobname', b:vimtex.name, 'g')
+
+ for l:f in split(l:entry, ',')
+ if stridx(l:f, '*') >= 0
+ let l:files += glob(l:f, 0, 1)
+ else
+ let l:files += [fnamemodify(l:f, ':r')]
+ endif
+ endfor
+
+ let l:current.files += l:files
+ endfor
+ endif
+
+ let l:bibfiles += l:current.files
+ endfor
+
+ " Write cache to file
+ call l:cache.write()
+
+ return vimtex#util#uniq(l:bibfiles)
+endfunction
+
+function! s:completer_bib.convert(entry) dict abort " {{{2
+ let cand = {'word': a:entry['key']}
+
+ let auth = get(a:entry, 'author', 'Unknown')[:20]
+ let auth = substitute(auth, '\~', ' ', 'g')
+ let substitutes = {
+ \ '@key' : a:entry['key'],
+ \ '@type' : empty(a:entry['type']) ? '-' : a:entry['type'],
+ \ '@author_all' : auth,
+ \ '@author_short' : substitute(auth, ',.*\ze', ' et al.', ''),
+ \ '@year' : get(a:entry, 'year', get(a:entry, 'date', '?')),
+ \ '@title' : get(a:entry, 'title', 'No title'),
+ \}
+
+ " Create menu string
+ if !empty(g:vimtex_complete_bib.menu_fmt)
+ let cand.menu = copy(g:vimtex_complete_bib.menu_fmt)
+ for [key, val] in items(substitutes)
+ let cand.menu = substitute(cand.menu, key, escape(val, '&'), '')
+ endfor
+ endif
+
+ " Create abbreviation string
+ if !empty(g:vimtex_complete_bib.abbr_fmt)
+ let cand.abbr = copy(g:vimtex_complete_bib.abbr_fmt)
+ for [key, val] in items(substitutes)
+ let cand.abbr = substitute(cand.abbr, key, escape(val, '&'), '')
+ endfor
+ endif
+
+ return cand
+endfunction
+
+" }}}1
+" {{{1 Labels
+
+let s:completer_ref = {
+ \ 'patterns' : [
+ \ '\v\\v?%(auto|eq|[cC]?%(page)?|labelc)?ref%(\s*\{[^}]*|range\s*\{[^,{}]*%(\}\{)?)$',
+ \ '\\hyperref\s*\[[^]]*$',
+ \ '\\subref\*\?{[^}]*$',
+ \ ],
+ \ 're_context' : '\\\w*{[^}]*$',
+ \ 'labels' : [],
+ \ 'initialized' : 0,
+ \}
+
+function! s:completer_ref.init() dict abort " {{{2
+ if self.initialized | return | endif
+ let self.initialized = 1
+
+ " Add custom patterns
+ let self.patterns += g:vimtex_complete_ref.custom_patterns
+endfunction
+
+function! s:completer_ref.complete(regex) dict abort " {{{2
+ let self.candidates = self.get_matches(a:regex)
+
+ if self.context =~# '\\eqref'
+ \ && !empty(filter(copy(self.matches), 'v:val.word =~# ''^eq:'''))
+ call filter(self.candidates, 'v:val.word =~# ''^eq:''')
+ endif
+
+ return self.candidates
+endfunction
+
+function! s:completer_ref.get_matches(regex) dict abort " {{{2
+ call self.parse_aux_files()
+
+ " Match number
+ let self.matches = filter(copy(self.labels), 'v:val.menu =~# ''' . a:regex . '''')
+ if !empty(self.matches) | return self.matches | endif
+
+ " Match label
+ let self.matches = filter(copy(self.labels), 'v:val.word =~# ''' . a:regex . '''')
+
+ " Match label and number
+ if empty(self.matches)
+ let l:regex_split = split(a:regex)
+ if len(l:regex_split) > 1
+ let l:base = l:regex_split[0]
+ let l:number = escape(join(l:regex_split[1:], ' '), '.')
+ let self.matches = filter(copy(self.labels),
+ \ 'v:val.word =~# ''' . l:base . ''' &&' .
+ \ 'v:val.menu =~# ''' . l:number . '''')
+ endif
+ endif
+
+ return self.matches
+endfunction
+
+function! s:completer_ref.parse_aux_files() dict abort " {{{2
+ let l:files = [[b:vimtex.aux(), '']]
+
+ " Handle local file editing (e.g. subfiles package)
+ if exists('b:vimtex_local') && b:vimtex_local.active
+ let l:files += [[vimtex#state#get(b:vimtex_local.main_id).aux(), '']]
+ endif
+
+ " Add externaldocuments (from \externaldocument in preamble)
+ let l:files += map(
+ \ vimtex#parser#get_externalfiles(),
+ \ '[v:val.aux, v:val.opt]')
+
+ let l:cache = vimtex#cache#open('refcomplete', {
+ \ 'local': 1,
+ \ 'default': {'labels': [], 'ftime': -1}
+ \})
+
+ let self.labels = []
+ for [l:file, l:prefix] in filter(l:files, 'filereadable(v:val[0])')
+ let l:current = l:cache.get(l:file)
+ let l:ftime = getftime(l:file)
+ if l:ftime > l:current.ftime
+ let l:current.ftime = l:ftime
+ let l:current.labels = self.parse_labels(l:file, l:prefix)
+ let l:cache.modified = 1
+ endif
+
+ let self.labels += l:current.labels
+ endfor
+
+ " Write cache to file
+ call l:cache.write()
+
+ return self.labels
+endfunction
+
+function! s:completer_ref.parse_labels(file, prefix) dict abort " {{{2
+ "
+ " Searches aux files recursively for commands of the form
+ "
+ " \newlabel{name}{{number}{page}.*}.*
+ " \newlabel{name}{{text {number}}{page}.*}.*
+ " \newlabel{name}{{number}{page}{...}{type}.*}.*
+ "
+ " Returns a list of candidates like {'word': name, 'menu': type number page}.
+ "
+
+ let l:labels = []
+ let l:lines = vimtex#parser#auxiliary(a:file)
+ let l:lines = filter(l:lines, 'v:val =~# ''\\newlabel{''')
+ let l:lines = filter(l:lines, 'v:val !~# ''@cref''')
+ let l:lines = filter(l:lines, 'v:val !~# ''sub@''')
+ let l:lines = filter(l:lines, 'v:val !~# ''tocindent-\?[0-9]''')
+ for l:line in l:lines
+ let l:line = vimtex#util#tex2unicode(l:line)
+ let l:tree = vimtex#util#tex2tree(l:line)[1:]
+ let l:name = get(remove(l:tree, 0), 0, '')
+ if empty(l:name)
+ continue
+ else
+ let l:name = a:prefix . l:name
+ endif
+ let l:context = remove(l:tree, 0)
+ if type(l:context) == type([]) && len(l:context) > 1
+ let l:menu = ''
+ try
+ let l:type = substitute(l:context[3][0], '\..*$', ' ', '')
+ let l:type = substitute(l:type, 'AMS', 'Equation', '')
+ let l:menu .= toupper(l:type[0]) . l:type[1:]
+ catch
+ endtry
+
+ let l:number = self.parse_number(l:context[0])
+ if l:menu =~# 'Equation'
+ let l:number = '(' . l:number . ')'
+ endif
+ let l:menu .= l:number
+
+ try
+ let l:menu .= ' [p. ' . l:context[1][0] . ']'
+ catch
+ endtry
+ call add(l:labels, {'word': l:name, 'menu': l:menu})
+ endif
+ endfor
+
+ return l:labels
+endfunction
+
+function! s:completer_ref.parse_number(num_tree) dict abort " {{{2
+ if type(a:num_tree) == type([])
+ if len(a:num_tree) == 0
+ return '-'
+ else
+ let l:index = len(a:num_tree) == 1 ? 0 : 1
+ return self.parse_number(a:num_tree[l:index])
+ endif
+ else
+ let l:matches = matchlist(a:num_tree, '\v(^|.*\s)((\u|\d+)(\.\d+)*\l?)($|\s.*)')
+ return len(l:matches) > 3 ? l:matches[2] : '-'
+ endif
+endfunction
+
+" }}}1
+" {{{1 Commands
+
+let s:completer_cmd = {
+ \ 'patterns' : [g:vimtex#re#not_bslash . '\\\a*$'],
+ \ 'inside_braces' : 0,
+ \}
+
+function! s:completer_cmd.complete(regex) dict abort " {{{2
+ let l:candidates = self.gather_candidates()
+ let l:mode = vimtex#util#in_mathzone() ? 'm' : 'n'
+
+ call s:filter_with_options(l:candidates, a:regex)
+ call filter(l:candidates, 'l:mode =~# v:val.mode')
+
+ return l:candidates
+endfunction
+
+function! s:completer_cmd.gather_candidates() dict abort " {{{2
+ let l:candidates = s:load_from_document('cmd')
+ let l:candidates += self.gather_candidates_from_lets()
+ for l:pkg in s:get_packages()
+ let l:candidates += s:load_from_package(l:pkg, 'cmd')
+ endfor
+ let l:candidates += self.gather_candidates_from_glossary_keys()
+
+ return vimtex#util#uniq_unsorted(l:candidates)
+endfunction
+
+function! s:completer_cmd.gather_candidates_from_glossary_keys() dict abort " {{{2
+ if !has_key(b:vimtex.packages, 'glossaries') | return [] | endif
+
+ let l:preamble = vimtex#parser#preamble(b:vimtex.tex)
+ call map(l:preamble, "substitute(v:val, '\\s*%.*', '', 'g')")
+ let l:glskeys = split(join(l:preamble, "\n"), '\n\s*\\glsaddkey\*\?')[1:]
+ call map(l:glskeys, "substitute(v:val, '\n\\s*', '', 'g')")
+ call map(l:glskeys, 'vimtex#util#tex2tree(v:val)[2:6]')
+
+ let l:candidates = map(vimtex#util#flatten(l:glskeys), '{
+ \ ''word'' : v:val[1:],
+ \ ''mode'' : ''.'',
+ \ ''kind'' : ''[cmd: glossaries]'',
+ \ }')
+
+ return l:candidates
+endfunction
+
+function! s:completer_cmd.gather_candidates_from_lets() dict abort " {{{2
+ let l:preamble = vimtex#parser#preamble(b:vimtex.tex)
+
+ let l:lets = filter(copy(l:preamble), 'v:val =~# ''\\let\>''')
+ let l:defs = filter(copy(l:preamble), 'v:val =~# ''\\def\>''')
+ let l:candidates = map(l:lets, '{
+ \ ''word'' : matchstr(v:val, ''\\let[^\\]*\\\zs\w*''),
+ \ ''mode'' : ''.'',
+ \ ''kind'' : ''[cmd: \let]'',
+ \ }')
+ \ + map(l:defs, '{
+ \ ''word'' : matchstr(v:val, ''\\def[^\\]*\\\zs\w*''),
+ \ ''mode'' : ''.'',
+ \ ''kind'' : ''[cmd: \def]'',
+ \ }')
+
+ return l:candidates
+endfunction
+
+" }}}1
+" {{{1 Environments
+
+let s:completer_env = {
+ \ 'patterns' : ['\v\\%(begin|end)%(\s*\[[^]]*\])?\s*\{[^}]*$'],
+ \}
+
+function! s:completer_env.complete(regex) dict abort " {{{2
+ if self.context =~# '^\\end\>'
+ " When completing \end{, search for an unmatched \begin{...}
+ let l:matching_env = ''
+ let l:save_pos = vimtex#pos#get_cursor()
+ let l:pos_val_cursor = vimtex#pos#val(l:save_pos)
+
+ let l:lnum = l:save_pos[1] + 1
+ while l:lnum > 1
+ let l:open = vimtex#delim#get_prev('env_tex', 'open')
+ if empty(l:open) || get(l:open, 'name', '') ==# 'document'
+ break
+ endif
+
+ let l:close = vimtex#delim#get_matching(l:open)
+ if empty(l:close.match)
+ let l:matching_env = l:close.name . (l:close.starred ? '*' : '')
+ break
+ endif
+
+ let l:pos_val_try = vimtex#pos#val(l:close) + strlen(l:close.match)
+ if l:pos_val_try > l:pos_val_cursor
+ break
+ else
+ let l:lnum = l:open.lnum
+ call vimtex#pos#set_cursor(vimtex#pos#prev(l:open))
+ endif
+ endwhile
+
+ call vimtex#pos#set_cursor(l:save_pos)
+
+ if !empty(l:matching_env) && l:matching_env =~# a:regex
+ return [{
+ \ 'word': l:matching_env,
+ \ 'kind': '[env: matching]',
+ \}]
+ endif
+ endif
+
+ return s:filter_with_options(copy(self.gather_candidates()), a:regex)
+endfunction
+
+" }}}2
+function! s:completer_env.gather_candidates() dict abort " {{{2
+ let l:candidates = s:load_from_document('env')
+ for l:pkg in s:get_packages()
+ let l:candidates += s:load_from_package(l:pkg, 'env')
+ endfor
+
+ return vimtex#util#uniq_unsorted(l:candidates)
+endfunction
+
+" }}}2
+" }}}1
+" {{{1 Filenames (\includegraphics)
+
+let s:completer_img = {
+ \ 'patterns' : ['\v\\includegraphics\*?%(\s*\[[^]]*\]){0,2}\s*\{[^}]*$'],
+ \ 'ext_re' : '\v\.%('
+ \ . join(['png', 'jpg', 'eps', 'pdf', 'pgf', 'tikz'], '|')
+ \ . ')$',
+ \}
+
+function! s:completer_img.complete(regex) dict abort " {{{2
+ return s:filter_with_options(self.gather_candidates(), a:regex)
+endfunction
+
+function! s:completer_img.gather_candidates() dict abort " {{{2
+ let l:added_files = []
+ let l:generated_pdf = b:vimtex.out()
+
+ let l:candidates = []
+ for l:path in b:vimtex.graphicspath + [b:vimtex.root]
+ let l:files = globpath(l:path, '**/*.*', 1, 1)
+
+ call filter(l:files, 'v:val =~? self.ext_re')
+ call filter(l:files, 'v:val !=# l:generated_pdf')
+ call filter(l:files, 'index(l:added_files, v:val) < 0')
+
+ let l:added_files += l:files
+ let l:candidates += map(l:files, "{
+ \ 'abbr': vimtex#paths#shorten_relative(v:val),
+ \ 'word': vimtex#paths#relative(v:val, l:path),
+ \ 'kind': '[graphics]',
+ \}")
+ endfor
+
+ return l:candidates
+endfunction
+
+" }}}1
+" {{{1 Filenames (\input, \include, and \subfile)
+
+let s:completer_inc = {
+ \ 'patterns' : [
+ \ g:vimtex#re#tex_input . '[^}]*$',
+ \ '\v\\includeonly\s*\{[^}]*$',
+ \ ],
+ \}
+
+function! s:completer_inc.complete(regex) dict abort " {{{2
+ let self.candidates = split(globpath(b:vimtex.root, '**/*.tex'), '\n')
+ let self.candidates = map(self.candidates,
+ \ 'strpart(v:val, len(b:vimtex.root)+1)')
+ call s:filter_with_options(self.candidates, a:regex)
+
+ if self.context =~# '\\include'
+ let self.candidates = map(self.candidates, '{
+ \ ''word'' : fnamemodify(v:val, '':r''),
+ \ ''kind'' : '' [include]'',
+ \}')
+ else
+ let self.candidates = map(self.candidates, '{
+ \ ''word'' : v:val,
+ \ ''kind'' : '' [input]'',
+ \}')
+ endif
+
+ return self.candidates
+endfunction
+
+" }}}1
+" {{{1 Filenames (\includepdf)
+
+let s:completer_pdf = {
+ \ 'patterns' : ['\v\\includepdf%(\s*\[[^]]*\])?\s*\{[^}]*$'],
+ \}
+
+function! s:completer_pdf.complete(regex) dict abort " {{{2
+ let self.candidates = split(globpath(b:vimtex.root, '**/*.pdf'), '\n')
+ let self.candidates = map(self.candidates,
+ \ 'strpart(v:val, len(b:vimtex.root)+1)')
+ call s:filter_with_options(self.candidates, a:regex)
+ let self.candidates = map(self.candidates, '{
+ \ ''word'' : v:val,
+ \ ''kind'' : '' [includepdf]'',
+ \}')
+ return self.candidates
+endfunction
+
+" }}}1
+" {{{1 Filenames (\includestandalone)
+
+let s:completer_sta = {
+ \ 'patterns' : ['\v\\includestandalone%(\s*\[[^]]*\])?\s*\{[^}]*$'],
+ \}
+
+function! s:completer_sta.complete(regex) dict abort " {{{2
+ let self.candidates = substitute(globpath(b:vimtex.root, '**/*.tex'), '\.tex', '', 'g')
+ let self.candidates = split(self.candidates, '\n')
+ let self.candidates = map(self.candidates,
+ \ 'strpart(v:val, len(b:vimtex.root)+1)')
+ call s:filter_with_options(self.candidates, a:regex)
+ let self.candidates = map(self.candidates, '{
+ \ ''word'' : v:val,
+ \ ''kind'' : '' [includestandalone]'',
+ \}')
+ return self.candidates
+endfunction
+
+" }}}1
+" {{{1 Glossary (\gls +++)
+
+let s:completer_gls = {
+ \ 'patterns' : [
+ \ '\v\\([cpdr]?(gls|Gls|GLS)|acr|Acr|ACR)\a*\s*\{[^}]*$',
+ \ '\v\\(ac|Ac|AC)\s*\{[^}]*$',
+ \ ],
+ \ 'key' : {
+ \ 'newglossaryentry' : ' [gls]',
+ \ 'longnewglossaryentry' : ' [gls]',
+ \ 'newacronym' : ' [acr]',
+ \ 'newabbreviation' : ' [abbr]',
+ \ 'glsxtrnewsymbol' : ' [symbol]',
+ \ },
+ \}
+
+function! s:completer_gls.init() dict abort " {{{2
+ if !has_key(b:vimtex.packages, 'glossaries-extra') | return | endif
+
+ " Detect stuff like this:
+ " \GlsXtrLoadResources[src=glossary.bib]
+ " \GlsXtrLoadResources[src={glossary.bib}, selection={all}]
+ " \GlsXtrLoadResources[selection={all},src={glossary.bib}]
+ " \GlsXtrLoadResources[
+ " src={glossary.bib},
+ " selection={all},
+ " ]
+
+ let l:do_search = 0
+ for l:line in vimtex#parser#preamble(b:vimtex.tex)
+ if line =~# '^\s*\\GlsXtrLoadResources\s*\['
+ let l:do_search = 1
+ let l:line = matchstr(l:line, '^\s*\\GlsXtrLoadResources\s*\[\zs.*')
+ endif
+ if !l:do_search | continue | endif
+
+ let l:matches = split(l:line, '[=,]')
+ if empty(l:matches) | continue | endif
+
+ while !empty(l:matches)
+ let l:key = vimtex#util#trim(remove(l:matches, 0))
+ if l:key ==# 'src'
+ let l:value = vimtex#util#trim(remove(l:matches, 0))
+ let l:value = substitute(l:value, '^{', '', '')
+ let l:value = substitute(l:value, '[]}]\s*', '', 'g')
+ let b:vimtex.complete.glsbib = l:value
+ break
+ endif
+ endwhile
+ endfor
+endfunction
+
+function! s:completer_gls.complete(regex) dict abort " {{{2
+ return s:filter_with_options(
+ \ self.parse_glsentries() + self.parse_glsbib(), a:regex)
+endfunction
+
+function! s:completer_gls.parse_glsentries() dict abort " {{{2
+ let l:candidates = []
+
+ let l:re_commands = '\v\\(' . join(keys(self.key), '|') . ')'
+ let l:re_matcher = l:re_commands . '\s*%(\[.*\])=\s*\{([^{}]*)'
+
+ for l:line in filter(
+ \ vimtex#parser#tex(b:vimtex.tex, {'detailed' : 0}),
+ \ 'v:val =~# l:re_commands')
+ let l:matches = matchlist(l:line, l:re_matcher)
+ call add(l:candidates, {
+ \ 'word' : l:matches[2],
+ \ 'menu' : self.key[l:matches[1]],
+ \})
+ endfor
+
+ return l:candidates
+endfunction
+
+function! s:completer_gls.parse_glsbib() dict abort " {{{2
+ let l:filename = get(b:vimtex.complete, 'glsbib', '')
+ if empty(l:filename) | return [] | endif
+
+ let l:candidates = []
+ for l:entry in vimtex#parser#bib(l:filename, {'backend': 'bibparse'})
+ call add(l:candidates, {
+ \ 'word': l:entry.key,
+ \ 'menu': get(l:entry, 'name', '--'),
+ \})
+ endfor
+
+ return l:candidates
+endfunction
+
+" }}}1
+" {{{1 Packages (\usepackage)
+
+let s:completer_pck = {
+ \ 'patterns' : [
+ \ '\v\\%(usepackage|RequirePackage)%(\s*\[[^]]*\])?\s*\{[^}]*$',
+ \ '\v\\PassOptionsToPackage\s*\{[^}]*\}\s*\{[^}]*$',
+ \ ],
+ \ 'candidates' : [],
+ \}
+
+function! s:completer_pck.complete(regex) dict abort " {{{2
+ return s:filter_with_options(self.gather_candidates(), a:regex)
+endfunction
+
+function! s:completer_pck.gather_candidates() dict abort " {{{2
+ if empty(self.candidates)
+ let self.candidates = map(s:get_texmf_candidates('sty'), '{
+ \ ''word'' : v:val,
+ \ ''kind'' : '' [package]'',
+ \}')
+ endif
+
+ return copy(self.candidates)
+endfunction
+
+" }}}1
+" {{{1 Documentclasses (\documentclass)
+
+let s:completer_doc = {
+ \ 'patterns' : ['\v\\documentclass%(\s*\[[^]]*\])?\s*\{[^}]*$'],
+ \ 'candidates' : [],
+ \}
+
+function! s:completer_doc.complete(regex) dict abort " {{{2
+ return s:filter_with_options(self.gather_candidates(), a:regex)
+endfunction
+
+function! s:completer_doc.gather_candidates() dict abort " {{{2
+ if empty(self.candidates)
+ let self.candidates = map(s:get_texmf_candidates('cls'), '{
+ \ ''word'' : v:val,
+ \ ''kind'' : '' [documentclass]'',
+ \}')
+ endif
+
+ return copy(self.candidates)
+endfunction
+
+" }}}1
+" {{{1 Bibliographystyles (\bibliographystyle)
+
+let s:completer_bst = {
+ \ 'patterns' : ['\v\\bibliographystyle\s*\{[^}]*$'],
+ \ 'candidates' : [],
+ \}
+
+function! s:completer_bst.complete(regex) dict abort " {{{2
+ return s:filter_with_options(self.gather_candidates(), a:regex)
+endfunction
+
+function! s:completer_bst.gather_candidates() dict abort " {{{2
+ if empty(self.candidates)
+ let self.candidates = map(s:get_texmf_candidates('bst'), '{
+ \ ''word'' : v:val,
+ \ ''kind'' : '' [bst files]'',
+ \}')
+ endif
+
+ return copy(self.candidates)
+endfunction
+
+" }}}1
+
+"
+" Functions to parse candidates from packages
+"
+function! s:get_packages() abort " {{{1
+ let l:packages = [
+ \ 'default',
+ \ 'class-' . get(b:vimtex, 'documentclass', ''),
+ \ ] + keys(b:vimtex.packages)
+
+ call vimtex#paths#pushd(s:complete_dir)
+
+ let l:missing = filter(copy(l:packages), '!filereadable(v:val)')
+ call filter(l:packages, 'filereadable(v:val)')
+
+ " Parse include statements in complete files
+ let l:queue = copy(l:packages)
+ while !empty(l:queue)
+ let l:current = remove(l:queue, 0)
+ let l:includes = filter(readfile(l:current), 'v:val =~# ''^\#\s*include:''')
+ if empty(l:includes) | continue | endif
+
+ call map(l:includes, 'matchstr(v:val, ''include:\s*\zs.*\ze\s*$'')')
+ let l:missing += filter(filter(copy(l:includes),
+ \ '!filereadable(v:val)'),
+ \ 'index(l:missing, v:val) < 0')
+ call filter(l:includes, 'filereadable(v:val)')
+ call filter(l:includes, 'index(l:packages, v:val) < 0')
+
+ let l:packages += l:includes
+ let l:queue += l:includes
+ endwhile
+
+ call vimtex#paths#popd()
+
+ return l:packages + l:missing
+endfunction
+
+" }}}1
+function! s:load_from_package(pkg, type) abort " {{{1
+ let s:pkg_cache = get(s:, 'pkg_cache',
+ \ vimtex#cache#open('pkgcomplete', {'default': {}}))
+ let l:current = s:pkg_cache.get(a:pkg)
+
+ let l:pkg_file = s:complete_dir . '/' . a:pkg
+ if filereadable(l:pkg_file)
+ if !has_key(l:current, 'candidates')
+ let s:pkg_cache.modified = 1
+ let l:current.candidates
+ \ = s:_load_candidates_from_complete_file(a:pkg, l:pkg_file)
+ endif
+ else
+ if !has_key(l:current, 'candidates')
+ let s:pkg_cache.modified = 1
+ let l:current.candidates = {'cmd': [], 'env': []}
+ endif
+
+ let l:filename = a:pkg =~# '^class-'
+ \ ? vimtex#kpsewhich#find(a:pkg[6:] . '.cls')
+ \ : vimtex#kpsewhich#find(a:pkg . '.sty')
+
+ let l:ftime = getftime(l:filename)
+ if l:ftime > get(l:current, 'ftime', -1)
+ let s:pkg_cache.modified = 1
+ let l:current.ftime = l:ftime
+ let l:current.candidates = s:_load_candidates_from_source(
+ \ readfile(l:filename), a:pkg)
+ endif
+ endif
+
+ " Write cache to file
+ call s:pkg_cache.write()
+
+ return copy(l:current.candidates[a:type])
+endfunction
+
+" }}}1
+function! s:load_from_document(type) abort " {{{1
+ let s:pkg_cache = get(s:, 'pkg_cache',
+ \ vimtex#cache#open('pkgcomplete', {'default': {}}))
+
+ let l:ftime = b:vimtex.getftime()
+ if l:ftime < 0 | return [] | endif
+
+ let l:current = s:pkg_cache.get(sha256(b:vimtex.tex))
+ if l:ftime > get(l:current, 'ftime', -1)
+ let l:current.ftime = l:ftime
+ let l:current.candidates = s:_load_candidates_from_source(
+ \ vimtex#parser#tex(b:vimtex.tex, {'detailed' : 0}),
+ \ 'local')
+
+ " Write cache to file
+ let s:pkg_cache.modified = 1
+ call s:pkg_cache.write()
+ endif
+
+ return copy(l:current.candidates[a:type])
+endfunction
+
+" }}}1
+function! s:_load_candidates_from_complete_file(pkg, pkgfile) abort " {{{1
+ let l:result = {'cmd': [], 'env': []}
+ let l:lines = readfile(a:pkgfile)
+
+ let l:candidates = filter(copy(l:lines), 'v:val =~# ''^\a''')
+ call map(l:candidates, 'split(v:val)')
+ call map(l:candidates, '{
+ \ ''word'' : v:val[0],
+ \ ''mode'' : ''.'',
+ \ ''kind'' : ''[cmd: '' . a:pkg . ''] '',
+ \ ''menu'' : (get(v:val, 1, '''')),
+ \}')
+ let l:result.cmd += l:candidates
+
+ let l:candidates = filter(l:lines, 'v:val =~# ''^\\begin{''')
+ call map(l:candidates, '{
+ \ ''word'' : substitute(v:val, ''^\\begin{\|}$'', '''', ''g''),
+ \ ''mode'' : ''.'',
+ \ ''kind'' : ''[env: '' . a:pkg . ''] '',
+ \}')
+ let l:result.env += l:candidates
+
+ return l:result
+endfunction
+
+" }}}1
+function! s:_load_candidates_from_source(lines, pkg) abort " {{{1
+ return {
+ \ 'cmd':
+ \ s:gather_candidates_from_newcommands(
+ \ copy(a:lines), 'cmd: ' . a:pkg),
+ \ 'env':
+ \ s:gather_candidates_from_newenvironments(
+ \ a:lines, 'env: ' . a:pkg)
+ \}
+endfunction
+
+" }}}1
+
+function! s:gather_candidates_from_newcommands(lines, label) abort " {{{1
+ " Arguments:
+ " a:lines Lines of TeX that may contain \newcommands (or some variant,
+ " e.g. as provided by xparse and standard declaration)
+ " a:label Label to use in the menu
+
+ call filter(a:lines, 'v:val =~# ''\v\\((provide|renew|new)command|(New|Declare|Provide|Renew)(Expandable)?DocumentCommand)''')
+ call map(a:lines, '{
+ \ ''word'' : matchstr(v:val, ''\v\\((provide|renew|new)command|(New|Declare|Provide|Renew)(Expandable)?DocumentCommand)\*?\{\\?\zs[^}]*''),
+ \ ''mode'' : ''.'',
+ \ ''kind'' : ''['' . a:label . '']'',
+ \ }')
+
+ return a:lines
+endfunction
+
+" }}}1
+function! s:gather_candidates_from_newenvironments(lines, label) abort " {{{1
+ " Arguments:
+ " a:lines Lines of TeX that may contain \newenvironments (or some
+ " variant, e.g. as provided by xparse and standard declaration)
+ " a:label Label to use in the menu
+
+ call filter(a:lines, 'v:val =~# ''\v\\((renew|new)environment|(New|Renew|Provide|Declare)DocumentEnvironment)''')
+ call map(a:lines, '{
+ \ ''word'' : matchstr(v:val, ''\v\\((renew|new)environment|(New|Renew|Provide|Declare)DocumentEnvironment)\*?\{\\?\zs[^}]*''),
+ \ ''mode'' : ''.'',
+ \ ''kind'' : ''['' . a:label . '']'',
+ \ }')
+
+ return a:lines
+endfunction
+
+" }}}1
+
+
+"
+" Utility functions
+"
+function! s:filter_with_options(input, regex, ...) abort " {{{1
+ if empty(a:input) | return a:input | endif
+
+ let l:opts = a:0 > 0 ? a:1 : {}
+ let l:expression = type(a:input[0]) == type({})
+ \ ? get(l:opts, 'filter_by_menu') ? 'v:val.menu' : 'v:val.word'
+ \ : 'v:val'
+
+ if g:vimtex_complete_ignore_case && (!g:vimtex_complete_smart_case || a:regex !~# '\u')
+ let l:expression .= ' =~? '
+ else
+ let l:expression .= ' =~# '
+ endif
+
+ if get(l:opts, 'anchor', 1)
+ let l:expression .= '''^'' . '
+ endif
+
+ let l:expression .= 'a:regex'
+
+ return filter(a:input, l:expression)
+endfunction
+
+" }}}1
+function! s:get_texmf_candidates(filetype) abort " {{{1
+ let l:candidates = []
+
+ let l:texmfhome = $TEXMFHOME
+ if empty(l:texmfhome)
+ let l:texmfhome = get(vimtex#kpsewhich#run('--var-value TEXMFHOME'), 0, '')
+ endif
+
+ " Add locally installed candidates first
+ if !empty(l:texmfhome)
+ let l:candidates += glob(l:texmfhome . '/**/*.' . a:filetype, 0, 1)
+ call map(l:candidates, 'fnamemodify(v:val, '':t:r'')')
+ endif
+
+ " Then add globally available candidates (based on ls-R files)
+ for l:file in vimtex#kpsewhich#run('--all ls-R')
+ let l:candidates += map(filter(readfile(l:file),
+ \ 'v:val =~# ''\.' . a:filetype . ''''),
+ \ 'fnamemodify(v:val, '':r'')')
+ endfor
+
+ return l:candidates
+endfunction
+
+" }}}1
+function! s:close_braces(candidates) abort " {{{1
+ if strpart(getline('.'), col('.') - 1) !~# '^\s*[,}]'
+ for l:cand in a:candidates
+ if !has_key(l:cand, 'abbr')
+ let l:cand.abbr = l:cand.word
+ endif
+ let l:cand.word = substitute(l:cand.word, '}*$', '}', '')
+ endfor
+ endif
+
+ return a:candidates
+endfunction
+
+" }}}1
+
+
+"
+" Initialize module
+"
+let s:completers = map(
+ \ filter(items(s:), 'v:val[0] =~# ''^completer_'''),
+ \ 'v:val[1]')
+
+let s:complete_dir = fnamemodify(expand('<sfile>'), ':r') . '/'
+
+endif
diff --git a/autoload/vimtex/debug.vim b/autoload/vimtex/debug.vim
new file mode 100644
index 00000000..1ddf3aa6
--- /dev/null
+++ b/autoload/vimtex/debug.vim
@@ -0,0 +1,114 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#debug#stacktrace(...) abort " {{{1
+ "
+ " This function builds on Luc Hermite's answer on Stack Exchange:
+ " http://vi.stackexchange.com/a/6024/21
+ "
+
+ "
+ " Get stack and exception
+ "
+ if empty(v:throwpoint)
+ try
+ throw 'dummy'
+ catch
+ let l:stack = reverse(split(v:throwpoint, '\.\.'))[1:]
+ let l:exception = 'Manual stacktrace'
+ endtry
+ else
+ let l:stack = reverse(split(v:throwpoint, '\.\.'))
+ let l:exception = v:exception
+ endif
+
+ "
+ " Build the quickfix entries
+ "
+ let l:qflist = []
+ let l:files = {}
+ for l:func in l:stack
+ try
+ let [l:name, l:offset] = (l:func =~# '\S\+\[\d')
+ \ ? matchlist(l:func, '\(\S\+\)\[\(\d\+\)\]')[1:2]
+ \ : matchlist(l:func, '\(\S\+\), line \(\d\+\)')[1:2]
+ catch
+ let l:name = l:func
+ let l:offset = 0
+ endtry
+
+ if l:name =~# '\v(\<SNR\>|^)\d+_'
+ let l:sid = matchstr(l:name, '\v(\<SNR\>|^)\zs\d+\ze_')
+ let l:name = substitute(l:name, '\v(\<SNR\>|^)\d+_', 's:', '')
+ let l:filename = substitute(
+ \ vimtex#util#command('scriptnames')[l:sid-1],
+ \ '^\s*\d\+:\s*', '', '')
+ else
+ let l:func_name = l:name =~# '^\d\+$' ? '{' . l:name . '}' : l:name
+ let l:filename = matchstr(
+ \ vimtex#util#command('verbose function ' . l:func_name)[1],
+ \ v:lang[0:1] ==# 'en'
+ \ ? 'Last set from \zs.*\.vim' : '\f\+\.vim')
+ endif
+
+ let l:filename = fnamemodify(l:filename, ':p')
+ if filereadable(l:filename)
+ if !has_key(l:files, l:filename)
+ let l:files[l:filename] = reverse(readfile(l:filename))
+ endif
+
+ if l:name =~# '^\d\+$'
+ let l:lnum = 0
+ let l:output = vimtex#util#command('function {' . l:name . '}')
+ let l:text = substitute(
+ \ matchstr(l:output, '^\s*' . l:offset),
+ \ '^\d\+\s*', '', '')
+ else
+ let l:lnum = l:offset + len(l:files[l:filename])
+ \ - match(l:files[l:filename], '^\s*fu\%[nction]!\=\s\+' . l:name .'(')
+ let l:lnum_rev = len(l:files[l:filename]) - l:lnum
+ let l:text = substitute(l:files[l:filename][l:lnum_rev], '^\s*', '', '')
+ endif
+ else
+ let l:filename = ''
+ let l:lnum = 0
+ let l:text = ''
+ endif
+
+ call add(l:qflist, {
+ \ 'filename': l:filename,
+ \ 'function': l:name,
+ \ 'lnum': l:lnum,
+ \ 'text': len(l:qflist) == 0 ? l:exception : l:text,
+ \ 'nr': len(l:qflist),
+ \})
+ endfor
+
+ " Fill in empty filenames
+ let l:prev_filename = '_'
+ call reverse(l:qflist)
+ for l:entry in l:qflist
+ if empty(l:entry.filename)
+ let l:entry.filename = l:prev_filename
+ endif
+ let l:prev_filename = l:entry.filename
+ endfor
+ call reverse(l:qflist)
+
+ if a:0 > 0
+ call setqflist(l:qflist)
+ execute 'copen' len(l:qflist) + 2
+ wincmd p
+ endif
+
+ return l:qflist
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/delim.vim b/autoload/vimtex/delim.vim
new file mode 100644
index 00000000..1a9b965a
--- /dev/null
+++ b/autoload/vimtex/delim.vim
@@ -0,0 +1,1096 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#delim#init_buffer() abort " {{{1
+ nnoremap <silent><buffer> <plug>(vimtex-delim-toggle-modifier)
+ \ :<c-u>call <sid>operator_setup('toggle_modifier_next')
+ \ <bar> normal! <c-r>=v:count ? v:count : ''<cr>g@l<cr>
+
+ nnoremap <silent><buffer> <plug>(vimtex-delim-toggle-modifier-reverse)
+ \ :<c-u>call <sid>operator_setup('toggle_modifier_prev')
+ \ <bar> normal! <c-r>=v:count ? v:count : ''<cr>g@l<cr>
+
+ xnoremap <silent><buffer> <plug>(vimtex-delim-toggle-modifier)
+ \ :<c-u>call vimtex#delim#toggle_modifier_visual()<cr>
+
+ xnoremap <silent><buffer> <plug>(vimtex-delim-toggle-modifier-reverse)
+ \ :<c-u>call vimtex#delim#toggle_modifier_visual({'dir': -1})<cr>
+
+ nnoremap <silent><buffer> <plug>(vimtex-delim-change-math)
+ \ :<c-u>call <sid>operator_setup('change')<bar>normal! g@l<cr>
+
+ nnoremap <silent><buffer> <plug>(vimtex-delim-delete)
+ \ :<c-u>call <sid>operator_setup('delete')<bar>normal! g@l<cr>
+
+ inoremap <silent><buffer> <plug>(vimtex-delim-close)
+ \ <c-r>=vimtex#delim#close()<cr>
+endfunction
+
+" }}}1
+
+function! vimtex#delim#close() abort " {{{1
+ let l:save_pos = vimtex#pos#get_cursor()
+ let l:posval_cursor = vimtex#pos#val(l:save_pos)
+ let l:posval_current = l:posval_cursor
+ let l:posval_last = l:posval_cursor + 1
+
+ while l:posval_current < l:posval_last
+ let l:open = vimtex#delim#get_prev('all', 'open',
+ \ { 'syn_exclude' : 'texComment' })
+ if empty(l:open) || get(l:open, 'name', '') ==# 'document'
+ break
+ endif
+
+ let l:close = vimtex#delim#get_matching(l:open)
+ if empty(l:close.match)
+ call vimtex#pos#set_cursor(l:save_pos)
+ return l:open.corr
+ endif
+
+ let l:posval_last = l:posval_current
+ let l:posval_current = vimtex#pos#val(l:open)
+ let l:posval_try = vimtex#pos#val(l:close) + strlen(l:close.match)
+ if l:posval_current != l:posval_cursor
+ \ && l:posval_try > l:posval_cursor
+ call vimtex#pos#set_cursor(l:save_pos)
+ return l:open.corr
+ endif
+
+ call vimtex#pos#set_cursor(vimtex#pos#prev(l:open))
+ endwhile
+
+ call vimtex#pos#set_cursor(l:save_pos)
+ return ''
+endfunction
+
+" }}}1
+function! vimtex#delim#toggle_modifier(...) abort " {{{1
+ let l:args = a:0 > 0 ? a:1 : {}
+ call extend(l:args, {
+ \ 'count': v:count1,
+ \ 'dir': 1,
+ \ 'repeat': 1,
+ \ 'openclose': [],
+ \ }, 'keep')
+
+ let [l:open, l:close] = !empty(l:args.openclose)
+ \ ? l:args.openclose
+ \ : vimtex#delim#get_surrounding('delim_modq_math')
+ if empty(l:open) | return | endif
+
+ let l:direction = l:args.dir < 0 ? -l:args.count : l:args.count
+
+ let newmods = ['', '']
+ let modlist = [['', '']] + get(g:, 'vimtex_delim_toggle_mod_list',
+ \ [['\left', '\right']])
+ let n = len(modlist)
+ for i in range(n)
+ let j = (i + l:direction) % n
+ if l:open.mod ==# modlist[i][0]
+ let newmods = modlist[j]
+ break
+ endif
+ endfor
+
+ let line = getline(l:open.lnum)
+ let line = strpart(line, 0, l:open.cnum - 1)
+ \ . newmods[0]
+ \ . strpart(line, l:open.cnum + len(l:open.mod) - 1)
+ call setline(l:open.lnum, line)
+
+ let l:cnum = l:close.cnum
+ if l:open.lnum == l:close.lnum
+ let n = len(newmods[0]) - len(l:open.mod)
+ let l:cnum += n
+ let pos = vimtex#pos#get_cursor()
+ if pos[2] > l:open.cnum + len(l:open.mod)
+ let pos[2] += n
+ call vimtex#pos#set_cursor(pos)
+ endif
+ endif
+
+ let line = getline(l:close.lnum)
+ let line = strpart(line, 0, l:cnum - 1)
+ \ . newmods[1]
+ \ . strpart(line, l:cnum + len(l:close.mod) - 1)
+ call setline(l:close.lnum, line)
+
+ return newmods
+endfunction
+
+" }}}1
+function! vimtex#delim#toggle_modifier_visual(...) abort " {{{1
+ let l:args = a:0 > 0 ? a:1 : {}
+ call extend(l:args, {
+ \ 'count': v:count1,
+ \ 'dir': 1,
+ \ 'reselect': 1,
+ \ }, 'keep')
+
+ let l:save_pos = vimtex#pos#get_cursor()
+ let l:start_pos = getpos("'<")
+ let l:end_pos = getpos("'>")
+ let l:end_pos_val = vimtex#pos#val(l:end_pos) + 1000
+ let l:cur_pos = l:start_pos
+
+ "
+ " Check if selection is swapped
+ "
+ let l:end_pos[1] += 1
+ call setpos("'>", l:end_pos)
+ let l:end_pos[1] -= 1
+ normal! gv
+ let l:swapped = l:start_pos != getpos("'<")
+
+ "
+ " First we generate a stack of all delimiters that should be toggled
+ "
+ let l:stack = []
+ while vimtex#pos#val(l:cur_pos) < l:end_pos_val
+ call vimtex#pos#set_cursor(l:cur_pos)
+ let l:open = vimtex#delim#get_next('delim_modq_math', 'open')
+ if empty(l:open) | break | endif
+
+ if vimtex#pos#val(l:open) >= l:end_pos_val
+ break
+ endif
+
+ let l:close = vimtex#delim#get_matching(l:open)
+ if !empty(get(l:close, 'match'))
+
+ if l:end_pos_val >= vimtex#pos#val(l:close) + strlen(l:close.match) - 1
+ let l:newmods = vimtex#delim#toggle_modifier({
+ \ 'repeat': 0,
+ \ 'count': l:args.count,
+ \ 'dir': l:args.dir,
+ \ 'openclose': [l:open, l:close],
+ \ })
+
+ let l:col_diff = (l:open.lnum == l:end_pos[1])
+ \ ? strlen(newmods[0]) - strlen(l:open.mod) : 0
+ let l:col_diff += (l:close.lnum == l:end_pos[1])
+ \ ? strlen(newmods[1]) - strlen(l:close.mod) : 0
+
+ if l:col_diff != 0
+ let l:end_pos[2] += l:col_diff
+ let l:end_pos_val += l:col_diff
+ endif
+ endif
+ endif
+
+ let l:cur_pos = vimtex#pos#next(l:open)
+ endwhile
+
+ "
+ " Finally we return to original position and reselect the region
+ "
+ call setpos(l:swapped? "'>" : "'<", l:start_pos)
+ call setpos(l:swapped? "'<" : "'>", l:end_pos)
+ call vimtex#pos#set_cursor(l:save_pos)
+ if l:args.reselect
+ normal! gv
+ endif
+endfunction
+
+" }}}1
+
+function! vimtex#delim#change(...) abort " {{{1
+ let [l:open, l:close] = vimtex#delim#get_surrounding('delim_math')
+ if empty(l:open) | return | endif
+
+ if a:0 > 0
+ let l:new_delim = a:1
+ else
+ let l:name = get(l:open, 'name', l:open.is_open
+ \ ? l:open.match . ' ... ' . l:open.corr
+ \ : l:open.match . ' ... ' . l:open.corr)
+
+ let l:new_delim = vimtex#echo#input({
+ \ 'info' :
+ \ ['Change surrounding delimiter: ', ['VimtexWarning', l:name]],
+ \ 'complete' : 'customlist,vimtex#delim#change_input_complete',
+ \})
+ endif
+
+ if empty(l:new_delim) | return | endif
+ call vimtex#delim#change_with_args(l:open, l:close, l:new_delim)
+endfunction
+
+" }}}1
+function! vimtex#delim#change_with_args(open, close, new) abort " {{{1
+ "
+ " Set target environment
+ "
+ if a:new ==# ''
+ let [l:beg, l:end] = ['', '']
+ elseif index(['{', '}'], a:new) >= 0
+ let [l:beg, l:end] = ['{', '}']
+ else
+ let l:side = a:new =~# g:vimtex#delim#re.delim_math.close
+ let l:index = index(map(copy(g:vimtex#delim#lists.delim_math.name),
+ \ 'v:val[' . l:side . ']'),
+ \ a:new)
+ if l:index >= 0
+ let [l:beg, l:end] = g:vimtex#delim#lists.delim_math.name[l:index]
+ else
+ let [l:beg, l:end] = [a:new, a:new]
+ endif
+ endif
+
+ let l:line = getline(a:open.lnum)
+ call setline(a:open.lnum,
+ \ strpart(l:line, 0, a:open.cnum-1)
+ \ . l:beg
+ \ . strpart(l:line, a:open.cnum + len(a:open.match) - 1))
+
+ let l:c1 = a:close.cnum
+ let l:c2 = a:close.cnum + len(a:close.match) - 1
+ if a:open.lnum == a:close.lnum
+ let n = len(l:beg) - len(a:open.match)
+ let l:c1 += n
+ let l:c2 += n
+ let pos = vimtex#pos#get_cursor()
+ if pos[2] > a:open.cnum + len(a:open.match) - 1
+ let pos[2] += n
+ call vimtex#pos#set_cursor(pos)
+ endif
+ endif
+
+ let l:line = getline(a:close.lnum)
+ call setline(a:close.lnum,
+ \ strpart(l:line, 0, l:c1-1) . l:end . strpart(l:line, l:c2))
+endfunction
+
+" }}}1
+function! vimtex#delim#change_input_complete(lead, cmdline, pos) abort " {{{1
+ let l:all = deepcopy(g:vimtex#delim#lists.delim_all.name)
+ let l:open = map(copy(l:all), 'v:val[0]')
+ let l:close = map(copy(l:all), 'v:val[1]')
+
+ let l:lead_re = escape(a:lead, '\$[]')
+ return filter(l:open + l:close, 'v:val =~# ''^' . l:lead_re . '''')
+endfunction
+
+" }}}1
+function! vimtex#delim#delete() abort " {{{1
+ let [l:open, l:close] = vimtex#delim#get_surrounding('delim_modq_math')
+ if empty(l:open) | return | endif
+
+ call vimtex#delim#change_with_args(l:open, l:close, '')
+endfunction
+
+" }}}1
+
+function! vimtex#delim#get_next(type, side, ...) abort " {{{1
+ return s:get_delim(extend({
+ \ 'direction' : 'next',
+ \ 'type' : a:type,
+ \ 'side' : a:side,
+ \}, get(a:, '1', {})))
+endfunction
+
+" }}}1
+function! vimtex#delim#get_prev(type, side, ...) abort " {{{1
+ return s:get_delim(extend({
+ \ 'direction' : 'prev',
+ \ 'type' : a:type,
+ \ 'side' : a:side,
+ \}, get(a:, '1', {})))
+endfunction
+
+" }}}1
+function! vimtex#delim#get_current(type, side, ...) abort " {{{1
+ return s:get_delim(extend({
+ \ 'direction' : 'current',
+ \ 'type' : a:type,
+ \ 'side' : a:side,
+ \}, get(a:, '1', {})))
+endfunction
+
+" }}}1
+function! vimtex#delim#get_matching(delim) abort " {{{1
+ if empty(a:delim) || !has_key(a:delim, 'lnum') | return {} | endif
+
+ "
+ " Get the matching position
+ "
+ let l:save_pos = vimtex#pos#get_cursor()
+ call vimtex#pos#set_cursor(a:delim)
+ let [l:match, l:lnum, l:cnum] = a:delim.get_matching()
+ call vimtex#pos#set_cursor(l:save_pos)
+
+ "
+ " Create the match result
+ "
+ let l:matching = deepcopy(a:delim)
+ let l:matching.lnum = l:lnum
+ let l:matching.cnum = l:cnum
+ let l:matching.match = l:match
+ let l:matching.corr = a:delim.match
+ let l:matching.side = a:delim.is_open ? 'close' : 'open'
+ let l:matching.is_open = !a:delim.is_open
+ let l:matching.re.corr = a:delim.re.this
+ let l:matching.re.this = a:delim.re.corr
+
+ if l:matching.type ==# 'delim'
+ let l:matching.corr_delim = a:delim.delim
+ let l:matching.corr_mod = a:delim.mod
+ let l:matching.delim = a:delim.corr_delim
+ let l:matching.mod = a:delim.corr_mod
+ elseif l:matching.type ==# 'env' && has_key(l:matching, 'name')
+ if l:matching.is_open
+ let l:matching.env_cmd = vimtex#cmd#get_at(l:lnum, l:cnum)
+ else
+ unlet l:matching.env_cmd
+ endif
+ endif
+
+ return l:matching
+endfunction
+
+" }}}1
+function! vimtex#delim#get_surrounding(type) abort " {{{1
+ let l:save_pos = vimtex#pos#get_cursor()
+ let l:pos_val_cursor = vimtex#pos#val(l:save_pos)
+ let l:pos_val_last = l:pos_val_cursor
+ let l:pos_val_open = l:pos_val_cursor - 1
+
+ while l:pos_val_open < l:pos_val_last
+ let l:open = vimtex#delim#get_prev(a:type, 'open')
+ if empty(l:open) | break | endif
+
+ let l:close = vimtex#delim#get_matching(l:open)
+ let l:pos_val_try = vimtex#pos#val(l:close) + strlen(l:close.match) - 1
+ if l:pos_val_try >= l:pos_val_cursor
+ call vimtex#pos#set_cursor(l:save_pos)
+ return [l:open, l:close]
+ else
+ call vimtex#pos#set_cursor(vimtex#pos#prev(l:open))
+ let l:pos_val_last = l:pos_val_open
+ let l:pos_val_open = vimtex#pos#val(l:open)
+ endif
+ endwhile
+
+ call vimtex#pos#set_cursor(l:save_pos)
+ return [{}, {}]
+endfunction
+
+" }}}1
+
+function! s:operator_setup(operator) abort " {{{1
+ let &opfunc = s:snr() . 'operator_function'
+
+ let s:operator = a:operator
+
+ " Ask for user input if necessary/relevant
+ if s:operator ==# 'change'
+ let [l:open, l:close] = vimtex#delim#get_surrounding('delim_math')
+ if empty(l:open) | return | endif
+
+ let l:name = get(l:open, 'name', l:open.is_open
+ \ ? l:open.match . ' ... ' . l:open.corr
+ \ : l:open.match . ' ... ' . l:open.corr)
+
+ let s:operator_delim = vimtex#echo#input({
+ \ 'info' :
+ \ ['Change surrounding delimiter: ', ['VimtexWarning', l:name]],
+ \ 'complete' : 'customlist,vimtex#delim#change_input_complete',
+ \})
+ endif
+endfunction
+
+" }}}1
+function! s:operator_function(_) abort " {{{1
+ let l:delim = get(s:, 'operator_delim', '')
+
+ execute 'call vimtex#delim#' . {
+ \ 'change': 'change(l:delim)',
+ \ 'delete': 'delete()',
+ \ 'toggle_modifier_next': 'toggle_modifier()',
+ \ 'toggle_modifier_prev': "toggle_modifier({'dir': -1})",
+ \}[s:operator]
+endfunction
+
+" }}}1
+function! s:snr() abort " {{{1
+ return matchstr(expand('<sfile>'), '<SNR>\d\+_')
+endfunction
+
+" }}}1
+"
+
+function! s:get_delim(opts) abort " {{{1
+ "
+ " Arguments:
+ " opts = {
+ " 'direction' : next
+ " prev
+ " current
+ " 'type' : env_tex
+ " env_math
+ " env_all
+ " delim_tex
+ " delim_math
+ " delim_modq_math (possibly modified math delimiter)
+ " delim_mod_math (modified math delimiter)
+ " delim_all
+ " all
+ " 'side' : open
+ " close
+ " both
+ " 'syn_exclude' : Don't match in given syntax
+ " }
+ "
+ " Returns:
+ " delim = {
+ " type : env | delim
+ " side : open | close
+ " name : name of environment [only for type env]
+ " lnum : number
+ " cnum : number
+ " match : unparsed matched delimiter
+ " corr : corresponding delimiter
+ " re : {
+ " open : regexp for the opening part
+ " close : regexp for the closing part
+ " }
+ " remove : method to remove the delimiter
+ " }
+ "
+ let l:save_pos = vimtex#pos#get_cursor()
+ let l:re = g:vimtex#delim#re[a:opts.type][a:opts.side]
+ while 1
+ let [l:lnum, l:cnum] = a:opts.direction ==# 'next'
+ \ ? searchpos(l:re, 'cnW', line('.') + g:vimtex_delim_stopline)
+ \ : a:opts.direction ==# 'prev'
+ \ ? searchpos(l:re, 'bcnW', max([line('.') - g:vimtex_delim_stopline, 1]))
+ \ : searchpos(l:re, 'bcnW', line('.'))
+ if l:lnum == 0 | break | endif
+
+ if has_key(a:opts, 'syn_exclude')
+ \ && vimtex#util#in_syntax(a:opts.syn_exclude, l:lnum, l:cnum)
+ call vimtex#pos#set_cursor(vimtex#pos#prev(l:lnum, l:cnum))
+ continue
+ endif
+
+ break
+ endwhile
+ call vimtex#pos#set_cursor(l:save_pos)
+
+ let l:match = matchstr(getline(l:lnum), '^' . l:re, l:cnum-1)
+
+ if a:opts.direction ==# 'current'
+ \ && l:cnum + strlen(l:match) + (mode() ==# 'i' ? 1 : 0) <= col('.')
+ let l:match = ''
+ let l:lnum = 0
+ let l:cnum = 0
+ endif
+
+ let l:result = {
+ \ 'type' : '',
+ \ 'lnum' : l:lnum,
+ \ 'cnum' : l:cnum,
+ \ 'match' : l:match,
+ \ 'remove' : function('s:delim_remove'),
+ \}
+
+ for l:type in s:types
+ if l:match =~# '^' . l:type.re
+ let l:result = extend(
+ \ l:type.parser(l:match, l:lnum, l:cnum,
+ \ a:opts.side, a:opts.type, a:opts.direction),
+ \ l:result, 'keep')
+ break
+ endif
+ endfor
+
+ return empty(l:result.type) ? {} : l:result
+endfunction
+
+" }}}1
+
+function! s:delim_remove() dict abort " {{{1
+ let l:line = getline(self.lnum)
+ let l:l1 = strpart(l:line, 0, self.cnum-1)
+ let l:l2 = strpart(l:line, self.cnum + strlen(self.match) - 1)
+
+ if self.side ==# 'close'
+ let l:l1 = substitute(l:l1, '\s\+$', '', '')
+ if empty(l:l1)
+ let l:l2 = substitute(l:l2, '^\s\+', '', '')
+ endif
+ else
+ let l:l2 = substitute(l:l2, '^\s\+', '', '')
+ if empty(l:l2)
+ let l:l1 = substitute(l:l1, '\s\+$', '', '')
+ endif
+ endif
+
+ call setline(self.lnum, l:l1 . l:l2)
+endfunction
+
+" }}}1
+
+function! s:parser_env(match, lnum, cnum, ...) abort " {{{1
+ let result = {}
+
+ let result.type = 'env'
+ let result.name = matchstr(a:match, '{\zs\k*\ze\*\?}')
+ let result.starred = match(a:match, '\*}$') > 0
+ let result.side = a:match =~# '\\begin' ? 'open' : 'close'
+ let result.is_open = result.side ==# 'open'
+ let result.get_matching = function('s:get_matching_env')
+
+ let result.gms_flags = result.is_open ? 'nW' : 'bnW'
+ let result.gms_stopline = result.is_open
+ \ ? line('.') + g:vimtex_delim_stopline
+ \ : max([1, line('.') - g:vimtex_delim_stopline])
+
+ if result.is_open
+ let result.env_cmd = vimtex#cmd#get_at(a:lnum, a:cnum)
+ endif
+
+ let result.corr = result.is_open
+ \ ? substitute(a:match, 'begin', 'end', '')
+ \ : substitute(a:match, 'end', 'begin', '')
+
+ let result.re = {
+ \ 'open' : '\m\\begin\s*{' . result.name . '\*\?}',
+ \ 'close' : '\m\\end\s*{' . result.name . '\*\?}',
+ \}
+
+ let result.re.this = result.is_open ? result.re.open : result.re.close
+ let result.re.corr = result.is_open ? result.re.close : result.re.open
+
+ return result
+endfunction
+
+" }}}1
+function! s:parser_tex(match, lnum, cnum, side, type, direction) abort " {{{1
+ "
+ " TeX shorthand are these
+ "
+ " $ ... $ (inline math)
+ " $$ ... $$ (displayed equations)
+ "
+ " The notation does not provide the delimiter side directly, which provides
+ " a slight problem. However, we can utilize the syntax information to parse
+ " the side.
+ "
+ let result = {}
+ let result.type = 'env'
+ let result.corr = a:match
+ let result.re = {
+ \ 'this' : '\m' . escape(a:match, '$'),
+ \ 'corr' : '\m' . escape(a:match, '$'),
+ \ 'open' : '\m' . escape(a:match, '$'),
+ \ 'close' : '\m' . escape(a:match, '$'),
+ \}
+ let result.side = vimtex#util#in_syntax(
+ \ (a:match ==# '$' ? 'texMathZoneX' : 'texMathZoneY'),
+ \ a:lnum, a:cnum+1)
+ \ ? 'open' : 'close'
+ let result.is_open = result.side ==# 'open'
+ let result.get_matching = function('s:get_matching_tex')
+ let result.gms_flags = result.is_open ? 'nW' : 'bnW'
+ let result.gms_stopline = result.is_open
+ \ ? line('.') + g:vimtex_delim_stopline
+ \ : max([1, line('.') - g:vimtex_delim_stopline])
+
+ if (a:side !=# 'both') && (a:side !=# result.side)
+ "
+ " The current match ($ or $$) is not the correct side, so we must
+ " continue the search recursively. We do this by changing the cursor
+ " position, since the function searchpos relies on the current cursor
+ " position.
+ "
+ let l:save_pos = vimtex#pos#get_cursor()
+
+ " Move the cursor
+ call vimtex#pos#set_cursor(a:direction ==# 'next'
+ \ ? vimtex#pos#next(a:lnum, a:cnum)
+ \ : vimtex#pos#prev(a:lnum, a:cnum))
+
+ " Get new result
+ let result = s:get_delim({
+ \ 'direction' : a:direction,
+ \ 'type' : a:type,
+ \ 'side' : a:side,
+ \})
+
+ " Restore the cursor
+ call vimtex#pos#set_cursor(l:save_pos)
+ endif
+
+ return result
+endfunction
+
+" }}}1
+function! s:parser_latex(match, lnum, cnum, ...) abort " {{{1
+ let result = {}
+
+ let result.type = 'env'
+ let result.side = a:match =~# '\\(\|\\\[' ? 'open' : 'close'
+ let result.is_open = result.side ==# 'open'
+ let result.get_matching = function('s:get_matching_latex')
+ let result.gms_flags = result.is_open ? 'nW' : 'bnW'
+ let result.gms_stopline = result.is_open
+ \ ? line('.') + g:vimtex_delim_stopline
+ \ : max([1, line('.') - g:vimtex_delim_stopline])
+
+ let result.corr = result.is_open
+ \ ? substitute(substitute(a:match, '\[', ']', ''), '(', ')', '')
+ \ : substitute(substitute(a:match, '\]', '[', ''), ')', '(', '')
+
+ let result.re = {
+ \ 'open' : g:vimtex#re#not_bslash
+ \ . (a:match =~# '\\(\|\\)' ? '\m\\(' : '\m\\\['),
+ \ 'close' : g:vimtex#re#not_bslash
+ \ . (a:match =~# '\\(\|\\)' ? '\m\\)' : '\m\\\]'),
+ \}
+
+ let result.re.this = result.is_open ? result.re.open : result.re.close
+ let result.re.corr = result.is_open ? result.re.close : result.re.open
+
+ return result
+endfunction
+
+" }}}1
+function! s:parser_delim(match, lnum, cnum, ...) abort " {{{1
+ let result = {}
+ let result.type = 'delim'
+ let result.side =
+ \ a:match =~# g:vimtex#delim#re.delim_all.open ? 'open' : 'close'
+ let result.is_open = result.side ==# 'open'
+ let result.get_matching = function('s:get_matching_delim')
+ let result.gms_flags = result.is_open ? 'nW' : 'bnW'
+ let result.gms_stopline = result.is_open
+ \ ? line('.') + g:vimtex_delim_stopline
+ \ : max([1, line('.') - g:vimtex_delim_stopline])
+
+ "
+ " Find corresponding delimiter and the regexps
+ "
+ if a:match =~# '^' . g:vimtex#delim#re.mods.both
+ let m1 = matchstr(a:match, '^' . g:vimtex#delim#re.mods.both)
+ let d1 = substitute(strpart(a:match, len(m1)), '^\s*', '', '')
+ let s1 = !result.is_open
+ let re1 = s:parser_delim_get_regexp(m1, s1, 'mods')
+ \ . '\s*' . s:parser_delim_get_regexp(d1, s1, 'delim_math')
+
+ let m2 = s:parser_delim_get_corr(m1, 'mods')
+ let d2 = s:parser_delim_get_corr(d1, 'delim_math')
+ let s2 = result.is_open
+ let re2 = s:parser_delim_get_regexp(m2, s2, 'mods') . '\s*'
+ \ . (m1 =~# '\\\%(left\|right\)'
+ \ ? '\%(' . s:parser_delim_get_regexp(d2, s2, 'delim_math') . '\|\.\)'
+ \ : s:parser_delim_get_regexp(d2, s2, 'delim_math'))
+ else
+ let d1 = a:match
+ let m1 = ''
+ let re1 = s:parser_delim_get_regexp(a:match, !result.is_open)
+
+ let d2 = s:parser_delim_get_corr(a:match)
+ let m2 = ''
+ let re2 = s:parser_delim_get_regexp(d2, result.is_open)
+ endif
+
+ let result.delim = d1
+ let result.mod = m1
+ let result.corr = m2 . d2
+ let result.corr_delim = d2
+ let result.corr_mod = m2
+ let result.re = {
+ \ 'this' : re1,
+ \ 'corr' : re2,
+ \ 'open' : result.is_open ? re1 : re2,
+ \ 'close' : result.is_open ? re2 : re1,
+ \}
+
+ return result
+endfunction
+
+" }}}1
+function! s:parser_delim_unmatched(match, lnum, cnum, ...) abort " {{{1
+ let result = {}
+ let result.type = 'delim'
+ let result.side =
+ \ a:match =~# g:vimtex#delim#re.delim_all.open ? 'open' : 'close'
+ let result.is_open = result.side ==# 'open'
+ let result.get_matching = function('s:get_matching_delim_unmatched')
+ let result.gms_flags = result.is_open ? 'nW' : 'bnW'
+ let result.gms_stopline = result.is_open
+ \ ? line('.') + g:vimtex_delim_stopline
+ \ : max([1, line('.') - g:vimtex_delim_stopline])
+ let result.delim = '.'
+ let result.corr_delim = '.'
+
+ "
+ " Find corresponding delimiter and the regexps
+ "
+ if result.is_open
+ let result.mod = '\left'
+ let result.corr_mod = '\right'
+ let result.corr = '\right.'
+ let re1 = '\\left\s*\.'
+ let re2 = s:parser_delim_get_regexp('\right', 1, 'mods')
+ \ . '\s*' . s:parser_delim_get_regexp('.', 0)
+ else
+ let result.mod = '\right'
+ let result.corr_mod = '\left'
+ let result.corr = '\left.'
+ let re1 = '\\right\s*\.'
+ let re2 = s:parser_delim_get_regexp('\left', 0, 'mods')
+ \ . '\s*' . s:parser_delim_get_regexp('.', 0)
+ endif
+
+ let result.re = {
+ \ 'this' : re1,
+ \ 'corr' : re2,
+ \ 'open' : result.is_open ? re1 : re2,
+ \ 'close' : result.is_open ? re2 : re1,
+ \}
+
+ return result
+endfunction
+
+" }}}1
+function! s:parser_delim_get_regexp(delim, side, ...) abort " {{{1
+ let l:type = a:0 > 0 ? a:1 : 'delim_all'
+
+ " First check for unmatched math delimiter
+ if a:delim ==# '.'
+ return g:vimtex#delim#re.delim_math[a:side ? 'open' : 'close']
+ endif
+
+ " Next check normal delimiters
+ let l:index = index(map(copy(g:vimtex#delim#lists[l:type].name),
+ \ 'v:val[' . a:side . ']'),
+ \ a:delim)
+ return l:index >= 0
+ \ ? g:vimtex#delim#lists[l:type].re[l:index][a:side]
+ \ : ''
+endfunction
+
+" }}}1
+function! s:parser_delim_get_corr(delim, ...) abort " {{{1
+ let l:type = a:0 > 0 ? a:1 : 'delim_all'
+
+ for l:pair in g:vimtex#delim#lists[l:type].name
+ if a:delim ==# l:pair[0]
+ return l:pair[1]
+ elseif a:delim ==# l:pair[1]
+ return l:pair[0]
+ endif
+ endfor
+endfunction
+
+" }}}1
+
+function! s:get_matching_env() dict abort " {{{1
+ try
+ let [lnum, cnum] = searchpairpos(self.re.open, '', self.re.close,
+ \ self.gms_flags, '', 0, s:get_timeout())
+ catch /E118/
+ let [lnum, cnum] = searchpairpos(self.re.open, '', self.re.close,
+ \ self.gms_flags, '', self.gms_stopline)
+ endtry
+
+ let match = matchstr(getline(lnum), '^' . self.re.corr, cnum-1)
+ return [match, lnum, cnum]
+endfunction
+
+" }}}1
+function! s:get_matching_tex() dict abort " {{{1
+ let [lnum, cnum] = searchpos(self.re.corr, self.gms_flags, self.gms_stopline)
+
+ let match = matchstr(getline(lnum), '^' . self.re.corr, cnum-1)
+ return [match, lnum, cnum]
+endfunction
+
+" }}}1
+function! s:get_matching_latex() dict abort " {{{1
+ let [lnum, cnum] = searchpos(self.re.corr, self.gms_flags, self.gms_stopline)
+
+ let match = matchstr(getline(lnum), '^' . self.re.corr, cnum-1)
+ return [match, lnum, cnum]
+endfunction
+
+" }}}1
+function! s:get_matching_delim() dict abort " {{{1
+ try
+ let [lnum, cnum] = searchpairpos(self.re.open, '', self.re.close,
+ \ self.gms_flags,
+ \ 'synIDattr(synID(line("."), col("."), 0), "name") =~? "comment"',
+ \ 0, s:get_timeout())
+ catch /E118/
+ let [lnum, cnum] = searchpairpos(self.re.open, '', self.re.close,
+ \ self.gms_flags,
+ \ 'synIDattr(synID(line("."), col("."), 0), "name") =~? "comment"',
+ \ self.gms_stopline)
+ endtry
+
+ let match = matchstr(getline(lnum), '^' . self.re.corr, cnum-1)
+ return [match, lnum, cnum]
+endfunction
+
+" }}}1
+function! s:get_matching_delim_unmatched() dict abort " {{{1
+ let tries = 0
+ let misses = []
+ while 1
+ try
+ let [lnum, cnum] = searchpairpos(self.re.open, '', self.re.close,
+ \ self.gms_flags,
+ \ 'index(misses, [line("."), col(".")]) >= 0',
+ \ 0, s:get_timeout())
+ catch /E118/
+ let [lnum, cnum] = searchpairpos(self.re.open, '', self.re.close,
+ \ self.gms_flags,
+ \ 'index(misses, [line("."), col(".")]) >= 0',
+ \ self.gms_stopline)
+ endtry
+ let match = matchstr(getline(lnum), '^' . self.re.corr, cnum-1)
+ if lnum == 0 | break | endif
+
+ let cand = vimtex#delim#get_matching(extend({
+ \ 'type' : '',
+ \ 'lnum' : lnum,
+ \ 'cnum' : cnum,
+ \ 'match' : match,
+ \}, s:parser_delim(match, lnum, cnum)))
+
+ if !empty(cand) && [self.lnum, self.cnum] == [cand.lnum, cand.cnum]
+ return [match, lnum, cnum]
+ else
+ let misses += [[lnum, cnum]]
+ let tries += 1
+ if tries == 10 | break | endif
+ endif
+ endwhile
+
+ return ['', 0, 0]
+endfunction
+
+" }}}1
+
+function! s:get_timeout() abort " {{{1
+ return (empty(v:insertmode) ? mode() : v:insertmode) ==# 'i'
+ \ ? g:vimtex_delim_insert_timeout
+ \ : g:vimtex_delim_timeout
+endfunction
+
+" }}}1
+
+
+function! s:init_delim_lists() abort " {{{1
+ " Define the default value
+ let l:lists = {
+ \ 'env_tex' : {
+ \ 'name' : [['begin', 'end']],
+ \ 're' : [['\\begin\s*{[^}]*}', '\\end\s*{[^}]*}']],
+ \ },
+ \ 'env_math' : {
+ \ 'name' : [
+ \ ['\(', '\)'],
+ \ ['\[', '\]'],
+ \ ['$$', '$$'],
+ \ ['$', '$'],
+ \ ],
+ \ 're' : [
+ \ ['\\(', '\\)'],
+ \ ['\\\@<!\\\[', '\\\]'],
+ \ ['\$\$', '\$\$'],
+ \ ['\$', '\$'],
+ \ ],
+ \ },
+ \ 'delim_tex' : {
+ \ 'name' : [
+ \ ['[', ']'],
+ \ ['{', '}'],
+ \ ],
+ \ 're' : [
+ \ ['\[', '\]'],
+ \ ['\\\@<!{', '\\\@<!}'],
+ \ ]
+ \ },
+ \ 'delim_math' : {
+ \ 'name' : [
+ \ ['(', ')'],
+ \ ['[', ']'],
+ \ ['\{', '\}'],
+ \ ['\langle', '\rangle'],
+ \ ['\lbrace', '\rbrace'],
+ \ ['\lvert', '\rvert'],
+ \ ['\lVert', '\rVert'],
+ \ ['\lfloor', '\rfloor'],
+ \ ['\lceil', '\rceil'],
+ \ ['\ulcorner', '\urcorner'],
+ \ ]
+ \ },
+ \ 'mods' : {
+ \ 'name' : [
+ \ ['\left', '\right'],
+ \ ['\bigl', '\bigr'],
+ \ ['\Bigl', '\Bigr'],
+ \ ['\biggl', '\biggr'],
+ \ ['\Biggl', '\Biggr'],
+ \ ['\big', '\big'],
+ \ ['\Big', '\Big'],
+ \ ['\bigg', '\bigg'],
+ \ ['\Bigg', '\Bigg'],
+ \ ],
+ \ 're' : [
+ \ ['\\left', '\\right'],
+ \ ['\\bigl', '\\bigr'],
+ \ ['\\Bigl', '\\Bigr'],
+ \ ['\\biggl', '\\biggr'],
+ \ ['\\Biggl', '\\Biggr'],
+ \ ['\\big\>', '\\big\>'],
+ \ ['\\Big\>', '\\Big\>'],
+ \ ['\\bigg\>', '\\bigg\>'],
+ \ ['\\Bigg\>', '\\Bigg\>'],
+ \ ]
+ \ },
+ \}
+
+ " Get user defined lists
+ call extend(l:lists, get(g:, 'vimtex_delim_list', {}))
+
+ " Generate corresponding regexes if necessary
+ for l:type in values(l:lists)
+ if !has_key(l:type, 're') && has_key(l:type, 'name')
+ let l:type.re = map(deepcopy(l:type.name),
+ \ 'map(v:val, ''escape(v:val, ''''\$[]'''')'')')
+ endif
+ endfor
+
+ " Generate combined lists
+ let l:lists.env_all = {}
+ let l:lists.delim_all = {}
+ let l:lists.all = {}
+ for k in ['name', 're']
+ let l:lists.env_all[k] = l:lists.env_tex[k] + l:lists.env_math[k]
+ let l:lists.delim_all[k] = l:lists.delim_math[k] + l:lists.delim_tex[k]
+ let l:lists.all[k] = l:lists.env_all[k] + l:lists.delim_all[k]
+ endfor
+
+ return l:lists
+endfunction
+
+" }}}1
+function! s:init_delim_regexes() abort " {{{1
+ let l:re = {}
+ let l:re.env_all = {}
+ let l:re.delim_all = {}
+ let l:re.all = {}
+
+ let l:re.env_tex = s:init_delim_regexes_generator('env_tex')
+ let l:re.env_math = s:init_delim_regexes_generator('env_math')
+ let l:re.delim_tex = s:init_delim_regexes_generator('delim_tex')
+ let l:re.delim_math = s:init_delim_regexes_generator('delim_math')
+ let l:re.mods = s:init_delim_regexes_generator('mods')
+
+ let l:o = join(map(copy(g:vimtex#delim#lists.delim_math.re), 'v:val[0]'), '\|')
+ let l:c = join(map(copy(g:vimtex#delim#lists.delim_math.re), 'v:val[1]'), '\|')
+
+ "
+ " Matches modified math delimiters
+ "
+ let l:re.delim_mod_math = {
+ \ 'open' : '\%(\%(' . l:re.mods.open . '\)\)\s*\\\@<!\%('
+ \ . l:o . '\)\|\\left\s*\.',
+ \ 'close' : '\%(\%(' . l:re.mods.close . '\)\)\s*\\\@<!\%('
+ \ . l:c . '\)\|\\right\s*\.',
+ \ 'both' : '\%(\%(' . l:re.mods.both . '\)\)\s*\\\@<!\%('
+ \ . l:o . '\|' . l:c . '\)\|\\\%(left\|right\)\s*\.',
+ \}
+
+ "
+ " Matches possibly modified math delimiters
+ "
+ let l:re.delim_modq_math = {
+ \ 'open' : '\%(\%(' . l:re.mods.open . '\)\s*\)\?\\\@<!\%('
+ \ . l:o . '\)\|\\left\s*\.',
+ \ 'close' : '\%(\%(' . l:re.mods.close . '\)\s*\)\?\\\@<!\%('
+ \ . l:c . '\)\|\\right\s*\.',
+ \ 'both' : '\%(\%(' . l:re.mods.both . '\)\s*\)\?\\\@<!\%('
+ \ . l:o . '\|' . l:c . '\)\|\\\%(left\|right\)\s*\.',
+ \}
+
+ for k in ['open', 'close', 'both']
+ let l:re.env_all[k] = l:re.env_tex[k] . '\|' . l:re.env_math[k]
+ let l:re.delim_all[k] = l:re.delim_modq_math[k] . '\|' . l:re.delim_tex[k]
+ let l:re.all[k] = l:re.env_all[k] . '\|' . l:re.delim_all[k]
+ endfor
+
+ "
+ " Be explicit about regex mode (set magic mode)
+ "
+ for l:type in values(l:re)
+ for l:side in ['open', 'close', 'both']
+ let l:type[l:side] = '\m' . l:type[l:side]
+ endfor
+ endfor
+
+ return l:re
+endfunction
+
+" }}}1
+function! s:init_delim_regexes_generator(list_name) abort " {{{1
+ let l:list = g:vimtex#delim#lists[a:list_name]
+ let l:open = join(map(copy(l:list.re), 'v:val[0]'), '\|')
+ let l:close = join(map(copy(l:list.re), 'v:val[1]'), '\|')
+
+ return {
+ \ 'open' : '\\\@<!\%(' . l:open . '\)',
+ \ 'close' : '\\\@<!\%(' . l:close . '\)',
+ \ 'both' : '\\\@<!\%(' . l:open . '\|' . l:close . '\)'
+ \}
+endfunction
+
+ " }}}1
+
+
+" {{{1 Initialize module
+
+"
+" Initialize lists of delimiter pairs and regexes
+"
+let g:vimtex#delim#lists = s:init_delim_lists()
+let g:vimtex#delim#re = s:init_delim_regexes()
+
+"
+" Initialize script variables
+"
+let s:types = [
+ \ {
+ \ 're' : '\\\%(begin\|end\)\>',
+ \ 'parser' : function('s:parser_env'),
+ \ },
+ \ {
+ \ 're' : '\$\$\?',
+ \ 'parser' : function('s:parser_tex'),
+ \ },
+ \ {
+ \ 're' : '\\\%((\|)\|\[\|\]\)',
+ \ 'parser' : function('s:parser_latex'),
+ \ },
+ \ {
+ \ 're' : '\\\%(left\|right\)\s*\.',
+ \ 'parser' : function('s:parser_delim_unmatched'),
+ \ },
+ \ {
+ \ 're' : g:vimtex#delim#re.delim_all.both,
+ \ 'parser' : function('s:parser_delim'),
+ \ },
+ \]
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/doc.vim b/autoload/vimtex/doc.vim
new file mode 100644
index 00000000..dd42a6ed
--- /dev/null
+++ b/autoload/vimtex/doc.vim
@@ -0,0 +1,251 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#doc#init_buffer() abort " {{{1
+ command! -buffer -nargs=? VimtexDocPackage call vimtex#doc#package(<q-args>)
+
+ nnoremap <buffer> <plug>(vimtex-doc-package) :VimtexDocPackage<cr>
+endfunction
+
+" }}}1
+
+function! vimtex#doc#package(word) abort " {{{1
+ let l:context = empty(a:word)
+ \ ? s:packages_get_from_cursor()
+ \ : {
+ \ 'type': 'word',
+ \ 'candidates': [a:word],
+ \ }
+ if empty(l:context) | return | endif
+
+ call s:packages_remove_invalid(l:context)
+
+ for l:handler in g:vimtex_doc_handlers
+ if exists('*' . l:handler)
+ if call(l:handler, [l:context]) | return | endif
+ endif
+ endfor
+
+ call s:packages_open(l:context)
+endfunction
+
+" }}}1
+function! vimtex#doc#make_selection(context) abort " {{{1
+ if has_key(a:context, 'selected') | return | endif
+
+ if len(a:context.candidates) == 0
+ if exists('a:context.name')
+ echohl ErrorMsg
+ echo 'Sorry, no doc for '.a:context.name
+ echohl NONE
+ endif
+ let a:context.selected = ''
+ return
+ endif
+
+ if len(a:context.candidates) == 1
+ let a:context.selected = a:context.candidates[0]
+ return
+ endif
+
+ call vimtex#echo#echo('Multiple candidates detected, please select one:')
+ let l:count = 0
+ for l:package in a:context.candidates
+ let l:count += 1
+ call vimtex#echo#formatted([
+ \ ' [' . string(l:count) . '] ',
+ \ ['VimtexSuccess', l:package]
+ \])
+ endfor
+
+ call vimtex#echo#echo('Type number (everything else cancels): ')
+ let l:choice = nr2char(getchar())
+ if l:choice !~# '\d'
+ \ || l:choice == 0
+ \ || l:choice > len(a:context.candidates)
+ echohl VimtexWarning
+ echon l:choice =~# '\d' ? l:choice : '-'
+ echohl NONE
+ let a:context.selected = ''
+ else
+ echon l:choice
+ let a:context.selected = a:context.candidates[l:choice-1]
+ let a:context.ask_before_open = 0
+ endif
+endfunction
+
+" }}}1
+
+function! s:packages_get_from_cursor() abort " {{{1
+ let l:cmd = vimtex#cmd#get_current()
+ if empty(l:cmd) | return {} | endif
+
+ if l:cmd.name ==# '\usepackage'
+ return s:packages_from_usepackage(l:cmd)
+ elseif l:cmd.name ==# '\documentclass'
+ return s:packages_from_documentclass(l:cmd)
+ else
+ return s:packages_from_command(strpart(l:cmd.name, 1))
+ endif
+endfunction
+
+" }}}1
+function! s:packages_from_usepackage(cmd) abort " {{{1
+ try
+ " Gather and clean up candidate list
+ let l:candidates = substitute(a:cmd.args[0].text, '%.\{-}\n', '', 'g')
+ let l:candidates = substitute(l:candidates, '\s*', '', 'g')
+ let l:candidates = split(l:candidates, ',')
+
+ let l:context = {
+ \ 'type': 'usepackage',
+ \ 'candidates': l:candidates,
+ \}
+
+ let l:cword = expand('<cword>')
+ if len(l:context.candidates) > 1 && index(l:context.candidates, l:cword) >= 0
+ let l:context.selected = l:cword
+ endif
+
+ return l:context
+ catch
+ call vimtex#log#warning('Could not parse the package from \usepackage!')
+ return {}
+ endtry
+endfunction
+
+" }}}1
+function! s:packages_from_documentclass(cmd) abort " {{{1
+ try
+ return {
+ \ 'type': 'documentclass',
+ \ 'candidates': [a:cmd.args[0].text],
+ \}
+ catch
+ call vimtex#log#warning('Could not parse the package from \documentclass!')
+ return {}
+ endtry
+endfunction
+
+" }}}1
+function! s:packages_from_command(cmd) abort " {{{1
+ let l:packages = [
+ \ 'default',
+ \ 'class-' . get(b:vimtex, 'documentclass', ''),
+ \] + keys(b:vimtex.packages)
+ call filter(l:packages, 'filereadable(s:complete_dir . v:val)')
+
+ let l:queue = copy(l:packages)
+ while !empty(l:queue)
+ let l:current = remove(l:queue, 0)
+ let l:includes = filter(readfile(s:complete_dir . l:current), 'v:val =~# ''^\#\s*include:''')
+ if empty(l:includes) | continue | endif
+
+ call map(l:includes, 'matchstr(v:val, ''include:\s*\zs.*\ze\s*$'')')
+ call filter(l:includes, 'filereadable(s:complete_dir . v:val)')
+ call filter(l:includes, 'index(l:packages, v:val) < 0')
+
+ let l:packages += l:includes
+ let l:queue += l:includes
+ endwhile
+
+ let l:candidates = []
+ let l:filter = 'v:val =~# ''^' . a:cmd . '\>'''
+ for l:package in l:packages
+ let l:cmds = filter(readfile(s:complete_dir . l:package), l:filter)
+ if empty(l:cmds) | continue | endif
+
+ if l:package ==# 'default'
+ call extend(l:candidates, ['latex2e', 'lshort'])
+ else
+ call add(l:candidates, substitute(l:package, '^class-', '', ''))
+ endif
+ endfor
+
+ return {
+ \ 'type': 'command',
+ \ 'name': a:cmd,
+ \ 'candidates': l:candidates,
+ \}
+endfunction
+
+" }}}1
+function! s:packages_remove_invalid(context) abort " {{{1
+ let l:invalid_packages = filter(copy(a:context.candidates),
+ \ 'empty(vimtex#kpsewhich#find(v:val . ''.sty'')) && '
+ \ . 'empty(vimtex#kpsewhich#find(v:val . ''.cls''))')
+
+ call filter(l:invalid_packages,
+ \ 'index([''latex2e'', ''lshort''], v:val) < 0')
+
+ " Warn about invalid candidates
+ if !empty(l:invalid_packages)
+ if len(l:invalid_packages) == 1
+ call vimtex#log#warning(
+ \ 'Package not recognized: ' . l:invalid_packages[0])
+ else
+ call vimtex#log#warning(
+ \ 'Packages not recognized:',
+ \ map(copy(l:invalid_packages), "'- ' . v:val"))
+ endif
+ endif
+
+ " Remove invalid candidates
+ call filter(a:context.candidates, 'index(l:invalid_packages, v:val) < 0')
+
+ " Reset the selection if the selected candidate is not valid
+ if has_key(a:context, 'selected')
+ \ && index(a:context.candidates, a:context.selected) < 0
+ unlet a:context.selected
+ endif
+endfunction
+
+" }}}1
+function! s:packages_open(context) abort " {{{1
+ if !has_key(a:context, 'selected')
+ call vimtex#doc#make_selection(a:context)
+ endif
+
+ if empty(a:context.selected) | return | endif
+
+ if get(a:context, 'ask_before_open', 1)
+ call vimtex#echo#formatted([
+ \ 'Open documentation for ',
+ \ ['VimtexSuccess', a:context.selected], ' [y/N]? '
+ \])
+
+ let l:choice = nr2char(getchar())
+ if l:choice ==# 'y'
+ echon 'y'
+ else
+ echohl VimtexWarning
+ echon l:choice =~# '\w' ? l:choice : 'N'
+ echohl NONE
+ return
+ endif
+ endif
+
+ let l:os = vimtex#util#get_os()
+ let l:url = 'http://texdoc.net/pkg/' . a:context.selected
+
+ silent execute (l:os ==# 'linux'
+ \ ? '!xdg-open'
+ \ : (l:os ==# 'mac'
+ \ ? '!open'
+ \ : '!start'))
+ \ . ' ' . l:url
+ \ . (l:os ==# 'win' ? '' : ' &')
+
+ redraw!
+endfunction
+
+" }}}1
+
+let s:complete_dir = fnamemodify(expand('<sfile>'), ':h') . '/complete/'
+
+endif
diff --git a/autoload/vimtex/echo.vim b/autoload/vimtex/echo.vim
new file mode 100644
index 00000000..9c761f8f
--- /dev/null
+++ b/autoload/vimtex/echo.vim
@@ -0,0 +1,121 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#echo#echo(message) abort " {{{1
+ echohl VimtexMsg
+ echo a:message
+ echohl None
+endfunction
+
+" }}}1
+function! vimtex#echo#input(opts) abort " {{{1
+ if g:vimtex_echo_verbose_input
+ \ && has_key(a:opts, 'info')
+ call vimtex#echo#formatted(a:opts.info)
+ endif
+
+ let l:args = [get(a:opts, 'prompt', '> ')]
+ let l:args += [get(a:opts, 'default', '')]
+ if has_key(a:opts, 'complete')
+ let l:args += [a:opts.complete]
+ endif
+
+ echohl VimtexMsg
+ let l:reply = call('input', l:args)
+ echohl None
+ return l:reply
+endfunction
+
+" }}}1
+function! vimtex#echo#choose(list_or_dict, prompt) abort " {{{1
+ if empty(a:list_or_dict) | return '' | endif
+
+ return type(a:list_or_dict) == type({})
+ \ ? s:choose_dict(a:list_or_dict, a:prompt)
+ \ : s:choose_list(a:list_or_dict, a:prompt)
+endfunction
+
+" }}}1
+function! vimtex#echo#formatted(parts) abort " {{{1
+ echo ''
+ try
+ for part in a:parts
+ if type(part) == type('')
+ echohl VimtexMsg
+ echon part
+ else
+ execute 'echohl' part[0]
+ echon part[1]
+ endif
+ unlet part
+ endfor
+ finally
+ echohl None
+ endtry
+endfunction
+
+" }}}1
+
+function! s:choose_dict(dict, prompt) abort " {{{1
+ if len(a:dict) == 1
+ return values(a:dict)[0]
+ endif
+
+ while v:true
+ redraw!
+ if !empty(a:prompt)
+ echohl VimtexMsg
+ unsilent echo a:prompt
+ echohl None
+ endif
+
+ let l:choice = 0
+ for l:x in values(a:dict)
+ let l:choice += 1
+ unsilent call vimtex#echo#formatted([['VimtexWarning', l:choice], ': ', l:x])
+ endfor
+
+ try
+ let l:choice = str2nr(input('> ')) - 1
+ if l:choice >= 0 && l:choice < len(a:dict)
+ return keys(a:dict)[l:choice]
+ endif
+ endtry
+ endwhile
+endfunction
+
+" }}}1
+function! s:choose_list(list, prompt) abort " {{{1
+ if len(a:list) == 1 | return a:list[0] | endif
+
+ while v:true
+ redraw!
+ if !empty(a:prompt)
+ echohl VimtexMsg
+ unsilent echo a:prompt
+ echohl None
+ endif
+
+ let l:choice = 0
+ for l:x in a:list
+ let l:choice += 1
+ unsilent call vimtex#echo#formatted([['VimtexWarning', l:choice], ': ', l:x])
+ endfor
+
+ try
+ let l:choice = str2nr(input('> ')) - 1
+ if l:choice >= 0 && l:choice < len(a:list)
+ return a:list[l:choice]
+ endif
+ endtry
+ endwhile
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/env.vim b/autoload/vimtex/env.vim
new file mode 100644
index 00000000..9576397f
--- /dev/null
+++ b/autoload/vimtex/env.vim
@@ -0,0 +1,202 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#env#init_buffer() abort " {{{1
+ nnoremap <silent><buffer> <plug>(vimtex-env-delete)
+ \ :<c-u>call <sid>operator_setup('delete', 'env_tex')<bar>normal! g@l<cr>
+
+ nnoremap <silent><buffer> <plug>(vimtex-env-change)
+ \ :<c-u>call <sid>operator_setup('change', 'env_tex')<bar>normal! g@l<cr>
+
+ nnoremap <silent><buffer> <plug>(vimtex-env-delete-math)
+ \ :<c-u>call <sid>operator_setup('delete', 'env_math')<bar>normal! g@l<cr>
+
+ nnoremap <silent><buffer> <plug>(vimtex-env-change-math)
+ \ :<c-u>call <sid>operator_setup('change', 'env_math')<bar>normal! g@l<cr>
+
+ nnoremap <silent><buffer> <plug>(vimtex-env-toggle-star)
+ \ :<c-u>call <sid>operator_setup('toggle_star', '')<bar>normal! g@l<cr>
+endfunction
+
+" }}}1
+
+function! vimtex#env#change(open, close, new) abort " {{{1
+ "
+ " Set target environment
+ "
+ if a:new ==# ''
+ let [l:beg, l:end] = ['', '']
+ elseif a:new ==# '$'
+ let [l:beg, l:end] = ['$', '$']
+ elseif a:new ==# '$$'
+ let [l:beg, l:end] = ['$$', '$$']
+ elseif a:new ==# '\['
+ let [l:beg, l:end] = ['\[', '\]']
+ elseif a:new ==# '\('
+ let [l:beg, l:end] = ['\(', '\)']
+ else
+ let l:beg = '\begin{' . a:new . '}'
+ let l:end = '\end{' . a:new . '}'
+ endif
+
+ let l:line = getline(a:open.lnum)
+ call setline(a:open.lnum,
+ \ strpart(l:line, 0, a:open.cnum-1)
+ \ . l:beg
+ \ . strpart(l:line, a:open.cnum + len(a:open.match) - 1))
+
+ let l:c1 = a:close.cnum
+ let l:c2 = a:close.cnum + len(a:close.match) - 1
+ if a:open.lnum == a:close.lnum
+ let n = len(l:beg) - len(a:open.match)
+ let l:c1 += n
+ let l:c2 += n
+ let pos = vimtex#pos#get_cursor()
+ if pos[2] > a:open.cnum + len(a:open.match) - 1
+ let pos[2] += n
+ call vimtex#pos#set_cursor(pos)
+ endif
+ endif
+
+ let l:line = getline(a:close.lnum)
+ call setline(a:close.lnum,
+ \ strpart(l:line, 0, l:c1-1) . l:end . strpart(l:line, l:c2))
+endfunction
+
+function! vimtex#env#change_surrounding_to(type, new) abort " {{{1
+ let [l:open, l:close] = vimtex#delim#get_surrounding(a:type)
+ if empty(l:open) | return | endif
+
+ return vimtex#env#change(l:open, l:close, a:new)
+endfunction
+
+function! vimtex#env#delete(type) abort " {{{1
+ let [l:open, l:close] = vimtex#delim#get_surrounding(a:type)
+ if empty(l:open) | return | endif
+
+ if a:type ==# 'env_tex'
+ call vimtex#cmd#delete_all(l:close)
+ call vimtex#cmd#delete_all(l:open)
+ else
+ call l:close.remove()
+ call l:open.remove()
+ endif
+
+ if getline(l:close.lnum) =~# '^\s*$'
+ execute l:close.lnum . 'd _'
+ endif
+
+ if getline(l:open.lnum) =~# '^\s*$'
+ execute l:open.lnum . 'd _'
+ endif
+endfunction
+
+function! vimtex#env#toggle_star() abort " {{{1
+ let [l:open, l:close] = vimtex#delim#get_surrounding('env_tex')
+ if empty(l:open) | return | endif
+
+ call vimtex#env#change(l:open, l:close,
+ \ l:open.starred ? l:open.name : l:open.name . '*')
+endfunction
+
+" }}}1
+
+function! vimtex#env#is_inside(env) abort " {{{1
+ let l:re_start = '\\begin\s*{' . a:env . '\*\?}'
+ let l:re_end = '\\end\s*{' . a:env . '\*\?}'
+ try
+ return searchpairpos(l:re_start, '', l:re_end, 'bnW', '', 0, 100)
+ catch /E118/
+ let l:stopline = max([line('.') - 500, 1])
+ return searchpairpos(l:re_start, '', l:re_end, 'bnW', '', l:stopline)
+ endtry
+endfunction
+
+" }}}1
+function! vimtex#env#input_complete(lead, cmdline, pos) abort " {{{1
+ let l:cands = map(vimtex#complete#complete('env', '', '\begin'), 'v:val.word')
+
+ " Never include document and remove current env (place it first)
+ call filter(l:cands, 'index([''document'', s:env_name], v:val) < 0')
+
+ " Always include current env and displaymath
+ let l:cands = [s:env_name] + l:cands + ['\[']
+
+ return filter(l:cands, 'v:val =~# ''^' . a:lead . '''')
+endfunction
+
+" }}}1
+
+function! s:change_prompt(type) abort " {{{1
+ let [l:open, l:close] = vimtex#delim#get_surrounding(a:type)
+ if empty(l:open) | return | endif
+
+ if g:vimtex_env_change_autofill
+ let l:name = get(l:open, 'name', l:open.match)
+ let s:env_name = l:name
+ return vimtex#echo#input({
+ \ 'prompt' : 'Change surrounding environment: ',
+ \ 'default' : l:name,
+ \ 'complete' : 'customlist,vimtex#env#input_complete',
+ \})
+ else
+ let l:name = get(l:open, 'name', l:open.is_open
+ \ ? l:open.match . ' ... ' . l:open.corr
+ \ : l:open.match . ' ... ' . l:open.corr)
+ let s:env_name = l:name
+ return vimtex#echo#input({
+ \ 'info' :
+ \ ['Change surrounding environment: ', ['VimtexWarning', l:name]],
+ \ 'complete' : 'customlist,vimtex#env#input_complete',
+ \})
+ endif
+endfunction
+
+" }}}1
+
+function! s:operator_setup(operator, type) abort " {{{1
+ let &opfunc = s:snr() . 'operator_function'
+
+ let s:operator_abort = 0
+ let s:operator = a:operator
+ let s:operator_type = a:type
+
+ " Ask for user input if necessary/relevant
+ if s:operator ==# 'change'
+ let l:new_env = s:change_prompt(s:operator_type)
+ if empty(l:new_env)
+ let s:operator_abort = 1
+ return
+ endif
+
+ let s:operator_name = l:new_env
+ endif
+endfunction
+
+" }}}1
+function! s:operator_function(_) abort " {{{1
+ if get(s:, 'operator_abort', 0) | return | endif
+
+ let l:type = get(s:, 'operator_type', '')
+ let l:name = get(s:, 'operator_name', '')
+
+ execute 'call vimtex#env#' . {
+ \ 'change': 'change_surrounding_to(l:type, l:name)',
+ \ 'delete': 'delete(l:type)',
+ \ 'toggle_star': 'toggle_star()',
+ \ }[s:operator]
+endfunction
+
+" }}}1
+function! s:snr() abort " {{{1
+ return matchstr(expand('<sfile>'), '<SNR>\d\+_')
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/fold.vim b/autoload/vimtex/fold.vim
new file mode 100644
index 00000000..fc897b69
--- /dev/null
+++ b/autoload/vimtex/fold.vim
@@ -0,0 +1,143 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#fold#init_buffer() abort " {{{1
+ if !g:vimtex_fold_enabled
+ \ || s:foldmethod_in_modeline() | return | endif
+
+ " Set fold options
+ setlocal foldmethod=expr
+ setlocal foldexpr=vimtex#fold#level(v:lnum)
+ setlocal foldtext=vimtex#fold#text()
+
+ if g:vimtex_fold_manual
+ " Remap zx to refresh fold levels
+ nnoremap <silent><nowait><buffer> zx :call vimtex#fold#refresh('zx')<cr>
+ nnoremap <silent><nowait><buffer> zX :call vimtex#fold#refresh('zX')<cr>
+
+ " Define commands
+ command! -buffer VimtexRefreshFolds call vimtex#fold#refresh('zx')
+
+ " Ensure that folds are refreshed on startup
+ augroup vimtex_temporary
+ autocmd! * <buffer>
+ autocmd CursorMoved <buffer>
+ \ call vimtex#fold#refresh('zx')
+ \ | autocmd! vimtex_temporary CursorMoved <buffer>
+ augroup END
+ endif
+endfunction
+
+" }}}1
+function! vimtex#fold#init_state(state) abort " {{{1
+ "
+ " Initialize the enabled fold types
+ "
+ let a:state.fold_types_dict = {}
+ for [l:key, l:val] in items(g:vimtex_fold_types_defaults)
+ let l:config = extend(deepcopy(l:val), get(g:vimtex_fold_types, l:key, {}))
+ if get(l:config, 'enabled', 1)
+ let a:state.fold_types_dict[l:key] = vimtex#fold#{l:key}#new(l:config)
+ endif
+ endfor
+
+ "
+ " Define ordered list and the global fold regex
+ "
+ let a:state.fold_types_ordered = []
+ let a:state.fold_re = '\v'
+ \ . '\\%(begin|end)>'
+ \ . '|^\s*\%'
+ \ . '|^\s*\]\s*%(\{|$)'
+ \ . '|^\s*}'
+ for l:name in [
+ \ 'preamble',
+ \ 'cmd_single',
+ \ 'cmd_single_opt',
+ \ 'cmd_multi',
+ \ 'cmd_addplot',
+ \ 'sections',
+ \ 'markers',
+ \ 'comments',
+ \ 'envs',
+ \ 'env_options',
+ \]
+ let l:type = get(a:state.fold_types_dict, l:name, {})
+ if !empty(l:type)
+ call add(a:state.fold_types_ordered, l:type)
+ if exists('l:type.re.fold_re')
+ let a:state.fold_re .= '|' . l:type.re.fold_re
+ endif
+ endif
+ endfor
+endfunction
+
+" }}}1
+
+function! vimtex#fold#refresh(map) abort " {{{1
+ setlocal foldmethod=expr
+ execute 'normal!' a:map
+ setlocal foldmethod=manual
+endfunction
+
+" }}}1
+function! vimtex#fold#level(lnum) abort " {{{1
+ let l:line = getline(a:lnum)
+
+ " Filter out lines that do not start any folds (optimization)
+ if l:line !~# b:vimtex.fold_re | return '=' | endif
+
+ " Never fold \begin|end{document}
+ if l:line =~# '^\s*\\\%(begin\|end\){document}'
+ return 0
+ endif
+
+ for l:type in b:vimtex.fold_types_ordered
+ let l:value = l:type.level(l:line, a:lnum)
+ if !empty(l:value) | return l:value | endif
+ endfor
+
+ " Return foldlevel of previous line
+ return '='
+endfunction
+
+" }}}1
+function! vimtex#fold#text() abort " {{{1
+ let l:line = getline(v:foldstart)
+ let l:level = v:foldlevel > 1
+ \ ? repeat('-', v:foldlevel-2) . g:vimtex_fold_levelmarker
+ \ : ''
+
+ for l:type in b:vimtex.fold_types_ordered
+ if l:line =~# l:type.re.start
+ let l:text = l:type.text(l:line, l:level)
+ if !empty(l:text) | return l:text | endif
+ endif
+ endfor
+endfunction
+
+" }}}1
+
+
+function! s:foldmethod_in_modeline() abort " {{{1
+ let l:cursor_pos = vimtex#pos#get_cursor()
+ let l:fdm_modeline = 'vim:.*\%(foldmethod\|fdm\)'
+
+ call vimtex#pos#set_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 vimtex#pos#set_cursor(l:cursor_pos)
+ return l:check_top || l:check_btm
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/fold/cmd_addplot.vim b/autoload/vimtex/fold/cmd_addplot.vim
new file mode 100644
index 00000000..9e997681
--- /dev/null
+++ b/autoload/vimtex/fold/cmd_addplot.vim
@@ -0,0 +1,51 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#fold#cmd_addplot#new(config) abort " {{{1
+ return extend(deepcopy(s:folder), a:config).init()
+endfunction
+
+" }}}1
+
+
+let s:folder = {
+ \ 'name' : 'cmd_addplot',
+ \ 're' : {},
+ \ 'opened' : 0,
+ \ 'cmds' : [],
+ \}
+function! s:folder.init() abort dict " {{{1
+ let l:re = '\v^\s*\\%(' . join(self.cmds, '|') . ')\s*%(\[[^\]]*\])?'
+
+ let self.re.start = l:re . '\s*\w+\s*%(\[[^\]]*\])?\s*\ze\{\s*%($|\%)'
+ let self.re.end = '^\s*}'
+ let self.re.fold_re = '\\%(' . join(self.cmds, '|') . ')'
+
+ return self
+endfunction
+
+" }}}1
+function! s:folder.level(line, lnum) abort dict " {{{1
+ if a:line =~# self.re.start
+ let self.opened = 1
+ return 'a1'
+ elseif self.opened && a:line =~# self.re.end
+ let self.opened = 0
+ return 's1'
+ endif
+endfunction
+
+" }}}1
+function! s:folder.text(line, level) abort dict " {{{1
+ return matchstr(a:line, self.re.start) . '{...}'
+ \ . substitute(getline(v:foldend), self.re.end, '', '')
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/fold/cmd_multi.vim b/autoload/vimtex/fold/cmd_multi.vim
new file mode 100644
index 00000000..cb84d09e
--- /dev/null
+++ b/autoload/vimtex/fold/cmd_multi.vim
@@ -0,0 +1,51 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#fold#cmd_multi#new(config) abort " {{{1
+ return extend(deepcopy(s:folder), a:config).init()
+endfunction
+
+" }}}1
+
+
+let s:folder = {
+ \ 'name' : 'cmd_multi',
+ \ 're' : {},
+ \ 'opened' : 0,
+ \ 'cmds' : [],
+ \}
+function! s:folder.init() abort dict " {{{1
+ let l:re = '\v^\s*\\%(' . join(self.cmds, '|') . ')\*?'
+
+ let self.re.start = l:re . '.*(\{|\[)\s*(\%.*)?$'
+ let self.re.end = '\v^\s*%(\}\s*\{)*\}\s*%(\%|$)'
+ let self.re.text = l:re . '\{[^}]*\}'
+ let self.re.fold_re = '\\%(' . join(self.cmds, '|') . ')'
+
+ return self
+endfunction
+
+" }}}1
+function! s:folder.level(line, lnum) abort dict " {{{1
+ if a:line =~# self.re.start
+ let self.opened += 1
+ return 'a1'
+ elseif self.opened > 0 && a:line =~# self.re.end
+ let self.opened -= 1
+ return 's1'
+ endif
+endfunction
+
+" }}}1
+function! s:folder.text(line, level) abort dict " {{{1
+ return a:line
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/fold/cmd_single.vim b/autoload/vimtex/fold/cmd_single.vim
new file mode 100644
index 00000000..1403ad9b
--- /dev/null
+++ b/autoload/vimtex/fold/cmd_single.vim
@@ -0,0 +1,52 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#fold#cmd_single#new(config) abort " {{{1
+ return extend(deepcopy(s:folder), a:config).init()
+endfunction
+
+" }}}1
+
+
+let s:folder = {
+ \ 'name' : 'cmd_single',
+ \ 're' : {},
+ \ 'opened' : 0,
+ \ 'cmds' : [],
+ \}
+function! s:folder.init() abort dict " {{{1
+ let l:re = '\v^\s*\\%(' . join(self.cmds, '|') . ')\*?\s*%(\[.*\])?'
+
+ let self.re.start = l:re . '\s*\{\s*%($|\%)'
+ let self.re.end = '^\s*}'
+ let self.re.text = l:re
+ let self.re.fold_re = '\\%(' . join(self.cmds, '|') . ')'
+
+ return self
+endfunction
+
+" }}}1
+function! s:folder.level(line, lnum) abort dict " {{{1
+ if a:line =~# self.re.start
+ let self.opened = 1
+ return 'a1'
+ elseif self.opened && a:line =~# self.re.end
+ let self.opened = 0
+ return 's1'
+ endif
+endfunction
+
+" }}}1
+function! s:folder.text(line, level) abort dict " {{{1
+ return matchstr(a:line, self.re.text) . '{...}'
+ \ . substitute(getline(v:foldend), self.re.end, '', '')
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/fold/cmd_single_opt.vim b/autoload/vimtex/fold/cmd_single_opt.vim
new file mode 100644
index 00000000..8def0234
--- /dev/null
+++ b/autoload/vimtex/fold/cmd_single_opt.vim
@@ -0,0 +1,53 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#fold#cmd_single_opt#new(config) abort " {{{1
+ return extend(deepcopy(s:folder), a:config).init()
+endfunction
+
+" }}}1
+
+
+let s:folder = {
+ \ 'name' : 'cmd_single_opt',
+ \ 're' : {},
+ \ 'opened' : 0,
+ \ 'cmds' : [],
+ \}
+function! s:folder.init() abort dict " {{{1
+ let l:re = '\v^\s*\\%(' . join(self.cmds, '|') . ')\*?'
+
+ let self.re.start = l:re . '\s*\[\s*%($|\%)'
+ let self.re.end = '^\s*\]{'
+ let self.re.text = l:re
+ let self.re.fold_re = '\\%(' . join(self.cmds, '|') . ')'
+
+ return self
+endfunction
+
+" }}}1
+function! s:folder.level(line, lnum) abort dict " {{{1
+ if a:line =~# self.re.start
+ let self.opened = 1
+ return 'a1'
+ elseif self.opened && a:line =~# self.re.end
+ let self.opened = 0
+ return 's1'
+ endif
+endfunction
+
+" }}}1
+function! s:folder.text(line, level) abort dict " {{{1
+ let l:col = strlen(matchstr(a:line, '^\s*')) + 1
+ return matchstr(a:line, self.re.text) . '[...]{'
+ \ . vimtex#cmd#get_at(v:foldstart, l:col).args[0].text . '}'
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/fold/comments.vim b/autoload/vimtex/fold/comments.vim
new file mode 100644
index 00000000..4a313064
--- /dev/null
+++ b/autoload/vimtex/fold/comments.vim
@@ -0,0 +1,46 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#fold#comments#new(config) abort " {{{1
+ return extend(deepcopy(s:folder), a:config)
+endfunction
+
+" }}}1
+
+
+let s:folder = {
+ \ 'name' : 'comments',
+ \ 're' : {'start' : '^\s*%'},
+ \ 'opened' : 0,
+ \}
+function! s:folder.level(line, lnum) abort dict " {{{1
+ if exists('b:vimtex.fold_types_dict.markers.opened')
+ \ && b:vimtex.fold_types_dict.markers.opened | return | endif
+
+ if a:line =~# self.re.start
+ let l:next = getline(a:lnum-1) !~# self.re.start
+ let l:prev = getline(a:lnum+1) !~# self.re.start
+ if l:next && !l:prev
+ let self.opened = 1
+ return 'a1'
+ elseif l:prev && !l:next
+ let self.opened = 0
+ return 's1'
+ endif
+ endif
+endfunction
+
+" }}}1
+function! s:folder.text(line, level) abort dict " {{{1
+ let l:lines = map(getline(v:foldstart, v:foldend), 'matchstr(v:val, ''%\s*\zs.*\ze\s*'')')
+ return matchstr(a:line, '^.*\s*%') . join(l:lines, ' ')
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/fold/env_options.vim b/autoload/vimtex/fold/env_options.vim
new file mode 100644
index 00000000..eab339bf
--- /dev/null
+++ b/autoload/vimtex/fold/env_options.vim
@@ -0,0 +1,53 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#fold#env_options#new(config) abort " {{{1
+ return extend(deepcopy(s:folder), a:config)
+endfunction
+
+" }}}1
+
+
+let s:folder = {
+ \ 'name' : 'envs with options',
+ \ 're' : {
+ \ 'start' : g:vimtex#re#not_comment . '\\begin\s*\{.{-}\}\[\s*($|\%)',
+ \ 'end' : '\s*\]\s*$',
+ \ },
+ \ 'opened' : 0,
+ \}
+function! s:folder.level(line, lnum) abort dict " {{{1
+ return self.opened
+ \ ? self.fold_closed(a:line, a:lnum)
+ \ : self.fold_opened(a:line, a:lnum)
+endfunction
+
+" }}}1
+function! s:folder.fold_opened(line, lnum) abort dict " {{{1
+ if a:line =~# self.re.start
+ let self.opened = 1
+ return 'a1'
+ endif
+endfunction
+
+" }}}1
+function! s:folder.fold_closed(line, lnum) abort dict " {{{1
+ if a:line =~# self.re.end
+ let self.opened = 0
+ return 's1'
+ endif
+endfunction
+
+" }}}1
+function! s:folder.text(line, level) abort dict " {{{1
+ return a:line . '...] '
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/fold/envs.vim b/autoload/vimtex/fold/envs.vim
new file mode 100644
index 00000000..1eb70f3c
--- /dev/null
+++ b/autoload/vimtex/fold/envs.vim
@@ -0,0 +1,188 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#fold#envs#new(config) abort " {{{1
+ return extend(deepcopy(s:folder), a:config).init()
+endfunction
+
+" }}}1
+
+
+let s:folder = {
+ \ 'name' : 'environments',
+ \ 're' : {
+ \ 'start' : g:vimtex#re#not_comment . '\\begin\s*\{.{-}\}',
+ \ 'end' : g:vimtex#re#not_comment . '\\end\s*\{.{-}\}',
+ \ 'name' : g:vimtex#re#not_comment . '\\%(begin|end)\s*\{\zs.{-}\ze\}'
+ \ },
+ \ 'whitelist' : [],
+ \ 'blacklist' : [],
+ \}
+function! s:folder.init() abort dict " {{{1
+ " Define the validator as simple as possible
+ if empty(self.whitelist) && empty(self.blacklist)
+ function! self.validate(env) abort dict
+ return 1
+ endfunction
+ elseif empty(self.whitelist)
+ function! self.validate(env) abort dict
+ return index(self.blacklist, a:env) < 0
+ endfunction
+ elseif empty(self.blacklist)
+ function! self.validate(env) abort dict
+ return index(self.whitelist, a:env) >= 0
+ endfunction
+ else
+ function! self.validate(env) abort dict
+ return index(self.whitelist, a:env) >= 0 && index(self.blacklist, a:env) < 0
+ endfunction
+ endif
+
+ return self
+endfunction
+
+" }}}1
+function! s:folder.level(line, lnum) abort dict " {{{1
+ let l:env = matchstr(a:line, self.re.name)
+
+ if !empty(l:env) && self.validate(l:env)
+ if a:line =~# self.re.start
+ if a:line !~# '\\end'
+ return 'a1'
+ endif
+ elseif a:line =~# self.re.end
+ if a:line !~# '\\begin'
+ return 's1'
+ endif
+ endif
+ endif
+endfunction
+
+" }}}1
+function! s:folder.text(line, level) abort dict " {{{1
+ let env = matchstr(a:line, self.re.name)
+ if !self.validate(env) | return | endif
+
+ " Set caption/label based on type of environment
+ if env ==# 'frame'
+ let label = ''
+ let caption = self.parse_caption_frame(a:line)
+ elseif env ==# 'table'
+ let label = self.parse_label()
+ let caption = self.parse_caption_table(a:line)
+ else
+ let label = self.parse_label()
+ let caption = self.parse_caption(a:line)
+ endif
+
+ let width_ind = len(matchstr(a:line, '^\s*'))
+ let width = winwidth(0) - (&number ? &numberwidth : 0) - 4 - width_ind
+
+ let width_env = 19
+ let width_lab = len(label) + 2 > width - width_env
+ \ ? width - width_env
+ \ : len(label) + 2
+ let width_cap = width - width_env - width_lab
+
+ if !empty(label)
+ let label = printf('(%.*S)', width_lab, label)
+ endif
+
+ if !empty(caption)
+ if strchars(caption) > width_cap
+ let caption = strpart(caption, 0, width_cap - 4) . '...'
+ endif
+ else
+ let width_env += width_cap
+ let width_cap = 0
+ endif
+
+ if strlen(env) > width_env - 8
+ let env = strpart(env, 0, width_env - 11) . '...'
+ endif
+ let env = '\begin{' . env . '}'
+
+ let title = printf('%*S%-*S %-*S %*S',
+ \ width_ind, '',
+ \ width_env, env,
+ \ width_cap, caption,
+ \ width_lab, label)
+
+ return substitute(title, '\s\+$', '', '')
+endfunction
+
+" }}}1
+function! s:folder.parse_label() abort dict " {{{1
+ let i = v:foldend
+ while i >= v:foldstart
+ if getline(i) =~# '^\s*\\label'
+ return matchstr(getline(i), '^\s*\\label\%(\[.*\]\)\?{\zs.*\ze}')
+ end
+ let i -= 1
+ endwhile
+ return ''
+endfunction
+
+" }}}1
+function! s:folder.parse_caption(line) abort dict " {{{1
+ let i = v:foldend
+ while i >= v:foldstart
+ if getline(i) =~# '^\s*\\caption'
+ return matchstr(getline(i),
+ \ '^\s*\\caption\(\[.*\]\)\?{\zs.\{-1,}\ze\(}\s*\)\?$')
+ end
+ let i -= 1
+ endwhile
+
+ " If no caption found, check for a caption comment
+ return matchstr(a:line,'\\begin\*\?{.*}\s*%\s*\zs.*')
+endfunction
+
+" }}}1
+function! s:folder.parse_caption_table(line) abort dict " {{{1
+ let i = v:foldstart
+ while i <= v:foldend
+ if getline(i) =~# '^\s*\\caption'
+ return matchstr(getline(i),
+ \ '^\s*\\caption\s*\(\[.*\]\)\?\s*{\zs.\{-1,}\ze\(}\s*\)\?$')
+ end
+ let i += 1
+ endwhile
+
+ " If no caption found, check for a caption comment
+ return matchstr(a:line,'\\begin\*\?{.*}\s*%\s*\zs.*')
+endfunction
+
+" }}}1
+function! s:folder.parse_caption_frame(line) abort dict " {{{1
+ " Test simple variants first
+ let caption1 = matchstr(a:line,'\\begin\*\?{.*}\(\[[^]]*\]\)\?{\zs.\+\ze}')
+ let caption2 = matchstr(a:line,'\\begin\*\?{.*}\(\[[^]]*\]\)\?{\zs.\+')
+ if !empty(caption1)
+ return caption1
+ elseif !empty(caption2)
+ return caption2
+ endif
+
+ " Search for \frametitle command
+ let i = v:foldstart
+ while i <= v:foldend
+ if getline(i) =~# '^\s*\\frametitle'
+ return matchstr(getline(i),
+ \ '^\s*\\frametitle\(\[.*\]\)\?{\zs.\{-1,}\ze\(}\s*\)\?$')
+ end
+ let i += 1
+ endwhile
+
+ " If no caption found, check for a caption comment
+ return matchstr(a:line,'\\begin\*\?{.*}\s*%\s*\zs.*')
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/fold/markers.vim b/autoload/vimtex/fold/markers.vim
new file mode 100644
index 00000000..00aa1fce
--- /dev/null
+++ b/autoload/vimtex/fold/markers.vim
@@ -0,0 +1,60 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#fold#markers#new(config) abort " {{{1
+ return extend(deepcopy(s:folder), a:config).init()
+endfunction
+
+" }}}1
+
+
+let s:folder = {
+ \ 'name' : 'markers',
+ \ 'open' : '{{{',
+ \ 'close' : '}}}',
+ \ 're' : {},
+ \ 'opened' : 0,
+ \}
+function! s:folder.init() abort dict " {{{1
+ let self.re.start = '%.*' . self.open
+ let self.re.end = '%.*' . self.close
+ let self.re.text = [
+ \ [self.re.start . '\d\?\s*\zs.*', '% ' . self.open . ' '],
+ \ ['%\s*\zs.*\ze' . self.open, '% ' . self.open . ' '],
+ \ ['^.*\ze\s*%', ''],
+ \]
+
+ let self.re.fold_re = escape(self.open . '|' . self.close, '{}%+*.')
+
+ return self
+endfunction
+
+" }}}1
+function! s:folder.level(line, lnum) abort dict " {{{1
+ if a:line =~# self.re.start
+ let s:self.opened = 1
+ return 'a1'
+ elseif a:line =~# self.re.end
+ let s:self.opened = 0
+ return 's1'
+ endif
+endfunction
+
+" }}}1
+function! s:folder.text(line, level) abort dict " {{{1
+ for [l:re, l:pre] in self.re.text
+ let l:text = matchstr(a:line, l:re)
+ if !empty(l:text) | return l:pre . l:text | endif
+ endfor
+
+ return '% ' . self.open . ' ' . getline(v:foldstart + 1)
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/fold/preamble.vim b/autoload/vimtex/fold/preamble.vim
new file mode 100644
index 00000000..434064ee
--- /dev/null
+++ b/autoload/vimtex/fold/preamble.vim
@@ -0,0 +1,36 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#fold#preamble#new(config) abort " {{{1
+ return extend(deepcopy(s:folder), a:config)
+endfunction
+
+" }}}1
+
+
+let s:folder = {
+ \ 'name' : 'preamble',
+ \ 're' : {
+ \ 'start' : '^\s*\\documentclass',
+ \ 'fold_re' : '\\documentclass',
+ \ },
+ \}
+function! s:folder.level(line, lnum) abort dict " {{{1
+ if a:line =~# self.re.start
+ return '>1'
+ endif
+endfunction
+
+" }}}1
+function! s:folder.text(line, level) abort dict " {{{1
+ return ' Preamble'
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/fold/sections.vim b/autoload/vimtex/fold/sections.vim
new file mode 100644
index 00000000..2929a728
--- /dev/null
+++ b/autoload/vimtex/fold/sections.vim
@@ -0,0 +1,180 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#fold#sections#new(config) abort " {{{1
+ return extend(deepcopy(s:folder), a:config).init()
+endfunction
+
+" }}}1
+
+
+let s:folder = {
+ \ 'name' : 'sections',
+ \ 'parse_levels' : 0,
+ \ 're' : {},
+ \ 'folds' : [],
+ \ 'sections' : [],
+ \ 'parts' : [],
+ \ 'time' : 0,
+ \}
+function! s:folder.init() abort dict " {{{1
+ let self.re.parts = '\v^\s*\\%(' . join(self.parts, '|') . ')'
+ let self.re.sections = '\v^\s*\\%(' . join(self.sections, '|') . ')'
+ let self.re.fake_sections = '\v^\s*\% Fake%('
+ \ . join(self.sections, '|') . ').*'
+ let self.re.any_sections = '\v^\s*%(\\|\% Fake)%('
+ \ . join(self.sections, '|') . ').*'
+
+ let self.re.start = self.re.parts
+ \ . '|' . self.re.sections
+ \ . '|' . self.re.fake_sections
+
+ let self.re.secpat1 = self.re.sections . '\*?\s*\{\zs.*'
+ let self.re.secpat2 = self.re.sections . '\*?\s*\[\zs.*'
+
+ let self.re.fold_re = '\\%(' . join(self.parts + self.sections, '|') . ')'
+
+ return self
+endfunction
+
+" }}}1
+function! s:folder.level(line, lnum) abort dict " {{{1
+ call self.refresh()
+
+ " Fold chapters and sections
+ for [l:part, l:level] in self.folds
+ if a:line =~# l:part
+ return '>' . l:level
+ endif
+ endfor
+endfunction
+
+" }}}1
+function! s:folder.text(line, level) abort dict " {{{1
+ if a:line =~# '\\frontmatter'
+ let l:title = 'Frontmatter'
+ elseif a:line =~# '\\mainmatter'
+ let l:title = 'Mainmatter'
+ elseif a:line =~# '\\backmatter'
+ let l:title = 'Backmatter'
+ elseif a:line =~# '\\appendix'
+ let l:title = 'Appendix'
+ elseif a:line =~# self.re.secpat1
+ let l:title = self.parse_title(matchstr(a:line, self.re.secpat1), 0)
+ elseif a:line =~# self.re.secpat2
+ let l:title = self.parse_title(matchstr(a:line, self.re.secpat2), 1)
+ elseif a:line =~# self.re.fake_sections
+ let l:title = matchstr(a:line, self.re.fake_sections)
+ endif
+
+ let l:level = self.parse_level(v:foldstart, a:level)
+
+ return printf('%-5s %-s', l:level,
+ \ substitute(strpart(l:title, 0, winwidth(0) - 7), '\s\+$', '', ''))
+endfunction
+
+" }}}1
+function! s:folder.parse_level(lnum, level) abort dict " {{{1
+ if !self.parse_levels | return a:level | endif
+
+ if !has_key(self, 'toc')
+ let self.toc = vimtex#toc#new({
+ \ 'name' : 'Fold text ToC',
+ \ 'layers' : ['content'],
+ \ 'refresh_always' : 0,
+ \})
+ let self.toc_updated = 0
+ let self.file_updated = {}
+ endif
+
+ let l:file = expand('%')
+ let l:ftime = getftime(l:file)
+
+ if l:ftime > get(self.file_updated, l:file)
+ \ || localtime() > self.toc_updated + 300
+ call self.toc.get_entries(1)
+ let self.toc_entries = filter(
+ \ self.toc.get_visible_entries(),
+ \ '!empty(v:val.number)')
+ let self.file_updated[l:file] = l:ftime
+ let self.toc_updated = localtime()
+ endif
+
+ let l:entries = filter(deepcopy(self.toc_entries), 'v:val.line == a:lnum')
+ if len(l:entries) > 1
+ call filter(l:entries, "v:val.file ==# expand('%:p')")
+ endif
+
+ return empty(l:entries) ? '' : self.toc.print_number(l:entries[0].number)
+endfunction
+
+" }}}1
+function! s:folder.parse_title(string, type) abort dict " {{{1
+ let l:idx = -1
+ let l:length = strlen(a:string)
+ let l:level = 1
+ while l:level >= 1
+ let l:idx += 1
+ if l:idx > l:length
+ break
+ elseif a:string[l:idx] ==# ['}',']'][a:type]
+ let l:level -= 1
+ elseif a:string[l:idx] ==# ['{','['][a:type]
+ let l:level += 1
+ endif
+ endwhile
+ let l:parsed = strpart(a:string, 0, l:idx)
+ return empty(l:parsed)
+ \ ? '<untitled>' : l:parsed
+endfunction
+
+" }}}1
+function! s:folder.refresh() abort dict " {{{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 == self.time | return | endif
+ let self.time = l:time
+
+ " Initialize
+ let self.folds = []
+ let level = 0
+ let buffer = getline(1,'$')
+
+ " Parse part commands (frontmatter, appendix, etc)
+ " Note: We want a minimum of two top level parts
+ let lines = filter(copy(buffer), 'v:val =~ ''' . self.re.parts . '''')
+ if len(lines) >= 2
+ let level += 1
+ call insert(self.folds, [self.re.parts, level])
+ endif
+
+ " Parse section commands (part, chapter, [sub...]section)
+ let lines = filter(copy(buffer), 'v:val =~ ''' . self.re.any_sections . '''')
+ for part in self.sections
+ let partpattern = '^\s*\%(\\\|% Fake\)' . part . ':\?\>'
+ for line in lines
+ if line =~# partpattern
+ let level += 1
+ call insert(self.folds, [partpattern, level])
+ break
+ endif
+ endfor
+ endfor
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/format.vim b/autoload/vimtex/format.vim
new file mode 100644
index 00000000..b68aa336
--- /dev/null
+++ b/autoload/vimtex/format.vim
@@ -0,0 +1,217 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#format#init_buffer() abort " {{{1
+ if !g:vimtex_format_enabled | return | endif
+
+ setlocal formatexpr=vimtex#format#formatexpr()
+endfunction
+
+" }}}1
+
+function! vimtex#format#formatexpr() abort " {{{1
+ if mode() =~# '[iR]' | return -1 | endif
+
+ " Temporary disable folds and save view
+ let l:save_view = winsaveview()
+ let l:foldenable = &l:foldenable
+ setlocal nofoldenable
+
+ let l:top = v:lnum
+ let l:bottom = v:lnum + v:count - 1
+ let l:lines_old = getline(l:top, l:bottom)
+ let l:tries = 5
+ let s:textwidth = &l:textwidth == 0 ? 79 : &l:textwidth
+
+ " This is a hack to make undo restore the correct position
+ if mode() !=# 'i'
+ normal! ix
+ normal! x
+ endif
+
+ " Main formatting algorithm
+ while l:tries > 0
+ " Format the range of lines
+ let l:bottom = s:format(l:top, l:bottom)
+
+ " Ensure proper indentation
+ if l:top < l:bottom
+ silent! execute printf('normal! %sG=%sG', l:top+1, l:bottom)
+ endif
+
+ " Check if any lines have changed
+ let l:lines_new = getline(l:top, l:bottom)
+ let l:index = s:compare_lines(l:lines_new, l:lines_old)
+ let l:top += l:index
+ if l:top > l:bottom | break | endif
+ let l:lines_old = l:lines_new[l:index : -1]
+ let l:tries -= 1
+ endwhile
+
+ " Restore fold and view
+ let &l:foldenable = l:foldenable
+ call winrestview(l:save_view)
+
+ " Set cursor at appropriate position
+ execute 'normal!' l:bottom . 'G^'
+
+ " Don't change the text if the formatting algorithm failed
+ if l:tries == 0
+ silent! undo
+ call vimtex#log#warning('Formatting of selected text failed!')
+ endif
+endfunction
+
+" }}}1
+
+function! s:format(top, bottom) abort " {{{1
+ let l:bottom = a:bottom
+ let l:mark = a:bottom
+ for l:current in range(a:bottom, a:top, -1)
+ let l:line = getline(l:current)
+
+ if vimtex#util#in_mathzone(l:current, 1)
+ \ && vimtex#util#in_mathzone(l:current, col([l:current, '$']))
+ let l:mark = l:current - 1
+ continue
+ endif
+
+ " Skip all lines with comments
+ if l:line =~# '\v%(^|[^\\])\%'
+ if l:current < l:mark
+ let l:bottom += s:format_build_lines(l:current+1, l:mark)
+ endif
+ let l:mark = l:current - 1
+ continue
+ endif
+
+ " Handle long lines
+ if strdisplaywidth(l:line) > s:textwidth
+ let l:bottom += s:format_build_lines(l:current, l:mark)
+ let l:mark = l:current-1
+ endif
+
+ if l:line =~# s:border_end
+ if l:current < l:mark
+ let l:bottom += s:format_build_lines(l:current+1, l:mark)
+ endif
+ let l:mark = l:current
+ endif
+
+ if l:line =~# s:border_beginning
+ if l:current < l:mark
+ let l:bottom += s:format_build_lines(l:current, l:mark)
+ endif
+ let l:mark = l:current-1
+ endif
+
+ if l:line =~# '^\s*$'
+ let l:bottom += s:format_build_lines(l:current+1, l:mark)
+ let l:mark = l:current-1
+ endif
+ endfor
+
+ if a:top <= l:mark
+ let l:bottom += s:format_build_lines(a:top, l:mark)
+ endif
+
+ return l:bottom
+endfunction
+
+" }}}1
+function! s:format_build_lines(start, end) abort " {{{1
+ "
+ " Get the desired text to format as a list of words, but preserve the ending
+ " line spaces
+ "
+ let l:text = join(map(getline(a:start, a:end),
+ \ 'substitute(v:val, ''^\s*'', '''', '''')'), ' ')
+ let l:spaces = matchstr(l:text, '\s*$')
+ let l:words = split(l:text, ' ')
+ if empty(l:words) | return 0 | endif
+
+ "
+ " Add the words in properly indented and formatted lines
+ "
+ let l:lnum = a:start-1
+ let l:current = s:get_indents(indent(a:start))
+ for l:word in l:words
+ if strdisplaywidth(l:word) + strdisplaywidth(l:current) > s:textwidth
+ call append(l:lnum, substitute(l:current, '\s$', '', ''))
+ let l:lnum += 1
+ let l:current = s:get_indents(VimtexIndent(a:start))
+ endif
+ let l:current .= l:word . ' '
+ endfor
+ if l:current !~# '^\s*$'
+ call append(l:lnum, substitute(l:current, '\s$', '', ''))
+ let l:lnum += 1
+ endif
+
+ "
+ " Append the ending line spaces
+ "
+ if !empty(l:spaces)
+ call setline(l:lnum, getline(l:lnum) . l:spaces)
+ endif
+
+ "
+ " Remove old text
+ "
+ silent! execute printf('%s;+%s delete', l:lnum+1, a:end-a:start)
+
+ "
+ " Return the difference between number of lines of old and new text
+ "
+ return l:lnum - a:end
+endfunction
+
+" }}}1
+
+function! s:compare_lines(new, old) abort " {{{1
+ let l:min_length = min([len(a:new), len(a:old)])
+ for l:i in range(l:min_length)
+ if a:new[l:i] !=# a:old[l:i]
+ return l:i
+ endif
+ endfor
+ return l:min_length
+endfunction
+
+" }}}1
+function! s:get_indents(number) abort " {{{1
+ return !&l:expandtab && &l:shiftwidth == &l:tabstop
+ \ ? repeat("\t", a:number/&l:tabstop)
+ \ : repeat(' ', a:number)
+endfunction
+
+" }}}1
+
+
+" {{{1 Initialize module
+
+let s:border_beginning = '\v^\s*%(' . join([
+ \ '\\item',
+ \ '\\begin',
+ \ '\\end',
+ \ '%(\\\[|\$\$)\s*$',
+ \], '|') . ')'
+
+let s:border_end = '\v\\%(' . join([
+ \ '\\\*?',
+ \ 'clear%(double)?page',
+ \ 'linebreak',
+ \ 'new%(line|page)',
+ \ 'pagebreak',
+ \ '%(begin|end)\{[^}]*\}',
+ \ ], '|') . ')\s*$'
+ \ . '|^\s*%(\\\]|\$\$)\s*$'
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/fzf.vim b/autoload/vimtex/fzf.vim
new file mode 100644
index 00000000..c7f62c99
--- /dev/null
+++ b/autoload/vimtex/fzf.vim
@@ -0,0 +1,114 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#fzf#run(...) abort " {{{1
+ " Arguments: Two optional arguments
+ "
+ " First argument: ToC filter (default: 'ctli')
+ " This may be used to select certain entry types according to the different
+ " "layers" of vimtex-toc:
+ " c: content: This is the main part and the "real" ToC
+ " t: todo: This shows TODOs from comments and `\todo{...}` commands
+ " l: label: This shows `\label{...}` commands
+ " i: include: This shows included files
+ "
+ " Second argument: Custom options for fzf
+ " It should be an object containing the parameters passed to fzf#run().
+
+ " Note: The '--with-nth 3..' option hides the first two words from the fzf
+ " window. These words are the file name and line number and are used by
+ " the sink.
+ let l:opts = extend({
+ \ 'source': <sid>parse_toc(a:0 == 0 ? 'ctli' : a:1),
+ \ 'sink': function('vimtex#fzf#open_selection'),
+ \ 'options': '--ansi --with-nth 3..',
+ \}, a:0 > 1 ? a:2 : {})
+
+ call fzf#run(l:opts)
+endfunction
+
+" }}}1
+function! vimtex#fzf#open_selection(sel) abort " {{{1
+ let line = split(a:sel)[0]
+ let file = split(a:sel)[1]
+ let curr_file = expand('%:p')
+
+ if curr_file == file
+ execute 'normal! ' . line . 'gg'
+ else
+ execute printf('edit +%s %s', line, file)
+ endif
+endfunction
+
+" }}}1
+
+
+function! s:parse_toc(filter) abort " {{{1
+ " Parsing is mostly adapted from the Denite source
+ " (see rplugin/python3/denite/source/vimtex.py)
+ python3 << EOF
+import vim
+import json
+
+def format_number(n):
+ if not n or not type(n) is dict or not 'chapter' in n:
+ return ''
+
+ num = [str(n[k]) for k in [
+ 'chapter',
+ 'section',
+ 'subsection',
+ 'subsubsection',
+ 'subsubsubsection'] if n[k] != '0']
+
+ if n['appendix'] != '0':
+ num[0] = chr(int(num[0]) + 64)
+
+ return '.'.join(num)
+
+def colorize(e):
+ try:
+ from colorama import Fore, Style
+ color = {'content' : Fore.WHITE,
+ 'include' : Fore.BLUE,
+ 'label' : Fore.GREEN,
+ 'todo' : Fore.RED}[e['type']]
+ return f"{color}{e['title']:65}{Style.RESET_ALL}"
+ except ModuleNotFoundError:
+ import os
+ if os.name == 'nt':
+ # Colour support on Windows requires Colorama
+ return f"{e['title']:65}"
+ else:
+ color = {'content' : "\u001b[37m",
+ 'include' : "\u001b[34m",
+ 'label' : "\u001b[32m",
+ 'todo' : "\u001b[31m"}[e['type']]
+ return f"{color}{e['title']:65}\u001b[0m"
+
+def create_candidate(e, depth):
+ number = format_number(dict(e['number']))
+ return f"{e.get('line', 0)} {e['file']} {colorize(e)} {number}"
+
+entries = vim.eval('vimtex#parser#toc()')
+depth = max([int(e['level']) for e in entries])
+filter = vim.eval("a:filter")
+candidates = [create_candidate(e, depth)
+ for e in entries if e['type'][0] in filter]
+
+# json.dumps will convert single quotes to double quotes
+# so that vim understands the ansi escape sequences
+vim.command(f"let candidates = {json.dumps(candidates)}")
+EOF
+
+ return candidates
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/imaps.vim b/autoload/vimtex/imaps.vim
new file mode 100644
index 00000000..6b682f0c
--- /dev/null
+++ b/autoload/vimtex/imaps.vim
@@ -0,0 +1,192 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#imaps#init_buffer() abort " {{{1
+ if !g:vimtex_imaps_enabled | return | endif
+
+ "
+ " Create imaps
+ "
+ let l:maps = g:vimtex_imaps_list
+ for l:disable in g:vimtex_imaps_disabled
+ let l:maps = filter(l:maps, 'v:val.lhs !=# ''' . l:disable . '''')
+ endfor
+ for l:map in l:maps + get(s:, 'custom_maps', [])
+ call s:create_map(l:map)
+ endfor
+
+ "
+ " Add mappings and commands
+ "
+ command! -buffer VimtexImapsList call vimtex#imaps#list()
+ nnoremap <buffer> <plug>(vimtex-imaps-list) :call vimtex#imaps#list()<cr>
+endfunction
+
+" }}}1
+
+function! vimtex#imaps#add_map(map) abort " {{{1
+ let s:custom_maps = get(s:, 'custom_maps', []) + [a:map]
+
+ if exists('s:created_maps')
+ call s:create_map(a:map)
+ endif
+endfunction
+
+" }}}1
+function! vimtex#imaps#list() abort " {{{1
+ silent new vimtex\ imaps
+
+ for l:map in s:created_maps
+ call append('$', printf('%5S -> %-30S %S',
+ \ get(l:map, 'leader', get(g:, 'vimtex_imaps_leader', '`')) . l:map.lhs,
+ \ l:map.rhs,
+ \ get(l:map, 'wrapper', 'vimtex#imaps#wrap_math')))
+ endfor
+ 0delete _
+
+ nnoremap <silent><nowait><buffer> q :bwipeout<cr>
+ nnoremap <silent><nowait><buffer> <esc> :bwipeout<cr>
+
+ setlocal bufhidden=wipe
+ setlocal buftype=nofile
+ setlocal concealcursor=nvic
+ setlocal conceallevel=0
+ setlocal cursorline
+ setlocal nobuflisted
+ setlocal nolist
+ setlocal nospell
+ setlocal noswapfile
+ setlocal nowrap
+ setlocal nonumber
+ setlocal norelativenumber
+ setlocal nomodifiable
+
+ syntax match VimtexImapsLhs /^.*\ze->/ nextgroup=VimtexImapsArrow
+ syntax match VimtexImapsArrow /->/ contained nextgroup=VimtexImapsRhs
+ syntax match VimtexImapsRhs /\s*\S*/ contained nextgroup=VimtexImapsWrapper
+ syntax match VimtexImapsWrapper /.*/ contained
+endfunction
+
+" }}}1
+
+"
+" The imap generator
+"
+function! s:create_map(map) abort " {{{1
+ if index(s:created_maps, a:map) >= 0 | return | endif
+
+ let l:leader = get(a:map, 'leader', get(g:, 'vimtex_imaps_leader', '`'))
+ if l:leader !=# '' && !hasmapto(l:leader, 'i')
+ silent execute 'inoremap <silent><nowait><buffer>' l:leader . l:leader l:leader
+ endif
+ let l:lhs = l:leader . a:map.lhs
+
+ let l:wrapper = get(a:map, 'wrapper', 'vimtex#imaps#wrap_math')
+ if ! exists('*' . l:wrapper)
+ echoerr 'vimtex error: imaps wrapper does not exist!'
+ echoerr ' ' . l:wrapper
+ return
+ endif
+
+ " Some wrappers use a context which must be made available to the wrapper
+ " function at run time.
+ if has_key(a:map, 'context')
+ execute 'let l:key = "' . escape(l:lhs, '<') . '"'
+ let l:key .= a:map.rhs
+ if !exists('b:vimtex_context')
+ let b:vimtex_context = {}
+ endif
+ let b:vimtex_context[l:key] = a:map.context
+ endif
+
+ " The rhs may be evaluated before being passed to wrapper, unless expr is
+ " disabled (which it is by default)
+ if !get(a:map, 'expr')
+ let a:map.rhs = string(a:map.rhs)
+ endif
+
+ silent execute 'inoremap <expr><silent><nowait><buffer>' l:lhs
+ \ l:wrapper . '("' . escape(l:lhs, '\') . '", ' . a:map.rhs . ')'
+
+ let s:created_maps += [a:map]
+endfunction
+
+" }}}1
+
+"
+" Wrappers
+"
+function! vimtex#imaps#wrap_trivial(lhs, rhs) abort " {{{1
+ return a:rhs
+endfunction
+
+" }}}1
+function! vimtex#imaps#wrap_math(lhs, rhs) abort " {{{1
+ return s:is_math() ? a:rhs : a:lhs
+endfunction
+
+" }}}1
+function! vimtex#imaps#wrap_environment(lhs, rhs) abort " {{{1
+ let l:return = a:lhs
+ let l:cursor = vimtex#pos#val(vimtex#pos#get_cursor())
+ let l:value = 0
+
+ for l:context in b:vimtex_context[a:lhs . a:rhs]
+ if type(l:context) == type('')
+ let l:envs = [l:context]
+ let l:rhs = a:rhs
+ elseif type(l:context) == type({})
+ let l:envs = l:context.envs
+ let l:rhs = l:context.rhs
+ endif
+
+ for l:env in l:envs
+ let l:candidate_value = vimtex#pos#val(vimtex#env#is_inside(l:env))
+ if l:candidate_value > l:value
+ let l:value = l:candidate_value
+ let l:return = l:rhs
+ endif
+ endfor
+
+ unlet l:context
+ endfor
+
+ return l:return
+endfunction
+
+" }}}1
+
+"
+" Special rhs styles
+"
+function! vimtex#imaps#style_math(command) " {{{1
+ return s:is_math()
+ \ ? '\' . a:command . '{' . nr2char(getchar()) . '}'
+ \ : ''
+endfunction
+
+" }}}1
+
+"
+" Helpers
+"
+function! s:is_math() abort " {{{1
+ return match(map(synstack(line('.'), max([col('.') - 1, 1])),
+ \ 'synIDattr(v:val, ''name'')'), '^texMathZone') >= 0
+endfunction
+
+" }}}1
+
+
+" {{{1 Initialize module
+
+let s:created_maps = []
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/include.vim b/autoload/vimtex/include.vim
new file mode 100644
index 00000000..2a58dffe
--- /dev/null
+++ b/autoload/vimtex/include.vim
@@ -0,0 +1,147 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#include#expr() abort " {{{1
+ call s:visited.timeout()
+ let l:fname = substitute(v:fname, '^\s*\|\s*$', '', 'g')
+
+ "
+ " Check if v:fname matches exactly
+ "
+ if filereadable(l:fname)
+ return s:visited.check(l:fname)
+ endif
+
+ "
+ " Parse \include or \input style lines
+ "
+ let l:file = s:input(l:fname, 'tex')
+ for l:candidate in [l:file, l:file . '.tex']
+ if filereadable(l:candidate)
+ return s:visited.check(l:candidate)
+ endif
+ endfor
+
+ "
+ " Parse \bibliography or \addbibresource
+ "
+ let l:candidate = s:input(l:fname, 'bib')
+ if filereadable(l:candidate)
+ return s:visited.check(l:candidate)
+ endif
+
+ "
+ " Check if v:fname matches in $TEXINPUTS
+ "
+ let l:candidate = s:search_candidates_texinputs(l:fname)
+ if !empty(l:candidate)
+ return s:visited.check(l:candidate)
+ endif
+
+ "
+ " Search for file with kpsewhich
+ "
+ if g:vimtex_include_search_enabled
+ let l:candidate = s:search_candidates_kpsewhich(l:fname)
+ if !empty(l:candidate)
+ return s:visited.check(l:candidate)
+ endif
+ endif
+
+ return s:visited.check(l:fname)
+endfunction
+
+" }}}1
+
+function! s:input(fname, type) abort " {{{1
+ let [l:lnum, l:cnum] = searchpos(g:vimtex#re#{a:type}_input, 'bcn', line('.'))
+ if l:lnum == 0 | return a:fname | endif
+
+ let l:cmd = vimtex#cmd#get_at(l:lnum, l:cnum)
+ let l:file = join(map(
+ \ get(l:cmd, 'args', [{}]),
+ \ "get(v:val, 'text', '')"),
+ \ '')
+ let l:file = substitute(l:file, '^\s*"\|"\s*$', '', 'g')
+ let l:file = substitute(l:file, '\\space', '', 'g')
+
+ if l:file[-3:] !=# a:type
+ let l:file .= '.' . a:type
+ endif
+
+ return l:file
+endfunction
+
+" }}}1
+function! s:search_candidates_texinputs(fname) abort " {{{1
+ for l:suffix in [''] + split(&l:suffixesadd, ',')
+ let l:candidates = glob(b:vimtex.root . '/**/' . a:fname . l:suffix, 0, 1)
+ if !empty(l:candidates)
+ return l:candidates[0]
+ endif
+ endfor
+
+ return ''
+endfunction
+
+" }}}1
+function! s:search_candidates_kpsewhich(fname) abort " {{{1
+ " Split input list on commas, and if applicable, ensure that the entry that
+ " the cursor is on is placed first in the queue
+ let l:files = split(a:fname, '\s*,\s*')
+ let l:current = expand('<cword>')
+ let l:index = index(l:files, l:current)
+ if l:index >= 0
+ call remove(l:files, l:index)
+ let l:files = [l:current] + l:files
+ endif
+
+ " Add file extensions to create the final list of candidate files
+ let l:candidates = []
+ for l:file in l:files
+ if !empty(fnamemodify(l:file, ':e'))
+ call add(l:candidates, l:file)
+ else
+ call extend(l:candidates, map(split(&l:suffixesadd, ','), 'l:file . v:val'))
+ endif
+ endfor
+
+ for l:file in l:candidates
+ let l:candidate = vimtex#kpsewhich#find(l:file)
+ if !empty(l:candidate) && filereadable(l:candidate) | return l:candidate | endif
+ endfor
+
+ return ''
+endfunction
+
+" }}}1
+
+let s:visited = {
+ \ 'time' : 0,
+ \ 'list' : [],
+ \}
+function! s:visited.timeout() abort dict " {{{1
+ if localtime() - self.time > 1
+ let self.time = localtime()
+ let self.list = [expand('%:p')]
+ endif
+endfunction
+
+" }}}1
+function! s:visited.check(fname) abort dict " {{{1
+ if index(self.list, fnamemodify(a:fname, ':p')) < 0
+ call add(self.list, fnamemodify(a:fname, ':p'))
+ return a:fname
+ endif
+
+ return ''
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/info.vim b/autoload/vimtex/info.vim
new file mode 100644
index 00000000..1057b578
--- /dev/null
+++ b/autoload/vimtex/info.vim
@@ -0,0 +1,222 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#info#init_buffer() abort " {{{1
+ command! -buffer -bang VimtexInfo call vimtex#info#open(<q-bang> == '!')
+
+ nnoremap <buffer> <plug>(vimtex-info) :VimtexInfo<cr>
+ nnoremap <buffer> <plug>(vimtex-info-full) :VimtexInfo!<cr>
+endfunction
+
+" }}}1
+function! vimtex#info#open(global) abort " {{{1
+ let s:info.global = a:global
+ call vimtex#scratch#new(s:info)
+endfunction
+
+" }}}1
+
+
+let s:info = {
+ \ 'name' : 'VimtexInfo',
+ \ 'global' : 0,
+ \}
+function! s:info.print_content() abort dict " {{{1
+ for l:line in self.gather_system_info()
+ call append('$', l:line)
+ endfor
+ call append('$', '')
+ for l:line in self.gather_state_info()
+ call append('$', l:line)
+ endfor
+endfunction
+
+" }}}1
+function! s:info.gather_system_info() abort dict " {{{1
+ let l:lines = [
+ \ 'System info',
+ \ ' OS: ' . s:get_os_info(),
+ \ ' Vim version: ' . s:get_vim_info(),
+ \]
+
+ if has('clientserver') || has('nvim')
+ call add(l:lines, ' Has clientserver: true')
+ call add(l:lines, ' Servername: '
+ \ . (empty(v:servername) ? 'undefined (vim started without --servername)' : v:servername))
+ else
+ call add(l:lines, ' Has clientserver: false')
+ endif
+
+ return l:lines
+endfunction
+
+" }}}1
+function! s:info.gather_state_info() abort dict " {{{1
+ if self.global
+ let l:lines = []
+ for l:data in vimtex#state#list_all()
+ let l:lines += s:get_info(l:data)
+ let l:lines += ['']
+ endfor
+ call remove(l:lines, -1)
+ else
+ let l:lines = s:get_info(b:vimtex)
+ endif
+
+ return l:lines
+endfunction
+
+" }}}1
+function! s:info.syntax() abort dict " {{{1
+ syntax match VimtexInfoOther /.*/
+ syntax match VimtexInfoKey /^.*:/ nextgroup=VimtexInfoValue
+ syntax match VimtexInfoValue /.*/ contained
+ syntax match VimtexInfoTitle /vimtex project:/ nextgroup=VimtexInfoValue
+ syntax match VimtexInfoTitle /System info/
+endfunction
+
+" }}}1
+
+"
+" Functions to parse the vimtex state data
+"
+function! s:get_info(item, ...) abort " {{{1
+ if empty(a:item) | return [] | endif
+ let l:indent = a:0 > 0 ? a:1 : 0
+
+ if type(a:item) == type({})
+ return s:parse_dict(a:item, l:indent)
+ endif
+
+ if type(a:item) == type([])
+ let l:entries = []
+ for [l:title, l:Value] in a:item
+ if type(l:Value) == type({})
+ call extend(l:entries, s:parse_dict(l:Value, l:indent, l:title))
+ elseif type(l:Value) == type([])
+ call extend(l:entries, s:parse_list(l:Value, l:indent, l:title))
+ else
+ call add(l:entries,
+ \ repeat(' ', l:indent) . printf('%s: %s', l:title, l:Value))
+ endif
+ unlet l:Value
+ endfor
+ return l:entries
+ endif
+endfunction
+
+" }}}1
+function! s:parse_dict(dict, indent, ...) abort " {{{1
+ if empty(a:dict) | return [] | endif
+ let l:dict = a:dict
+ let l:indent = a:indent
+ let l:entries = []
+
+ if a:0 > 0
+ let l:title = a:1
+ let l:name = ''
+ if has_key(a:dict, 'name')
+ let l:dict = deepcopy(a:dict)
+ let l:name = remove(l:dict, 'name')
+ endif
+ call add(l:entries,
+ \ repeat(' ', l:indent) . printf('%s: %s', l:title, l:name))
+ let l:indent += 1
+ endif
+
+ let l:items = has_key(l:dict, 'pprint_items')
+ \ ? l:dict.pprint_items() : items(l:dict)
+
+ return extend(l:entries, s:get_info(l:items, l:indent))
+endfunction
+
+" }}}1
+function! s:parse_list(list, indent, title) abort " {{{1
+ if empty(a:list) | return [] | endif
+
+ let l:entries = []
+ let l:indent = repeat(' ', a:indent)
+ if type(a:list[0]) == type([])
+ let l:name = ''
+ let l:index = 0
+
+ " l:entry[0] == title
+ " l:entry[1] == value
+ for l:entry in a:list
+ if l:entry[0] ==# 'name'
+ let l:name = l:entry[1]
+ break
+ endif
+ let l:index += 1
+ endfor
+
+ if empty(l:name)
+ let l:list = a:list
+ else
+ let l:list = deepcopy(a:list)
+ call remove(l:list, l:index)
+ endif
+
+ call add(l:entries, l:indent . printf('%s: %s', a:title, l:name))
+ call extend(l:entries, s:get_info(l:list, a:indent+1))
+ else
+ call add(l:entries, l:indent . printf('%s:', a:title))
+ for l:value in a:list
+ call add(l:entries, l:indent . printf(' %s', l:value))
+ endfor
+ endif
+
+ return l:entries
+endfunction
+
+" }}}1
+
+"
+" Other utility functions
+"
+function! s:get_os_info() abort " {{{1
+ let l:os = vimtex#util#get_os()
+
+ if l:os ==# 'linux'
+ let l:result = executable('lsb_release')
+ \ ? system('lsb_release -d')[12:-2]
+ \ : system('uname -sr')[:-2]
+ return substitute(l:result, '^\s*', '', '')
+ elseif l:os ==# 'mac'
+ let l:name = system('sw_vers -productName')[:-2]
+ let l:version = system('sw_vers -productVersion')[:-2]
+ let l:build = system('sw_vers -buildVersion')[:-2]
+ return l:name . ' ' . l:version . ' (' . l:build . ')'
+ else
+ if !exists('s:win_info')
+ let s:win_info = vimtex#process#capture('systeminfo')
+ endif
+
+ let l:name = matchstr(s:win_info[1], ':\s*\zs.*')
+ let l:version = matchstr(s:win_info[2], ':\s*\zs.*')
+ return l:name . ' (' . l:version . ')'
+ endif
+endfunction
+
+" }}}1
+function! s:get_vim_info() abort " {{{1
+ let l:info = vimtex#util#command('version')
+
+ if has('nvim')
+ return l:info[0]
+ else
+ let l:version = 'VIM ' . strpart(l:info[0], 18, 3) . ' ('
+ let l:index = 2 - (l:info[1] =~# ':\s*\d')
+ let l:version .= matchstr(l:info[l:index], ':\s*\zs.*') . ')'
+ return l:version
+ endif
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/kpsewhich.vim b/autoload/vimtex/kpsewhich.vim
new file mode 100644
index 00000000..65533e28
--- /dev/null
+++ b/autoload/vimtex/kpsewhich.vim
@@ -0,0 +1,53 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#kpsewhich#find(file) abort " {{{1
+ return s:find_cached(a:file)
+endfunction
+
+" }}}1
+function! vimtex#kpsewhich#run(args) abort " {{{1
+ " kpsewhich should be run at the project root directory
+ if exists('b:vimtex.root')
+ call vimtex#paths#pushd(b:vimtex.root)
+ endif
+ let l:output = vimtex#process#capture('kpsewhich ' . a:args)
+ if exists('b:vimtex.root')
+ call vimtex#paths#popd()
+ endif
+
+ " Remove warning lines from output
+ call filter(l:output, 'stridx(v:val, "kpsewhich: warning: ") == -1')
+
+ return l:output
+endfunction
+
+" }}}1
+
+function! s:find(file) abort " {{{1
+ let l:output = vimtex#kpsewhich#run(fnameescape(a:file))
+ if empty(l:output) | return '' | endif
+
+ let l:filename = l:output[0]
+
+ " Ensure absolute path
+ if !vimtex#paths#is_abs(l:filename) && exists('b:vimtex.root')
+ let l:filename = simplify(b:vimtex.root . '/' . l:filename)
+ endif
+
+ return l:filename
+endfunction
+
+" Use caching if possible (requires 'lambda' feature)
+let s:find_cached = has('lambda')
+ \ ? vimtex#cache#wrap(function('s:find'), 'kpsewhich')
+ \ : function('s:find')
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/log.vim b/autoload/vimtex/log.vim
new file mode 100644
index 00000000..f7569a65
--- /dev/null
+++ b/autoload/vimtex/log.vim
@@ -0,0 +1,137 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#log#init_buffer() abort " {{{1
+ command! -buffer -bang VimtexLog call vimtex#log#open()
+
+ nnoremap <buffer> <plug>(vimtex-log) :VimtexLog<cr>
+endfunction
+
+" }}}1
+
+function! vimtex#log#info(...) abort " {{{1
+ call s:logger.add(a:000, 'info')
+endfunction
+
+" }}}1
+function! vimtex#log#warning(...) abort " {{{1
+ call s:logger.add(a:000, 'warning')
+endfunction
+
+" }}}1
+function! vimtex#log#error(...) abort " {{{1
+ call s:logger.add(a:000, 'error')
+endfunction
+
+" }}}1
+
+function! vimtex#log#get() abort " {{{1
+ return s:logger.entries
+endfunction
+
+" }}}1
+
+function! vimtex#log#open() abort " {{{1
+ call vimtex#scratch#new(s:logger)
+endfunction
+
+" }}}1
+function! vimtex#log#toggle_verbose() abort " {{{1
+ if s:logger.verbose
+ let s:logger.verbose = 0
+ call vimtex#log#info('Logging is now quiet')
+ else
+ call vimtex#log#info('Logging is now verbose')
+ let s:logger.verbose = 1
+ endif
+endfunction
+
+" }}}1
+
+
+let s:logger = {
+ \ 'name' : 'VimtexMessageLog',
+ \ 'entries' : [],
+ \ 'type_to_highlight' : {
+ \ 'info' : 'VimtexInfo',
+ \ 'warning' : 'VimtexWarning',
+ \ 'error' : 'VimtexError',
+ \ },
+ \ 'verbose' : get(g:, 'vimtex_log_verbose', 1),
+ \}
+function! s:logger.add(msg_arg, type) abort dict " {{{1
+ let l:msg_list = []
+ for l:msg in a:msg_arg
+ if type(l:msg) == type('')
+ call add(l:msg_list, l:msg)
+ elseif type(l:msg) == type([])
+ call extend(l:msg_list, filter(l:msg, "type(v:val) == type('')"))
+ endif
+ endfor
+
+ let l:entry = {}
+ let l:entry.type = a:type
+ let l:entry.time = strftime('%T')
+ let l:entry.callstack = vimtex#debug#stacktrace()[1:]
+ let l:entry.msg = l:msg_list
+ call add(self.entries, l:entry)
+
+ if !self.verbose | return | endif
+
+ " Ignore message
+ for l:re in get(g:, 'vimtex_log_ignore', [])
+ if join(l:msg_list) =~# l:re | return | endif
+ endfor
+
+ call vimtex#echo#formatted([
+ \ [self.type_to_highlight[a:type], 'vimtex:'],
+ \ ' ' . l:msg_list[0]
+ \])
+ for l:line in l:msg_list[1:]
+ call vimtex#echo#echo(' ' . l:line)
+ endfor
+endfunction
+
+" }}}1
+function! s:logger.print_content() abort dict " {{{1
+ for l:entry in self.entries
+ call append('$', printf('%s: %s', l:entry.time, l:entry.type))
+ for l:stack in l:entry.callstack
+ if l:stack.lnum > 0
+ call append('$', printf(' #%d %s:%d', l:stack.nr, l:stack.filename, l:stack.lnum))
+ else
+ call append('$', printf(' #%d %s', l:stack.nr, l:stack.filename))
+ endif
+ call append('$', printf(' In %s', l:stack.function))
+ if !empty(l:stack.text)
+ call append('$', printf(' %s', l:stack.text))
+ endif
+ endfor
+ for l:msg in l:entry.msg
+ call append('$', printf(' %s', l:msg))
+ endfor
+ call append('$', '')
+ endfor
+endfunction
+
+" }}}1
+function! s:logger.syntax() abort dict " {{{1
+ syntax match VimtexInfoOther /.*/
+
+ syntax include @VIM syntax/vim.vim
+ syntax match VimtexInfoVimCode /^ .*/ transparent contains=@VIM
+
+ syntax match VimtexInfoKey /^\S*:/ nextgroup=VimtexInfoValue
+ syntax match VimtexInfoKey /^ #\d\+/ nextgroup=VimtexInfoValue
+ syntax match VimtexInfoKey /^ In/ nextgroup=VimtexInfoValue
+ syntax match VimtexInfoValue /.*/ contained
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/matchparen.vim b/autoload/vimtex/matchparen.vim
new file mode 100644
index 00000000..80442cd9
--- /dev/null
+++ b/autoload/vimtex/matchparen.vim
@@ -0,0 +1,111 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#matchparen#init_buffer() abort " {{{1
+ if !g:vimtex_matchparen_enabled | return | endif
+
+ call vimtex#matchparen#enable()
+endfunction
+
+" }}}1
+
+function! vimtex#matchparen#enable() abort " {{{1
+ call s:matchparen.enable()
+endfunction
+
+" }}}1
+function! vimtex#matchparen#disable() abort " {{{1
+ call s:matchparen.disable()
+endfunction
+
+" }}}1
+function! vimtex#matchparen#popup_check(...) abort " {{{1
+ if pumvisible()
+ call s:matchparen.highlight()
+ endif
+endfunction
+
+" }}}1
+
+let s:matchparen = {}
+
+function! s:matchparen.enable() abort dict " {{{1
+ " vint: -ProhibitAutocmdWithNoGroup
+
+ execute 'augroup vimtex_matchparen' . bufnr('%')
+ autocmd!
+ autocmd CursorMoved <buffer> call s:matchparen.highlight()
+ autocmd CursorMovedI <buffer> call s:matchparen.highlight()
+ try
+ autocmd TextChangedP <buffer> call s:matchparen.highlight()
+ catch /E216/
+ silent! let self.timer =
+ \ timer_start(50, 'vimtex#matchparen#popup_check', {'repeat' : -1})
+ endtry
+ augroup END
+
+ call self.highlight()
+
+ " vint: +ProhibitAutocmdWithNoGroup
+endfunction
+
+" }}}1
+function! s:matchparen.disable() abort dict " {{{1
+ call self.clear()
+ execute 'autocmd! vimtex_matchparen' . bufnr('%')
+ silent! call timer_stop(self.timer)
+endfunction
+
+" }}}1
+function! s:matchparen.clear() abort dict " {{{1
+ silent! call matchdelete(w:vimtex_match_id1)
+ silent! call matchdelete(w:vimtex_match_id2)
+ unlet! w:vimtex_match_id1
+ unlet! w:vimtex_match_id2
+endfunction
+function! s:matchparen.highlight() abort dict " {{{1
+ call self.clear()
+
+ if vimtex#util#in_comment() | return | endif
+
+ " This is a hack to ensure that $ in visual block mode adhers to the rule
+ " specified in :help v_$
+ if mode() ==# "\<c-v>"
+ let l:pos = vimtex#pos#get_cursor()
+ if len(l:pos) == 5 && l:pos[-1] == 2147483647
+ call feedkeys('$', 'in')
+ endif
+ endif
+
+ let l:current = vimtex#delim#get_current('all', 'both')
+ if empty(l:current) | return | endif
+
+ let l:corresponding = vimtex#delim#get_matching(l:current)
+ if empty(l:corresponding) | return | endif
+ if empty(l:corresponding.match) | return | endif
+
+ let [l:open, l:close] = l:current.is_open
+ \ ? [l:current, l:corresponding]
+ \ : [l:corresponding, l:current]
+
+ if exists('*matchaddpos')
+ let w:vimtex_match_id1 = matchaddpos('MatchParen',
+ \ [[l:open.lnum, l:open.cnum, strlen(l:open.match)]])
+ let w:vimtex_match_id2 = matchaddpos('MatchParen',
+ \ [[l:close.lnum, l:close.cnum, strlen(l:close.match)]])
+ else
+ let w:vimtex_match_id1 = matchadd('MatchParen',
+ \ '\%' . l:open.lnum . 'l\%' . l:open.cnum . 'c' . l:open.re.this)
+ let w:vimtex_match_id2 = matchadd('MatchParen',
+ \ '\%' . l:close.lnum . 'l\%' . l:close.cnum . 'c' . l:close.re.this)
+ endif
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/misc.vim b/autoload/vimtex/misc.vim
new file mode 100644
index 00000000..1b39a645
--- /dev/null
+++ b/autoload/vimtex/misc.vim
@@ -0,0 +1,158 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#misc#init_buffer() abort " {{{1
+ command! -buffer VimtexReload call vimtex#misc#reload()
+ command! -buffer -bang -range=% VimtexCountWords
+ \ call vimtex#misc#wordcount_display({
+ \ 'range' : [<line1>, <line2>],
+ \ 'detailed' : <q-bang> == '!',
+ \ 'count_letters' : 0,
+ \ })
+ command! -buffer -bang -range=% VimtexCountLetters
+ \ call vimtex#misc#wordcount_display({
+ \ 'range' : [<line1>, <line2>],
+ \ 'detailed' : <q-bang> == '!',
+ \ 'count_letters' : 1,
+ \ })
+
+ nnoremap <buffer> <plug>(vimtex-reload) :VimtexReload<cr>
+endfunction
+
+" }}}1
+
+function! vimtex#misc#get_graphicspath(fname) abort " {{{1
+ for l:root in b:vimtex.graphicspath + ['.']
+ let l:candidate = simplify(b:vimtex.root . '/' . l:root . '/' . a:fname)
+ for l:suffix in ['', '.jpg', '.png', '.pdf']
+ if filereadable(l:candidate . l:suffix)
+ return l:candidate . l:suffix
+ endif
+ endfor
+ endfor
+
+ return a:fname
+endfunction
+
+" }}}1
+function! vimtex#misc#wordcount(...) abort " {{{1
+ let l:opts = a:0 > 0 ? a:1 : {}
+
+ let l:range = get(l:opts, 'range', [1, line('$')])
+ if l:range == [1, line('$')]
+ let l:file = b:vimtex
+ else
+ let l:file = vimtex#parser#selection_to_texfile('arg', l:range)
+ endif
+
+ let cmd = 'cd ' . vimtex#util#shellescape(l:file.root)
+ let cmd .= has('win32') ? '& ' : '; '
+ let cmd .= 'texcount -nosub -sum '
+ let cmd .= get(l:opts, 'count_letters') ? '-letter ' : ''
+ let cmd .= get(l:opts, 'detailed') ? '-inc ' : '-q -1 -merge '
+ let cmd .= g:vimtex_texcount_custom_arg . ' '
+ let cmd .= vimtex#util#shellescape(l:file.base)
+ let lines = vimtex#process#capture(cmd)
+
+ if l:file.base !=# b:vimtex.base
+ call delete(l:file.tex)
+ endif
+
+ if get(l:opts, 'detailed')
+ return lines
+ else
+ call filter(lines, 'v:val !~# ''ERROR\|^\s*$''')
+ return join(lines, '')
+ endif
+endfunction
+
+" }}}1
+function! vimtex#misc#wordcount_display(opts) abort " {{{1
+ let output = vimtex#misc#wordcount(a:opts)
+
+ if !get(a:opts, 'detailed')
+ call vimtex#log#info('Counted '
+ \ . (get(a:opts, 'count_letters') ? 'letters: ' : 'words: ')
+ \ . output)
+ return
+ endif
+
+ " Create wordcount window
+ if bufnr('TeXcount') >= 0
+ bwipeout TeXcount
+ endif
+ split TeXcount
+
+ " Add lines to buffer
+ for line in output
+ call append('$', printf('%s', line))
+ endfor
+ 0delete _
+
+ " Set mappings
+ nnoremap <buffer><nowait><silent> q :bwipeout<cr>
+
+ " Set buffer options
+ setlocal bufhidden=wipe
+ setlocal buftype=nofile
+ setlocal cursorline
+ setlocal nobuflisted
+ setlocal nolist
+ setlocal nospell
+ setlocal noswapfile
+ setlocal nowrap
+ setlocal tabstop=8
+ setlocal nomodifiable
+
+ " Set highlighting
+ syntax match TexcountText /^.*:.*/ contains=TexcountValue
+ syntax match TexcountValue /.*:\zs.*/
+ highlight link TexcountText VimtexMsg
+ highlight link TexcountValue Constant
+endfunction
+
+" }}}1
+" {{{1 function! vimtex#misc#reload()
+if get(s:, 'reload_guard', 1)
+ function! vimtex#misc#reload() abort
+ let s:reload_guard = 0
+
+ for l:file in glob(fnamemodify(s:file, ':h') . '/../**/*.vim', 0, 1)
+ execute 'source' l:file
+ endfor
+
+ " Temporarily unset b:current_syntax (if active)
+ let l:reload_syntax = get(b:, 'current_syntax', '') ==# 'tex'
+ if l:reload_syntax
+ unlet b:current_syntax
+ endif
+
+ call vimtex#init()
+
+ " Reload syntax
+ if l:reload_syntax
+ runtime! syntax/tex.vim
+ endif
+
+ " Reload indent file
+ if exists('b:did_vimtex_indent')
+ unlet b:did_indent
+ runtime indent/tex.vim
+ endif
+
+ call vimtex#log#info('The plugin has been reloaded!')
+ unlet s:reload_guard
+ endfunction
+endif
+
+" }}}1
+
+
+let s:file = expand('<sfile>')
+
+endif
diff --git a/autoload/vimtex/motion.vim b/autoload/vimtex/motion.vim
new file mode 100644
index 00000000..86b42055
--- /dev/null
+++ b/autoload/vimtex/motion.vim
@@ -0,0 +1,207 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#motion#init_buffer() abort " {{{1
+ if !g:vimtex_motion_enabled | return | endif
+
+ " Utility map to avoid conflict with "normal" command
+ nnoremap <buffer> <sid>(v) v
+ nnoremap <buffer> <sid>(V) V
+
+ " Matching pairs
+ nnoremap <silent><buffer> <plug>(vimtex-%) :call vimtex#motion#find_matching_pair()<cr>
+ xnoremap <silent><buffer> <sid>(vimtex-%) :<c-u>call vimtex#motion#find_matching_pair(1)<cr>
+ xmap <silent><buffer> <plug>(vimtex-%) <sid>(vimtex-%)
+ onoremap <silent><buffer> <plug>(vimtex-%) :execute "normal \<sid>(v)\<sid>(vimtex-%)"<cr>
+
+ " Sections
+ nnoremap <silent><buffer> <plug>(vimtex-]]) :<c-u>call vimtex#motion#section(0,0,0)<cr>
+ nnoremap <silent><buffer> <plug>(vimtex-][) :<c-u>call vimtex#motion#section(1,0,0)<cr>
+ nnoremap <silent><buffer> <plug>(vimtex-[]) :<c-u>call vimtex#motion#section(1,1,0)<cr>
+ nnoremap <silent><buffer> <plug>(vimtex-[[) :<c-u>call vimtex#motion#section(0,1,0)<cr>
+ xnoremap <silent><buffer> <sid>(vimtex-]]) :<c-u>call vimtex#motion#section(0,0,1)<cr>
+ xnoremap <silent><buffer> <sid>(vimtex-][) :<c-u>call vimtex#motion#section(1,0,1)<cr>
+ xnoremap <silent><buffer> <sid>(vimtex-[]) :<c-u>call vimtex#motion#section(1,1,1)<cr>
+ xnoremap <silent><buffer> <sid>(vimtex-[[) :<c-u>call vimtex#motion#section(0,1,1)<cr>
+ xmap <silent><buffer> <plug>(vimtex-]]) <sid>(vimtex-]])
+ xmap <silent><buffer> <plug>(vimtex-][) <sid>(vimtex-][)
+ xmap <silent><buffer> <plug>(vimtex-[]) <sid>(vimtex-[])
+ xmap <silent><buffer> <plug>(vimtex-[[) <sid>(vimtex-[[)
+ onoremap <silent><buffer> <plug>(vimtex-]])
+ \ :execute "normal \<sid>(V)" . v:count1 . "\<sid>(vimtex-]])"<cr>
+ onoremap <silent><buffer> <plug>(vimtex-][)
+ \ :execute "normal \<sid>(V)" . v:count1 . "\<sid>(vimtex-][)"<cr>
+ onoremap <silent><buffer> <plug>(vimtex-[])
+ \ :execute "normal \<sid>(V)" . v:count1 . "\<sid>(vimtex-[])"<cr>
+ onoremap <silent><buffer> <plug>(vimtex-[[)
+ \ :execute "normal \<sid>(V)" . v:count1 . "\<sid>(vimtex-[[)"<cr>
+
+ " Environments
+ nnoremap <silent><buffer> <plug>(vimtex-]m) :<c-u>call vimtex#motion#environment(1,0,0)<cr>
+ nnoremap <silent><buffer> <plug>(vimtex-]M) :<c-u>call vimtex#motion#environment(0,0,0)<cr>
+ nnoremap <silent><buffer> <plug>(vimtex-[m) :<c-u>call vimtex#motion#environment(1,1,0)<cr>
+ nnoremap <silent><buffer> <plug>(vimtex-[M) :<c-u>call vimtex#motion#environment(0,1,0)<cr>
+ xnoremap <silent><buffer> <sid>(vimtex-]m) :<c-u>call vimtex#motion#environment(1,0,1)<cr>
+ xnoremap <silent><buffer> <sid>(vimtex-]M) :<c-u>call vimtex#motion#environment(0,0,1)<cr>
+ xnoremap <silent><buffer> <sid>(vimtex-[m) :<c-u>call vimtex#motion#environment(1,1,1)<cr>
+ xnoremap <silent><buffer> <sid>(vimtex-[M) :<c-u>call vimtex#motion#environment(0,1,1)<cr>
+ xmap <silent><buffer> <plug>(vimtex-]m) <sid>(vimtex-]m)
+ xmap <silent><buffer> <plug>(vimtex-]M) <sid>(vimtex-]M)
+ xmap <silent><buffer> <plug>(vimtex-[m) <sid>(vimtex-[m)
+ xmap <silent><buffer> <plug>(vimtex-[M) <sid>(vimtex-[M)
+ onoremap <silent><buffer> <plug>(vimtex-]m)
+ \ :execute "normal \<sid>(V)" . v:count1 . "\<sid>(vimtex-]m)"<cr>
+ onoremap <silent><buffer> <plug>(vimtex-]M)
+ \ :execute "normal \<sid>(V)" . v:count1 . "\<sid>(vimtex-]M)"<cr>
+ onoremap <silent><buffer> <plug>(vimtex-[m)
+ \ :execute "normal \<sid>(V)" . v:count1 . "\<sid>(vimtex-[m)"<cr>
+ onoremap <silent><buffer> <plug>(vimtex-[M)
+ \ :execute "normal \<sid>(V)" . v:count1 . "\<sid>(vimtex-[M)"<cr>
+
+ " Comments
+ nnoremap <silent><buffer> <plug>(vimtex-]/) :<c-u>call vimtex#motion#comment(1,0,0)<cr>
+ nnoremap <silent><buffer> <plug>(vimtex-]*) :<c-u>call vimtex#motion#comment(0,0,0)<cr>
+ nnoremap <silent><buffer> <plug>(vimtex-[/) :<c-u>call vimtex#motion#comment(1,1,0)<cr>
+ nnoremap <silent><buffer> <plug>(vimtex-[*) :<c-u>call vimtex#motion#comment(0,1,0)<cr>
+ xnoremap <silent><buffer> <sid>(vimtex-]/) :<c-u>call vimtex#motion#comment(1,0,1)<cr>
+ xnoremap <silent><buffer> <sid>(vimtex-]*) :<c-u>call vimtex#motion#comment(0,0,1)<cr>
+ xnoremap <silent><buffer> <sid>(vimtex-[/) :<c-u>call vimtex#motion#comment(1,1,1)<cr>
+ xnoremap <silent><buffer> <sid>(vimtex-[*) :<c-u>call vimtex#motion#comment(0,1,1)<cr>
+ xmap <silent><buffer> <plug>(vimtex-]/) <sid>(vimtex-]/)
+ xmap <silent><buffer> <plug>(vimtex-]*) <sid>(vimtex-]*)
+ xmap <silent><buffer> <plug>(vimtex-[/) <sid>(vimtex-[/)
+ xmap <silent><buffer> <plug>(vimtex-[*) <sid>(vimtex-[*)
+ onoremap <silent><buffer> <plug>(vimtex-]/)
+ \ :execute "normal \<sid>(V)" . v:count1 . "\<sid>(vimtex-]/)"<cr>
+ onoremap <silent><buffer> <plug>(vimtex-]*)
+ \ :execute "normal \<sid>(V)" . v:count1 . "\<sid>(vimtex-]*)"<cr>
+ onoremap <silent><buffer> <plug>(vimtex-[/)
+ \ :execute "normal \<sid>(V)" . v:count1 . "\<sid>(vimtex-[/)"<cr>
+ onoremap <silent><buffer> <plug>(vimtex-[*)
+ \ :execute "normal \<sid>(V)" . v:count1 . "\<sid>(vimtex-[*)"<cr>
+endfunction
+
+" }}}1
+
+function! vimtex#motion#find_matching_pair(...) abort " {{{1
+ if a:0 > 0
+ normal! gv
+ endif
+
+ let delim = vimtex#delim#get_current('all', 'both')
+ if empty(delim)
+ let delim = vimtex#delim#get_next('all', 'both')
+ if empty(delim) | return | endif
+ endif
+
+ let delim = vimtex#delim#get_matching(delim)
+ if empty(delim) | return | endif
+ if empty(delim.match) | return | endif
+
+ normal! m`
+ call vimtex#pos#set_cursor(delim.lnum,
+ \ (delim.is_open
+ \ ? delim.cnum
+ \ : delim.cnum + strlen(delim.match) - 1))
+endfunction
+
+" }}}1
+function! vimtex#motion#section(type, backwards, visual) abort " {{{1
+ let l:count = v:count1
+ if a:visual
+ normal! gv
+ endif
+
+ " Check trivial cases
+ let l:top = search(s:re_sec, 'nbW') == 0
+ let l:bottom = search(a:type == 1 ? s:re_sec_t2 : s:re_sec, 'nW') == 0
+ if a:backwards && l:top
+ return vimtex#pos#set_cursor([1, 1])
+ elseif !a:backwards && l:bottom
+ return vimtex#pos#set_cursor([line('$'), 1])
+ endif
+
+ " Define search pattern and search flag
+ let l:re = a:type == 0 ? s:re_sec : s:re_sec_t1
+ let l:flags = 'W'
+ if a:backwards
+ let l:flags .= 'b'
+ endif
+
+ for l:_ in range(l:count)
+ let l:save_pos = vimtex#pos#get_cursor()
+
+ if a:type == 1
+ call search('\S', 'W')
+ endif
+
+ let l:bottom = search(s:re_sec_t2, 'nW') == 0
+ if a:type == 1 && !a:backwards && l:bottom
+ return vimtex#pos#set_cursor([line('$'), 1])
+ endif
+
+ let l:top = search(s:re_sec, 'ncbW') == 0
+ let l:lnum = search(l:re, l:flags)
+
+ if l:top && l:lnum > 0 && a:type == 1 && !a:backwards
+ let l:lnum = search(l:re, l:flags)
+ endif
+
+ if a:type == 1
+ call search('\S\s*\n\zs', 'Wb')
+
+ " Move to start of file if cursor was moved to top part of document
+ if search(s:re_sec, 'ncbW') == 0
+ call vimtex#pos#set_cursor([1, 1])
+ endif
+ endif
+ endfor
+endfunction
+
+" }}}1
+function! vimtex#motion#environment(begin, backwards, visual) abort " {{{1
+ let l:count = v:count1
+ if a:visual
+ normal! gv
+ endif
+
+ let l:re = g:vimtex#re#not_comment . (a:begin ? '\\begin\s*\{' : '\\end\s*\{')
+ let l:flags = 'W' . (a:backwards ? 'b' : '')
+
+ for l:_ in range(l:count)
+ call search(l:re, l:flags)
+ endfor
+endfunction
+
+" }}}1
+function! vimtex#motion#comment(begin, backwards, visual) abort " {{{1
+ let l:count = v:count1
+ if a:visual
+ normal! gv
+ endif
+
+ let l:re = a:begin
+ \ ? '\v%(^\s*\%.*\n)@<!\s*\%'
+ \ : '\v^\s*\%.*\n%(^\s*\%)@!'
+ let l:flags = 'W' . (a:backwards ? 'b' : '')
+
+ for l:_ in range(l:count)
+ call search(l:re, l:flags)
+ endfor
+endfunction
+
+" }}}1
+
+
+" Patterns to match section/chapter/...
+let s:re_sec = '\v^\s*\\%(%(sub)?paragraph|%(sub)*section|chapter|part|'
+ \ . 'appendi%(x|ces)|%(front|back|main)matter)>'
+let s:re_sec_t1 = '\v%(' . s:re_sec . '|^\s*%(\\end\{document\}|%$))'
+let s:re_sec_t2 = '\v%(' . s:re_sec . '|^\s*\\end\{document\})'
+
+endif
diff --git a/autoload/vimtex/parser.vim b/autoload/vimtex/parser.vim
new file mode 100644
index 00000000..e1a7970f
--- /dev/null
+++ b/autoload/vimtex/parser.vim
@@ -0,0 +1,144 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#parser#tex(file, ...) abort " {{{1
+ return vimtex#parser#tex#parse(a:file, a:0 > 0 ? a:1 : {})
+endfunction
+
+" }}}1
+function! vimtex#parser#preamble(file, ...) abort " {{{1
+ return vimtex#parser#tex#parse_preamble(a:file, a:0 > 0 ? a:1 : {})
+endfunction
+
+" }}}1
+function! vimtex#parser#auxiliary(file) abort " {{{1
+ return vimtex#parser#auxiliary#parse(a:file)
+endfunction
+
+" }}}1
+function! vimtex#parser#fls(file) abort " {{{1
+ return vimtex#parser#fls#parse(a:file)
+endfunction
+
+" }}}1
+function! vimtex#parser#toc(...) abort " {{{1
+ let l:vimtex = a:0 > 0 ? a:1 : b:vimtex
+
+ let l:cache = vimtex#cache#open('parsertoc', {
+ \ 'persistent': 0,
+ \ 'default': {'entries': [], 'ftime': -1},
+ \})
+ let l:current = l:cache.get(l:vimtex.tex)
+
+ " Update cache if relevant
+ let l:ftime = l:vimtex.getftime()
+ if l:ftime > l:current.ftime
+ let l:cache.modified = 1
+ let l:current.ftime = l:ftime
+ let l:current.entries = vimtex#parser#toc#parse(l:vimtex.tex)
+ endif
+
+ return deepcopy(l:current.entries)
+endfunction
+
+" }}}1
+function! vimtex#parser#bib(file, ...) abort " {{{1
+ return vimtex#parser#bib#parse(a:file, a:0 > 0 ? a:1 : {})
+endfunction
+
+" }}}1
+
+function! vimtex#parser#get_externalfiles() abort " {{{1
+ let l:preamble = vimtex#parser#preamble(b:vimtex.tex)
+
+ let l:result = []
+ for l:line in filter(l:preamble, 'v:val =~# ''\\externaldocument''')
+ let l:name = matchstr(l:line, '{\zs[^}]*\ze}')
+ call add(l:result, {
+ \ 'tex' : l:name . '.tex',
+ \ 'aux' : l:name . '.aux',
+ \ 'opt' : matchstr(l:line, '\[\zs[^]]*\ze\]'),
+ \ })
+ endfor
+
+ return l:result
+endfunction
+
+" }}}1
+function! vimtex#parser#selection_to_texfile(type, ...) range abort " {{{1
+ "
+ " Get selected lines. Method depends on type of selection, which may be
+ " either of
+ "
+ " 1. range from argument
+ " 2. Command range
+ " 3. Visual mapping
+ " 4. Operator mapping
+ "
+ if a:type ==# 'arg'
+ let l:lines = getline(a:1[0], a:1[1])
+ elseif a:type ==# 'cmd'
+ let l:lines = getline(a:firstline, a:lastline)
+ elseif a:type ==# 'visual'
+ let l:lines = getline(line("'<"), line("'>"))
+ else
+ let l:lines = getline(line("'["), line("']"))
+ endif
+
+ "
+ " Use only the part of the selection that is within the
+ "
+ " \begin{document} ... \end{document}
+ "
+ " environment.
+ "
+ let l:start = 0
+ let l:end = len(l:lines)
+ for l:n in range(len(l:lines))
+ if l:lines[l:n] =~# '\\begin\s*{document}'
+ let l:start = l:n + 1
+ elseif l:lines[l:n] =~# '\\end\s*{document}'
+ let l:end = l:n - 1
+ break
+ endif
+ endfor
+
+ "
+ " Check if the selection has any real content
+ "
+ if l:start >= len(l:lines)
+ \ || l:end < 0
+ \ || empty(substitute(join(l:lines[l:start : l:end], ''), '\s*', '', ''))
+ return {}
+ endif
+
+ "
+ " Define the set of lines to compile
+ "
+ let l:lines = vimtex#parser#preamble(b:vimtex.tex)
+ \ + ['\begin{document}']
+ \ + l:lines[l:start : l:end]
+ \ + ['\end{document}']
+
+ "
+ " Write content to temporary file
+ "
+ let l:file = {}
+ let l:file.root = b:vimtex.root
+ let l:file.base = b:vimtex.name . '_vimtex_selected.tex'
+ let l:file.tex = l:file.root . '/' . l:file.base
+ let l:file.pdf = fnamemodify(l:file.tex, ':r') . '.pdf'
+ let l:file.log = fnamemodify(l:file.tex, ':r') . '.log'
+ call writefile(l:lines, l:file.tex)
+
+ return l:file
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/parser/auxiliary.vim b/autoload/vimtex/parser/auxiliary.vim
new file mode 100644
index 00000000..b8805ebd
--- /dev/null
+++ b/autoload/vimtex/parser/auxiliary.vim
@@ -0,0 +1,58 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#parser#auxiliary#parse(file) abort " {{{1
+ return s:parse_recurse(a:file, [])
+endfunction
+
+" }}}1
+
+function! s:parse_recurse(file, parsed) abort " {{{1
+ if !filereadable(a:file) || index(a:parsed, a:file) >= 0
+ return []
+ endif
+ call add(a:parsed, a:file)
+
+ let l:lines = []
+ for l:line in readfile(a:file)
+ call add(l:lines, l:line)
+
+ if l:line =~# '\\@input{'
+ let l:file = s:input_line_parser(l:line, a:file)
+ call extend(l:lines, s:parse_recurse(l:file, a:parsed))
+ endif
+ endfor
+
+ return l:lines
+endfunction
+
+" }}}1
+
+function! s:input_line_parser(line, file) abort " {{{1
+ let l:file = matchstr(a:line, '\\@input{\zs[^}]\+\ze}')
+
+ " Remove extension to simplify the parsing (e.g. for "my file name".aux)
+ let l:file = substitute(l:file, '\.aux', '', '')
+
+ " Trim whitespaces and quotes from beginning/end of string, append extension
+ let l:file = substitute(l:file, '^\(\s\|"\)*', '', '')
+ let l:file = substitute(l:file, '\(\s\|"\)*$', '', '')
+ let l:file .= '.aux'
+
+ " Use absolute paths
+ if l:file !~# '\v^(\/|[A-Z]:)'
+ let l:file = fnamemodify(a:file, ':p:h') . '/' . l:file
+ endif
+
+ " Only return filename if it is readable
+ return filereadable(l:file) ? l:file : ''
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/parser/bib.vim b/autoload/vimtex/parser/bib.vim
new file mode 100644
index 00000000..7ea2c238
--- /dev/null
+++ b/autoload/vimtex/parser/bib.vim
@@ -0,0 +1,370 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#parser#bib#parse(file, opts) abort " {{{1
+ if !filereadable(a:file) | return [] | endif
+
+ let l:backend = get(a:opts, 'backend', g:vimtex_parser_bib_backend)
+
+ if l:backend ==# 'bibtex'
+ if !executable('bibtex') | let l:backend = 'vim' | endif
+ elseif l:backend ==# 'bibparse'
+ if !executable('bibparse') | let l:backend = 'vim' | endif
+ else
+ let l:backend = 'vim'
+ endif
+
+ return s:parse_with_{l:backend}(a:file)
+endfunction
+
+" }}}1
+
+
+function! s:parse_with_bibtex(file) abort " {{{1
+ call s:parse_with_bibtex_init()
+ if s:bibtex_not_executable | return [] | endif
+
+ " Define temporary files
+ let tmp = {
+ \ 'aux' : 'tmpfile.aux',
+ \ 'bbl' : 'tmpfile.bbl',
+ \ 'blg' : 'tmpfile.blg',
+ \ }
+
+ " Write temporary aux file
+ call writefile([
+ \ '\citation{*}',
+ \ '\bibstyle{' . s:bibtex_bstfile . '}',
+ \ '\bibdata{' . fnamemodify(a:file, ':r') . '}',
+ \ ], tmp.aux)
+
+ " Create the temporary bbl file
+ call vimtex#process#run('bibtex -terse ' . fnameescape(tmp.aux), {
+ \ 'background' : 0,
+ \ 'silent' : 1,
+ \})
+
+ " Parse temporary bbl file
+ let lines = join(readfile(tmp.bbl), "\n")
+ let lines = substitute(lines, '\n\n\@!\(\s\=\)\s*\|{\|}', '\1', 'g')
+ let lines = vimtex#util#tex2unicode(lines)
+ let lines = split(lines, "\n")
+
+ let l:entries = []
+ for line in lines
+ let matches = split(line, '||')
+ if empty(matches) || empty(matches[0]) | continue | endif
+
+ let l:entry = {
+ \ 'key': matches[0],
+ \ 'type': matches[1],
+ \}
+
+ if !empty(matches[2])
+ let l:entry.author = matches[2]
+ endif
+ if !empty(matches[3])
+ let l:entry.year = matches[3]
+ endif
+ if !empty(get(matches, 4, ''))
+ let l:entry.title = get(matches, 4, '')
+ endif
+
+ call add(l:entries, l:entry)
+ endfor
+
+ " Clean up
+ call delete(tmp.aux)
+ call delete(tmp.bbl)
+ call delete(tmp.blg)
+
+ return l:entries
+endfunction
+
+" }}}1
+function! s:parse_with_bibtex_init() abort " {{{1
+ if exists('s:bibtex_init_done') | return | endif
+
+ " Check if bibtex is executable
+ let s:bibtex_not_executable = !executable('bibtex')
+ if s:bibtex_not_executable
+ call vimtex#log#warning(
+ \ 'bibtex is not executable and may not be used to parse bib files!')
+ endif
+
+ " Check if bstfile contains whitespace (not handled by vimtex)
+ if stridx(s:bibtex_bstfile, ' ') >= 0
+ let l:oldbst = s:bibtex_bstfile . '.bst'
+ let s:bibtex_bstfile = tempname()
+ call writefile(readfile(l:oldbst), s:bibtex_bstfile . '.bst')
+ endif
+
+ let s:bibtex_init_done = 1
+endfunction
+
+let s:bibtex_bstfile = expand('<sfile>:p:h') . '/vimcomplete'
+
+" }}}1
+
+function! s:parse_with_bibparse(file) abort " {{{1
+ call s:parse_with_bibparse_init()
+ if s:bibparse_not_executable | return [] | endif
+
+ call vimtex#process#run('bibparse ' . fnameescape(a:file)
+ \ . ' >_vimtex_bibparsed.log', {'background' : 0, 'silent' : 1})
+ let l:lines = readfile('_vimtex_bibparsed.log')
+ call delete('_vimtex_bibparsed.log')
+
+ let l:current = {}
+ let l:entries = []
+ for l:line in l:lines
+ if l:line[0] ==# '@'
+ if !empty(l:current)
+ call add(l:entries, l:current)
+ let l:current = {}
+ endif
+
+ let l:index = stridx(l:line, ' ')
+ if l:index > 0
+ let l:type = l:line[1:l:index-1]
+ let l:current.type = l:type
+ let l:current.key = l:line[l:index+1:]
+ endif
+ elseif !empty(l:current)
+ let l:index = stridx(l:line, '=')
+ if l:index < 0 | continue | endif
+
+ let l:key = l:line[:l:index-1]
+ let l:value = l:line[l:index+1:]
+ let l:current[tolower(l:key)] = l:value
+ endif
+ endfor
+
+ if !empty(l:current)
+ call add(l:entries, l:current)
+ endif
+
+ return l:entries
+endfunction
+
+" }}}1
+function! s:parse_with_bibparse_init() abort " {{{1
+ if exists('s:bibparse_init_done') | return | endif
+
+ " Check if bibtex is executable
+ let s:bibparse_not_executable = !executable('bibparse')
+ if s:bibparse_not_executable
+ call vimtex#log#warning(
+ \ 'bibparse is not executable and may not be used to parse bib files!')
+ endif
+
+ let s:bibparse_init_done = 1
+endfunction
+
+" }}}1
+
+function! s:parse_with_vim(file) abort " {{{1
+ " Adheres to the format description found here:
+ " http://www.bibtex.org/Format/
+
+ if !filereadable(a:file)
+ return []
+ endif
+
+ let l:current = {}
+ let l:strings = {}
+ let l:entries = []
+ for l:line in filter(readfile(a:file), 'v:val !~# ''^\s*\%(%\|$\)''')
+ if empty(l:current)
+ if s:parse_type(l:line, l:current, l:strings)
+ let l:current = {}
+ endif
+ continue
+ endif
+
+ if l:current.type ==# 'string'
+ if s:parse_string(l:line, l:current, l:strings)
+ let l:current = {}
+ endif
+ else
+ if s:parse_entry(l:line, l:current, l:entries)
+ let l:current = {}
+ endif
+ endif
+ endfor
+
+ return map(l:entries, 's:parse_entry_body(v:val, l:strings)')
+endfunction
+
+" }}}1
+
+function! s:parse_type(line, current, strings) abort " {{{1
+ let l:matches = matchlist(a:line, '\v^\@(\w+)\s*\{\s*(.*)')
+ if empty(l:matches) | return 0 | endif
+
+ let l:type = tolower(l:matches[1])
+ if index(['preamble', 'comment'], l:type) >= 0 | return 0 | endif
+
+ let a:current.level = 1
+ let a:current.body = ''
+
+ if l:type ==# 'string'
+ return s:parse_string(l:matches[2], a:current, a:strings)
+ else
+ let a:current.type = l:type
+ let a:current.key = matchstr(l:matches[2], '.*\ze,\s*')
+ return 0
+ endif
+endfunction
+
+" }}}1
+function! s:parse_string(line, string, strings) abort " {{{1
+ let a:string.level += s:count(a:line, '{') - s:count(a:line, '}')
+ if a:string.level > 0
+ let a:string.body .= a:line
+ return 0
+ endif
+
+ let a:string.body .= matchstr(a:line, '.*\ze}')
+
+ let l:matches = matchlist(a:string.body, '\v^\s*(\w+)\s*\=\s*"(.*)"\s*$')
+ if !empty(l:matches) && !empty(l:matches[1])
+ let a:strings[l:matches[1]] = l:matches[2]
+ endif
+
+ return 1
+endfunction
+
+" }}}1
+function! s:parse_entry(line, entry, entries) abort " {{{1
+ let a:entry.level += s:count(a:line, '{') - s:count(a:line, '}')
+ if a:entry.level > 0
+ let a:entry.body .= a:line
+ return 0
+ endif
+
+ let a:entry.body .= matchstr(a:line, '.*\ze}')
+
+ call add(a:entries, a:entry)
+ return 1
+endfunction
+
+" }}}1
+
+function! s:parse_entry_body(entry, strings) abort " {{{1
+ unlet a:entry.level
+
+ let l:key = ''
+ let l:pos = matchend(a:entry.body, '^\s*')
+ while l:pos >= 0
+ if empty(l:key)
+ let [l:key, l:pos] = s:get_key(a:entry.body, l:pos)
+ else
+ let [l:value, l:pos] = s:get_value(a:entry.body, l:pos, a:strings)
+ let a:entry[l:key] = l:value
+ let l:key = ''
+ endif
+ endwhile
+
+ unlet a:entry.body
+ return a:entry
+endfunction
+
+" }}}1
+function! s:get_key(body, head) abort " {{{1
+ " Parse the key part of a bib entry tag.
+ " Assumption: a:body is left trimmed and either empty or starts with a key.
+ " Returns: The key and the remaining part of the entry body.
+
+ let l:matches = matchlist(a:body, '^\v(\w+)\s*\=\s*', a:head)
+ return empty(l:matches)
+ \ ? ['', -1]
+ \ : [tolower(l:matches[1]), a:head + strlen(l:matches[0])]
+endfunction
+
+" }}}1
+function! s:get_value(body, head, strings) abort " {{{1
+ " Parse the value part of a bib entry tag, until separating comma or end.
+ " Assumption: a:body is left trimmed and either empty or starts with a value.
+ " Returns: The value and the remaining part of the entry body.
+ "
+ " A bib entry value is either
+ " 1. A number.
+ " 2. A concatenation (with #s) of double quoted strings, curlied strings,
+ " and/or bibvariables,
+ "
+ if a:body[a:head] =~# '\d'
+ let l:value = matchstr(a:body, '^\d\+', a:head)
+ let l:head = matchend(a:body, '^\s*,\s*', a:head + len(l:value))
+ return [l:value, l:head]
+ else
+ return s:get_value_string(a:body, a:head, a:strings)
+ endif
+
+ return ['s:get_value failed', -1]
+endfunction
+
+" }}}1
+function! s:get_value_string(body, head, strings) abort " {{{1
+ if a:body[a:head] ==# '{'
+ let l:sum = 1
+ let l:i1 = a:head + 1
+ let l:i0 = l:i1
+
+ while l:sum > 0
+ let [l:match, l:_, l:i1] = matchstrpos(a:body, '[{}]', l:i1)
+ if l:i1 < 0 | break | endif
+
+ let l:i0 = l:i1
+ let l:sum += l:match ==# '{' ? 1 : -1
+ endwhile
+
+ let l:value = a:body[a:head+1:l:i0-2]
+ let l:head = matchend(a:body, '^\s*', l:i0)
+ elseif a:body[a:head] ==# '"'
+ let l:index = match(a:body, '\\\@<!"', a:head+1)
+ if l:index < 0
+ return ['s:get_value_string failed', '']
+ endif
+
+ let l:value = a:body[a:head+1:l:index-1]
+ let l:head = matchend(a:body, '^\s*', l:index+1)
+ return [l:value, l:head]
+ elseif a:body[a:head:] =~# '^\w'
+ let l:value = matchstr(a:body, '^\w\+', a:head)
+ let l:head = matchend(a:body, '^\s*', a:head + strlen(l:value))
+ let l:value = get(a:strings, l:value, '@(' . l:value . ')')
+ else
+ let l:head = a:head
+ endif
+
+ if a:body[l:head] ==# '#'
+ let l:head = matchend(a:body, '^\s*', l:head + 1)
+ let [l:vadd, l:head] = s:get_value_string(a:body, l:head, a:strings)
+ let l:value .= l:vadd
+ endif
+
+ return [l:value, matchend(a:body, '^,\s*', l:head)]
+endfunction
+
+" }}}1
+
+function! s:count(container, item) abort " {{{1
+ " Necessary because in old Vim versions, count() does not work for strings
+ try
+ let l:count = count(a:container, a:item)
+ catch /E712/
+ let l:count = count(split(a:container, '\zs'), a:item)
+ endtry
+
+ return l:count
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/parser/fls.vim b/autoload/vimtex/parser/fls.vim
new file mode 100644
index 00000000..46b0db2f
--- /dev/null
+++ b/autoload/vimtex/parser/fls.vim
@@ -0,0 +1,19 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#parser#fls#parse(file) abort " {{{1
+ if !filereadable(a:file)
+ return []
+ endif
+
+ return readfile(a:file)
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/parser/tex.vim b/autoload/vimtex/parser/tex.vim
new file mode 100644
index 00000000..6259b5fa
--- /dev/null
+++ b/autoload/vimtex/parser/tex.vim
@@ -0,0 +1,205 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#parser#tex#parse(file, opts) abort " {{{1
+ let l:opts = extend({
+ \ 'detailed': 1,
+ \ 'root' : exists('b:vimtex.root') ? b:vimtex.root : '',
+ \}, a:opts)
+
+ let l:cache = vimtex#cache#open('texparser', {
+ \ 'local': 1,
+ \ 'persistent': 0,
+ \ 'default': {'ftime': -2},
+ \})
+
+ let l:parsed = s:parse(a:file, l:opts, l:cache)
+
+ if !l:opts.detailed
+ call map(l:parsed, 'v:val[2]')
+ endif
+
+ return l:parsed
+endfunction
+
+" }}}1
+function! vimtex#parser#tex#parse_files(file, opts) abort " {{{1
+ let l:opts = extend({
+ \ 'root' : exists('b:vimtex.root') ? b:vimtex.root : '',
+ \}, a:opts)
+
+ let l:cache = vimtex#cache#open('texparser', {
+ \ 'local': 1,
+ \ 'persistent': 0,
+ \ 'default': {'ftime': -2},
+ \})
+
+ return vimtex#util#uniq_unsorted(
+ \ s:parse_files(a:file, l:opts, l:cache))
+endfunction
+
+" }}}1
+function! vimtex#parser#tex#parse_preamble(file, opts) abort " {{{1
+ let l:opts = extend({
+ \ 'inclusive' : 0,
+ \ 'root' : exists('b:vimtex.root') ? b:vimtex.root : '',
+ \}, a:opts)
+
+ return s:parse_preamble(a:file, l:opts, [])
+endfunction
+
+" }}}1
+
+function! s:parse(file, opts, cache) abort " {{{1
+ let l:current = a:cache.get(a:file)
+ let l:ftime = getftime(a:file)
+ if l:ftime > l:current.ftime
+ let l:current.ftime = l:ftime
+ call s:parse_current(a:file, a:opts, l:current)
+ endif
+
+ let l:parsed = []
+
+ for l:val in l:current.lines
+ if type(l:val) == type([])
+ call add(l:parsed, l:val)
+ else
+ call extend(l:parsed, s:parse(l:val, a:opts, a:cache))
+ endif
+ endfor
+
+ return l:parsed
+endfunction
+
+" }}}1
+function! s:parse_files(file, opts, cache) abort " {{{1
+ let l:current = a:cache.get(a:file)
+ let l:ftime = getftime(a:file)
+ if l:ftime > l:current.ftime
+ let l:current.ftime = l:ftime
+ call s:parse_current(a:file, a:opts, l:current)
+ endif
+
+ " Only include existing files
+ if !filereadable(a:file) | return [] | endif
+
+ let l:files = [a:file]
+ for l:file in l:current.includes
+ let l:files += s:parse_files(l:file, a:opts, a:cache)
+ endfor
+
+ return l:files
+endfunction
+
+" }}}1
+function! s:parse_current(file, opts, current) abort " {{{1
+ let a:current.lines = []
+ let a:current.includes = []
+
+ " Also load includes from glsentries
+ let l:re_input = g:vimtex#re#tex_input . '|^\s*\\loadglsentries'
+
+ if filereadable(a:file)
+ let l:lnum = 0
+ for l:line in readfile(a:file)
+ let l:lnum += 1
+ call add(a:current.lines, [a:file, l:lnum, l:line])
+
+ " Minor optimization: Avoid complex regex on "simple" lines
+ if stridx(l:line, '\') < 0 | continue | endif
+
+ if l:line =~# l:re_input
+ let l:file = s:input_parser(l:line, a:file, a:opts.root)
+ call add(a:current.lines, l:file)
+ call add(a:current.includes, l:file)
+ endif
+ endfor
+ endif
+endfunction
+
+" }}}1
+function! s:parse_preamble(file, opts, parsed_files) abort " {{{1
+ if !filereadable(a:file) || index(a:parsed_files, a:file) >= 0
+ return []
+ endif
+ call add(a:parsed_files, a:file)
+
+ let l:lines = []
+ for l:line in readfile(a:file)
+ if l:line =~# '\\begin\s*{document}'
+ if a:opts.inclusive
+ call add(l:lines, l:line)
+ endif
+ break
+ endif
+
+ call add(l:lines, l:line)
+
+ if l:line =~# g:vimtex#re#tex_input
+ let l:file = s:input_parser(l:line, a:file, a:opts.root)
+ call extend(l:lines, s:parse_preamble(l:file, a:opts, a:parsed_files))
+ endif
+ endfor
+
+ return l:lines
+endfunction
+
+" }}}1
+
+function! s:input_parser(line, current_file, root) abort " {{{1
+ " Handle \space commands
+ let l:file = substitute(a:line, '\\space\s*', ' ', 'g')
+
+ " Handle import package commands
+ if l:file =~# g:vimtex#re#tex_input_import
+ let l:root = l:file =~# '\\sub'
+ \ ? fnamemodify(a:current_file, ':p:h')
+ \ : a:root
+
+ let l:candidate = s:input_to_filename(
+ \ substitute(copy(l:file), '}\s*{', '', 'g'), l:root)
+ if !empty(l:candidate)
+ return l:candidate
+ else
+ return s:input_to_filename(
+ \ substitute(copy(l:file), '{.{-}}', '', ''), l:root)
+ endif
+ else
+ return s:input_to_filename(l:file, a:root)
+ endif
+endfunction
+
+" }}}1
+function! s:input_to_filename(input, root) abort " {{{1
+ let l:file = matchstr(a:input, '\zs[^{}]\+\ze}\s*\%(%\|$\)')
+
+ " Trim whitespaces and quotes from beginning/end of string
+ let l:file = substitute(l:file, '^\(\s\|"\)*', '', '')
+ let l:file = substitute(l:file, '\(\s\|"\)*$', '', '')
+
+ " Ensure that the file name has extension
+ if empty(fnamemodify(l:file, ':e'))
+ let l:file .= '.tex'
+ endif
+
+ if vimtex#paths#is_abs(l:file)
+ return l:file
+ endif
+
+ let l:candidate = a:root . '/' . l:file
+ if filereadable(l:candidate)
+ return l:candidate
+ endif
+
+ let l:candidate = vimtex#kpsewhich#find(l:file)
+ return filereadable(l:candidate) ? l:candidate : l:file
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/parser/toc.vim b/autoload/vimtex/parser/toc.vim
new file mode 100644
index 00000000..517a25be
--- /dev/null
+++ b/autoload/vimtex/parser/toc.vim
@@ -0,0 +1,778 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+"
+" Parses tex project for ToC-like entries. Each entry is a dictionary
+" similar to the following:
+"
+" entry = {
+" title : "Some title",
+" number : "3.1.2",
+" file : /path/to/file.tex,
+" line : 142,
+" rank : cumulative line number,
+" level : 2,
+" type : [content | label | todo | include],
+" link : [0 | 1],
+" }
+"
+
+function! vimtex#parser#toc#parse(file) abort " {{{1
+ let l:entries = []
+ let l:content = vimtex#parser#tex(a:file)
+
+ let l:max_level = 0
+ for [l:file, l:lnum, l:line] in l:content
+ if l:line =~# s:matcher_sections.re
+ let l:max_level = max([
+ \ l:max_level,
+ \ s:sec_to_value[matchstr(l:line, s:matcher_sections.re_level)]
+ \])
+ endif
+ endfor
+
+ call s:level.reset('preamble', l:max_level)
+
+ " No more parsing if there is no content
+ if empty(l:content) | return l:entries | endif
+
+ "
+ " Begin parsing LaTeX files
+ "
+ let l:lnum_total = 0
+ let l:matchers = s:matchers_preamble
+ for [l:file, l:lnum, l:line] in l:content
+ let l:lnum_total += 1
+ let l:context = {
+ \ 'file' : l:file,
+ \ 'line' : l:line,
+ \ 'lnum' : l:lnum,
+ \ 'lnum_total' : l:lnum_total,
+ \ 'level' : s:level,
+ \ 'max_level' : l:max_level,
+ \ 'entry' : get(l:entries, -1, {}),
+ \ 'num_entries' : len(l:entries),
+ \}
+
+ " Detect end of preamble
+ if s:level.preamble && l:line =~# '\v^\s*\\begin\{document\}'
+ let s:level.preamble = 0
+ let l:matchers = s:matchers_content
+ continue
+ endif
+
+ " Handle multi-line entries
+ if exists('s:matcher_continue')
+ call s:matcher_continue.continue(l:context)
+ continue
+ endif
+
+ " Apply prefilter - this gives considerable speedup for large documents
+ if l:line !~# s:re_prefilter | continue | endif
+
+ " Apply the matchers
+ for l:matcher in l:matchers
+ if l:line =~# l:matcher.re
+ let l:entry = l:matcher.get_entry(l:context)
+ if type(l:entry) == type([])
+ call extend(l:entries, l:entry)
+ elseif !empty(l:entry)
+ call add(l:entries, l:entry)
+ endif
+ endif
+ endfor
+ endfor
+
+ for l:matcher in s:matchers
+ try
+ call l:matcher.filter(l:entries)
+ catch /E716/
+ endtry
+ endfor
+
+ return l:entries
+endfunction
+
+" }}}1
+function! vimtex#parser#toc#get_topmatters() abort " {{{1
+ let l:topmatters = s:level.frontmatter
+ let l:topmatters += s:level.mainmatter
+ let l:topmatters += s:level.appendix
+ let l:topmatters += s:level.backmatter
+
+ for l:level in get(s:level, 'old', [])
+ let l:topmatters += l:level.frontmatter
+ let l:topmatters += l:level.mainmatter
+ let l:topmatters += l:level.appendix
+ let l:topmatters += l:level.backmatter
+ endfor
+
+ return l:topmatters
+endfunction
+
+" }}}1
+function! vimtex#parser#toc#get_entry_general(context) abort dict " {{{1
+ return {
+ \ 'title' : self.title,
+ \ 'number' : '',
+ \ 'file' : a:context.file,
+ \ 'line' : a:context.lnum,
+ \ 'rank' : a:context.lnum_total,
+ \ 'level' : 0,
+ \ 'type' : 'content',
+ \}
+endfunction
+
+" }}}1
+
+" IMPORTANT: The following defines a prefilter for optimizing the toc parser.
+" Any line that should be parsed has to be matched by this regexp!
+" {{{1 let s:re_prefilter = ...
+let s:re_prefilter = '\v%(\\' . join([
+ \ '%(front|main|back)matter',
+ \ 'add%(global|section)?bib',
+ \ 'appendix',
+ \ 'begin',
+ \ 'bibliography',
+ \ 'chapter',
+ \ 'documentclass',
+ \ 'import',
+ \ 'include',
+ \ 'includegraphics',
+ \ 'input',
+ \ 'label',
+ \ 'part',
+ \ 'printbib',
+ \ 'printindex',
+ \ 'paragraph',
+ \ 'section',
+ \ 'subfile',
+ \ 'tableofcontents',
+ \ 'todo',
+ \], '|') . ')'
+ \ . '|\%\s*%(' . join(g:vimtex_toc_todo_keywords, '|') . ')'
+ \ . '|\%\s*vimtex-include'
+for s:m in g:vimtex_toc_custom_matchers
+ if has_key(s:m, 'prefilter')
+ let s:re_prefilter .= '|' . s:m.prefilter
+ endif
+endfor
+
+" }}}1
+
+" Adds entries for included files
+let s:matcher_include = {
+ \ 're' : vimtex#re#tex_input . '\zs\f{-}\s*\ze\}',
+ \ 'in_preamble' : 1,
+ \ 'priority' : 0,
+ \}
+function! s:matcher_include.get_entry(context) abort dict " {{{1
+ let l:file = matchstr(a:context.line, self.re)
+ if !vimtex#paths#is_abs(l:file[0])
+ let l:file = b:vimtex.root . '/' . l:file
+ endif
+ let l:file = fnamemodify(l:file, ':~:.')
+ if !filereadable(l:file)
+ let l:file .= '.tex'
+ endif
+ return {
+ \ 'title' : 'tex incl: ' . (strlen(l:file) < 70
+ \ ? l:file
+ \ : l:file[0:30] . '...' . l:file[-36:]),
+ \ 'number' : '',
+ \ 'file' : l:file,
+ \ 'line' : 1,
+ \ 'level' : a:context.max_level - a:context.level.current,
+ \ 'rank' : a:context.lnum_total,
+ \ 'type' : 'include',
+ \ }
+endfunction
+
+" }}}1
+
+" Adds entries for included graphics files (filetype tikz, tex)
+let s:matcher_include_graphics = {
+ \ 're' : '\v^\s*\\includegraphics\*?%(\s*\[[^]]*\]){0,2}\s*\{\zs[^}]*',
+ \ 'priority' : 1,
+ \}
+function! s:matcher_include_graphics.get_entry(context) abort dict " {{{1
+ let l:file = matchstr(a:context.line, self.re)
+ if !vimtex#paths#is_abs(l:file)
+ let l:file = vimtex#misc#get_graphicspath(l:file)
+ endif
+ let l:file = fnamemodify(l:file, ':~:.')
+ let l:ext = fnamemodify(l:file, ':e')
+
+ return !filereadable(l:file) || index(['asy', 'tikz'], l:ext) < 0
+ \ ? {}
+ \ : {
+ \ 'title' : 'fig incl: ' . (strlen(l:file) < 70
+ \ ? l:file
+ \ : l:file[0:30] . '...' . l:file[-36:]),
+ \ 'number' : '',
+ \ 'file' : l:file,
+ \ 'line' : 1,
+ \ 'level' : a:context.max_level - a:context.level.current,
+ \ 'rank' : a:context.lnum_total,
+ \ 'type' : 'include',
+ \ 'link' : 1,
+ \ }
+endfunction
+
+" }}}1
+
+" Adds entries for included files through vimtex specific syntax (this allows
+" to add entries for any filetype or file)
+let s:matcher_include_vimtex = {
+ \ 're' : '^\s*%\s*vimtex-include:\?\s\+\zs\f\+',
+ \ 'in_preamble' : 1,
+ \ 'priority' : 1,
+ \}
+function! s:matcher_include_vimtex.get_entry(context) abort dict " {{{1
+ let l:file = matchstr(a:context.line, self.re)
+ if !vimtex#paths#is_abs(l:file)
+ let l:file = b:vimtex.root . '/' . l:file
+ endif
+ let l:file = fnamemodify(l:file, ':~:.')
+ return {
+ \ 'title' : 'vtx incl: ' . (strlen(l:file) < 70
+ \ ? l:file
+ \ : l:file[0:30] . '...' . l:file[-36:]),
+ \ 'number' : '',
+ \ 'file' : l:file,
+ \ 'line' : 1,
+ \ 'level' : a:context.max_level - a:context.level.current,
+ \ 'rank' : a:context.lnum_total,
+ \ 'type' : 'include',
+ \ 'link' : 1,
+ \ }
+endfunction
+
+" }}}1
+
+let s:matcher_include_bibtex = {
+ \ 're' : '\v^\s*\\bibliography\s*\{\zs[^}]+\ze\}',
+ \ 'in_preamble' : 1,
+ \ 'priority' : 0,
+ \}
+function! s:matcher_include_bibtex.get_entry(context) abort dict " {{{1
+ let l:entries = []
+
+ for l:file in split(matchstr(a:context.line, self.re), ',')
+ " Ensure that the file name has extension
+ if l:file !~# '\.bib$'
+ let l:file .= '.bib'
+ endif
+
+ call add(l:entries, {
+ \ 'title' : printf('bib incl: %-.67s', fnamemodify(l:file, ':t')),
+ \ 'number' : '',
+ \ 'file' : vimtex#kpsewhich#find(l:file),
+ \ 'line' : 1,
+ \ 'level' : 0,
+ \ 'rank' : a:context.lnum_total,
+ \ 'type' : 'include',
+ \ 'link' : 1,
+ \})
+ endfor
+
+ return l:entries
+endfunction
+
+" }}}1
+
+let s:matcher_include_biblatex = {
+ \ 're' : '\v^\s*\\add(bibresource|globalbib|sectionbib)\s*\{\zs[^}]+\ze\}',
+ \ 'in_preamble' : 1,
+ \ 'in_content' : 0,
+ \ 'priority' : 0,
+ \}
+function! s:matcher_include_biblatex.get_entry(context) abort dict " {{{1
+ let l:file = matchstr(a:context.line, self.re)
+
+ return {
+ \ 'title' : printf('bib incl: %-.67s', fnamemodify(l:file, ':t')),
+ \ 'number' : '',
+ \ 'file' : vimtex#kpsewhich#find(l:file),
+ \ 'line' : 1,
+ \ 'level' : 0,
+ \ 'rank' : a:context.lnum_total,
+ \ 'type' : 'include',
+ \ 'link' : 1,
+ \}
+endfunction
+
+" }}}1
+
+let s:matcher_preamble = {
+ \ 're' : '\v^\s*\\documentclass',
+ \ 'in_preamble' : 1,
+ \ 'in_content' : 0,
+ \ 'priority' : 0,
+ \}
+function! s:matcher_preamble.get_entry(context) abort " {{{1
+ return g:vimtex_toc_show_preamble
+ \ ? {
+ \ 'title' : 'Preamble',
+ \ 'number' : '',
+ \ 'file' : a:context.file,
+ \ 'line' : a:context.lnum,
+ \ 'level' : 0,
+ \ 'rank' : a:context.lnum_total,
+ \ 'type' : 'content',
+ \ }
+ \ : {}
+endfunction
+
+" }}}1
+
+let s:matcher_parts = {
+ \ 're' : '\v^\s*\\\zs((front|main|back)matter|appendix)>',
+ \ 'priority' : 0,
+ \}
+function! s:matcher_parts.get_entry(context) abort dict " {{{1
+ call a:context.level.reset(
+ \ matchstr(a:context.line, self.re),
+ \ a:context.max_level)
+ return {}
+endfunction
+
+" }}}1
+
+let s:matcher_sections = {
+ \ 're' : '\v^\s*\\%(part|chapter|%(sub)*section|%(sub)?paragraph)\*?\s*(\[|\{)',
+ \ 're_starred' : '\v^\s*\\%(part|chapter|%(sub)*section)\*',
+ \ 're_level' : '\v^\s*\\\zs%(part|chapter|%(sub)*section|%(sub)?paragraph)',
+ \ 'priority' : 0,
+ \}
+let s:matcher_sections.re_title = s:matcher_sections.re . '\zs.{-}\ze\%?\s*$'
+function! s:matcher_sections.get_entry(context) abort dict " {{{1
+ let level = matchstr(a:context.line, self.re_level)
+ let type = matchlist(a:context.line, self.re)[1]
+ let title = matchstr(a:context.line, self.re_title)
+ let number = ''
+
+ let [l:end, l:count] = s:find_closing(0, title, 1, type)
+ if l:count == 0
+ let title = self.parse_title(strpart(title, 0, l:end+1))
+ else
+ let self.type = type
+ let self.count = l:count
+ let s:matcher_continue = deepcopy(self)
+ endif
+
+ if a:context.line !~# self.re_starred
+ call a:context.level.increment(level)
+ if a:context.line !~# '\v^\s*\\%(sub)?paragraph'
+ let number = deepcopy(a:context.level)
+ endif
+ endif
+
+ return {
+ \ 'title' : title,
+ \ 'number' : number,
+ \ 'file' : a:context.file,
+ \ 'line' : a:context.lnum,
+ \ 'level' : a:context.max_level - a:context.level.current,
+ \ 'rank' : a:context.lnum_total,
+ \ 'type' : 'content',
+ \ }
+endfunction
+
+" }}}1
+function! s:matcher_sections.parse_title(title) abort dict " {{{1
+ let l:title = substitute(a:title, '\v%(\]|\})\s*$', '', '')
+ return s:clear_texorpdfstring(l:title)
+endfunction
+
+" }}}1
+function! s:matcher_sections.continue(context) abort dict " {{{1
+ let [l:end, l:count] = s:find_closing(0, a:context.line, self.count, self.type)
+ if l:count == 0
+ let a:context.entry.title = self.parse_title(a:context.entry.title . strpart(a:context.line, 0, l:end+1))
+ unlet! s:matcher_continue
+ else
+ let a:context.entry.title .= a:context.line
+ let self.count = l:count
+ endif
+endfunction
+
+" }}}1
+
+let s:matcher_table_of_contents = {
+ \ 'title' : 'Table of contents',
+ \ 're' : '\v^\s*\\tableofcontents',
+ \ 'priority' : 0,
+ \}
+
+let s:matcher_index = {
+ \ 'title' : 'Alphabetical index',
+ \ 're' : '\v^\s*\\printindex\[?',
+ \ 'priority' : 0,
+ \}
+
+let s:matcher_titlepage = {
+ \ 'title' : 'Titlepage',
+ \ 're' : '\v^\s*\\begin\{titlepage\}',
+ \ 'priority' : 0,
+ \}
+
+let s:matcher_bibliography = {
+ \ 'title' : 'Bibliography',
+ \ 're' : '\v^\s*\\%('
+ \ . 'printbib%(liography|heading)\s*(\{|\[)?'
+ \ . '|begin\s*\{\s*thebibliography\s*\}'
+ \ . '|bibliography\s*\{)',
+ \ 're_biblatex' : '\v^\s*\\printbib%(liography|heading)',
+ \ 'priority' : 0,
+ \}
+function! s:matcher_bibliography.get_entry(context) abort dict " {{{1
+ let l:entry = call('vimtex#parser#toc#get_entry_general', [a:context], self)
+
+ if a:context.line !~# self.re_biblatex
+ return l:entry
+ endif
+
+ let self.options = matchstr(a:context.line, self.re_biblatex . '\s*\[\zs.*')
+
+ let [l:end, l:count] = s:find_closing(
+ \ 0, self.options, !empty(self.options), '[')
+ if l:count == 0
+ let self.options = strpart(self.options, 0, l:end)
+ call self.parse_options(a:context, l:entry)
+ else
+ let self.count = l:count
+ let s:matcher_continue = deepcopy(self)
+ endif
+
+ return l:entry
+endfunction
+
+" }}}1
+function! s:matcher_bibliography.continue(context) abort dict " {{{1
+ let [l:end, l:count] = s:find_closing(0, a:context.line, self.count, '[')
+ if l:count == 0
+ let self.options .= strpart(a:context.line, 0, l:end)
+ unlet! s:matcher_continue
+ call self.parse_options(a:context, a:context.entry)
+ else
+ let self.options .= a:context.line
+ let self.count = l:count
+ endif
+endfunction
+
+" }}}1
+function! s:matcher_bibliography.parse_options(context, entry) abort dict " {{{1
+ " Parse the options
+ let l:opt_pairs = map(split(self.options, ','), 'split(v:val, ''='')')
+ let l:opts = {}
+ for [l:key, l:val] in l:opt_pairs
+ let l:key = substitute(l:key, '^\s*\|\s*$', '', 'g')
+ let l:val = substitute(l:val, '^\s*\|\s*$', '', 'g')
+ let l:val = substitute(l:val, '{\|}', '', 'g')
+ let l:opts[l:key] = l:val
+ endfor
+
+ " Check if entry should appear in the TOC
+ let l:heading = get(l:opts, 'heading')
+ let a:entry.added_to_toc = l:heading =~# 'intoc\|numbered'
+
+ " Check if entry should be numbered
+ if l:heading =~# '\v%(sub)?bibnumbered'
+ if a:context.level.chapter > 0
+ let l:levels = ['chapter', 'section']
+ else
+ let l:levels = ['section', 'subsection']
+ endif
+ call a:context.level.increment(l:levels[l:heading =~# '^sub'])
+ let a:entry.level = a:context.max_level - a:context.level.current
+ let a:entry.number = deepcopy(a:context.level)
+ endif
+
+ " Parse title
+ try
+ let a:entry.title = remove(l:opts, 'title')
+ catch /E716/
+ let a:entry.title = l:heading =~# '^sub' ? 'References' : 'Bibliography'
+ endtry
+endfunction
+
+" }}}1
+function! s:matcher_bibliography.filter(entries) abort dict " {{{1
+ if !empty(
+ \ filter(deepcopy(a:entries), 'get(v:val, "added_to_toc")'))
+ call filter(a:entries, 'get(v:val, "added_to_toc", 1)')
+ endif
+endfunction
+
+" }}}1
+
+let s:matcher_todos = {
+ \ 're' : g:vimtex#re#not_bslash . '\%\s+('
+ \ . join(g:vimtex_toc_todo_keywords, '|') . ')[ :]+\s*(.*)',
+ \ 'in_preamble' : 1,
+ \ 'priority' : 2,
+ \}
+function! s:matcher_todos.get_entry(context) abort dict " {{{1
+ let [l:type, l:text] = matchlist(a:context.line, self.re)[1:2]
+ return {
+ \ 'title' : toupper(l:type) . ': ' . l:text,
+ \ 'number' : '',
+ \ 'file' : a:context.file,
+ \ 'line' : a:context.lnum,
+ \ 'level' : a:context.max_level - a:context.level.current,
+ \ 'rank' : a:context.lnum_total,
+ \ 'type' : 'todo',
+ \ }
+endfunction
+
+" }}}1
+
+let s:matcher_todonotes = {
+ \ 're' : g:vimtex#re#not_comment . '\\\w*todo\w*%(\[[^]]*\])?\{\zs.*',
+ \ 'priority' : 2,
+ \}
+function! s:matcher_todonotes.get_entry(context) abort dict " {{{1
+ let title = matchstr(a:context.line, self.re)
+
+ let [l:end, l:count] = s:find_closing(0, title, 1, '{')
+ if l:count == 0
+ let title = strpart(title, 0, l:end)
+ else
+ let self.count = l:count
+ let s:matcher_continue = deepcopy(self)
+ endif
+
+ return {
+ \ 'title' : 'TODO: ' . title,
+ \ 'number' : '',
+ \ 'file' : a:context.file,
+ \ 'line' : a:context.lnum,
+ \ 'level' : a:context.max_level - a:context.level.current,
+ \ 'rank' : a:context.lnum_total,
+ \ 'type' : 'todo',
+ \ }
+endfunction
+
+" }}}1
+function! s:matcher_todonotes.continue(context) abort dict " {{{1
+ let [l:end, l:count] = s:find_closing(0, a:context.line, self.count, '{')
+ if l:count == 0
+ let a:context.entry.title .= strpart(a:context.line, 0, l:end)
+ unlet! s:matcher_continue
+ else
+ let a:context.entry.title .= a:context.line
+ let self.count = l:count
+ endif
+endfunction
+
+" }}}1
+
+let s:matcher_labels = {
+ \ 're' : g:vimtex#re#not_comment . '\\label\{\zs.{-}\ze\}',
+ \ 'priority' : 1,
+ \}
+function! s:matcher_labels.get_entry(context) abort dict " {{{1
+ return {
+ \ 'title' : matchstr(a:context.line, self.re),
+ \ 'number' : '',
+ \ 'file' : a:context.file,
+ \ 'line' : a:context.lnum,
+ \ 'level' : a:context.max_level - a:context.level.current,
+ \ 'rank' : a:context.lnum_total,
+ \ 'type' : 'label',
+ \ }
+endfunction
+" }}}1
+
+let s:matcher_beamer_frame = {
+ \ 're' : '^\s*\\begin{frame}',
+ \ 'priority' : 0,
+ \}
+function! s:matcher_beamer_frame.get_entry(context) abort dict " {{{1
+ let l:title = vimtex#util#trim(
+ \ matchstr(a:context.line, self.re . '\s*{\zs.*\ze}\s*$'))
+
+ return {
+ \ 'title' : 'Frame' . (empty(l:title) ? '' : ': ' . l:title),
+ \ 'number' : '',
+ \ 'file' : a:context.file,
+ \ 'line' : a:context.lnum,
+ \ 'level' : a:context.max_level - a:context.level.current,
+ \ 'rank' : a:context.lnum_total,
+ \ 'type' : 'content',
+ \ }
+endfunction
+" }}}1
+
+"
+" Utility functions
+"
+function! s:clear_texorpdfstring(title) abort " {{{1
+ let l:i1 = match(a:title, '\\texorpdfstring')
+ if l:i1 < 0 | return a:title | endif
+
+ " Find start of included part
+ let [l:i2, l:dummy] = s:find_closing(
+ \ match(a:title, '{', l:i1+1), a:title, 1, '{')
+ let l:i2 = match(a:title, '{', l:i2+1)
+ if l:i2 < 0 | return a:title | endif
+
+ " Find end of included part
+ let [l:i3, l:dummy] = s:find_closing(l:i2, a:title, 1, '{')
+ if l:i3 < 0 | return a:title | endif
+
+ return strpart(a:title, 0, l:i1)
+ \ . strpart(a:title, l:i2+1, l:i3-l:i2-1)
+ \ . s:clear_texorpdfstring(strpart(a:title, l:i3+1))
+endfunction
+
+" }}}1
+function! s:find_closing(start, string, count, type) abort " {{{1
+ if a:type ==# '{'
+ let l:re = '{\|}'
+ let l:open = '{'
+ else
+ let l:re = '\[\|\]'
+ let l:open = '['
+ endif
+ let l:i2 = a:start-1
+ let l:count = a:count
+ while l:count > 0
+ let l:i2 = match(a:string, l:re, l:i2+1)
+ if l:i2 < 0 | break | endif
+
+ if a:string[l:i2] ==# l:open
+ let l:count += 1
+ else
+ let l:count -= 1
+ endif
+ endwhile
+
+ return [l:i2, l:count]
+endfunction
+
+" }}}1
+function! s:sort_by_priority(d1, d2) abort " {{{1
+ let l:p1 = get(a:d1, 'priority')
+ let l:p2 = get(a:d2, 'priority')
+ return l:p1 >= l:p2 ? l:p1 > l:p2 : -1
+endfunction
+
+" }}}1
+
+"
+" Section level counter
+"
+let s:level = {}
+function! s:level.reset(part, level) abort dict " {{{1
+ if a:part ==# 'preamble'
+ let self.old = []
+ else
+ let self.old += [copy(self)]
+ endif
+
+ let self.preamble = 0
+ let self.frontmatter = 0
+ let self.mainmatter = 0
+ let self.appendix = 0
+ let self.backmatter = 0
+ let self.part = 0
+ let self.chapter = 0
+ let self.section = 0
+ let self.subsection = 0
+ let self.subsubsection = 0
+ let self.subsubsubsection = 0
+ let self.paragraph = 0
+ let self.subparagraph = 0
+ let self.current = a:level
+ let self[a:part] = 1
+endfunction
+
+" }}}1
+function! s:level.increment(level) abort dict " {{{1
+ let self.current = s:sec_to_value[a:level]
+
+ let self.part_toggle = 0
+
+ if a:level ==# 'part'
+ let self.part += 1
+ let self.part_toggle = 1
+ elseif a:level ==# 'chapter'
+ let self.chapter += 1
+ let self.section = 0
+ let self.subsection = 0
+ let self.subsubsection = 0
+ let self.subsubsubsection = 0
+ let self.paragraph = 0
+ let self.subparagraph = 0
+ elseif a:level ==# 'section'
+ let self.section += 1
+ let self.subsection = 0
+ let self.subsubsection = 0
+ let self.subsubsubsection = 0
+ let self.paragraph = 0
+ let self.subparagraph = 0
+ elseif a:level ==# 'subsection'
+ let self.subsection += 1
+ let self.subsubsection = 0
+ let self.subsubsubsection = 0
+ let self.paragraph = 0
+ let self.subparagraph = 0
+ elseif a:level ==# 'subsubsection'
+ let self.subsubsection += 1
+ let self.subsubsubsection = 0
+ let self.paragraph = 0
+ let self.subparagraph = 0
+ elseif a:level ==# 'subsubsubsection'
+ let self.subsubsubsection += 1
+ let self.paragraph = 0
+ let self.subparagraph = 0
+ elseif a:level ==# 'paragraph'
+ let self.paragraph += 1
+ let self.subparagraph = 0
+ elseif a:level ==# 'subparagraph'
+ let self.subparagraph += 1
+ endif
+endfunction
+
+" }}}1
+
+let s:sec_to_value = {
+ \ '_' : 0,
+ \ 'subparagraph' : 1,
+ \ 'paragraph' : 2,
+ \ 'subsubsubsection' : 3,
+ \ 'subsubsection' : 4,
+ \ 'subsection' : 5,
+ \ 'section' : 6,
+ \ 'chapter' : 7,
+ \ 'part' : 8,
+ \ }
+
+"
+" Create the lists of matchers
+"
+let s:matchers = map(
+ \ filter(items(s:), 'v:val[0] =~# ''^matcher_'''),
+ \ 'v:val[1]')
+ \ + g:vimtex_toc_custom_matchers
+call sort(s:matchers, function('s:sort_by_priority'))
+
+for s:m in s:matchers
+ if !has_key(s:m, 'get_entry')
+ let s:m.get_entry = function('vimtex#parser#toc#get_entry_general')
+ endif
+endfor
+unlet! s:m
+
+let s:matchers_preamble = filter(
+ \ deepcopy(s:matchers), "get(v:val, 'in_preamble')")
+let s:matchers_content = filter(
+ \ deepcopy(s:matchers), "get(v:val, 'in_content', 1)")
+
+endif
diff --git a/autoload/vimtex/paths.vim b/autoload/vimtex/paths.vim
new file mode 100644
index 00000000..0d308ed8
--- /dev/null
+++ b/autoload/vimtex/paths.vim
@@ -0,0 +1,91 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#paths#pushd(path) abort " {{{1
+ if empty(a:path) || getcwd() ==# fnamemodify(a:path, ':p')
+ let s:qpath += ['']
+ else
+ let s:qpath += [getcwd()]
+ execute s:cd fnameescape(a:path)
+ endif
+endfunction
+
+" }}}1
+function! vimtex#paths#popd() abort " {{{1
+ let l:path = remove(s:qpath, -1)
+ if !empty(l:path)
+ execute s:cd fnameescape(l:path)
+ endif
+endfunction
+
+" }}}1
+
+function! vimtex#paths#is_abs(path) abort " {{{1
+ return a:path =~# s:re_abs
+endfunction
+
+" }}}1
+
+function! vimtex#paths#shorten_relative(path) abort " {{{1
+ " Input: An absolute path
+ " Output: Relative path with respect to the vimtex root, path relative to
+ " vimtex root (unless absolute path is shorter)
+
+ let l:relative = vimtex#paths#relative(a:path, b:vimtex.root)
+ return strlen(l:relative) < strlen(a:path)
+ \ ? l:relative : a:path
+endfunction
+
+" }}}1
+function! vimtex#paths#relative(path, current) abort " {{{1
+ " Note: This algorithm is based on the one presented by @Offirmo at SO,
+ " http://stackoverflow.com/a/12498485/51634
+
+ let l:target = simplify(substitute(a:path, '\\', '/', 'g'))
+ let l:common = simplify(substitute(a:current, '\\', '/', 'g'))
+
+ " This only works on absolute paths
+ if !vimtex#paths#is_abs(l:target)
+ return substitute(a:path, '^\.\/', '', '')
+ endif
+
+ let l:tries = 50
+ let l:result = ''
+ while stridx(l:target, l:common) != 0 && l:tries > 0
+ let l:common = fnamemodify(l:common, ':h')
+ let l:result = empty(l:result) ? '..' : '../' . l:result
+ let l:tries -= 1
+ endwhile
+
+ if l:tries == 0 | return a:path | endif
+
+ if l:common ==# '/'
+ let l:result .= '/'
+ endif
+
+ let l:forward = strpart(l:target, strlen(l:common))
+ if !empty(l:forward)
+ let l:result = empty(l:result)
+ \ ? l:forward[1:]
+ \ : l:result . l:forward
+ endif
+
+ return l:result
+endfunction
+
+" }}}1
+
+
+let s:cd = exists('*haslocaldir') && haslocaldir()
+ \ ? 'lcd'
+ \ : exists(':tcd') && haslocaldir(-1) ? 'tcd' : 'cd'
+let s:qpath = get(s:, 'qpath', [])
+
+let s:re_abs = has('win32') ? '^[A-Z]:[\\/]' : '^/'
+
+endif
diff --git a/autoload/vimtex/pos.vim b/autoload/vimtex/pos.vim
new file mode 100644
index 00000000..127fcd46
--- /dev/null
+++ b/autoload/vimtex/pos.vim
@@ -0,0 +1,97 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#pos#set_cursor(...) abort " {{{1
+ call cursor(s:parse_args(a:000))
+endfunction
+
+" }}}1
+function! vimtex#pos#get_cursor() abort " {{{1
+ return exists('*getcurpos') ? getcurpos() : getpos('.')
+endfunction
+
+" }}}1
+function! vimtex#pos#get_cursor_line() abort " {{{1
+ let l:pos = vimtex#pos#get_cursor()
+ return l:pos[1]
+endfunction
+
+" }}}1
+
+function! vimtex#pos#val(...) abort " {{{1
+ let [l:lnum, l:cnum; l:rest] = s:parse_args(a:000)
+
+ return 100000*l:lnum + min([l:cnum, 90000])
+endfunction
+
+" }}}1
+function! vimtex#pos#next(...) abort " {{{1
+ let [l:lnum, l:cnum; l:rest] = s:parse_args(a:000)
+
+ return l:cnum < strlen(getline(l:lnum))
+ \ ? [0, l:lnum, l:cnum+1, 0]
+ \ : [0, l:lnum+1, 1, 0]
+endfunction
+
+" }}}1
+function! vimtex#pos#prev(...) abort " {{{1
+ let [l:lnum, l:cnum; l:rest] = s:parse_args(a:000)
+
+ return l:cnum > 1
+ \ ? [0, l:lnum, l:cnum-1, 0]
+ \ : [0, max([l:lnum-1, 1]), strlen(getline(l:lnum-1)), 0]
+endfunction
+
+" }}}1
+function! vimtex#pos#larger(pos1, pos2) abort " {{{1
+ return vimtex#pos#val(a:pos1) > vimtex#pos#val(a:pos2)
+endfunction
+
+" }}}1
+function! vimtex#pos#equal(p1, p2) abort " {{{1
+ let l:pos1 = s:parse_args(a:p1)
+ let l:pos2 = s:parse_args(a:p2)
+ return l:pos1[:1] == l:pos2[:1]
+endfunction
+
+" }}}1
+function! vimtex#pos#smaller(pos1, pos2) abort " {{{1
+ return vimtex#pos#val(a:pos1) < vimtex#pos#val(a:pos2)
+endfunction
+
+" }}}1
+
+function! s:parse_args(args) abort " {{{1
+ "
+ " The arguments should be in one of the following forms (when unpacked):
+ "
+ " [lnum, cnum]
+ " [bufnum, lnum, cnum, ...]
+ " {'lnum' : lnum, 'cnum' : cnum}
+ "
+
+ if len(a:args) > 1
+ return s:parse_args([a:args])
+ elseif len(a:args) == 1
+ if type(a:args[0]) == type({})
+ return [get(a:args[0], 'lnum'), get(a:args[0], 'cnum')]
+ else
+ if len(a:args[0]) == 2
+ return a:args[0]
+ else
+ return a:args[0][1:]
+ endif
+ endif
+ else
+ return a:args
+ endif
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/process.vim b/autoload/vimtex/process.vim
new file mode 100644
index 00000000..a22709c3
--- /dev/null
+++ b/autoload/vimtex/process.vim
@@ -0,0 +1,233 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#process#new(...) abort " {{{1
+ let l:opts = a:0 > 0 ? a:1 : {}
+ return extend(deepcopy(s:process), l:opts)
+endfunction
+
+" }}}1
+function! vimtex#process#run(cmd, ...) abort " {{{1
+ let l:opts = a:0 > 0 ? a:1 : {}
+ let l:opts.cmd = a:cmd
+ let l:process = vimtex#process#new(l:opts)
+
+ return l:process.run()
+endfunction
+
+" }}}1
+function! vimtex#process#capture(cmd) abort " {{{1
+ return vimtex#process#run(a:cmd, {'capture': 1})
+endfunction
+
+" }}}1
+function! vimtex#process#start(cmd, ...) abort " {{{1
+ let l:opts = a:0 > 0 ? a:1 : {}
+ let l:opts.continuous = 1
+ return vimtex#process#run(a:cmd, l:opts)
+endfunction
+
+" }}}1
+
+let s:process = {
+ \ 'cmd' : '',
+ \ 'pid' : 0,
+ \ 'background' : 1,
+ \ 'continuous' : 0,
+ \ 'output' : '',
+ \ 'workdir' : '',
+ \ 'silent' : 1,
+ \ 'capture' : 0,
+ \ 'result' : '',
+ \}
+
+function! s:process.run() abort dict " {{{1
+ if self._do_not_run() | return | endif
+
+ call self._pre_run()
+ call self._prepare()
+ call self._execute()
+ call self._restore()
+ call self._post_run()
+
+ return self.capture ? self.result : self
+endfunction
+
+" }}}1
+function! s:process.stop() abort dict " {{{1
+ if !self.pid | return | endif
+
+ let l:cmd = has('win32')
+ \ ? 'taskkill /PID ' . self.pid . ' /T /F'
+ \ : 'kill ' . self.pid
+ call vimtex#process#run(l:cmd, {'background': 0})
+
+ let self.pid = 0
+endfunction
+
+" }}}1
+function! s:process.pprint_items() abort dict " {{{1
+ let l:list = [
+ \ ['pid', self.pid ? self.pid : '-'],
+ \ ['cmd', get(self, 'prepared_cmd', self.cmd)],
+ \]
+
+ return l:list
+endfunction
+
+" }}}1
+
+function! s:process._do_not_run() abort dict " {{{1
+ if empty(self.cmd)
+ call vimtex#log#warning('Can''t run empty command')
+ return 1
+ endif
+ if self.pid
+ call vimtex#log#warning('Process already running!')
+ return 1
+ endif
+
+ return 0
+endfunction
+
+" }}}1
+function! s:process._pre_run() abort dict " {{{1
+ if self.capture
+ let self.silent = 0
+ let self.background = 0
+ elseif empty(self.output) && self.background
+ let self.output = 'null'
+ endif
+
+ call vimtex#paths#pushd(self.workdir)
+endfunction
+
+" }}}1
+function! s:process._execute() abort dict " {{{1
+ if self.capture
+ let self.result = split(system(self.prepared_cmd), '\n')
+ elseif self.silent
+ silent call system(self.prepared_cmd)
+ elseif self.background
+ silent execute '!' . self.prepared_cmd
+ if !has('gui_running')
+ redraw!
+ endif
+ else
+ execute '!' . self.prepared_cmd
+ endif
+
+ " Capture the pid if relevant
+ if has_key(self, 'set_pid') && self.continuous
+ call self.set_pid()
+ endif
+endfunction
+
+" }}}1
+function! s:process._post_run() abort dict " {{{1
+ call vimtex#paths#popd()
+endfunction
+
+" }}}1
+
+if has('win32')
+ function! s:process._prepare() abort dict " {{{1
+ if &shell !~? 'cmd'
+ let self.win32_restore_shell = 1
+ let self.win32_saved_shell = [
+ \ &shell,
+ \ &shellcmdflag,
+ \ &shellxquote,
+ \ &shellxescape,
+ \ &shellquote,
+ \ &shellpipe,
+ \ &shellredir,
+ \ &shellslash
+ \]
+ set shell& shellcmdflag& shellxquote& shellxescape&
+ set shellquote& shellpipe& shellredir& shellslash&
+ else
+ let self.win32_restore_shell = 0
+ endif
+
+ let l:cmd = self.cmd
+
+ if self.background
+ if !empty(self.output)
+ let l:cmd .= self.output ==# 'null'
+ \ ? ' >nul'
+ \ : ' >' . self.output
+ let l:cmd = 'cmd /s /c "' . l:cmd . '"'
+ else
+ let l:cmd = 'cmd /c "' . l:cmd . '"'
+ endif
+ let l:cmd = 'start /b ' . cmd
+ endif
+
+ if self.silent && self.output ==# 'null'
+ let self.prepared_cmd = '"' . l:cmd . '"'
+ else
+ let self.prepared_cmd = l:cmd
+ endif
+ endfunction
+
+ " }}}1
+ function! s:process._restore() abort dict " {{{1
+ if self.win32_restore_shell
+ let [ &shell,
+ \ &shellcmdflag,
+ \ &shellxquote,
+ \ &shellxescape,
+ \ &shellquote,
+ \ &shellpipe,
+ \ &shellredir,
+ \ &shellslash] = self.win32_saved_shell
+ endif
+ endfunction
+
+ " }}}1
+ function! s:process.get_pid() abort dict " {{{1
+ let self.pid = 0
+ endfunction
+
+ " }}}1
+else
+ function! s:process._prepare() abort dict " {{{1
+ let l:cmd = self.cmd
+
+ if self.background
+ if !empty(self.output)
+ let l:cmd .= ' >'
+ \ . (self.output ==# 'null'
+ \ ? '/dev/null'
+ \ : shellescape(self.output))
+ \ . ' 2>&1'
+ endif
+ let l:cmd .= ' &'
+ endif
+
+ if !self.silent
+ let l:cmd = escape(l:cmd, '%#')
+ endif
+
+ let self.prepared_cmd = l:cmd
+ endfunction
+
+ " }}}1
+ function! s:process._restore() abort dict " {{{1
+ endfunction
+
+ " }}}1
+ function! s:process.get_pid() abort dict " {{{1
+ let self.pid = 0
+ endfunction
+
+ " }}}1
+endif
+
+endif
diff --git a/autoload/vimtex/profile.vim b/autoload/vimtex/profile.vim
new file mode 100644
index 00000000..bb1db390
--- /dev/null
+++ b/autoload/vimtex/profile.vim
@@ -0,0 +1,125 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#profile#start() abort " {{{1
+ profile start prof.log
+ profile func *
+endfunction
+
+" }}}1
+function! vimtex#profile#stop() abort " {{{1
+ profile stop
+ call s:fix_sids()
+endfunction
+
+" }}}1
+"
+function! vimtex#profile#open() abort " {{{1
+ source ~/.vim/vimrc
+ silent edit prof.log
+endfunction
+
+" }}}1
+function! vimtex#profile#print() abort " {{{1
+ for l:line in readfile('prof.log')
+ echo l:line
+ endfor
+ echo ''
+ quit!
+endfunction
+
+" }}}1
+
+function! vimtex#profile#file(filename) abort " {{{1
+ call vimtex#profile#start()
+
+ execute 'silent edit' a:filename
+
+ call vimtex#profile#stop()
+endfunction
+
+" }}}1
+function! vimtex#profile#command(cmd) abort " {{{1
+ call vimtex#profile#start()
+
+ execute a:cmd
+
+ call vimtex#profile#stop()
+endfunction
+
+" }}}1
+
+function! vimtex#profile#filter(sections) abort " {{{1
+ let l:lines = readfile('prof.log')
+ " call filter(l:lines, 'v:val !~# ''FTtex''')
+ " call filter(l:lines, 'v:val !~# ''LoadFTPlugin''')
+
+ let l:new = []
+ for l:sec in a:sections
+ call extend(l:new, s:get_section(l:sec, l:lines))
+ endfor
+
+ call writefile(l:new, 'prof.log')
+endfunction
+
+" }}}1
+
+function! s:fix_sids() abort " {{{1
+ let l:lines = readfile('prof.log')
+ let l:new = []
+ for l:line in l:lines
+ let l:sid = matchstr(l:line, '\v\<SNR\>\zs\d+\ze_')
+ if !empty(l:sid)
+ let l:filename = map(
+ \ vimtex#util#command('scriptnames'),
+ \ 'split(v:val, "\\v:=\\s+")[1]')[l:sid-1]
+ if l:filename =~# 'vimtex'
+ let l:filename = substitute(l:filename, '^.*autoload\/', '', '')
+ let l:filename = substitute(l:filename, '\.vim$', '#s:', '')
+ let l:filename = substitute(l:filename, '\/', '#', 'g')
+ else
+ let l:filename .= ':'
+ endif
+ call add(l:new, substitute(l:line, '\v\<SNR\>\d+_', l:filename, 'g'))
+ else
+ call add(l:new, substitute(l:line, '\s\+$', '', ''))
+ endif
+ endfor
+ call writefile(l:new, 'prof.log')
+endfunction
+
+" }}}1
+function! s:get_section(name, lines) abort " {{{1
+ let l:active = 0
+ let l:section = []
+ for l:line in a:lines
+ if l:active
+ if l:line =~# '^FUNCTION' && l:line !~# a:name
+ let l:active = 0
+ else
+ call add(l:section, l:line)
+ endif
+ continue
+ endif
+
+ if l:line =~# a:name
+ call add(l:section, l:line)
+ let l:active = 1
+ endif
+ endfor
+
+ if l:active
+ call add(l:section, ' ')
+ endif
+
+ return l:section
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/qf.vim b/autoload/vimtex/qf.vim
new file mode 100644
index 00000000..6cc530e6
--- /dev/null
+++ b/autoload/vimtex/qf.vim
@@ -0,0 +1,245 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#qf#init_buffer() abort " {{{1
+ if !g:vimtex_quickfix_enabled | return | endif
+
+ command! -buffer VimtexErrors call vimtex#qf#toggle()
+
+ nnoremap <buffer> <plug>(vimtex-errors) :call vimtex#qf#toggle()<cr>
+endfunction
+
+" }}}1
+function! vimtex#qf#init_state(state) abort " {{{1
+ if !g:vimtex_quickfix_enabled | return | endif
+
+ try
+ let l:qf = vimtex#qf#{g:vimtex_quickfix_method}#new()
+ call l:qf.init(a:state)
+ unlet l:qf.init
+ let a:state.qf = l:qf
+ catch /vimtex: Requirements not met/
+ call vimtex#log#warning(
+ \ 'Quickfix state not initialized!',
+ \ 'Please see :help g:vimtex_quickfix_method')
+ endtry
+endfunction
+
+" }}}1
+
+function! vimtex#qf#toggle() abort " {{{1
+ if vimtex#qf#is_open()
+ cclose
+ else
+ call vimtex#qf#open(1)
+ endif
+endfunction
+
+" }}}1
+function! vimtex#qf#open(force) abort " {{{1
+ if !exists('b:vimtex.qf.addqflist') | return | endif
+
+ try
+ call vimtex#qf#setqflist()
+ catch /Vimtex: No log file found/
+ if a:force
+ call vimtex#log#warning('No log file found')
+ endif
+ if g:vimtex_quickfix_mode > 0
+ cclose
+ endif
+ return
+ catch
+ call vimtex#log#error('Something went wrong when parsing log files!')
+ if g:vimtex_quickfix_mode > 0
+ cclose
+ endif
+ return
+ endtry
+
+ if empty(getqflist())
+ if a:force
+ call vimtex#log#info('No errors!')
+ endif
+ if g:vimtex_quickfix_mode > 0
+ cclose
+ endif
+ return
+ endif
+
+ "
+ " There are two options that determine when to open the quickfix window. If
+ " forced, the quickfix window is always opened when there are errors or
+ " warnings (forced typically imply that the functions is called from the
+ " normal mode mapping). Else the behaviour is based on the settings.
+ "
+ let l:errors_or_warnings = s:qf_has_errors()
+ \ || g:vimtex_quickfix_open_on_warning
+
+ if a:force || (g:vimtex_quickfix_mode > 0 && l:errors_or_warnings)
+ call s:window_save()
+ botright cwindow
+ if g:vimtex_quickfix_mode == 2
+ call s:window_restore()
+ endif
+ if g:vimtex_quickfix_autoclose_after_keystrokes > 0
+ augroup vimtex_qf_autoclose
+ autocmd!
+ autocmd CursorMoved,CursorMovedI * call s:qf_autoclose_check()
+ augroup END
+ endif
+ redraw
+ endif
+endfunction
+
+" }}}1
+function! vimtex#qf#setqflist(...) abort " {{{1
+ if !exists('b:vimtex.qf.addqflist') | return | endif
+
+ if a:0 > 0
+ let l:tex = a:1
+ let l:log = fnamemodify(l:tex, ':r') . '.log'
+ let l:blg = fnamemodify(l:tex, ':r') . '.blg'
+ let l:jump = 0
+ else
+ let l:tex = b:vimtex.tex
+ let l:log = b:vimtex.log()
+ let l:blg = b:vimtex.ext('blg')
+ let l:jump = g:vimtex_quickfix_autojump
+ endif
+
+ try
+ " Initialize the quickfix list
+ " Note: Only create new list if the current list is not a vimtex qf list
+ if get(getqflist({'title': 1}), 'title') =~# 'Vimtex'
+ call setqflist([], 'r')
+ else
+ call setqflist([])
+ endif
+
+ " Parse LaTeX errors
+ call b:vimtex.qf.addqflist(l:tex, l:log)
+
+ " Parse bibliography errors
+ if has_key(b:vimtex.packages, 'biblatex')
+ call vimtex#qf#biblatex#addqflist(l:blg)
+ else
+ call vimtex#qf#bibtex#addqflist(l:blg)
+ endif
+
+ " Ignore entries if desired
+ if !empty(g:vimtex_quickfix_ignore_filters)
+ let l:qflist = getqflist()
+ for l:re in g:vimtex_quickfix_ignore_filters
+ call filter(l:qflist, 'v:val.text !~# l:re')
+ endfor
+ call setqflist(l:qflist, 'r')
+ endif
+
+ " Set title if supported
+ try
+ call setqflist([], 'r', {'title': 'Vimtex errors (' . b:vimtex.qf.name . ')'})
+ catch
+ endtry
+
+ " Jump to first error if wanted
+ if l:jump
+ cfirst
+ endif
+ catch /Vimtex: No log file found/
+ throw 'Vimtex: No log file found'
+ endtry
+endfunction
+
+" }}}1
+function! vimtex#qf#inquire(file) abort " {{{1
+ try
+ call vimtex#qf#setqflist(a:file)
+ return s:qf_has_errors()
+ catch
+ return 0
+ endtry
+endfunction
+
+" }}}1
+
+function! vimtex#qf#is_open() abort " {{{1
+ redir => l:bufstring
+ silent! ls!
+ redir END
+
+ let l:buflist = filter(split(l:bufstring, '\n'), 'v:val =~# ''Quickfix''')
+
+ for l:line in l:buflist
+ let l:bufnr = str2nr(matchstr(l:line, '^\s*\zs\d\+'))
+ if bufwinnr(l:bufnr) >= 0
+ \ && getbufvar(l:bufnr, '&buftype', '') ==# 'quickfix'
+ return 1
+ endif
+ endfor
+
+ return 0
+endfunction
+
+" }}}1
+
+function! s:window_save() abort " {{{1
+ if exists('*win_gotoid')
+ let s:previous_window = win_getid()
+ else
+ let w:vimtex_remember_window = 1
+ endif
+endfunction
+
+" }}}1
+function! s:window_restore() abort " {{{1
+ if exists('*win_gotoid')
+ call win_gotoid(s:previous_window)
+ else
+ for l:winnr in range(1, winnr('$'))
+ if getwinvar(l:winnr, 'vimtex_remember_window')
+ execute l:winnr . 'wincmd p'
+ unlet! w:vimtex_remember_window
+ endif
+ endfor
+ endif
+endfunction
+
+" }}}1
+
+function! s:qf_has_errors() abort " {{{1
+ return len(filter(getqflist(), 'v:val.type ==# ''E''')) > 0
+endfunction
+
+" }}}1
+"
+function! s:qf_autoclose_check() abort " {{{1
+ if get(s:, 'keystroke_counter') == 0
+ let s:keystroke_counter = g:vimtex_quickfix_autoclose_after_keystrokes
+ endif
+
+ redir => l:bufstring
+ silent! ls!
+ redir END
+
+ if empty(filter(split(l:bufstring, '\n'), 'v:val =~# ''%a- .*Quickfix'''))
+ let s:keystroke_counter -= 1
+ else
+ let s:keystroke_counter = g:vimtex_quickfix_autoclose_after_keystrokes + 1
+ endif
+
+ if s:keystroke_counter == 0
+ cclose
+ autocmd! vimtex_qf_autoclose
+ augroup! vimtex_qf_autoclose
+ endif
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/qf/biblatex.vim b/autoload/vimtex/qf/biblatex.vim
new file mode 100644
index 00000000..2e5e08a5
--- /dev/null
+++ b/autoload/vimtex/qf/biblatex.vim
@@ -0,0 +1,250 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#qf#biblatex#addqflist(blg) abort " {{{1
+ if get(g:vimtex_quickfix_blgparser, 'disable') | return | endif
+
+ try
+ call s:biblatex.addqflist(a:blg)
+ catch /biblatex Aborted/
+ endtry
+endfunction
+
+" }}}1
+
+let s:biblatex = {
+ \ 'file' : '',
+ \ 'types' : [],
+ \ 'db_files' : [],
+ \}
+function! s:biblatex.addqflist(blg) abort " {{{1
+ let self.file = a:blg
+ let self.root = fnamemodify(a:blg, ':h')
+ if empty(self.file) | throw 'biblatex Aborted' | endif
+
+ let self.types = map(
+ \ filter(items(s:), 'v:val[0] =~# ''^type_'''),
+ \ 'v:val[1]')
+ let self.db_files = []
+
+ let self.errorformat_saved = &l:errorformat
+ setlocal errorformat=%+E%.%#\>\ ERROR%m
+ setlocal errorformat+=%+W%.%#\>\ WARN\ -\ Duplicate\ entry%m
+ setlocal errorformat+=%+W%.%#\>\ WARN\ -\ The\ entry%.%#cannot\ be\ encoded%m
+ setlocal errorformat+=%-G%.%#
+ execute 'caddfile' fnameescape(self.file)
+ let &l:errorformat = self.errorformat_saved
+
+ call self.fix_paths()
+endfunction
+
+" }}}1
+function! s:biblatex.fix_paths() abort " {{{1
+ let l:qflist = getqflist()
+ try
+ let l:title = getqflist({'title': 1})
+ catch /E118/
+ let l:title = 'Vimtex errors'
+ endtry
+
+ for l:qf in l:qflist
+ for l:type in self.types
+ if l:type.fix(self, l:qf) | break | endif
+ endfor
+ endfor
+
+ call setqflist(l:qflist, 'r')
+
+ " Set title if supported
+ try
+ call setqflist([], 'r', l:title)
+ catch
+ endtry
+endfunction
+
+" }}}1
+function! s:biblatex.get_db_files() abort " {{{1
+ if empty(self.db_files)
+ let l:preamble = vimtex#parser#preamble(b:vimtex.tex, {
+ \ 'root' : b:vimtex.root,
+ \})
+ let l:files = map(
+ \ filter(l:preamble, 'v:val =~# ''\\addbibresource'''),
+ \ 'matchstr(v:val, ''{\zs.*\ze}'')')
+ let self.db_files = []
+ for l:file in l:files
+ if filereadable(l:file)
+ let self.db_files += [l:file]
+ elseif filereadable(expand(l:file))
+ let self.db_files += [expand(l:file)]
+ else
+ let l:cand = vimtex#kpsewhich#run(l:file)
+ if len(l:cand) == 1
+ let self.db_files += [l:cand[0]]
+ endif
+ endif
+ endfor
+ endif
+
+ return self.db_files
+endfunction
+
+" }}}1
+function! s:biblatex.get_filename(name) abort " {{{1
+ if !filereadable(a:name)
+ for l:root in [self.root, b:vimtex.root]
+ let l:candidate = fnamemodify(simplify(l:root . '/' . a:name), ':.')
+ if filereadable(l:candidate)
+ return l:candidate
+ endif
+ endfor
+ endif
+
+ return a:name
+endfunction
+
+" }}}1
+function! s:biblatex.get_key_pos(key) abort " {{{1
+ for l:file in self.get_db_files()
+ let l:lnum = self.get_key_lnum(a:key, l:file)
+ if l:lnum > 0
+ return [l:file, l:lnum]
+ endif
+ endfor
+
+ return []
+endfunction
+
+" }}}1
+function! s:biblatex.get_key_lnum(key, filename) abort " {{{1
+ if !filereadable(a:filename) | return 0 | endif
+
+ let l:lines = readfile(a:filename)
+ let l:lnums = range(len(l:lines))
+ let l:annotated_lines = map(l:lnums, '[v:val, l:lines[v:val]]')
+ let l:matches = filter(l:annotated_lines, 'v:val[1] =~# ''^\s*@\w*{\s*\V' . a:key . '''')
+
+ return len(l:matches) > 0 ? l:matches[-1][0]+1 : 0
+endfunction
+
+" }}}1
+function! s:biblatex.get_entry_key(filename, lnum) abort " {{{1
+ for l:file in self.get_db_files()
+ if fnamemodify(l:file, ':t') !=# a:filename | continue | endif
+
+ let l:entry = get(filter(readfile(l:file, 0, a:lnum), 'v:val =~# ''^@'''), -1)
+ if empty(l:entry) | continue | endif
+
+ return matchstr(l:entry, '{\v\zs.{-}\ze(,|$)')
+ endfor
+
+ return ''
+endfunction
+
+" }}}1
+
+"
+" Parsers for the various warning types
+"
+
+let s:type_parse_error = {}
+function! s:type_parse_error.fix(ctx, entry) abort " {{{1
+ if a:entry.text =~# 'ERROR - BibTeX subsystem.*expected end of entry'
+ let l:matches = matchlist(a:entry.text, '\v(\S*\.bib).*line (\d+)')
+ let a:entry.filename = a:ctx.get_filename(fnamemodify(l:matches[1], ':t'))
+ let a:entry.lnum = l:matches[2]
+
+ " Use filename and line number to get entry name
+ let l:key = a:ctx.get_entry_key(a:entry.filename, a:entry.lnum)
+ if !empty(l:key)
+ let a:entry.text = 'biblatex: Error parsing entry with key "' . l:key . '"'
+ endif
+ return 1
+ endif
+endfunction
+
+" }}}1
+
+let s:type_duplicate = {}
+function! s:type_duplicate.fix(ctx, entry) abort " {{{1
+ if a:entry.text =~# 'WARN - Duplicate entry'
+ let l:matches = matchlist(a:entry.text, '\v: ''(\S*)'' in file ''(.{-})''')
+ let l:key = l:matches[1]
+ let a:entry.filename = a:ctx.get_filename(l:matches[2])
+ let a:entry.lnum = a:ctx.get_key_lnum(l:key, a:entry.filename)
+ let a:entry.text = 'biblatex: Duplicate entry key "' . l:key . '"'
+ return 1
+ endif
+endfunction
+
+" }}}1
+
+let s:type_no_driver = {}
+function! s:type_no_driver.fix(ctx, entry) abort " {{{1
+ if a:entry.text =~# 'No driver for entry type'
+ let l:key = matchstr(a:entry.text, 'entry type ''\v\zs.{-}\ze''')
+ let a:entry.text = 'biblatex: Using fallback driver for ''' . l:key . ''''
+
+ let l:pos = a:ctx.get_key_pos(l:key)
+ if !empty(l:pos)
+ let a:entry.filename = a:ctx.get_filename(l:pos[0])
+ let a:entry.lnum = l:pos[1]
+ if has_key(a:entry, 'bufnr')
+ unlet a:entry.bufnr
+ endif
+ endif
+
+ return 1
+ endif
+endfunction
+
+" }}}1
+
+let s:type_not_found = {}
+function! s:type_not_found.fix(ctx, entry) abort " {{{1
+ if a:entry.text =~# 'The following entry could not be found'
+ let l:key = split(a:entry.text, ' ')[-1]
+ let a:entry.text = 'biblatex: Entry with key ''' . l:key . ''' not found'
+
+ for [l:file, l:lnum, l:line] in vimtex#parser#tex(b:vimtex.tex)
+ if l:line =~# g:vimtex#re#not_comment . '\\\S*\V' . l:key
+ let a:entry.lnum = l:lnum
+ let a:entry.filename = l:file
+ unlet a:entry.bufnr
+ break
+ endif
+ endfor
+
+ return 1
+ endif
+endfunction
+
+" }}}1
+
+let s:type_encoding = {}
+function! s:type_encoding.fix(ctx, entry) abort " {{{1
+ if a:entry.text =~# 'The entry .* has characters which cannot'
+ let l:key = matchstr(a:entry.text, 'The entry ''\v\zs.{-}\ze''')
+ let a:entry.text = 'biblatex: Entry with key ''' . l:key . ''' has non-ascii characters'
+
+ let l:pos = a:ctx.get_key_pos(l:key)
+ if !empty(l:pos)
+ let a:entry.filename = a:ctx.get_filename(l:pos[0])
+ let a:entry.lnum = l:pos[1]
+ if has_key(a:entry, 'bufnr')
+ unlet a:entry.bufnr
+ endif
+ endif
+
+ return 1
+ endif
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/qf/bibtex.vim b/autoload/vimtex/qf/bibtex.vim
new file mode 100644
index 00000000..94bdcafa
--- /dev/null
+++ b/autoload/vimtex/qf/bibtex.vim
@@ -0,0 +1,187 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#qf#bibtex#addqflist(blg) abort " {{{1
+ if get(g:vimtex_quickfix_blgparser, 'disable') | return | endif
+
+ try
+ call s:bibtex.addqflist(a:blg)
+ catch /BibTeX Aborted/
+ endtry
+endfunction
+
+" }}}1
+
+let s:bibtex = {
+ \ 'file' : '',
+ \ 'types' : [],
+ \ 'db_files' : [],
+ \}
+function! s:bibtex.addqflist(blg) abort " {{{1
+ let self.file = a:blg
+ if empty(self.file) || !filereadable(self.file) | throw 'BibTeX Aborted' | endif
+
+ let self.types = map(
+ \ filter(items(s:), 'v:val[0] =~# ''^type_'''),
+ \ 'v:val[1]')
+ let self.db_files = []
+
+ let self.errorformat_saved = &l:errorformat
+ setlocal errorformat=%+E%.%#---line\ %l\ of\ file\ %f
+ setlocal errorformat+=%+EI\ found\ %.%#---while\ reading\ file\ %f
+ setlocal errorformat+=%+WWarning--empty\ %.%#\ in\ %.%m
+ setlocal errorformat+=%+WWarning--entry\ type\ for%m
+ setlocal errorformat+=%-C--line\ %l\ of\ file\ %f
+ setlocal errorformat+=%-G%.%#
+ execute 'caddfile' fnameescape(self.file)
+ let &l:errorformat = self.errorformat_saved
+
+ call self.fix_paths()
+endfunction
+
+" }}}1
+function! s:bibtex.fix_paths() abort " {{{1
+ let l:qflist = getqflist()
+ try
+ let l:title = getqflist({'title': 1})
+ catch /E118/
+ let l:title = 'Vimtex errors'
+ endtry
+
+ for l:qf in l:qflist
+ for l:type in self.types
+ if l:type.fix(self, l:qf) | break | endif
+ endfor
+ endfor
+
+ call setqflist(l:qflist, 'r')
+
+ " Set title if supported
+ try
+ call setqflist([], 'r', l:title)
+ catch
+ endtry
+endfunction
+
+" }}}1
+function! s:bibtex.get_db_files() abort " {{{1
+ if empty(self.db_files)
+ let l:build_dir = fnamemodify(b:vimtex.ext('log'), ':.:h') . '/'
+ for l:file in map(
+ \ filter(readfile(self.file), 'v:val =~# ''Database file #\d:'''),
+ \ 'matchstr(v:val, '': \zs.*'')')
+ if filereadable(l:file)
+ call add(self.db_files, l:file)
+ elseif filereadable(l:build_dir . l:file)
+ call add(self.db_files, l:build_dir . l:file)
+ endif
+ endfor
+ endif
+
+ return self.db_files
+endfunction
+
+" }}}1
+function! s:bibtex.get_key_loc(key) abort " {{{1
+ for l:file in self.get_db_files()
+ let l:lines = readfile(l:file)
+ let l:lnum = 0
+ for l:line in l:lines
+ let l:lnum += 1
+ if l:line =~# '^\s*@\w*{\s*\V' . a:key
+ return [l:file, l:lnum]
+ endif
+ endfor
+ endfor
+
+ return []
+endfunction
+
+" }}}1
+
+"
+" Parsers for the various warning types
+"
+
+let s:type_syn_error = {}
+function! s:type_syn_error.fix(ctx, entry) abort " {{{1
+ if a:entry.text =~# '---line \d\+ of file'
+ let a:entry.text = split(a:entry.text, '---')[0]
+ return 1
+ endif
+endfunction
+
+" }}}1
+
+let s:type_empty = {
+ \ 're' : '\vWarning--empty (.*) in (\S*)',
+ \}
+function! s:type_empty.fix(ctx, entry) abort " {{{1
+ let l:matches = matchlist(a:entry.text, self.re)
+ if empty(l:matches) | return 0 | endif
+
+ let l:type = l:matches[1]
+ let l:key = l:matches[2]
+
+ unlet a:entry.bufnr
+ let a:entry.text = printf('Missing "%s" in "%s"', l:type, l:key)
+
+ let l:loc = a:ctx.get_key_loc(l:key)
+ if !empty(l:loc)
+ let a:entry.filename = l:loc[0]
+ let a:entry.lnum = l:loc[1]
+ endif
+
+ return 1
+endfunction
+
+" }}}1
+
+let s:type_style_file_defined = {
+ \ 're' : '\vWarning--entry type for "(\w+)"',
+ \}
+function! s:type_style_file_defined.fix(ctx, entry) abort " {{{1
+ let l:matches = matchlist(a:entry.text, self.re)
+ if empty(l:matches) | return 0 | endif
+
+ let l:key = l:matches[1]
+
+ unlet a:entry.bufnr
+ let a:entry.text = 'Entry type for "' . l:key . '" isn''t style-file defined'
+
+ let l:loc = a:ctx.get_key_loc(l:key)
+ if !empty(l:loc)
+ let a:entry.filename = l:loc[0]
+ let a:entry.lnum = l:loc[1]
+ endif
+
+ return 1
+endfunction
+
+" }}}1
+
+let s:type_no_bibstyle = {}
+function! s:type_no_bibstyle.fix(ctx, entry) abort " {{{1
+ if a:entry.text =~# 'I found no \\bibstyle'
+ let a:entry.text = 'BibTeX found no \bibstyle command (missing \bibliographystyle?)'
+ let a:entry.filename = b:vimtex.tex
+ unlet a:entry.bufnr
+ for [l:file, l:lnum, l:line] in vimtex#parser#tex(b:vimtex.tex)
+ if l:line =~# g:vimtex#re#not_comment . '\\bibliography'
+ let a:entry.lnum = l:lnum
+ let a:entry.filename = l:file
+ break
+ endif
+ endfor
+ return 1
+ endif
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/qf/latexlog.vim b/autoload/vimtex/qf/latexlog.vim
new file mode 100644
index 00000000..0046543e
--- /dev/null
+++ b/autoload/vimtex/qf/latexlog.vim
@@ -0,0 +1,209 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#qf#latexlog#new() abort " {{{1
+ return deepcopy(s:qf)
+endfunction
+
+" }}}1
+
+
+let s:qf = {
+ \ 'name' : 'LaTeX logfile',
+ \}
+
+function! s:qf.init(state) abort dict "{{{1
+ let self.config = get(g:, 'vimtex_quickfix_latexlog', {})
+ let self.config.default = get(self.config, 'default', 1)
+ let self.config.packages = get(self.config, 'packages', {})
+ let self.config.packages.default = get(self.config.packages, 'default',
+ \ self.config.default)
+
+ let self.types = map(
+ \ filter(items(s:), 'v:val[0] =~# ''^type_'''),
+ \ 'v:val[1]')
+endfunction
+
+" }}}1
+function! s:qf.set_errorformat() abort dict "{{{1
+ "
+ " Note: The errorformat assumes we're using the -file-line-error with
+ " [pdf]latex. For more info, see |errorformat-LaTeX|.
+ "
+
+ " Push file to file stack
+ setlocal errorformat=%-P**%f
+ setlocal errorformat+=%-P**\"%f\"
+
+ " Match errors
+ setlocal errorformat+=%E!\ LaTeX\ %trror:\ %m
+ setlocal errorformat+=%E%f:%l:\ %m
+ setlocal errorformat+=%E!\ %m
+
+ " More info for undefined control sequences
+ setlocal errorformat+=%Z<argument>\ %m
+
+ " More info for some errors
+ setlocal errorformat+=%Cl.%l\ %m
+
+ "
+ " Define general warnings
+ "
+ let l:default = self.config.default
+ if get(self.config, 'font', l:default)
+ setlocal errorformat+=%+WLaTeX\ Font\ Warning:\ %.%#line\ %l%.%#
+ setlocal errorformat+=%-CLaTeX\ Font\ Warning:\ %m
+ setlocal errorformat+=%-C(Font)%m
+ else
+ setlocal errorformat+=%-WLaTeX\ Font\ Warning:\ %m
+ endif
+
+ if !get(self.config, 'references', l:default)
+ setlocal errorformat+=%-WLaTeX\ %.%#Warning:\ %.%#eference%.%#undefined%.%#line\ %l%.%#
+ setlocal errorformat+=%-WLaTeX\ %.%#Warning:\ %.%#undefined\ references.
+ endif
+
+ if get(self.config, 'general', l:default)
+ setlocal errorformat+=%+WLaTeX\ %.%#Warning:\ %.%#line\ %l%.%#
+ setlocal errorformat+=%+WLaTeX\ %.%#Warning:\ %m
+ endif
+
+ if get(self.config, 'overfull', l:default)
+ setlocal errorformat+=%+WOverfull\ %\\%\\hbox%.%#\ at\ lines\ %l--%*\\d
+ setlocal errorformat+=%+WOverfull\ %\\%\\hbox%.%#\ at\ line\ %l
+ setlocal errorformat+=%+WOverfull\ %\\%\\vbox%.%#\ at\ line\ %l
+ endif
+
+ if get(self.config, 'underfull', l:default)
+ setlocal errorformat+=%+WUnderfull\ %\\%\\hbox%.%#\ at\ lines\ %l--%*\\d
+ setlocal errorformat+=%+WUnderfull\ %\\%\\vbox%.%#\ at\ line\ %l
+ endif
+
+ "
+ " Define package related warnings
+ "
+ let l:default = self.config.packages.default
+ if get(self.config.packages, 'natbib', l:default)
+ setlocal errorformat+=%+WPackage\ natbib\ Warning:\ %m\ on\ input\ line\ %l.
+ else
+ setlocal errorformat+=%-WPackage\ natbib\ Warning:\ %m\ on\ input\ line\ %l.
+ endif
+
+ if get(self.config.packages, 'biblatex', l:default)
+ setlocal errorformat+=%+WPackage\ biblatex\ Warning:\ %m
+ setlocal errorformat+=%-C(biblatex)%.%#in\ t%.%#
+ setlocal errorformat+=%-C(biblatex)%.%#Please\ v%.%#
+ setlocal errorformat+=%-C(biblatex)%.%#LaTeX\ a%.%#
+ setlocal errorformat+=%-C(biblatex)%m
+ else
+ setlocal errorformat+=%-WPackage\ biblatex\ Warning:\ %m
+ endif
+
+ if get(self.config.packages, 'babel', l:default)
+ setlocal errorformat+=%+WPackage\ babel\ Warning:\ %m
+ setlocal errorformat+=%-Z(babel)%.%#input\ line\ %l.
+ setlocal errorformat+=%-C(babel)%m
+ else
+ setlocal errorformat+=%-WPackage\ babel\ Warning:\ %m
+ endif
+
+ if get(self.config.packages, 'hyperref', l:default)
+ setlocal errorformat+=%+WPackage\ hyperref\ Warning:\ %m
+ setlocal errorformat+=%-C(hyperref)%m\ on\ input\ line\ %l.
+ setlocal errorformat+=%-C(hyperref)%m
+ else
+ setlocal errorformat+=%-WPackage\ hyperref\ Warning:\ %m
+ endif
+
+ if get(self.config.packages, 'scrreprt', l:default)
+ setlocal errorformat+=%+WPackage\ scrreprt\ Warning:\ %m
+ setlocal errorformat+=%-C(scrreprt)%m
+ else
+ setlocal errorformat+=%-WPackage\ scrreprt\ Warning:\ %m
+ endif
+
+ if get(self.config.packages, 'fixltx2e', l:default)
+ setlocal errorformat+=%+WPackage\ fixltx2e\ Warning:\ %m
+ setlocal errorformat+=%-C(fixltx2e)%m
+ else
+ setlocal errorformat+=%-WPackage\ fixltx2e\ Warning:\ %m
+ endif
+
+ if get(self.config.packages, 'titlesec', l:default)
+ setlocal errorformat+=%+WPackage\ titlesec\ Warning:\ %m
+ setlocal errorformat+=%-C(titlesec)%m
+ else
+ setlocal errorformat+=%-WPackage\ titlesec\ Warning:\ %m
+ endif
+
+ if get(self.config.packages, 'general', l:default)
+ setlocal errorformat+=%+WPackage\ %.%#\ Warning:\ %m\ on\ input\ line\ %l.
+ setlocal errorformat+=%+WPackage\ %.%#\ Warning:\ %m
+ setlocal errorformat+=%-Z(%.%#)\ %m\ on\ input\ line\ %l.
+ setlocal errorformat+=%-C(%.%#)\ %m
+ endif
+
+ " Ignore unmatched lines
+ setlocal errorformat+=%-G%.%#
+endfunction
+
+" }}}1
+function! s:qf.addqflist(tex, log) abort dict "{{{1
+ if empty(a:log) || !filereadable(a:log)
+ throw 'Vimtex: No log file found'
+ endif
+
+ let self.errorformat_saved = &l:errorformat
+ call self.set_errorformat()
+ execute 'caddfile' fnameescape(a:log)
+ let &l:errorformat = self.errorformat_saved
+
+ " Apply some post processing of the quickfix list
+ let self.main = a:tex
+ let self.root = b:vimtex.root
+ call self.fix_paths()
+endfunction
+
+" }}}1
+function! s:qf.pprint_items() abort dict " {{{1
+ return [[ 'config', self.config ]]
+endfunction
+
+" }}}1
+function! s:qf.fix_paths() abort dict " {{{1
+ let l:qflist = getqflist()
+
+ for l:qf in l:qflist
+ " For errors and warnings that don't supply a file, the basename of the
+ " main file is used. However, if the working directory is not the root of
+ " the LaTeX project, than this results in bufnr = 0.
+ if l:qf.bufnr == 0
+ let l:qf.bufnr = bufnr(self.main)
+ continue
+ endif
+
+ " The buffer names of all file:line type errors are relative to the root of
+ " the main LaTeX file.
+ let l:file = fnamemodify(
+ \ simplify(self.root . '/' . bufname(l:qf.bufnr)), ':.')
+ if !filereadable(l:file) | continue | endif
+
+ if !bufexists(l:file)
+ execute 'badd' l:file
+ endif
+
+ let l:qf.filename = l:file
+ let l:qf.bufnr = bufnr(l:file)
+ endfor
+
+ call setqflist(l:qflist, 'r')
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/qf/pplatex.vim b/autoload/vimtex/qf/pplatex.vim
new file mode 100644
index 00000000..39e9a03e
--- /dev/null
+++ b/autoload/vimtex/qf/pplatex.vim
@@ -0,0 +1,98 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" CreatedBy: Johannes Wienke (languitar@semipol.de)
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#qf#pplatex#new() abort " {{{1
+ return deepcopy(s:qf)
+endfunction
+
+" }}}1
+
+
+let s:qf = {
+ \ 'name' : 'LaTeX logfile using pplatex',
+ \}
+
+function! s:qf.init(state) abort dict "{{{1
+ if !executable('pplatex')
+ call vimtex#log#error('pplatex is not executable!')
+ throw 'vimtex: Requirements not met'
+ endif
+
+ " Automatically remove the -file-line-error option if we use the latexmk
+ " backend (for convenience)
+ if a:state.compiler.name ==# 'latexmk'
+ let l:index = index(a:state.compiler.options, '-file-line-error')
+ if l:index >= 0
+ call remove(a:state.compiler.options, l:index)
+ endif
+ endif
+endfunction
+
+function! s:qf.set_errorformat() abort dict "{{{1
+ " Each new item starts with two asterics followed by the file, potentially
+ " a line number and sometimes even the message itself is on the same line.
+ " Please note that the trailing whitspaces in the error formats are
+ " intentional as pplatex produces these.
+
+ " Start of new items with file and line number, message on next line(s).
+ setlocal errorformat=%E**\ Error\ \ \ in\ %f\\,\ Line\ %l:%m
+ setlocal errorformat+=%W**\ Warning\ in\ %f\\,\ Line\ %l:%m
+ setlocal errorformat+=%I**\ BadBox\ \ in\ %f\\,\ Line\ %l:%m
+
+ " Start of items with with file, line and message on the same line. There are
+ " no BadBoxes reported this way.
+ setlocal errorformat+=%E**\ Error\ \ \ in\ %f\\,\ Line\ %l:%m
+ setlocal errorformat+=%W**\ Warning\ in\ %f\\,\ Line\ %l:%m
+
+ " Start of new items with only a file.
+ setlocal errorformat+=%E**\ Error\ \ \ in\ %f:%m
+ setlocal errorformat+=%W**\ Warning\ in\ %f:%m
+ setlocal errorformat+=%I**\ BadBox\ \ in\ %f:%m
+
+ " Start of items with with file and message on the same line. There are
+ " no BadBoxes reported this way.
+ setlocal errorformat+=%E**\ Error\ in\ %f:%m
+ setlocal errorformat+=%W**\ Warning\ in\ %f:%m
+
+ " Some errors are difficult even for pplatex
+ setlocal errorformat+=%E**\ Error\ \ :%m
+
+ " Anything that starts with three spaces is part of the message from a
+ " previously started multiline error item.
+ setlocal errorformat+=%C\ \ \ %m\ on\ input\ line\ %l.
+ setlocal errorformat+=%C\ \ \ %m
+
+ " Items are terminated with two newlines.
+ setlocal errorformat+=%-Z
+
+ " Skip statistical results at the bottom of the output.
+ setlocal errorformat+=%-GResult%.%#
+ setlocal errorformat+=%-G
+endfunction
+
+" }}}1
+function! s:qf.addqflist(tex, log) abort dict " {{{1
+ if empty(a:log) || !filereadable(a:log)
+ throw 'Vimtex: No log file found'
+ endif
+
+ let l:tmp = fnameescape(fnamemodify(a:log, ':r') . '.pplatex')
+ let l:log = fnameescape(a:log)
+
+ silent call system(printf('pplatex -i %s >%s', l:log, l:tmp))
+ let self.errorformat_saved = &l:errorformat
+ call self.set_errorformat()
+ execute 'caddfile' l:tmp
+ let &l:errorformat = self.errorformat_saved
+ silent call system('rm ' . l:tmp)
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/qf/pulp.vim b/autoload/vimtex/qf/pulp.vim
new file mode 100644
index 00000000..0f0b73ef
--- /dev/null
+++ b/autoload/vimtex/qf/pulp.vim
@@ -0,0 +1,67 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#qf#pulp#new() abort " {{{1
+ return deepcopy(s:qf)
+endfunction
+
+" }}}1
+
+
+let s:qf = {
+ \ 'name' : 'LaTeX logfile using pulp',
+ \}
+
+function! s:qf.init(state) abort dict "{{{1
+ if !executable('pulp')
+ call vimtex#log#error('pulp is not executable!')
+ throw 'vimtex: Requirements not met'
+ endif
+
+ " Automatically remove the -file-line-error option if we use the latexmk
+ " backend (for convenience)
+ if a:state.compiler.name ==# 'latexmk'
+ let l:index = index(a:state.compiler.options, '-file-line-error')
+ if l:index >= 0
+ call remove(a:state.compiler.options, l:index)
+ endif
+ endif
+endfunction
+
+function! s:qf.set_errorformat() abort dict "{{{1
+ setlocal errorformat=
+ setlocal errorformat+=%-G%*[^\ ])\ %.%#
+ setlocal errorformat+=%-G%.%#For\ some\ reason%.%#
+ setlocal errorformat+=%W%f:%l-%*[0-9?]:\ %*[^\ ]\ warning:\ %m
+ setlocal errorformat+=%E%f:%l-%*[0-9?]:\ %*[^\ ]\ error:\ %m
+ setlocal errorformat+=%W%f:%l-%*[0-9?]:\ %m
+ setlocal errorformat+=%W%l-%*[0-9?]:\ %m
+ setlocal errorformat+=%-G%.%#
+endfunction
+
+" }}}1
+function! s:qf.addqflist(tex, log) abort dict " {{{1
+ if empty(a:log) || !filereadable(a:log)
+ call setqflist([])
+ throw 'Vimtex: No log file found'
+ endif
+
+ let l:tmp = fnameescape(fnamemodify(a:log, ':r') . '.pulp')
+ let l:log = fnameescape(a:log)
+
+ silent call system(printf('pulp %s >%s', l:log, l:tmp))
+ let self.errorformat_saved = &l:errorformat
+ call self.set_errorformat()
+ execute 'caddfile' l:tmp
+ let &l:errorformat = self.errorformat_saved
+ silent call system('rm ' . l:tmp)
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/re.vim b/autoload/vimtex/re.vim
new file mode 100644
index 00000000..0b3aab58
--- /dev/null
+++ b/autoload/vimtex/re.vim
@@ -0,0 +1,110 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+let g:vimtex#re#not_bslash = '\v%(\\@<!%(\\\\)*)@<='
+let g:vimtex#re#not_comment = '\v%(' . g:vimtex#re#not_bslash . '\%.*)@<!'
+
+let g:vimtex#re#tex_input_root =
+ \ '\v^\s*\%\s*!?\s*[tT][eE][xX]\s+[rR][oO][oO][tT]\s*\=\s*\zs.*\ze\s*$'
+let g:vimtex#re#tex_input_latex = '\v\\%('
+ \ . join(get(g:, 'vimtex_include_indicators',
+ \ ['input', 'include', 'subfile', 'subfileinclude']),
+ \ '|') . ')\s*\{'
+let g:vimtex#re#tex_input_import =
+ \ '\v\\%(sub)?%(import|%(input|include)from)\*?\{[^\}]*\}\{'
+let g:vimtex#re#tex_input_package =
+ \ '\v\\%(usepackage|RequirePackage)%(\s*\[[^]]*\])?\s*\{\zs[^}]*\ze\}'
+
+let g:vimtex#re#tex_input = '\v^\s*%(' . join([
+ \ g:vimtex#re#tex_input_latex,
+ \ g:vimtex#re#tex_input_import,
+ \ ], '|') . ')'
+
+let g:vimtex#re#bib_input = '\v\\%(addbibresource|bibliography)>'
+
+let g:vimtex#re#tex_include = g:vimtex#re#tex_input_root
+ \ . '|' . g:vimtex#re#tex_input . '\zs[^\}]*\ze\}?'
+ \ . '|' . g:vimtex#re#tex_input_package
+
+" {{{1 Completion regexes
+let g:vimtex#re#neocomplete =
+ \ '\v\\%('
+ \ . '\a*cite\a*%(\s*\[[^]]*\]){0,2}\s*\{[^}]*'
+ \ . '|%(text|block)cquote\*?%(\s*\[[^]]*\]){0,2}\s*\{[^}]*'
+ \ . '|%(for|hy)\w*cquote\*?\{[^}]*}%(\s*\[[^]]*\]){0,2}\s*\{[^}]*'
+ \ . '|\a*ref%(\s*\{[^}]*|range\s*\{[^,}]*%(}\{)?)'
+ \ . '|hyperref\s*\[[^]]*'
+ \ . '|includegraphics\*?%(\s*\[[^]]*\]){0,2}\s*\{[^}]*'
+ \ . '|%(include%(only)?|input|subfile)\s*\{[^}]*'
+ \ . '|([cpdr]?(gls|Gls|GLS)|acr|Acr|ACR)\a*\s*\{[^}]*'
+ \ . '|(ac|Ac|AC)\s*\{[^}]*'
+ \ . '|includepdf%(\s*\[[^]]*\])?\s*\{[^}]*'
+ \ . '|includestandalone%(\s*\[[^]]*\])?\s*\{[^}]*'
+ \ . '|%(usepackage|RequirePackage|PassOptionsToPackage)%(\s*\[[^]]*\])?\s*\{[^}]*'
+ \ . '|documentclass%(\s*\[[^]]*\])?\s*\{[^}]*'
+ \ . '|begin%(\s*\[[^]]*\])?\s*\{[^}]*'
+ \ . '|end%(\s*\[[^]]*\])?\s*\{[^}]*'
+ \ . '|\a*'
+ \ . ')'
+
+let g:vimtex#re#deoplete = '\\(?:'
+ \ . '\w*cite\w*(?:\s*\[[^]]*\]){0,2}\s*{[^}]*'
+ \ . '|(text|block)cquote\*?(?:\s*\[[^]]*\]){0,2}\s*{[^}]*'
+ \ . '|(for|hy)\w*cquote\*?{[^}]*}(?:\s*\[[^]]*\]){0,2}\s*{[^}]*'
+ \ . '|\w*ref(?:\s*\{[^}]*|range\s*\{[^,}]*(?:}{)?)'
+ \ . '|hyperref\s*\[[^]]*'
+ \ . '|includegraphics\*?(?:\s*\[[^]]*\]){0,2}\s*\{[^}]*'
+ \ . '|(?:include(?:only)?|input|subfile)\s*\{[^}]*'
+ \ . '|([cpdr]?(gls|Gls|GLS)|acr|Acr|ACR)[a-zA-Z]*\s*\{[^}]*'
+ \ . '|(ac|Ac|AC)\s*\{[^}]*'
+ \ . '|includepdf(\s*\[[^]]*\])?\s*\{[^}]*'
+ \ . '|includestandalone(\s*\[[^]]*\])?\s*\{[^}]*'
+ \ . '|(usepackage|RequirePackage|PassOptionsToPackage)(\s*\[[^]]*\])?\s*\{[^}]*'
+ \ . '|documentclass(\s*\[[^]]*\])?\s*\{[^}]*'
+ \ . '|begin(\s*\[[^]]*\])?\s*\{[^}]*'
+ \ . '|end(\s*\[[^]]*\])?\s*\{[^}]*'
+ \ . '|\w*'
+ \ .')'
+
+let g:vimtex#re#ncm2#cmds = [
+ \ '\\[A-Za-z]+',
+ \ '\\(usepackage|RequirePackage|PassOptionsToPackage)(\s*\[[^]]*\])?\s*\{[^}]*',
+ \ '\\documentclass(\s*\[[^]]*\])?\s*\{[^}]*',
+ \ '\\begin(\s*\[[^]]*\])?\s*\{[^}]*',
+ \ '\\end(\s*\[[^]]*\])?\s*\{[^}]*',
+ \]
+let g:vimtex#re#ncm2#bibtex = [
+ \ '\\[A-Za-z]*cite[A-Za-z]*(\[[^]]*\]){0,2}{[^}]*',
+ \ '\\(text|block)cquote\*?(\[[^]]*\]){0,2}{[^}]*',
+ \ '\\(for|hy)[A-Za-z]*cquote\*?{[^}]*}(\[[^]]*\]){0,2}{[^}]*',
+ \]
+let g:vimtex#re#ncm2#labels = [
+ \ '\\[A-Za-z]*ref({[^}]*|range{([^,{}]*(}{)?))',
+ \ '\\hyperref\[[^]]*',
+ \ '\\([cpdr]?(gls|Gls|GLS)|acr|Acr|ACR)[a-zA-Z]*\s*\{[^}]*',
+ \ '\\(ac|Ac|AC)\s*\{[^}]*',
+ \]
+let g:vimtex#re#ncm2#files = [
+ \ '\\includegraphics\*?(\[[^]]*\]){0,2}{[^}]*',
+ \ '\\(include(only)?|input|subfile){[^}]*',
+ \ '\\includepdf(\s*\[[^]]*\])?\s*\{[^}]*',
+ \ '\\includestandalone(\s*\[[^]]*\])?\s*\{[^}]*',
+ \]
+
+let g:vimtex#re#ncm2 = g:vimtex#re#ncm2#cmds +
+ \ g:vimtex#re#ncm2#bibtex +
+ \ g:vimtex#re#ncm2#labels +
+ \ g:vimtex#re#ncm2#files
+
+let g:vimtex#re#ncm = copy(g:vimtex#re#ncm2)
+
+let g:vimtex#re#youcompleteme = map(copy(g:vimtex#re#ncm), "'re!' . v:val")
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/scratch.vim b/autoload/vimtex/scratch.vim
new file mode 100644
index 00000000..299cc0ee
--- /dev/null
+++ b/autoload/vimtex/scratch.vim
@@ -0,0 +1,72 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#scratch#new(opts) abort " {{{1
+ let l:buf = extend(deepcopy(s:scratch), a:opts)
+ call l:buf.open()
+endfunction
+
+" }}}1
+
+
+let s:scratch = {
+ \ 'name' : 'VimtexScratch'
+ \}
+function! s:scratch.open() abort dict " {{{1
+ let l:bufnr = bufnr('')
+ let l:vimtex = get(b:, 'vimtex', {})
+
+ silent execute 'keepalt edit' escape(self.name, ' ')
+
+ let self.prev_bufnr = l:bufnr
+ let b:scratch = self
+ let b:vimtex = l:vimtex
+
+ setlocal bufhidden=wipe
+ setlocal buftype=nofile
+ setlocal concealcursor=nvic
+ setlocal conceallevel=0
+ setlocal nobuflisted
+ setlocal nolist
+ setlocal nospell
+ setlocal noswapfile
+ setlocal nowrap
+ setlocal tabstop=8
+
+ nnoremap <silent><nowait><buffer> q :call b:scratch.close()<cr>
+ nnoremap <silent><nowait><buffer> <esc> :call b:scratch.close()<cr>
+ nnoremap <silent><nowait><buffer> <c-6> :call b:scratch.close()<cr>
+ nnoremap <silent><nowait><buffer> <c-^> :call b:scratch.close()<cr>
+ nnoremap <silent><nowait><buffer> <c-e> :call b:scratch.close()<cr>
+
+ if has_key(self, 'syntax')
+ call self.syntax()
+ endif
+
+ call self.fill()
+endfunction
+
+" }}}1
+function! s:scratch.close() abort dict " {{{1
+ silent execute 'keepalt buffer' self.prev_bufnr
+endfunction
+
+" }}}1
+function! s:scratch.fill() abort dict " {{{1
+ setlocal modifiable
+ %delete
+
+ call self.print_content()
+
+ 0delete _
+ setlocal nomodifiable
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/state.vim b/autoload/vimtex/state.vim
new file mode 100644
index 00000000..98019f89
--- /dev/null
+++ b/autoload/vimtex/state.vim
@@ -0,0 +1,745 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#state#init_buffer() abort " {{{1
+ command! -buffer VimtexToggleMain call vimtex#state#toggle_main()
+ command! -buffer VimtexReloadState call vimtex#state#reload()
+
+ nnoremap <buffer> <plug>(vimtex-toggle-main) :VimtexToggleMain<cr>
+ nnoremap <buffer> <plug>(vimtex-reload-state) :VimtexReloadState<cr>
+endfunction
+
+" }}}1
+function! vimtex#state#init() abort " {{{1
+ let [l:main, l:main_type] = s:get_main()
+ let l:id = s:get_main_id(l:main)
+
+ if l:id >= 0
+ let b:vimtex_id = l:id
+ let b:vimtex = s:vimtex_states[l:id]
+ else
+ let b:vimtex_id = s:vimtex_next_id
+ let b:vimtex = s:vimtex.new(l:main, l:main_type, 0)
+ let s:vimtex_next_id += 1
+ let s:vimtex_states[b:vimtex_id] = b:vimtex
+ endif
+endfunction
+
+" }}}1
+function! vimtex#state#init_local() abort " {{{1
+ let l:filename = expand('%:p')
+ let l:preserve_root = get(s:, 'subfile_preserve_root')
+ unlet! s:subfile_preserve_root
+
+ if b:vimtex.tex ==# l:filename | return | endif
+
+ let l:vimtex_id = s:get_main_id(l:filename)
+
+ if l:vimtex_id < 0
+ let l:vimtex_id = s:vimtex_next_id
+ let l:vimtex = s:vimtex.new(l:filename, 'local file', l:preserve_root)
+ let s:vimtex_next_id += 1
+ let s:vimtex_states[l:vimtex_id] = l:vimtex
+
+ if !has_key(b:vimtex, 'subids')
+ let b:vimtex.subids = []
+ endif
+ call add(b:vimtex.subids, l:vimtex_id)
+ let l:vimtex.main_id = b:vimtex_id
+ endif
+
+ let b:vimtex_local = {
+ \ 'active' : 0,
+ \ 'main_id' : b:vimtex_id,
+ \ 'sub_id' : l:vimtex_id,
+ \}
+endfunction
+
+" }}}1
+function! vimtex#state#reload() abort " {{{1
+ let l:id = s:get_main_id(expand('%:p'))
+ if has_key(s:vimtex_states, l:id)
+ let l:vimtex = remove(s:vimtex_states, l:id)
+ call l:vimtex.cleanup()
+ endif
+
+ if has_key(s:vimtex_states, get(b:, 'vimtex_id', -1))
+ let l:vimtex = remove(s:vimtex_states, b:vimtex_id)
+ call l:vimtex.cleanup()
+ endif
+
+ call vimtex#state#init()
+ call vimtex#state#init_local()
+endfunction
+
+" }}}1
+
+function! vimtex#state#toggle_main() abort " {{{1
+ if exists('b:vimtex_local')
+ let b:vimtex_local.active = !b:vimtex_local.active
+
+ let b:vimtex_id = b:vimtex_local.active
+ \ ? b:vimtex_local.sub_id
+ \ : b:vimtex_local.main_id
+ let b:vimtex = vimtex#state#get(b:vimtex_id)
+
+ call vimtex#log#info('Changed to `' . b:vimtex.base . "' "
+ \ . (b:vimtex_local.active ? '[local]' : '[main]'))
+ endif
+endfunction
+
+" }}}1
+function! vimtex#state#list_all() abort " {{{1
+ return values(s:vimtex_states)
+endfunction
+
+" }}}1
+function! vimtex#state#exists(id) abort " {{{1
+ return has_key(s:vimtex_states, a:id)
+endfunction
+
+" }}}1
+function! vimtex#state#get(id) abort " {{{1
+ return s:vimtex_states[a:id]
+endfunction
+
+" }}}1
+function! vimtex#state#get_all() abort " {{{1
+ return s:vimtex_states
+endfunction
+
+" }}}1
+function! vimtex#state#cleanup(id) abort " {{{1
+ if !vimtex#state#exists(a:id) | return | endif
+
+ "
+ " Count the number of open buffers for the given blob
+ "
+ let l:buffers = filter(range(1, bufnr('$')), 'buflisted(v:val)')
+ let l:ids = map(l:buffers, 'getbufvar(v:val, ''vimtex_id'', -1)')
+ let l:count = count(l:ids, a:id)
+
+ "
+ " Don't clean up if there are more than one buffer connected to the current
+ " blob
+ "
+ if l:count > 1 | return | endif
+ let l:vimtex = vimtex#state#get(a:id)
+
+ "
+ " Handle possible subfiles properly
+ "
+ if has_key(l:vimtex, 'subids')
+ let l:subcount = 0
+ for l:sub_id in get(l:vimtex, 'subids', [])
+ let l:subcount += count(l:ids, l:sub_id)
+ endfor
+ if l:count + l:subcount > 1 | return | endif
+
+ for l:sub_id in get(l:vimtex, 'subids', [])
+ call remove(s:vimtex_states, l:sub_id).cleanup()
+ endfor
+
+ call remove(s:vimtex_states, a:id).cleanup()
+ else
+ call remove(s:vimtex_states, a:id).cleanup()
+
+ if has_key(l:vimtex, 'main_id')
+ let l:main = vimtex#state#get(l:vimtex.main_id)
+
+ let l:count_main = count(l:ids, l:vimtex.main_id)
+ for l:sub_id in get(l:main, 'subids', [])
+ let l:count_main += count(l:ids, l:sub_id)
+ endfor
+
+ if l:count_main + l:count <= 1
+ call remove(s:vimtex_states, l:vimtex.main_id).cleanup()
+ endif
+ endif
+ endif
+endfunction
+
+" }}}1
+
+function! s:get_main_id(main) abort " {{{1
+ for [l:id, l:state] in items(s:vimtex_states)
+ if l:state.tex == a:main
+ return str2nr(l:id)
+ endif
+ endfor
+
+ return -1
+endfunction
+
+function! s:get_main() abort " {{{1
+ if exists('s:disabled_modules')
+ unlet s:disabled_modules
+ endif
+
+ "
+ " Use buffer variable if it exists
+ "
+ if exists('b:vimtex_main') && filereadable(b:vimtex_main)
+ return [fnamemodify(b:vimtex_main, ':p'), 'buffer variable']
+ endif
+
+ "
+ " Search for TEX root specifier at the beginning of file. This is used by
+ " several other plugins and editors.
+ "
+ let l:candidate = s:get_main_from_texroot()
+ if !empty(l:candidate)
+ return [l:candidate, 'texroot specifier']
+ endif
+
+ "
+ " Check if the current file is a main file
+ "
+ if s:file_is_main(expand('%:p'))
+ return [expand('%:p'), 'current file verified']
+ endif
+
+ "
+ " Support for subfiles package
+ "
+ let l:candidate = s:get_main_from_subfile()
+ if !empty(l:candidate)
+ return [l:candidate, 'subfiles']
+ endif
+
+ "
+ " Search for .latexmain-specifier
+ "
+ let l:candidate = s:get_main_latexmain(expand('%:p'))
+ if !empty(l:candidate)
+ return [l:candidate, 'latexmain specifier']
+ endif
+
+ "
+ " Search for .latexmkrc @default_files specifier
+ "
+ let l:candidate = s:get_main_latexmk()
+ if !empty(l:candidate)
+ return [l:candidate, 'latexmkrc @default_files']
+ endif
+
+ "
+ " Check if we are class or style file
+ "
+ if index(['cls', 'sty'], expand('%:e')) >= 0
+ let l:id = getbufvar('#', 'vimtex_id', -1)
+ if l:id >= 0 && has_key(s:vimtex_states, l:id)
+ return [s:vimtex_states[l:id].tex, 'cls/sty file (inherit from alternate)']
+ else
+ let s:disabled_modules = ['latexmk', 'view', 'toc']
+ return [expand('%:p'), 'cls/sty file']
+ endif
+ endif
+
+ "
+ " Search for main file recursively through include specifiers
+ "
+ if !get(g:, 'vimtex_disable_recursive_main_file_detection', 0)
+ let l:candidate = s:get_main_choose(s:get_main_recurse())
+ if !empty(l:candidate)
+ return [l:candidate, 'recursive search']
+ endif
+ endif
+
+ "
+ " Use fallback candidate or the current file
+ "
+ let l:candidate = get(s:, 'cand_fallback', expand('%:p'))
+ if exists('s:cand_fallback')
+ unlet s:cand_fallback
+ return [l:candidate, 'fallback']
+ else
+ return [l:candidate, 'current file']
+ endif
+endfunction
+
+" }}}1
+function! s:get_main_from_texroot() abort " {{{1
+ for l:line in getline(1, 5)
+ let l:file_pattern = matchstr(l:line, g:vimtex#re#tex_input_root)
+ if empty(l:file_pattern) | continue | endif
+
+ if !vimtex#paths#is_abs(l:file_pattern)
+ let l:file_pattern = simplify(expand('%:p:h') . '/' . l:file_pattern)
+ endif
+
+ let l:candidates = glob(l:file_pattern, 0, 1)
+ if len(l:candidates) > 1
+ return s:get_main_choose(l:candidates)
+ elseif len(l:candidates) == 1
+ return l:candidates[0]
+ endif
+ endfor
+
+ return ''
+endfunction
+
+" }}}1
+function! s:get_main_from_subfile() abort " {{{1
+ for l:line in getline(1, 5)
+ let l:filename = matchstr(l:line,
+ \ '^\C\s*\\documentclass\[\zs.*\ze\]{subfiles}')
+ if len(l:filename) > 0
+ if l:filename !~# '\.tex$'
+ let l:filename .= '.tex'
+ endif
+
+ if vimtex#paths#is_abs(l:filename)
+ " Specified path is absolute
+ if filereadable(l:filename) | return l:filename | endif
+ else
+ " Try specified path as relative to current file path
+ let l:candidate = simplify(expand('%:p:h') . '/' . l:filename)
+ if filereadable(l:candidate) | return l:candidate | endif
+
+ " Try specified path as relative to the project main file. This is
+ " difficult, since the main file is the one we are looking for. We
+ " therefore assume that the main file lives somewhere upwards in the
+ " directory tree.
+ let l:candidate = fnamemodify(findfile(l:filename, '.;'), ':p')
+ if filereadable(l:candidate)
+ \ && s:file_reaches_current(l:candidate)
+ let s:subfile_preserve_root = 1
+ return fnamemodify(candidate, ':p')
+ endif
+
+ " Check the alternate buffer. This seems sensible e.g. in cases where one
+ " enters an "outer" subfile through a 'gf' motion from the main file.
+ let l:vimtex = getbufvar('#', 'vimtex', {})
+ for l:file in get(l:vimtex, 'sources', [])
+ if expand('%:p') ==# simplify(l:vimtex.root . '/' . l:file)
+ let s:subfile_preserve_root = 1
+ return l:vimtex.tex
+ endif
+ endfor
+ endif
+ endif
+ endfor
+
+ return ''
+endfunction
+
+" }}}1
+function! s:get_main_latexmain(file) abort " {{{1
+ for l:cand in s:findfiles_recursive('*.latexmain', expand('%:p:h'))
+ let l:cand = fnamemodify(l:cand, ':p:r')
+ if s:file_reaches_current(l:cand)
+ return l:cand
+ else
+ let s:cand_fallback = l:cand
+ endif
+ endfor
+
+ return ''
+endfunction
+
+function! s:get_main_latexmk() abort " {{{1
+ let l:root = expand('%:p:h')
+ let l:results = vimtex#compiler#latexmk#get_rc_opt(
+ \ l:root, 'default_files', 2, [])
+ if l:results[1] < 1 | return '' | endif
+
+ for l:candidate in l:results[0]
+ let l:file = l:root . '/' . l:candidate
+ if filereadable(l:file)
+ return l:file
+ endif
+ endfor
+
+ return ''
+endfunction
+
+function! s:get_main_recurse(...) abort " {{{1
+ " Either start the search from the original file, or check if the supplied
+ " file is a main file (or invalid)
+ if a:0 == 0
+ let l:file = expand('%:p')
+ let l:tried = {}
+ else
+ let l:file = a:1
+ let l:tried = a:2
+
+ if s:file_is_main(l:file)
+ return [l:file]
+ elseif !filereadable(l:file)
+ return []
+ endif
+ endif
+
+ " Create list of candidates that was already tried for the current file
+ if !has_key(l:tried, l:file)
+ let l:tried[l:file] = [l:file]
+ endif
+
+ " Apply filters successively (minor optimization)
+ let l:re_filter1 = fnamemodify(l:file, ':t:r')
+ let l:re_filter2 = g:vimtex#re#tex_input . '\s*\f*' . l:re_filter1
+
+ " Search through candidates found recursively upwards in the directory tree
+ let l:results = []
+ for l:cand in s:findfiles_recursive('*.tex', fnamemodify(l:file, ':p:h'))
+ if index(l:tried[l:file], l:cand) >= 0 | continue | endif
+ call add(l:tried[l:file], l:cand)
+
+ if len(filter(filter(readfile(l:cand),
+ \ 'v:val =~# l:re_filter1'),
+ \ 'v:val =~# l:re_filter2')) > 0
+ let l:results += s:get_main_recurse(fnamemodify(l:cand, ':p'), l:tried)
+ endif
+ endfor
+
+ return l:results
+endfunction
+
+" }}}1
+function! s:get_main_choose(list) abort " {{{1
+ let l:list = vimtex#util#uniq_unsorted(a:list)
+
+ if empty(l:list) | return '' | endif
+ if len(l:list) == 1 | return l:list[0] | endif
+
+ let l:all = map(copy(l:list), '[s:get_main_id(v:val), v:val]')
+ let l:new = map(filter(copy(l:all), 'v:val[0] < 0'), 'v:val[1]')
+ let l:existing = {}
+ for [l:key, l:val] in filter(copy(l:all), 'v:val[0] >= 0')
+ let l:existing[l:key] = l:val
+ endfor
+ let l:alternate_id = getbufvar('#', 'vimtex_id', -1)
+
+ if len(l:existing) == 1
+ return values(l:existing)[0]
+ elseif len(l:existing) > 1 && has_key(l:existing, l:alternate_id)
+ return l:existing[l:alternate_id]
+ elseif len(l:existing) < 1 && len(l:new) == 1
+ return l:new[0]
+ else
+ let l:choices = {}
+ for l:tex in l:list
+ let l:choices[l:tex] = vimtex#paths#relative(l:tex, getcwd())
+ endfor
+
+ return vimtex#echo#choose(l:choices,
+ \ 'Please select an appropriate main file:')
+ endif
+endfunction
+
+" }}}1
+function! s:file_is_main(file) abort " {{{1
+ if !filereadable(a:file) | return 0 | endif
+
+ "
+ " Check if a:file is a main file by looking for the \documentclass command,
+ " but ignore the following:
+ "
+ " \documentclass[...]{subfiles}
+ " \documentclass[...]{standalone}
+ "
+ let l:lines = readfile(a:file, 0, 50)
+ call filter(l:lines, 'v:val =~# ''\C\\documentclass\_\s*[\[{]''')
+ call filter(l:lines, 'v:val !~# ''{subfiles}''')
+ call filter(l:lines, 'v:val !~# ''{standalone}''')
+ if len(l:lines) == 0 | return 0 | endif
+
+ " A main file contains `\begin{document}`
+ let l:lines = vimtex#parser#preamble(a:file, {
+ \ 'inclusive' : 1,
+ \ 'root' : fnamemodify(a:file, ':p:h'),
+ \})
+ call filter(l:lines, 'v:val =~# ''\\begin\s*{document}''')
+ return len(l:lines) > 0
+endfunction
+
+" }}}1
+function! s:file_reaches_current(file) abort " {{{1
+ " Note: This function assumes that the input a:file is an absolute path
+ if !filereadable(a:file) | return 0 | endif
+
+ for l:line in filter(readfile(a:file), 'v:val =~# g:vimtex#re#tex_input')
+ let l:file = matchstr(l:line, g:vimtex#re#tex_input . '\zs\f+')
+ if empty(l:file) | continue | endif
+
+ if !vimtex#paths#is_abs(l:file)
+ let l:file = fnamemodify(a:file, ':h') . '/' . l:file
+ endif
+
+ if l:file !~# '\.tex$'
+ let l:file .= '.tex'
+ endif
+
+ if expand('%:p') ==# l:file
+ \ || s:file_reaches_current(l:file)
+ return 1
+ endif
+ endfor
+
+ return 0
+endfunction
+
+" }}}1
+function! s:findfiles_recursive(expr, path) abort " {{{1
+ let l:path = a:path
+ let l:dirs = l:path
+ while l:path != fnamemodify(l:path, ':h')
+ let l:path = fnamemodify(l:path, ':h')
+ let l:dirs .= ',' . l:path
+ endwhile
+ return split(globpath(fnameescape(l:dirs), a:expr), '\n')
+endfunction
+
+" }}}1
+
+let s:vimtex = {}
+
+function! s:vimtex.new(main, main_parser, preserve_root) abort dict " {{{1
+ let l:new = deepcopy(self)
+ let l:new.tex = a:main
+ let l:new.root = fnamemodify(l:new.tex, ':h')
+ let l:new.base = fnamemodify(l:new.tex, ':t')
+ let l:new.name = fnamemodify(l:new.tex, ':t:r')
+ let l:new.main_parser = a:main_parser
+
+ if a:preserve_root && exists('b:vimtex')
+ let l:new.root = b:vimtex.root
+ let l:new.base = vimtex#paths#relative(a:main, l:new.root)
+ endif
+
+ if exists('s:disabled_modules')
+ let l:new.disabled_modules = s:disabled_modules
+ endif
+
+ "
+ " The preamble content is used to parse for the engine directive, the
+ " documentclass and the package list; we store it as a temporary shared
+ " object variable
+ "
+ let l:new.preamble = vimtex#parser#preamble(l:new.tex, {
+ \ 'root' : l:new.root,
+ \})
+
+ call l:new.parse_tex_program()
+ call l:new.parse_documentclass()
+ call l:new.parse_graphicspath()
+ call l:new.gather_sources()
+
+ call vimtex#view#init_state(l:new)
+ call vimtex#compiler#init_state(l:new)
+ call vimtex#qf#init_state(l:new)
+ call vimtex#toc#init_state(l:new)
+ call vimtex#fold#init_state(l:new)
+
+ " Parsing packages might depend on the compiler setting for build_dir
+ call l:new.parse_packages()
+
+ unlet l:new.preamble
+ unlet l:new.new
+ return l:new
+endfunction
+
+" }}}1
+function! s:vimtex.cleanup() abort dict " {{{1
+ if exists('self.compiler.cleanup')
+ call self.compiler.cleanup()
+ endif
+
+ if exists('#User#VimtexEventQuit')
+ if exists('b:vimtex')
+ let b:vimtex_tmp = b:vimtex
+ endif
+ let b:vimtex = self
+ doautocmd <nomodeline> User VimtexEventQuit
+ if exists('b:vimtex_tmp')
+ let b:vimtex = b:vimtex_tmp
+ unlet b:vimtex_tmp
+ else
+ unlet b:vimtex
+ endif
+ endif
+
+ " Close quickfix window
+ silent! cclose
+endfunction
+
+" }}}1
+function! s:vimtex.parse_tex_program() abort dict " {{{1
+ let l:lines = copy(self.preamble[:20])
+ let l:tex_program_re =
+ \ '\v^\c\s*\%\s*\!?\s*tex\s+%(TS-)?program\s*\=\s*\zs.*\ze\s*$'
+ call map(l:lines, 'matchstr(v:val, l:tex_program_re)')
+ call filter(l:lines, '!empty(v:val)')
+ let self.tex_program = tolower(get(l:lines, -1, '_'))
+endfunction
+
+" }}}1
+function! s:vimtex.parse_documentclass() abort dict " {{{1
+ let self.documentclass = ''
+ for l:line in self.preamble
+ let l:class = matchstr(l:line, '^\s*\\documentclass.*{\zs\w*\ze}')
+ if !empty(l:class)
+ let self.documentclass = l:class
+ break
+ endif
+ endfor
+endfunction
+
+" }}}1
+function! s:vimtex.parse_graphicspath() abort dict " {{{1
+ " Combine the preamble as one long string of commands
+ let l:preamble = join(map(copy(self.preamble),
+ \ 'substitute(v:val, ''\\\@<!%.*'', '''', '''')'))
+
+ " Extract the graphicspath command from this string
+ let l:graphicspath = matchstr(l:preamble,
+ \ g:vimtex#re#not_bslash
+ \ . '\\graphicspath\s*\{\s*\{\s*\zs.{-}\ze\s*\}\s*\}'
+ \)
+
+ " Add all parsed graphicspaths
+ let self.graphicspath = []
+ for l:path in split(l:graphicspath, '\s*}\s*{\s*')
+ let l:path = substitute(l:path, '\/\s*$', '', '')
+ call add(self.graphicspath, vimtex#paths#is_abs(l:path)
+ \ ? l:path
+ \ : simplify(self.root . '/' . l:path))
+ endfor
+endfunction
+
+" }}}1
+function! s:vimtex.parse_packages() abort dict " {{{1
+ let self.packages = get(self, 'packages', {})
+
+ " Try to parse .fls file if present, as it is usually more complete. That is,
+ " it contains a generated list of all the packages that are used.
+ for l:line in vimtex#parser#fls(self.fls())
+ let l:package = matchstr(l:line, '^INPUT \zs.\+\ze\.sty$')
+ let l:package = fnamemodify(l:package, ':t')
+ if !empty(l:package)
+ let self.packages[l:package] = {}
+ endif
+ endfor
+
+ " Now parse preamble as well for usepackage and RequirePackage
+ if !has_key(self, 'preamble') | return | endif
+ let l:usepackages = filter(copy(self.preamble), 'v:val =~# ''\v%(usep|RequireP)ackage''')
+ let l:pat = g:vimtex#re#not_comment . g:vimtex#re#not_bslash
+ \ . '\v\\%(usep|RequireP)ackage\s*%(\[[^[\]]*\])?\s*\{\s*\zs%([^{}]+)\ze\s*\}'
+ call map(l:usepackages, 'matchstr(v:val, l:pat)')
+ call map(l:usepackages, 'split(v:val, ''\s*,\s*'')')
+
+ for l:packages in l:usepackages
+ for l:package in l:packages
+ let self.packages[l:package] = {}
+ endfor
+ endfor
+endfunction
+
+" }}}1
+function! s:vimtex.gather_sources() abort dict " {{{1
+ let self.sources = vimtex#parser#tex#parse_files(
+ \ self.tex, {'root' : self.root})
+
+ call map(self.sources, 'vimtex#paths#relative(v:val, self.root)')
+endfunction
+
+" }}}1
+function! s:vimtex.pprint_items() abort dict " {{{1
+ let l:items = [
+ \ ['name', self.name],
+ \ ['base', self.base],
+ \ ['root', self.root],
+ \ ['tex', self.tex],
+ \ ['out', self.out()],
+ \ ['log', self.log()],
+ \ ['aux', self.aux()],
+ \ ['fls', self.fls()],
+ \ ['main parser', self.main_parser],
+ \]
+
+ if self.tex_program !=# '_'
+ call add(l:items, ['tex program', self.tex_program])
+ endif
+
+ if len(self.sources) >= 2
+ call add(l:items, ['source files', self.sources])
+ endif
+
+ call add(l:items, ['compiler', get(self, 'compiler', {})])
+ call add(l:items, ['viewer', get(self, 'viewer', {})])
+ call add(l:items, ['qf', get(self, 'qf', {})])
+
+ if exists('self.documentclass')
+ call add(l:items, ['document class', self.documentclass])
+ endif
+
+ if !empty(self.packages)
+ call add(l:items, ['packages', sort(keys(self.packages))])
+ endif
+
+ return [['vimtex project', l:items]]
+endfunction
+
+" }}}1
+function! s:vimtex.log() abort dict " {{{1
+ return self.ext('log')
+endfunction
+
+" }}}1
+function! s:vimtex.aux() abort dict " {{{1
+ return self.ext('aux')
+endfunction
+
+" }}}1
+function! s:vimtex.fls() abort dict " {{{1
+ return self.ext('fls')
+endfunction
+
+" }}}1
+function! s:vimtex.out(...) abort dict " {{{1
+ return call(self.ext, ['pdf'] + a:000, self)
+endfunction
+
+" }}}1
+function! s:vimtex.ext(ext, ...) abort dict " {{{1
+ " First check build dir (latexmk -output_directory option)
+ if !empty(get(get(self, 'compiler', {}), 'build_dir', ''))
+ let cand = self.compiler.build_dir . '/' . self.name . '.' . a:ext
+ if !vimtex#paths#is_abs(self.compiler.build_dir)
+ let cand = self.root . '/' . cand
+ endif
+ if a:0 > 0 || filereadable(cand)
+ return fnamemodify(cand, ':p')
+ endif
+ endif
+
+ " Next check for file in project root folder
+ let cand = self.root . '/' . self.name . '.' . a:ext
+ if a:0 > 0 || filereadable(cand)
+ return fnamemodify(cand, ':p')
+ endif
+
+ " Finally return empty string if no entry is found
+ return ''
+endfunction
+
+" }}}1
+function! s:vimtex.getftime() abort dict " {{{1
+ return max(map(copy(self.sources), 'getftime(self.root . ''/'' . v:val)'))
+endfunction
+
+" }}}1
+
+
+" Initialize module
+let s:vimtex_states = {}
+let s:vimtex_next_id = 0
+
+endif
diff --git a/autoload/vimtex/syntax.vim b/autoload/vimtex/syntax.vim
new file mode 100644
index 00000000..428ccaaa
--- /dev/null
+++ b/autoload/vimtex/syntax.vim
@@ -0,0 +1,66 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#syntax#init() abort " {{{1
+ if !get(g:, 'vimtex_syntax_enabled', 1) | return | endif
+
+ " The following ensures that syntax addons are not loaded until after the
+ " filetype plugin has been sourced. See e.g. #1428 for more info.
+ if exists('b:vimtex')
+ call vimtex#syntax#load()
+ else
+ augroup vimtex_syntax
+ autocmd!
+ autocmd User VimtexEventInitPost call vimtex#syntax#load()
+ augroup END
+ endif
+endfunction
+
+" }}}1
+function! vimtex#syntax#load() abort " {{{1
+ if s:is_loaded() | return | endif
+
+ " Initialize project cache (used e.g. for the minted package)
+ if !has_key(b:vimtex, 'syntax')
+ let b:vimtex.syntax = {}
+ endif
+
+ " Initialize b:vimtex_syntax
+ let b:vimtex_syntax = {}
+
+ " Reset included syntaxes (necessary e.g. when doing :e)
+ call vimtex#syntax#misc#include_reset()
+
+ " Set some better defaults
+ syntax spell toplevel
+ syntax sync maxlines=500
+
+ " Load some general syntax improvements
+ call vimtex#syntax#load#general()
+
+ " Load syntax for documentclass and packages
+ call vimtex#syntax#load#packages()
+
+ " Hack to make it possible to determine if vimtex syntax was loaded
+ syntax match texVimtexLoaded 'dummyVimtexLoadedText' contained
+endfunction
+
+" }}}1
+
+function! s:is_loaded() abort " {{{1
+ if exists('*execute')
+ let l:result = split(execute('syntax'), "\n")
+ return !empty(filter(l:result, 'v:val =~# "texVimtexLoaded"'))
+ else
+ return 0
+ endif
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/syntax/load.vim b/autoload/vimtex/syntax/load.vim
new file mode 100644
index 00000000..d1b8b09b
--- /dev/null
+++ b/autoload/vimtex/syntax/load.vim
@@ -0,0 +1,82 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#syntax#load#general() abort " {{{1
+ " I don't see why we can't match Math zones in the MatchNMGroup
+ if !exists('g:tex_no_math')
+ syntax cluster texMatchNMGroup add=@texMathZones
+ endif
+
+ " Todo elements
+ syntax match texStatement '\\todo\w*' contains=texTodo
+ syntax match texTodo '\\todo\w*'
+
+ " Fix strange mistake in main syntax file where \usepackage is added to the
+ " texInputFile group
+ syntax match texDocType /\\usepackage\>/
+ \ nextgroup=texBeginEndName,texDocTypeArgs
+
+ " Improve support for italic font, bold font and some conceals
+ if get(g:, 'tex_fast', 'b') =~# 'b'
+ let s:conceal = (has('conceal') && get(g:, 'tex_conceal', 'b') =~# 'b')
+ \ ? 'concealends' : ''
+
+ for [s:style, s:group, s:commands] in [
+ \ ['texItalStyle', 'texItalGroup', ['emph', 'textit']],
+ \ ['texBoldStyle', 'texBoldGroup', ['textbf']],
+ \]
+ for s:cmd in s:commands
+ execute 'syntax region' s:style 'matchgroup=texTypeStyle'
+ \ 'start="\\' . s:cmd . '\s*{" end="}"'
+ \ 'contains=@Spell,@' . s:group
+ \ s:conceal
+ endfor
+ execute 'syntax cluster texMatchGroup add=' . s:style
+ endfor
+ endif
+
+ " Allow arguments in newenvironments
+ syntax region texEnvName contained matchgroup=Delimiter
+ \ start="{"rs=s+1 end="}"
+ \ nextgroup=texEnvBgn,texEnvArgs contained skipwhite skipnl
+ syntax region texEnvArgs contained matchgroup=Delimiter
+ \ start="\["rs=s+1 end="]"
+ \ nextgroup=texEnvBgn,texEnvArgs skipwhite skipnl
+ syntax cluster texEnvGroup add=texDefParm,texNewEnv,texComment
+
+ " Add support for \renewenvironment and \renewcommand
+ syntax match texNewEnv "\\renewenvironment\>"
+ \ nextgroup=texEnvName skipwhite skipnl
+ syntax match texNewCmd "\\renewcommand\>"
+ \ nextgroup=texCmdName skipwhite skipnl
+
+ " Match nested DefParms
+ syntax match texDefParmNested contained "##\+\d\+"
+ highlight def link texDefParmNested Identifier
+ syntax cluster texEnvGroup add=texDefParmNested
+ syntax cluster texCmdGroup add=texDefParmNested
+endfunction
+
+" }}}1
+function! vimtex#syntax#load#packages() abort " {{{1
+ try
+ call vimtex#syntax#p#{b:vimtex.documentclass}#load()
+ catch /E117:/
+ endtry
+
+ for l:pkg in map(keys(b:vimtex.packages), "substitute(v:val, '-', '_', 'g')")
+ try
+ call vimtex#syntax#p#{l:pkg}#load()
+ catch /E117:/
+ endtry
+ endfor
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/syntax/misc.vim b/autoload/vimtex/syntax/misc.vim
new file mode 100644
index 00000000..633a1d75
--- /dev/null
+++ b/autoload/vimtex/syntax/misc.vim
@@ -0,0 +1,92 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#syntax#misc#add_to_section_clusters(group) abort " {{{1
+ for l:cluster in [
+ \ 'texPartGroup',
+ \ 'texChapterGroup',
+ \ 'texSectionGroup',
+ \ 'texSubSectionGroup',
+ \ 'texSubSubSectionGroup',
+ \ 'texParaGroup',
+ \]
+ execute printf('syntax cluster %s add=%s', l:cluster, a:group)
+ endfor
+
+ execute printf('syntax cluster texVimtexGlobal add=%s', a:group)
+endfunction
+
+" }}}1
+function! vimtex#syntax#misc#include(name) abort " {{{1
+ let l:inc_name = 'vimtex_nested_' . a:name
+
+ if !has_key(s:included, l:inc_name)
+ let s:included[l:inc_name] = s:include(l:inc_name, a:name)
+ endif
+
+ return s:included[l:inc_name] ? l:inc_name : ''
+endfunction
+
+" }}}1
+function! vimtex#syntax#misc#include_reset() abort " {{{1
+ let s:included = {'vimtex_nested_tex': 0}
+endfunction
+
+let s:included = {'vimtex_nested_tex': 0}
+
+" }}}1
+function! vimtex#syntax#misc#new_math_zone(sfx, mathzone, starred) abort " {{{1
+ " This function is based on Charles E. Campbell's amsmath.vba file 2018-06-29
+
+ if get(g:, 'tex_fast', 'M') !~# 'M' | return | endif
+
+ let foldcmd = get(g:, 'tex_fold_enabled') ? ' fold' : ''
+
+ let grp = 'texMathZone' . a:sfx
+ execute 'syntax cluster texMathZones add=' . grp
+ execute 'syntax region ' . grp
+ \ . ' start=''\\begin\s*{\s*' . a:mathzone . '\s*}'''
+ \ . ' end=''\\end\s*{\s*' . a:mathzone . '\s*}'''
+ \ . foldcmd . ' keepend contains=@texMathZoneGroup'
+ execute 'highlight def link '.grp.' texMath'
+
+ if a:starred
+ let grp .= 'S'
+ execute 'syntax cluster texMathZones add=' . grp
+ execute 'syntax region ' . grp
+ \ . ' start=''\\begin\s*{\s*' . a:mathzone . '\*\s*}'''
+ \ . ' end=''\\end\s*{\s*' . a:mathzone . '\*\s*}'''
+ \ . foldcmd . ' keepend contains=@texMathZoneGroup'
+ execute 'highlight def link '.grp.' texMath'
+ endif
+
+ execute 'syntax match texBadMath ''\\end\s*{\s*' . a:mathzone . '\*\=\s*}'''
+endfunction
+
+" }}}1
+
+function! s:include(cluster, name) abort " {{{1
+ let l:name = get(g:vimtex_syntax_nested.aliases, a:name, a:name)
+ let l:path = 'syntax/' . l:name . '.vim'
+
+ if empty(globpath(&runtimepath, l:path)) | return 0 | endif
+
+ unlet b:current_syntax
+ execute 'syntax include @' . a:cluster l:path
+ let b:current_syntax = 'tex'
+
+ for l:ignored_group in get(g:vimtex_syntax_nested.ignored, l:name, [])
+ execute 'syntax cluster' a:cluster 'remove=' . l:ignored_group
+ endfor
+
+ return 1
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/syntax/p/amsmath.vim b/autoload/vimtex/syntax/p/amsmath.vim
new file mode 100644
index 00000000..d202d140
--- /dev/null
+++ b/autoload/vimtex/syntax/p/amsmath.vim
@@ -0,0 +1,47 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+scriptencoding utf-8
+
+function! vimtex#syntax#p#amsmath#load() abort " {{{1
+ if has_key(b:vimtex_syntax, 'amsmath') | return | endif
+ let b:vimtex_syntax.amsmath = 1
+
+ " Allow subequations (fixes #1019)
+ " - This should be temporary, as it seems subequations is erroneously part of
+ " texBadMath from Charles Campbell's syntax plugin.
+ syntax match texBeginEnd
+ \ "\(\\begin\>\|\\end\>\)\ze{subequations}"
+ \ nextgroup=texBeginEndName
+
+ call vimtex#syntax#misc#new_math_zone('AmsA', 'align', 1)
+ call vimtex#syntax#misc#new_math_zone('AmsB', 'alignat', 1)
+ call vimtex#syntax#misc#new_math_zone('AmsD', 'flalign', 1)
+ call vimtex#syntax#misc#new_math_zone('AmsC', 'gather', 1)
+ call vimtex#syntax#misc#new_math_zone('AmsD', 'multline', 1)
+ call vimtex#syntax#misc#new_math_zone('AmsE', 'xalignat', 1)
+ call vimtex#syntax#misc#new_math_zone('AmsF', 'xxalignat', 0)
+ call vimtex#syntax#misc#new_math_zone('AmsG', 'mathpar', 1)
+
+ " Amsmath [lr][vV]ert (Holger Mitschke)
+ if has('conceal') && &enc ==# 'utf-8' && get(g:, 'tex_conceal', 'd') =~# 'd'
+ for l:texmath in [
+ \ ['\\lvert', '|'] ,
+ \ ['\\rvert', '|'] ,
+ \ ['\\lVert', '‖'] ,
+ \ ['\\rVert', '‖'] ,
+ \ ]
+ execute "syntax match texMathDelim '\\\\[bB]igg\\=[lr]\\="
+ \ . l:texmath[0] . "' contained conceal cchar=" . l:texmath[1]
+ endfor
+ endif
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/syntax/p/array.vim b/autoload/vimtex/syntax/p/array.vim
new file mode 100644
index 00000000..bc45e79b
--- /dev/null
+++ b/autoload/vimtex/syntax/p/array.vim
@@ -0,0 +1,35 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#syntax#p#array#load() abort " {{{1
+ if has_key(b:vimtex_syntax, 'array') | return | endif
+ let b:vimtex_syntax.array = 1
+
+ call vimtex#syntax#p#tabularx#load()
+ if !get(g:, 'tex_fast', 'M') =~# 'M' | return | endif
+
+ "
+ " The following code changes inline math so as to support the column
+ " specifiers [0], e.g.
+ "
+ " \begin{tabular}{*{3}{>{$}c<{$}}}
+ "
+ " [0]: https://en.wikibooks.org/wiki/LaTeX/Tables#Column_specification_using_.3E.7B.5Ccmd.7D_and_.3C.7B.5Ccmd.7D
+ "
+
+ syntax clear texMathZoneX
+ if has('conceal') && &enc ==# 'utf-8' && get(g:, 'tex_conceal', 'd') =~# 'd'
+ syntax region texMathZoneX matchgroup=Delimiter start="\([<>]{\)\@<!\$" skip="\%(\\\\\)*\\\$" matchgroup=Delimiter end="\$" end="%stopzone\>" concealends contains=@texMathZoneGroup
+ else
+ syntax region texMathZoneX matchgroup=Delimiter start="\([<>]{\)\@<!\$" skip="\%(\\\\\)*\\\$" matchgroup=Delimiter end="\$" end="%stopzone\>" contains=@texMathZoneGroup
+ endif
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/syntax/p/asymptote.vim b/autoload/vimtex/syntax/p/asymptote.vim
new file mode 100644
index 00000000..137c3890
--- /dev/null
+++ b/autoload/vimtex/syntax/p/asymptote.vim
@@ -0,0 +1,34 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#syntax#p#asymptote#load() abort " {{{1
+ if has_key(b:vimtex_syntax, 'asymptote') | return | endif
+ let b:vimtex_syntax.asymptote = 1
+
+ call vimtex#syntax#misc#add_to_section_clusters('texZoneAsymptote')
+
+ if !empty(vimtex#syntax#misc#include('asy'))
+ syntax region texZoneAsymptote
+ \ start='\\begin{asy\z(def\)\?}'rs=s
+ \ end='\\end{asy\z1}'re=e
+ \ keepend
+ \ transparent
+ \ contains=texBeginEnd,@vimtex_nested_asy
+ else
+ syntax region texZoneAsymptote
+ \ start='\\begin{asy\z(def\)\?}'rs=s
+ \ end='\\end{asy\z1}'re=e
+ \ keepend
+ \ contains=texBeginEnd
+ highlight def link texZoneAsymptote texZone
+ endif
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/syntax/p/beamer.vim b/autoload/vimtex/syntax/p/beamer.vim
new file mode 100644
index 00000000..341c229c
--- /dev/null
+++ b/autoload/vimtex/syntax/p/beamer.vim
@@ -0,0 +1,32 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#syntax#p#beamer#load() abort " {{{1
+ if has_key(b:vimtex_syntax, 'beamer') | return | endif
+ let b:vimtex_syntax.beamer = 1
+
+ syntax match texBeamerDelimiter '<\|>' contained
+ syntax match texBeamerOpt '<[^>]*>' contained contains=texBeamerDelimiter
+
+ syntax match texStatementBeamer '\\only\(<[^>]*>\)\?' contains=texBeamerOpt
+ syntax match texStatementBeamer '\\item<[^>]*>' contains=texBeamerOpt
+
+ syntax match texInputFile
+ \ '\\includegraphics<[^>]*>\(\[.\{-}\]\)\=\s*{.\{-}}'
+ \ contains=texStatement,texBeamerOpt,texInputCurlies,texInputFileOpt
+
+ call vimtex#syntax#misc#add_to_section_clusters('texStatementBeamer')
+
+ highlight link texStatementBeamer texStatement
+ highlight link texBeamerOpt Identifier
+ highlight link texBeamerDelimiter Delimiter
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/syntax/p/biblatex.vim b/autoload/vimtex/syntax/p/biblatex.vim
new file mode 100644
index 00000000..1c620d6c
--- /dev/null
+++ b/autoload/vimtex/syntax/p/biblatex.vim
@@ -0,0 +1,84 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#syntax#p#biblatex#load() abort " {{{1
+ if has_key(b:vimtex_syntax, 'biblatex') | return | endif
+ let b:vimtex_syntax.biblatex = 1
+
+ if get(g:, 'tex_fast', 'r') !~# 'r' | return | endif
+
+ for l:pattern in [
+ \ 'bibentry',
+ \ 'cite[pt]?\*?',
+ \ 'citeal[tp]\*?',
+ \ 'cite(num|text|url)',
+ \ '[Cc]ite%(title|author|year(par)?|date)\*?',
+ \ '[Pp]arencite\*?',
+ \ 'foot%(full)?cite%(text)?',
+ \ 'fullcite',
+ \ '[Tt]extcite',
+ \ '[Ss]martcite',
+ \ 'supercite',
+ \ '[Aa]utocite\*?',
+ \ '[Ppf]?[Nn]otecite',
+ \ '%(text|block)cquote\*?',
+ \]
+ execute 'syntax match texStatement'
+ \ '/\v\\' . l:pattern . '\ze\s*%(\[|\{)/'
+ \ 'nextgroup=texRefOption,texCite'
+ endfor
+
+ for l:pattern in [
+ \ '[Cc]ites',
+ \ '[Pp]arencites',
+ \ 'footcite%(s|texts)',
+ \ '[Tt]extcites',
+ \ '[Ss]martcites',
+ \ 'supercites',
+ \ '[Aa]utocites',
+ \ '[pPfFsStTaA]?[Vv]olcites?',
+ \ 'cite%(field|list|name)',
+ \]
+ execute 'syntax match texStatement'
+ \ '/\v\\' . l:pattern . '\ze\s*%(\[|\{)/'
+ \ 'nextgroup=texRefOptions,texCites'
+ endfor
+
+ for l:pattern in [
+ \ '%(foreign|hyphen)textcquote\*?',
+ \ '%(foreign|hyphen)blockcquote',
+ \ 'hybridblockcquote',
+ \]
+ execute 'syntax match texStatement'
+ \ '/\v\\' . l:pattern . '\ze\s*%(\[|\{)/'
+ \ 'nextgroup=texQuoteLang'
+ endfor
+
+ syntax region texRefOptions contained matchgroup=Delimiter
+ \ start='\[' end=']'
+ \ contains=@texRefGroup,texRefZone
+ \ nextgroup=texRefOptions,texCites
+
+ syntax region texCites contained matchgroup=Delimiter
+ \ start='{' end='}'
+ \ contains=@texRefGroup,texRefZone,texCites
+ \ nextgroup=texRefOptions,texCites
+
+ syntax region texQuoteLang contained matchgroup=Delimiter
+ \ start='{' end='}'
+ \ transparent
+ \ contains=@texMatchGroup
+ \ nextgroup=texRefOption,texCite
+
+ highlight def link texRefOptions texRefOption
+ highlight def link texCites texCite
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/syntax/p/breqn.vim b/autoload/vimtex/syntax/p/breqn.vim
new file mode 100644
index 00000000..ab81db2f
--- /dev/null
+++ b/autoload/vimtex/syntax/p/breqn.vim
@@ -0,0 +1,23 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+scriptencoding utf-8
+
+function! vimtex#syntax#p#breqn#load() abort " {{{1
+ if has_key(b:vimtex_syntax, 'breqn') | return | endif
+ let b:vimtex_syntax.breqn = 1
+
+ call vimtex#syntax#misc#new_math_zone('BreqnA', 'dmath', 1)
+ call vimtex#syntax#misc#new_math_zone('BreqnB', 'dseries', 1)
+ call vimtex#syntax#misc#new_math_zone('BreqnC', 'dgroup', 1)
+ call vimtex#syntax#misc#new_math_zone('BreqnD', 'darray', 1)
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/syntax/p/cases.vim b/autoload/vimtex/syntax/p/cases.vim
new file mode 100644
index 00000000..383cd8a7
--- /dev/null
+++ b/autoload/vimtex/syntax/p/cases.vim
@@ -0,0 +1,20 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+scriptencoding utf-8
+
+function! vimtex#syntax#p#cases#load() abort " {{{1
+ if has_key(b:vimtex_syntax, 'cases') | return | endif
+ let b:vimtex_syntax.cases = 1
+
+ call VimtexNewMathZone('E', '\(sub\)\?numcases', 0)
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/syntax/p/cleveref.vim b/autoload/vimtex/syntax/p/cleveref.vim
new file mode 100644
index 00000000..1066e4ab
--- /dev/null
+++ b/autoload/vimtex/syntax/p/cleveref.vim
@@ -0,0 +1,44 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#syntax#p#cleveref#load() abort " {{{1
+ if has_key(b:vimtex_syntax, 'cleveref') | return | endif
+ let b:vimtex_syntax.cleveref = 1
+ if get(g:, 'tex_fast', 'r') !~# 'r' | return | endif
+
+ syntax match texStatement '\\\(\(label\)\?c\(page\)\?\|C\|auto\)ref\>'
+ \ nextgroup=texCRefZone
+
+ " \crefrange, \cpagerefrange (these commands expect two arguments)
+ syntax match texStatement '\\c\(page\)\?refrange\>'
+ \ nextgroup=texCRefZoneRange skipwhite skipnl
+
+ " \label[xxx]{asd}
+ syntax match texStatement '\\label\[.\{-}\]'
+ \ nextgroup=texCRefZone skipwhite skipnl
+ \ contains=texCRefLabelOpts
+
+ syntax region texCRefZone contained matchgroup=Delimiter
+ \ start="{" end="}"
+ \ contains=@texRefGroup,texRefZone
+ syntax region texCRefZoneRange contained matchgroup=Delimiter
+ \ start="{" end="}"
+ \ contains=@texRefGroup,texRefZone
+ \ nextgroup=texCRefZone skipwhite skipnl
+ syntax region texCRefLabelOpts contained matchgroup=Delimiter
+ \ start='\[' end=']'
+ \ contains=@texRefGroup,texRefZone
+
+ highlight link texCRefZone texRefZone
+ highlight link texCRefZoneRange texRefZone
+ highlight link texCRefLabelOpts texCmdArgs
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/syntax/p/csquotes.vim b/autoload/vimtex/syntax/p/csquotes.vim
new file mode 100644
index 00000000..1bcfb740
--- /dev/null
+++ b/autoload/vimtex/syntax/p/csquotes.vim
@@ -0,0 +1,18 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#syntax#p#csquotes#load() abort " {{{1
+ if has_key(b:vimtex_syntax, 'csquotes') | return | endif
+ let b:vimtex_syntax.csquotes = 1
+
+ call vimtex#syntax#p#biblatex#load()
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/syntax/p/dot2texi.vim b/autoload/vimtex/syntax/p/dot2texi.vim
new file mode 100644
index 00000000..881c2397
--- /dev/null
+++ b/autoload/vimtex/syntax/p/dot2texi.vim
@@ -0,0 +1,25 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#syntax#p#dot2texi#load() abort " {{{1
+ if has_key(b:vimtex_syntax, 'dot2texi') | return | endif
+ let b:vimtex_syntax.dot2texi = 1
+
+ call vimtex#syntax#misc#include('dot')
+ call vimtex#syntax#misc#add_to_section_clusters('texZoneDot')
+ syntax region texZoneDot
+ \ start="\\begin{dot2tex}"rs=s
+ \ end="\\end{dot2tex}"re=e
+ \ keepend
+ \ transparent
+ \ contains=texBeginEnd,@vimtex_nested_dot
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/syntax/p/glossaries.vim b/autoload/vimtex/syntax/p/glossaries.vim
new file mode 100644
index 00000000..259919cb
--- /dev/null
+++ b/autoload/vimtex/syntax/p/glossaries.vim
@@ -0,0 +1,20 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+scriptencoding utf-8
+
+function! vimtex#syntax#p#glossaries#load() abort " {{{1
+ if has_key(b:vimtex_syntax, 'glossaries') | return | endif
+ let b:vimtex_syntax.glossaries = 1
+
+ " Currently nothing here
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/syntax/p/glossaries_extra.vim b/autoload/vimtex/syntax/p/glossaries_extra.vim
new file mode 100644
index 00000000..4a6d29f2
--- /dev/null
+++ b/autoload/vimtex/syntax/p/glossaries_extra.vim
@@ -0,0 +1,21 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+scriptencoding utf-8
+
+function! vimtex#syntax#p#glossaries_extra#load() abort " {{{1
+ if has_key(b:vimtex_syntax, 'glossaries_extra') | return | endif
+ let b:vimtex_syntax.glossaries_extra = 1
+
+ " Load amsmath
+ call vimtex#syntax#p#glossaries#load()
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/syntax/p/gnuplottex.vim b/autoload/vimtex/syntax/p/gnuplottex.vim
new file mode 100644
index 00000000..7ecaee54
--- /dev/null
+++ b/autoload/vimtex/syntax/p/gnuplottex.vim
@@ -0,0 +1,25 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#syntax#p#gnuplottex#load() abort " {{{1
+ if has_key(b:vimtex_syntax, 'gnuplottex') | return | endif
+ let b:vimtex_syntax.gnuplottex = 1
+
+ call vimtex#syntax#misc#include('gnuplot')
+ call vimtex#syntax#misc#add_to_section_clusters('texZoneGnuplot')
+ syntax region texZoneGnuplot
+ \ start='\\begin{gnuplot}\(\_s*\[\_[\]]\{-}\]\)\?'rs=s
+ \ end='\\end{gnuplot}'re=e
+ \ keepend
+ \ transparent
+ \ contains=texBeginEnd,texBeginEndModifier,@vimtex_nested_gnuplot
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/syntax/p/hyperref.vim b/autoload/vimtex/syntax/p/hyperref.vim
new file mode 100644
index 00000000..2eb1cf19
--- /dev/null
+++ b/autoload/vimtex/syntax/p/hyperref.vim
@@ -0,0 +1,35 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#syntax#p#hyperref#load() abort " {{{1
+ if has_key(b:vimtex_syntax, 'hyperref') | return | endif
+ let b:vimtex_syntax.hyperref = 1
+
+ syntax match texStatement '\\url\ze[^\ta-zA-Z]' nextgroup=texUrlVerb
+ syntax region texUrlVerb matchgroup=Delimiter
+ \ start='\z([^\ta-zA-Z]\)' end='\z1' contained
+
+ syntax match texStatement '\\url\ze\s*{' nextgroup=texUrl
+ syntax region texUrl matchgroup=Delimiter start='{' end='}' contained
+
+ syntax match texStatement '\\href' nextgroup=texHref
+ syntax region texHref matchgroup=Delimiter start='{' end='}' contained
+ \ nextgroup=texMatcher
+
+ syntax match texStatement '\\hyperref' nextgroup=texHyperref
+ syntax region texHyperref matchgroup=Delimiter start='\[' end='\]' contained
+
+ highlight link texUrl Function
+ highlight link texUrlVerb texUrl
+ highlight link texHref texUrl
+ highlight link texHyperref texRefZone
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/syntax/p/listings.vim b/autoload/vimtex/syntax/p/listings.vim
new file mode 100644
index 00000000..81c7da24
--- /dev/null
+++ b/autoload/vimtex/syntax/p/listings.vim
@@ -0,0 +1,75 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#syntax#p#listings#load() abort " {{{1
+ if has_key(b:vimtex_syntax, 'listings') | return | endif
+ let b:vimtex_syntax.listings = s:get_nested_languages()
+
+ " First some general support
+ syntax match texInputFile
+ \ "\\lstinputlisting\s*\(\[.\{-}\]\)\={.\{-}}"
+ \ contains=texStatement,texInputCurlies,texInputFileOpt
+ syntax match texZone "\\lstinline\s*\(\[.\{-}\]\)\={.\{-}}"
+
+ " Set all listings environments to listings
+ syntax cluster texFoldGroup add=texZoneListings
+ syntax region texZoneListings
+ \ start="\\begin{lstlisting}\(\_s*\[\_[^\]]\{-}\]\)\?"rs=s
+ \ end="\\end{lstlisting}\|%stopzone\>"re=e
+ \ keepend
+ \ contains=texBeginEnd
+
+ " Next add nested syntax support for desired languages
+ for l:nested in b:vimtex_syntax.listings
+ let l:cluster = vimtex#syntax#misc#include(l:nested)
+ if empty(l:cluster) | continue | endif
+
+ let l:group_main = 'texZoneListings' . toupper(l:nested[0]) . l:nested[1:]
+ let l:group_lstset = l:group_main . 'Lstset'
+ let l:group_contained = l:group_main . 'Contained'
+ execute 'syntax cluster texFoldGroup add=' . l:group_main
+ execute 'syntax cluster texFoldGroup add=' . l:group_lstset
+
+ execute 'syntax region' l:group_main
+ \ 'start="\c\\begin{lstlisting}\s*'
+ \ . '\[\_[^\]]\{-}language=' . l:nested . '\%(\s*,\_[^\]]\{-}\)\?\]"rs=s'
+ \ 'end="\\end{lstlisting}"re=e'
+ \ 'keepend'
+ \ 'transparent'
+ \ 'contains=texBeginEnd,@' . l:cluster
+
+ execute 'syntax match' l:group_lstset
+ \ '"\c\\lstset{.*language=' . l:nested . '\%(\s*,\|}\)"'
+ \ 'transparent'
+ \ 'contains=texStatement,texMatcher'
+ \ 'skipwhite skipempty'
+ \ 'nextgroup=' . l:group_contained
+
+ execute 'syntax region' l:group_contained
+ \ 'start="\\begin{lstlisting}"rs=s'
+ \ 'end="\\end{lstlisting}"re=e'
+ \ 'keepend'
+ \ 'transparent'
+ \ 'containedin=' . l:group_lstset
+ \ 'contains=texStatement,texBeginEnd,@' . l:cluster
+ endfor
+
+ highlight link texZoneListings texZone
+endfunction
+
+" }}}1
+
+function! s:get_nested_languages() abort " {{{1
+ return map(
+ \ filter(getline(1, '$'), "v:val =~# 'language='"),
+ \ 'matchstr(v:val, ''language=\zs\w\+'')')
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/syntax/p/luacode.vim b/autoload/vimtex/syntax/p/luacode.vim
new file mode 100644
index 00000000..5e00c690
--- /dev/null
+++ b/autoload/vimtex/syntax/p/luacode.vim
@@ -0,0 +1,31 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#syntax#p#luacode#load() abort " {{{1
+ if has_key(b:vimtex_syntax, 'luacode') | return | endif
+ let b:vimtex_syntax.luacode = 1
+
+ call vimtex#syntax#misc#include('lua')
+ call vimtex#syntax#misc#add_to_section_clusters('texZoneLua')
+ syntax region texZoneLua
+ \ start='\\begin{luacode\*\?}'rs=s
+ \ end='\\end{luacode\*\?}'re=e
+ \ keepend
+ \ transparent
+ \ contains=texBeginEnd,@vimtex_nested_lua
+ syntax match texStatement '\\\(directlua\|luadirect\)' nextgroup=texZoneLuaArg
+ syntax region texZoneLuaArg matchgroup=Delimiter
+ \ start='{'
+ \ end='}'
+ \ contained
+ \ contains=@vimtex_nested_lua
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/syntax/p/markdown.vim b/autoload/vimtex/syntax/p/markdown.vim
new file mode 100644
index 00000000..6b7a6ad6
--- /dev/null
+++ b/autoload/vimtex/syntax/p/markdown.vim
@@ -0,0 +1,43 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#syntax#p#markdown#load() abort " {{{1
+ if has_key(b:vimtex_syntax, 'markdown') | return | endif
+ let b:vimtex_syntax.markdown = 1
+
+ call vimtex#syntax#misc#add_to_section_clusters('texZoneMarkdown')
+ call vimtex#syntax#misc#include('markdown')
+
+ " Don't quite know why this is necessary, but it is
+ syntax match texBeginEnd
+ \ '\(\\begin\>\|\\end\>\)\ze{markdown}'
+ \ nextgroup=texBeginEndName
+
+ syntax region texZoneMarkdown
+ \ start='\\begin{markdown}'rs=s
+ \ end='\\end{markdown}'re=e
+ \ keepend
+ \ transparent
+ \ contains=@texFoldGroup,@texDocGroup,@vimtex_nested_markdown
+
+ " Input files
+ syntax match texInputFile /\\markdownInput\>/
+ \ contains=texStatement
+ \ nextgroup=texInputFileArg
+ syntax region texInputFileArg
+ \ matchgroup=texInputCurlies
+ \ start="{" end="}"
+ \ contained
+ \ contains=texComment
+
+ highlight default link texInputFileArg texInputFile
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/syntax/p/mathtools.vim b/autoload/vimtex/syntax/p/mathtools.vim
new file mode 100644
index 00000000..24f7080a
--- /dev/null
+++ b/autoload/vimtex/syntax/p/mathtools.vim
@@ -0,0 +1,21 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+scriptencoding utf-8
+
+function! vimtex#syntax#p#mathtools#load() abort " {{{1
+ if has_key(b:vimtex_syntax, 'mathtools') | return | endif
+ let b:vimtex_syntax.mathtools = 1
+
+ " Load amsmath
+ call vimtex#syntax#p#amsmath#load()
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/syntax/p/minted.vim b/autoload/vimtex/syntax/p/minted.vim
new file mode 100644
index 00000000..02793c34
--- /dev/null
+++ b/autoload/vimtex/syntax/p/minted.vim
@@ -0,0 +1,256 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#syntax#p#minted#load() abort " {{{1
+ if has_key(b:vimtex_syntax, 'minted') | return | endif
+ let b:vimtex_syntax.minted = 1
+
+ " Parse minted macros in the current project
+ call s:parse_minted_constructs()
+
+ " Match minted language names
+ syntax region texMintedName matchgroup=Delimiter start="{" end="}" contained
+ syntax region texMintedNameOpt matchgroup=Delimiter start="\[" end="\]" contained
+
+ " Match boundaries of minted environments
+ syntax match texMintedBounds '\\end{minted}'
+ \ contained
+ \ contains=texBeginEnd
+ syntax match texMintedBounds '\\begin{minted}'
+ \ contained
+ \ contains=texBeginEnd
+ \ nextgroup=texMintedBoundsOpts,texMintedName
+ syntax region texMintedBoundsOpts matchgroup=Delimiter
+ \ start="\[" end="\]"
+ \ contained
+ \ nextgroup=texMintedName
+
+ " Match starred custom minted environments with options
+ syntax match texMintedStarred "\\begin{\w\+\*}"
+ \ contained
+ \ contains=texBeginEnd
+ \ nextgroup=texMintedStarredOpts
+ syntax region texMintedStarredOpts matchgroup=Delimiter
+ \ start='{'
+ \ end='}'
+ \ contained
+ \ containedin=texMintedStarred
+
+ " Match \newminted type macros
+ syntax match texStatement '\\newmint\%(ed\|inline\)\?' nextgroup=texMintedName,texMintedNameOpt
+
+ " Match "unknown" environments
+ call vimtex#syntax#misc#add_to_section_clusters('texZoneMinted')
+ syntax region texZoneMinted
+ \ start="\\begin{minted}\%(\_s*\[\_[^\]]\{-}\]\)\?\_s*{\w\+}"rs=s
+ \ end="\\end{minted}"re=e
+ \ keepend
+ \ contains=texMintedBounds.*
+
+ " Match "unknown" commands
+ syntax match texArgMinted "{\w\+}"
+ \ contained
+ \ contains=texMintedName
+ \ nextgroup=texZoneMintedCmd
+ syntax region texZoneMintedCmd matchgroup=Delimiter
+ \ start='\z([|+/]\)'
+ \ end='\z1'
+ \ contained
+ syntax region texZoneMintedCmd matchgroup=Delimiter
+ \ start='{'
+ \ end='}'
+ \ contained
+
+ " Next add nested syntax support for desired languages
+ for [l:nested, l:config] in items(b:vimtex.syntax.minted)
+ let l:cluster = vimtex#syntax#misc#include(l:nested)
+
+ let l:name = 'Minted' . toupper(l:nested[0]) . l:nested[1:]
+ let l:group_main = 'texZone' . l:name
+ let l:group_arg = 'texArg' . l:name
+ let l:group_arg_zone = 'texArgZone' . l:name
+ call vimtex#syntax#misc#add_to_section_clusters(l:group_main)
+
+ if empty(l:cluster)
+ let l:transparent = ''
+ let l:contains_env = ''
+ let l:contains_macro = ''
+ execute 'highlight link' l:group_main 'texZoneMinted'
+ execute 'highlight link' l:group_arg_zone 'texZoneMinted'
+ else
+ let l:transparent = 'transparent'
+ let l:contains_env = ',@' . l:cluster
+ let l:contains_macro = 'contains=@' . l:cluster
+ endif
+
+ " Match minted environment
+ execute 'syntax region' l:group_main
+ \ 'start="\\begin{minted}\%(\_s*\[\_[^\]]\{-}\]\)\?\_s*{' . l:nested . '}"rs=s'
+ \ 'end="\\end{minted}"re=e'
+ \ 'keepend'
+ \ l:transparent
+ \ 'contains=texMintedBounds.*' . l:contains_env
+
+ " Match custom environment names
+ for l:env in get(l:config, 'environments', [])
+ execute 'syntax region' l:group_main
+ \ 'start="\\begin{\z(' . l:env . '\*\?\)}"rs=s'
+ \ 'end="\\end{\z1}"re=e'
+ \ 'keepend'
+ \ l:transparent
+ \ 'contains=texMintedStarred,texBeginEnd' . l:contains_env
+ endfor
+
+ " Match minted macros
+ " - \mint[]{lang}|...|
+ " - \mint[]{lang}{...}
+ " - \mintinline[]{lang}|...|
+ " - \mintinline[]{lang}{...}
+ execute 'syntax match' l:group_arg '''{' . l:nested . '}'''
+ \ 'contained'
+ \ 'contains=texMintedName'
+ \ 'nextgroup=' . l:group_arg_zone
+ execute 'syntax region' l:group_arg_zone
+ \ 'matchgroup=Delimiter'
+ \ 'start=''\z([|+/]\)'''
+ \ 'end=''\z1'''
+ \ 'contained'
+ \ l:contains_macro
+ execute 'syntax region' l:group_arg_zone
+ \ 'matchgroup=Delimiter'
+ \ 'start=''{'''
+ \ 'end=''}'''
+ \ 'contained'
+ \ l:contains_macro
+
+ " Match minted custom macros
+ for l:cmd in sort(get(l:config, 'commands', []))
+ execute printf('syntax match texStatement ''\\%s'' nextgroup=%s',
+ \ l:cmd, l:group_arg_zone)
+ endfor
+ endfor
+
+ " Main matcher for the minted statements/commands
+ " - Note: This comes last to allow the nextgroup pattern
+ syntax match texStatement '\\mint\(inline\)\?' nextgroup=texArgOptMinted,texArgMinted.*
+ syntax region texArgOptMinted matchgroup=Delimiter
+ \ start='\['
+ \ end='\]'
+ \ contained
+ \ nextgroup=texArgMinted.*
+
+ highlight link texZoneMinted texZone
+ highlight link texZoneMintedCmd texZone
+ highlight link texMintedName texInputFileOpt
+ highlight link texMintedNameOpt texMintedName
+endfunction
+
+" }}}1
+
+function! s:parse_minted_constructs() abort " {{{1
+ if has_key(b:vimtex.syntax, 'minted') | return | endif
+
+ let l:db = deepcopy(s:db)
+ let b:vimtex.syntax.minted = l:db.data
+
+ let l:in_multi = 0
+ for l:line in vimtex#parser#tex(b:vimtex.tex, {'detailed': 0})
+ " Multiline minted environments
+ if l:in_multi
+ let l:lang = matchstr(l:line, '\]\s*{\zs\w\+\ze}')
+ if !empty(l:lang)
+ call l:db.register(l:lang)
+ let l:in_multi = 0
+ endif
+ continue
+ endif
+ if l:line =~# '\\begin{minted}\s*\[[^\]]*$'
+ let l:in_multi = 1
+ continue
+ endif
+
+ " Single line minted environments
+ let l:lang = matchstr(l:line, '\\begin{minted}\%(\s*\[\[^\]]*\]\)\?\s*{\zs\w\+\ze}')
+ if !empty(l:lang)
+ call l:db.register(l:lang)
+ continue
+ endif
+
+ " Simple minted commands
+ let l:lang = matchstr(l:line, '\\mint\%(\s*\[[^\]]*\]\)\?\s*{\zs\w\+\ze}')
+ if !empty(l:lang)
+ call l:db.register(l:lang)
+ continue
+ endif
+
+ " Custom environments:
+ " - \newminted{lang}{opts} -> langcode
+ " - \newminted[envname]{lang}{opts} -> envname
+ let l:matches = matchlist(l:line,
+ \ '\\newminted\%(\s*\[\([^\]]*\)\]\)\?\s*{\([a-zA-Z-]\+\)}')
+ if !empty(l:matches)
+ call l:db.register(l:matches[2])
+ call l:db.add_environment(!empty(l:matches[1])
+ \ ? l:matches[1]
+ \ : l:matches[2] . 'code')
+ continue
+ endif
+
+ " Custom macros:
+ " - \newmint(inline){lang}{opts} -> \lang(inline)
+ " - \newmint(inline)[macroname]{lang}{opts} -> \macroname
+ let l:matches = matchlist(l:line,
+ \ '\\newmint\(inline\)\?\%(\s*\[\([^\]]*\)\]\)\?\s*{\([a-zA-Z-]\+\)}')
+ if !empty(l:matches)
+ call l:db.register(l:matches[3])
+ call l:db.add_macro(!empty(l:matches[2])
+ \ ? l:matches[2]
+ \ : l:matches[3] . l:matches[1])
+ continue
+ endif
+ endfor
+endfunction
+
+" }}}1
+
+
+let s:db = {
+ \ 'data' : {},
+ \}
+
+function! s:db.register(lang) abort dict " {{{1
+ " Avoid dashes in langnames
+ let l:lang = substitute(a:lang, '-', '', 'g')
+
+ if !has_key(self.data, l:lang)
+ let self.data[l:lang] = {
+ \ 'environments' : [],
+ \ 'commands' : [],
+ \}
+ endif
+
+ let self.cur = self.data[l:lang]
+endfunction
+
+" }}}1
+function! s:db.add_environment(envname) abort dict " {{{1
+ if index(self.cur.environments, a:envname) < 0
+ let self.cur.environments += [a:envname]
+ endif
+endfunction
+
+" }}}1
+function! s:db.add_macro(macroname) abort dict " {{{1
+ if index(self.cur.commands, a:macroname) < 0
+ let self.cur.commands += [a:macroname]
+ endif
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/syntax/p/moreverb.vim b/autoload/vimtex/syntax/p/moreverb.vim
new file mode 100644
index 00000000..f6bb8f8c
--- /dev/null
+++ b/autoload/vimtex/syntax/p/moreverb.vim
@@ -0,0 +1,26 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#syntax#p#moreverb#load() abort " {{{1
+ if has_key(b:vimtex_syntax, 'moreverb') | return | endif
+ let b:vimtex_syntax.moreverb = 1
+
+ if exists('g:tex_verbspell')
+ syntax region texZone start="\\begin{verbatimtab}" end="\\end{verbatimtab}\|%stopzone\>" contains=@Spell
+ syntax region texZone start="\\begin{verbatimwrite}" end="\\end{verbatimwrite}\|%stopzone\>" contains=@Spell
+ syntax region texZone start="\\begin{boxedverbatim}" end="\\end{boxedverbatim}\|%stopzone\>" contains=@Spell
+ else
+ syntax region texZone start="\\begin{verbatimtab}" end="\\end{verbatimtab}\|%stopzone\>"
+ syntax region texZone start="\\begin{verbatimwrite}" end="\\end{verbatimwrite}\|%stopzone\>"
+ syntax region texZone start="\\begin{boxedverbatim}" end="\\end{boxedverbatim}\|%stopzone\>"
+ endif
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/syntax/p/natbib.vim b/autoload/vimtex/syntax/p/natbib.vim
new file mode 100644
index 00000000..f28e2a94
--- /dev/null
+++ b/autoload/vimtex/syntax/p/natbib.vim
@@ -0,0 +1,18 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#syntax#p#natbib#load() abort " {{{1
+ if has_key(b:vimtex_syntax, 'natbib') | return | endif
+ let b:vimtex_syntax.natbib = 1
+
+ call vimtex#syntax#p#biblatex#load()
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/syntax/p/pdfpages.vim b/autoload/vimtex/syntax/p/pdfpages.vim
new file mode 100644
index 00000000..ddd390d5
--- /dev/null
+++ b/autoload/vimtex/syntax/p/pdfpages.vim
@@ -0,0 +1,33 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#syntax#p#pdfpages#load() abort " {{{1
+ if has_key(b:vimtex_syntax, 'pdfpages') | return | endif
+ let b:vimtex_syntax.pdfpages = 1
+
+ syntax match texInputFile /\\includepdf\>/
+ \ contains=texStatement
+ \ nextgroup=texInputFileOpt,texInputFileArg
+ syntax region texInputFileOpt
+ \ matchgroup=Delimiter
+ \ start="\[" end="\]"
+ \ contained
+ \ contains=texComment,@NoSpell
+ \ nextgroup=texInputFileArg
+ syntax region texInputFileArg
+ \ matchgroup=texInputCurlies
+ \ start="{" end="}"
+ \ contained
+ \ contains=texComment
+
+ highlight default link texInputFileArg texInputFile
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/syntax/p/pgfplots.vim b/autoload/vimtex/syntax/p/pgfplots.vim
new file mode 100644
index 00000000..15b0fe4a
--- /dev/null
+++ b/autoload/vimtex/syntax/p/pgfplots.vim
@@ -0,0 +1,38 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#syntax#p#pgfplots#load() abort " {{{1
+ if has_key(b:vimtex_syntax, 'pgfplots') | return | endif
+ let b:vimtex_syntax.pgfplots = 1
+
+ " Load Tikz first
+ call vimtex#syntax#p#tikz#load()
+
+ " Add texAxisStatement to Tikz cluster
+ syntax cluster texTikz add=texAxisStatement
+
+ " Match pgfplotsset and axis environments
+ syntax match texTikzSet /\\pgfplotsset\>/
+ \ contains=texStatement skipwhite nextgroup=texTikzOptsCurly
+ syntax match texTikzEnv /\v\\begin\{%(log)*axis}/
+ \ contains=texBeginEnd nextgroup=texTikzOpts skipwhite
+ syntax match texTikzEnv /\v\\begin\{groupplot}/
+ \ contains=texBeginEnd nextgroup=texTikzOpts skipwhite
+
+ " Match some custom pgfplots macros
+ syntax match texAxisStatement /\\addplot3\>/
+ \ contained skipwhite nextgroup=texTikzOpts
+ syntax match texAxisStatement /\\nextgroupplot\>/
+ \ contained skipwhite nextgroup=texTikzOpts
+
+ highlight def link texAxisStatement texStatement
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/syntax/p/pythontex.vim b/autoload/vimtex/syntax/p/pythontex.vim
new file mode 100644
index 00000000..e58c3747
--- /dev/null
+++ b/autoload/vimtex/syntax/p/pythontex.vim
@@ -0,0 +1,40 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#syntax#p#pythontex#load() abort " {{{1
+ if has_key(b:vimtex_syntax, 'pythontex') | return | endif
+ let b:vimtex_syntax.pythontex = 1
+
+ call vimtex#syntax#misc#include('python')
+
+ syntax match texStatement /\\py[bsc]\?/ contained nextgroup=texPythontexArg
+ syntax region texPythontexArg matchgroup=Delimiter
+ \ start='{' end='}'
+ \ contained contains=@vimtex_nested_python
+ syntax region texPythontexArg matchgroup=Delimiter
+ \ start='\z([#@]\)' end='\z1'
+ \ contained contains=@vimtex_nested_python
+
+ call vimtex#syntax#misc#add_to_section_clusters('texZonePythontex')
+ syntax region texZonePythontex
+ \ start='\\begin{pyblock}'rs=s
+ \ end='\\end{pyblock}'re=e
+ \ keepend
+ \ transparent
+ \ contains=texBeginEnd,@vimtex_nested_python
+ syntax region texZonePythontex
+ \ start='\\begin{pycode}'rs=s
+ \ end='\\end{pycode}'re=e
+ \ keepend
+ \ transparent
+ \ contains=texBeginEnd,@vimtex_nested_python
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/syntax/p/subfile.vim b/autoload/vimtex/syntax/p/subfile.vim
new file mode 100644
index 00000000..9192f09e
--- /dev/null
+++ b/autoload/vimtex/syntax/p/subfile.vim
@@ -0,0 +1,19 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#syntax#p#subfile#load() abort " {{{1
+ if has_key(b:vimtex_syntax, 'subfile') | return | endif
+ let b:vimtex_syntax.subfile = 1
+
+ syntax match texInputFile /\\subfile\s*\%(\[.\{-}\]\)\=\s*{.\{-}}/
+ \ contains=texStatement,texInputCurlies,texInputFileOpt
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/syntax/p/tabularx.vim b/autoload/vimtex/syntax/p/tabularx.vim
new file mode 100644
index 00000000..0ff623c2
--- /dev/null
+++ b/autoload/vimtex/syntax/p/tabularx.vim
@@ -0,0 +1,77 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#syntax#p#tabularx#load() abort " {{{1
+ if has_key(b:vimtex_syntax, 'tabularx') | return | endif
+ let b:vimtex_syntax.tabularx = 1
+
+ call vimtex#syntax#misc#add_to_section_clusters('texTabular')
+
+ syntax match texTabular '\\begin{tabular}\_[^{]\{-}\ze{'
+ \ contains=texBeginEnd
+ \ nextgroup=texTabularArg
+ \ contained
+ syntax region texTabularArg matchgroup=Delimiter
+ \ start='{' end='}'
+ \ contained
+
+ syntax match texTabularCol /[lcr]/
+ \ containedin=texTabularArg
+ \ contained
+ syntax match texTabularCol /[pmb]/
+ \ containedin=texTabularArg
+ \ nextgroup=texTabularLength
+ \ contained
+ syntax match texTabularCol /\*/
+ \ containedin=texTabularArg
+ \ nextgroup=texTabularMulti
+ \ contained
+ syntax region texTabularMulti matchgroup=Delimiter
+ \ start='{' end='}'
+ \ containedin=texTabularArg
+ \ nextgroup=texTabularArg
+ \ contained
+
+ syntax match texTabularAtSep /@/
+ \ containedin=texTabularArg
+ \ nextgroup=texTabularLength
+ \ contained
+ syntax match texTabularVertline /||\?/
+ \ containedin=texTabularArg
+ \ contained
+ syntax match texTabularPostPre /[<>]/
+ \ containedin=texTabularArg
+ \ nextgroup=texTabularPostPreArg
+ \ contained
+
+ syntax region texTabularPostPreArg matchgroup=Delimiter
+ \ start='{' end='}'
+ \ containedin=texTabularArg
+ \ contains=texLength,texStatement,texMathDelimSingle
+ \ contained
+
+ syntax region texTabularLength matchgroup=Delimiter
+ \ start='{' end='}'
+ \ containedin=texTabularArg
+ \ contains=texLength,texStatement
+ \ contained
+
+ syntax match texMathDelimSingle /\$\$\?/
+ \ containedin=texTabularPostPreArg
+ \ contained
+
+ highlight def link texTabularCol Directory
+ highlight def link texTabularAtSep Type
+ highlight def link texTabularVertline Type
+ highlight def link texTabularPostPre Type
+ highlight def link texMathDelimSingle Delimiter
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/syntax/p/tikz.vim b/autoload/vimtex/syntax/p/tikz.vim
new file mode 100644
index 00000000..fe0d7089
--- /dev/null
+++ b/autoload/vimtex/syntax/p/tikz.vim
@@ -0,0 +1,47 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#syntax#p#tikz#load() abort " {{{1
+ if has_key(b:vimtex_syntax, 'tikz') | return | endif
+ let b:vimtex_syntax.tikz = 1
+
+ call vimtex#syntax#misc#add_to_section_clusters('texTikzSet')
+ call vimtex#syntax#misc#add_to_section_clusters('texTikzpicture')
+
+ " Define clusters
+ syntax cluster texTikz contains=texTikzEnv,texBeginEnd,texStatement,texTikzSemicolon,texComment,@texVimtexGlobal
+ syntax cluster texTikzOS contains=texTikzOptsCurly,texTikzEqual,texMathZoneX,texTypeSize,texStatement,texLength,texComment
+
+ " Define tikz option groups
+ syntax match texTikzSet /\\tikzset\>/
+ \ contains=texStatement skipwhite nextgroup=texTikzOptsCurly
+ syntax region texTikzOpts matchgroup=Delimiter
+ \ start='\[' end='\]' contained contains=@texTikzOS
+ syntax region texTikzOptsCurly matchgroup=Delimiter
+ \ start='{' end='}' contained contains=@texTikzOS
+
+ syntax region texTikzpicture
+ \ start='\\begin{tikzpicture}'rs=s
+ \ end='\\end{tikzpicture}'re=e
+ \ keepend
+ \ transparent
+ \ contains=@texTikz
+ syntax match texTikzEnv /\v\\begin\{tikzpicture\}/
+ \ contains=texBeginEnd nextgroup=texTikzOpts skipwhite
+
+ syntax match texTikzEqual /=/ contained
+ syntax match texTikzSemicolon /;/ contained
+
+ highlight def link texTikzEqual Operator
+ highlight def link texTikzSemicolon Delimiter
+endfunction
+
+" }}}1
+
+
+endif
diff --git a/autoload/vimtex/syntax/p/url.vim b/autoload/vimtex/syntax/p/url.vim
new file mode 100644
index 00000000..a944cae9
--- /dev/null
+++ b/autoload/vimtex/syntax/p/url.vim
@@ -0,0 +1,18 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#syntax#p#url#load() abort " {{{1
+ if has_key(b:vimtex_syntax, 'url') | return | endif
+ let b:vimtex_syntax.url = 1
+
+ call vimtex#syntax#p#hyperref#load()
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/syntax/p/varioref.vim b/autoload/vimtex/syntax/p/varioref.vim
new file mode 100644
index 00000000..020162d9
--- /dev/null
+++ b/autoload/vimtex/syntax/p/varioref.vim
@@ -0,0 +1,25 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#syntax#p#varioref#load() abort " {{{1
+ if has_key(b:vimtex_syntax, 'varioref') | return | endif
+ let b:vimtex_syntax.varioref = 1
+ if get(g:, 'tex_fast', 'r') !~# 'r' | return | endif
+
+ syntax match texStatement '\\Vref\>' nextgroup=texVarioRefZone
+
+ syntax region texVarioRefZone contained matchgroup=Delimiter
+ \ start="{" end="}"
+ \ contains=@texRefGroup,texRefZone
+
+ highlight link texVarioRefZone texRefZone
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/syntax/p/wiki.vim b/autoload/vimtex/syntax/p/wiki.vim
new file mode 100644
index 00000000..46edf7b7
--- /dev/null
+++ b/autoload/vimtex/syntax/p/wiki.vim
@@ -0,0 +1,26 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#syntax#p#wiki#load() abort " {{{1
+ if has_key(b:vimtex_syntax, 'wiki') | return | endif
+ let b:vimtex_syntax.wiki = 1
+
+ call vimtex#syntax#misc#add_to_section_clusters('texZoneWiki')
+ call vimtex#syntax#misc#include('markdown')
+
+ syntax region texZoneWiki
+ \ start='\\wikimarkup\>'
+ \ end='\\nowikimarkup\>'re=e
+ \ keepend
+ \ transparent
+ \ contains=@vimtex_nested_markdown,@texFoldGroup,@texDocGroup
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/test.vim b/autoload/vimtex/test.vim
new file mode 100644
index 00000000..9b9d50a2
--- /dev/null
+++ b/autoload/vimtex/test.vim
@@ -0,0 +1,98 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#test#assert(condition) abort " {{{1
+ if a:condition | return 1 | endif
+
+ call s:fail()
+endfunction
+
+" }}}1
+function! vimtex#test#assert_equal(expect, observe) abort " {{{1
+ if a:expect ==# a:observe | return 1 | endif
+
+ call s:fail([
+ \ 'expect: ' . string(a:expect),
+ \ 'observe: ' . string(a:observe),
+ \])
+endfunction
+
+" }}}1
+function! vimtex#test#assert_match(x, regex) abort " {{{1
+ if a:x =~# a:regex | return 1 | endif
+
+ call s:fail([
+ \ 'x = ' . string(a:x),
+ \ 'regex = ' . a:regex,
+ \])
+endfunction
+
+" }}}1
+
+function! vimtex#test#completion(context, ...) abort " {{{1
+ let l:base = a:0 > 0 ? a:1 : ''
+
+ try
+ silent execute 'normal GO' . a:context . "\<c-x>\<c-o>"
+ silent normal! u
+ return vimtex#complete#omnifunc(0, l:base)
+ catch /.*/
+ call s:fail(v:exception)
+ endtry
+endfunction
+
+" }}}1
+function! vimtex#test#keys(keys, context, expected) abort " {{{1
+ normal! gg0dG
+ call append(1, a:context)
+ normal! ggdd
+
+ let l:fail_msg = ['keys: ' . a:keys]
+ let l:fail_msg += ['context:']
+ let l:fail_msg += map(copy(a:context), '" " . v:val')
+ let l:fail_msg += ['expected:']
+ let l:fail_msg += map(copy(a:expected), '" " . v:val')
+
+ try
+ silent execute 'normal' a:keys
+ catch
+ let l:fail_msg += ['error:']
+ let l:fail_msg += [' ' . v:exception]
+ call s:fail(l:fail_msg)
+ endtry
+
+ let l:result = getline(1, line('$'))
+ if l:result ==# a:expected | return 1 | endif
+
+ let l:fail_msg += ['result:']
+ let l:fail_msg += map(l:result, '" " . v:val')
+ call s:fail(l:fail_msg)
+endfunction
+
+" }}}1
+
+function! s:fail(...) abort " {{{1
+ echo 'Assertion failed!'
+
+ if a:0 > 0 && !empty(a:1)
+ if type(a:1) == type('')
+ echo a:1
+ else
+ for line in a:1
+ echo line
+ endfor
+ endif
+ endif
+ echon "\n"
+
+ cquit
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/text_obj.vim b/autoload/vimtex/text_obj.vim
new file mode 100644
index 00000000..27d1a8b6
--- /dev/null
+++ b/autoload/vimtex/text_obj.vim
@@ -0,0 +1,447 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#text_obj#init_buffer() abort " {{{1
+ if !g:vimtex_text_obj_enabled | return | endif
+
+ " Note: I've permitted myself long lines here to make this more readable.
+ for [l:map, l:name, l:opt] in [
+ \ ['c', 'commands', ''],
+ \ ['d', 'delimited', 'delim_all'],
+ \ ['e', 'delimited', 'env_tex'],
+ \ ['$', 'delimited', 'env_math'],
+ \ ['P', 'sections', ''],
+ \ ['m', 'items', ''],
+ \]
+ let l:optional = empty(l:opt) ? '' : ',''' . l:opt . ''''
+ execute printf('xnoremap <silent><buffer> <plug>(vimtex-i%s) :<c-u>call vimtex#text_obj#%s(1, 1%s)<cr>', l:map, l:name, l:optional)
+ execute printf('xnoremap <silent><buffer> <plug>(vimtex-a%s) :<c-u>call vimtex#text_obj#%s(0, 1%s)<cr>', l:map, l:name, l:optional)
+ execute printf('onoremap <silent><buffer> <plug>(vimtex-i%s) :<c-u>call vimtex#text_obj#%s(1, 0%s)<cr>', l:map, l:name, l:optional)
+ execute printf('onoremap <silent><buffer> <plug>(vimtex-a%s) :<c-u>call vimtex#text_obj#%s(0, 0%s)<cr>', l:map, l:name, l:optional)
+ endfor
+endfunction
+
+" }}}1
+
+function! vimtex#text_obj#commands(is_inner, mode) abort " {{{1
+ let l:obj = {}
+ let l:pos_save = vimtex#pos#get_cursor()
+ if a:mode
+ call vimtex#pos#set_cursor(getpos("'>"))
+ endif
+
+ " Get the delimited text object positions
+ for l:count in range(v:count1)
+ if !empty(l:obj)
+ call vimtex#pos#set_cursor(vimtex#pos#prev(l:obj.cmd_start))
+ endif
+
+ let l:obj_prev = l:obj
+ let l:obj = {}
+
+ let l:cmd = vimtex#cmd#get_current()
+ if empty(l:cmd) | break | endif
+
+ let l:pos_start = copy(l:cmd.pos_start)
+ let l:pos_end = l:cmd.pos_end
+
+ if a:is_inner
+ let l:pos_end.lnum = l:pos_start.lnum
+ let l:pos_end.cnum = l:pos_start.cnum + strlen(l:cmd.name) - 1
+ let l:pos_start.cnum += 1
+ endif
+
+ if a:mode
+ \ && vimtex#pos#equal(l:pos_start, getpos("'<"))
+ \ && vimtex#pos#equal(l:pos_end, getpos("'>"))
+ let l:pos_old = l:cmd.pos_start
+ call vimtex#pos#set_cursor(vimtex#pos#prev(l:pos_old))
+
+ let l:cmd = vimtex#cmd#get_current()
+ if empty(l:cmd) | break | endif
+
+ if vimtex#pos#smaller(l:pos_old, l:cmd.pos_end)
+ let l:pos_start = l:cmd.pos_start
+ let l:pos_end = l:cmd.pos_end
+
+ if a:is_inner
+ let l:pos_end.lnum = l:pos_start.lnum
+ let l:pos_end.cnum = l:pos_start.cnum + strlen(l:cmd.name) - 1
+ let l:pos_start.cnum += 1
+ endif
+ endif
+ endif
+
+ let l:obj = {
+ \ 'pos_start' : l:pos_start,
+ \ 'pos_end' : l:pos_end,
+ \ 'cmd_start' : l:cmd.pos_start,
+ \}
+ endfor
+
+ if empty(l:obj)
+ if empty(l:obj_prev) || g:vimtex_text_obj_variant ==# 'targets'
+ if a:mode
+ normal! gv
+ else
+ call vimtex#pos#set_cursor(l:pos_save)
+ endif
+ return
+ endif
+ let l:obj = l:obj_prev
+ endif
+
+ call vimtex#pos#set_cursor(l:pos_start)
+ normal! v
+ call vimtex#pos#set_cursor(l:pos_end)
+endfunction
+
+" }}}1
+function! vimtex#text_obj#delimited(is_inner, mode, type) abort " {{{1
+ let l:object = {}
+ let l:prev_object = {}
+ let l:pos_save = vimtex#pos#get_cursor()
+ let l:startpos = getpos("'>")
+
+ " Get the delimited text object positions
+ for l:count in range(v:count1)
+ if !empty(l:object)
+ let l:pos_next = vimtex#pos#prev(
+ \ a:is_inner ? l:object.open : l:object.pos_start)
+
+ if a:mode
+ let l:startpos = l:pos_next
+ else
+ call vimtex#pos#set_cursor(l:pos_next)
+ endif
+ endif
+
+ if a:mode
+ let l:object = s:get_sel_delimited_visual(a:is_inner, a:type, l:startpos)
+ else
+ let [l:open, l:close] = vimtex#delim#get_surrounding(a:type)
+ let l:object = empty(l:open)
+ \ ? {} : s:get_sel_delimited(l:open, l:close, a:is_inner)
+ endif
+
+ if empty(l:object)
+ if !empty(l:prev_object) && g:vimtex_text_obj_variant !=# 'targets'
+ let l:object = l:prev_object
+ break
+ endif
+
+ if a:mode
+ normal! gv
+ else
+ call vimtex#pos#set_cursor(l:pos_save)
+ endif
+ return
+ endif
+
+ let l:prev_object = l:object
+ endfor
+
+ " Handle empty inner objects
+ if vimtex#pos#smaller(l:object.pos_end, l:object.pos_start)
+ if v:operator ==# 'y' && !a:mode
+ return
+ endif
+
+ if index(['c', 'd'], v:operator) >= 0
+ call vimtex#pos#set_cursor(l:object.pos_start)
+ normal! ix
+ endif
+
+ let l:object.pos_end = l:object.pos_start
+ endif
+
+ " Apply selection
+ execute 'normal!' l:object.select_mode
+ call vimtex#pos#set_cursor(l:object.pos_start)
+ normal! o
+ call vimtex#pos#set_cursor(l:object.pos_end)
+endfunction
+
+" }}}1
+function! vimtex#text_obj#sections(is_inner, mode) abort " {{{1
+ let l:pos_save = vimtex#pos#get_cursor()
+ call vimtex#pos#set_cursor(vimtex#pos#next(l:pos_save))
+
+ " Get section border positions
+ let [l:pos_start, l:pos_end, l:type]
+ \ = s:get_sel_sections(a:is_inner, '')
+ if empty(l:pos_start)
+ call vimtex#pos#set_cursor(l:pos_save)
+ return
+ endif
+
+ " Increase visual area if applicable
+ if a:mode
+ \ && visualmode() ==# 'V'
+ \ && getpos("'<")[1] == l:pos_start[0]
+ \ && getpos("'>")[1] == l:pos_end[0]
+ let [l:pos_start_new, l:pos_end_new, l:type]
+ \ = s:get_sel_sections(a:is_inner, l:type)
+ if !empty(l:pos_start_new)
+ let l:pos_start = l:pos_start_new
+ let l:pos_end = l:pos_end_new
+ endif
+ endif
+
+ " Repeat for count
+ for l:count in range(v:count1 - 1)
+ let [l:pos_start_new, l:pos_end_new, l:type]
+ \ = s:get_sel_sections(a:is_inner, l:type)
+
+ if empty(l:pos_start_new) | break | endif
+ let l:pos_start = l:pos_start_new
+ let l:pos_end = l:pos_end_new
+ endfor
+
+ " Apply selection
+ call vimtex#pos#set_cursor(l:pos_start)
+ normal! V
+ call vimtex#pos#set_cursor(l:pos_end)
+endfunction
+
+" }}}1
+function! vimtex#text_obj#items(is_inner, mode) abort " {{{1
+ let l:pos_save = vimtex#pos#get_cursor()
+
+ " Get section border positions
+ let [l:pos_start, l:pos_end] = s:get_sel_items(a:is_inner)
+ if empty(l:pos_start)
+ call vimtex#pos#set_cursor(l:pos_save)
+ return
+ endif
+
+ " Apply selection
+ execute 'normal!' (v:operator ==# ':') ? visualmode() : 'v'
+ call vimtex#pos#set_cursor(l:pos_start)
+ normal! o
+ call vimtex#pos#set_cursor(l:pos_end)
+endfunction
+
+" }}}1
+
+function! s:get_sel_delimited_visual(is_inner, type, startpos) abort " {{{1
+ if a:is_inner
+ call vimtex#pos#set_cursor(vimtex#pos#next(a:startpos))
+ let [l:open, l:close] = vimtex#delim#get_surrounding(a:type)
+ if !empty(l:open)
+ let l:object = s:get_sel_delimited(l:open, l:close, a:is_inner)
+
+ " Select next pair if we reached the same selection
+ if (l:object.select_mode ==# 'v'
+ \ && getpos("'<")[1:2] == l:object.pos_start
+ \ && getpos("'>")[1:2] == l:object.pos_end)
+ \ || (l:object.select_mode ==# 'V'
+ \ && getpos("'<")[1] == l:object.pos_start[0]
+ \ && getpos("'>")[1] == l:object.pos_end[0])
+ call vimtex#pos#set_cursor(vimtex#pos#prev(l:open.lnum, l:open.cnum))
+ let [l:open, l:close] = vimtex#delim#get_surrounding(a:type)
+ if empty(l:open) | return {} | endif
+ return s:get_sel_delimited(l:open, l:close, a:is_inner)
+ endif
+ endif
+ endif
+
+ call vimtex#pos#set_cursor(a:startpos)
+ let [l:open, l:close] = vimtex#delim#get_surrounding(a:type)
+ if empty(l:open) | return {} | endif
+ let l:object = s:get_sel_delimited(l:open, l:close, a:is_inner)
+ if a:is_inner | return l:object | endif
+
+ " Select next pair if we reached the same selection
+ if (l:object.select_mode ==# 'v'
+ \ && getpos("'<")[1:2] == l:object.pos_start
+ \ && getpos("'>")[1:2] == l:object.pos_end)
+ \ || (l:object.select_mode ==# 'V'
+ \ && getpos("'<")[1] == l:object.pos_start[0]
+ \ && getpos("'>")[1] == l:object.pos_end[0])
+ call vimtex#pos#set_cursor(vimtex#pos#prev(l:open.lnum, l:open.cnum))
+ let [l:open, l:close] = vimtex#delim#get_surrounding(a:type)
+ if empty(l:open) | return {} | endif
+ return s:get_sel_delimited(l:open, l:close, a:is_inner)
+ endif
+
+ return l:object
+endfunction
+
+" }}}1
+function! s:get_sel_delimited(open, close, is_inner) abort " {{{1
+ " Determine if operator is linewise
+ let l:linewise = index(g:vimtex_text_obj_linewise_operators, v:operator) >= 0
+
+ let [l1, c1, l2, c2] = [a:open.lnum, a:open.cnum, a:close.lnum, a:close.cnum]
+
+ " Adjust the borders
+ if a:is_inner
+ if has_key(a:open, 'env_cmd') && !empty(a:open.env_cmd)
+ let l1 = a:open.env_cmd.pos_end.lnum
+ let c1 = a:open.env_cmd.pos_end.cnum+1
+ else
+ let c1 += len(a:open.match)
+ endif
+ let c2 -= 1
+
+ let l:is_inline = (l2 - l1) > 1
+ \ && match(strpart(getline(l1), c1), '^\s*$') >= 0
+ \ && match(strpart(getline(l2), 0, c2), '^\s*$') >= 0
+
+ if l:is_inline
+ let l1 += 1
+ let c1 = strlen(matchstr(getline(l1), '^\s*')) + 1
+ let l2 -= 1
+ let c2 = strlen(getline(l2))
+ if c2 == 0 && !l:linewise
+ let l2 -= 1
+ let c2 = len(getline(l2)) + 1
+ endif
+ elseif c2 == 0
+ let l2 -= 1
+ let c2 = len(getline(l2)) + 1
+ endif
+ else
+ let c2 += len(a:close.match) - 1
+
+ let l:is_inline = (l2 - l1) > 1
+ \ && match(strpart(getline(l1), 0, c1-1), '^\s*$') >= 0
+ \ && match(strpart(getline(l2), 0, c2), '^\s*$') >= 0
+ endif
+
+ return {
+ \ 'open' : a:open,
+ \ 'close' : a:close,
+ \ 'pos_start' : [l1, c1],
+ \ 'pos_end' : [l2, c2],
+ \ 'is_inline' : l:is_inline,
+ \ 'select_mode' : l:is_inline && l:linewise
+ \ ? 'V' : (v:operator ==# ':') ? visualmode() : 'v',
+ \}
+endfunction
+
+" }}}1
+function! s:get_sel_sections(is_inner, type) abort " {{{1
+ let l:pos_save = vimtex#pos#get_cursor()
+ let l:min_val = get(s:section_to_val, a:type)
+
+ " Get the position of the section start
+ while 1
+ let l:pos_start = searchpos(s:section_search, 'bcnW')
+ if l:pos_start == [0, 0] | return [[], [], ''] | endif
+
+ let l:sec_type = matchstr(getline(l:pos_start[0]), s:section_search)
+ let l:sec_val = s:section_to_val[l:sec_type]
+
+ if !empty(a:type)
+ if l:sec_val >= l:min_val
+ call vimtex#pos#set_cursor(vimtex#pos#prev(l:pos_start))
+ else
+ call vimtex#pos#set_cursor(l:pos_save)
+ break
+ endif
+ else
+ break
+ endif
+ endwhile
+
+ " Get the position of the section end
+ while 1
+ let l:pos_end = searchpos(s:section_search, 'nW')
+ if l:pos_end == [0, 0]
+ let l:pos_end = [line('$')+1, 1]
+ break
+ endif
+
+ let l:cur_val = s:section_to_val[
+ \ matchstr(getline(l:pos_end[0]), s:section_search)]
+ if l:cur_val <= l:sec_val
+ let l:pos_end[0] -= 1
+ break
+ endif
+
+ call vimtex#pos#set_cursor(l:pos_end)
+ endwhile
+
+ " Adjust for inner text object
+ if a:is_inner
+ call vimtex#pos#set_cursor(l:pos_start[0]+1, l:pos_start[1])
+ let l:pos_start = searchpos('\S', 'cnW')
+ call vimtex#pos#set_cursor(l:pos_end)
+ let l:pos_end = searchpos('\S', 'bcnW')
+ elseif l:sec_val ==# 'document'
+ let l:pos_start = [l:pos_start[0]+1, l:pos_start[1]]
+ endif
+
+ return [l:pos_start, l:pos_end, l:sec_type]
+endfunction
+
+" }}}1
+function! s:get_sel_items(is_inner) abort " {{{1
+ let l:pos_cursor = vimtex#pos#get_cursor()
+
+ " Find previous \item
+ call vimtex#pos#set_cursor(l:pos_cursor[0], 1)
+ let l:pos_start = searchpos('^\s*\\item\S*', 'bcnWz')
+ if l:pos_start == [0, 0] | return [[], []] | endif
+
+ " Find end of current \item
+ call vimtex#pos#set_cursor(l:pos_start)
+ let l:pos_end = searchpos('\ze\n\s*\%(\\item\|\\end{itemize}\)', 'nW')
+ if l:pos_end == [0, 0]
+ \ || vimtex#pos#val(l:pos_cursor) > vimtex#pos#val(l:pos_end)
+ return [[], []]
+ endif
+
+ " Adjust for outer text object
+ if a:is_inner
+ let l:pos_start[1] = searchpos('^\s*\\item\S*\s\?', 'cne')[1] + 1
+ let l:pos_end[1] = col([l:pos_end[0], '$']) - 1
+ endif
+
+ return [l:pos_start, l:pos_end]
+endfunction
+
+" }}}1
+
+
+" {{{1 Initialize module
+
+" Pattern to match section/chapter/...
+let s:section_search = '\v%(%(\\@<!%(\\\\)*)@<=\%.*)@<!\s*\\\zs('
+ \ . join([
+ \ '%(sub)?paragraph>',
+ \ '%(sub)*section>',
+ \ 'chapter>',
+ \ 'part>',
+ \ 'appendix>',
+ \ '%(front|back|main)matter>',
+ \ '%(begin|end)\{\zsdocument\ze\}'
+ \ ], '|')
+ \ .')'
+
+" Dictionary to give values to sections in order to compare them
+let s:section_to_val = {
+ \ 'document': 0,
+ \ 'frontmatter': 1,
+ \ 'mainmatter': 1,
+ \ 'appendix': 1,
+ \ 'backmatter': 1,
+ \ 'part': 1,
+ \ 'chapter': 2,
+ \ 'section': 3,
+ \ 'subsection': 4,
+ \ 'subsubsection': 5,
+ \ 'paragraph': 6,
+ \ 'subparagraph': 7,
+ \}
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/text_obj/cmdtargets.vim b/autoload/vimtex/text_obj/cmdtargets.vim
new file mode 100644
index 00000000..611285c7
--- /dev/null
+++ b/autoload/vimtex/text_obj/cmdtargets.vim
@@ -0,0 +1,85 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#text_obj#cmdtargets#new(args) " {{{1
+ return {
+ \ 'genFuncs': {
+ \ 'c': function('vimtex#text_obj#cmdtargets#current'),
+ \ 'n': function('vimtex#text_obj#cmdtargets#next'),
+ \ 'l': function('vimtex#text_obj#cmdtargets#last'),
+ \ },
+ \ 'modFuncs': {
+ \ 'i': [function('vimtex#text_obj#cmdtargets#inner'),
+ \ function('targets#modify#drop')],
+ \ 'a': [function('targets#modify#keep')],
+ \ 'I': [function('vimtex#text_obj#cmdtargets#inner'),
+ \ function('targets#modify#shrink')],
+ \ 'A': [function('targets#modify#expand')],
+ \ }}
+endfunction
+
+" }}}1
+function! vimtex#text_obj#cmdtargets#current(args, opts, state) " {{{1
+ let target = s:select(a:opts.first ? 1 : 2)
+ call target.cursorE() " keep going from right end
+ return target
+endfunction
+
+" }}}1
+function! vimtex#text_obj#cmdtargets#next(args, opts, state) " {{{1
+ if targets#util#search('\\\S*{', 'W') > 0
+ return targets#target#withError('no target')
+ endif
+
+ let oldpos = getpos('.')
+ let target = s:select(1)
+ call setpos('.', oldpos)
+ return target
+endfunction
+
+" }}}1
+function! vimtex#text_obj#cmdtargets#last(args, opts, state) " {{{1
+ " Move to the last non-surrounding cmd
+ if targets#util#search('\\\S\+{\_.\{-}}', 'bWe') > 0
+ return targets#target#withError('no target')
+ endif
+
+ let oldpos = getpos('.')
+ let target = s:select(1)
+ call setpos('.', oldpos)
+ return target
+endfunction
+
+" }}}1
+function! vimtex#text_obj#cmdtargets#inner(target, args) " {{{1
+ if a:target.state().isInvalid()
+ return
+ endif
+
+ call a:target.cursorS()
+ silent! normal! f{
+ call a:target.setS()
+endfunction
+
+" }}}1
+
+function! s:select(count) " {{{1
+ " Try to select command
+ silent! execute 'keepjumps normal v'.a:count."\<Plug>(vimtex-ac)v"
+ let target = targets#target#fromVisualSelection()
+
+ if target.sc == target.ec && target.sl == target.el
+ return targets#target#withError('tex_cmd select')
+ endif
+
+ return target
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/text_obj/envtargets.vim b/autoload/vimtex/text_obj/envtargets.vim
new file mode 100644
index 00000000..10e3ae93
--- /dev/null
+++ b/autoload/vimtex/text_obj/envtargets.vim
@@ -0,0 +1,110 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#text_obj#envtargets#new(args) " {{{1
+ return {
+ \ 'genFuncs': {
+ \ 'c': function('vimtex#text_obj#envtargets#current'),
+ \ 'n': function('vimtex#text_obj#envtargets#next'),
+ \ 'l': function('vimtex#text_obj#envtargets#last'),
+ \ },
+ \ 'modFuncs': {
+ \ 'i': [function('vimtex#text_obj#envtargets#inner'), function('targets#modify#drop')],
+ \ 'a': [function('targets#modify#keep')],
+ \ 'I': [function('vimtex#text_obj#envtargets#inner'), function('targets#modify#shrink')],
+ \ 'A': [function('vimtex#text_obj#envtargets#expand')],
+ \ }}
+endfunction
+
+" }}}1
+function! vimtex#text_obj#envtargets#current(args, opts, state) " {{{1
+ let target = s:select(a:opts.first ? 1 : 2)
+ call target.cursorE() " keep going from right end
+ return target
+endfunction
+
+" }}}1
+function! vimtex#text_obj#envtargets#next(args, opts, state) " {{{1
+ if targets#util#search('\\begin{.*}', 'W') > 0
+ return targets#target#withError('no target')
+ endif
+
+ let oldpos = getpos('.')
+ let target = s:select(1)
+ call setpos('.', oldpos)
+ return target
+endfunction
+
+" }}}1
+function! vimtex#text_obj#envtargets#last(args, opts, state) " {{{1
+ if targets#util#search('\\end{.*}', 'bW') > 0
+ return targets#target#withError('no target')
+ endif
+
+ let oldpos = getpos('.')
+ let target = s:select(1)
+ call setpos('.', oldpos)
+ return target
+endfunction
+
+" }}}1
+function! vimtex#text_obj#envtargets#inner(target, args) " {{{1
+ call a:target.cursorS()
+ call a:target.searchposS('\\begin{.*}', 'Wce')
+ call a:target.cursorE()
+ call a:target.searchposE('\\end{.*}', 'bWc')
+endfunction
+
+" }}}1
+function! vimtex#text_obj#envtargets#expand(target, args) " {{{1
+ " Based on targets#modify#expand() from
+ " $VIMMRUNTIME/autoload/targets/modify.vim
+
+ " Add outer whitespace
+ if a:0 == 0 || a:1 ==# '>'
+ call a:target.cursorE()
+ let [line, column] = searchpos('\S\|$', '')
+ if line > a:target.el || (line > 0 && column-1 > a:target.ec)
+ " non whitespace or EOL after trailing whitespace found
+ " not counting whitespace directly after end
+ return a:target.setE(line, column-1)
+ endif
+ endif
+
+ if a:0 == 0 || a:1 ==# '<'
+ call a:target.cursorS()
+ let [line, column] = searchpos('\S', 'b')
+ if line < a:target.sl
+ return a:target.setS(line+1, 0)
+ elseif line > 0
+ " non whitespace before leading whitespace found
+ return a:target.setS(line, column+1)
+ endif
+ " only whitespace in front of start
+ " include all leading whitespace from beginning of line
+ let a:target.sc = 1
+ endif
+endfunction
+
+" }}}1
+
+function! s:select(count) " {{{1
+ " Try to select environment
+ silent! execute 'keepjumps normal v'.a:count."\<Plug>(vimtex-ae)v"
+ let target = targets#target#fromVisualSelection()
+
+ if target.sc == target.ec && target.sl == target.el
+ return targets#target#withError('tex_env select')
+ endif
+
+ return target
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/text_obj/targets.vim b/autoload/vimtex/text_obj/targets.vim
new file mode 100644
index 00000000..6bbdf710
--- /dev/null
+++ b/autoload/vimtex/text_obj/targets.vim
@@ -0,0 +1,49 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#text_obj#targets#enabled() abort " {{{1
+ return exists('g:loaded_targets')
+ \ && ( (type(g:loaded_targets) == type(0) && g:loaded_targets)
+ \ || (type(g:loaded_targets) == type('') && !empty(g:loaded_targets)))
+ \ && ( g:vimtex_text_obj_variant ==# 'auto'
+ \ || g:vimtex_text_obj_variant ==# 'targets')
+endfunction
+
+" }}}1
+function! vimtex#text_obj#targets#init() abort " {{{1
+ let g:vimtex_text_obj_variant = 'targets'
+
+ " Create intermediate mappings
+ omap <expr> <plug>(vimtex-targets-i) targets#e('o', 'i', 'i')
+ xmap <expr> <plug>(vimtex-targets-i) targets#e('x', 'i', 'i')
+ omap <expr> <plug>(vimtex-targets-a) targets#e('o', 'a', 'a')
+ xmap <expr> <plug>(vimtex-targets-a) targets#e('x', 'a', 'a')
+
+ augroup vimtex_targets
+ autocmd!
+ autocmd User targets#sources call s:init_sources()
+ autocmd User targets#mappings#plugin call s:init_mappings()
+ augroup END
+endfunction
+
+" }}}1
+
+function! s:init_mappings() abort " {{{1
+ call targets#mappings#extend({'e': {'tex_env': [{}]}})
+ call targets#mappings#extend({'c': {'tex_cmd': [{}]}})
+endfunction
+
+" }}}1
+function! s:init_sources() abort " {{{1
+ call targets#sources#register('tex_env', function('vimtex#text_obj#envtargets#new'))
+ call targets#sources#register('tex_cmd', function('vimtex#text_obj#cmdtargets#new'))
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/toc.vim b/autoload/vimtex/toc.vim
new file mode 100644
index 00000000..ac660112
--- /dev/null
+++ b/autoload/vimtex/toc.vim
@@ -0,0 +1,801 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#toc#init_buffer() abort " {{{1
+ if !g:vimtex_toc_enabled | return | endif
+
+ command! -buffer VimtexTocOpen call b:vimtex.toc.open()
+ command! -buffer VimtexTocToggle call b:vimtex.toc.toggle()
+
+ nnoremap <buffer> <plug>(vimtex-toc-open) :call b:vimtex.toc.open()<cr>
+ nnoremap <buffer> <plug>(vimtex-toc-toggle) :call b:vimtex.toc.toggle()<cr>
+endfunction
+
+" }}}1
+function! vimtex#toc#init_state(state) abort " {{{1
+ if !g:vimtex_toc_enabled | return | endif
+
+ let a:state.toc = vimtex#toc#new()
+endfunction
+
+" }}}1
+
+function! vimtex#toc#new(...) abort " {{{1
+ return extend(
+ \ deepcopy(s:toc),
+ \ vimtex#util#extend_recursive(
+ \ deepcopy(g:vimtex_toc_config),
+ \ a:0 > 0 ? a:1 : {}))
+endfunction
+
+" }}}1
+function! vimtex#toc#get_entries() abort " {{{1
+ if !has_key(b:vimtex, 'toc') | return [] | endif
+
+ return b:vimtex.toc.get_entries(0)
+endfunction
+
+" }}}1
+function! vimtex#toc#refresh() abort " {{{1
+ if has_key(b:vimtex, 'toc')
+ call b:vimtex.toc.get_entries(1)
+ endif
+endfunction
+
+" }}}1
+
+let s:toc = {}
+
+"
+" Open and close TOC window
+"
+function! s:toc.open() abort dict " {{{1
+ if self.is_open() | return | endif
+
+ if has_key(self, 'layers')
+ for l:key in keys(self.layer_status)
+ let self.layer_status[l:key] = index(self.layers, l:key) >= 0
+ endfor
+ endif
+
+ let self.calling_file = expand('%:p')
+ let self.calling_line = line('.')
+
+ call self.get_entries(0)
+
+ if self.mode > 1
+ call setloclist(0, map(filter(deepcopy(self.entries), 'v:val.active'), '{
+ \ ''lnum'': v:val.line,
+ \ ''filename'': v:val.file,
+ \ ''text'': v:val.title,
+ \}'))
+ try
+ call setloclist(0, [], 'r', {'title': self.name})
+ catch
+ endtry
+ if self.mode == 4 | lopen | endif
+ endif
+
+ if self.mode < 3
+ call self.create()
+ endif
+endfunction
+
+" }}}1
+function! s:toc.is_open() abort dict " {{{1
+ return bufwinnr(bufnr(self.name)) >= 0
+endfunction
+
+" }}}1
+function! s:toc.toggle() abort dict " {{{1
+ if self.is_open()
+ call self.close()
+ else
+ call self.open()
+ if has_key(self, 'prev_winid')
+ call win_gotoid(self.prev_winid)
+ endif
+ endif
+endfunction
+
+" }}}1
+function! s:toc.close() abort dict " {{{1
+ let self.fold_level = &l:foldlevel
+
+ if self.resize
+ silent exe 'set columns -=' . self.split_width
+ endif
+
+ if self.split_pos ==# 'full'
+ silent execute 'buffer' self.prev_bufnr
+ else
+ silent execute 'bwipeout' bufnr(self.name)
+ endif
+endfunction
+
+" }}}1
+function! s:toc.goto() abort dict " {{{1
+ if self.is_open()
+ let l:prev_winid = win_getid()
+ silent execute bufwinnr(bufnr(self.name)) . 'wincmd w'
+ let b:toc.prev_winid = l:prev_winid
+ endif
+endfunction
+
+" }}}1
+
+"
+" Get the TOC entries
+"
+function! s:toc.get_entries(force) abort dict " {{{1
+ if has_key(self, 'entries') && !self.refresh_always && !a:force
+ return self.entries
+ endif
+
+ let self.entries = vimtex#parser#toc()
+ let self.topmatters = vimtex#parser#toc#get_topmatters()
+
+ "
+ " Sort todo entries
+ "
+ if self.todo_sorted
+ let l:todos = filter(copy(self.entries), 'v:val.type ==# ''todo''')
+ for l:t in l:todos[1:]
+ let l:t.level = 1
+ endfor
+ call filter(self.entries, 'v:val.type !=# ''todo''')
+ let self.entries = l:todos + self.entries
+ endif
+
+ "
+ " Add hotkeys to entries
+ "
+ if self.hotkeys_enabled
+ let k = strwidth(self.hotkeys)
+ let n = len(self.entries)
+ let m = len(s:base(n, k))
+ let i = 0
+ for entry in self.entries
+ let keys = map(s:base(i, k), 'strcharpart(self.hotkeys, v:val, 1)')
+ let keys = repeat([self.hotkeys[0]], m - len(keys)) + keys
+ let i+=1
+ let entry.num = i
+ let entry.hotkey = join(keys, '')
+ endfor
+ endif
+
+ "
+ " Apply active layers
+ "
+ for entry in self.entries
+ let entry.active = self.layer_status[entry.type]
+ endfor
+
+ "
+ " Refresh if wanted
+ "
+ if a:force && self.is_open()
+ call self.refresh()
+ endif
+
+ return self.entries
+endfunction
+
+" }}}1
+function! s:toc.get_visible_entries() abort dict " {{{1
+ return filter(deepcopy(get(self, 'entries', [])), 'self.entry_is_visible(v:val)')
+endfunction
+
+" }}}1
+function! s:toc.entry_is_visible(entry) abort " {{{1
+ return get(a:entry, 'active', 1) && !get(a:entry, 'hidden')
+ \ && (a:entry.type !=# 'content' || a:entry.level <= self.tocdepth)
+endfunction
+
+" }}}1
+
+"
+" Creating, refreshing and filling the buffer
+"
+function! s:toc.create() abort dict " {{{1
+ let l:bufnr = bufnr('')
+ let l:winid = win_getid()
+ let l:vimtex = get(b:, 'vimtex', {})
+ let l:vimtex_syntax = get(b:, 'vimtex_syntax', {})
+
+ if self.split_pos ==# 'full'
+ silent execute 'edit' escape(self.name, ' ')
+ else
+ if self.resize
+ silent exe 'set columns +=' . self.split_width
+ endif
+ silent execute
+ \ self.split_pos self.split_width
+ \ 'new' escape(self.name, ' ')
+ endif
+
+ let self.prev_bufnr = l:bufnr
+ let self.prev_winid = l:winid
+ let b:toc = self
+ let b:vimtex = l:vimtex
+ let b:vimtex_syntax = l:vimtex_syntax
+
+ setlocal bufhidden=wipe
+ setlocal buftype=nofile
+ setlocal concealcursor=nvic
+ setlocal conceallevel=2
+ setlocal cursorline
+ setlocal nobuflisted
+ setlocal nolist
+ setlocal nospell
+ setlocal noswapfile
+ setlocal nowrap
+ setlocal tabstop=8
+
+ if self.hide_line_numbers
+ setlocal nonumber
+ setlocal norelativenumber
+ endif
+
+ call self.refresh()
+ call self.set_syntax()
+
+ if self.fold_enable
+ let self.foldexpr = function('s:foldexpr')
+ let self.foldtext = function('s:foldtext')
+ setlocal foldmethod=expr
+ setlocal foldexpr=b:toc.foldexpr(v:lnum)
+ setlocal foldtext=b:toc.foldtext()
+ let &l:foldlevel = get(self, 'fold_level',
+ \ (self.fold_level_start > 0
+ \ ? self.fold_level_start
+ \ : self.tocdepth))
+ endif
+
+ nnoremap <silent><nowait><buffer><expr> gg b:toc.show_help ? 'gg}}j' : 'gg'
+ nnoremap <silent><nowait><buffer> <esc>OA k
+ nnoremap <silent><nowait><buffer> <esc>OB j
+ nnoremap <silent><nowait><buffer> <esc>OC k
+ nnoremap <silent><nowait><buffer> <esc>OD j
+ nnoremap <silent><nowait><buffer> q :call b:toc.close()<cr>
+ nnoremap <silent><nowait><buffer> <esc> :call b:toc.close()<cr>
+ nnoremap <silent><nowait><buffer> <space> :call b:toc.activate_current(0)<cr>
+ nnoremap <silent><nowait><buffer> <2-leftmouse> :call b:toc.activate_current(0)<cr>
+ nnoremap <silent><nowait><buffer> <cr> :call b:toc.activate_current(1)<cr>
+ nnoremap <buffer><nowait><silent> h :call b:toc.toggle_help()<cr>
+ nnoremap <buffer><nowait><silent> f :call b:toc.filter()<cr>
+ nnoremap <buffer><nowait><silent> F :call b:toc.clear_filter()<cr>
+ nnoremap <buffer><nowait><silent> s :call b:toc.toggle_numbers()<cr>
+ nnoremap <buffer><nowait><silent> t :call b:toc.toggle_sorted_todos()<cr>
+ nnoremap <buffer><nowait><silent> r :call b:toc.get_entries(1)<cr>
+ nnoremap <buffer><nowait><silent> - :call b:toc.decrease_depth()<cr>
+ nnoremap <buffer><nowait><silent> + :call b:toc.increase_depth()<cr>
+
+ for [type, key] in items(self.layer_keys)
+ execute printf(
+ \ 'nnoremap <buffer><nowait><silent> %s'
+ \ . ' :call b:toc.toggle_type(''%s'')<cr>',
+ \ key, type)
+ endfor
+
+ if self.hotkeys_enabled
+ for entry in self.entries
+ execute printf(
+ \ 'nnoremap <buffer><nowait><silent> %s%s'
+ \ . ' :call b:toc.activate_hotkey(''%s'')<cr>',
+ \ self.hotkeys_leader, entry.hotkey, entry.hotkey)
+ endfor
+ endif
+
+ " Jump to closest index
+ call vimtex#pos#set_cursor(self.get_closest_index())
+
+ if exists('#User#VimtexEventTocCreated')
+ doautocmd <nomodeline> User VimtexEventTocCreated
+ endif
+endfunction
+
+" }}}1
+function! s:toc.refresh() abort dict " {{{1
+ let l:toc_winnr = bufwinnr(bufnr(self.name))
+ let l:buf_winnr = bufwinnr(bufnr(''))
+
+ if l:toc_winnr < 0
+ return
+ elseif l:buf_winnr != l:toc_winnr
+ silent execute l:toc_winnr . 'wincmd w'
+ endif
+
+ call self.position_save()
+ setlocal modifiable
+ silent %delete _
+
+ call self.print_help()
+ call self.print_entries()
+
+ 0delete _
+ setlocal nomodifiable
+ call self.position_restore()
+
+ if l:buf_winnr != l:toc_winnr
+ silent execute l:buf_winnr . 'wincmd w'
+ endif
+endfunction
+
+" }}}1
+function! s:toc.set_syntax() abort dict "{{{1
+ syntax clear
+
+ if self.show_help
+ execute 'syntax match VimtexTocHelp'
+ \ '/^\%<' . self.help_nlines . 'l.*/'
+ \ 'contains=VimtexTocHelpKey,VimtexTocHelpLayerOn,VimtexTocHelpLayerOff'
+
+ syntax match VimtexTocHelpKey /<\S*>/ contained
+ syntax match VimtexTocHelpKey /^\s*[-+<>a-zA-Z\/]\+\ze\s/ contained
+ \ contains=VimtexTocHelpKeySeparator
+ syntax match VimtexTocHelpKey /^Layers:\s*\zs[-+<>a-zA-Z\/]\+/ contained
+ syntax match VimtexTocHelpKeySeparator /\// contained
+
+ syntax match VimtexTocHelpLayerOn /\w\++/ contained
+ \ contains=VimtexTocHelpConceal
+ syntax match VimtexTocHelpLayerOff /(hidden)/ contained
+ syntax match VimtexTocHelpLayerOff /\w\+-/ contained
+ \ contains=VimtexTocHelpConceal
+ syntax match VimtexTocHelpConceal /[+-]/ contained conceal
+
+ highlight link VimtexTocHelpKeySeparator VimtexTocHelp
+ endif
+
+ syntax match VimtexTocNum /\v(([A-Z]+>|\d+)(\.\d+)*)?\s*/ contained
+ execute 'syntax match VimtexTocTodo'
+ \ '/\v\s\zs%(' . toupper(join(g:vimtex_toc_todo_keywords, '|')) . '): /'
+ \ 'contained'
+ syntax match VimtexTocHotkey /\[[^]]\+\]/ contained
+
+ syntax match VimtexTocSec0 /^L0.*/ contains=@VimtexTocStuff
+ syntax match VimtexTocSec1 /^L1.*/ contains=@VimtexTocStuff
+ syntax match VimtexTocSec2 /^L2.*/ contains=@VimtexTocStuff
+ syntax match VimtexTocSec3 /^L3.*/ contains=@VimtexTocStuff
+ syntax match VimtexTocSec4 /^L[4-9].*/ contains=@VimtexTocStuff
+ syntax match VimtexTocSecLabel /^L\d / contained conceal
+ \ nextgroup=VimtexTocNum
+ syntax cluster VimtexTocStuff
+ \ contains=VimtexTocSecLabel,VimtexTocHotkey,VimtexTocTodo,@Tex
+
+ syntax match VimtexTocIncl /\v^L\d (\[i\])?\s*(\[\w+\] )?\w+ incl:/
+ \ contains=VimtexTocSecLabel,VimtexTocHotkey
+ \ nextgroup=VimtexTocInclPath
+ syntax match VimtexTocInclPath /.*/ contained
+
+ syntax match VimtexTocLabelsSecs /\v^L\d \s*(\[\w+\] )?(chap|sec):.*$/
+ \ contains=VimtexTocSecLabel,VimtexTocHotkey
+ syntax match VimtexTocLabelsEq /\v^L\d \s*(\[\w+\] )?eq:.*$/
+ \ contains=VimtexTocSecLabel,VimtexTocHotkey
+ syntax match VimtexTocLabelsFig /\v^L\d \s*(\[\w+\] )?fig:.*$/
+ \ contains=VimtexTocSecLabel,VimtexTocHotkey
+ syntax match VimtexTocLabelsTab /\v^L\d \s*(\[\w+\] )?tab:.*$/
+ \ contains=VimtexTocSecLabel,VimtexTocHotkey
+endfunction
+
+" }}}1
+
+"
+" Print the TOC entries
+"
+function! s:toc.print_help() abort dict " {{{1
+ let self.help_nlines = 0
+ if !self.show_help | return | endif
+
+ let help_text = [
+ \ '<Esc>/q Close',
+ \ '<Space> Jump',
+ \ '<Enter> Jump and close',
+ \ ' r Refresh',
+ \ ' h Toggle help text',
+ \ ' t Toggle sorted TODOs',
+ \ ' -/+ Decrease/increase ToC depth (for content layer)',
+ \ ' f/F Apply/clear filter',
+ \]
+
+ if self.layer_status.content
+ call add(help_text, ' s Hide numbering')
+ endif
+ call add(help_text, '')
+
+ let l:first = 1
+ let l:frmt = printf('%%-%ds',
+ \ max(map(values(self.layer_keys), 'strlen(v:val)')) + 2)
+ for [layer, status] in items(self.layer_status)
+ call add(help_text,
+ \ (l:first ? 'Layers: ' : ' ')
+ \ . printf(l:frmt, self.layer_keys[layer])
+ \ . layer . (status ? '+' : '- (hidden)'))
+ let l:first = 0
+ endfor
+
+ call append('$', help_text)
+ call append('$', '')
+
+ let self.help_nlines += len(help_text) + 1
+endfunction
+
+" }}}1
+function! s:toc.print_entries() abort dict " {{{1
+ call self.set_number_format()
+
+ for entry in self.get_visible_entries()
+ call self.print_entry(entry)
+ endfor
+endfunction
+
+" }}}1
+function! s:toc.print_entry(entry) abort dict " {{{1
+ let output = 'L' . a:entry.level . ' '
+ if self.show_numbers
+ let number = a:entry.level >= self.tocdepth + 2 ? ''
+ \ : strpart(self.print_number(a:entry.number),
+ \ 0, self.number_width - 1)
+ let output .= printf(self.number_format, number)
+ endif
+
+ if self.hotkeys_enabled
+ let output .= printf('[%S] ', a:entry.hotkey)
+ endif
+
+ let output .= a:entry.title
+
+ call append('$', output)
+endfunction
+
+" }}}1
+function! s:toc.print_number(number) abort dict " {{{1
+ if empty(a:number) | return '' | endif
+ if type(a:number) == type('') | return a:number | endif
+
+ if get(a:number, 'part_toggle')
+ return s:int_to_roman(a:number.part)
+ endif
+
+ let number = [
+ \ a:number.chapter,
+ \ a:number.section,
+ \ a:number.subsection,
+ \ a:number.subsubsection,
+ \ a:number.subsubsubsection,
+ \ ]
+
+ " Remove unused parts
+ while len(number) > 0 && number[0] == 0
+ call remove(number, 0)
+ endwhile
+ while len(number) > 0 && number[-1] == 0
+ call remove(number, -1)
+ endwhile
+
+ " Change numbering in frontmatter, appendix, and backmatter
+ if self.topmatters > 1
+ \ && (a:number.frontmatter || a:number.backmatter)
+ return ''
+ elseif a:number.appendix
+ let number[0] = nr2char(number[0] + 64)
+ endif
+
+ return join(number, '.')
+endfunction
+
+" }}}1
+function! s:toc.set_number_format() abort dict " {{{1
+ let number_width = 0
+ for entry in self.get_visible_entries()
+ let number_width = max([number_width, strlen(self.print_number(entry.number)) + 1])
+ endfor
+
+ let self.number_width = self.layer_status.content
+ \ ? max([0, min([2*(self.tocdepth + 2), number_width])])
+ \ : 0
+ let self.number_format = '%-' . self.number_width . 's'
+endfunction
+
+" }}}1
+
+"
+" Interactions with TOC buffer/window
+"
+function! s:toc.activate_current(close_after) abort dict "{{{1
+ let n = vimtex#pos#get_cursor_line() - 1
+ if n < self.help_nlines | return {} | endif
+
+ let l:count = 0
+ for l:entry in self.get_visible_entries()
+ if l:count == n - self.help_nlines
+ return self.activate_entry(l:entry, a:close_after)
+ endif
+ let l:count += 1
+ endfor
+
+ return {}
+endfunction
+
+" }}}1
+function! s:toc.activate_hotkey(key) abort dict "{{{1
+ for entry in self.entries
+ if entry.hotkey ==# a:key
+ return self.activate_entry(entry, 1)
+ endif
+ endfor
+
+ return {}
+endfunction
+
+" }}}1
+function! s:toc.activate_entry(entry, close_after) abort dict "{{{1
+ let self.prev_index = vimtex#pos#get_cursor_line()
+ let l:vimtex_main = get(b:vimtex, 'tex', '')
+
+ " Save toc winnr info for later use
+ let toc_winnr = winnr()
+
+ " Return to calling window
+ call win_gotoid(self.prev_winid)
+
+ " Get buffer number, add buffer if necessary
+ let bnr = bufnr(a:entry.file)
+ if bnr == -1
+ execute 'badd ' . fnameescape(a:entry.file)
+ let bnr = bufnr(a:entry.file)
+ endif
+
+ " Set bufferopen command
+ " The point here is to use existing open buffer if the user has turned on
+ " the &switchbuf option to either 'useopen' or 'usetab'
+ let cmd = 'buffer! '
+ if &switchbuf =~# 'usetab'
+ for i in range(tabpagenr('$'))
+ if index(tabpagebuflist(i + 1), bnr) >= 0
+ let cmd = 'sbuffer! '
+ break
+ endif
+ endfor
+ elseif &switchbuf =~# 'useopen'
+ if bufwinnr(bnr) > 0
+ let cmd = 'sbuffer! '
+ endif
+ endif
+
+ " Open file buffer
+ execute 'keepalt' cmd bnr
+
+ " Go to entry line
+ if has_key(a:entry, 'line')
+ call vimtex#pos#set_cursor(a:entry.line, 0)
+ endif
+
+ " If relevant, enable vimtex stuff
+ if get(a:entry, 'link', 0) && !empty(l:vimtex_main)
+ let b:vimtex_main = l:vimtex_main
+ call vimtex#init()
+ endif
+
+ " Ensure folds are opened
+ normal! zv
+
+ " Keep or close toc window (based on options)
+ if a:close_after && self.split_pos !=# 'full'
+ call self.close()
+ else
+ " Return to toc window
+ execute toc_winnr . 'wincmd w'
+ endif
+
+ " Allow user entry points through autocmd events
+ if exists('#User#VimtexEventTocActivated')
+ doautocmd <nomodeline> User VimtexEventTocActivated
+ endif
+endfunction
+
+" }}}1
+function! s:toc.toggle_help() abort dict "{{{1
+ let l:pos = vimtex#pos#get_cursor()
+ if self.show_help
+ let l:pos[1] -= self.help_nlines
+ call vimtex#pos#set_cursor(l:pos)
+ endif
+
+ let self.show_help = self.show_help ? 0 : 1
+ call self.refresh()
+ call self.set_syntax()
+
+ if self.show_help
+ let l:pos[1] += self.help_nlines
+ call vimtex#pos#set_cursor(l:pos)
+ endif
+endfunction
+
+" }}}1
+function! s:toc.toggle_numbers() abort dict "{{{1
+ let self.show_numbers = self.show_numbers ? 0 : 1
+ call self.refresh()
+endfunction
+
+" }}}1
+function! s:toc.toggle_sorted_todos() abort dict "{{{1
+ let self.todo_sorted = self.todo_sorted ? 0 : 1
+ call self.get_entries(1)
+ call vimtex#pos#set_cursor(self.get_closest_index())
+endfunction
+
+" }}}1
+function! s:toc.toggle_type(type) abort dict "{{{1
+ let self.layer_status[a:type] = !self.layer_status[a:type]
+ for entry in self.entries
+ if entry.type ==# a:type
+ let entry.active = self.layer_status[a:type]
+ endif
+ endfor
+ call self.refresh()
+endfunction
+
+" }}}1
+function! s:toc.decrease_depth() abort dict "{{{1
+ let self.tocdepth = max([self.tocdepth - 1, -2])
+ call self.refresh()
+endfunction
+
+" }}}1
+function! s:toc.increase_depth() abort dict "{{{1
+ let self.tocdepth = min([self.tocdepth + 1, 5])
+ call self.refresh()
+endfunction
+
+" }}}1
+function! s:toc.filter() dict abort "{{{1
+ let re_filter = input('filter entry title by: ')
+ for entry in self.entries
+ let entry.hidden = get(entry, 'hidden') || entry.title !~# re_filter
+ endfor
+ call self.refresh()
+endfunction
+
+" }}}1
+function! s:toc.clear_filter() dict abort "{{{1
+ for entry in self.entries
+ let entry.hidden = 0
+ endfor
+ call self.refresh()
+endfunction
+
+" }}}1
+
+"
+" Utility functions
+"
+function! s:toc.get_closest_index() abort dict " {{{1
+ let l:calling_rank = 0
+ let l:not_found = 1
+ for [l:file, l:lnum, l:line] in vimtex#parser#tex(b:vimtex.tex)
+ let l:calling_rank += 1
+ if l:file ==# self.calling_file && l:lnum >= self.calling_line
+ let l:not_found = 0
+ break
+ endif
+ endfor
+
+ if l:not_found
+ return [0, get(self, 'prev_index', self.help_nlines + 1), 0, 0]
+ endif
+
+ let l:index = 0
+ let l:dist = 0
+ let l:closest_index = 1
+ let l:closest_dist = 10000
+ for l:entry in self.get_visible_entries()
+ let l:index += 1
+ let l:dist = l:calling_rank - entry.rank
+
+ if l:dist >= 0 && l:dist < l:closest_dist
+ let l:closest_dist = l:dist
+ let l:closest_index = l:index
+ endif
+ endfor
+
+ return [0, l:closest_index + self.help_nlines, 0, 0]
+endfunction
+
+" }}}1
+function! s:toc.position_save() abort dict " {{{1
+ let self.position = vimtex#pos#get_cursor()
+endfunction
+
+" }}}1
+function! s:toc.position_restore() abort dict " {{{1
+ if self.position[1] <= self.help_nlines
+ let self.position[1] = self.help_nlines + 1
+ endif
+ call vimtex#pos#set_cursor(self.position)
+endfunction
+
+" }}}1
+
+
+function! s:foldexpr(lnum) abort " {{{1
+ let pline = getline(a:lnum - 1)
+ let cline = getline(a:lnum)
+ let nline = getline(a:lnum + 1)
+ let l:pn = matchstr(pline, '^L\zs\d')
+ let l:cn = matchstr(cline, '^L\zs\d')
+ let l:nn = matchstr(nline, '^L\zs\d')
+
+ " Don't fold options
+ if cline =~# '^\s*$'
+ return 0
+ endif
+
+ if l:nn > l:cn
+ return '>' . l:nn
+ endif
+
+ if l:cn < l:pn
+ return l:cn
+ endif
+
+ return '='
+endfunction
+
+" }}}1
+function! s:foldtext() abort " {{{1
+ let l:line = getline(v:foldstart)[3:]
+ if b:toc.todo_sorted
+ \ && l:line =~# '\v%(' . join(g:vimtex_toc_todo_keywords, '|') . ')'
+ return substitute(l:line, '\w+\zs:.*', 's', '')
+ else
+ return l:line
+ endif
+endfunction
+
+" }}}1
+
+function! s:int_to_roman(number) abort " {{{1
+ let l:number = a:number
+ let l:result = ''
+ for [l:val, l:romn] in [
+ \ ['1000', 'M'],
+ \ ['900', 'CM'],
+ \ ['500', 'D'],
+ \ ['400', 'CD' ],
+ \ ['100', 'C'],
+ \ ['90', 'XC'],
+ \ ['50', 'L'],
+ \ ['40', 'XL'],
+ \ ['10', 'X'],
+ \ ['9', 'IX'],
+ \ ['5', 'V'],
+ \ ['4', 'IV'],
+ \ ['1', 'I'],
+ \]
+ while l:number >= l:val
+ let l:number -= l:val
+ let l:result .= l:romn
+ endwhile
+ endfor
+
+ return l:result
+endfunction
+
+" }}}1
+function! s:base(n, k) abort " {{{1
+ if a:n < a:k
+ return [a:n]
+ else
+ return add(s:base(a:n/a:k, a:k), a:n % a:k)
+ endif
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/util.vim b/autoload/vimtex/util.vim
new file mode 100644
index 00000000..eccf466e
--- /dev/null
+++ b/autoload/vimtex/util.vim
@@ -0,0 +1,273 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#util#command(cmd) abort " {{{1
+ let l:a = @a
+ try
+ silent! redir @a
+ silent! execute a:cmd
+ redir END
+ finally
+ let l:res = @a
+ let @a = l:a
+ return split(l:res, "\n")
+ endtry
+endfunction
+
+" }}}1
+function! vimtex#util#flatten(list) abort " {{{1
+ let l:result = []
+
+ for l:element in a:list
+ if type(l:element) == type([])
+ call extend(l:result, vimtex#util#flatten(l:element))
+ else
+ call add(l:result, l:element)
+ endif
+ unlet l:element
+ endfor
+
+ return l:result
+endfunction
+
+" }}}1
+function! vimtex#util#get_os() abort " {{{1
+ if has('win32') || has('win32unix')
+ return 'win'
+ elseif has('unix')
+ if has('mac') || system('uname') =~# 'Darwin'
+ return 'mac'
+ else
+ return 'linux'
+ endif
+ endif
+endfunction
+
+" }}}1
+function! vimtex#util#in_comment(...) abort " {{{1
+ return call('vimtex#util#in_syntax', ['texComment'] + a:000)
+endfunction
+
+" }}}1
+function! vimtex#util#in_mathzone(...) abort " {{{1
+ return call('vimtex#util#in_syntax', ['texMathZone'] + a:000)
+endfunction
+
+" }}}1
+function! vimtex#util#in_syntax(name, ...) abort " {{{1
+
+ " Usage: vimtex#util#in_syntax(name, [line, col])
+
+ " Get position and correct it if necessary
+ let l:pos = a:0 > 0 ? [a:1, a:2] : [line('.'), col('.')]
+ if mode() ==# 'i'
+ let l:pos[1] -= 1
+ endif
+ call map(l:pos, 'max([v:val, 1])')
+
+ " Check syntax at position
+ return match(map(synstack(l:pos[0], l:pos[1]),
+ \ "synIDattr(v:val, 'name')"),
+ \ '^' . a:name) >= 0
+endfunction
+
+" }}}1
+function! vimtex#util#extend_recursive(dict1, dict2, ...) abort " {{{1
+ let l:option = a:0 > 0 ? a:1 : 'force'
+ if index(['force', 'keep', 'error'], l:option) < 0
+ throw 'E475: Invalid argument: ' . l:option
+ endif
+
+ for [l:key, l:value] in items(a:dict2)
+ if !has_key(a:dict1, l:key)
+ let a:dict1[l:key] = l:value
+ elseif type(l:value) == type({})
+ call vimtex#util#extend_recursive(a:dict1[l:key], l:value, l:option)
+ elseif l:option ==# 'error'
+ throw 'E737: Key already exists: ' . l:key
+ elseif l:option ==# 'force'
+ let a:dict1[l:key] = l:value
+ endif
+ unlet l:value
+ endfor
+
+ return a:dict1
+endfunction
+
+" }}}1
+function! vimtex#util#shellescape(cmd) abort " {{{1
+ "
+ " Path used in "cmd" only needs to be enclosed by double quotes.
+ " shellescape() on Windows with "shellslash" set will produce a path
+ " enclosed by single quotes, which "cmd" does not recognize and reports an
+ " error.
+ "
+ if has('win32')
+ let l:shellslash = &shellslash
+ set noshellslash
+ let l:cmd = escape(shellescape(a:cmd), '\')
+ let &shellslash = l:shellslash
+ return l:cmd
+ else
+ return escape(shellescape(a:cmd), '\')
+ endif
+endfunction
+
+" }}}1
+function! vimtex#util#tex2unicode(line) abort " {{{1
+ " Convert compositions to unicode
+ let l:line = a:line
+ for [l:pat, l:symbol] in s:tex2unicode_list
+ let l:line = substitute(l:line, l:pat, l:symbol, 'g')
+ endfor
+
+ " Remove the \IeC macro
+ let l:line = substitute(l:line, '\\IeC\s*{\s*\([^}]\{-}\)\s*}', '\1', 'g')
+
+ return l:line
+endfunction
+
+"
+" Define list for converting compositions like \"u to unicode ű
+let s:tex2unicode_list = [
+ \ ['\\''A', 'Á'],
+ \ ['\\`A', 'À'],
+ \ ['\\^A', 'À'],
+ \ ['\\¨A', 'Ä'],
+ \ ['\\"A', 'Ä'],
+ \ ['\\''a', 'á'],
+ \ ['\\`a', 'à'],
+ \ ['\\^a', 'à'],
+ \ ['\\¨a', 'ä'],
+ \ ['\\"a', 'ä'],
+ \ ['\\\~a', 'ã'],
+ \ ['\\''E', 'É'],
+ \ ['\\`E', 'È'],
+ \ ['\\^E', 'Ê'],
+ \ ['\\¨E', 'Ë'],
+ \ ['\\"E', 'Ë'],
+ \ ['\\''e', 'é'],
+ \ ['\\`e', 'è'],
+ \ ['\\^e', 'ê'],
+ \ ['\\¨e', 'ë'],
+ \ ['\\"e', 'ë'],
+ \ ['\\''I', 'Í'],
+ \ ['\\`I', 'Î'],
+ \ ['\\^I', 'Ì'],
+ \ ['\\¨I', 'Ï'],
+ \ ['\\"I', 'Ï'],
+ \ ['\\''i', 'í'],
+ \ ['\\`i', 'î'],
+ \ ['\\^i', 'ì'],
+ \ ['\\¨i', 'ï'],
+ \ ['\\"i', 'ï'],
+ \ ['\\''i', 'í'],
+ \ ['\\''O', 'Ó'],
+ \ ['\\`O', 'Ò'],
+ \ ['\\^O', 'Ô'],
+ \ ['\\¨O', 'Ö'],
+ \ ['\\"O', 'Ö'],
+ \ ['\\''o', 'ó'],
+ \ ['\\`o', 'ò'],
+ \ ['\\^o', 'ô'],
+ \ ['\\¨o', 'ö'],
+ \ ['\\"o', 'ö'],
+ \ ['\\o', 'ø'],
+ \ ['\\''U', 'Ú'],
+ \ ['\\`U', 'Ù'],
+ \ ['\\^U', 'Û'],
+ \ ['\\¨U', 'Ü'],
+ \ ['\\"U', 'Ü'],
+ \ ['\\''u', 'ú'],
+ \ ['\\`u', 'ù'],
+ \ ['\\^u', 'û'],
+ \ ['\\¨u', 'ü'],
+ \ ['\\"u', 'ü'],
+ \ ['\\`N', 'Ǹ'],
+ \ ['\\\~N', 'Ñ'],
+ \ ['\\''n', 'ń'],
+ \ ['\\`n', 'ǹ'],
+ \ ['\\\~n', 'ñ'],
+ \]
+
+" }}}1
+function! vimtex#util#tex2tree(str) abort " {{{1
+ let tree = []
+ let i1 = 0
+ let i2 = -1
+ let depth = 0
+ while i2 < len(a:str)
+ let i2 = match(a:str, '[{}]', i2 + 1)
+ if i2 < 0
+ let i2 = len(a:str)
+ endif
+ if i2 >= len(a:str) || a:str[i2] ==# '{'
+ if depth == 0
+ let item = substitute(strpart(a:str, i1, i2 - i1),
+ \ '^\s*\|\s*$', '', 'g')
+ if !empty(item)
+ call add(tree, item)
+ endif
+ let i1 = i2 + 1
+ endif
+ let depth += 1
+ else
+ let depth -= 1
+ if depth == 0
+ call add(tree, vimtex#util#tex2tree(strpart(a:str, i1, i2 - i1)))
+ let i1 = i2 + 1
+ endif
+ endif
+ endwhile
+ return tree
+endfunction
+
+" }}}1
+function! vimtex#util#trim(str) abort " {{{1
+ if exists('*trim') | return trim(a:str) | endif
+
+ let l:str = substitute(a:str, '^\s*', '', '')
+ let l:str = substitute(l:str, '\s*$', '', '')
+
+ return l:str
+endfunction
+
+" }}}1
+function! vimtex#util#uniq(list) abort " {{{1
+ if exists('*uniq') | return uniq(a:list) | endif
+ if len(a:list) <= 1 | return a:list | endif
+
+ let l:uniq = [a:list[0]]
+ for l:next in a:list[1:]
+ if l:uniq[-1] != l:next
+ call add(l:uniq, l:next)
+ endif
+ endfor
+ return l:uniq
+endfunction
+
+" }}}1
+function! vimtex#util#uniq_unsorted(list) abort " {{{1
+ if len(a:list) <= 1 | return a:list | endif
+
+ let l:visited = {}
+ let l:result = []
+ for l:x in a:list
+ let l:key = string(l:x)
+ if !has_key(l:visited, l:key)
+ let l:visited[l:key] = 1
+ call add(l:result, l:x)
+ endif
+ endfor
+
+ return l:result
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/view.vim b/autoload/vimtex/view.vim
new file mode 100644
index 00000000..bebb7be9
--- /dev/null
+++ b/autoload/vimtex/view.vim
@@ -0,0 +1,115 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#view#init_buffer() abort " {{{1
+ if !g:vimtex_view_enabled | return | endif
+
+ command! -buffer -nargs=? -complete=file VimtexView
+ \ call b:vimtex.viewer.view(<q-args>)
+ if has_key(b:vimtex.viewer, 'reverse_search')
+ command! -buffer -nargs=* VimtexRSearch
+ \ call b:vimtex.viewer.reverse_search()
+ endif
+
+ nnoremap <buffer> <plug>(vimtex-view)
+ \ :call b:vimtex.viewer.view('')<cr>
+ if has_key(b:vimtex.viewer, 'reverse_search')
+ nnoremap <buffer> <plug>(vimtex-reverse-search)
+ \ :call b:vimtex.viewer.reverse_search()<cr>
+ endif
+endfunction
+
+" }}}1
+function! vimtex#view#init_state(state) abort " {{{1
+ if !g:vimtex_view_enabled | return | endif
+ if has_key(a:state, 'viewer') | return | endif
+
+ try
+ let a:state.viewer = vimtex#view#{g:vimtex_view_method}#new()
+ catch /E117/
+ call vimtex#log#warning(
+ \ 'Invalid viewer: ' . g:vimtex_view_method,
+ \ 'Please see :h g:vimtex_view_method')
+ return
+ endtry
+
+ " Make the following code more concise
+ let l:v = a:state.viewer
+
+ "
+ " Add compiler callback to callback hooks (if it exists)
+ "
+ if exists('*l:v.compiler_callback')
+ \ && index(g:vimtex_compiler_callback_hooks,
+ \ 'b:vimtex.viewer.compiler_callback') == -1
+ call add(g:vimtex_compiler_callback_hooks,
+ \ 'b:vimtex.viewer.compiler_callback')
+ endif
+
+ "
+ " Create view and/or callback hooks (if they exist)
+ "
+ for l:point in ['view', 'callback']
+ execute 'let l:hook = ''g:vimtex_view_'
+ \ . g:vimtex_view_method . '_hook_' . l:point . ''''
+ if exists(l:hook)
+ execute 'let hookfunc = ''*'' . ' . l:hook
+ if exists(hookfunc)
+ execute 'let l:v.hook_' . l:point . ' = function(' . l:hook . ')'
+ endif
+ endif
+ endfor
+endfunction
+
+" }}}1
+
+function! vimtex#view#reverse_goto(line, filename) abort " {{{1
+ if mode() ==# 'i' | stopinsert | endif
+
+ let l:file = resolve(a:filename)
+
+ " Open file if necessary
+ if !bufexists(l:file)
+ if filereadable(l:file)
+ execute 'silent edit' l:file
+ else
+ call vimtex#log#warning("Reverse goto failed for file:\n" . l:file)
+ return
+ endif
+ endif
+
+ " Go to correct buffer and line
+ let l:bufnr = bufnr(l:file)
+ let l:winnr = bufwinnr(l:file)
+ execute l:winnr >= 0
+ \ ? l:winnr . 'wincmd w'
+ \ : 'buffer ' . l:bufnr
+
+ execute 'normal!' a:line . 'G'
+ normal! zMzvzz
+
+ " Attempt to focus Vim
+ if executable('pstree') && executable('xdotool')
+ let l:pids = reverse(split(system('pstree -s -p ' . getpid()), '\D\+'))
+
+ let l:xwinids = []
+ call map(copy(l:pids), 'extend(l:xwinids, reverse(split('
+ \ . "system('xdotool search --onlyvisible --pid ' . v:val)"
+ \ . ')))')
+ call filter(l:xwinids, '!empty(v:val)')
+
+ if !empty(l:xwinids)
+ call system('xdotool windowactivate ' . l:xwinids[0] . ' &')
+ call feedkeys("\<c-l>", 'tn')
+ endif
+ endif
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/view/common.vim b/autoload/vimtex/view/common.vim
new file mode 100644
index 00000000..ca39cf0a
--- /dev/null
+++ b/autoload/vimtex/view/common.vim
@@ -0,0 +1,211 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#view#common#apply_common_template(viewer) abort " {{{1
+ return extend(a:viewer, deepcopy(s:common_template))
+endfunction
+
+" }}}1
+function! vimtex#view#common#apply_xwin_template(class, viewer) abort " {{{1
+ let a:viewer.class = a:class
+ let a:viewer.xwin_id = 0
+ call extend(a:viewer, deepcopy(s:xwin_template))
+ return a:viewer
+endfunction
+
+" }}}1
+function! vimtex#view#common#not_readable(output) abort " {{{1
+ if !filereadable(a:output)
+ call vimtex#log#warning('Viewer cannot read PDF file!', a:output)
+ return 1
+ else
+ return 0
+ endif
+endfunction
+
+" }}}1
+
+let s:common_template = {}
+
+function! s:common_template.out() dict abort " {{{1
+ return g:vimtex_view_use_temp_files
+ \ ? b:vimtex.root . '/' . b:vimtex.name . '_vimtex.pdf'
+ \ : b:vimtex.out(1)
+endfunction
+
+" }}}1
+function! s:common_template.synctex() dict abort " {{{1
+ return fnamemodify(self.out(), ':r') . '.synctex.gz'
+endfunction
+
+" }}}1
+function! s:common_template.copy_files() dict abort " {{{1
+ if !g:vimtex_view_use_temp_files | return | endif
+
+ "
+ " Copy pdf file
+ "
+ let l:out = self.out()
+ if getftime(b:vimtex.out()) > getftime(l:out)
+ call writefile(readfile(b:vimtex.out(), 'b'), l:out, 'b')
+ endif
+
+ "
+ " Copy synctex file
+ "
+ let l:old = b:vimtex.ext('synctex.gz')
+ let l:new = self.synctex()
+ if getftime(l:old) > getftime(l:new)
+ call rename(l:old, l:new)
+ endif
+endfunction
+
+" }}}1
+function! s:common_template.pprint_items() abort dict " {{{1
+ let l:list = []
+
+ if has_key(self, 'xwin_id')
+ call add(l:list, ['xwin id', self.xwin_id])
+ endif
+
+ if has_key(self, 'process')
+ call add(l:list, ['process', self.process])
+ endif
+
+ for l:key in filter(keys(self), 'v:val =~# ''^cmd_''')
+ call add(l:list, [l:key, self[l:key]])
+ endfor
+
+ return l:list
+endfunction
+
+" }}}1
+
+let s:xwin_template = {}
+
+function! s:xwin_template.view(file) dict abort " {{{1
+ if empty(a:file)
+ let outfile = self.out()
+ else
+ let outfile = a:file
+ endif
+ if vimtex#view#common#not_readable(outfile)
+ return
+ endif
+
+ if self.xwin_exists()
+ call self.forward_search(outfile)
+ else
+ if g:vimtex_view_use_temp_files
+ call self.copy_files()
+ endif
+ call self.start(outfile)
+ endif
+
+ if has_key(self, 'hook_view')
+ call self.hook_view()
+ endif
+endfunction
+
+" }}}1
+function! s:xwin_template.xwin_get_id() dict abort " {{{1
+ if !executable('xdotool') | return 0 | endif
+ if self.xwin_id > 0 | return self.xwin_id | endif
+
+ " Allow some time for the viewer to start properly
+ sleep 500m
+
+ "
+ " Get the window ID
+ "
+ let cmd = 'xdotool search --class ' . self.class
+ let xwin_ids = split(system(cmd), '\n')
+ if len(xwin_ids) == 0
+ call vimtex#log#warning('Viewer cannot find ' . self.class . ' window ID!')
+ let self.xwin_id = 0
+ else
+ let self.xwin_id = xwin_ids[-1]
+ endif
+
+ return self.xwin_id
+endfunction
+
+" }}}1
+function! s:xwin_template.xwin_exists() dict abort " {{{1
+ if !executable('xdotool') | return 0 | endif
+
+ "
+ " If xwin_id is already set, check if it still exists
+ "
+ if self.xwin_id > 0
+ let cmd = 'xdotool search --class ' . self.class
+ if index(split(system(cmd), '\n'), self.xwin_id) < 0
+ let self.xwin_id = 0
+ endif
+ endif
+
+ "
+ " If xwin_id is unset, check if matching viewer windows exist
+ "
+ if self.xwin_id == 0
+ if has_key(self, 'get_pid')
+ let cmd = 'xdotool search'
+ \ . ' --all --pid ' . self.get_pid()
+ \ . ' --name ' . fnamemodify(self.out(), ':t')
+ let self.xwin_id = get(split(system(cmd), '\n'), 0)
+ else
+ let cmd = 'xdotool search --name ' . fnamemodify(self.out(), ':t')
+ let ids = split(system(cmd), '\n')
+ let ids_already_used = filter(map(deepcopy(vimtex#state#list_all()),
+ \ "get(get(v:val, 'viewer', {}), 'xwin_id')"), 'v:val > 0')
+ for id in ids
+ if index(ids_already_used, id) < 0
+ let self.xwin_id = id
+ break
+ endif
+ endfor
+ endif
+ endif
+
+ return self.xwin_id > 0
+endfunction
+
+" }}}1
+function! s:xwin_template.xwin_send_keys(keys) dict abort " {{{1
+ if a:keys ==# '' || !executable('xdotool') || self.xwin_id <= 0
+ return
+ endif
+
+ let cmd = 'xdotool key --window ' . self.xwin_id
+ let cmd .= ' ' . a:keys
+ silent call system(cmd)
+endfunction
+
+" }}}1
+function! s:xwin_template.move(arg) abort " {{{1
+ if !executable('xdotool') || self.xwin_id <= 0
+ return
+ endif
+
+ let l:cmd = 'xdotool windowmove ' . self.xwin_get_id() . ' ' . a:arg
+ silent call system(l:cmd)
+endfunction
+
+" }}}1
+function! s:xwin_template.resize(arg) abort " {{{1
+ if !executable('xdotool') || self.xwin_id <= 0
+ return
+ endif
+
+ let l:cmd = 'xdotool windowsize ' . self.xwin_get_id() . ' ' . a:arg
+ silent call system(l:cmd)
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/view/general.vim b/autoload/vimtex/view/general.vim
new file mode 100644
index 00000000..701fe33c
--- /dev/null
+++ b/autoload/vimtex/view/general.vim
@@ -0,0 +1,111 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#view#general#new() abort " {{{1
+ " Check if the viewer is executable
+ " * split to ensure that we handle stuff like "gio open"
+ let l:exe = get(split(g:vimtex_view_general_viewer), 0, '')
+ if empty(l:exe) || !executable(l:exe)
+ call vimtex#log#warning(
+ \ 'Selected viewer is not executable!',
+ \ '- Selection: ' . g:vimtex_view_general_viewer .
+ \ '- Executable: ' . l:exe .
+ \ '- Please see :h g:vimtex_view_general_viewer')
+ return {}
+ endif
+
+ " Start from standard template
+ let l:viewer = vimtex#view#common#apply_common_template(deepcopy(s:general))
+
+ " Add callback hook
+ if exists('g:vimtex_view_general_callback')
+ let l:viewer.compiler_callback = function(g:vimtex_view_general_callback)
+ endif
+
+ return l:viewer
+endfunction
+
+" }}}1
+
+let s:general = {
+ \ 'name' : 'General'
+ \}
+
+function! s:general.view(file) dict abort " {{{1
+ if empty(a:file)
+ let outfile = self.out()
+
+ " Only copy files if they don't exist
+ if g:vimtex_view_use_temp_files
+ \ && vimtex#view#common#not_readable(outfile)
+ call self.copy_files()
+ endif
+ else
+ let outfile = a:file
+ endif
+
+ " Update the path for Windows on cygwin
+ if executable('cygpath')
+ let outfile = join(
+ \ vimtex#process#capture('cygpath -aw "' . outfile . '"'), '')
+ endif
+
+ if vimtex#view#common#not_readable(outfile) | return | endif
+
+ " Parse options
+ let l:cmd = g:vimtex_view_general_viewer
+ let l:cmd .= ' ' . g:vimtex_view_general_options
+
+ " Substitute magic patterns
+ let l:cmd = substitute(l:cmd, '@line', line('.'), 'g')
+ let l:cmd = substitute(l:cmd, '@col', col('.'), 'g')
+ let l:cmd = substitute(l:cmd, '@tex',
+ \ vimtex#util#shellescape(expand('%:p')), 'g')
+ let l:cmd = substitute(l:cmd, '@pdf', vimtex#util#shellescape(outfile), 'g')
+
+ " Start the view process
+ let self.process = vimtex#process#start(l:cmd, {'silent': 0})
+
+ if has_key(self, 'hook_view')
+ call self.hook_view()
+ endif
+endfunction
+
+" }}}1
+function! s:general.latexmk_append_argument() dict abort " {{{1
+ if g:vimtex_view_use_temp_files
+ return ' -view=none'
+ else
+ let l:option = g:vimtex_view_general_viewer
+ if !empty(g:vimtex_view_general_options_latexmk)
+ let l:option .= ' '
+ let l:option .= substitute(g:vimtex_view_general_options_latexmk,
+ \ '@line', line('.'), 'g')
+ endif
+ return vimtex#compiler#latexmk#wrap_option('pdf_previewer', l:option)
+ endif
+endfunction
+
+" }}}1
+function! s:general.compiler_callback(status) dict abort " {{{1
+ if !a:status && g:vimtex_view_use_temp_files < 2
+ return
+ endif
+
+ if g:vimtex_view_use_temp_files
+ call self.copy_files()
+ endif
+
+ if has_key(self, 'hook_callback')
+ call self.hook_callback()
+ endif
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/view/mupdf.vim b/autoload/vimtex/view/mupdf.vim
new file mode 100644
index 00000000..95d3710c
--- /dev/null
+++ b/autoload/vimtex/view/mupdf.vim
@@ -0,0 +1,186 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#view#mupdf#new() abort " {{{1
+ " Check if the viewer is executable
+ if !executable('mupdf')
+ call vimtex#log#error(
+ \ 'MuPDF is not executable!',
+ \ '- vimtex viewer will not work!')
+ return {}
+ endif
+
+ " Use the xwin template
+ return vimtex#view#common#apply_xwin_template('MuPDF',
+ \ vimtex#view#common#apply_common_template(deepcopy(s:mupdf)))
+endfunction
+
+" }}}1
+
+let s:mupdf = {
+ \ 'name': 'MuPDF',
+ \}
+
+function! s:mupdf.start(outfile) dict abort " {{{1
+ let l:cmd = 'mupdf ' . g:vimtex_view_mupdf_options
+ \ . ' ' . vimtex#util#shellescape(a:outfile)
+ let self.process = vimtex#process#start(l:cmd)
+
+ call self.xwin_get_id()
+ call self.xwin_send_keys(g:vimtex_view_mupdf_send_keys)
+
+ if g:vimtex_view_forward_search_on_start
+ call self.forward_search(a:outfile)
+ endif
+endfunction
+
+" }}}1
+function! s:mupdf.forward_search(outfile) dict abort " {{{1
+ if !executable('xdotool') | return | endif
+ if !executable('synctex') | return | endif
+
+ let self.cmd_synctex_view = 'synctex view -i '
+ \ . (line('.') + 1) . ':'
+ \ . (col('.') + 1) . ':'
+ \ . vimtex#util#shellescape(expand('%:p'))
+ \ . ' -o ' . vimtex#util#shellescape(a:outfile)
+ \ . " | grep -m1 'Page:' | sed 's/Page://' | tr -d '\n'"
+ let self.page = system(self.cmd_synctex_view)
+
+ if self.page > 0
+ let l:cmd = 'xdotool'
+ \ . ' type --window ' . self.xwin_id
+ \ . ' "' . self.page . 'g"'
+ call vimtex#process#run(l:cmd)
+ let self.cmd_forward_search = l:cmd
+ endif
+
+ call self.focus_viewer()
+endfunction
+
+" }}}1
+function! s:mupdf.reverse_search() dict abort " {{{1
+ if !executable('xdotool') | return | endif
+ if !executable('synctex') | return | endif
+
+ let outfile = self.out()
+ if vimtex#view#common#not_readable(outfile) | return | endif
+
+ if !self.xwin_exists()
+ call vimtex#log#warning('Reverse search failed (is MuPDF open?)')
+ return
+ endif
+
+ " Get page number
+ let self.cmd_getpage = 'xdotool getwindowname ' . self.xwin_id
+ let self.cmd_getpage .= " | sed 's:.* - \\([0-9]*\\)/.*:\\1:'"
+ let self.cmd_getpage .= " | tr -d '\n'"
+ let self.page = system(self.cmd_getpage)
+ if self.page <= 0 | return | endif
+
+ " Get file
+ let self.cmd_getfile = 'synctex edit '
+ let self.cmd_getfile .= "-o \"" . self.page . ':288:108:' . outfile . "\""
+ let self.cmd_getfile .= "| grep 'Input:' | sed 's/Input://' "
+ let self.cmd_getfile .= "| head -n1 | tr -d '\n' 2>/dev/null"
+ let self.file = system(self.cmd_getfile)
+
+ " Get line
+ let self.cmd_getline = 'synctex edit '
+ let self.cmd_getline .= "-o \"" . self.page . ':288:108:' . outfile . "\""
+ let self.cmd_getline .= "| grep -m1 'Line:' | sed 's/Line://' "
+ let self.cmd_getline .= "| head -n1 | tr -d '\n'"
+ let self.line = system(self.cmd_getline)
+
+ " Go to file and line
+ silent exec 'edit ' . fnameescape(self.file)
+ if self.line > 0
+ silent exec ':' . self.line
+ " Unfold, move to top line to correspond to top pdf line, and go to end of
+ " line in case the corresponding pdf line begins on previous pdf page.
+ normal! zvztg_
+ endif
+endfunction
+
+" }}}1
+function! s:mupdf.compiler_callback(status) dict abort " {{{1
+ if !a:status && g:vimtex_view_use_temp_files < 2
+ return
+ endif
+
+ if g:vimtex_view_use_temp_files
+ call self.copy_files()
+ endif
+
+ if !filereadable(self.out()) | return | endif
+
+ if g:vimtex_view_automatic
+ "
+ " Search for existing window created by latexmk
+ " It may be necessary to wait some time before it is opened and
+ " recognized. Sometimes it is very quick, other times it may take
+ " a second. This way, we don't block longer than necessary.
+ "
+ if !has_key(self, 'started_through_callback')
+ for l:dummy in range(30)
+ sleep 50m
+ if self.xwin_exists() | break | endif
+ endfor
+ endif
+
+ if !self.xwin_exists() && !has_key(self, 'started_through_callback')
+ call self.start(self.out())
+ let self.started_through_callback = 1
+ endif
+ endif
+
+ if g:vimtex_view_use_temp_files || get(b:vimtex.compiler, 'callback')
+ call self.xwin_send_keys('r')
+ endif
+
+ if has_key(self, 'hook_callback')
+ call self.hook_callback()
+ endif
+endfunction
+
+" }}}1
+function! s:mupdf.latexmk_append_argument() dict abort " {{{1
+ if g:vimtex_view_use_temp_files
+ let cmd = ' -view=none'
+ else
+ let cmd = vimtex#compiler#latexmk#wrap_option('new_viewer_always', '0')
+ let cmd .= vimtex#compiler#latexmk#wrap_option('pdf_update_method', '2')
+ let cmd .= vimtex#compiler#latexmk#wrap_option('pdf_update_signal', 'SIGHUP')
+ let cmd .= vimtex#compiler#latexmk#wrap_option('pdf_previewer',
+ \ 'mupdf ' . g:vimtex_view_mupdf_options)
+ endif
+
+ return cmd
+endfunction
+
+" }}}1
+function! s:mupdf.focus_viewer() dict abort " {{{1
+ if !executable('xdotool') | return | endif
+
+ if self.xwin_id > 0
+ silent call system('xdotool windowactivate ' . self.xwin_id . ' --sync')
+ silent call system('xdotool windowraise ' . self.xwin_id)
+ endif
+endfunction
+
+" }}}1
+function! s:mupdf.focus_vim() dict abort " {{{1
+ if !executable('xdotool') | return | endif
+
+ silent call system('xdotool windowactivate ' . v:windowid . ' --sync')
+ silent call system('xdotool windowraise ' . v:windowid)
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/view/skim.vim b/autoload/vimtex/view/skim.vim
new file mode 100644
index 00000000..c3dc6dec
--- /dev/null
+++ b/autoload/vimtex/view/skim.vim
@@ -0,0 +1,114 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#view#skim#new() abort " {{{1
+ " Check if Skim is installed
+ let l:cmd = join([
+ \ 'osascript -e ',
+ \ '''tell application "Finder" to POSIX path of ',
+ \ '(get application file id (id of application "Skim") as alias)''',
+ \])
+
+ if system(l:cmd)
+ call vimtex#log#error('Skim is not installed!')
+ return {}
+ endif
+
+ return vimtex#view#common#apply_common_template(deepcopy(s:skim))
+endfunction
+
+" }}}1
+
+let s:skim = {
+ \ 'name' : 'Skim',
+ \ 'startskim' : 'open -a Skim',
+ \}
+
+function! s:skim.view(file) dict abort " {{{1
+ if empty(a:file)
+ let outfile = self.out()
+
+ " Only copy files if they don't exist
+ if g:vimtex_view_use_temp_files
+ \ && vimtex#view#common#not_readable(outfile)
+ call self.copy_files()
+ endif
+ else
+ let outfile = a:file
+ endif
+ if vimtex#view#common#not_readable(outfile) | return | endif
+
+ let l:cmd = join([
+ \ 'osascript',
+ \ '-e ''set theLine to ' . line('.') . ' as integer''',
+ \ '-e ''set theFile to POSIX file "' . outfile . '"''',
+ \ '-e ''set thePath to POSIX path of (theFile as alias)''',
+ \ '-e ''set theSource to POSIX file "' . expand('%:p') . '"''',
+ \ '-e ''tell application "Skim"''',
+ \ '-e ''try''',
+ \ '-e ''set theDocs to get documents whose path is thePath''',
+ \ '-e ''if (count of theDocs) > 0 then revert theDocs''',
+ \ '-e ''end try''',
+ \ '-e ''open theFile''',
+ \ '-e ''tell front document to go to TeX line theLine from theSource',
+ \ g:vimtex_view_skim_reading_bar ? 'showing reading bar true''' : '''',
+ \ g:vimtex_view_skim_activate ? '-e ''activate''' : '',
+ \ '-e ''end tell''',
+ \])
+
+ let self.process = vimtex#process#start(l:cmd)
+
+ if has_key(self, 'hook_view')
+ call self.hook_view()
+ endif
+endfunction
+
+" }}}1
+function! s:skim.compiler_callback(status) dict abort " {{{1
+ if !a:status && g:vimtex_view_use_temp_files < 2
+ return
+ endif
+
+ if g:vimtex_view_use_temp_files
+ call self.copy_files()
+ endif
+
+ if !filereadable(self.out()) | return | endif
+
+ let l:cmd = join([
+ \ 'osascript',
+ \ '-e ''set theFile to POSIX file "' . self.out() . '"''',
+ \ '-e ''set thePath to POSIX path of (theFile as alias)''',
+ \ '-e ''tell application "Skim"''',
+ \ '-e ''try''',
+ \ '-e ''set theDocs to get documents whose path is thePath''',
+ \ '-e ''if (count of theDocs) > 0 then revert theDocs''',
+ \ '-e ''end try''',
+ \ '-e ''open theFile''',
+ \ '-e ''end tell''',
+ \])
+
+ let self.process = vimtex#process#start(l:cmd)
+
+ if has_key(self, 'hook_callback')
+ call self.hook_callback()
+ endif
+endfunction
+
+" }}}1
+function! s:skim.latexmk_append_argument() dict abort " {{{1
+ if g:vimtex_view_use_temp_files || g:vimtex_view_automatic
+ return ' -view=none'
+ else
+ return vimtex#compiler#latexmk#wrap_option('pdf_previewer', self.startskim)
+ endif
+endfunction
+
+" }}}1
+
+endif
diff --git a/autoload/vimtex/view/zathura.vim b/autoload/vimtex/view/zathura.vim
new file mode 100644
index 00000000..48e8e27a
--- /dev/null
+++ b/autoload/vimtex/view/zathura.vim
@@ -0,0 +1,155 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+function! vimtex#view#zathura#new() abort " {{{1
+ " Check if the viewer is executable
+ if !executable('zathura')
+ call vimtex#log#error('Zathura is not executable!')
+ return {}
+ endif
+
+ if executable('ldd')
+ let l:shared = split(system('ldd =zathura'))
+ if v:shell_error == 0
+ \ && empty(filter(l:shared, 'v:val =~# ''libsynctex'''))
+ call vimtex#log#warning('Zathura is not linked to libsynctex!')
+ let s:zathura.has_synctex = 0
+ endif
+ endif
+
+ " Check if the xdotool is available
+ if !executable('xdotool')
+ call vimtex#log#warning('Zathura requires xdotool for forward search!')
+ endif
+
+ "
+ " Use the xwin template
+ "
+ return vimtex#view#common#apply_xwin_template('Zathura',
+ \ vimtex#view#common#apply_common_template(deepcopy(s:zathura)))
+endfunction
+
+" }}}1
+
+let s:zathura = {
+ \ 'name' : 'Zathura',
+ \ 'has_synctex' : 1,
+ \}
+
+function! s:zathura.start(outfile) dict abort " {{{1
+ let l:cmd = 'zathura'
+ if self.has_synctex
+ let l:cmd .= ' -x "' . g:vimtex_compiler_progname
+ \ . ' --servername ' . v:servername
+ \ . ' --remote-expr '
+ \ . '\"vimtex#view#reverse_goto(%{line}, ''%{input}'')\""'
+ if g:vimtex_view_forward_search_on_start
+ let l:cmd .= ' --synctex-forward '
+ \ . line('.')
+ \ . ':' . col('.')
+ \ . ':' . vimtex#util#shellescape(expand('%:p'))
+ endif
+ endif
+ let l:cmd .= ' ' . g:vimtex_view_zathura_options
+ let l:cmd .= ' ' . vimtex#util#shellescape(a:outfile)
+ let self.process = vimtex#process#start(l:cmd)
+
+ call self.xwin_get_id()
+ let self.outfile = a:outfile
+endfunction
+
+" }}}1
+function! s:zathura.forward_search(outfile) dict abort " {{{1
+ if !self.has_synctex | return | endif
+ if !filereadable(self.synctex()) | return | endif
+
+ let l:cmd = 'zathura --synctex-forward '
+ let l:cmd .= line('.')
+ let l:cmd .= ':' . col('.')
+ let l:cmd .= ':' . vimtex#util#shellescape(expand('%:p'))
+ let l:cmd .= ' ' . vimtex#util#shellescape(a:outfile)
+ call vimtex#process#run(l:cmd)
+ let self.cmd_forward_search = l:cmd
+ let self.outfile = a:outfile
+endfunction
+
+" }}}1
+function! s:zathura.compiler_callback(status) dict abort " {{{1
+ if !a:status && g:vimtex_view_use_temp_files < 2
+ return
+ endif
+
+ if g:vimtex_view_use_temp_files
+ call self.copy_files()
+ endif
+
+ if !filereadable(self.out()) | return | endif
+
+ if g:vimtex_view_automatic
+ "
+ " Search for existing window created by latexmk
+ " It may be necessary to wait some time before it is opened and
+ " recognized. Sometimes it is very quick, other times it may take
+ " a second. This way, we don't block longer than necessary.
+ "
+ if !has_key(self, 'started_through_callback')
+ for l:dummy in range(30)
+ sleep 50m
+ if self.xwin_exists() | break | endif
+ endfor
+ endif
+
+ if !self.xwin_exists() && !has_key(self, 'started_through_callback')
+ call self.start(self.out())
+ let self.started_through_callback = 1
+ endif
+ endif
+
+ if has_key(self, 'hook_callback')
+ call self.hook_callback()
+ endif
+endfunction
+
+" }}}1
+function! s:zathura.latexmk_append_argument() dict abort " {{{1
+ if g:vimtex_view_use_temp_files
+ let cmd = ' -view=none'
+ else
+ let zathura = 'zathura ' . g:vimtex_view_zathura_options
+ if self.has_synctex
+ let zathura .= ' -x \"' . g:vimtex_compiler_progname
+ \ . ' --servername ' . v:servername
+ \ . ' --remote +\%{line} \%{input}\" \%S'
+ endif
+
+ let cmd = vimtex#compiler#latexmk#wrap_option('new_viewer_always', '0')
+ let cmd .= vimtex#compiler#latexmk#wrap_option('pdf_previewer', zathura)
+ endif
+
+ return cmd
+endfunction
+
+" }}}1
+function! s:zathura.get_pid() dict abort " {{{1
+ " First try to match full output file name
+ let cmd = 'pgrep -nf "zathura.*'
+ \ . escape(get(self, 'outfile', self.out()), '~\%.') . '"'
+ let pid = str2nr(system(cmd)[:-2])
+
+ " Now try to match correct servername as fallback
+ if empty(pid)
+ let cmd = 'pgrep -nf "zathura.+--servername ' . v:servername . '"'
+ let pid = str2nr(system(cmd)[:-2])
+ endif
+
+ return pid
+endfunction
+
+" }}}1
+
+endif
diff --git a/build b/build
index fd442c14..d5759060 100755
--- a/build
+++ b/build
@@ -227,7 +227,7 @@ PACKS="
jsx:MaxMEllon/vim-jsx-pretty:_ALL
julia:JuliaEditorSupport/julia-vim
kotlin:udalov/kotlin-vim
- latex:LaTeX-Box-Team/LaTeX-Box
+ latex:lervag/vimtex
less:groenewege/vim-less:_NOAFTER
lilypond:anowlcalledjosh/vim-lilypond
livescript:gkz/vim-ls
diff --git a/compiler/bibertool.vim b/compiler/bibertool.vim
new file mode 100644
index 00000000..48388180
--- /dev/null
+++ b/compiler/bibertool.vim
@@ -0,0 +1,26 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+if exists('current_compiler') | finish | endif
+let current_compiler = 'bibertool'
+
+" Older Vim always used :setlocal
+if exists(':CompilerSet') != 2
+ command -nargs=* CompilerSet setlocal <args>
+endif
+
+let s:cpo_save = &cpo
+set cpo&vim
+
+CompilerSet makeprg=biber\ --nodieonerror\ --noconf\ --nolog\ --output-file=-\ --validate-datamodel\ --tool\ %:S
+
+let &l:errorformat = "%-PINFO - Globbing data source '%f',"
+let &l:errorformat .= '%EERROR - %*[^\,]\, line %l\, %m,'
+let &l:errorformat .= "%WWARN - Datamodel: Entry '%s' (%f): %m,"
+let &l:errorformat .= '%WWARN - Datamodel: %m,'
+let &l:errorformat .= '%-G%.%#'
+silent CompilerSet errorformat
+
+let &cpo = s:cpo_save
+unlet s:cpo_save
+
+endif
diff --git a/compiler/chktex.vim b/compiler/chktex.vim
new file mode 100644
index 00000000..e9cbc1e5
--- /dev/null
+++ b/compiler/chktex.vim
@@ -0,0 +1,20 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+if exists('current_compiler') | finish | endif
+let current_compiler = 'chktex'
+
+" Older Vim always used :setlocal
+if exists(':CompilerSet') != 2
+ command -nargs=* CompilerSet setlocal <args>
+endif
+
+let s:cpo_save = &cpo
+set cpo&vim
+
+CompilerSet makeprg=chktex\ --localrc\ --inputfiles\ --quiet\ -v6\ %:S
+CompilerSet errorformat="%f",\ line\ %l.%c:\ %m
+
+let &cpo = s:cpo_save
+unlet s:cpo_save
+
+endif
diff --git a/compiler/lacheck.vim b/compiler/lacheck.vim
new file mode 100644
index 00000000..b8b4c84a
--- /dev/null
+++ b/compiler/lacheck.vim
@@ -0,0 +1,20 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+if exists('current_compiler') | finish | endif
+let current_compiler = 'lacheck'
+
+" Older Vim always used :setlocal
+if exists(':CompilerSet') != 2
+ command -nargs=* CompilerSet setlocal <args>
+endif
+
+let s:cpo_save = &cpo
+set cpo&vim
+
+CompilerSet makeprg=lacheck\ %:S
+CompilerSet errorformat="%f",\ line\ %l:\ %m
+
+let &cpo = s:cpo_save
+unlet s:cpo_save
+
+endif
diff --git a/compiler/style-check.vim b/compiler/style-check.vim
new file mode 100644
index 00000000..514f5267
--- /dev/null
+++ b/compiler/style-check.vim
@@ -0,0 +1,24 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+if exists('current_compiler') | finish | endif
+let current_compiler = 'style-check'
+
+" older Vim always used :setlocal
+if exists(':CompilerSet') != 2
+ command -nargs=* CompilerSet setlocal <args>
+endif
+
+let s:cpo_save = &cpo
+set cpo&vim
+
+CompilerSet makeprg=style-check.rb\ %:S
+
+setlocal errorformat=
+setlocal errorformat+=%f:%l:%c:\ %m
+setlocal errorformat+=%-G%.%#
+silent CompilerSet errorformat
+
+let &cpo = s:cpo_save
+unlet s:cpo_save
+
+endif
diff --git a/compiler/textidote.vim b/compiler/textidote.vim
new file mode 100644
index 00000000..f7ecab30
--- /dev/null
+++ b/compiler/textidote.vim
@@ -0,0 +1,38 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+if exists('current_compiler') | finish | endif
+let current_compiler = 'textidote'
+
+" older Vim always used :setlocal
+if exists(':CompilerSet') != 2
+ command -nargs=* CompilerSet setlocal <args>
+endif
+
+let s:cpo_save = &cpo
+set cpo&vim
+
+if exists('g:vimtex_textidote_jar')
+ \ && filereadable(fnamemodify(g:vimtex_textidote_jar, ':p'))
+ let s:textidote_cmd = 'java -jar '
+ \ . shellescape(fnamemodify(g:vimtex_textidote_jar, ':p'))
+else
+ echoerr 'To use the textidote compiler, '
+ \ . 'please set g:vimtex_textidote_jar to the path of textidote.jar!'
+ finish
+endif
+
+let &l:makeprg = s:textidote_cmd
+ \ . ' --no-color --output singleline --check '
+ \ . matchstr(&spelllang, '^\a\a') . ' %:S'
+
+setlocal errorformat=
+setlocal errorformat+=%f(L%lC%c-L%\\d%\\+C%\\d%\\+):\ %m
+setlocal errorformat+=%-G%.%#
+
+silent CompilerSet makeprg
+silent CompilerSet errorformat
+
+let &cpo = s:cpo_save
+unlet s:cpo_save
+
+endif
diff --git a/ftplugin/bib.vim b/ftplugin/bib.vim
new file mode 100644
index 00000000..ceadde15
--- /dev/null
+++ b/ftplugin/bib.vim
@@ -0,0 +1,28 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+if !get(g:, 'vimtex_enabled', 1)
+ finish
+endif
+
+if exists('b:did_ftplugin')
+ finish
+endif
+let b:did_ftplugin = 1
+
+setlocal comments=sO:%\ -,mO:%\ \ ,eO:%%,:%
+setlocal commentstring=\%\ %s
+
+" Initialize local LaTeX state if applicable
+let b:vimtex = getbufvar('#', 'vimtex', {})
+if empty(b:vimtex) | unlet b:vimtex | finish | endif
+
+" Apply errorformat for properly handling quickfix entries
+silent! call b:vimtex.qf.set_errorformat()
+
+endif
diff --git a/ftplugin/latex-box/common.vim b/ftplugin/latex-box/common.vim
deleted file mode 100644
index 1a972c9b..00000000
--- a/ftplugin/latex-box/common.vim
+++ /dev/null
@@ -1,417 +0,0 @@
-if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
-
-" LaTeX Box common functions
-
-" Error Format {{{
-" Note: The error formats assume we're using the -file-line-error with
-" [pdf]latex.
-" Note: See |errorformat-LaTeX| for more info.
-
-" Check for options
-if !exists("g:LatexBox_show_warnings")
- let g:LatexBox_show_warnings=1
-endif
-if !exists("g:LatexBox_ignore_warnings")
- let g:LatexBox_ignore_warnings =
- \['Underfull',
- \ 'Overfull',
- \ 'specifier changed to']
-endif
-
-" Standard error message formats
-" Note: We consider statements that starts with "!" as errors
-setlocal efm=%E!\ LaTeX\ %trror:\ %m
-setlocal efm+=%E%f:%l:\ %m
-setlocal efm+=%E!\ %m
-
-" More info for undefined control sequences
-setlocal efm+=%Z<argument>\ %m
-
-" More info for some errors
-setlocal efm+=%Cl.%l\ %m
-
-" Show or ignore warnings
-if g:LatexBox_show_warnings
- " Parse biblatex warnings
- setlocal efm+=%-C(biblatex)%.%#in\ t%.%#
- setlocal efm+=%-C(biblatex)%.%#Please\ v%.%#
- setlocal efm+=%-C(biblatex)%.%#LaTeX\ a%.%#
- setlocal efm+=%-Z(biblatex)%m
-
- " Parse hyperref warnings
- setlocal efm+=%-C(hyperref)%.%#on\ input\ line\ %l.
-
- for w in g:LatexBox_ignore_warnings
- let warning = escape(substitute(w, '[\,]', '%\\\\&', 'g'), ' ')
- exe 'setlocal efm+=%-G%.%#'. warning .'%.%#'
- endfor
- setlocal efm+=%+WLaTeX\ %.%#Warning:\ %.%#line\ %l%.%#
- setlocal efm+=%+W%.%#\ at\ lines\ %l--%*\\d
- setlocal efm+=%+WLaTeX\ %.%#Warning:\ %m
- setlocal efm+=%+W%.%#Warning:\ %m
-else
- setlocal efm+=%-WLaTeX\ %.%#Warning:\ %.%#line\ %l%.%#
- setlocal efm+=%-W%.%#\ at\ lines\ %l--%*\\d
- setlocal efm+=%-WLaTeX\ %.%#Warning:\ %m
- setlocal efm+=%-W%.%#Warning:\ %m
-endif
-
-" Push file to file stack
-setlocal efm+=%+P**%f
-setlocal efm+=%+P**\"%f\"
-
-" Ignore unmatched lines
-setlocal efm+=%-G%.%#
-" }}}
-
-" Vim Windows {{{
-
-" Type of split, "new" for horiz. "vnew" for vert.
-if !exists('g:LatexBox_split_type')
- let g:LatexBox_split_type = "vnew"
-endif
-
-" Length of vertical splits
-if !exists('g:LatexBox_split_length')
- let g:LatexBox_split_length = 15
-endif
-
-" Width of horizontal splits
-if !exists('g:LatexBox_split_width')
- let g:LatexBox_split_width = 30
-endif
-
-" Where splits appear
-if !exists('g:LatexBox_split_side')
- let g:LatexBox_split_side = "aboveleft"
-endif
-
-" Resize when split?
-if !exists('g:LatexBox_split_resize')
- let g:LatexBox_split_resize = 0
-endif
-
-" Toggle help info
-if !exists('g:LatexBox_toc_hidehelp')
- let g:LatexBox_toc_hidehelp = 0
-endif
-" }}}
-
-" Filename utilities {{{
-function! LatexBox_GetMainTexFile()
-
- " 1. check for the b:main_tex_file variable
- if exists('b:main_tex_file') && filereadable(b:main_tex_file)
- return b:main_tex_file
- endif
-
-
- " 2. scan the first few lines of the file for root = filename
- for linenum in range(1,5)
- let linecontents = getline(linenum)
- if linecontents =~ 'root\s*='
- " Remove everything but the filename
- let b:main_tex_file = substitute(linecontents,
- \ '.*root\s*=\s*', "", "")
- let b:main_tex_file = substitute(b:main_tex_file, '\s*$', "", "")
- " Prepend current directory if this isn't an absolute path
- if b:main_tex_file !~ '^/'
- let b:main_tex_file = expand('%:p:h') . '/' . b:main_tex_file
- endif
- let b:main_tex_file = fnamemodify(b:main_tex_file, ":p")
- if b:main_tex_file !~ '\.tex$'
- let b:main_tex_file .= '.tex'
- endif
- return b:main_tex_file
- endif
- endfor
-
- " 3. scan current file for "\begin{document}"
- if &filetype == 'tex' && search('\m\C\\begin\_\s*{document}', 'nw') != 0
- return expand('%:p')
- endif
-
- " 4. use 'main.tex' if it exists in the same directory (and is readable)
- let s:main_dot_tex_file=expand('%:p:h') . '/main.tex'
- if filereadable(s:main_dot_tex_file)
- let b:main_tex_file=s:main_dot_tex_file
- return b:main_tex_file
- endif
-
- " 5. borrow the Vim-Latex-Suite method of finding it
- if LatexBox_GetMainFileName() != expand('%:p')
- let b:main_tex_file = LatexBox_GetMainFileName()
- return b:main_tex_file
- endif
-
- " 6. prompt for file with completion
- let b:main_tex_file = s:PromptForMainFile()
- return b:main_tex_file
-endfunction
-
-function! s:PromptForMainFile()
- let saved_dir = getcwd()
- execute 'cd ' . fnameescape(expand('%:p:h'))
-
- " Prompt for file
- let l:file = ''
- while !filereadable(l:file)
- let l:file = input('main LaTeX file: ', '', 'file')
- if l:file !~ '\.tex$'
- let l:file .= '.tex'
- endif
- endwhile
- let l:file = fnamemodify(l:file, ':p')
-
- " Make persistent
- let l:persistent = ''
- while l:persistent !~ '\v^(y|n)'
- let l:persistent = input('make choice persistent? (y, n) ')
- if l:persistent == 'y'
- call writefile([], l:file . '.latexmain')
- endif
- endwhile
-
- execute 'cd ' . fnameescape(saved_dir)
- return l:file
-endfunction
-
-" Return the directory of the main tex file
-function! LatexBox_GetTexRoot()
- return fnamemodify(LatexBox_GetMainTexFile(), ':h')
-endfunction
-
-function! LatexBox_GetBuildBasename(with_dir)
- " 1. Check for g:LatexBox_jobname
- if exists('g:LatexBox_jobname')
- return g:LatexBox_jobname
- endif
-
- " 2. Get the basename from the main tex file
- if a:with_dir
- return fnamemodify(LatexBox_GetMainTexFile(), ':r')
- else
- return fnamemodify(LatexBox_GetMainTexFile(), ':t:r')
- endif
-endfunction
-
-function! LatexBox_GetAuxFile()
- " 1. check for b:build_dir variable
- if exists('b:build_dir') && isdirectory(b:build_dir)
- return b:build_dir . '/' . LatexBox_GetBuildBasename(0) . '.aux'
- endif
-
- " 2. check for g:LatexBox_build_dir variable
- if exists('g:LatexBox_build_dir') && isdirectory(g:LatexBox_build_dir)
- return g:LatexBox_build_dir . '/' . LatexBox_GetBuildBasename(0) . '.aux'
- endif
-
- " 3. use the base name of main tex file
- return LatexBox_GetBuildBasename(1) . '.aux'
-endfunction
-
-function! LatexBox_GetLogFile()
- " 1. check for b:build_dir variable
- if exists('b:build_dir') && isdirectory(b:build_dir)
- return b:build_dir . '/' . LatexBox_GetBuildBasename(0) . '.log'
- endif
-
- " 2. check for g:LatexBox_build_dir variable
- if exists('g:LatexBox_build_dir') && isdirectory(g:LatexBox_build_dir)
- return g:LatexBox_build_dir . '/' . LatexBox_GetBuildBasename(0) . '.log'
- endif
-
- " 3. use the base name of main tex file
- return LatexBox_GetBuildBasename(1) . '.log'
-endfunction
-
-function! LatexBox_GetOutputFile()
- " 1. check for b:build_dir variable
- if exists('b:build_dir') && isdirectory(b:build_dir)
- return b:build_dir . '/' . LatexBox_GetBuildBasename(0)
- \ . '.' . g:LatexBox_output_type
- endif
-
- " 2. check for g:LatexBox_build_dir variable
- if exists('g:LatexBox_build_dir') && isdirectory(g:LatexBox_build_dir)
- return g:LatexBox_build_dir . '/' . LatexBox_GetBuildBasename(0)
- \ . '.' . g:LatexBox_output_type
- endif
-
- " 3. use the base name of main tex file
- return LatexBox_GetBuildBasename(1) . '.' . g:LatexBox_output_type
-endfunction
-" }}}
-
-" View Output {{{
-
-" Default pdf viewer
-if !exists('g:LatexBox_viewer')
- " On windows, 'running' a file will open it with the default program
- let s:viewer = ''
- if has('unix')
- " echo -n necessary as uname -s will append \n otherwise
- let s:uname = system('echo -n $(uname -s)')
- if s:uname == "Darwin"
- let s:viewer = 'open'
- else
- let s:viewer = 'xdg-open'
- endif
- endif
- let g:LatexBox_viewer = s:viewer
-endif
-
-function! LatexBox_View(...)
- let lvargs = join(a:000, ' ')
- let outfile = LatexBox_GetOutputFile()
- if !filereadable(outfile)
- echomsg fnamemodify(outfile, ':.') . ' is not readable'
- return
- endif
- let cmd = g:LatexBox_viewer . ' ' . lvargs . ' ' . shellescape(outfile)
- if has('win32')
- let cmd = '!start /b ' . cmd . ' >nul'
- else
- let cmd = '!' . cmd . ' '
- if fnamemodify(&shell, ':t') ==# 'fish'
- let cmd .= ' >/dev/null ^/dev/null &'
- else
- let cmd .= ' &>/dev/null &'
- endif
- endif
- silent execute cmd
- if !has("gui_running")
- redraw!
- endif
-endfunction
-
-command! -nargs=* LatexView call LatexBox_View('<args>')
-" }}}
-
-" In Comment {{{
-
-" LatexBox_InComment([line], [col])
-" return true if inside comment
-function! LatexBox_InComment(...)
- let line = a:0 >= 1 ? a:1 : line('.')
- let col = a:0 >= 2 ? a:2 : col('.')
- return synIDattr(synID(line, col, 0), "name") =~# '^texComment'
-endfunction
-" }}}
-
-" Get Current Environment {{{
-
-" LatexBox_GetCurrentEnvironment([with_pos])
-" Returns:
-" - environment
-" if with_pos is not given
-" - [envirnoment, lnum_begin, cnum_begin, lnum_end, cnum_end]
-" if with_pos is nonzero
-function! LatexBox_GetCurrentEnvironment(...)
- if a:0 > 0
- let with_pos = a:1
- else
- let with_pos = 0
- endif
-
- let begin_pat = '\C\\begin\_\s*{[^}]*}\|\\\@<!\\\[\|\\\@<!\\('
- let end_pat = '\C\\end\_\s*{[^}]*}\|\\\@<!\\\]\|\\\@<!\\)'
- let saved_pos = getpos('.')
-
- " move to the left until on a backslash
- let [bufnum, lnum, cnum, off] = getpos('.')
- let line = getline(lnum)
- while cnum > 1 && line[cnum - 1] != '\'
- let cnum -= 1
- endwhile
- call cursor(lnum, cnum)
-
- " match begin/end pairs but skip comments
- let flags = 'bnW'
- if strpart(getline('.'), col('.') - 1) =~ '^\%(' . begin_pat . '\)'
- let flags .= 'c'
- endif
- let [lnum1, cnum1] = searchpairpos(begin_pat, '', end_pat, flags,
- \ 'LatexBox_InComment()')
-
- let env = ''
-
- if lnum1
- let line = strpart(getline(lnum1), cnum1 - 1)
-
- if empty(env)
- let env = matchstr(line, '^\C\\begin\_\s*{\zs[^}]*\ze}')
- endif
- if empty(env)
- let env = matchstr(line, '^\\\[')
- endif
- if empty(env)
- let env = matchstr(line, '^\\(')
- endif
- endif
-
- if with_pos == 1
- let flags = 'nW'
- if !(lnum1 == lnum && cnum1 == cnum)
- let flags .= 'c'
- endif
-
- let [lnum2, cnum2] = searchpairpos(begin_pat, '', end_pat, flags,
- \ 'LatexBox_InComment()')
-
- call setpos('.', saved_pos)
- return [env, lnum1, cnum1, lnum2, cnum2]
- else
- call setpos('.', saved_pos)
- return env
- endif
-endfunction
-" }}}
-
-" Tex To Tree {{{
-" stores nested braces in a tree structure
-function! LatexBox_TexToTree(str)
- let tree = []
- let i1 = 0
- let i2 = -1
- let depth = 0
- while i2 < len(a:str)
- let i2 = match(a:str, '[{}]', i2 + 1)
- if i2 < 0
- let i2 = len(a:str)
- endif
- if i2 >= len(a:str) || a:str[i2] == '{'
- if depth == 0
- let item = substitute(strpart(a:str, i1, i2 - i1),
- \ '^\s*\|\s*$', '', 'g')
- if !empty(item)
- call add(tree, item)
- endif
- let i1 = i2 + 1
- endif
- let depth += 1
- else
- let depth -= 1
- if depth == 0
- call add(tree, LatexBox_TexToTree(strpart(a:str, i1, i2 - i1)))
- let i1 = i2 + 1
- endif
- endif
- endwhile
- return tree
-endfunction
-" }}}
-
-" Tree To Tex {{{
-function! LatexBox_TreeToTex(tree)
- if type(a:tree) == type('')
- return a:tree
- else
- return '{' . join(map(a:tree, 'LatexBox_TreeToTex(v:val)'), '') . '}'
- endif
-endfunction
-" }}}
-
-" vim:fdm=marker:ff=unix:noet:ts=4:sw=4
-
-endif
diff --git a/ftplugin/latex-box/complete.vim b/ftplugin/latex-box/complete.vim
deleted file mode 100644
index 56e7516e..00000000
--- a/ftplugin/latex-box/complete.vim
+++ /dev/null
@@ -1,936 +0,0 @@
-if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
-
-" LaTeX Box completion
-
-setlocal omnifunc=LatexBox_Complete
-
-" <SID> Wrap {{{
-function! s:GetSID()
- return matchstr(expand('<sfile>'), '\zs<SNR>\d\+_\ze.*$')
-endfunction
-let s:SID = s:GetSID()
-function! s:SIDWrap(func)
- return s:SID . a:func
-endfunction
-" }}}
-
-" Completion {{{
-if !exists('g:LatexBox_completion_close_braces')
- let g:LatexBox_completion_close_braces = 1
-endif
-if !exists('g:LatexBox_bibtex_wild_spaces')
- let g:LatexBox_bibtex_wild_spaces = 1
-endif
-
-if !exists('g:LatexBox_cite_pattern')
- let g:LatexBox_cite_pattern = '\C\\\a*cite\a*\*\?\(\[[^\]]*\]\)*\_\s*{'
-endif
-if !exists('g:LatexBox_ref_pattern')
- let g:LatexBox_ref_pattern = '\C\\v\?\(eq\|page\|[cC]\|labelc\|name\|auto\)\?ref\*\?\_\s*{'
-endif
-
-if !exists('g:LatexBox_completion_environments')
- let g:LatexBox_completion_environments = [
- \ {'word': 'itemize', 'menu': 'bullet list' },
- \ {'word': 'enumerate', 'menu': 'numbered list' },
- \ {'word': 'description', 'menu': 'description' },
- \ {'word': 'center', 'menu': 'centered text' },
- \ {'word': 'figure', 'menu': 'floating figure' },
- \ {'word': 'table', 'menu': 'floating table' },
- \ {'word': 'equation', 'menu': 'equation (numbered)' },
- \ {'word': 'align', 'menu': 'aligned equations (numbered)' },
- \ {'word': 'align*', 'menu': 'aligned equations' },
- \ {'word': 'document' },
- \ {'word': 'abstract' },
- \ ]
-endif
-
-if !exists('g:LatexBox_completion_commands')
- let g:LatexBox_completion_commands = [
- \ {'word': '\begin{' },
- \ {'word': '\end{' },
- \ {'word': '\item' },
- \ {'word': '\label{' },
- \ {'word': '\ref{' },
- \ {'word': '\eqref{eq:' },
- \ {'word': '\cite{' },
- \ {'word': '\chapter{' },
- \ {'word': '\section{' },
- \ {'word': '\subsection{' },
- \ {'word': '\subsubsection{' },
- \ {'word': '\paragraph{' },
- \ {'word': '\nonumber' },
- \ {'word': '\bibliography' },
- \ {'word': '\bibliographystyle' },
- \ ]
-endif
-
-if !exists('g:LatexBox_complete_inlineMath')
- let g:LatexBox_complete_inlineMath = 0
-endif
-
-if !exists('g:LatexBox_eq_env_patterns')
- let g:LatexBox_eq_env_patterns = 'equation\|gather\|multiline\|align\|flalign\|alignat\|eqnarray'
-endif
-
-" }}}
-
-"LatexBox_kpsewhich {{{
-function! LatexBox_kpsewhich(file)
- let old_dir = getcwd()
- execute 'lcd ' . fnameescape(LatexBox_GetTexRoot())
- let out = system('kpsewhich "' . a:file . '"')
-
- " If kpsewhich has found something, it returns a non-empty string with a
- " newline at the end; otherwise the string is empty
- if len(out)
- " Remove the trailing newline
- let out = fnamemodify(out[:-2], ':p')
- endif
-
- execute 'lcd ' . fnameescape(old_dir)
-
- return out
-endfunction
-"}}}
-
-" Omni Completion {{{
-
-let s:completion_type = ''
-
-function! LatexBox_Complete(findstart, base)
- if a:findstart
- " return the starting position of the word
- let line = getline('.')
- let pos = col('.') - 1
- while pos > 0 && line[pos - 1] !~ '\\\|{'
- let pos -= 1
- endwhile
-
- let line_start = line[:pos-1]
- if line_start =~ '\m\C\\begin\_\s*{$'
- let s:completion_type = 'begin'
- elseif line_start =~ '\m\C\\end\_\s*{$'
- let s:completion_type = 'end'
- elseif line_start =~ '\m' . g:LatexBox_ref_pattern . '$'
- let s:completion_type = 'ref'
- elseif line_start =~ '\m' . g:LatexBox_cite_pattern . '$'
- let s:completion_type = 'bib'
- " check for multiple citations
- let pos = col('.') - 1
- while pos > 0 && line[pos - 1] !~ '{\|,'
- let pos -= 1
- endwhile
- elseif s:LatexBox_complete_inlineMath_or_not()
- let s:completion_type = 'inlineMath'
- let pos = s:eq_pos
- else
- let s:completion_type = 'command'
- if line[pos - 1] == '\'
- let pos -= 1
- endif
- endif
- return pos
- else
- " return suggestions in an array
- let suggestions = []
-
- if s:completion_type == 'begin'
- " suggest known environments
- for entry in g:LatexBox_completion_environments
- if entry.word =~ '^' . escape(a:base, '\')
- if g:LatexBox_completion_close_braces && !s:NextCharsMatch('^}')
- " add trailing '}'
- let entry = copy(entry)
- let entry.abbr = entry.word
- let entry.word = entry.word . '}'
- endif
- call add(suggestions, entry)
- endif
- endfor
- elseif s:completion_type == 'end'
- " suggest known environments
- let env = LatexBox_GetCurrentEnvironment()
- if env != ''
- if g:LatexBox_completion_close_braces && !s:NextCharsMatch('^\s*[,}]')
- call add(suggestions, {'word': env . '}', 'abbr': env})
- else
- call add(suggestions, env)
- endif
- endif
- elseif s:completion_type == 'command'
- " suggest known commands
- for entry in g:LatexBox_completion_commands
- if entry.word =~ '^' . escape(a:base, '\')
- " do not display trailing '{'
- if entry.word =~ '{'
- let entry.abbr = entry.word[0:-2]
- endif
- call add(suggestions, entry)
- endif
- endfor
- elseif s:completion_type == 'ref'
- let suggestions = s:CompleteLabels(a:base)
- elseif s:completion_type == 'bib'
- " suggest BibTeX entries
- let suggestions = LatexBox_BibComplete(a:base)
- elseif s:completion_type == 'inlineMath'
- let suggestions = s:LatexBox_inlineMath_completion(a:base)
- endif
- if !has('gui_running')
- redraw!
- endif
- return suggestions
- endif
-endfunction
-" }}}
-
-" BibTeX search {{{
-
-" find the \bibliography{...} commands
-" the optional argument is the file name to be searched
-
-function! s:FindBibData(...)
- if a:0 == 0
- let file = LatexBox_GetMainTexFile()
- else
- let file = a:1
- endif
-
- if !filereadable(file)
- return []
- endif
- let lines = readfile(file)
- let bibdata_list = []
-
- "
- " Search for added bibliographies
- "
- let bibliography_cmds = [
- \ '\\bibliography',
- \ '\\addbibresource',
- \ '\\addglobalbib',
- \ '\\addsectionbib',
- \ ]
- for cmd in bibliography_cmds
- let filtered = filter(copy(lines),
- \ 'v:val =~ ''\C' . cmd . '\s*{[^}]\+}''')
- let files = map(filtered,
- \ 'matchstr(v:val, ''\C' . cmd . '\s*{\zs[^}]\+\ze}'')')
- for file in files
- let bibdata_list += map(split(file, ','),
- \ 'fnamemodify(v:val, '':r'')')
- endfor
- endfor
-
- "
- " Also search included files
- "
- for input in filter(lines,
- \ 'v:val =~ ''\C\\\%(input\|include\)\s*{[^}]\+}''')
- let bibdata_list += s:FindBibData(LatexBox_kpsewhich(
- \ matchstr(input,
- \ '\C\\\%(input\|include\)\s*{\zs[^}]\+\ze}')))
- endfor
-
- return bibdata_list
-endfunction
-
-let s:bstfile = expand('<sfile>:p:h') . '/vimcomplete'
-
-function! LatexBox_BibSearch(regexp)
- let res = []
-
- " Find data from bib files
- let bibdata = join(s:FindBibData(), ',')
- if bibdata != ''
-
- " write temporary aux file
- let tmpbase = LatexBox_GetTexRoot() . '/_LatexBox_BibComplete'
- let auxfile = tmpbase . '.aux'
- let bblfile = tmpbase . '.bbl'
- let blgfile = tmpbase . '.blg'
-
- call writefile(['\citation{*}', '\bibstyle{' . s:bstfile . '}',
- \ '\bibdata{' . bibdata . '}'], auxfile)
-
- if has('win32')
- let l:old_shellslash = &l:shellslash
- setlocal noshellslash
- call system('cd ' . shellescape(LatexBox_GetTexRoot()) .
- \ ' & bibtex -terse '
- \ . fnamemodify(auxfile, ':t') . ' >nul')
- let &l:shellslash = l:old_shellslash
- else
- call system('cd ' . shellescape(LatexBox_GetTexRoot()) .
- \ ' ; bibtex -terse '
- \ . fnamemodify(auxfile, ':t') . ' >/dev/null')
- endif
-
- let lines = split(substitute(join(readfile(bblfile), "\n"),
- \ '\n\n\@!\(\s\=\)\s*\|{\|}', '\1', 'g'), "\n")
-
- for line in filter(lines, 'v:val =~ a:regexp')
- let matches = matchlist(line,
- \ '^\(.*\)||\(.*\)||\(.*\)||\(.*\)||\(.*\)')
- if !empty(matches) && !empty(matches[1])
- let s:type_length = max([s:type_length,
- \ len(matches[2]) + 3])
- call add(res, {
- \ 'key': matches[1],
- \ 'type': matches[2],
- \ 'author': matches[3],
- \ 'year': matches[4],
- \ 'title': matches[5],
- \ })
- endif
- endfor
-
- call delete(auxfile)
- call delete(bblfile)
- call delete(blgfile)
- endif
-
- " Find data from 'thebibliography' environments
- let lines = readfile(LatexBox_GetMainTexFile())
- if match(lines, '\C\\begin{thebibliography}') >= 0
- for line in filter(filter(lines, 'v:val =~ ''\C\\bibitem'''),
- \ 'v:val =~ a:regexp')
- let match = matchlist(line, '\\bibitem{\([^}]*\)')[1]
- call add(res, {
- \ 'key': match,
- \ 'type': '',
- \ 'author': '',
- \ 'year': '',
- \ 'title': match,
- \ })
- endfor
- endif
-
- return res
-endfunction
-" }}}
-
-" BibTeX completion {{{
-let s:type_length=0
-function! LatexBox_BibComplete(regexp)
-
- " treat spaces as '.*' if needed
- if g:LatexBox_bibtex_wild_spaces
- "let regexp = substitute(a:regexp, '\s\+', '.*', 'g')
- let regexp = '.*' . substitute(a:regexp, '\s\+', '\\\&.*', 'g')
- else
- let regexp = a:regexp
- endif
-
- let res = []
- let s:type_length = 4
- for m in LatexBox_BibSearch(regexp)
- let type = m['type'] == '' ? '[-]' : '[' . m['type'] . '] '
- let type = printf('%-' . s:type_length . 's', type)
- let auth = m['author'] == '' ? '' : m['author'][:20] . ' '
- let auth = substitute(auth, '\~', ' ', 'g')
- let auth = substitute(auth, ',.*\ze', ' et al. ', '')
- let year = m['year'] == '' ? '' : '(' . m['year'] . ')'
- let w = { 'word': m['key'],
- \ 'abbr': type . auth . year,
- \ 'menu': m['title'] }
-
- " close braces if needed
- if g:LatexBox_completion_close_braces && !s:NextCharsMatch('^\s*[,}]')
- let w.word = w.word . '}'
- endif
-
- call add(res, w)
- endfor
- return res
-endfunction
-" }}}
-
-" ExtractLabels {{{
-" Generate list of \newlabel commands in current buffer.
-"
-" Searches the current buffer for commands of the form
-" \newlabel{name}{{number}{page}.*
-" and returns list of [ name, number, page ] tuples.
-function! s:ExtractLabels()
- call cursor(1,1)
-
- let matches = []
- let [lblline, lblbegin] = searchpos( '\\newlabel{', 'ecW' )
-
- while [lblline, lblbegin] != [0,0]
- let [nln, nameend] = searchpairpos( '{', '', '}', 'W' )
- if nln != lblline
- let [lblline, lblbegin] = searchpos( '\\newlabel{', 'ecW' )
- continue
- endif
- let curname = strpart( getline( lblline ), lblbegin, nameend - lblbegin - 1 )
-
- " Ignore cref entries (because they are duplicates)
- if curname =~# "@cref$"
- let [lblline, lblbegin] = searchpos( '\\newlabel{', 'ecW' )
- continue
- endif
-
- if 0 == search( '\m{\w*{', 'ce', lblline )
- let [lblline, lblbegin] = searchpos( '\\newlabel{', 'ecW' )
- continue
- endif
-
- let numberbegin = getpos('.')[2]
- let [nln, numberend] = searchpairpos( '{', '', '}', 'W' )
- if nln != lblline
- let [lblline, lblbegin] = searchpos( '\\newlabel{', 'ecW' )
- continue
- endif
- let curnumber = strpart( getline( lblline ), numberbegin, numberend - numberbegin - 1 )
-
- if 0 == search( '\m\w*{', 'ce', lblline )
- let [lblline, lblbegin] = searchpos( '\\newlabel{', 'ecW' )
- continue
- endif
-
- let pagebegin = getpos('.')[2]
- let [nln, pageend] = searchpairpos( '{', '', '}', 'W' )
- if nln != lblline
- let [lblline, lblbegin] = searchpos( '\\newlabel{', 'ecW' )
- continue
- endif
- let curpage = strpart( getline( lblline ), pagebegin, pageend - pagebegin - 1 )
-
- let matches += [ [ curname, curnumber, curpage ] ]
-
- let [lblline, lblbegin] = searchpos( '\\newlabel{', 'ecW' )
- endwhile
-
- return matches
-endfunction
-"}}}
-
-" ExtractInputs {{{
-" Generate list of \@input commands in current buffer.
-"
-" Searches the current buffer for \@input{file} entries and
-" returns list of all files.
-function! s:ExtractInputs()
- call cursor(1,1)
-
- let matches = []
- let [inline, inbegin] = searchpos( '\\@input{', 'ecW' )
-
- while [inline, inbegin] != [0,0]
- let [nln, inend] = searchpairpos( '{', '', '}', 'W' )
- if nln != inline
- let [inline, inbegin] = searchpos( '\\@input{', 'ecW' )
- continue
- endif
- let matches += [ LatexBox_kpsewhich(strpart( getline( inline ), inbegin, inend - inbegin - 1 )) ]
-
- let [inline, inbegin] = searchpos( '\\@input{', 'ecW' )
- endwhile
-
- " Remove empty strings for nonexistant .aux files
- return filter(matches, 'v:val != ""')
-endfunction
-"}}}
-
-" LabelCache {{{
-" Cache of all labels.
-"
-" LabelCache is a dictionary mapping filenames to tuples
-" [ time, labels, inputs ]
-" where
-" * time is modification time of the cache entry
-" * labels is a list like returned by ExtractLabels
-" * inputs is a list like returned by ExtractInputs
-let s:LabelCache = {}
-"}}}
-
-" GetLabelCache {{{
-" Extract labels from LabelCache and update it.
-"
-" Compares modification time of each entry in the label
-" cache and updates it, if necessary. During traversal of
-" the LabelCache, all current labels are collected and
-" returned.
-function! s:GetLabelCache(file)
- if !filereadable(a:file)
- return []
- endif
-
- if !has_key(s:LabelCache , a:file) || s:LabelCache[a:file][0] != getftime(a:file)
- " Open file in temporary split window for label extraction.
- let main_tex_file = LatexBox_GetMainTexFile()
- silent execute '1sp +let\ b:main_tex_file=main_tex_file|let\ labels=s:ExtractLabels()|let\ inputs=s:ExtractInputs()|quit! ' . fnameescape(a:file)
- let s:LabelCache[a:file] = [ getftime(a:file), labels, inputs ]
- endif
-
- " We need to create a copy of s:LabelCache[fid][1], otherwise all inputs'
- " labels would be added to the current file's label cache upon each
- " completion call, leading to duplicates/triplicates/etc. and decreased
- " performance.
- " Also, because we don't anything with the list besides matching copies,
- " we can get away with a shallow copy for now.
- let labels = copy(s:LabelCache[a:file][1])
-
- for input in s:LabelCache[a:file][2]
- let labels += s:GetLabelCache(input)
- endfor
-
- return labels
-endfunction
-"}}}
-
-" Complete Labels {{{
-function! s:CompleteLabels(regex)
- let labels = s:GetLabelCache(LatexBox_GetAuxFile())
-
- let matches = filter( copy(labels), 'match(v:val[0], "' . a:regex . '") != -1' )
- if empty(matches)
- " also try to match label and number
- let regex_split = split(a:regex)
- if len(regex_split) > 1
- let base = regex_split[0]
- let number = escape(join(regex_split[1:], ' '), '.')
- let matches = filter( copy(labels), 'match(v:val[0], "' . base . '") != -1 && match(v:val[1], "' . number . '") != -1' )
- endif
- endif
- if empty(matches)
- " also try to match number
- let matches = filter( copy(labels), 'match(v:val[1], "' . a:regex . '") != -1' )
- endif
-
- let suggestions = []
- for m in matches
- let entry = {'word': m[0], 'menu': printf("%7s [p. %s]", '('.m[1].')', m[2])}
- if g:LatexBox_completion_close_braces && !s:NextCharsMatch('^\s*[,}]')
- " add trailing '}'
- let entry = copy(entry)
- let entry.abbr = entry.word
- let entry.word = entry.word . '}'
- endif
- call add(suggestions, entry)
- endfor
-
- return suggestions
-endfunction
-" }}}
-
-" Complete Inline Math Or Not {{{
-" Return 1, when cursor is in a math env:
-" 1, there is a single $ in the current line on the left of cursor
-" 2, there is an open-eq-env on/above the current line
-" (open-eq-env : \(, \[, and \begin{eq-env} )
-" Return 0, when cursor is not in a math env
-function! s:LatexBox_complete_inlineMath_or_not()
-
- " switch of inline math completion feature
- if g:LatexBox_complete_inlineMath == 0
- return 0
- endif
-
- " env names that can't appear in an eq env
- if !exists('s:LatexBox_doc_structure_patterns')
- let s:LatexBox_doc_structure_patterns = '\%(' . '\\begin\s*{document}\|' .
- \ '\\\%(chapter\|section\|subsection\|subsubsection\)\*\?\s*{' . '\)'
- endif
-
- if !exists('s:LatexBox_eq_env_open_patterns')
- let s:LatexBox_eq_env_open_patterns = ['\\(','\\\[']
- endif
- if !exists('s:LatexBox_eq_env_close_patterns')
- let s:LatexBox_eq_env_close_patterns = ['\\)','\\\]']
- endif
-
- let notcomment = '\%(\%(\\\@<!\%(\\\\\)*\)\@<=%.*\)\@<!'
-
- let lnum_saved = line('.')
- let cnum_saved = col('.') -1
-
- let line = getline('.')
- let line_start_2_cnum_saved = line[:cnum_saved]
-
- " determine whether there is a single $ before cursor
- let cursor_dollar_pair = 0
- while matchend(line_start_2_cnum_saved, '\$[^$]\+\$', cursor_dollar_pair) >= 0
- " find the end of dollar pair
- let cursor_dollar_pair = matchend(line_start_2_cnum_saved, '\$[^$]\+\$', cursor_dollar_pair)
- endwhile
- " find single $ after cursor_dollar_pair
- let cursor_single_dollar = matchend(line_start_2_cnum_saved, '\$', cursor_dollar_pair)
-
- " if single $ is found
- if cursor_single_dollar >= 0
- " check whether $ is in \(...\), \[...\], or \begin{eq}...\end{eq}
-
- " check current line,
- " search for LatexBox_eq_env_close_patterns: \[ and \(
- let lnum = line('.')
- for i in range(0, (len(s:LatexBox_eq_env_open_patterns)-1))
- call cursor(lnum_saved, cnum_saved)
- let cnum_close = searchpos(''. s:LatexBox_eq_env_close_patterns[i].'', 'cbW', lnum_saved)[1]
- let cnum_open = matchend(line_start_2_cnum_saved, s:LatexBox_eq_env_open_patterns[i], cnum_close)
- if cnum_open >= 0
- let s:eq_dollar_parenthesis_bracket_empty = ''
- let s:eq_pos = cursor_single_dollar - 1
- return 1
- end
- endfor
-
- " check the lines above
- " search for s:LatexBox_doc_structure_patterns, and end-of-math-env
- let lnum -= 1
- while lnum > 0
- let line = getline(lnum)
- if line =~ notcomment . '\(' . s:LatexBox_doc_structure_patterns .
- \ '\|' . '\\end\s*{\(' . g:LatexBox_eq_env_patterns . '\)\*\?}\)'
- " when s:LatexBox_doc_structure_patterns or g:LatexBox_eq_env_patterns
- " are found first, complete math, leave with $ at both sides
- let s:eq_dollar_parenthesis_bracket_empty = '$'
- let s:eq_pos = cursor_single_dollar
- break
- elseif line =~ notcomment . '\\begin\s*{\(' . g:LatexBox_eq_env_patterns . '\)\*\?}'
- " g:LatexBox_eq_env_patterns is found, complete math, remove $
- let s:eq_dollar_parenthesis_bracket_empty = ''
- let s:eq_pos = cursor_single_dollar - 1
- break
- endif
- let lnum -= 1
- endwhile
-
- return 1
- else
- " no $ is found, then search for \( or \[ in current line
- " 1, whether there is \(
- call cursor(lnum_saved, cnum_saved)
- let cnum_parenthesis_close = searchpos('\\)', 'cbW', lnum_saved)[1]
- let cnum_parenthesis_open = matchend(line_start_2_cnum_saved, '\\(', cnum_parenthesis_close)
- if cnum_parenthesis_open >= 0
- let s:eq_dollar_parenthesis_bracket_empty = '\)'
- let s:eq_pos = cnum_parenthesis_open
- return 1
- end
-
- " 2, whether there is \[
- call cursor(lnum_saved, cnum_saved)
- let cnum_bracket_close = searchpos('\\\]', 'cbW', lnum_saved)[1]
- let cnum_bracket_open = matchend(line_start_2_cnum_saved, '\\\[', cnum_bracket_close)
- if cnum_bracket_open >= 0
- let s:eq_dollar_parenthesis_bracket_empty = '\]'
- let s:eq_pos = cnum_bracket_open
- return 1
- end
-
- " not inline math completion
- return 0
- endif
-
-endfunction
-" }}}
-
-" Complete inline euqation{{{
-function! s:LatexBox_inlineMath_completion(regex, ...)
-
- if a:0 == 0
- let file = LatexBox_GetMainTexFile()
- else
- let file = a:1
- endif
-
- if empty(glob(file, 1))
- return ''
- endif
-
- if empty(s:eq_dollar_parenthesis_bracket_empty)
- let inline_pattern1 = '\$\s*\(' . escape(substitute(a:regex[1:], '^\s\+', '', ""), '\.*^') . '[^$]*\)\s*\$'
- let inline_pattern2 = '\\(\s*\(' . escape(substitute(a:regex[1:], '^\s\+', '', ""), '\.*^') . '.*\)\s*\\)'
- else
- let inline_pattern1 = '\$\s*\(' . escape(substitute(a:regex, '^\s\+', '', ""), '\.*^') . '[^$]*\)\s*\$'
- let inline_pattern2 = '\\(\s*\(' . escape(substitute(a:regex, '^\s\+', '', ""), '\.*^') . '.*\)\s*\\)'
- endif
-
-
- let suggestions = []
- let line_num = 0
- for line in readfile(file)
- let line_num = line_num + 1
-
- let suggestions += s:LatexBox_inlineMath_mathlist(line,inline_pattern1 , line_num) + s:LatexBox_inlineMath_mathlist( line,inline_pattern2, line_num)
-
- " search for included files
- let included_file = matchstr(line, '^\\@input{\zs[^}]*\ze}')
- if included_file != ''
- let included_file = LatexBox_kpsewhich(included_file)
- call extend(suggestions, s:LatexBox_inlineMath_completion(a:regex, included_file))
- endif
- endfor
-
- return suggestions
-endfunction
-" }}}
-
-" Search for inline maths {{{
-" search for $ ... $ and \( ... \) in each line
-function! s:LatexBox_inlineMath_mathlist(line,inline_pattern, line_num)
- let col_start = 0
- let suggestions = []
- while 1
- let matches = matchlist(a:line, a:inline_pattern, col_start)
- if !empty(matches)
-
- " show line number of inline math
- let entry = {'word': matches[1], 'menu': '[' . a:line_num . ']'}
-
- if s:eq_dollar_parenthesis_bracket_empty != ''
- let entry = copy(entry)
- let entry.abbr = entry.word
- let entry.word = entry.word . s:eq_dollar_parenthesis_bracket_empty
- endif
- call add(suggestions, entry)
-
- " update col_start
- let col_start = matchend(a:line, a:inline_pattern, col_start)
- else
- break
- endif
- endwhile
-
- return suggestions
-endfunction
-" }}}
-
-" Close Current Environment {{{
-function! s:CloseCurEnv()
- " first, try with \left/\right pairs
- let [lnum, cnum] = searchpairpos('\C\\left\>', '', '\C\\right\>', 'bnW', 'LatexBox_InComment()')
- if lnum
- let line = strpart(getline(lnum), cnum - 1)
- let bracket = matchstr(line, '^\\left\zs\((\|\[\|\\{\||\|\.\)\ze')
- for [open, close] in [['(', ')'], ['\[', '\]'], ['\\{', '\\}'], ['|', '|'], ['\.', '|']]
- let bracket = substitute(bracket, open, close, 'g')
- endfor
- return '\right' . bracket
- endif
-
- " second, try with environments
- let env = LatexBox_GetCurrentEnvironment()
- if env == '\['
- return '\]'
- elseif env == '\('
- return '\)'
- elseif env != ''
- return '\end{' . env . '}'
- endif
- return ''
-endfunction
-" }}}
-
-" Wrap Selection {{{
-function! s:WrapSelection(wrapper)
- keepjumps normal! `>a}
- execute 'keepjumps normal! `<i\' . a:wrapper . '{'
-endfunction
-" }}}
-
-" Wrap Selection in Environment with Prompt {{{
-function! s:PromptEnvWrapSelection(...)
- let env = input('environment: ', '', 'customlist,' . s:SIDWrap('GetEnvironmentList'))
- if empty(env)
- return
- endif
- " LaTeXBox's custom indentation can interfere with environment
- " insertion when environments are indented (common for nested
- " environments). Temporarily disable it for this operation:
- let ieOld = &indentexpr
- setlocal indentexpr=""
- if visualmode() ==# 'V'
- execute 'keepjumps normal! `>o\end{' . env . '}'
- execute 'keepjumps normal! `<O\begin{' . env . '}'
- " indent and format, if requested.
- if a:0 && a:1
- normal! gv>
- normal! gvgq
- endif
- else
- execute 'keepjumps normal! `>a\end{' . env . '}'
- execute 'keepjumps normal! `<i\begin{' . env . '}'
- endif
- exe "setlocal indentexpr=" . ieOld
-endfunction
-" }}}
-
-" List Labels with Prompt {{{
-function! s:PromptLabelList(...)
- " Check if window already exists
- let winnr = bufwinnr(bufnr('LaTeX Labels'))
- if winnr >= 0
- if a:0 == 0
- silent execute winnr . 'wincmd w'
- else
- " Supplying an argument to this function causes toggling instead
- " of jumping to the labels window
- if g:LatexBox_split_resize
- silent exe "set columns-=" . g:LatexBox_split_width
- endif
- silent execute 'bwipeout' . bufnr('LaTeX Labels')
- endif
- return
- endif
-
- " Get label suggestions
- let regexp = input('filter labels with regexp: ', '')
- let labels = s:CompleteLabels(regexp)
-
- let calling_buf = bufnr('%')
-
- " Create labels window and set local settings
- if g:LatexBox_split_resize
- silent exe "set columns+=" . g:LatexBox_split_width
- endif
- silent exe g:LatexBox_split_side g:LatexBox_split_width . 'vnew LaTeX\ Labels'
- let b:toc = []
- let b:toc_numbers = 1
- let b:calling_win = bufwinnr(calling_buf)
- setlocal filetype=latextoc
-
- " Add label entries and jump to the closest section
- for entry in labels
- let number = matchstr(entry['menu'], '^\s*(\zs[^)]\+\ze)')
- let page = matchstr(entry['menu'], '^[^)]*)\s*\[\zs[^]]\+\ze\]')
- let e = {'file': bufname(calling_buf),
- \ 'level': 'label',
- \ 'number': number,
- \ 'text': entry['abbr'],
- \ 'page': page}
- call add(b:toc, e)
- if b:toc_numbers
- call append('$', e['number'] . "\t" . e['text'])
- else
- call append('$', e['text'])
- endif
- endfor
- if !g:LatexBox_toc_hidehelp
- call append('$', "")
- call append('$', "<Esc>/q: close")
- call append('$', "<Space>: jump")
- call append('$', "<Enter>: jump and close")
- call append('$', "s: hide numbering")
- endif
- 0delete _
-
- " Lock buffer
- setlocal nomodifiable
-endfunction
-" }}}
-
-" Change Environment {{{
-function! s:ChangeEnvPrompt()
-
- let [env, lnum, cnum, lnum2, cnum2] = LatexBox_GetCurrentEnvironment(1)
-
- let new_env = input('change ' . env . ' for: ', '', 'customlist,' . s:SIDWrap('GetEnvironmentList'))
- if empty(new_env)
- return
- endif
-
- if new_env == '\[' || new_env == '['
- let begin = '\['
- let end = '\]'
- elseif new_env == '\(' || new_env == '('
- let begin = '\('
- let end = '\)'
- else
- let l:begin = '\begin{' . new_env . '}'
- let l:end = '\end{' . new_env . '}'
- endif
-
- if env == '\[' || env == '\('
- let line = getline(lnum2)
- let line = strpart(line, 0, cnum2 - 1) . l:end . strpart(line, cnum2 + 1)
- call setline(lnum2, line)
-
- let line = getline(lnum)
- let line = strpart(line, 0, cnum - 1) . l:begin . strpart(line, cnum + 1)
- call setline(lnum, line)
- else
- let line = getline(lnum2)
- let line = strpart(line, 0, cnum2 - 1) . l:end . strpart(line, cnum2 + len(env) + 5)
- call setline(lnum2, line)
-
- let line = getline(lnum)
- let line = strpart(line, 0, cnum - 1) . l:begin . strpart(line, cnum + len(env) + 7)
- call setline(lnum, line)
- endif
-endfunction
-
-function! s:GetEnvironmentList(lead, cmdline, pos)
- let suggestions = []
- for entry in g:LatexBox_completion_environments
- let env = entry.word
- if env =~ '^' . a:lead
- call add(suggestions, env)
- endif
- endfor
- return suggestions
-endfunction
-
-function! s:LatexToggleStarEnv()
- let [env, lnum, cnum, lnum2, cnum2] = LatexBox_GetCurrentEnvironment(1)
-
- if env == '\('
- return
- elseif env == '\['
- let begin = '\begin{equation}'
- let end = '\end{equation}'
- elseif env[-1:] == '*'
- let begin = '\begin{' . env[:-2] . '}'
- let end = '\end{' . env[:-2] . '}'
- else
- let begin = '\begin{' . env . '*}'
- let end = '\end{' . env . '*}'
- endif
-
- if env == '\['
- let line = getline(lnum2)
- let line = strpart(line, 0, cnum2 - 1) . l:end . strpart(line, cnum2 + 1)
- call setline(lnum2, line)
-
- let line = getline(lnum)
- let line = strpart(line, 0, cnum - 1) . l:begin . strpart(line, cnum + 1)
- call setline(lnum, line)
- else
- let line = getline(lnum2)
- let line = strpart(line, 0, cnum2 - 1) . l:end . strpart(line, cnum2 + len(env) + 5)
- call setline(lnum2, line)
-
- let line = getline(lnum)
- let line = strpart(line, 0, cnum - 1) . l:begin . strpart(line, cnum + len(env) + 7)
- call setline(lnum, line)
- endif
-endfunction
-" }}}
-
-" Next Charaters Match {{{
-function! s:NextCharsMatch(regex)
- let rest_of_line = strpart(getline('.'), col('.') - 1)
- return rest_of_line =~ a:regex
-endfunction
-" }}}
-
-" Mappings {{{
-inoremap <silent> <Plug>LatexCloseCurEnv <C-R>=<SID>CloseCurEnv()<CR>
-vnoremap <silent> <Plug>LatexWrapSelection :<c-u>call <SID>WrapSelection('')<CR>i
-vnoremap <silent> <Plug>LatexEnvWrapSelection :<c-u>call <SID>PromptEnvWrapSelection()<CR>
-vnoremap <silent> <Plug>LatexEnvWrapFmtSelection :<c-u>call <SID>PromptEnvWrapSelection(1)<CR>
-nnoremap <silent> <Plug>LatexChangeEnv :call <SID>ChangeEnvPrompt()<CR>
-nnoremap <silent> <Plug>LatexToggleStarEnv :call <SID>LatexToggleStarEnv()<CR>
-" }}}
-
-" Commands {{{
-command! LatexLabels call <SID>PromptLabelList()
-" }}}
-
-" vim:fdm=marker:ff=unix:noet:ts=4:sw=4
-
-endif
diff --git a/ftplugin/latex-box/findmain.vim b/ftplugin/latex-box/findmain.vim
deleted file mode 100644
index 6d1a9928..00000000
--- a/ftplugin/latex-box/findmain.vim
+++ /dev/null
@@ -1,66 +0,0 @@
-if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
-
-" LatexBox_GetMainFileName: gets the name of the main file being compiled. {{{
-" Description: returns the full path name of the main file.
-" This function checks for the existence of a .latexmain file
-" which might point to the location of a "main" latex file.
-" If .latexmain exists, then return the full path name of the
-" file being pointed to by it.
-"
-" Otherwise, return the full path name of the current buffer.
-"
-" You can supply an optional "modifier" argument to the
-" function, which will optionally modify the file name before
-" returning.
-" NOTE: From version 1.6 onwards, this function always trims
-" away the .latexmain part of the file name before applying the
-" modifier argument.
-" NOTE: This function is copied from the Latex-Suite project!
-function! LatexBox_GetMainFileName(...)
- if a:0 > 0
- let modifier = a:1
- else
- let modifier = ':p'
- endif
-
- let s:origdir = fnameescape(getcwd())
-
- let dirmodifier = '%:p:h'
- let dirLast = fnameescape(expand(dirmodifier))
- exe 'cd '.dirLast
-
- " move up the directory tree until we find a .latexmain file.
- " TODO: Should we be doing this recursion by default, or should there be a
- " setting?
- while glob('*.latexmain',1) == ''
- let dirmodifier = dirmodifier.':h'
- let dirNew = fnameescape(expand(dirmodifier))
- " break from the loop if we cannot go up any further.
- if dirNew == dirLast
- break
- endif
- let dirLast = dirNew
- exe 'cd '.dirLast
- endwhile
-
- let lheadfile = glob('*.latexmain',1)
- if lheadfile != ''
- " Remove the trailing .latexmain part of the filename... We never want
- " that.
- let lheadfile = fnamemodify(substitute(lheadfile, '\.latexmain$', '', ''), modifier)
- else
- " If we cannot find any main file, just modify the filename of the
- " current buffer.
- let lheadfile = expand('%'.modifier)
- endif
-
- exe 'cd '.s:origdir
-
- " NOTE: The caller of this function needs to escape the file name with
- " fnameescape() . The reason its not done here is that escaping is not
- " safe if this file is to be used as part of an external command on
- " certain platforms.
- return lheadfile
-endfunction
-
-endif
diff --git a/ftplugin/latex-box/folding.vim b/ftplugin/latex-box/folding.vim
deleted file mode 100644
index e24e036d..00000000
--- a/ftplugin/latex-box/folding.vim
+++ /dev/null
@@ -1,382 +0,0 @@
-if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
-
-" Folding support for LaTeX
-
-"
-" Options
-" g:LatexBox_Folding - Turn on/off folding
-" g:LatexBox_fold_text - Turn on/off LatexBox fold text function
-" g:LatexBox_fold_preamble - Turn on/off folding of preamble
-" g:LatexBox_fold_parts - Define parts (eq. appendix, frontmatter) to fold
-" g:LatexBox_fold_sections - Define section levels to fold
-" g:LatexBox_fold_envs - Turn on/off folding of environments
-" g:LatexBox_fold_toc - Turn on/off folding of TOC
-" g:LatexBox_fold_toc_levels - Set max TOC fold level
-"
-" {{{1 Initialize options to default values.
-if !exists('g:LatexBox_Folding')
- let g:LatexBox_Folding=0
-endif
-if !exists('g:LatexBox_fold_text')
- let g:LatexBox_fold_text=1
-endif
-if !exists('g:LatexBox_fold_preamble')
- let g:LatexBox_fold_preamble=1
-endif
-if !exists('g:LatexBox_fold_envs')
- let g:LatexBox_fold_envs=1
-endif
-if !exists('g:LatexBox_fold_envs_force')
- let g:LatexBox_fold_envs_force = []
-endif
-if !exists('g:LatexBox_fold_parts')
- let g:LatexBox_fold_parts=[
- \ "appendix",
- \ "frontmatter",
- \ "mainmatter",
- \ "backmatter"
- \ ]
-endif
-if !exists('g:LatexBox_fold_sections')
- let g:LatexBox_fold_sections=[
- \ "part",
- \ "chapter",
- \ "section",
- \ "subsection",
- \ "subsubsection"
- \ ]
-endif
-if !exists('g:LatexBox_fold_toc')
- let g:LatexBox_fold_toc=0
-endif
-if !exists('g:LatexBox_fold_toc_levels')
- let g:LatexBox_fold_toc_levels=1
-endif
-if !exists('g:LatexBox_fold_automatic')
- let g:LatexBox_fold_automatic=1
-endif
-" }}}1
-
-if g:LatexBox_Folding == 0
- finish
-endif
-
-" {{{1 Set folding options for vim
-setl foldexpr=LatexBox_FoldLevel(v:lnum)
-if g:LatexBox_fold_text == 1
- setl foldtext=LatexBox_FoldText()
-endif
-if g:LatexBox_fold_automatic == 1
- setl foldmethod=expr
-
- "
- " The foldexpr function returns "=" for most lines, which means it can become
- " slow for large files. The following is a hack that is based on this reply to
- " a discussion on the Vim Developer list:
- " http://permalink.gmane.org/gmane.editors.vim.devel/14100
- "
- augroup FastFold
- autocmd!
- autocmd InsertEnter *.tex if !&diff | setlocal foldmethod=manual | endif
- autocmd InsertLeave *.tex if !&diff | setlocal foldmethod=expr | endif
- augroup end
-else
- setl foldmethod=manual
-endif
-
-function! LatexBox_FoldOnDemand()
- setl foldmethod=expr
- normal! zx
- setl foldmethod=manual
-endfunction
-
-command! LatexFold call LatexBox_FoldOnDemand()
-
-" {{{1 LatexBox_FoldLevel help functions
-
-" This function parses the tex file to find the sections that are to be folded
-" and their levels, and then predefines the patterns for optimized folding.
-function! s:FoldSectionLevels()
- " Initialize
- let level = 1
- let foldsections = []
-
- " If we use two or more of the *matter commands, we need one more foldlevel
- let nparts = 0
- for part in g:LatexBox_fold_parts
- let i = 1
- while i < line("$")
- if getline(i) =~ '^\s*\\' . part . '\>'
- let nparts += 1
- break
- endif
- let i += 1
- endwhile
- if nparts > 1
- let level = 2
- break
- endif
- endfor
-
- " Combine sections and levels, but ignore unused section commands: If we
- " don't use the part command, then chapter should have the highest
- " level. If we don't use the chapter command, then section should be the
- " highest level. And so on.
- let ignore = 1
- for part in g:LatexBox_fold_sections
- " For each part, check if it is used in the file. We start adding the
- " part patterns to the fold sections array whenever we find one.
- let partpattern = '^\s*\(\\\|% Fake\)' . part . '\>'
- if ignore
- let i = 1
- while i < line("$")
- if getline(i) =~# partpattern
- call insert(foldsections, [partpattern, level])
- let level += 1
- let ignore = 0
- break
- endif
- let i += 1
- endwhile
- else
- call insert(foldsections, [partpattern, level])
- let level += 1
- endif
- endfor
-
- return foldsections
-endfunction
-
-" {{{1 LatexBox_FoldLevel
-
-" Parse file to dynamically set the sectioning fold levels
-let b:LatexBox_FoldSections = s:FoldSectionLevels()
-
-" Optimize by predefine common patterns
-let s:notbslash = '\%(\\\@<!\%(\\\\\)*\)\@<='
-let s:notcomment = '\%(\%(\\\@<!\%(\\\\\)*\)\@<=%.*\)\@<!'
-let s:envbeginpattern = s:notcomment . s:notbslash . '\\begin\s*{.\{-}}'
-let s:envendpattern = s:notcomment . s:notbslash . '\\end\s*{.\{-}}'
-let s:foldparts = '^\s*\\\%(' . join(g:LatexBox_fold_parts, '\|') . '\)'
-let s:folded = '\(% Fake\|\\\(document\|begin\|end\|paragraph\|'
- \ . 'front\|main\|back\|app\|sub\|section\|chapter\|part\)\)'
-
-function! LatexBox_FoldLevel(lnum)
- " Check for normal lines first (optimization)
- let line = getline(a:lnum)
- if line !~ s:folded
- return "="
- endif
-
- " Fold preamble
- if g:LatexBox_fold_preamble == 1
- if line =~# s:notcomment . s:notbslash . '\s*\\documentclass'
- return ">1"
- elseif line =~# s:notcomment . s:notbslash . '\s*\\begin\s*{\s*document\s*}'
- return "0"
- endif
- endif
-
- " Fold parts (\frontmatter, \mainmatter, \backmatter, and \appendix)
- if line =~# s:foldparts
- return ">1"
- endif
-
- " Fold chapters and sections
- for [part, level] in b:LatexBox_FoldSections
- if line =~# part
- return ">" . level
- endif
- endfor
-
- " Never fold \end{document}
- if line =~# '^\s*\\end{document}'
- return 0
- endif
-
- " Fold environments
- if line =~# s:envbeginpattern && line =~# s:envendpattern
- " If the begin and end pattern are on the same line , do not fold
- return "="
- else
- if line =~# s:envbeginpattern
- if g:LatexBox_fold_envs == 1
- return "a1"
- else
- let env = matchstr(line,'\\begin\*\?{\zs\w*\*\?\ze}')
- if index(g:LatexBox_fold_envs_force, env) >= 0
- return "a1"
- else
- return "="
- endif
- endif
- elseif line =~# s:envendpattern
- if g:LatexBox_fold_envs == 1
- return "s1"
- else
- let env = matchstr(line,'\\end\*\?{\zs\w*\*\?\ze}')
- if index(g:LatexBox_fold_envs_force, env) >= 0
- return "s1"
- else
- return "="
- endif
- endif
- endif
- endif
-
- " Return foldlevel of previous line
- return "="
-endfunction
-
-" {{{1 LatexBox_FoldText help functions
-function! s:LabelEnv()
- let i = v:foldend
- while i >= v:foldstart
- if getline(i) =~ '^\s*\\label'
- return matchstr(getline(i), '^\s*\\label{\zs.*\ze}')
- end
- let i -= 1
- endwhile
- return ""
-endfunction
-
-function! s:CaptionEnv()
- let i = v:foldend
- while i >= v:foldstart
- if getline(i) =~ '^\s*\\caption'
- return matchstr(getline(i), '^\s*\\caption\(\[.*\]\)\?{\zs.\+')
- end
- let i -= 1
- endwhile
- return ""
-endfunction
-
-function! s:CaptionTable()
- let i = v:foldstart
- while i <= v:foldend
- if getline(i) =~ '^\s*\\caption'
- return matchstr(getline(i), '^\s*\\caption\(\[.*\]\)\?{\zs.\+')
- end
- let i += 1
- endwhile
- return ""
-endfunction
-
-function! s:CaptionFrame(line)
- " Test simple variants first
- let caption1 = matchstr(a:line,'\\begin\*\?{.*}{\zs.\+\ze}')
- let caption2 = matchstr(a:line,'\\begin\*\?{.*}{\zs.\+')
-
- if len(caption1) > 0
- return caption1
- elseif len(caption2) > 0
- return caption2
- else
- let i = v:foldstart
- while i <= v:foldend
- if getline(i) =~ '^\s*\\frametitle'
- return matchstr(getline(i),
- \ '^\s*\\frametitle\(\[.*\]\)\?{\zs.\+')
- end
- let i += 1
- endwhile
-
- return ""
- endif
-endfunction
-
-function! LatexBox_FoldText_title()
- let line = getline(v:foldstart)
- let title = 'Not defined'
-
- " Preamble
- if line =~ '\s*\\documentclass'
- return "Preamble"
- endif
-
- " Parts, sections and fakesections
- let sections = '\(\(sub\)*\(section\|paragraph\)\|part\|chapter\)'
- let secpat1 = '^\s*\\' . sections . '\*\?\s*{'
- let secpat2 = '^\s*\\' . sections . '\*\?\s*\['
- if line =~ '\\frontmatter'
- let title = "Frontmatter"
- elseif line =~ '\\mainmatter'
- let title = "Mainmatter"
- elseif line =~ '\\backmatter'
- let title = "Backmatter"
- elseif line =~ '\\appendix'
- let title = "Appendix"
- elseif line =~ secpat1 . '.*}'
- let title = matchstr(line, secpat1 . '\zs.\{-}\ze}')
- elseif line =~ secpat1
- let title = matchstr(line, secpat1 . '\zs.*')
- elseif line =~ secpat2 . '.*\]'
- let title = matchstr(line, secpat2 . '\zs.\{-}\ze\]')
- elseif line =~ secpat2
- let title = matchstr(line, secpat2 . '\zs.*')
- elseif line =~ 'Fake' . sections . ':'
- let title = matchstr(line,'Fake' . sections . ':\s*\zs.*')
- elseif line =~ 'Fake' . sections
- let title = matchstr(line, 'Fake' . sections)
- endif
-
- " Environments
- if line =~ '\\begin'
- " Capture environment name
- let env = matchstr(line,'\\begin\*\?{\zs\w*\*\?\ze}')
-
- " Set caption based on type of environment
- if env == 'frame'
- let label = ''
- let caption = s:CaptionFrame(line)
- elseif env == 'table'
- let label = s:LabelEnv()
- let caption = s:CaptionTable()
- else
- let label = s:LabelEnv()
- let caption = s:CaptionEnv()
- endif
-
- " If no caption found, check for a caption comment
- if caption == ''
- let caption = matchstr(line,'\\begin\*\?{.*}\s*%\s*\zs.*')
- endif
-
- " Create title based on caption and label
- if caption . label == ''
- let title = env
- elseif label == ''
- let title = printf('%-12s%s', env . ':',
- \ substitute(caption, '}\s*$', '',''))
- elseif caption == ''
- let title = printf('%-12s%56s', env, '(' . label . ')')
- else
- let title = printf('%-12s%-30s %21s', env . ':',
- \ strpart(substitute(caption, '}\s*$', '',''),0,34),
- \ '(' . label . ')')
- endif
- endif
-
- return title
-endfunction
-
-" {{{1 LatexBox_FoldText
-function! LatexBox_FoldText()
- let nlines = v:foldend - v:foldstart + 1
- let title = strpart(LatexBox_FoldText_title(), 0, 68)
- let level = ''
-
- " Fold level
- let level = strpart(repeat('-', v:foldlevel-1) . '*',0,3)
- if v:foldlevel > 3
- let level = strpart(level, 1) . v:foldlevel
- endif
- let level = printf('%-3s', level)
-
- return printf('%-3s %-68s #%5d', level, title, nlines)
-endfunction
-
-" {{{1 Footer
-" vim:fdm=marker:ff=unix:ts=4:sw=4
-
-endif
diff --git a/ftplugin/latex-box/latexmk.vim b/ftplugin/latex-box/latexmk.vim
deleted file mode 100644
index ef9ef024..00000000
--- a/ftplugin/latex-box/latexmk.vim
+++ /dev/null
@@ -1,558 +0,0 @@
-if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
-
-" LaTeX Box latexmk functions
-
-" Options and variables {{{
-
-if !exists('g:LatexBox_latexmk_options')
- let g:LatexBox_latexmk_options = ''
-endif
-if !exists('g:LatexBox_latexmk_env')
- let g:LatexBox_latexmk_env = ''
-endif
-if !exists('g:LatexBox_latexmk_async')
- let g:LatexBox_latexmk_async = 0
-endif
-if !exists('g:LatexBox_latexmk_preview_continuously')
- let g:LatexBox_latexmk_preview_continuously = 0
-endif
-if !exists('g:LatexBox_output_type')
- let g:LatexBox_output_type = 'pdf'
-endif
-if !exists('g:LatexBox_autojump')
- let g:LatexBox_autojump = 0
-endif
-if ! exists('g:LatexBox_quickfix')
- let g:LatexBox_quickfix = 1
-endif
-if ! exists('g:LatexBox_personal_latexmkrc')
- let g:LatexBox_personal_latexmkrc = 0
-endif
-
-" }}}
-
-" Process ID management (used for asynchronous and continuous mode) {{{
-
-" A dictionary of latexmk PID's (basename: pid)
-if !exists('g:latexmk_running_pids')
- let g:latexmk_running_pids = {}
-endif
-
-" Set PID {{{
-function! s:LatexmkSetPID(basename, pid)
- let g:latexmk_running_pids[a:basename] = a:pid
-endfunction
-" }}}
-
-" kill_latexmk_process {{{
-function! s:kill_latexmk_process(pid)
- if has('win32')
- silent execute '!taskkill /PID ' . a:pid . ' /T /F'
- else
- if g:LatexBox_latexmk_async
- " vim-server mode
- let pids = []
- let tmpfile = tempname()
- silent execute '!ps x -o pgid,pid > ' . tmpfile
- for line in readfile(tmpfile)
- let new_pid = matchstr(line, '^\s*' . a:pid . '\s\+\zs\d\+\ze')
- if !empty(new_pid)
- call add(pids, new_pid)
- endif
- endfor
- call delete(tmpfile)
- if !empty(pids)
- silent execute '!kill ' . join(pids)
- endif
- else
- " single background process
- silent execute '!kill ' . a:pid
- endif
- endif
- if !has('gui_running')
- redraw!
- endif
-endfunction
-" }}}
-
-" kill_all_latexmk_processes {{{
-function! s:kill_all_latexmk_processes()
- for pid in values(g:latexmk_running_pids)
- call s:kill_latexmk_process(pid)
- endfor
-endfunction
-" }}}
-
-" }}}
-
-" Setup for vim-server {{{
-function! s:SIDWrap(func)
- if !exists('s:SID')
- let s:SID = matchstr(expand('<sfile>'), '\zs<SNR>\d\+_\ze.*$')
- endif
- return s:SID . a:func
-endfunction
-
-function! s:LatexmkCallback(basename, status)
- " Only remove the pid if not in continuous mode
- if !g:LatexBox_latexmk_preview_continuously
- call remove(g:latexmk_running_pids, a:basename)
- endif
- call LatexBox_LatexErrors(a:status, a:basename)
-endfunction
-
-function! s:setup_vim_server()
- if !exists('g:vim_program')
-
- " attempt autodetection of vim executable
- let g:vim_program = ''
- if has('win32')
- " Just drop through to the default for windows
- else
- if match(&shell, '\(bash\|zsh\)$') >= 0
- let ppid = '$PPID'
- else
- let ppid = '$$'
- endif
-
- let tmpfile = tempname()
- silent execute '!ps -o command= -p ' . ppid . ' > ' . tmpfile
- for line in readfile(tmpfile)
- let line = matchstr(line, '^\S\+\>')
- if !empty(line) && executable(line)
- let g:vim_program = line . ' -g'
- break
- endif
- endfor
- call delete(tmpfile)
- endif
-
- if empty(g:vim_program)
- if has('gui_macvim')
- let g:vim_program
- \ = '/Applications/MacVim.app/Contents/MacOS/Vim -g'
- else
- let g:vim_program = v:progname
- endif
- endif
- endif
-endfunction
-" }}}
-
-" Latexmk {{{
-
-function! LatexBox_Latexmk(force)
- " Define often used names
- let basepath = LatexBox_GetBuildBasename(1)
- let basename = fnamemodify(basepath, ':t')
- let texroot = shellescape(LatexBox_GetTexRoot())
- let mainfile = fnameescape(fnamemodify(LatexBox_GetMainTexFile(), ':t'))
-
- " Check if latexmk is installed
- if !executable('latexmk')
- echomsg "Error: LaTeX-Box relies on latexmk for compilation, but it" .
- \ " is not installed!"
- return
- endif
-
- " Check if already running
- if has_key(g:latexmk_running_pids, basepath)
- echomsg "latexmk is already running for `" . basename . "'"
- return
- endif
-
- " Set wrap width in log file
- let max_print_line = 2000
- if has('win32')
- let env = 'set max_print_line=' . max_print_line . ' & '
- elseif match(&shell, '/tcsh$') >= 0
- let env = 'setenv max_print_line ' . max_print_line . '; '
- else
- if fnamemodify(&shell, ':t') ==# 'fish'
- let env = 'set max_print_line ' . max_print_line . '; and '
- else
- let env = 'max_print_line=' . max_print_line
- endif
- endif
-
- " Set environment options
- let env .= ' ' . g:LatexBox_latexmk_env . ' '
-
- " Set latexmk command with options
- if has('win32')
- " Make sure to switch drive as well as directory
- let cmd = 'cd /D ' . texroot . ' && '
- else
- if fnamemodify(&shell, ':t') ==# 'fish'
- let cmd = 'cd ' . texroot . '; and '
- else
- let cmd = 'cd ' . texroot . ' && '
- endif
- endif
- let cmd .= env . ' latexmk'
- if ! g:LatexBox_personal_latexmkrc
- let cmd .= ' -' . g:LatexBox_output_type
- endif
- let cmd .= ' -quiet '
- let cmd .= g:LatexBox_latexmk_options
- if a:force
- let cmd .= ' -g'
- endif
- if g:LatexBox_latexmk_preview_continuously
- let cmd .= ' -pvc'
- endif
- let cmd .= ' -e ' . shellescape('$pdflatex =~ s/ / -file-line-error /')
- let cmd .= ' -e ' . shellescape('$latex =~ s/ / -file-line-error /')
- if g:LatexBox_latexmk_preview_continuously
- let cmd .= ' -e ' . shellescape('$success_cmd = $ENV{SUCCESSCMD}')
- let cmd .= ' -e ' . shellescape('$failure_cmd = $ENV{FAILURECMD}')
- endif
- let cmd .= ' ' . mainfile
-
- " Redirect output to null
- if has('win32')
- let cmd .= ' >nul'
- else
- if fnamemodify(&shell, ':t') ==# 'fish'
- let cmd .= ' >/dev/null ^/dev/null'
- else
- let cmd .= ' &>/dev/null'
- endif
- endif
-
- if g:LatexBox_latexmk_async
- " Check if VIM server exists
- if empty(v:servername)
- echoerr "cannot run latexmk in background without a VIM server"
- echoerr "set g:LatexBox_latexmk_async to 0 to change compiling mode"
- return
- endif
-
- " Start vim server if necessary
- call s:setup_vim_server()
-
- let setpidfunc = s:SIDWrap('LatexmkSetPID')
- let callbackfunc = s:SIDWrap('LatexmkCallback')
- if has('win32')
- let vim_program = substitute(g:vim_program,
- \ 'gvim\.exe$', 'vim.exe', '')
-
- " Define callback to set the pid
- let callsetpid = setpidfunc . '(''' . basepath . ''', %CMDPID%)'
- let vimsetpid = vim_program . ' --servername ' . v:servername
- \ . ' --remote-expr ' . shellescape(callsetpid)
-
- " Define callback after latexmk is finished
- let callback = callbackfunc . '(''' . basepath . ''', %LATEXERR%)'
- let vimcmd = vim_program . ' --servername ' . v:servername
- \ . ' --remote-expr ' . shellescape(callback)
- let scallback = callbackfunc . '(''' . basepath . ''', 0)'
- let svimcmd = vim_program . ' --servername ' . v:servername
- \ . ' --remote-expr ' . shellescape(scallback)
- let fcallback = callbackfunc . '(''' . basepath . ''', 1)'
- let fvimcmd = vim_program . ' --servername ' . v:servername
- \ . ' --remote-expr ' . shellescape(fcallback)
-
- let asyncbat = tempname() . '.bat'
- if g:LatexBox_latexmk_preview_continuously
- call writefile(['setlocal',
- \ 'set T=%TEMP%\sthUnique.tmp',
- \ 'wmic process where (Name="WMIC.exe" AND CommandLine LIKE "%%%TIME%%%") '
- \ . 'get ParentProcessId /value | find "ParentProcessId" >%T%',
- \ 'set /P A=<%T%',
- \ 'set CMDPID=%A:~16% & del %T%',
- \ vimsetpid,
- \ 'set SUCCESSCMD='.svimcmd,
- \ 'set FAILURECMD='.fvimcmd,
- \ cmd,
- \ 'endlocal'], asyncbat)
- else
- call writefile(['setlocal',
- \ 'set T=%TEMP%\sthUnique.tmp',
- \ 'wmic process where (Name="WMIC.exe" AND CommandLine LIKE "%%%TIME%%%") '
- \ . 'get ParentProcessId /value | find "ParentProcessId" >%T%',
- \ 'set /P A=<%T%',
- \ 'set CMDPID=%A:~16% & del %T%',
- \ vimsetpid,
- \ cmd,
- \ 'set LATEXERR=%ERRORLEVEL%',
- \ vimcmd,
- \ 'endlocal'], asyncbat)
- endif
-
- " Define command
- let cmd = '!start /b ' . asyncbat . ' & del ' . asyncbat
- else
- " Define callback to set the pid
- let callsetpid = shellescape(setpidfunc).'"(\"'.basepath.'\",$$)"'
- let vimsetpid = g:vim_program . ' --servername ' . v:servername
- \ . ' --remote-expr ' . callsetpid
-
- " Define callback after latexmk is finished
- let callback = shellescape(callbackfunc).'"(\"'.basepath.'\",$?)"'
- let vimcmd = g:vim_program . ' --servername ' . v:servername
- \ . ' --remote-expr ' . callback
- let scallback = shellescape(callbackfunc).'"(\"'.basepath.'\",0)"'
- let svimcmd = g:vim_program . ' --servername ' . v:servername
- \ . ' --remote-expr ' . scallback
- let fcallback = shellescape(callbackfunc).'"(\"'.basepath.'\",1)"'
- let fvimcmd = g:vim_program . ' --servername ' . v:servername
- \ . ' --remote-expr ' . fcallback
-
- " Define command
- " Note: Here we escape '%' because it may be given as a user option
- " through g:LatexBox_latexmk_options, for instance with
- " g:Latex..._options = "-pdflatex='pdflatex -synctex=1 \%O \%S'"
- if g:LatexBox_latexmk_preview_continuously
- let cmd = vimsetpid . ' ; '
- \ . 'export SUCCESSCMD=' . shellescape(svimcmd) . ' '
- \ . ' FAILURECMD=' . shellescape(fvimcmd) . ' ; '
- \ . escape(cmd, '%')
- else
- let cmd = vimsetpid . ' ; ' . escape(cmd, '%') . ' ; ' . vimcmd
- endif
- let cmd = '! (' . cmd . ') >/dev/null &'
- endif
-
- if g:LatexBox_latexmk_preview_continuously
- echo 'Compiling to ' . g:LatexBox_output_type
- \ . ' with continuous preview.'
- else
- echo 'Compiling to ' . g:LatexBox_output_type . ' ...'
- endif
- silent execute cmd
- else
- if g:LatexBox_latexmk_preview_continuously
- if has('win32')
- let cmd = '!start /b cmd /s /c "' . cmd . '"'
- else
- let cmd = '!' . cmd . ' &'
- endif
- echo 'Compiling to ' . g:LatexBox_output_type . ' ...'
- silent execute cmd
-
- " Save PID in order to be able to kill the process when wanted.
- if has('win32')
- let tmpfile = tempname()
- let pidcmd = 'cmd /c "wmic process where '
- \ . '(CommandLine LIKE "latexmk\%'.mainfile.'\%") '
- \ . 'get ProcessId /value | find "ProcessId" '
- \ . '>'.tmpfile.' "'
- silent execute '! ' . pidcmd
- let pids = readfile(tmpfile)
- let pid = strpart(pids[0], 10)
- let g:latexmk_running_pids[basepath] = pid
- else
- let pid = substitute(system('pgrep -f "perl.*'
- \ . mainfile . '" | head -n 1'),'\D','','')
- let g:latexmk_running_pids[basepath] = pid
- endif
- else
- " Execute command and check for errors
- echo 'Compiling to ' . g:LatexBox_output_type . ' ... (async off!)'
- call system(cmd)
- call LatexBox_LatexErrors(v:shell_error)
- endif
- endif
-
- " Redraw screen if necessary
- if !has("gui_running")
- redraw!
- endif
-endfunction
-" }}}
-
-" LatexmkClean {{{
-function! LatexBox_LatexmkClean(cleanall)
- " Check if latexmk is installed
- if !executable('latexmk')
- echomsg "Error: LaTeX-Box relies on latexmk for compilation, but it" .
- \ " is not installed!"
- return
- endif
-
- let basename = LatexBox_GetBuildBasename(1)
-
- if has_key(g:latexmk_running_pids, basename)
- echomsg "don't clean when latexmk is running"
- return
- endif
-
- if has('win32')
- let cmd = 'cd /D ' . shellescape(LatexBox_GetTexRoot()) . ' & '
- else
- let cmd = 'cd ' . shellescape(LatexBox_GetTexRoot()) . ';'
- endif
- if a:cleanall
- let cmd .= 'latexmk -C '
- else
- let cmd .= 'latexmk -c '
- endif
- let cmd .= shellescape(LatexBox_GetMainTexFile())
- if has('win32')
- let cmd .= ' >nul'
- else
- let cmd .= ' >&/dev/null'
- endif
-
- call system(cmd)
- if !has('gui_running')
- redraw!
- endif
-
- echomsg "latexmk clean finished"
-endfunction
-" }}}
-
-" LatexErrors {{{
-function! LatexBox_LatexErrors(status, ...)
- if a:0 >= 1
- let log = a:1 . '.log'
- else
- let log = LatexBox_GetLogFile()
- endif
-
- cclose
-
- " set cwd to expand error file correctly
- let l:cwd = fnamemodify(getcwd(), ':p')
- execute 'lcd ' . fnameescape(LatexBox_GetTexRoot())
- try
- if g:LatexBox_autojump
- execute 'cfile ' . fnameescape(log)
- else
- execute 'cgetfile ' . fnameescape(log)
- endif
- finally
- " restore cwd
- execute 'lcd ' . fnameescape(l:cwd)
- endtry
-
- " Always open window if started by LatexErrors command
- if a:status < 0
- botright copen
- else
- " Only open window when an error/warning is detected
- if g:LatexBox_quickfix >= 3
- \ ? s:log_contains_error(log)
- \ : g:LatexBox_quickfix > 0
- belowright cw
- if g:LatexBox_quickfix == 2 || g:LatexBox_quickfix == 4
- wincmd p
- endif
- endif
- redraw
-
- " Write status message to screen
- if a:status > 0 || len(getqflist())>1
- if s:log_contains_error(log)
- let l:status_msg = ' ... failed!'
- else
- let l:status_msg = ' ... there were warnings!'
- endif
- else
- let l:status_msg = ' ... success!'
- endif
- echomsg 'Compiling to ' . g:LatexBox_output_type . l:status_msg
- endif
-endfunction
-
-" Redefine uniq() for compatibility with older Vim versions (< 7.4.218)
-function! s:uniq(list)
- if exists('*uniq')
- return uniq(a:list)
- elseif len(a:list) <= 1
- return a:list
- endif
-
- let last_element = get(a:list,0)
- let uniq_list = [last_element]
-
- for i in range(1, len(a:list)-1)
- let next_element = get(a:list, i)
- if last_element == next_element
- continue
- endif
- let last_element = next_element
- call add(uniq_list, next_element)
- endfor
- return uniq_list
-endfunction
-
-function! s:log_contains_error(file)
- let lines = readfile(a:file)
- let lines = filter(lines, 'v:val =~ ''^.*:\d\+: ''')
- let lines = s:uniq(map(lines, 'matchstr(v:val, ''^.*\ze:\d\+:'')'))
- let lines = filter(lines, 'filereadable(fnameescape(v:val))')
- return len(lines) > 0
-endfunction
-" }}}
-
-" LatexmkStatus {{{
-function! LatexBox_LatexmkStatus(detailed)
- if a:detailed
- if empty(g:latexmk_running_pids)
- echo "latexmk is not running"
- else
- let plist = ""
- for [basename, pid] in items(g:latexmk_running_pids)
- if !empty(plist)
- let plist .= '; '
- endif
- let plist .= fnamemodify(basename, ':t') . ':' . pid
- endfor
- echo "latexmk is running (" . plist . ")"
- endif
- else
- let basename = LatexBox_GetBuildBasename(1)
- if has_key(g:latexmk_running_pids, basename)
- echo "latexmk is running"
- else
- echo "latexmk is not running"
- endif
- endif
-endfunction
-" }}}
-
-" LatexmkStop {{{
-function! LatexBox_LatexmkStop(silent)
- if empty(g:latexmk_running_pids)
- if !a:silent
- let basepath = LatexBox_GetBuildBasename(1)
- let basename = fnamemodify(basepath, ':t')
- echoerr "latexmk is not running for `" . basename . "'"
- endif
- else
- let basepath = LatexBox_GetBuildBasename(1)
- let basename = fnamemodify(basepath, ':t')
- if has_key(g:latexmk_running_pids, basepath)
- call s:kill_latexmk_process(g:latexmk_running_pids[basepath])
- call remove(g:latexmk_running_pids, basepath)
- if !a:silent
- echomsg "latexmk stopped for `" . basename . "'"
- endif
- elseif !a:silent
- echoerr "latexmk is not running for `" . basename . "'"
- endif
- endif
-endfunction
-" }}}
-
-" Commands {{{
-
-command! -bang Latexmk call LatexBox_Latexmk(<q-bang> == "!")
-command! -bang LatexmkClean call LatexBox_LatexmkClean(<q-bang> == "!")
-command! -bang LatexmkStatus call LatexBox_LatexmkStatus(<q-bang> == "!")
-command! LatexmkStop call LatexBox_LatexmkStop(0)
-command! LatexErrors call LatexBox_LatexErrors(-1)
-
-if g:LatexBox_latexmk_async || g:LatexBox_latexmk_preview_continuously
- autocmd BufUnload <buffer> call LatexBox_LatexmkStop(1)
- autocmd VimLeave * call <SID>kill_all_latexmk_processes()
-endif
-
-" }}}
-
-" vim:fdm=marker:ff=unix:noet:ts=4:sw=4
-
-endif
diff --git a/ftplugin/latex-box/mappings.vim b/ftplugin/latex-box/mappings.vim
deleted file mode 100644
index b64a88df..00000000
--- a/ftplugin/latex-box/mappings.vim
+++ /dev/null
@@ -1,110 +0,0 @@
-if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
-
-" LaTeX Box mappings
-
-if exists("g:LatexBox_no_mappings")
- finish
-endif
-
-" latexmk {{{
-noremap <buffer> <LocalLeader>ll :Latexmk<CR>
-noremap <buffer> <LocalLeader>lL :Latexmk!<CR>
-noremap <buffer> <LocalLeader>lc :LatexmkClean<CR>
-noremap <buffer> <LocalLeader>lC :LatexmkClean!<CR>
-noremap <buffer> <LocalLeader>lg :LatexmkStatus<CR>
-noremap <buffer> <LocalLeader>lG :LatexmkStatus!<CR>
-noremap <buffer> <LocalLeader>lk :LatexmkStop<CR>
-noremap <buffer> <LocalLeader>le :LatexErrors<CR>
-" }}}
-
-" View {{{
-noremap <buffer> <LocalLeader>lv :LatexView<CR>
-" }}}
-
-" TOC {{{
-noremap <silent> <buffer> <LocalLeader>lt :LatexTOC<CR>
-" }}}
-
-" List of labels {{{
-noremap <silent> <buffer> <LocalLeader>lj :LatexLabels<CR>
-" }}}
-
-" Folding {{{
-if g:LatexBox_Folding == 1
- noremap <buffer> <LocalLeader>lf :LatexFold<CR>
-endif
-" }}}
-
-" Jump to match {{{
-if !exists('g:LatexBox_loaded_matchparen')
- nmap <buffer> % <Plug>LatexBox_JumpToMatch
- vmap <buffer> % <Plug>LatexBox_JumpToMatch
- omap <buffer> % <Plug>LatexBox_JumpToMatch
-endif
-" }}}
-
-" Define text objects {{{
-vmap <buffer> ie <Plug>LatexBox_SelectCurrentEnvInner
-vmap <buffer> ae <Plug>LatexBox_SelectCurrentEnvOuter
-onoremap <buffer> ie :normal vie<CR>
-onoremap <buffer> ae :normal vae<CR>
-vmap <buffer> i$ <Plug>LatexBox_SelectInlineMathInner
-vmap <buffer> a$ <Plug>LatexBox_SelectInlineMathOuter
-onoremap <buffer> i$ :normal vi$<CR>
-onoremap <buffer> a$ :normal va$<CR>
-" }}}
-
-" Jump between sections {{{
-function! s:LatexBoxNextSection(type, backwards, visual)
- " Restore visual mode if desired
- if a:visual
- normal! gv
- endif
-
- " For the [] and ][ commands we move up or down before the search
- if a:type == 1
- if a:backwards
- normal! k
- else
- normal! j
- endif
- endif
-
- " Define search pattern and do the search while preserving "/
- let save_search = @/
- let flags = 'W'
- if a:backwards
- let flags = 'b' . flags
- endif
- let notcomment = '\%(\%(\\\@<!\%(\\\\\)*\)\@<=%.*\)\@<!'
- let pattern = notcomment . '\v\s*\\(' . join([
- \ '(sub)*section',
- \ 'chapter',
- \ 'part',
- \ 'appendix',
- \ '(front|back|main)matter'], '|') . ')>'
- call search(pattern, flags)
- let @/ = save_search
-
- " For the [] and ][ commands we move down or up after the search
- if a:type == 1
- if a:backwards
- normal! j
- else
- normal! k
- endif
- endif
-endfunction
-noremap <buffer> <silent> ]] :call <SID>LatexBoxNextSection(0,0,0)<CR>
-noremap <buffer> <silent> ][ :call <SID>LatexBoxNextSection(1,0,0)<CR>
-noremap <buffer> <silent> [] :call <SID>LatexBoxNextSection(1,1,0)<CR>
-noremap <buffer> <silent> [[ :call <SID>LatexBoxNextSection(0,1,0)<CR>
-vnoremap <buffer> <silent> ]] :<c-u>call <SID>LatexBoxNextSection(0,0,1)<CR>
-vnoremap <buffer> <silent> ][ :<c-u>call <SID>LatexBoxNextSection(1,0,1)<CR>
-vnoremap <buffer> <silent> [] :<c-u>call <SID>LatexBoxNextSection(1,1,1)<CR>
-vnoremap <buffer> <silent> [[ :<c-u>call <SID>LatexBoxNextSection(0,1,1)<CR>
-" }}}
-
-" vim:fdm=marker:ff=unix:noet:ts=4:sw=4
-
-endif
diff --git a/ftplugin/latex-box/motion.vim b/ftplugin/latex-box/motion.vim
deleted file mode 100644
index 7982d04b..00000000
--- a/ftplugin/latex-box/motion.vim
+++ /dev/null
@@ -1,548 +0,0 @@
-if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
-
-" LaTeX Box motion functions
-
-" Motion options {{{
-" Opening and closing patterns
-if !exists('g:LatexBox_open_pats')
- let g:LatexBox_open_pats = [ '\\{','{','\\(','(','\\\[','\[',
- \ '\\begin\s*{.\{-}}', '\\left\s*\%([^\\]\|\\.\|\\\a*\)']
- let g:LatexBox_close_pats = [ '\\}','}','\\)',')','\\\]','\]',
- \ '\\end\s*{.\{-}}', '\\right\s*\%([^\\]\|\\.\|\\\a*\)']
-endif
-" }}}
-
-" HasSyntax {{{
-" s:HasSyntax(syntaxName, [line], [col])
-function! s:HasSyntax(syntaxName, ...)
- let line = a:0 >= 1 ? a:1 : line('.')
- let col = a:0 >= 2 ? a:2 : col('.')
- return index(map(synstack(line, col),
- \ 'synIDattr(v:val, "name") == "' . a:syntaxName . '"'),
- \ 1) >= 0
-endfunction
-" }}}
-
-" Search and Skip Comments {{{
-" s:SearchAndSkipComments(pattern, [flags], [stopline])
-function! s:SearchAndSkipComments(pat, ...)
- let flags = a:0 >= 1 ? a:1 : ''
- let stopline = a:0 >= 2 ? a:2 : 0
- let saved_pos = getpos('.')
-
- " search once
- let ret = search(a:pat, flags, stopline)
-
- if ret
- " do not match at current position if inside comment
- let flags = substitute(flags, 'c', '', 'g')
-
- " keep searching while in comment
- while LatexBox_InComment()
- let ret = search(a:pat, flags, stopline)
- if !ret
- break
- endif
- endwhile
- endif
-
- if !ret
- " if no match found, restore position
- call setpos('.', saved_pos)
- endif
-
- return ret
-endfunction
-" }}}
-
-" Finding Matching Pair {{{
-function! s:FindMatchingPair(mode)
-
- if a:mode =~ 'h\|i'
- 2match none
- elseif a:mode == 'v'
- normal! gv
- endif
-
- if LatexBox_InComment() | return | endif
-
- " open/close pairs (dollars signs are treated apart)
- let dollar_pat = '\$'
- let notbslash = '\%(\\\@<!\%(\\\\\)*\)\@<='
- let notcomment = '\%(\%(\\\@<!\%(\\\\\)*\)\@<=%.*\)\@<!'
- let anymatch = '\('
- \ . join(g:LatexBox_open_pats + g:LatexBox_close_pats, '\|')
- \ . '\|' . dollar_pat . '\)'
-
- let lnum = line('.')
- let cnum = searchpos('\A', 'cbnW', lnum)[1]
- " if the previous char is a backslash
- if strpart(getline(lnum), cnum-2, 1) == '\'
- let cnum = cnum-1
- endif
- let delim = matchstr(getline(lnum), '\C^'. anymatch , cnum - 1)
-
- if empty(delim) || strlen(delim)+cnum-1< col('.')
- if a:mode =~ 'n\|v\|o'
- " if not found, search forward
- let cnum = match(getline(lnum), '\C'. anymatch , col('.') - 1) + 1
- if cnum == 0 | return | endif
- call cursor(lnum, cnum)
- let delim = matchstr(getline(lnum), '\C^'. anymatch , cnum - 1)
- elseif a:mode =~ 'i'
- " if not found, move one char bacward and search
- let cnum = searchpos('\A', 'bnW', lnum)[1]
- " if the previous char is a backslash
- if strpart(getline(lnum), cnum-2, 1) == '\'
- let cnum = cnum-1
- endif
- let delim = matchstr(getline(lnum), '\C^'. anymatch , cnum - 1)
- if empty(delim) || strlen(delim)+cnum< col('.') | return | endif
- elseif a:mode =~ 'h'
- return
- endif
- endif
-
- if delim =~ '^\$'
-
- " match $-pairs
- " check if next character is in inline math
- let [lnum0, cnum0] = searchpos('.', 'nW')
- if lnum0 && s:HasSyntax('texMathZoneX', lnum0, cnum0)
- let [lnum2, cnum2] = searchpos(notcomment . notbslash. dollar_pat, 'nW', line('w$')*(a:mode =~ 'h\|i') , 200)
- else
- let [lnum2, cnum2] = searchpos('\%(\%'. lnum . 'l\%' . cnum . 'c\)\@!'. notcomment . notbslash . dollar_pat, 'bnW', line('w0')*(a:mode =~ 'h\|i') , 200)
- endif
-
- if a:mode =~ 'h\|i'
- execute '2match MatchParen /\%(\%' . lnum . 'l\%' . cnum . 'c\$' . '\|\%' . lnum2 . 'l\%' . cnum2 . 'c\$\)/'
- elseif a:mode =~ 'n\|v\|o'
- call cursor(lnum2,cnum2)
- endif
-
- else
- " match other pairs
- for i in range(len(g:LatexBox_open_pats))
- let open_pat = notbslash . g:LatexBox_open_pats[i]
- let close_pat = notbslash . g:LatexBox_close_pats[i]
-
- if delim =~# '^' . open_pat
- " if on opening pattern, search for closing pattern
- let [lnum2, cnum2] = searchpairpos('\C' . open_pat, '', '\C'
- \ . close_pat, 'nW', 'LatexBox_InComment()',
- \ line('w$')*(a:mode =~ 'h\|i') , 200)
- if a:mode =~ 'h\|i'
- execute '2match MatchParen /\%(\%' . lnum . 'l\%' . cnum
- \ . 'c' . g:LatexBox_open_pats[i] . '\|\%'
- \ . lnum2 . 'l\%' . cnum2 . 'c'
- \ . g:LatexBox_close_pats[i] . '\)/'
- elseif a:mode =~ 'n\|v\|o'
- call cursor(lnum2,cnum2)
- if strlen(close_pat)>1 && a:mode =~ 'o'
- call cursor(lnum2, matchend(getline('.'), '\C'
- \ . close_pat, col('.')-1))
- endif
- endif
- break
- elseif delim =~# '^' . close_pat
- " if on closing pattern, search for opening pattern
- let [lnum2, cnum2] = searchpairpos('\C' . open_pat, '',
- \ '\C\%(\%'. lnum . 'l\%' . cnum . 'c\)\@!'
- \ . close_pat, 'bnW', 'LatexBox_InComment()',
- \ line('w0')*(a:mode =~ 'h\|i') , 200)
- if a:mode =~ 'h\|i'
- execute '2match MatchParen /\%(\%' . lnum2 . 'l\%' . cnum2
- \ . 'c' . g:LatexBox_open_pats[i] . '\|\%'
- \ . lnum . 'l\%' . cnum . 'c'
- \ . g:LatexBox_close_pats[i] . '\)/'
- elseif a:mode =~ 'n\|v\|o'
- call cursor(lnum2,cnum2)
- endif
- break
- endif
- endfor
-
- endif
-endfunction
-
-" Allow to disable functionality if desired
-if !exists('g:LatexBox_loaded_matchparen')
- " Disable matchparen autocommands
- augroup LatexBox_HighlightPairs
- autocmd BufEnter * if !exists("g:loaded_matchparen") || !g:loaded_matchparen | runtime plugin/matchparen.vim | endif
- autocmd BufEnter *.tex 3match none | unlet! g:loaded_matchparen | au! matchparen
- autocmd! CursorMoved *.tex call s:FindMatchingPair('h')
- autocmd! CursorMovedI *.tex call s:FindMatchingPair('i')
- augroup END
-endif
-
-" Use LatexBox'es FindMatchingPair as '%' (enable jump between e.g. $'s)
-nnoremap <silent> <Plug>LatexBox_JumpToMatch :call <SID>FindMatchingPair('n')<CR>
-vnoremap <silent> <Plug>LatexBox_JumpToMatch :call <SID>FindMatchingPair('v')<CR>
-onoremap <silent> <Plug>LatexBox_JumpToMatch v:call <SID>FindMatchingPair('o')<CR>
-
-" }}}
-
-" select inline math {{{
-" s:SelectInlineMath(seltype)
-" where seltype is either 'inner' or 'outer'
-function! s:SelectInlineMath(seltype)
-
- let dollar_pat = '\\\@<!\$'
-
- if s:HasSyntax('texMathZoneX')
- call s:SearchAndSkipComments(dollar_pat, 'cbW')
- elseif getline('.')[col('.') - 1] == '$'
- call s:SearchAndSkipComments(dollar_pat, 'bW')
- else
- return
- endif
-
- if a:seltype == 'inner'
- normal! l
- endif
-
- if visualmode() ==# 'V'
- normal! V
- else
- normal! v
- endif
-
- call s:SearchAndSkipComments(dollar_pat, 'W')
-
- if a:seltype == 'inner'
- normal! h
- endif
-endfunction
-
-vnoremap <silent> <Plug>LatexBox_SelectInlineMathInner
- \ :<C-U>call <SID>SelectInlineMath('inner')<CR>
-vnoremap <silent> <Plug>LatexBox_SelectInlineMathOuter
- \ :<C-U>call <SID>SelectInlineMath('outer')<CR>
-" }}}
-
-" select current environment {{{
-function! s:SelectCurrentEnv(seltype)
- let [env, lnum, cnum, lnum2, cnum2] = LatexBox_GetCurrentEnvironment(1)
- call cursor(lnum, cnum)
- if a:seltype == 'inner'
- if env =~ '^\'
- call search('\\.\_\s*\S', 'eW')
- else
- call search('}\(\_\s*\[\_[^]]*\]\)\?\_\s*\S', 'eW')
- endif
- endif
- if visualmode() ==# 'V'
- normal! V
- else
- normal! v
- endif
- call cursor(lnum2, cnum2)
- if a:seltype == 'inner'
- call search('\S\_\s*', 'bW')
- else
- if env =~ '^\'
- normal! l
- else
- call search('}', 'eW')
- endif
- endif
-endfunction
-vnoremap <silent> <Plug>LatexBox_SelectCurrentEnvInner :<C-U>call <SID>SelectCurrentEnv('inner')<CR>
-vnoremap <silent> <Plug>LatexBox_SelectCurrentEnvOuter :<C-U>call <SID>SelectCurrentEnv('outer')<CR>
-" }}}
-
-" Jump to the next braces {{{
-"
-function! LatexBox_JumpToNextBraces(backward)
- let flags = ''
- if a:backward
- normal h
- let flags .= 'b'
- else
- let flags .= 'c'
- endif
- if search('[][}{]', flags) > 0
- normal l
- endif
- let prev = strpart(getline('.'), col('.') - 2, 1)
- let next = strpart(getline('.'), col('.') - 1, 1)
- if next =~ '[]}]' && prev !~ '[][{}]'
- return "\<Right>"
- else
- return ''
- endif
-endfunction
-" }}}
-
-" Table of Contents {{{
-
-" Special UTF-8 conversion
-function! s:ConvertBack(line)
- let line = a:line
- if exists('g:LatexBox_plaintext_toc')
- "
- " Substitute stuff like '\IeC{\"u}' to plain 'u'
- "
- let line = substitute(line, '\\IeC\s*{\\.\(.\)}', '\1', 'g')
- else
- "
- " Substitute stuff like '\IeC{\"u}' to corresponding unicode symbols
- "
- for [pat, symbol] in s:ConvBackPats
- let line = substitute(line, pat, symbol, 'g')
- endfor
- endif
- return line
-endfunction
-
-function! s:ReadTOC(auxfile, texfile, ...)
- let texfile = a:texfile
- let prefix = fnamemodify(a:auxfile, ':p:h')
-
- if a:0 != 2
- let toc = []
- let fileindices = { texfile : [] }
- else
- let toc = a:1
- let fileindices = a:2
- let fileindices[ texfile ] = []
- endif
-
- for line in readfile(a:auxfile)
- let included = matchstr(line, '^\\@input{\zs[^}]*\ze}')
- if included != ''
- " append the input TOX to `toc` and `fileindices`
- let newaux = prefix . '/' . included
- let newtex = fnamemodify(newaux, ':r') . '.tex'
- call s:ReadTOC(newaux, newtex, toc, fileindices)
- continue
- endif
-
- " Parse statements like:
- " \@writefile{toc}{\contentsline {section}{\numberline {secnum}Section Title}{pagenumber}}
- " \@writefile{toc}{\contentsline {section}{\tocsection {}{1}{Section Title}}{pagenumber}}
- " \@writefile{toc}{\contentsline {section}{\numberline {secnum}Section Title}{pagenumber}{otherstuff}}
-
- let line = matchstr(line,
- \ '\\@writefile{toc}{\\contentsline\s*\zs.*\ze}\s*$')
- if empty(line)
- continue
- endif
-
- let tree = LatexBox_TexToTree(s:ConvertBack(line))
-
- if len(tree) < 3
- " unknown entry type: just skip it
- continue
- endif
-
- " parse level
- let level = tree[0][0]
- " parse page
- if !empty(tree[2])
- let page = tree[2][0]
- else
- let page = ''
- endif
- " parse section number
- let secnum = ''
- let tree = tree[1]
- if len(tree) > 3 && empty(tree[1])
- call remove(tree, 1)
- endif
- if len(tree) > 1 && type(tree[0]) == type("") && tree[0] =~ '^\\\(\(chapter\)\?numberline\|tocsection\)'
- let secnum = LatexBox_TreeToTex(tree[1])
- let secnum = substitute(secnum, '\\\S\+\s', '', 'g')
- let secnum = substitute(secnum, '\\\S\+{\(.\{-}\)}', '\1', 'g')
- let secnum = substitute(secnum, '^{\+\|}\+$', '', 'g')
- call remove(tree, 1)
- endif
- " parse section title
- let text = LatexBox_TreeToTex(tree)
- let text = substitute(text, '^{\+\|}\+$', '', 'g')
- let text = substitute(text, '\m^\\\(no\)\?\(chapter\)\?numberline\s*', '', '')
- let text = substitute(text, '\*', '', 'g')
-
- " add TOC entry
- call add(fileindices[texfile], len(toc))
- call add(toc, {'file': texfile,
- \ 'level': level,
- \ 'number': secnum,
- \ 'text': text,
- \ 'page': page})
- endfor
-
- return [toc, fileindices]
-
-endfunction
-
-function! LatexBox_TOC(...)
-
- " Check if window already exists
- let winnr = bufwinnr(bufnr('LaTeX TOC'))
- " Two types of splits, horizontal and vertical
- let l:hori = "new"
- let l:vert = "vnew"
-
- " Set General Vars and initialize size
- let l:type = g:LatexBox_split_type
- let l:size = 10
-
- " Size detection
- if l:type == l:hori
- let l:size = g:LatexBox_split_length
- elseif l:type == l:vert
- let l:size = g:LatexBox_split_width
- endif
-
- if winnr >= 0
- if a:0 == 0
- silent execute winnr . 'wincmd w'
- else
- " Supplying an argument to this function causes toggling instead
- " of jumping to the TOC window
- if g:LatexBox_split_resize
- silent exe "set columns-=" . l:size
- endif
- silent execute 'bwipeout' . bufnr('LaTeX TOC')
- endif
- return
- endif
- " Read TOC
- let [toc, fileindices] = s:ReadTOC(LatexBox_GetAuxFile(),
- \ LatexBox_GetMainTexFile())
- let calling_buf = bufnr('%')
-
- " Find closest section in current buffer
- let closest_index = s:FindClosestSection(toc,fileindices)
-
- " Create TOC window and set local settings
- if g:LatexBox_split_resize
- silent exe "set columns+=" . l:size
- endif
- silent exe g:LatexBox_split_side l:size . l:type . ' LaTeX\ TOC'
-
- let b:toc = toc
- let b:toc_numbers = 1
- let b:calling_win = bufwinnr(calling_buf)
- setlocal filetype=latextoc
-
- " Add TOC entries and jump to the closest section
- for entry in toc
- call append('$', entry['number'] . "\t" . entry['text'])
- endfor
- if !g:LatexBox_toc_hidehelp
- call append('$', "")
- call append('$', "<Esc>/q: close")
- call append('$', "<Space>: jump")
- call append('$', "<Enter>: jump and close")
- call append('$', "s: hide numbering")
- endif
- 0delete _
-
- execute 'normal! ' . (closest_index + 1) . 'G'
-
- " Lock buffer
- setlocal nomodifiable
-endfunction
-
-" Binary search for the closest section
-" return the index of the TOC entry
-function! s:FindClosestSection(toc, fileindices)
- let file = expand('%:p')
- if !has_key(a:fileindices, file)
- return 0
- endif
-
- let imax = len(a:fileindices[file])
- if imax > 0
- let imin = 0
- while imin < imax - 1
- let i = (imax + imin) / 2
- let tocindex = a:fileindices[file][i]
- let entry = a:toc[tocindex]
- let titlestr = entry['text']
- let titlestr = escape(titlestr, '\')
- let titlestr = substitute(titlestr, ' ', '\\_\\s\\+', 'g')
- let [lnum, cnum] = searchpos('\\' . entry['level'] . '\_\s*{' . titlestr . '}', 'nW')
- if lnum
- let imax = i
- else
- let imin = i
- endif
- endwhile
- return a:fileindices[file][imin]
- else
- return 0
- endif
-endfunction
-
-let s:ConvBackPats = map([
- \ ['\\''A}' , 'Á'],
- \ ['\\`A}' , 'À'],
- \ ['\\^A}' , 'À'],
- \ ['\\¨A}' , 'Ä'],
- \ ['\\"A}' , 'Ä'],
- \ ['\\''a}' , 'á'],
- \ ['\\`a}' , 'à'],
- \ ['\\^a}' , 'à'],
- \ ['\\¨a}' , 'ä'],
- \ ['\\"a}' , 'ä'],
- \ ['\\''E}' , 'É'],
- \ ['\\`E}' , 'È'],
- \ ['\\^E}' , 'Ê'],
- \ ['\\¨E}' , 'Ë'],
- \ ['\\"E}' , 'Ë'],
- \ ['\\''e}' , 'é'],
- \ ['\\`e}' , 'è'],
- \ ['\\^e}' , 'ê'],
- \ ['\\¨e}' , 'ë'],
- \ ['\\"e}' , 'ë'],
- \ ['\\''I}' , 'Í'],
- \ ['\\`I}' , 'Î'],
- \ ['\\^I}' , 'Ì'],
- \ ['\\¨I}' , 'Ï'],
- \ ['\\"I}' , 'Ï'],
- \ ['\\''i}' , 'í'],
- \ ['\\`i}' , 'î'],
- \ ['\\^i}' , 'ì'],
- \ ['\\¨i}' , 'ï'],
- \ ['\\"i}' , 'ï'],
- \ ['\\''{\?\\i }' , 'í'],
- \ ['\\''O}' , 'Ó'],
- \ ['\\`O}' , 'Ò'],
- \ ['\\^O}' , 'Ô'],
- \ ['\\¨O}' , 'Ö'],
- \ ['\\"O}' , 'Ö'],
- \ ['\\''o}' , 'ó'],
- \ ['\\`o}' , 'ò'],
- \ ['\\^o}' , 'ô'],
- \ ['\\¨o}' , 'ö'],
- \ ['\\"o}' , 'ö'],
- \ ['\\''U}' , 'Ú'],
- \ ['\\`U}' , 'Ù'],
- \ ['\\^U}' , 'Û'],
- \ ['\\¨U}' , 'Ü'],
- \ ['\\"U}' , 'Ü'],
- \ ['\\''u}' , 'ú'],
- \ ['\\`u}' , 'ù'],
- \ ['\\^u}' , 'û'],
- \ ['\\¨u}' , 'ü'],
- \ ['\\"u}' , 'ü'],
- \ ['\\`N}' , 'Ǹ'],
- \ ['\\\~N}' , 'Ñ'],
- \ ['\\''n}' , 'ń'],
- \ ['\\`n}' , 'ǹ'],
- \ ['\\\~n}' , 'ñ'],
- \], '[''\C\(\\IeC\s*{\)\?'' . v:val[0], v:val[1]]')
-" }}}
-
-" TOC Command {{{
-command! LatexTOC call LatexBox_TOC()
-command! LatexTOCToggle call LatexBox_TOC(1)
-" }}}
-
-" vim:fdm=marker:ff=unix:noet:ts=4:sw=4
-
-endif
diff --git a/ftplugin/latextoc.vim b/ftplugin/latextoc.vim
deleted file mode 100644
index 5e709f67..00000000
--- a/ftplugin/latextoc.vim
+++ /dev/null
@@ -1,206 +0,0 @@
-if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
-
-" {{{1 Settings
-setlocal buftype=nofile
-setlocal bufhidden=wipe
-setlocal nobuflisted
-setlocal noswapfile
-setlocal nowrap
-setlocal nospell
-setlocal cursorline
-setlocal nonumber
-setlocal nolist
-setlocal tabstop=8
-setlocal cole=0
-setlocal cocu=nvic
-if g:LatexBox_fold_toc
- setlocal foldmethod=expr
- setlocal foldexpr=TOCFoldLevel(v:lnum)
- setlocal foldtext=TOCFoldText()
-endif
-" }}}1
-
-" {{{1 Functions
-" {{{2 TOCClose
-function! s:TOCClose()
- if g:LatexBox_split_resize
- silent exe "set columns-=" . g:LatexBox_split_width
- endif
- bwipeout
-endfunction
-
-" {{{2 TOCToggleNumbers
-function! s:TOCToggleNumbers()
- if b:toc_numbers
- setlocal conceallevel=3
- let b:toc_numbers = 0
- else
- setlocal conceallevel=0
- let b:toc_numbers = 1
- endif
-endfunction
-
-" {{{2 EscapeTitle
-function! s:EscapeTitle(titlestr)
- let titlestr = substitute(a:titlestr, '\\[a-zA-Z@]*\>\s*{\?', '.*', 'g')
- let titlestr = substitute(titlestr, '}', '', 'g')
- let titlestr = substitute(titlestr, '\%(\.\*\s*\)\{2,}', '.*', 'g')
- return titlestr
-endfunction
-
-" {{{2 TOCActivate
-function! s:TOCActivate(close)
- let n = getpos('.')[1] - 1
-
- if n >= len(b:toc)
- return
- endif
-
- let entry = b:toc[n]
-
- let titlestr = s:EscapeTitle(entry['text'])
-
- " Search for duplicates
- "
- let i=0
- let entry_hash = entry['level'].titlestr
- let duplicates = 0
- while i<n
- let i_entry = b:toc[n]
- let i_hash = b:toc[i]['level'].s:EscapeTitle(b:toc[i]['text'])
- if i_hash == entry_hash
- let duplicates += 1
- endif
- let i += 1
- endwhile
- let toc_bnr = bufnr('%')
- let toc_wnr = winnr()
-
- execute b:calling_win . 'wincmd w'
-
- let root = fnamemodify(entry['file'], ':h') . '/'
- let files = [entry['file']]
- for line in filter(readfile(entry['file']), 'v:val =~ ''\\input{''')
- let file = matchstr(line, '{\zs.\{-}\ze\(\.tex\)\?}') . '.tex'
- if file[0] != '/'
- let file = root . file
- endif
- call add(files, file)
- endfor
-
- " Find section in buffer (or inputted files)
- if entry['level'] == 'label'
- let re = '\(\\label\_\s*{\|label\s*=\s*\)' . titlestr . '\>'
- else
- let re = '\\' . entry['level'] . '\_\s*{' . titlestr . '}'
- endif
- call s:TOCFindMatch(re, duplicates, files)
-
- if a:close
- if g:LatexBox_split_resize
- silent exe "set columns-=" . g:LatexBox_split_width
- endif
- execute 'bwipeout ' . toc_bnr
- else
- execute toc_wnr . 'wincmd w'
- endif
-endfunction
-
-" {{{2 TOCFindMatch
-function! s:TOCFindMatch(strsearch,duplicates,files)
- if len(a:files) == 0
- echoerr "Could not find: " . a:strsearch
- return
- endif
-
- call s:TOCOpenBuf(a:files[0])
- let dups = a:duplicates
-
- " Skip duplicates
- while dups > 0
- if search(a:strsearch, 'w')
- let dups -= 1
- else
- break
- endif
- endwhile
-
- if search(a:strsearch, 'w')
- normal! zv
- return
- endif
-
- call s:TOCFindMatch(a:strsearch,dups,a:files[1:])
-endfunction
-
-" {{{2 TOCFoldLevel
-function! TOCFoldLevel(lnum)
- let line = getline(a:lnum)
- let match_s1 = line =~# '^\w\+\s'
- let match_s2 = line =~# '^\w\+\.\w\+\s'
- let match_s3 = line =~# '^\w\+\.\w\+\.\w\+\s'
-
- if g:LatexBox_fold_toc_levels >= 3
- if match_s3
- return ">3"
- endif
- endif
-
- if g:LatexBox_fold_toc_levels >= 2
- if match_s2
- return ">2"
- endif
- endif
-
- if match_s1
- return ">1"
- endif
-
- " Don't fold options
- if line =~# '^\s*$'
- return 0
- endif
-
- " Return previous fold level
- return "="
-endfunction
-
-" {{{2 TOCFoldText
-function! TOCFoldText()
- let parts = matchlist(getline(v:foldstart), '^\(.*\)\t\(.*\)$')
- return printf('%-8s%-72s', parts[1], parts[2])
-endfunction
-
-" {{{2 TOCOpenBuf
-function! s:TOCOpenBuf(file)
-
- let bnr = bufnr(a:file)
- if bnr == -1
- execute 'badd ' . a:file
- let bnr = bufnr(a:file)
- endif
- execute 'buffer! ' . bnr
- normal! gg
-
-endfunction
-
-" }}}1
-
-" {{{1 Mappings
-nnoremap <buffer> <silent> s :call <SID>TOCToggleNumbers()<CR>
-nnoremap <buffer> <silent> q :call <SID>TOCClose()<CR>
-nnoremap <buffer> <silent> <Esc> :call <SID>TOCClose()<CR>
-nnoremap <buffer> <silent> <Space> :call <SID>TOCActivate(0)<CR>
-nnoremap <buffer> <silent> <CR> :call <SID>TOCActivate(1)<CR>
-nnoremap <buffer> <silent> <leftrelease> :call <SID>TOCActivate(0)<cr>
-nnoremap <buffer> <silent> <2-leftmouse> :call <SID>TOCActivate(1)<cr>
-nnoremap <buffer> <silent> G G4k
-nnoremap <buffer> <silent> <Esc>OA k
-nnoremap <buffer> <silent> <Esc>OB j
-nnoremap <buffer> <silent> <Esc>OC l
-nnoremap <buffer> <silent> <Esc>OD h
-" }}}1
-
-" vim:fdm=marker:ff=unix:et:ts=4:sw=4
-
-endif
diff --git a/ftplugin/tex.vim b/ftplugin/tex.vim
new file mode 100644
index 00000000..cb6c503e
--- /dev/null
+++ b/ftplugin/tex.vim
@@ -0,0 +1,29 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+if !get(g:, 'vimtex_enabled', 1)
+ finish
+endif
+
+if exists('b:did_ftplugin')
+ finish
+endif
+let b:did_ftplugin = 1
+
+if !(!get(g:, 'vimtex_version_check', 1)
+ \ || has('nvim-0.1.7')
+ \ || v:version >= 704)
+ echoerr 'Error: vimtex does not support your version of Vim'
+ echom 'Please update to Vim 7.4 or neovim 0.1.7 or later!'
+ echom 'For more info, please see :h vimtex_version_check'
+ finish
+endif
+
+call vimtex#init()
+
+endif
diff --git a/ftplugin/tex_LatexBox.vim b/ftplugin/tex_LatexBox.vim
deleted file mode 100644
index f2a2ea6e..00000000
--- a/ftplugin/tex_LatexBox.vim
+++ /dev/null
@@ -1,37 +0,0 @@
-if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
-
-" LaTeX Box plugin for Vim
-" Maintainer: David Munger
-" Email: mungerd@gmail.com
-" Version: 0.9.6
-
-if exists('*fnameescape')
- function! s:FNameEscape(s)
- return fnameescape(a:s)
- endfunction
-else
- function! s:FNameEscape(s)
- return a:s
- endfunction
-endif
-
-if !exists('b:LatexBox_loaded')
-
- let prefix = expand('<sfile>:p:h') . '/latex-box/'
-
- execute 'source ' . s:FNameEscape(prefix . 'common.vim')
- execute 'source ' . s:FNameEscape(prefix . 'complete.vim')
- execute 'source ' . s:FNameEscape(prefix . 'motion.vim')
- execute 'source ' . s:FNameEscape(prefix . 'latexmk.vim')
- execute 'source ' . s:FNameEscape(prefix . 'folding.vim')
- " added by AH to add main.tex file finder
- execute 'source ' . s:FNameEscape(prefix . 'findmain.vim')
- execute 'source ' . s:FNameEscape(prefix . 'mappings.vim')
-
- let b:LatexBox_loaded = 1
-
-endif
-
-" vim:fdm=marker:ff=unix:noet:ts=4:sw=4
-
-endif
diff --git a/indent/bib.vim b/indent/bib.vim
new file mode 100644
index 00000000..fa326f3a
--- /dev/null
+++ b/indent/bib.vim
@@ -0,0 +1,85 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
+
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+if exists('b:did_indent')
+ finish
+endif
+
+if !get(g:, 'vimtex_indent_bib_enabled', 1) | finish | endif
+
+let b:did_indent = 1
+
+let s:cpo_save = &cpo
+set cpo&vim
+
+setlocal autoindent
+setlocal indentexpr=VimtexIndentBib()
+
+function! VimtexIndentBib() abort " {{{1
+ " Find first non-blank line above the current line
+ let lnum = prevnonblank(v:lnum - 1)
+ if lnum == 0
+ return 0
+ endif
+
+ " Get some initial conditions
+ let ind = indent(lnum)
+ let line = getline(lnum)
+ let cline = getline(v:lnum)
+ let g:test = 1
+
+ " Zero indent for first line of each entry
+ if cline =~# '^\s*@'
+ return 0
+ endif
+
+ " Title line of entry
+ if line =~# '^@'
+ if cline =~# '^\s*}'
+ return 0
+ else
+ return &sw
+ endif
+ endif
+
+ if line =~# '='
+ " Indent continued bib info entries
+ if s:count('{', line) - s:count('}', line) > 0
+ let match = searchpos('.*=\s*{','bcne')
+ return match[1]
+ elseif cline =~# '^\s*}'
+ return 0
+ endif
+ elseif s:count('{', line) - s:count('}', line) < 0
+ if s:count('{', cline) - s:count('}', cline) < 0
+ return 0
+ else
+ return &sw
+ endif
+ endif
+
+ return ind
+endfunction
+
+function! s:count(pattern, line) abort " {{{1
+ let sum = 0
+ let indx = match(a:line, a:pattern)
+ while indx >= 0
+ let sum += 1
+ let indx += 1
+ let indx = match(a:line, a:pattern, indx)
+ endwhile
+ return sum
+endfunction
+
+" }}}1
+
+let &cpo = s:cpo_save
+unlet s:cpo_save
+
+endif
diff --git a/indent/tex.vim b/indent/tex.vim
index f129157e..d0f44282 100644
--- a/indent/tex.vim
+++ b/indent/tex.vim
@@ -1,140 +1,338 @@
if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
-" LaTeX indent file (part of LaTeX Box)
-" Maintainer: David Munger (mungerd@gmail.com)
-
-if exists("g:LatexBox_custom_indent") && ! g:LatexBox_custom_indent
- finish
-endif
-if exists("b:did_indent")
- finish
+" vimtex - LaTeX plugin for Vim
+"
+" Maintainer: Karl Yngve Lervåg
+" Email: karl.yngve@gmail.com
+"
+
+if exists('b:did_indent')
+ finish
endif
+if !get(g:, 'vimtex_indent_enabled', 1) | finish | endif
+
+let b:did_vimtex_indent = 1
let b:did_indent = 1
-setlocal indentexpr=LatexBox_TexIndent()
-setlocal indentkeys=0=\\end,0=\\end{enumerate},0=\\end{itemize},0=\\end{description},0=\\right,0=\\item,0=\\),0=\\],0},o,O,0\\
+let s:cpo_save = &cpoptions
+set cpoptions&vim
-let s:list_envs = ['itemize', 'enumerate', 'description']
-" indent on \left( and on \(, but not on (
-" indent on \left[ and on \[, but not on [
-" indent on \left\{ and on {, but not on \{
-let s:open_pat = '\\\@<!\%(\\begin\|\\left\a\@!\|\\(\|\\\[\|{\)'
-let s:close_pat = '\\\@<!\%(\\end\|\\right\a\@!\|\\)\|\\\]\|}\)'
-let s:list_open_pat = '\\\@<!\\begin{\%(' . join(s:list_envs, '\|') . '\)}'
-let s:list_close_pat = '\\\@<!\\end{\%(' . join(s:list_envs, '\|') . '\)}'
+setlocal autoindent
+setlocal indentexpr=VimtexIndentExpr()
+setlocal indentkeys=!^F,o,O,(,),],},\&,=item,=else,=fi
-function! s:CountMatches(str, pat)
- return len(substitute(substitute(a:str, a:pat, "\n", 'g'), "[^\n]", '', 'g'))
-endfunction
+" Add standard closing math delimiters to indentkeys
+for s:delim in [
+ \ 'rangle', 'rbrace', 'rvert', 'rVert', 'rfloor', 'rceil', 'urcorner']
+ let &l:indentkeys .= ',=' . s:delim
+endfor
-" TexIndent {{{
-function! LatexBox_TexIndent()
+function! VimtexIndentExpr() abort " {{{1
+ return VimtexIndent(v:lnum)
+endfunction
- let lnum_curr = v:lnum
- let lnum_prev = prevnonblank(lnum_curr - 1)
+"}}}
+function! VimtexIndent(lnum) abort " {{{1
+ let s:sw = exists('*shiftwidth') ? shiftwidth() : &shiftwidth
+
+ let [l:prev_lnum, l:prev_line] = s:get_prev_lnum(prevnonblank(a:lnum - 1))
+ if l:prev_lnum == 0 | return indent(a:lnum) | endif
+ let l:line = s:clean_line(getline(a:lnum))
+
+ " Check for verbatim modes
+ if s:is_verbatim(l:line, a:lnum)
+ return empty(l:line) ? indent(l:prev_lnum) : indent(a:lnum)
+ endif
+
+ " Use previous indentation for comments
+ if l:line =~# '^\s*%'
+ return indent(a:lnum)
+ endif
+
+ " Align on ampersands
+ let l:ind = s:indent_amps.check(a:lnum, l:line, l:prev_lnum, l:prev_line)
+ if s:indent_amps.finished | return l:ind | endif
+ let l:prev_lnum = s:indent_amps.prev_lnum
+ let l:prev_line = s:indent_amps.prev_line
+
+ " Indent environments, delimiters, and tikz
+ let l:ind += s:indent_envs(l:line, l:prev_line)
+ let l:ind += s:indent_delims(l:line, a:lnum, l:prev_line, l:prev_lnum)
+ let l:ind += s:indent_conditionals(l:line, a:lnum, l:prev_line, l:prev_lnum)
+ let l:ind += s:indent_tikz(l:prev_lnum, l:prev_line)
+
+ return l:ind
+endfunction
- if lnum_prev == 0
- return 0
- endif
+"}}}
- let line_curr = getline(lnum_curr)
- let line_prev = getline(lnum_prev)
+function! s:get_prev_lnum(lnum) abort " {{{1
+ let l:lnum = a:lnum
+ let l:line = getline(l:lnum)
- " remove \\
- let line_curr = substitute(line_curr, '\\\\', '', 'g')
- let line_prev = substitute(line_prev, '\\\\', '', 'g')
+ while l:lnum != 0 && (l:line =~# '^\s*%' || s:is_verbatim(l:line, l:lnum))
+ let l:lnum = prevnonblank(l:lnum - 1)
+ let l:line = getline(l:lnum)
+ endwhile
- " strip comments
- let line_curr = substitute(line_curr, '\\\@<!%.*$', '', 'g')
- let line_prev = substitute(line_prev, '\\\@<!%.*$', '', 'g')
+ return [
+ \ l:lnum,
+ \ l:lnum > 0 ? s:clean_line(l:line) : '',
+ \]
+endfunction
- " find unmatched opening patterns on previous line
- let n = s:CountMatches(line_prev, s:open_pat)-s:CountMatches(line_prev, s:close_pat)
- let n += s:CountMatches(line_prev, s:list_open_pat)-s:CountMatches(line_prev, s:list_close_pat)
+" }}}1
+function! s:clean_line(line) abort " {{{1
+ return substitute(a:line, '\s*\\\@<!%.*', '', '')
+endfunction
- " reduce indentation if current line starts with a closing pattern
- if line_curr =~ '^\s*\%(' . s:close_pat . '\)'
- let n -= 1
- endif
+" }}}1
+function! s:is_verbatim(line, lnum) abort " {{{1
+ return a:line !~# '\v\\%(begin|end)\{%(verbatim|lstlisting|minted)'
+ \ && vimtex#env#is_inside('\%(lstlisting\|verbatim\|minted\)')[0]
+endfunction
- " compensate indentation if previous line starts with a closing pattern
- if line_prev =~ '^\s*\%(' . s:close_pat . '\)'
- let n += 1
- endif
+" }}}1
+
+let s:indent_amps = {}
+let s:indent_amps.re_amp = g:vimtex#re#not_bslash . '\&'
+let s:indent_amps.re_align = '^[ \t\\]*' . s:indent_amps.re_amp
+function! s:indent_amps.check(lnum, cline, plnum, pline) abort dict " {{{1
+ let self.finished = 0
+ let self.amp_ind = -1
+ let self.init_ind = -1
+ let self.prev_lnum = a:plnum
+ let self.prev_line = a:pline
+ let self.prev_ind = a:plnum > 0 ? indent(a:plnum) : 0
+ if !get(g:, 'vimtex_indent_on_ampersands', 1) | return self.prev_ind | endif
+
+ if a:cline =~# self.re_align
+ \ || a:cline =~# self.re_amp
+ \ || a:cline =~# '^\v\s*\\%(end|])'
+ call self.parse_context(a:lnum, a:cline)
+ endif
+
+ if a:cline =~# self.re_align
+ let self.finished = 1
+ let l:ind_diff =
+ \ strdisplaywidth(strpart(a:cline, 0, match(a:cline, self.re_amp)))
+ \ - strdisplaywidth(strpart(a:cline, 0, match(a:cline, '\S')))
+ return self.amp_ind - l:ind_diff
+ endif
+
+ if self.amp_ind >= 0
+ \ && (a:cline =~# '^\v\s*\\%(end|])' || a:cline =~# self.re_amp)
+ let self.prev_lnum = self.init_lnum
+ let self.prev_line = self.init_line
+ return self.init_ind
+ endif
+
+ return self.prev_ind
+endfunction
+
+" }}}1
+function! s:indent_amps.parse_context(lnum, line) abort dict " {{{1
+ let l:depth = 1
+ let l:init_depth = l:depth
+ let l:lnum = prevnonblank(a:lnum - 1)
+
+ while l:lnum >= 1
+ let l:line = getline(l:lnum)
+
+ if l:line =~# '\v^\s*%(}|\\%(end|]))'
+ let l:depth += 1
+ endif
+
+ if l:line =~# '\v\\begin\s*\{|\\[|\\\w+\{\s*$'
+ let l:depth -= 1
+ if l:depth == l:init_depth - 1
+ let self.init_lnum = l:lnum
+ let self.init_line = l:line
+ let self.init_ind = indent(l:lnum)
+ break
+ endif
+ endif
+
+ if l:depth == 1 && l:line =~# self.re_amp
+ if self.amp_ind < 0
+ let self.amp_ind = strdisplaywidth(
+ \ strpart(l:line, 0, match(l:line, self.re_amp)))
+ endif
+ if l:line !~# self.re_align
+ let self.init_lnum = l:lnum
+ let self.init_line = l:line
+ let self.init_ind = indent(l:lnum)
+ break
+ endif
+ endif
+
+ let l:lnum = prevnonblank(l:lnum - 1)
+ endwhile
+endfunction
- " reduce indentation if current line starts with a closing list
- if line_curr =~ '^\s*\%(' . s:list_close_pat . '\)'
- let n -= 1
- endif
+" }}}1
- " compensate indentation if previous line starts with a closing list
- if line_prev =~ '^\s*\%(' . s:list_close_pat . '\)'
- let n += 1
- endif
+function! s:indent_envs(cur, prev) abort " {{{1
+ let l:ind = 0
- " reduce indentation if previous line is \begin{document}
- if line_prev =~ '\\begin\s*{document}'
- let n -= 1
- endif
+ " First for general environments
+ let l:ind += s:sw*(
+ \ a:prev =~# s:envs_begin
+ \ && a:prev !~# s:envs_end
+ \ && a:prev !~# s:envs_ignored)
+ let l:ind -= s:sw*(
+ \ a:cur !~# s:envs_begin
+ \ && a:cur =~# s:envs_end
+ \ && a:cur !~# s:envs_ignored)
- " less shift for lines starting with \item
- let item_here = line_curr =~ '^\s*\\item'
- let item_above = line_prev =~ '^\s*\\item'
- if !item_here && item_above
- let n += 1
- elseif item_here && !item_above
- let n -= 1
- endif
+ " Indentation for prolonged items in lists
+ let l:ind += s:sw*((a:prev =~# s:envs_item) && (a:cur !~# s:envs_enditem))
+ let l:ind -= s:sw*((a:cur =~# s:envs_item) && (a:prev !~# s:envs_begitem))
+ let l:ind -= s:sw*((a:cur =~# s:envs_endlist) && (a:prev !~# s:envs_begitem))
- return indent(lnum_prev) + n * &sw
+ return l:ind
endfunction
-" }}}
-" Restore cursor position, window position, and last search after running a
-" command.
-function! Latexbox_CallIndent()
- " Save the current cursor position.
- let cursor = getpos('.')
+let s:envs_begin = '\\begin{.*}\|\\\@<!\\\['
+let s:envs_end = '\\end{.*}\|\\\]'
+let s:envs_ignored = '\v'
+ \ . join(get(g:, 'vimtex_indent_ignored_envs', ['document']), '|')
+
+let s:envs_lists = join(get(g:, 'vimtex_indent_lists', [
+ \ 'itemize',
+ \ 'description',
+ \ 'enumerate',
+ \ 'thebibliography',
+ \]), '\|')
+let s:envs_item = '^\s*\\item'
+let s:envs_beglist = '\\begin{\%(' . s:envs_lists . '\)'
+let s:envs_endlist = '\\end{\%(' . s:envs_lists . '\)'
+let s:envs_begitem = s:envs_item . '\|' . s:envs_beglist
+let s:envs_enditem = s:envs_item . '\|' . s:envs_endlist
+
+" }}}1
+function! s:indent_delims(line, lnum, prev_line, prev_lnum) abort " {{{1
+ if s:re_opt.close_indented
+ return s:sw*(s:count(a:prev_line, s:re_open)
+ \ - s:count(a:prev_line, s:re_close))
+ else
+ return s:sw*( max([ s:count(a:prev_line, s:re_open)
+ \ - s:count(a:prev_line, s:re_close), 0])
+ \ - max([ s:count(a:line, s:re_close)
+ \ - s:count(a:line, s:re_open), 0]))
+ endif
+endfunction
- " Save the current window position.
- normal! H
- let window = getpos('.')
- call setpos('.', cursor)
+let s:re_opt = extend({
+ \ 'open' : ['{'],
+ \ 'close' : ['}'],
+ \ 'close_indented' : 0,
+ \ 'include_modified_math' : 1,
+ \}, get(g:, 'vimtex_indent_delims', {}))
+let s:re_open = join(s:re_opt.open, '\|')
+let s:re_close = join(s:re_opt.close, '\|')
+if s:re_opt.include_modified_math
+ let s:re_open .= (empty(s:re_open) ? '' : '\|') . g:vimtex#delim#re.delim_mod_math.open
+ let s:re_close .= (empty(s:re_close) ? '' : '\|') . g:vimtex#delim#re.delim_mod_math.close
+endif
- " Get first non-whitespace character of current line.
- let line_start_char = matchstr(getline('.'), '\S')
+" }}}1
+function! s:indent_conditionals(line, lnum, prev_line, prev_lnum) abort " {{{1
+ if !exists('s:re_cond')
+ let l:cfg = {}
+
+ if exists('g:vimtex_indent_conditionals')
+ let l:cfg = g:vimtex_indent_conditionals
+ if empty(l:cfg)
+ let s:re_cond = {}
+ return 0
+ endif
+ endif
+
+ let s:re_cond = extend({
+ \ 'open': '\v(\\newif\s*)@<!\\if(f|field|name|numequal|thenelse)@!',
+ \ 'else': '\\else\>',
+ \ 'close': '\\fi\>',
+ \}, l:cfg)
+ endif
+
+ if empty(s:re_cond) | return 0 | endif
+
+ if get(s:, 'conditional_opened')
+ if a:line =~# s:re_cond.close
+ silent! unlet s:conditional_opened
+ return a:prev_line =~# s:re_cond.open ? 0 : -s:sw
+ elseif a:line =~# s:re_cond.else
+ return -s:sw
+ elseif a:prev_line =~# s:re_cond.else
+ return s:sw
+ elseif a:prev_line =~# s:re_cond.open
+ return s:sw
+ endif
+ endif
+
+ if a:line =~# s:re_cond.open
+ \ && a:line !~# s:re_cond.close
+ let s:conditional_opened = 1
+ endif
+
+ return 0
+endfunction
- " Get initial tab position.
- let initial_tab = stridx(getline('.'), line_start_char)
+" }}}1
+function! s:indent_tikz(lnum, prev) abort " {{{1
+ if !has_key(b:vimtex.packages, 'tikz') | return 0 | endif
- " Execute the command.
- execute 'normal! =='
+ let l:env_pos = vimtex#env#is_inside('tikzpicture')
+ if l:env_pos[0] > 0 && l:env_pos[0] < a:lnum
+ let l:prev_starts = a:prev =~# s:tikz_commands
+ let l:prev_stops = a:prev =~# ';\s*$'
- " Get tab position difference.
- let difference = stridx(getline('.'), line_start_char) - initial_tab
+ " Increase indent on tikz command start
+ if l:prev_starts && ! l:prev_stops
+ return s:sw
+ endif
- " Set new cursor Y position based on calculated difference.
- let cursor[2] = cursor[2] + difference
+ " Decrease indent on tikz command end, i.e. on semicolon
+ if ! l:prev_starts && l:prev_stops
+ let l:context = join(getline(l:env_pos[0], a:lnum-1), '')
+ return -s:sw*(l:context =~# s:tikz_commands)
+ endif
+ endif
- " Restore the previous window position.
- call setpos('.', window)
- normal! zt
+ return 0
+endfunction
- " Restore the previous cursor position.
- call setpos('.', cursor)
+let s:tikz_commands = '\v\\%(' . join([
+ \ 'draw',
+ \ 'fill',
+ \ 'path',
+ \ 'node',
+ \ 'coordinate',
+ \ 'add%(legendentry|plot)',
+ \ ], '|') . ')'
+
+" }}}1
+
+function! s:count(line, pattern) abort " {{{1
+ if empty(a:pattern) | return 0 | endif
+
+ let l:sum = 0
+ let l:indx = match(a:line, a:pattern)
+ while l:indx >= 0
+ let l:sum += 1
+ let l:match = matchstr(a:line, a:pattern, l:indx)
+ let l:indx += len(l:match)
+ let l:indx = match(a:line, a:pattern, l:indx)
+ endwhile
+ return l:sum
endfunction
-" autocmd to call indent after completion
-" 7.3.598
-if v:version > 703 || (v:version == 703 && has('patch598'))
- augroup LatexBox_Completion
- autocmd!
- autocmd CompleteDone <buffer> call Latexbox_CallIndent()
- augroup END
-endif
+" }}}1
-" vim:fdm=marker:ff=unix:noet:ts=4:sw=4
+let &cpoptions = s:cpo_save
+unlet s:cpo_save
endif
diff --git a/syntax/latextoc.vim b/syntax/latextoc.vim
deleted file mode 100644
index 454651ca..00000000
--- a/syntax/latextoc.vim
+++ /dev/null
@@ -1,13 +0,0 @@
-if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
-
-syntax match helpText /^.*: .*/
-syntax match secNum /^\S\+\(\.\S\+\)\?\s*/ contained conceal
-syntax match secLine /^\S\+\t.\+/ contains=secNum
-syntax match mainSecLine /^[^\.]\+\t.*/ contains=secNum
-syntax match ssubSecLine /^[^\.]\+\.[^\.]\+\.[^\.]\+\t.*/ contains=secNum
-highlight link helpText PreProc
-highlight link secNum Number
-highlight link mainSecLine Title
-highlight link ssubSecLine Comment
-
-endif