diff options
| author | Adam Stankiewicz <sheerun@sher.pl> | 2020-04-25 21:30:46 +0200 | 
|---|---|---|
| committer | Adam Stankiewicz <sheerun@sher.pl> | 2020-04-25 21:30:46 +0200 | 
| commit | d757bfd643cc73c2d495355c153ed0257f5d5b47 (patch) | |
| tree | ff210950456938a779d98f6a2ba7321aca512897 | |
| parent | 8ec73a3a8974a62a613680a6b6222a77a7b99546 (diff) | |
| download | vim-polyglot-d757bfd643cc73c2d495355c153ed0257f5d5b47.tar.gz vim-polyglot-d757bfd643cc73c2d495355c153ed0257f5d5b47.zip  | |
Change latex provider to luatex, closes #476
Diffstat (limited to '')
125 files changed, 17908 insertions, 3385 deletions
@@ -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 @@ -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  | 
