[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