Wednesday, March 05, 2008

Fixing Vim-LaTeX Compiler Error Messages

UPDATE: Another interesting option is to use rubber, a Python-based LaTeX wrapper. Among lots of other things, it sanitizes both error and warning messages in a format that's easy to use by Vim.
UPDATE: Most recent implementations of LaTeX can be told to generate sanitized error (but not warning!!) messages automatically. See Additionally, Martin Sander has put together a Python version of the vimlatex script.

NOTE: Personally, I use vimlatex and I do not instruct LaTeX to sanitize its error messages internally.

Quick Links:
  • vimlatex — LaTeX wrapper that sanitizes error messages for Vim
  • vimlatexpipe — a piped version
  • vimlatex.py — Martin Sander's Python alternative to the vimlatex shell script

If you use Vim-LaTeX, you've probably noticed that sometimes errors will cause Vim to open up a completely unrelated file on top of your current file. This is Vim's way of bringing you to the source of the error, but it parsed the latex return text improperly.

The problem is that LaTeX status messages keep track of which file is being used with parentheses over multiple lines. So, in order to figure out where an error is, Vim has to match opening parentheses with closing parentheses over multiple lines. Unfortunately, Vim's compiler error message features cannot do this if multiple parentheses are on one line.

So, I shamelessly borrowed an solution from Luc Hermitte's VIM Macros. Specifically, I was inspired by the vim-tex.sh, which pipes TeX output through a filter that corrects any problems that could confuse Vim. However, I didn't like that his script needed to create a temporary file, so I then borrowed a shell script tip off of a UNIX forum that copies file descriptors around so that actual disk files aren't needed.

The result is vimlatex, a shell script that post-processes TeX output so that it's in a form that Vim likes. To use it, just put it in your PATH and make it executable (i.e., chmod it 0755) and then add vimlatex in front of any compiler command you have. For example, if you have a compiler line in your .vimrc or in your Makefile that starts with latex, insert vimlatex (and a space) in front of the latex (i.e., change latex to vimlatex latex).

Alternatively, any output piped to vimlatexpipe will get properly sanitized for Vim too.

9 comments:

Martin Sander said...

vimlatexpipe don't need to be a shell script, you can save one fork():

just use:
#!/bin/sed -f

# ...

/[0-9][0-9]*$/{N;s/\n\([0-9][0-9]*\)/\1/;}
/[0-9][0-9]*$/{N;s/\n\([0-9][0-9]*\)/\1/;}
s/(/\'$'\n''(/g
s/)/)\'$'\n''/g

I hope there are no typos, this blogger entry window is extremely small..

Ted said...

It's true that executing the pipe this way saves you a little bit in latency (one execution, but not any fewer processes), but there's not much additional gain after that. Additionally, my point in showing it as a line you could execute on the CLI was that you could load that entire CLI line into your vimrc, if you wanted, as part of your latex execution.

But it's a good point that you don't need to use the shell just to exec a single command. However, I have a feeling that most people will use the "vimlatex" method over the "vimlatexpipe" method.

Martin said...

You're probably right that people will use vimlatex, given the fact that vimlatexpipe does not work as expected.

I don't have a "hello world" example, but using the pipe, vim still jumps to scrarctl.cls instead of the location in my file, although to me it seems like vimlatex and vimlatexpipe give the same output.

Anyway, vimlatex works, thanks a lot!

Ted said...

Martin -- how are you using vimlatexpipe? Also, are you using MY vimlatexpipe or yours? In looking back at yours, I see that you're still using escape elements that REQUIRE the use of the bash shell.

Also, remember that the return code of a pipeline is the return code of the last thing in the pipeline. So, vimlatexpipe eats the return code of the latex. That's why the script in "vimlatex" is so complicated -- it has been specially crafted to save the return code and yet still pipe things to sed.

Now, IIRC, Vim responds to text it finds in the compiler/make log rather than looking at return codes. However, it's possible that it acts differently because of the return code issue.

All of that being said, I have a feeling the problem has to do with the use of sed directly (rather than using the shell to process the command line).

ofenerci said...

It is nice to read your posts about vimlatex suite.

Please keep going!

Regards,
Ozhan

Gijs van Oort said...

The following tex-file does not work with your code... The problem is that an overfull hbox warning is given, and within that warning there is a ')' which screws up the pushing/popping of file names. Could you think of an easy way to circumvent it?

\documentclass{report}
\begin{document}

An error, correctly displayed with the file name right
\aNonExistingCommand

A very long formula which generates a nasty overfull hbox warning: Notice the `)':
\begin{equation}
\mbox{\hspace{35cm}} )
\end{equation}

And now another error which is displayed without the filename:
\anotherNonExistingCommand
\end{document}

Ted said...

Gijs -- without having vim-latex be able to parse TeX (or at least math environments), it's difficult to think of a way to deal with that. That is, it would be hard to show that any fix to vim-latex wouldn't miss other errors that happen to occur within math environments.

Have you tried the modifications mentioned in the update? That is, for catastrophic errors like these, you can have LaTeX generate "normal" compiler messages. Those normal compiler messages can be parsed by Vim.

Additionally, rubber (which is also mentioned in the updates) is a nice Python wrapper that MAY be able to handle this case more gracefully (it parses error messages too).

Gijs van Oort said...

Ted -- Thanks for your answer. I did try the -file-line-error switch, but it only works for error --- not for warnings.

I might try rubber (read about it) but I prefer having simple solutions that do not need such additional packages.

A not-so-nice but probably working solution could be to check for "^Overfull \\hbox" strings and then ignore all subsequent lines until you find a line that is shorter than the MaxLineLength (ignore that too because that is the last line of the overfull hbox-message). Success rate = (maxLineLength-1)/maxLineLength \approx 1.

But for now, chose to use the following work-around (at the moment I'm working on a real document suffering from the problem) which just suppresses the warning.

%% in preamble:
% Suppressing overfull hbox problems
\newlength\suppressoverfullhboxlength
\newcommand\suppressoverfullhbox[2]{%
\par%
\setlength\suppressoverfullhboxlength{1.1\textwidth}%
\addtolength{\suppressoverfullhboxlength}{#1}%
\makebox[0pt]{\hspace{0.5\suppressoverfullhboxlength}\parbox[l]{\suppressoverfullhboxlength}{#2}}%
}

%%in text:
\suppressoverfullhboxlength{655pt}{
\begin{equation}
\mbox{\hspace{35cm}} )
\end{equation}
}

When I finish writing the document, I will remove the \suppress.... and solve the overfull hbox problem.

Ted said...

OK. I read your previous comment too quickly. Yes, your problem involves the warning. Again, rubber might handle these things better, and so you could try using it as a frontend to LaTeX.

On a different note, are you sure that you couldn't use, say... \phantom{} in an {align} environment instead of what you're doing now? Usually when I want to align parentheses in math environments perfectly, I use \phantom (and things like \mathrel, \mathord, \mathop, etc. to get the spacing right) to provide the appropriate displacement.

Additionally, the mathtools package (which is an upgrade to amsmath and will someday be included in the next version of LaTeX to deprecate amsmath) has nice features that allow you to (for example) do a \left( on one line and a \right) on a second line.

Anyway, now that I understand your problem, I think there probably are OK solutions that could be added to vimlatex to handle this specific case... but I just don't have time right now to implement and test them. (and it sounds like you have a passable working solution at the moment)

Good luck. Again, try rubber (and looking at other ways to do what you're doing).