[TxMt] LaTeX Watch
Robin Houston
robin.houston at gmail.com
Mon Apr 2 13:09:37 UTC 2007
Okay, I guess a lot of people might want to use Fink to install gv, so I've
added /sw/bin to the path in the script. However, if that were the only
problem, you ought to get an error dialog from my script. I guess the reason
nothing happened at all is that you didn't set the executable bit on the
script.
I attach a new version (1.2) which ought to solve these problems. If it
still doesn't work, then go into the Bundle Editor, click on the watch
command, and change "Output: Discard" to "Output: Create New Document". Then
try it again — you should then get a document containing relevant error
messages.
Thanks for testing!
Robin
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macromates.com/textmate/attachments/20070402/6ede630f/attachment.html>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: Watch document.tmcommand
Type: application/octet-stream
Size: 2175 bytes
Desc: not available
URL: <http://lists.macromates.com/textmate/attachments/20070402/6ede630f/attachment.tmcommand>
-------------- next part --------------
#! /usr/bin/perl
# LaTeX Watch, version 1.2
# - by Robin Houston, March 2007.
# Changes
# 1.1:
# - Include $! in error message if ps_viewer fails to start
# - run etex in batchmode
# - deal sensibly with compilation errors (don't just quit, offer to show log)
# - use 'gv -scale 1' (x 1.414) instead of '-scale 2' (x 2)
#
# 1.2:
# - Add Fink path (/sw/bin) to $PATH
# - Improved error handling in the command; also don't assume this script is executable.
use strict;
use warnings;
use POSIX;
use File::Copy 'copy';
# Debugging flag (currently 0 to disable, or 1 to enable)
use constant DEBUG => 0;
# Add teTeX path
$ENV{PATH} .= ":/usr/local/teTeX/bin/".`/usr/local/teTeX/bin/highesttexbin.pl`;
# Add TextMate support path
$ENV{PATH} .= ":$ENV{TM_SUPPORT_PATH}/bin";
# Add Fink path
$ENV{PATH} .= ":/sw/bin";
# PostScript viewer
my @ps_viewer = qw(gv -spartan -scale 1 -nocenter -antialias -nowatch);
# Location of CocoaDialog binary
my $CocoaDialog = "$ENV{TM_SUPPORT_PATH}/bin/CocoaDialog.app/Contents/MacOS/CocoaDialog";
# Explain what's happening (if we're debugging)
sub debug_msg {
system($CocoaDialog, "msgbox",
"--button1" => "OK",
"--text" => $_[0])
if DEBUG;
}
# Display an error dialog and exit with exit-code 1
sub fail {
my ($message, $explanation) = @_;
system($CocoaDialog, "msgbox",
"--button1" => "Cancel",
"--title" => "LaTeX Watch error",
"--text" => "Error: $message",
"--informative-text" => "$explanation.");
exit(1)
}
sub fail_unless_system {
system(@_);
if ($? == -1) {
fail("Failed to execute $_[0]",
"The command '@_' failed to execute: $!");
}
elsif ($? & 127) {
fail("Command failed",
"The command '@_' caused $_[0] to die with signal ".($? & 127));
}
elsif ($? >>= 8) {
fail("Command failed",
"The command '@_' failed (error code $?)");
}
}
my $filepath = $ENV{TM_FILEPATH};
fail("File not saved", "You must save the file before it can be watched")
if !defined($filepath);
my ($wd, $name);
if ($filepath =~ m!(.*)/!) {
$wd = $1;
my $fullname = $';
if ($fullname =~ /\.tex$/) {
$name = $`;
}
else {
fail("Filename doesn't end in .tex",
"The filename ($fullname) does not end with the .tex extension");
}
}
else {
fail("Path does not contain /", "The file path ($filepath) does not contain a '/'");
}
if (! -W $wd) {
fail("Directory not writeable", "I can't write to the directory $wd");
}
my ($preamble, $viewer_pid);
# Clean up if we're interrupted or die
sub clean_up {
debug_msg("Cleaning up");
unlink(map("$wd/.$name.$_", qw(ini fmt tex dvi ps bbl log watcher_pid)));
kill (2, $viewer_pid) if defined $viewer_pid;
}
END { clean_up() }
$SIG{INT} = $SIG{TERM} = sub { exit(0) };
my $mtime;
while(1) {
my $current_mtime = -M $filepath;
if (!defined($current_mtime)) {
fail("Failed to get modification time",
"I failed to find the modification time of the file '$filepath': $!");
}
if (!defined($mtime) or $current_mtime < $mtime) {
debug_msg("Reloading file");
$mtime = $current_mtime;
reload();
compile() and view();
}
if (defined($viewer_pid) and waitpid($viewer_pid, POSIX::WNOHANG)) {
my $r = $?;
debug_msg("Viewer appears to have been closed ($r). Exiting.");
if ($r & 127) {
fail("Viewer failed",
"The PostScript viewer died with signal ".($r & 127));
}
elsif ($r >>= 8) {
fail("Viewer failed",
"The PostScript viewer exited with an error (error code $r)");
}
exit;
}
sleep(1);
}
sub reload {
open (my $f, "<", $filepath)
or fail ("Failed to open file",
"I couldn't open the file '$filepath' for reading: $!");
local $/ = "\\begin{document}";
my $new_preamble = <$f>;
chomp ($new_preamble)
or fail ("No \\begin{document} found",
"I couldn't find the command \\begin{document} in your file");
if ($new_preamble ne $preamble) {
debug_msg("Preamble has changed. Regenerating format.");
regenerate_format($new_preamble);
}
undef $/;
save_body(<$f>);
close $f
or fail ("Failed to close file",
"I got an error closing the file '$filepath': $!");
}
sub regenerate_format {
($preamble) = @_;
open (my $ini, ">", "$wd/.$name.ini")
or fail("Failed to create file",
"The file '$wd/.$name.ini' could not be opened for writing: $!");
print $ini ($preamble, "\n\\dump\n");
close $ini
or fail("Failed to close file",
"The file '$wd/.$name.ini' gave an error on closing: $!");
copy("$wd/$name.bbl", "$wd/.$name.bbl"); # Ignore errors
fail_unless_system("etex", "-ini",
-interaction => "batchmode",
"&latex",
"$wd/.$name.ini");
}
sub save_body {
open (my $f, ">", "$wd/.$name.tex")
or fail("Failed to create file",
"I couldn't create the file '$wd/.$name.tex': $!");
print $f ("\\begin{document}\n", @_);
close($f)
or fail("Failed to close file",
"I got an error on closing the file '$wd/.$name.tex': $!");
}
sub compile {
copy("$wd/$name.bbl", "$wd/.$name.bbl"); # Ignore errors
copy("$wd/$name.aux", "$wd/.$name.aux"); # Ignore errors
my @tex_command = ("etex",
-interaction => "batchmode",
"&.$name", "$wd/.$name.tex");
system(@tex_command);
if ($? == -1) {
fail("Failed to execute tex",
"The command '@tex_command' failed to execute: $!");
}
elsif ($? & 127) {
fail("Command failed",
"The command '@tex_command' caused $tex_command[0] to die with signal ".($? & 127));
}
elsif ($? >>= 8) {
if ($? == 1) {
# Probably an error in the document
offer_to_show_log();
return;
}
else {
fail("Command failed",
"The command '@tex_command' exited with unexpected error code $?");
}
}
else {
# Success!
rename("$wd/.$name.aux", "$wd/$name.aux");
fail_unless_system("dvips", "$wd/.$name.dvi", "-o");
return 1;
}
}
sub offer_to_show_log {
pipe (my $rh, my $wh);
if (my $pid = fork()) {
# Parent
my $button = <$rh>;
waitpid($pid, 0);
if ($?) {
# If we failed to show the dialog, there's not much sense
# in trying to put up another dialog to explain what happened!
# Reluctantly ignore it.
debug_msg("Failed to offer to show log");
}
elsif ($button == 1) {
# OK button pressed
fail_unless_system("mate", "$wd/.$name.log");
}
}
else {
close(STDOUT);
open(STDOUT, ">&", $wh); # Talk to the pipe!
exec($CocoaDialog, "ok-msgbox",
"--title" => "LaTeX Watch: compilation error",
"--text" => "Error compiling $name.tex",
"--informative-text" => "TeX gave an error compiling the file. Shall I show the log?");
# If there's an error, just exit with a non-zero code.
debug_msg("Child process failed to offer to show log.");
POSIX::_exit(2); # Use _exit so we don't trigger cleanup code.
}
}
sub view {
if (defined($viewer_pid)) {
kill(1, $viewer_pid)
or fail("Failed to signal viewer",
"I failed to signal the PostScript viewer (PID $viewer_pid) to reload: $!");
}
else {
$viewer_pid = start_viewer("$wd/.$name.ps");
}
}
sub start_viewer {
my ($ps_file) = @_;
my $pid = fork();
if ($pid) {
# In parent
return $pid;
}
else {
# In child
POSIX::setsid(); # detach from terminal
close STDOUT; open(STDOUT, ">", "/dev/null");
close STDERR; open(STDERR, ">", "/dev/null");
debug_msg("Starting PostScript viewer ($$)");
exec(@ps_viewer, $ps_file);
fail("Failed to start PostScript viewer",
"I failed to run the PostScript viewer (@ps_viewer): $!");
}
}
More information about the textmate
mailing list