LaTeXTools icon indicating copy to clipboard operation
LaTeXTools copied to clipboard

Feature request: deeper integration with Zotero

Open retorquere opened this issue 9 years ago • 26 comments

I maintain the Zotero "Better BibTeX" (BBT) plugin, and I'm looking to integrate it more with ST through LaTeXTools. BBT currently has bibliography auto-export which already solves much of the integration issue, but there's a way to take it one step further, and here's the general idea:

Instead of scanning the bibtex file while adding a citation, LaTeXTools could call a json-rpc method hosted by BBT to get the candidates for insertion. This selection could be from the whole Zotero database. Upon selection, BBT could add this reference to the bibtex file referenced by the LaTeX document, and the existing auto-export would make sure it's there. That way, you could do cite-as-you-write from your whole zotero library, and the collection that tracks the current document would always be up to date.

In this setup, I could build the json-rpc methods to search the Zotero database and to add a reference to the tracked collection upon confirmation of a pick.

retorquere avatar Jun 06 '15 13:06 retorquere

I like this idea, but since I don't use Zotero myself, I'm a bit hesitant to try implementing it. However, here's an idea for how it could be implemented.

I've been working on a plugin system for LaTeXTools, especially focused on making the bibliography stuff implementable through plugins. It's a bit rough (i.e. it works on my machines, but your mileage may vary), but anyone interested in implementing this could have a look at my bibliography_plugins branch. Essentially to implement this, you'd create a file in the bibliography_plugins subfolder called something like 'ZoteroPlugin.py' with contents like this:

from latextools_plugin import LaTeXToolsPlugin

class ZoteroPlugin(LaTeXToolsPlugin):
    def get_entries(self, *bib_files):
        # do whatever json-rpc stuff you need to get a list of bibliography entries from Better BibTeX
        # return this as some kind of iterable of dict-like objects; note in particular, the citekey should
        # be accessed through the 'keyword' entry

    def on_insert_citation(self, citekey):
        # do whatever json-rpc stuff you need to to update Better BibTeX

Look at traditionalBibliography.py for an idea of how this might be done.

The user could then create a bibliography_plugins setting in their LaTeXTools.sublime-settings set to something like ['zotero', 'traditional_bibliography'] and have access to their Zotero library as described.

ig0774 avatar Jun 11 '15 16:06 ig0774

I'm going to pick this up after the summer holiday

retorquere avatar Aug 04 '15 15:08 retorquere

Hi, I'm also using Sublime / LatexTools together with Better BibTex and I like the idea of closer integration between the two. If you need someone to test any thing, drop me a line. I'd be happy to help out.

felixhaass avatar Feb 01 '16 12:02 felixhaass

BBT auto-export should suffice for this kind of use-case; any deeper integration wouldn't yield anything interesting. The BBT CAYW picker is going to be more interesting. How would I add that to LatexTools? I could make the CAYW picker add a picked reference to a selected Zotero group; that in turn could trigger auto-export.

retorquere avatar Feb 16 '16 09:02 retorquere

@retorquere: That seems to completely change the character of the request so that it seems to me that this is something best done outside LaTeXTools altogether.

As I understand it, the CAYW picker you indicate enables you to select a citation in Zotero (or via its own UI) and gives you a properly formatted \cite{} command to insert into a file. I can see how that's useful, but I can't see how LaTeXTools adds any value to that particular process. This is different from the initial request which relied on LaTeXTools citation autocompletion infrastructure to provide a means of selecting the appropriate entry, but see below.

What you basically need is a small Sublime Text plugin probably with a key-binding that invokes a TextCommand that accesses the appropriate URL and then inserts the resulting command into the current view, at the current buffer point.

So you'd be looking at doing something like this:

import sublime_plugin

class InsertZoteroCitationCommand(sublime_plugin.TextCommand):
    def run(edit):
        # invoke the url and get a response
        cite_command = ... # extract citation from response

        self.view.insert(edit, self.view.sel()[0].b, cite_command)

And then defining an appropriate key binding to execute this command:

{ "keys": [ "..." ], "command": "insert_zotero_citation", "context":
    [
        {"key": "selector", "operator": "equal", "operand": "text.tex.latex"}
    ]
}

Of course, you could be proposing to do something else which would make more sense to leverage LaTeXTools to do, for example, continuing to use our citation-command recognition facilities and then (somehow) invoke the CAYW utility, get back just the cite key, etc. that's not impossible to do, but you're looking at something pretty much the same as the original solution I outlined, just instead of making (whatever) json-rpc calls, you'd have to extract the cite key out of the returned string. You could take advantage of the feature where, if get_entries() only returns a single entry then it will just be automatically inserted, so have get_entries() called, invoke the CAYW utility, then return a single entry with the selected key (or no entries if nothing was selected).

ig0774 avatar Feb 16 '16 13:02 ig0774

There's no need to extract out the key, the CAYW picker can just yield only the key. What you suggest is super interesting though -- you mean I can react to people typing \cite{? because at that point I could just call the cayw URL and return the citekey. Returning just the citekey(s) is is the simplest case, simply calling http://localhost:23119/better-bibtex/cayw will do just that.

retorquere avatar Feb 16 '16 13:02 retorquere

The auto-export functionality would rely on the traditional bibliography, but add new bib files. For this it would be necessary to change the existing plugin with a command like add_bib_files. The zetero plugin could download the bib files via auto-export, store them in a temp folder and provide them to the existing cite completion.

I made a working example for the CAYW picker (zoteroBibliography.py.txt), which as @ig0774 suggests just returns one entry and relies on auto-insert functionality. (More than one entry is currently not working, because ST seems to get the enter key to affirm the selection and cancels the panel with index -1.)

zetero_cayw_example

