Skip to content

Instantly share code, notes, and snippets.

@Teggy
Created October 9, 2012 11:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Teggy/3858038 to your computer and use it in GitHub Desktop.
Save Teggy/3858038 to your computer and use it in GitHub Desktop.
Live LaTeX previewing with Chocolat and Skim.app
/*!
* Experimental Chocolat mixins
* Copyright(c) 2012 Torsten Grust <torsten.grust@gmail.com>
*/
var fs = require('fs');
var path = require('path');
var spawn = require('child_process').spawn;
var flashLaTeX = require('./tex.js').flashLaTeX;
/* Storage keys */
var isActive = '__LaTeX_Flash_active__';
var retryPending = '__LaTeX_Flash_pending__';
var isTeXing = '__LaTeX_Flash_texing__';
/* retry TeXing after this many ms */
var retryInterval = 100;
/* LaTeX Flash working directory */
var flashWorkingDirectory = '.flashchoc';
/* toggle Flash LaTeX, initialization */
Hooks.addMenuItem('Actions/Teggy/Start LaTeX Flash', 'control-option-command-f',
function() {
var doc = Document.current(),
transient = Storage.transient();
/* only continue for LaTeX buffers */
if ('latex.tex.text'.indexOf(doc.rootScope()))
return;
/* only continue if buffer is associated with a file */
if (!doc.path())
return;
var texDirectory = path.dirname(doc.path()); /* directory component of LaTeX source */
transient.set(isTeXing, false);
/* toggle LaTeX flash activity */
var active = !transient.get(isActive);
transient.set(isActive, active);
Alert.notify({
title: 'LaTeX Flash',
subtitle: active ? 'is running' : 'has been stopped',
body: 'Working directory is ' + texDirectory
});
if (!active)
return;
/* establish LaTeX Flash working directory if needed */
var flashDirectory = path.join(texDirectory, flashWorkingDirectory);
if (!(fs.existsSync(flashDirectory) &&
fs.statSync(flashDirectory).isDirectory())) {
fs.mkdirSync(flashDirectory);
}
/* first trigger LaTeX format recompilation, then flash for the first time */
flash(true);
flash(false);
})
/* trigger on-the-fly LaTeX recompilation */
function flash(ini) {
var doc = Document.current(),
transient = Storage.transient(),
error;
/* only continue if LaTeX Flash is active */
if (!transient.get(isActive))
return null;
/* only continue for LaTeX buffers */
if ('latex.tex.text'.indexOf(doc.rootScope()))
return null;
/* only continue if buffer is associated with a file */
if (!doc.path())
return null;
var texDirectory = path.dirname(doc.path()); /* directory component of LaTeX source */
/* only continue if no other TeX run is active */
if (transient.get(isTeXing))
{
if (transient.get(retryPending))
return null;
transient.set(retryPending, true);
/* retry in a little while ... */
setTimeout(function() { transient.set(retryPending, false); flash(ini); },
retryInterval);
return null;
}
Recipe.run(function(recipe) {
var lineNo,
error;
lineNo = ini ?
0 :
recipe.lineIndexesForCharacterRange(recipe.selection).location + 1;
flashLaTeX(transient, doc.text, texDirectory, lineNo);
})
}
/* Flash LaTeX support: on-the-fly recompilation
(initial code written during ICFP 2012, Copenhagen)
*/
Hooks.onInsertText(function(c, editor) {
/* trigger LaTeX recompilation */
flash(false);
})
/*!
* Chocolate mixin for LaTeX Flash
* Copyright(c) 2012 Torsten Grust <torsten.grust@gmail.com>
*
* Initial code written during ICFP 2012, Copenhagen
*/
/* spawning the TeX process */
var child_process = require('child_process');
/* file I/O */
var fs = require('fs');
/* path handling */
var path = require('path');
/* Haskell-style array processing */
var prelude = require('./lib/prelude');
/* LaTeX Flash working directory */
var flashWorkingDirectory = '.flashchoc';
var isTeXing = '__LaTeX_Flash_texing__';
var flashLaTeX;
exports.flashLaTeX = flashLaTeX = function(transient, source, texDirectory, lineNo) {
var flashDirectory = path.join(texDirectory, flashWorkingDirectory);
var preamble_body = source.split('\\begin{document}'),
preamble = preamble_body[0],
body = preamble_body[1];
var begin_document = prelude.lines(preamble).length,
inside_preamble = lineNo < begin_document;
if (inside_preamble) {
preamble = preamble +
'\\usepackage{flashmate}' +
'\\latexdump' +
'\\begin{document}\\end{document}';
fs.writeFileSync(path.join(flashDirectory, 'preamble.tex'), preamble);
if (transient.get(isTeXing))
return;
transient.set(isTeXing, true);
return child_process.execFile(
'/usr/texbin/pdfetex',
['-ini',
'-jobname=choc',
'-output-directory', flashDirectory,
'-interaction', 'batchmode',
'&pdflatex',
'\\input', path.join(flashDirectory, 'preamble')
],
{ cwd: texDirectory,
timeout: 10000
},
function(error, stdout, stderr) {
transient.set(isTeXing, false);
if (!error) {
body = '{\\Large\\ttfamily Recompiling \\LaTeX{} preamble\\dots}\n';
runLaTeXviewPDF(transient, body, texDirectory, 1);
}
});
}
else {
var cursor = lineNo - begin_document, /* cursor = 0 if on \begin{document} line */
sections = body.split(/\\section\{|\\chapter\{/); /* array of sections */
var section_breaks =
prelude.scanl(function(b, s) { return b + prelude.lines(s).length - 1},
0,
sections);
/* determine top and bottom line of cursor's section */
var section =
prelude.head(
prelude.filter(function(s) { return (s[0] <= cursor && cursor < s[1]) },
prelude.zip(section_breaks,
prelude.drop(1, section_breaks))));
if (section) {
var top = prelude.max(0, section[0]),
bot = section[1];
/* extract body slice of affected section */
body = prelude.unlines(prelude.lines(body).slice(top, bot));
return runLaTeXviewPDF(transient, body, texDirectory, cursor - top);
}
}
}
/* compile TeX body and view PDF */
function runLaTeXviewPDF(transient, body, texDirectory, lineNo) {
var flashDirectory = path.join(texDirectory, flashWorkingDirectory);
fs.writeFileSync(path.join(flashDirectory, 'body.tex'),
'\\begin{document}\n' +
body +
'\n\\end{document}\n');
if (transient.get(isTeXing))
return;
transient.set(isTeXing, true);
return child_process.execFile(
'/usr/texbin/pdfetex',
['-synctex=1',
'-shell-escape',
'-output-format', 'pdf',
'-jobname=body',
'-output-directory', flashDirectory,
'-interaction', 'nonstopmode',
'-fmt', path.join(flashDirectory, 'choc'),
path.join(flashDirectory, 'body')
],
{ cwd: texDirectory,
timeout: 10000
},
function(error, stdout, stderr) {
transient.set(isTeXing, false);
if (!error) {
child_process.execFile(
'/Applications/Skim.app/Contents/SharedSupport/displayline',
['-r', '-b', '-g',
lineNo.toString(),
'body.pdf',
'body.tex'
],
{ cwd: flashDirectory }
);
}
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment