4

I want to define a Vim command that will install all LaTeX packages used in a .tex file.

To install packages I can do sudo tlmgr install package1 package2 package3 etc. in the command line.

So now I need a way to get the names of all packages used in the file and use them with tlmgr.

It would be even better if I don't have to leave the buffer to type the sudo password, but that's not strictly necessary.


The packages are added like so: \usepackage{whatever}.

I don't know if this is the best way to do it, but I'm doing this to match the lines that call packages:

/^\\usepackage\(\[.*\]\)\?{\(.*\)}$

But it matches the whole line. Then I added that second pair of escaped parenthesis hoping to capture just the name of the package… but now I have no idea how to call tlmgr using just those names.

7

To avoid leaving the buffer for entering the password, you can use sudo's -S flag:

 -S, --stdin
             Write the prompt to the standard error and read the password
             from the standard input instead of using the terminal device.
             The password must be followed by a newline character.

Then your function could look like:

function! InstallPackages()
    let winview = winsaveview()
    call inputsave()
    let cmd = ['sudo -S tlmgr install']
    %call add(cmd, matchstr(getline('.'), '\\usepackage\(\[.*\]\)\?{\zs.*\ze\}'))
    echomsg join(cmd)
    let pass = inputsecret('Enter sudo password:') . "\n"
    echo system(join(cmd), pass)
    call inputrestore()
    call winrestview(winview)
endfunction

Since the second argument to system is passed as standard input, we can use Vim's inputsecret() function to read the password and send it to sudo (but we need to add the trailing newline ourselves, or sudo will wait around for it).

| improve this answer | |
  • It does not search for the pattern \usepackage in the whole file, or am I missing something? – Christian Brabandt Jun 8 '16 at 14:31
  • @ChristianBrabandt %call ... getline(.) - I'm calling matchstr on each line. – muru Jun 8 '16 at 14:33
  • Awesome, thank you! Just two things: it should be call inputsave() there in the function's second line. And the second was my mistake, the command is actually tlmgr install. I changed those two lines and it works like a charm! – dbmrq Jun 8 '16 at 19:44
  • @danielbmarques my bad, corrected. – muru Jun 8 '16 at 19:44
3

I'm not sure because I don't have the tlmgr shell command to test it, but you could try this:

command! InstallTexPackages call s:install_tex_packages()
function! s:install_tex_packages() abort
    let winview = winsaveview()
    let packages = []
    sil! %s/^\\usepackage{\zs.*\ze}$/\=add(packages, submatch(0))/gn
    exe '!sudo tlmgr' join(packages)
    call winrestview(winview)
endfunction

It installs the :InstallTexPackages command which calls the s:install_tex_packages() function.

The pattern used to search for the packages names is ^\\usepackage{\zs.*\ze}$ which should match foo, bar, baz in the following buffer:

\usepackage{foo}
\usepackage{bar}
\usepackage{baz}

The atoms \zs and \ze allow you to use the text before and after the packages names as anchors, and not include them in the match.

The names are stored in the list packages, and the substitution command :s is called to progressively populate the list.

Each time a package name is found, :s evaluates (\=) the expression add(packages, submatch(0)). add() adds an item to a list. Here the list is packages and the item is submatch(0), which is the text which has just been matched.

No substitution is made thanks to the n flag (:h :s_flags).

To be able to evaluate an expression with the n flag, your Vim version needs to be newer than 7.3.627. If it's not, you would have to replace the substitution command with this:

call cursor(1, 1)
let matchline = search('^\\usepackage{\zs.*\ze}$', 'cW')
while matchline
    let packages += [matchstr(getline(matchline), '^\\usepackage{\zs.*\ze}$')]
    let matchline = search('^\\usepackage{\zs.*\ze}$', 'W')
endwhile
| improve this answer | |
  • What happened? Why did you delete this? :( – muru Jun 8 '16 at 12:20
  • @muru Sorry, I realised that the answer had too many flaws. I don't know the syntax of tlmgr, i think but not sure that you have to add the argument install after it. The substitution command or the while loop are necessary only if there're several matches per line. But in this case there can only be one match at most per line. So your syntax :%call add(…) is better, because it should work on more versions of Vim, while being shorter. Finally, your answer allows the user to stay in Vim, thanks to inputsecret() and system(). – user9433424 Jun 8 '16 at 12:37
  • @muru So, all in all, I found this answer not very good. I undeleted it, in case someone finds something useful in it. – user9433424 Jun 8 '16 at 12:37
  • I wouldn't worry too much about tlmgr syntax, I'm sure OP can figure that out. :) But its always nice to have multiple ways to tackle a problem. – muru Jun 8 '16 at 12:41
  • I accepted muru's answer because of the things you mentioned, but I loved your explanations, I didn't know about \zs and \ze. Also you're right, I forgot to add the install argument. – dbmrq Jun 8 '16 at 19:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.