" LaTeX plugin for Vim " " Maintainer: Karl Yngve LervÄg " Email: karl.yngve@gmail.com " function! latex#toc#init(initialized) " {{{1 if !g:latex_toc_enabled | return | endif " Define commands command! -buffer VimLatexTocOpen call latex#toc#open() command! -buffer VimLatexTocToggle call latex#toc#toggle() " Define mappings if g:latex_mappings_enabled nnoremap lt :call latex#toc#open() nnoremap lT :call latex#toc#toggle() endif endfunction function! latex#toc#open() " {{{1 " Go to TOC if it already exists let winnr = bufwinnr(bufnr('LaTeX TOC')) if winnr >= 0 silent execute winnr . 'wincmd w' return endif " Store current buffer number and position let calling_buf = bufnr('%') let calling_file = expand('%:p') let calling_line = line('.') " Parse TOC data let toc = s:parse_toc() " Resize vim session if wanted, then create TOC window if g:latex_toc_resize silent exe "set columns+=" . g:latex_toc_width endif silent exe g:latex_toc_split_side g:latex_toc_width . 'vnew LaTeX\ TOC' " Set buffer local variables let b:toc = toc let b:toc_numbers = 1 let b:calling_win = bufwinnr(calling_buf) " Add TOC entries (and keep track of closest index) let index = 0 let closest_index = 0 for entry in toc call append('$', \ printf('%-10s%-140s%s', \ entry.number, \ entry.title, \ s:max_level - entry.level)) let index += 1 if entry.file == calling_file && entry.line <= calling_line let closest_index = index endif endfor " Add help info (if desired) if !g:latex_toc_hide_help call append('$', "") call append('$', "/q: close") call append('$', ": jump") call append('$', ": jump and close") call append('$', "s: hide numbering") endif " Delete empty first line and jump to the closest section 0delete _ call setpos('.', [0, closest_index, 0, 0]) " Set filetype and lock buffer setlocal filetype=latextoc setlocal nomodifiable endfunction function! latex#toc#toggle() " {{{1 if bufwinnr(bufnr('LaTeX TOC')) >= 0 if g:latex_toc_resize silent exe "set columns-=" . g:latex_toc_width endif silent execute 'bwipeout' . bufnr('LaTeX TOC') else call latex#toc#open() silent execute 'wincmd w' endif endfunction " }}}1 " {{{1 TOC variables let s:root = '' let s:max_level = 0 let s:count_matters = 0 " Define dictionary to keep track of TOC numbers let s:number = { \ 'part' : 0, \ 'chapter' : 0, \ 'section' : 0, \ 'subsection' : 0, \ 'subsubsection' : 0, \ 'subsubsubsection' : 0, \ 'current_level' : 0, \ 'preamble' : 0, \ 'frontmatter' : 0, \ 'mainmatter' : 0, \ 'appendix' : 0, \ 'backmatter' : 0, \ } " Map for section hierarchy let s:sec_to_value = { \ '_' : 0, \ 'subsubsubsection' : 1, \ 'subsubsection' : 2, \ 'subsection' : 3, \ 'section' : 4, \ 'chapter' : 5, \ 'part' : 6, \ } " Define regular expressions to match document parts let s:re_input = '\v^\s*\\%(input|include)\s*\{' let s:re_input_file = s:re_input . '\zs[^\}]+\ze}' let s:re_sec = '\v^\s*\\%(part|chapter|%(sub)*section)\*?\s*\{' let s:re_sec_starred = '\v^\s*\\%(part|chapter|%(sub)*section)\*' let s:re_sec_level = '\v^\s*\\\zs%(part|chapter|%(sub)*section)' let s:re_sec_title = s:re_sec . '\zs.{-}\ze\}?$' let s:re_matters = '\v^\s*\\%(front|main|back)matter>' let s:re_structure = '\v^\s*\\((front|main|back)matter|appendix)>' let s:re_structure_match = '\v((front|main|back)matter|appendix)' let s:re_other = { \ 'toc' : { \ 'title' : 'Table of contents', \ 're' : '\v^\s*\\tableofcontents', \ }, \ 'index' : { \ 'title' : 'Alphabetical index', \ 're' : '\v^\s*\\printindex\[?', \ }, \ 'bib' : { \ 'title' : 'Bibliography', \ 're' : '\v^\s*\\%(' \ . 'printbib%(liography|heading)\s*(\{|\[)?' \ . '|begin\s*\{\s*thebibliography\s*\}' \ . '|bibliography\s*\{)', \ }, \ } " }}}1 function! s:parse_toc() " {{{1 let file = g:latex#data[b:latex.id].tex " Store project root for use in recursive file searches let s:root = fnamemodify(file, ':p:h') " Reset TOC numbering call s:number_reset('preamble') " Find max level and number of \*matter commands let s:max_level = 0 let s:count_matters = 0 call s:parse_limits(file) " Parse TOC data return s:parse_file(file) endfunction " }}}1 function! s:parse_limits(file) " {{{1 if !filereadable(a:file) echoerr "Error in latex#toc s:parse_limits" echoerr "File not readable: " . a:file return '' endif for line in readfile(a:file) if line =~# s:re_input call s:parse_limits(s:parse_line_input(line)) elseif line =~# s:re_sec let s:max_level = max([s:max_level, \ s:sec_to_value[matchstr(line, s:re_sec_level)]]) elseif line =~# s:re_matters let s:count_matters += 1 endif endfor endfunction " }}}1 function! s:parse_file(file) " {{{1 " Parses tex file for TOC entries " " The function returns a list of entries. Each entry is a dictionary: " " entry = { " title : "Some title", " number : "3.1.2", " file : /path/to/file.tex, " line : 142, " level : 2, " } if !filereadable(a:file) echoerr "Error in latex#toc s:parse_file" echoerr "File not readable: " . a:file return [] endif let toc = [] let lnum = 0 for line in readfile(a:file) let lnum += 1 " 1. Parse inputs or includes if line =~# s:re_input call extend(toc, s:parse_file(s:parse_line_input(line))) continue endif " 2. Parse preamble if s:number.preamble if line =~# '\v^\s*\\documentclass' call add(toc, { \ 'title' : 'Preamble', \ 'number' : '', \ 'file' : a:file, \ 'line' : lnum, \ 'level' : s:max_level, \ }) continue endif if line =~# '\v^\s*\\begin\{document\}' let s:number.preamble = 0 endif continue endif " 3. Parse document structure (front-/main-/backmatter, appendix) if line =~# s:re_structure call s:number_reset(matchstr(line, s:re_structure_match)) continue endif " 4. Parse \parts, \chapters, \sections, and \subsections if line =~# s:re_sec call add(toc, s:parse_line_sec(a:file, lnum, line)) continue endif " 5. Parse other stuff for other in values(s:re_other) if line =~# other.re call add(toc, { \ 'title' : other.title, \ 'number' : '', \ 'file' : a:file, \ 'line' : lnum, \ 'level' : s:max_level, \ }) continue endif endfor endfor return toc endfunction function! s:parse_line_input(line) " {{{1 let l:file = matchstr(a:line, s:re_input_file) if l:file !~# '.tex$' let l:file .= '.tex' endif " Return full path return s:root . '/' . l:file endfunction function! s:parse_line_sec(file, lnum, line) " {{{1 let title = matchstr(a:line, s:re_sec_title) let level = matchstr(a:line, s:re_sec_level) let starred = a:line =~# s:re_sec_starred ? 1 : 0 let number = s:number_increment(level, starred) return { \ 'title' : title, \ 'number' : number, \ 'file' : a:file, \ 'line' : a:lnum, \ 'level' : s:number.current_level, \ } endfunction " }}}1 function! s:number_reset(part) " {{{1 for key in keys(s:number) let s:number[key] = 0 endfor let s:number[a:part] = 1 endfunction function! s:number_increment(level, starred) " {{{1 " Store current level let s:number.current_level = s:sec_to_value[a:level] " Check if level should be incremented if a:starred return '' endif " Increment numbers if a:level == 'part' let s:number.part += 1 let s:number.chapter = 0 let s:number.section = 0 let s:number.subsection = 0 let s:number.subsubsection = 0 let s:number.subsubsubsection = 0 elseif a:level == 'chapter' let s:number.chapter += 1 let s:number.section = 0 let s:number.subsection = 0 let s:number.subsubsection = 0 let s:number.subsubsubsection = 0 elseif a:level == 'section' let s:number.section += 1 let s:number.subsection = 0 let s:number.subsubsection = 0 let s:number.subsubsubsection = 0 elseif a:level == 'subsection' let s:number.subsection += 1 let s:number.subsubsection = 0 let s:number.subsubsubsection = 0 elseif a:level == 'subsubsection' let s:number.subsubsection += 1 let s:number.subsubsubsection = 0 elseif a:level == 'subsubsubsection' let s:number.subsubsubsection += 1 endif return s:number_print() endfunction function! s:number_print() " {{{1 let number = [ \ s:number.part, \ s:number.chapter, \ s:number.section, \ s:number.subsection, \ s:number.subsubsection, \ s:number.subsubsubsection, \ ] " Remove unused parts while number[0] == 0 call remove(number, 0) endwhile while number[-1] == 0 call remove(number, -1) endwhile " Change numbering in frontmatter, appendix, and backmatter if s:count_matters > 1 \ && (s:number.frontmatter || s:number.backmatter) return "" elseif s:number.appendix let number[0] = nr2char(number[0] + 64) endif return join(number, '.') endfunction " }}}1 " vim: fdm=marker