I finally bit the bullet and rewrote the "texify" command so as to provide better feedback during compilation. The latest version of the plugin behaves as follows: as soon as you invoke the "texify" command (bound to ctrl+alt+t
by default), a quick panel is displayed, initially showing the exact texify
command issued. When compilation finishes, errors and warnings are displayed, followed by the contents of the log file. Previously, the quick panel was only shown upon completion of the texify
command, so one was left wondering whether compilation had actually started upon hitting ctrl+alt+t
. The new behavior is a lot more informative and user-friendly. As was the case previously, you can click on any error message containing a line number to jump to the corresponding line in the source file. The new code also has other niceties related to error detection; I will discuss them in a later post.
Figuring out how to display some text in the quick panel, leave the latter open while an external command runs, and then add more text to the quick panel took me on an interesting, if lengthy trip into Python threading land. Suppose you try the following code:
class PanelTestCommand(sublimeplugin.WindowCommand): def run(self, window, args): args = ["wait 5 seconds..."] window.showQuickPanel("panel", "", args) print "Wait" import time time.sleep(5) args.append("Done!") print "Done" window.showQuickPanel("panel", "", args)
Update: WordPress seems to kill spacing at the beginning of a line… please pretend the above listing and the one below are correctly indented 🙂
Update 2: Actually, there is a nice, easy way to post source code: the sourcecode
shortcode! See here for details. The only issue seems to be that quotes are transformed into HTML entities, so when you get back your post from WordPress, it looks a bit messy. I am also worried about round-tripping. But, I’ll look into this later.
For non-SublimeText people, you define a command by subclassing sublimeplugin.WindowCommand
and redefining the run
method. The showQuickPanel
method takes three arguments, the last one being a list of strings to be displayed. The print
statement displays text in the console, which is useful for debugging purposes. The rest of the code should be self-explanatory; the 5-second wait is a stand-in for a blocking system call (e.g. to texify
). Unfortunately, when you invoke this command, nothing happens for 5 seconds, after which (a) all output is shown in the console, and (b) the quick panel is displayed.
This reply by the Sublime Text author, Jon Skinner, to a post of mine in the tech support forum pointed me in the right direction. Sublime Text works asynchronously, and apparently buffers output as needed. So, to get the intended behavior, you need to do things in a somewhat roundabout way:
showQuickPanel
from the thread, via the sublime.setTimeout
method.This reminds me of how you deal with the event dispatch thread in Java Swing; I guess something similar is going on. The bottom line is, I had to learn the bare minimum about threading in Python, and then take care of certain annoyances. This is the relevant portion of the current implementation of the “texify” command:
class TexifyCommand(sublimeplugin.WindowCommand): def run(self, window, args): [omitted: set things up] view = window.activeView() texFile, texExt = os.path.splitext(view.fileName()) [omitted: create texify command line] pContents = ["Texify executing command:", cmd] window.showQuickPanel("texify", "", pContents) class CmdThread ( threading.Thread ): def __init__ (self, cmd, pContent, texFile, window): self.cmd = cmd self.pContent = pContent self.texFile = texFile self.qp = window.showQuickPanel threading.Thread.__init__ ( self ) def run ( self ): [omitted: set things up] p = Popen(cmd, stdout=PIPE, stderr=STDOUT, shell=True) stdoutdata, stderrdata = p.communicate() pContent = [omitted: define content to be displayed] sublime.setTimeout(functools.partial(self.qp,"texify","gotoTeXError", pContent), 0) CmdThread(cmd, pContents, texFile, window).start()
Let’s take a close look. When the command is invoked, we do some housecleaning, and then create the texify
command line from the name of the file in the current buffer (details omitted); this is stored in the variable named cmd
. We then display this information in the quick panel.
Next, we use the Python threading module. We must subclass threading.Thread
; all the action takes place in the run
method, which sets things up, invokes the texify command, and then uses sublime.setTimeout
to display the results via showQuickPanel
.
While this description is correct, it overlooks an important fact: the CmdThread
class does not have access to variables such as cmd
or window
, and hence to the window.showQuickPanel
method. To address this, we use the __init__
method to save copies of the objects we need—including the window.showQuickPanel
method, which is stored in self.qp
.
But there is one more wrinkle. The setTimeout
method accepts two parameters: the method to be called, and a delay value. In our case, we want to call the self.qp = window.showQuickPanel
method… but how can we pass parameters (such as the text to be displayed!) to it? The functools
Python module comes to the rescue: using functools.partial
, we create a new method that works like window.showQuickPanel
, but with the parameters we need—in particular, the content to be displayed is in the variable code pContent
.
Voila’! We then finally create our thread object and start it. So, to summarize, the key “tricks” are:
showQuickPanel
from that thread via setTimeout
showQuickPanel
method, use the thread object’s __init__
method.showQuickPanel
call, use functools.partial
.Comments and advice is most welcome—I’m still learning this stuff!
Hi, may I know how do I go about using OTF fonts with Latex? I tried setting the environment variable PDFLATEX to xelatex, and it works in the command line, but not when using the texify (ctrl alt t combination) within sublime text.
But I lose microtype. Also, the texify when manually entered in the command line always downloads fontspec. Why doesnt it stay installed?
Also, using your plug in, I cant get Ctrl Alt C to work at all.
Traceback (most recent call last):
File “.\sublimeplugin.py”, line 119, in execTextCommand
File “.\texCite.py”, line 42, in run
AttributeError: ‘NoneType’ object has no attribute ‘group’
Hi yihong—I’m sorry I haven’t made much progress on the bundle (or blog!) recently. But, I’m not set up to work a bit more frequently on it, so please keep the comments coming!
Re. OTF fonts, I think there should be a way to configure texify to do what you want it to; I’ll have to look into this, and possibly provide some kind of front-end under sublime text.
Re. Ctrl Alt C, it looks like the issue is that the plugin cannot find the bibliography file, or that no bibliographic entry matches the first fiew letters to the left of the cursor when you hit the key combination. It is, in any case, a bug, because it should at least fail gracefully and tell you what’s wrong. Again, please be patient—I’ll fix it in the next iteration.