Showing posts with label PDFView. Show all posts
Showing posts with label PDFView. Show all posts

Sunday, November 4, 2007

SEEing LaTeX 14: General Use of Environments

We've now seen how to define and update an environment for SubEthaEdit. The approach is modeled on how Mac OS X applications store their preferences; effectively, I used application preferences as a design pattern. To demonstrate the general applicability of the approach, let's apply it to some additional scripts; specifically, let's revisit viewing the PDF compiled from a LaTeX document and cleaning up the auxiliary files that LaTeX produces. The short version is that the approach works smoothly in both cases, with minimal differences in the AppleScripts used to add behavior to SEE. The long version follows, including the scripts to actually implement it.

First, let's look at viewing the compilation product. Previously, I'd just used the LaTeX file name to define the PDF file name, and sent it to PDFView using AppleScript. With the new approach, I need a shell script defining the default behavior, and an AppleScript invoking the shell script from SEE. The shell script is:
PATH="$PATH:/usr/texbin:/usr/local/bin"
export PATH

VIEWER=${SEE_LATEX_VIEWER:-'open "$PRODUCT"'}
PRODUCT_TYPE="${SEE_LATEX_PRODUCT_TYPE:-pdf}"

FILE="$(basename "$1")"
DIRNAME="$(dirname "$1")"
LINE="$2"
PRODUCT="$(basename "$1" .tex).$PRODUCT_TYPE"

cd "$DIRNAME"
if [ -s "$PRODUCT" ]
then
    eval $VIEWER
fi
Note that the new definition nowhere assumes that we will produce a PDF file as output; it could be used with latex to produce a DVI, for instance.
The AppleScript is:
tell application "SubEthaEdit"
    if exists path of front document then
        set filePath to path of front document
        set lineNumber to startLineNumber of selection of front document
        set activeMode to mode of front document
        set modeResources to resource path of activeMode
    else
        error "You have to save the document first"
    end if
end tell


set viewScript to prependEnvironment for activeMode onto (join of {quotedForm for (modeResources & "/bin/viewproduct.sh"), quotedForm for filePath, lineNumber} by space)

do shell script viewScript


-- SubEthaEdit settings

on seescriptsettings()
    {displayName:"View", shortDisplayName:"View", keyboardShortcut:"^~@o", toolbarIcon:"ToolbarIconRun", inDefaultToolbar:"yes", toolbarTooltip:"View current document in external viewer", inContextMenu:"no"}
end seescriptsettings

on join of tokenList by delimiter
    set oldTIDs to text item delimiters of AppleScript
    set text item delimiters of AppleScript to delimiter
    set joinedString to tokenList as string
    set text item delimiters of AppleScript to oldTIDs
    return joinedString
end join

on quotedForm for baseString  
    quote & baseString & quote
end quotedForm

to prependEnvironment for seeMode onto scriptString
    set envFilePath to (path to preferences from user domain as string) & "de.codingmonkeys.SubEthaEdit." & (name of seeMode) & "_environment.plist"
    (readEnvironment out of envFilePath) & scriptString
end prependEnvironment

to readEnvironment out of plist
    readListPair out of plist
    environmentString from result
end readEnvironment

to readListPair out of plist
    tell application "System Events"
        if exists file plist then
            tell property list file plist
                get {name, value} of every property list item
            end tell
        else
            {{}, {}}
        end if
    end tell
end readPlist

on environmentString from keyValueListPair
    set {plistKeys, plistValues} to keyValueListPair
    set accumulator to {}
    set oldTIDs to text item delimiters of AppleScript
    set text item delimiters of AppleScript to ""
    repeat with i from 1 to number of items in plistKeys
        set tokens to {"export ", item i of plistKeys, "=", item i of plistValues, ";"}
        copy (tokens as string) to the end of the accumulator
    end repeat
    set AppleScript's text item delimiters to space
    set envString to accumulator as string
    set AppleScript's text item delimiters to oldTIDs
    envString
end environmentString


