Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 79 additions & 12 deletions autoload/vimtex/state/class.vim
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ function! vimtex#state#class#new(opts) abort " {{{1
\ ? vimtex#parser#preamble(l:new.tex, {'root' : l:new.root})
\ : []

let l:new.documentclass = s:parse_documentclass(l:preamble)
let [l:new.documentclass, l:new.documentclass_options] =
\ s:parse_documentclass(l:preamble)
let l:new.packages = s:parse_packages(l:preamble)
let l:new.graphicspath = s:parse_graphicspath(l:preamble, l:new.root)
let l:new.glossaries = s:parse_glossaries(
Expand Down Expand Up @@ -74,6 +75,16 @@ function! s:vimtex.__pprint() abort dict " {{{1
call add(l:items, ['document class', self.documentclass])
endif

if exists('self.documentclass_options')
let l:string = join(map(sort(keys(self.documentclass_options)),
\ {_, key -> key.."="..
\ (self.documentclass_options[key]== v:true ? 'true' :
\ self.documentclass_options[key] == v:false ? 'false' :
\ self.documentclass_options[key])
\ }))
call add(l:items, ['document class options', l:string])
endif

if !empty(self.packages)
call add(l:items, ['packages', join(sort(keys(self.packages)))])
endif
Expand Down Expand Up @@ -194,23 +205,79 @@ endfunction


function! s:parse_documentclass(preamble) abort " {{{1
let l:preamble_lines = filter(copy(a:preamble), {_, x -> x !~# '^\s*%'})
return matchstr(join(l:preamble_lines, ''),
\ '\\documentclass[^{]*{\zs[^}]\+\ze}')
" Remove EOL comments and then join
let l:preamble_joined = join(map(copy(a:preamble),
\ {_, x -> split(x..' ', '%')[0]}), '')

let l:docclass = matchstr(l:preamble_joined, '\\documentclass[^{]*{\zs[^}]\+\ze}')

let l:docclass_options = {}
let l:unparsed = matchstr(l:preamble_joined, '\\documentclass[^\[]*\[\zs[^\]]\+\ze\]')
for l:el in map(split(l:unparsed, ','), {_, x -> trim(x)})
if l:el == ''
" Empty option
continue
elseif l:el =~ '='
" Key-value option
let [l:key, l:value] = map(split(l:el, '='), {_, x -> trim(x)} )

if l:value ==? 'true'
let l:docclass_options[l:key] = v:true
elseif l:value ==? 'false'
let l:docclass_options[l:key] = v:false
else
let l:docclass_options[l:key] = l:value
endif

else
" Key-only option
let l:docclass_options[l:el] = v:true
endif
endfor

return [l:docclass, l:docclass_options]
endfunction

" }}}1
function! s:parse_packages(preamble) abort " {{{1
let l:usepackages = filter(copy(a: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, {_, x -> split(matchstr(x, l:pat), '\s*,\s*')})
" Remove EOL comments and then join
let l:preamble_joined = join(map(copy(a:preamble),
\ {_, x -> split(x..' ', '%')[0]}), '')

let l:pat = g:vimtex#re#not_comment . g:vimtex#re#not_bslash
\ . '\v\\%(usep|RequireP)ackage\s*%(\[([^[\]]*)\])?\s*\{\s*\zs%([^{}]+\S)\ze\s*\}'
" Regex:
" - Match contains package name(s)
" - First submatch contains package options
let l:parsed = {}
for l:packages in l:usepackages
for l:package in l:packages
let l:parsed[l:package] = {}
for l:el in matchstrlist([l:preamble_joined], pat, #{submatches:v:true})
let l:packages = map(split(l:el['text'], ','), {_, x -> trim(x)})
let l:options = {}
if l:el['submatches'][0] != ''
for l:el in map(split(l:el['submatches'][0], ','), {_, x -> trim(x)})
if l:el == ''
" Empty option
continue
elseif l:el =~ '='
" Key-value option
let [l:key, l:value] = map(split(l:el, '='), {_, x -> trim(x)} )

if l:value ==? 'true'
let l:options[l:key] = v:true
elseif l:value ==? 'false'
let l:options[l:key] = v:false
else
let l:options[l:key] = l:value
endif

else
" Key-only option
let l:options[l:el] = v:true
endif
endfor
endif
for l:pkg in l:packages
let l:parsed[l:pkg] = l:options
endfor
endfor

Expand Down
34 changes: 34 additions & 0 deletions test/test-state/test_parse_documentclass_options.tex
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
% Leading comments here

\documentclass[% Options of scrbook
%
% draft,
fontsize=12pt,
% smallheadings,
headings=big,
english,
paper=a4,
twoside,
open=right,
DIV=14,
BCOR=20mm,
headinclude=false,
footinclude=false,
mpinclude=false,
% pagesize,
titlepage,
parskip=half,
headsepline,
chapterprefix=false,
appendixprefix=Appendix,
appendixwithprefixline=true,
bibliography=totoc,
toc=graduated,
numbers=noenddot,
]{scrbook}

\begin{document}

Hello, world!

\end{document}
39 changes: 39 additions & 0 deletions test/test-state/test_parse_documentclass_options.vim
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
set nocompatible
set runtimepath^=../..
filetype plugin on

nnoremap q :qall!<cr>

call vimtex#log#set_silent()

silent edit test_parse_documentclass_options.tex

if empty($INMAKE) | finish | endif

call assert_equal('scrbook', b:vimtex.documentclass)

let s:options = {
\ 'fontsize': '12pt',
\ 'headings': 'big',
\ 'english': v:true,
\ 'paper': 'a4',
\ 'twoside': v:true,
\ 'open': 'right',
\ 'DIV': '14',
\ 'BCOR': '20mm',
\ 'headinclude': v:false,
\ 'footinclude': v:false,
\ 'mpinclude': v:false,
\ 'titlepage': v:true,
\ 'parskip': 'half',
\ 'headsepline': v:true,
\ 'chapterprefix': v:false,
\ 'appendixprefix': 'Appendix',
\ 'appendixwithprefixline': v:true,
\ 'bibliography': 'totoc',
\ 'toc': 'graduated',
\ 'numbers': 'noenddot',
\ }
call assert_equal(s:options, b:vimtex.documentclass_options)

call vimtex#test#finished()
34 changes: 34 additions & 0 deletions test/test-state/test_parse_package_options.tex
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
\RequirePackage[debrief]{silence}
\documentclass{article}

\usepackage[main=german,english]{babel}

\usepackage[acronyms]{glossaries}
\usepackage[record,style=long]{glossaries-extra}

\usepackage[% comment
backend=biber,
style=numeric-comp,
maxcitenames=99,
% doi=false, % commented option
url=false,
giveninits=true,
]{biblatex}

\usepackage[notes, useibid]{biblatex-chicago} % with space

% Issue in VimTeX 2.16 from January 2025:
% Following multi-line syntax of \usepackage is not detected.
\usepackage{
amsmath,
tikz
}

% Invalid syntax but parsed anyway
\usepackage[draft]{package1, package2} % packages with a shared option

\begin{document}

Hello, world!

\end{document}
33 changes: 33 additions & 0 deletions test/test-state/test_parse_package_options.vim
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
set nocompatible
set runtimepath^=../..
filetype plugin on

nnoremap q :qall!<cr>

call vimtex#log#set_silent()

silent edit test_parse_package_options.tex

if empty($INMAKE) | finish | endif

let s:packages = {
\ 'glossaries-extra': {'style': 'long', 'record': v:true},
\ 'biblatex': {
\ 'backend': 'biber',
\ 'url': v:false,
\ 'giveninits': v:true,
\ 'style': 'numeric-comp',
\ 'maxcitenames': '99'
\ },
\ 'glossaries': {'acronyms': v:true},
\ 'tikz': {},
\ 'babel' : {'main': 'german', 'english': v:true},
\ 'silence': {'debrief': v:true},
\ 'package1': {'draft': v:true},
\ 'biblatex-chicago': {'notes': v:true, 'useibid': v:true},
\ 'amsmath': {},
\ 'package2': {'draft': v:true}
\ }
call assert_equal(s:packages, b:vimtex.packages)

call vimtex#test#finished()
Loading