From 32b308461fe686297050e18ee9e9a7260ee3990b Mon Sep 17 00:00:00 2001 From: kiryph Date: Wed, 11 Jun 2025 11:54:09 +0200 Subject: [PATCH 1/2] Parse options given to packages with test file --- autoload/vimtex/state/class.vim | 44 +++++++++++++++---- .../test-state/test_parse_package_options.tex | 34 ++++++++++++++ .../test-state/test_parse_package_options.vim | 33 ++++++++++++++ 3 files changed, 103 insertions(+), 8 deletions(-) create mode 100644 test/test-state/test_parse_package_options.tex create mode 100644 test/test-state/test_parse_package_options.vim diff --git a/autoload/vimtex/state/class.vim b/autoload/vimtex/state/class.vim index 39468654925..332eb13557f 100644 --- a/autoload/vimtex/state/class.vim +++ b/autoload/vimtex/state/class.vim @@ -201,16 +201,44 @@ 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 diff --git a/test/test-state/test_parse_package_options.tex b/test/test-state/test_parse_package_options.tex new file mode 100644 index 00000000000..4f533391da2 --- /dev/null +++ b/test/test-state/test_parse_package_options.tex @@ -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} diff --git a/test/test-state/test_parse_package_options.vim b/test/test-state/test_parse_package_options.vim new file mode 100644 index 00000000000..4e39b5906bb --- /dev/null +++ b/test/test-state/test_parse_package_options.vim @@ -0,0 +1,33 @@ +set nocompatible +set runtimepath^=../.. +filetype plugin on + +nnoremap q :qall! + +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() From b463a44b1fd5f08198e0a16726ffc3007b50b755 Mon Sep 17 00:00:00 2001 From: kiryph Date: Wed, 11 Jun 2025 11:53:20 +0200 Subject: [PATCH 2/2] Parse options of documentclass with updated __pprint and test file --- autoload/vimtex/state/class.vim | 47 +++++++++++++++++-- .../test_parse_documentclass_options.tex | 34 ++++++++++++++ .../test_parse_documentclass_options.vim | 39 +++++++++++++++ 3 files changed, 116 insertions(+), 4 deletions(-) create mode 100644 test/test-state/test_parse_documentclass_options.tex create mode 100644 test/test-state/test_parse_documentclass_options.vim diff --git a/autoload/vimtex/state/class.vim b/autoload/vimtex/state/class.vim index 332eb13557f..340192d2eb0 100644 --- a/autoload/vimtex/state/class.vim +++ b/autoload/vimtex/state/class.vim @@ -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( @@ -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 @@ -194,9 +205,37 @@ 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 diff --git a/test/test-state/test_parse_documentclass_options.tex b/test/test-state/test_parse_documentclass_options.tex new file mode 100644 index 00000000000..7874458f71f --- /dev/null +++ b/test/test-state/test_parse_documentclass_options.tex @@ -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} diff --git a/test/test-state/test_parse_documentclass_options.vim b/test/test-state/test_parse_documentclass_options.vim new file mode 100644 index 00000000000..bc27b4cd111 --- /dev/null +++ b/test/test-state/test_parse_documentclass_options.vim @@ -0,0 +1,39 @@ +set nocompatible +set runtimepath^=../.. +filetype plugin on + +nnoremap q :qall! + +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()