The shell script uses the same SEE_LATEX_VIEWER environment variable used for compiling; I'll adapt the compilation script a little to allow separate viewing behavior for the two cases, defaulting to both using the SEE_LATEX_VIEWER contents. Essentially, this consists of changing just one line, replacing
VIEWER=${SEE_LATEX_VIEWER:-'open "$PRODUCT"'}
with
VIEWER=${SEE_LATEX_COMPILEVIEWER:-${SEE_LATEX_VIEWER:-'open "$PRODUCT"'}}
Note that the AppleScript uses the same code to read from the same plist of environment settings as the compilation script--no changes were needed to accommodate the new settings.

Second, let's examine cleaning up the auxiliary files. The shell script is:
PATH="$PATH:/usr/texbin:/usr/local/bin"
export PATH

CLEANUP=${SEE_LATEX_CLEANUP:-'rm -f $(basename "$FILE" .tex).{aux,bbl,blg,dvi,log,out,ps,pdf,pdfsync,toc}'}

FILE="$(basename "$1")"
DIRNAME="$(dirname "$1")"
PRODUCT="$(basename "$1" .tex).$PRODUCT_TYPE"

cd "$DIRNAME"
eval $CLEANUP
The cleanup behavior can be defined in a SEE_LATEX_CLEANUP variable, used to set the CLEANUP variable. The default value for CLEANUP is to remove files with the same name as the LaTeX file but with different filename extensions. The list of extensions (aux, bbl, blg, dvi, log, out, ps, pdf, pdfsync, toc) is pretty arbitrary, being essentially what were created for my own writings.

The associated AppleScript is:
tell application "SubEthaEdit"
    if exists path of front document then
        set filePath to path of front document
        set activeMode to mode of front document
        set modeResources to resource path of activeMode
    else
        --Unsaved document, so LaTeX not run on it and can just return
        return
    end if
end tell

set cleanupScript to prependEnvironment for activeMode onto (join of {quotedForm for (modeResources & "/bin/cleanupaux.sh"), quotedForm for filePath} by space)

do shell script cleanupScript

on seescriptsettings()
    return {displayName:"Clean Up Auxiliary Files"}
end seescriptsettings

on join of tokenList by delimiter
    set oldTIDs to text item delimiters of AppleScript
    set text item delimiters of AppleScript to delimiter
    set joinedString to tokenList as string
    set text item delimiters of AppleScript to oldTIDs
    return joinedString
end join

on quotedForm for baseString  
    quote & baseString & quote
end quotedForm

to prependEnvironment for seeMode onto scriptString
    set envFilePath to (path to preferences from user domain as string) & "de.codingmonkeys.SubEthaEdit." & (name of seeMode) & "_environment.plist"
    (readEnvironment out of envFilePath) & scriptString
end prependEnvironment

to readEnvironment out of plist
    readListPair out of plist
    environmentString from result
end readEnvironment

to readListPair out of plist
    tell application "System Events"
        if exists file plist then
            tell property list file plist
                get {name, value} of every property list item
            end tell
        else
            {{}, {}}
        end if
    end tell
end readPlist

on environmentString from keyValueListPair
    set {plistKeys, plistValues} to keyValueListPair
    set accumulator to {}
    set oldTIDs to text item delimiters of AppleScript
    set text item delimiters of AppleScript to ""
    repeat with i from 1 to number of items in plistKeys
        set tokens to {"export ", item i of plistKeys, "=", item i of plistValues, ";"}
        copy (tokens as string) to the end of the accumulator
    end repeat
    set AppleScript's text item delimiters to space
    set envString to accumulator as string
    set AppleScript's text item delimiters to oldTIDs
    envString
end environmentString
Again, the bulk of the script is unchanged, with no changes at all to the portions handling the environment settings.

Sunday, September 30, 2007

SEEing LaTeX 11: Typesetting Extended