Although it does not benefit from the plugin architecture, I think it feels more user friendly. In this way he just can change the settings instead adding keybindings. However it might be necessary to deactivate it for the autocompletion event.

r-stein avatar Feb 16 '16 13:02 r-stein

@retorquere

You can add a keybinding on the { after typing \cite via

    {
        "keys": ["{"],
        "context": [
            { "key": "selector", "operator": "equal", "operand": "text.tex.latex" },
            { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true },
            { "key": "preceding_text", "operator": "regex_contains",
                "operand": "\\\\(cite([a-zX*]*?)(\\[.*?\\]){0,2}))$", "match_all": true }
        ],
        "command": "insert_zetero"
    },

And create a class which inherites from TextCommand and is called InsertZeteroCommand.

r-stein avatar Feb 16 '16 13:02 r-stein

I shouldn't even be necessary to fetch the bib files via http (even though you can -- the pull export facility is already there), I would recommend to set BBT up to auto-export to a known bib file, should yield better performance.

retorquere avatar Feb 16 '16 13:02 retorquere

@r-stein 's code already seems to do the trick. That's what I meant to do once I understood how it works.

retorquere avatar Feb 16 '16 13:02 retorquere

@r-stein: That's really cool! This actually looks like the sort of thing I envisioned the plugin architecture being useful for. I guess what we need to resolve is what needs to happen to support multiple selections from a non-ST GUI.

ig0774 avatar Feb 16 '16 14:02 ig0774

The picker will just return a comma-separated list of citekeys when called this way. Does ST in some way capture the return key before the picker can process it? Usually, you close the picker by entering return twice (once to confirm the citation pick, once more to confirm you're done picking).

retorquere avatar Feb 16 '16 14:02 retorquere

(I mean it's rather odd ST would do this because technically, Firefox has focus when the picker is up, not ST, so it seems to be stealing keystrokes from another process)

retorquere avatar Feb 16 '16 14:02 retorquere

Actually, for me it works just fine as it is for multiple entries:

test

Since the returned value is just a comma-separated list, as long as we do nothing to parse that, I think its ok... (from LaTeXTools perspective it only every gets a single entry; the key just happens to have a comma in it).

ig0774 avatar Feb 16 '16 15:02 ig0774

Perhaps because you're using the mouse to select rather than the keyboard?

retorquere avatar Feb 16 '16 15:02 retorquere

Works just the same for me when using the keyboard or mouse.

ig0774 avatar Feb 16 '16 15:02 ig0774

Unless its platform-specific behaviour...

ig0774 avatar Feb 16 '16 15:02 ig0774

It works fine with only the zetero plugin, but combining it with other cite completions (which is not useful anyway) does not show the panel in ST. Because it seems to get the enter key command from zetero. It immediately calls the on_done callback with index -1.

r-stein avatar Feb 16 '16 15:02 r-stein

Oh yeah... That's kind of a basic assumption of the latex_cite_completions bit, that only a single plugin is feeding us any citations. So if Zotero returns any non-None result and doesn't raise a NotImplementedError, it just assumes there's no more work to be done, so the traditional_bibliography code never even gets called. I'm kind of assuming that people only want to manage their bibliographies using a single technology...

The reason for having the traditional_bibliography as a fallback is I have some plans to move other aspects of the bibliography-handling code to plugins to allow, e.g. customisation of how we determine what bibliography files are in use (if, for example, a bibliography is loaded by an unrecognised command or even hard-coded in a cls file) and I didn't want to force every plugin to implement every conceivable function. This gives them some way to implement only part of the functionality and then fall-back on our standard implementation (which will always be in traditional_bibliography) or even to have multiple bibliography plugins that do different things...

That's probably way more explanation than anyone needed, but that's what happens and why...

ig0774 avatar Feb 16 '16 15:02 ig0774

Just wondering: do we have a best practice for using Zotero's cite-as-write? has this function been integrated into LaTexTools?

ma-ji avatar Mar 23 '18 16:03 ma-ji

It hasn’t been integrated into LaTeXTools yet. The above code from @r-stein and setup should still work, though I haven’t tested it recently.

ig0774 avatar Mar 24 '18 15:03 ig0774

The zotero plugin doesn't send a enter character btw.

retorquere avatar Mar 24 '18 15:03 retorquere

Have there been any developments on this? It would be wonderful to have this deeper integration with Better Bibtex - Zotero 5 and LatexTools.

davdent avatar Dec 04 '19 17:12 davdent

Not on my end, but I'd be happy to implement the necessary parts if something is still missing.

retorquere avatar Dec 04 '19 18:12 retorquere

I'm also very interested in this, but as someone who's unfortunately unfamiliar with python (and ST3), I've been struggling to follow @r-stein's solution. Is there some documentation for this? Implementing this is probably well above my pay grade, but in particular I'm confused about the class which inherits from TextCommand called InsertZeteroCommand and (I think) does the json stuff?
Sorry for the basic questions!

liamjb0 avatar Mar 30 '20 05:03 liamjb0

@lblb98 Unfortunately my solution does not work anymore.

  1. It seems that the zotero api has changed since then. So additional adaptions are necessary there.
  2. The class must be renamed from ZoteroPlugin to ZoteroBibliographyPlugin.

In general you could have been used by

  1. Opening Preferences > Browse Packages...
  2. Open LaTeXTools/bibliography_plugins
  3. Download the file an change the extension from .py.txt to .py
  4. Open the file and rename the class from ZoteroPlugin to ZoteroBibliographyPlugin
  5. Open the LaTeXTools settings and change the setting from "bibliography": "traditional", to "bibliography": "zotero",
  6. Restart Sublime Text and check the console View > Show Console... for errors

r-stein avatar Mar 30 '20 08:03 r-stein