[TxMt] LaTeX Watch

Robin Houston robin.houston at gmail.com
Mon Apr 2 10:04:33 UTC 2007


Excellent, I'm pleased that someone is interested! About your problem:

1. What error message do you get, when you try and run gv from the Terminal?
2. Have you actually installed gv? If so, where is it installed?

I'll attach the current version of LaTeX Watch, which has better error
handling.

Robin
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macromates.com/textmate/attachments/20070402/93a9001c/attachment.html>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: Watch document.tmcommand
Type: application/octet-stream
Size: 1553 bytes
Desc: not available
URL: <http://lists.macromates.com/textmate/attachments/20070402/93a9001c/attachment.tmcommand>
-------------- next part --------------
#! /usr/bin/perl

# LaTeX Watch, version 1.1
#	- 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)

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";

# 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