In installment 10 of this series, I refactored the shell scripts used to compile and view a LaTeX document from SubEthaEdit. To change how a LaTeX document is compiled or viewed, it is now only necessary to change a variable, rather than messing around with the logic of the script. Let's take things a step further.

The relevant variables in the script are LATEX, PRODUCT_TYPE, and VIEWER. Their values in the script shown before were to use latexmk to build a PDF and PDFView to display it. However, other settings are possible. For example, setting
LATEX='pdflatex "$FILE"'
VIEWER='open "$PRODUCT"'
PRODUCT_TYPE=pdf
builds a PDF using pdflatex and shows it in Preview. These settings would be quite suitable for a "vanilla" installation, assuming no extras are installed beyond TeX.

A more flexible approach is to use those vanilla settings as defaults, and allow them to be overridden, giving
LATEX=${SEE_LATEX_COMPILER:-'pdflatex "$FILE"'}
VIEWER=${SEE_LATEX_VIEWER:-'open "$PRODUCT"'}
PRODUCT_TYPE="${SEE_LATEX_PRODUCT_TYPE:-pdf}"
Now, if we run the script from the shell, we can set environment variables to change how the script does its work.

Within SEE, the environment variables will be provided by the AppleScript that runs the script. The change is relatively simple: we extend the string that calls our shell script to also set the environment variables. Since a number of minor changes to the AppleScript have accumulated, I'll show the whole thing:
tell application "SubEthaEdit"
    if exists path of front document then
        if modified of front document then
            try
                save front document
            end try
        end if
        set filePath to path of front document
        set lineNumber to startLineNumber of selection of front document
        set modeResources to resource path of mode of front document
    else
        error "You have to save the document first"
    end if
end tell

set buildScript to prependEnvironment onto (join of {quotedForm for (modeResources & "/Scripts/shell/buildlatex.sh"), quotedForm for filePath, lineNumber} by space)

do shell script buildScript

on seescriptsettings()
    return {displayName:"Typeset and View PDF", shortDisplayName:"Typeset", keyboardShortcut:"@b", toolbarIcon:"ToolbarIconBuildAndRun", inDefaultToolbar:"yes", toolbarTooltip:"Typeset and view the current document", inContextMenu:"no"}
end seescriptsettings

on join of tokenList by delimiter
    set oldTIDs to text item delimiters of AppleScript
    set text item delimiters of AppleScript to delimiter
    set joinedString to tokenList as string
    set text item delimiters of AppleScript to oldTIDs
    return joinedString
end join

on quotedForm for baseString  
    quote & baseString & quote
end quotedForm

to prependEnvironment onto scriptString
    "export SEE_LATEX_COMPILER='latexmk -pdf -quiet \"$FILE\"'; export SEE_LATEX_PRODUCT_TYPE=pdf; export SEE_LATEX_VIEWER='/Applications/Skim.app/Contents/SharedSupport/displayline \"$LINE\" \"$PRODUCT\" \"$FILE\"';" & scriptString
end prependEnvironment

The basic strategy is still to construct a string that calls our shell script and feed it into do shell script. The string is changed in two ways. First, since the shell script now takes a line number as a second argument, I get the current line from SEE and pass it in. Second, and of more immediate interest, I set the environment variables and prepend those to the earlier buildScript. I've simplified the construction by defining join, quotedForm, and prependEnvironment handlers.

PrependEnvironment sets the SEE_LATEX_COMPILER, SEE_LATEX_PRODUCT_TYPE, and SEE_LATEX_VIEWER environment variables, customizing the behavior of the shell script. In the above AppleScript, I have set the environment so that latexmk and Skim are used to build and view a PDF.

Sunday, September 23, 2007

SEEing LaTeX 10: Typesetting Refactored

Let's unify the scripts shown in the preceding installment. The approach I'll take is to move everything that I can into shell variables. My earlier script
PATH=/usr/texbin:/usr/local/bin:$PATH
export PATH

cd "$(dirname "$1")"
latexmk -pdf -quiet "$(basename "$1")"
PRODUCT="$(basename "$1" .tex).pdf"

if [ -s "$PRODUCT" ]
then
    open -a PDFView.app "$PRODUCT"
fi

becomes
PATH=/usr/texbin:/usr/local/bin:$PATH
export PATH

LATEX='latexmk -pdf -quiet "$FILE"'
PRODUCT_TYPE=pdf
VIEWER='open -a PDFView.app "$PRODUCT"'

FILE="$(basename "$1")"
DIRNAME="$(dirname "$1")"
LINE="$2"
PRODUCT="$(basename "$1" .tex).$PRODUCT_TYPE"

cd "$DIRNAME"
eval $LATEX
if [ -s "$PRODUCT" ]
then
    eval $VIEWER
fi

All I've really done is to move some things around; note the use of eval to allow shell variables to be referred to before they are defined. The script behaves the same. That's fine, the goal for the moment it just to refactor for some later behavioral changes, not to make those behavioral changes now.

The behavior of the script provided by Al Kasprzyk can be produced by the above script by changing just the line defining VIEWER. That line becomes
VIEWER='/Applications/Skim.app/Contents/SharedSupport/displayline "$LINE" "$PRODUCT" "$FILE"'

Nothing else need be changed; we now can compile our LaTeX file and see the resulting PDF in Skim.

To incorporate this into the SubEthaEdit mode, we do need to have the AppleScript pass the line number to the shell script. For now, I'll omit showing the AppleScript, since the essential changes are minor. I'll take a closer look at the AppleScript next time.

Update: I've renamed the LINENUM variable in the script to LINE. This is just to be more symmetric with how applications like PDFView and Skim work with pdfsync.

Tuesday, September 18, 2007

SEEing LaTeX 9: Typesetting Revisited

Earlier, I discussed typesetting a LaTeX document from SubEthaEdit and sending it to PDFView. PDFView works fine, but is no longer being developed; the developer recommends Skim.

Replacing PDFView with Skim is something that I've been thinking about for a while, but it hasn't been a high priority. Happily, Al Kasprzyk provided a script to make that replacement in a comment to the earlier post. For convenience, I'll reproduce it here, with syntax highlighting:
cd "`dirname "$1"`"
latexmk -pdf -quiet "`basename "$1"`"
pdfName=$(basename "$1" tex)pdf

if test -s "$pdfName"; then
/Applications/Skim.app/Contents/SharedSupport/displayline $2 "$pdfName" "$1"
fi

Perhaps the greatest difference is that Al uses the displayline script included with Skim, instead of just opening the PDF created from the LaTeX document. Thus, instead of having latexmk open the preview, he handles it himself. behaviorally, the preview is shown in Skim, with the current line selected in SEE being visible in Skim.

We can rewrite my earlier script to take a similar approach. Without either following Al's script exactly or avoiding looking at it, I came up with this:
PATH=/usr/texbin:/usr/local/bin:$PATH
export PATH

cd "$(dirname "$1")"
latexmk -pdf -quiet "$(basename "$1")"
PRODUCT="$(basename "$1" .tex).pdf"

if [ -s "$PRODUCT" ]
then
    open -a PDFView.app "$PRODUCT"
fi

The differences between the two scripts are minor. Apart from some formatting choices, there are just a few differences. First, I set the PATH environment variable. I'm pretty sure Al must have done so as well, but he didn't include it in his comment. Second, I have an extra layer of quoting for what I called the PRODUCT; based on restrictions for LaTeX file names, these are superfluous. Third is the obvious and essential difference of which viewer we call.

Right now, there is no change in behavior between my earlier script and the one I present above. However, I no longer need to define anything in a .latexmkrc file, which is helpful in making a portable LaTeX mode. Better, though, is that the obvious parallels between Al's script and mine can be taken advantage of. Next time.

Sunday, July 1, 2007

SEEing LaTeX 3: From PDFView to SubEthaEdit

In my last post, I presented an AppleScript that took us from a LaTeX document in SubEthaEdit to an appropriate portion of the corresponding PDF document in PDFView. This useful action is possible thanks to the magic of pdfsync.

What's more, pdfsync can be used to work both ways. Let's take a look at how to go from a PDF document open in PDFView to the appropriate line in the LaTeX document. In PDFView, you can command-click in the PDF, which invokes a shell command to presumably take you to the right spot in your editor. This works very nicely in TextMate, for example.

PDFView has a "command" text field and an "arguments" text field in its preferences. I don't really see why it's broken into two fields, but that's how it is, so that's what we'll work with. The arguments can include "%file" and "%line" tokens to indicate the file and line number, respectively. PDFView substitutes those tokens appropriately, and runs the script.

SubEthaEdit has the see command line tool that lets us open a file. That will be our starting point. Shockingly, it doesn't have a switch to go to a particular line! We'll work around that with AppleScript. This will be needlessly ugly, I'm afraid.

The see tool opens a file, and makes it the front document. That is the behavior we want for the document, so we'll start there and build a shell script to extend the behavior. We won't try to expose all the options of see in our script, instead just opening a file and going to a given line. We'll call the script seeline, and take its first argument to be the filename.

The next step in the script is to set the selection in the front document to a line number given as the second script argument. This is easy enough with AppleScript, and is a fairly direct transcription of the English-language description. Unfortunately, that turns out not to be enough. The selection is set as desired, but the selection may not be visible! What's worse, there is nothing in SubEthaEdit's AppleScript dictionary that lets us scroll the view as needed.

SubEthaEdit does have a "Jump to Selection" item in its "Find" menu. We could just hit command-J after PDFView transfers us to the LaTeX document in SubEthaEdit, I guess, but it is inelegant, at best. Instead, let's try something else. AppleScript can be used to script the user interface. You may need to first open the AppleScript Utility (why isn't this a preference pane, anyway?) and check the "Enable GUI Scripting" box. We can then use System Events to directly work with the menus of SubEthaEdit, choosing the "Jump to Selection" item.

Taken all together, we get a script:
#!/bin/sh
#
# Brings document to front in SubEthaEdit, opening if necessary, and
# selects given line.
#

#$Id: seeline.sh,v 1.4 2007/07/01 18:28:31 mjb Exp $


/usr/bin/see "$1"
/usr/bin/osascript > /dev/null <<ASCPT

tell application "SubEthaEdit"
    tell front document to set selection to paragraph $2
end tell

tell application "System Events"
    tell process "SubEthaEdit"
        tell menu bar 1
            tell menu bar item "Find"
                tell menu 1
                    click menu item "Jump to Selection"
                end tell
            end tell
        end tell
    end tell
end tell
ASCPT

All the AppleScript is included as a here document, which prevents lots of trouble with how the shell processes quotes and other characters.

I'm not pleased with using UI scripting. It seems fragile and likely to break with updated version of SubEthaEdit. Clearly, this should be an option for the see command line tool, which pretty much means it needs to be handled by the developers.

Whatever my feelings on the aesthetics of UI scripting, the important question is something else: does it work? Yes, it does, and pretty well at that. I have saved the script as ~/Library/bin/seeline, and set the "Command" and "Arguments" in the PDFView preferences to '$HOME/Library/bin/seeline' and '"%file" %line', respectively. Command-clicking in the PDF takes me to the LaTeX file.

Edit: Fixed formatting of included script.

Update: As of version 3.0 of SubEthaEdit, the see command line tool has an option to go to a desired line.

SEEing LaTeX 2: Opening the PDF in a Viewer

Getting completions for citations in SubEthaEdit is easy. Of course, someone else did all the work for us, so it should be easy! Let's try something a little more ambitious.

I generally use pdflatex. Thanks to pdfsync, a number of editors can integrate nicely with PDF viewers, albeit the viewers themselves must incorporate support for pdfsync. So, let's try to set up pdfsync support for SubEthaEdit. I'll focus on integrating SubEthaEdit with my current viewer of choice, PDFView. Sadly, PDFView appears not to be supported any more, but it's still useful and the basic approach should be the same for any viewer. For now, I'll just focus on going from SubEthaEdit to PDFView.

The mechanism for linking SubEthaEdit to PDFVIew will be AppleScript. You can have PDFView display a particular LaTeX line using AppleScript, which is just what we need. The strategy then is to get the line number for the selection in SubEthaEdit, figure out the file name for the PDF, and then tell PDFView to show the line number we obtained from SubEthaEdit. According to the SubEthaEdit scripting guide, we also need to include a seescriptsettings handler. Here, I present something simple, without working too hard to catch errors:
tell application "SubEthaEdit"
    tellfront document
        
set lineNumber to startLineNumber of selection
        set texFile to path
    end tell
end tell

set pdfFile to (text 1 through ((length of texFile) - 3) of texFile) & "pdf"

tell application "PDFView"
    activate
    display tex line lineNumber of file pdfFile
end tell

-- SubEthaEdit settings

on seescriptsettings()
    {displayName:"Show in PDFView", shortDisplayName:"PDFView", keyboardShortcut:"^~@o", toolbarIcon:"ToolbarPDFView.png", inDefaultToolbar:"yes", toolbarTooltip:"Opens PDF for document in PDFView", inContextMenu:"no"}
end seescriptsettings

I saved the above script as a compiled script into a copy of the LaTeX mode. Now, for a LaTeX document, the Mode menu in SubEthaEdit shows a script called "View in PDFView" which can be activated by a keyboard shortcut. It works well.

I also added an image to the LaTeX mode bundle to provide a toolbar item. I encountered some difficulties with this. The description in the scripting guide isn't very specific, it really just says to put images in the bundle. I got an appropriate image by copying the icon from the PDFView application bundle, and converting it to PNG (since that's what all the other images in SubEthaEdit mode bundles were). This didn't work. It took me some time to discover that the toolbar icon cannot be any size; the 128 by 128 pixel image I had simply didn't show up. Downsizing it to 32 by 32 using ImageWell solved the problem, providing a nice toolbar item. Of course, I normally leave the toolbar hidden, so I doubt I'll be using it much, but it is good to know how that works.

The script has a couple of oddities and shortcomings. The tell block for SubEthaEdit needs to get the selection and path for the front document. However, the scripting dictionary for SubEthaEdit has both documents containing windows and windows containing documents. Is tell front document the right thing to do? Should it be tell document of front window? Or maybe something else?

Also, I don't check to see if the document has been saved. I'm not really sure what the right solution is: unsaved changes won't be reflected in the PDF, anyway, so should I care? I'll revisit that later, assuming that I decide SubEthaEdit is suitable for my LaTeX needs.

Nor do I check to see if there is a document open at all. As far as I can tell, this is not an error. The script is mode dependent. Since the LaTeX mode is only active if there is a document open, there is thus no need to check.

The transformation of the LaTeX file path to a PDF file path is not done well. It should really be improved by properly splitting off the file extension, instead of just chopping off the last three characters and assuming we've gotten the file extension. While it is probably OK for LaTeX, I'm not sure if it could cause any problems. Again, this is something to fix later, once a more complete picture of editing LaTeX in SubEthaEdit takes shape.

The tell block for PDFView is weird. There aren't any changes I'd make, but it is just strange. If you open the script in Script Editor and compile it, the display tex line lineNumber of file pdfFile becomes display tex line lineNumber file pdfFile, which is syntactically invalid. However, you can run the script without first compiling, and it works fine. Which is really weird, because the script has to be compiled before running it, so it works once but fails the second time you run it. My solution was to do the editing in SubEthaEdit, compile it in Terminal using osacompile, and not worry about the matter. I suspect that the scripting dictionary in PDFView should just be considered in error, even though it can be made to work.

Next, I'll look at going the other direction, from PDFView to SubEthaEdit. A quite pleasant environment already looks possible.

Edit: Fixed formatting of included script.