SublimeText/LaTeXTools

Subversion checkout URL

You can clone with
or
.

ref/cite completion: fixes and enhancements#120

Merged
merged 25 commits into from
+522 −474

8 participants

I've made a bunch of refinements/refactoring to the ref and cite completion systems:

• For each of ref and cite, I've refactored the common code between the autocomplete plugin and the quick panel command into a single shared function.
• Fixed an issue where the commands would crash if the current file is unnamed and unsaved.
• Fixed various cases where the completion would generate an extra leading backslash, or a missing closing brace, etc.
• Changed the ref/cite dispatching command so that it directly uses the actual regexes used internally in the ref and cite functions to determine which, if any, to use. (Previously the dispatcher function was too restrictive, and would reject otherwise valid usage scenarios.)
• Make the autocomplete plugins less eager (see Issue 118), so that they don't make unwanted modifications to the text if the user is in the middle of typing something else.
• Enhanced cite completion to work with multiple, comma-separated references in a single \cite{} command. (I didn't do this for ref, since most ref commands do not work with a list of multiple references.)
• Changed ref completion so that it will also work when other known ref commands are used (e.g. pageref, autoref, cref, etc.).
• Added keybinding for "{" and "," such that, if they are typed in very specifically appropriate contexts (e.g., "{" following "\ref"), they will automatically open the appropriate completion menu. This feature can be disabled by adding "disable_latex_ref_cite_auto_trigger": true to your user preferences.
• Incorporated jlegewie's options for customizing the format of the citation quick panel.
 Westacular ref and cite autocomplete would crash if the current file is unnamed … …and unsaved d773fea Westacular Correct bug that could result in "\\ref{" (note double slash) and mak… …e ref/cite dispatcher more tolerant of allowed formats c548a0c Westacular Automatically open the ref/cite completion list when typing { if the … …context is appropriate i.e., if the cursor is immediately preceded by "\ref" or "\cite" or recognized variants. bea6293 Westacular Make command versions of cite/ref autocompleters also tolerant of the… … unnamed file scenario a33c728 Westacular Refactor common code of the autocomplete plugin and the quick panel c… …ommand into a function used by both. f0ef951 Westacular Rename error type, remove overzealous error response. a54f3d5 Westacular Refactor common autocomplete / quick panel command code into a shared… … function. Also, fixed a few corner cases along the way. c544ab8 Westacular Tweak ref_cite regexes to more accurately align with new and old form… …s (shouldn't actually change what is/isn't matched) 059708a Westacular Enable completion of multiple, comma-separated references in a single… … \cite{}. 06f9958 Westacular Correct some typos 93d1a51 Westacular Make ref and cite autocompletion slightly less eager (see: SublimeTex… …t#118) 3b996c2 Westacular Some more fixes to the ref/cite completion dispatcher regexes, to acc… …ept more of the forms that the completion functions internally accept. 361d16a Westacular Some fixing of the fix to make autocomplete less disruptive to normal… … typing. 90cc976 Westacular Set the ref/cite dispatcher to just directly use the exact same regex… …es as used in the ref and cite completion functions. 882b064 Westacular Fix an error that prevented the { and , auto-opening of the cite/ref … …quick panel from working except at the start of a line. b74abf3 Westacular Add to ref completion recognition of/support for a variety of other r… …ef commands 0ab3411 Westacular Add a setting -- "disable_latex_ref_cite_auto_trigger" -- that when s… …et to true, stops { and , from automatically opening the ref/cite completion quick panels. 59d14c6 Westacular Incorporating jlegewie's options for formatting of citation quick panel See SublimeText#92 2cbeff4 Westacular Fix an error with ref completions. ecb7262 Westacular Tweak the { and , completion triggers, and add them to Linux and Wind… …ows keymaps. 18b8734

Thanks! And, yeah, sorry about the size of the change. Almost all of the individual commits are pretty small, but I got on a roll, and they started to stack up.

Also, there's no real way to stop a refactoring of the guts of the completion into shared functions (f0ef951 and c544ab8) from showing up as a huge diff, even though most of the code is actually unchanged in both those commits.

i tested if this resolves Issue #118 and found that it indeed does,
but the auto completion trigger on '{' does not seem to work for me,
which makes me unable to insert '{' after '\ref' unless i deactivate that keybinding.
as a note, even without these changes there is no autocomplete popup when i type 'ref' and it gets expanded.
console says that it parsed the file, but no popup, pressing ctrl+space (in a '\ref{}') does open a popup with correct completions.

also with these patches everything i type is echoed back in the console

Thanks for the feedback!

Regarding the echoing: there's a lot of diagnostic print statements in there. I haven't added any, but I didn't turn any off, either. (They should probably be turned off eventually, though.)

It's odd that the '{' trigger is failing. What you're describing implies that the context is matching enough to activate the keybinding (and thus run the completion function), but something in the function must be failing (or hitting an exception) before it gets to the point where a { would be inserted -- and doing so in a way that doesn't generate an error message. I'm not sure what could be causing that. What gets output on the console when this happens?

i have compared what happens on my windows desktop (which is where the error occurs) with my linux laptop.
basically, without any the changes here, the autocompletion does not work correctly on my windows machine.
on linux i do get a popup when completing 'ref' on windows i do not.
the output when that happens is on both machines:

TEX root: u'/path/to/file.tex'
Searching file: u'/path/to/file.tex'
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 7: ordinal not in range(128)

i also tried with a file which does not contain any unicode characters.
the same happens, but without the UnicodeDecodeError.

so basically there seems to be some other error that prevents the command from completing,
thus preventing the character to be inserted.

i will later try with a clean version of sublime, when im back on my desktop.

 Westacular Fix problems handling non-ASCII charsets (and disabled several print … …statements) The Sublime terminal apparently only handles ASCII, so printing unicode text to it causes exceptions. So I've disabled several superfluous print statements that can run into trouble there. Also, the ref completion would hit an exception if it read a \label from a file that contained a non-ASCII character. I've added code to check for \usepackage[.*]{inputenc} and, if present, read the file using the appropriate decoder. Similarly, I've added analogous code to the cite completion, although in that case it's only guarding against non-ASCII characters in .bib filenames; the contents of the .bib files are still assumed to be ASCII. This is a tricky problem since the character encoding of .bib files is a messy topic. (And since the bib parsing needs other fixes anyway, it's probably not worth worrying about it too much right now.) 7ef2e7d

I went through and made some changes that I hope fix a couple issues regarding the handling of unicode characters. This might solve the '{' not appearing issue, but I'm not sure; from what you're saying, there could still be another problem.

 Westacular Handle the { and , triggers in a more robust fashion, so that even if… … the completion crashes or fails miserably, at least a { or , should be inserted. b38fc7c

This latest commit should solve the problem of { not appearing, even if I've still missed some other underlying issue that's stopping the completion menu from working.

i tested it again with the current version and everything seems to work for me now.
although i do not like the workflow of switching to the command palette selection when typing '{',
this is not a problem for me because disabling the keybinding just gives me the expected completions when i continue typing.

so i say my thanks.
merging this will close issue #118 as far as im concerned.

 Westacular Add formatting options for the cite autocomplete entries (analogous t… …o formatting options added for cite quick panel) 0e4726a

My pleasure! You're welcome.

Building off of jlegewie's changes, I just added the same options for formatting to the autocomplete menu items. This reminded me: One big question for thought/discussion is, what should the default formats for the cite autocomplete and quick panel menus be?

I suppose it really depends on how people use these, which in turn hinges on how people think when looking for a reference. How often are they searching using the title or author, and how often do they know the keyword and are just typing that directly?

 Westacular Set the ref and cite quick panel commands to leave the caret at the e… …nd and the filled-in keyword unselected after inserting the completion. 665cbd7

Just to clarify: Your pull request now includes my changes? I was going to ask whether the set of changes from both of us are in conflict. If you have included my changes, I will switch to your branch and close my pull request. That would also add another tester to the rather large changes.

edit: I just saw that you also included yunjing's changes so that his/her pull request can be closed too. I am just saying because concentrating on one request keeps it simpler for msiniscalchi.

About the default: I purposefully used the old behavior as the default in my pull request so that msiniscalchi can decide about any changes in the default behavior. My proposal would be ["{author_short} {year} - {title_short} ({keyword})","{title}"] but that is based on my implementation. Ie would be better to use the second line in the quick panel for something like journal title, volume and page number for articles, place and publisher for books, edited volume title for chapters etc. But that requires some more work particularly because the placeholder {publication} (or something) would depend on the item type.

Yes, this includes your changes exactly. I suspected that our branches wouldn't merge cleanly because of the refactoring I did, so I applied your changes manually in 2cbeff4, as you probably noticed.

This branch also implements the ability to have comma-separated cite items, as yunjing did, but it is implemented differently here.

I like your idea for what to add to the second line, using additional fields for the bib entry, but it wouldn't work with the way the parsing is currently done (since it breaks down if any entry is missing any of the fields it's looking for). Seems like something that would be best put on hold until a real bibtex parser is added.

referenced this pull request
Closed

option for formatting of citation quick panel #92

Sounds good and I agree about the parser. I closed my pull request and switched to your autocomplete branch. Everything is working smoothly so far but I haven't really used it a lot so far.

Opening the quick panel with tab does not seem to work for cite keys like this \citep[34]{}. I can an error Ref/cite: unrecognized format. I think the problem is in the OLD_STYLE_CITE_REGEX or NEW_STYLE_CITE_REGEX variable but I am not really sure. I thought I will mention this here instead of opening another ticket.

 Westacular Adds awareness of optional arguments to cite completion, allowing com… …pletion to work for things like \citep[14]{ 404c0bf

Yeah, the cite regexes had no awareness of optional arguments to the cite command. 404c0bf fixes that. Thanks for pointing it out.

It works great with the commit as long as there is only one citation in the paragraph. If there are any other citations before the one I want to add, I get the Ref/cite: unrecognized format error. Can you reproduce that?

No, everything seems to be working for me. Could you post a text sample for where it's failing?

Yes, you are right. It was a problem with one of my custom key combinations. I have one that opens the quick select panel when I press tab and the cursor is inside the brackets of \citep{}, which seems to cause the problem. Sorry for the trouble. I have been using your autocomplete branch for a while now and it works smoothly. I hope it gets merged soon.

Owner

Does is also solve Issue 134?
(That is, does it parse entries separately, allowing that "author" may not exist?)

I writing my own script to fix that (and also show journal name in the search panel), but it does not use regexes (to avoid problems with nested {/}).

It goes like that (just functions):

entries = data.split("@")

def process_entry(entry):
level = 0
last_split = 0
splitted = []
for i, l in enumerate(entry):
if l == "{":
level += 1
if level == 1:
last_split = i + 1
elif l == "}":
level -= 1
if level == 0:
splitted.append(entry[last_split:i].strip())
break
elif level == 1 and l in [",", "="]:
splitted.append(entry[last_split:i].strip())
last_split = i + 1
return splitted

def extract_fields(splitted, fields):
pairs = []
for i, splinter in enumerate(splitted):
if splinter in fields:
pairs.append((splinter, bib_strip(splitted[i + 1])))
return dict(pairs)

def bib_strip(s):
if s[0] == "{" and s[-1] == "}":
return bib_strip(s[1:-1].strip())
else:
return s


No, it doesn't really fix the problem. It's still using the quick and dirty regexes to pull out the field values. It DOES avoid the problem of having the wrong author shown, but that's because in the case where there's an entry with no author field, it returns an error and aborts, instead of continuing with mismatched values.

Your approach is a much better idea, but there was talking of adding a proper full bib parser, so I didn't do much to change the current parsing code in my patches.

Good to hear that is fixes "wrong author" problem. :)

I wanted to fix it with regex, about observed that it cannot work in general (just, typically, fields does not contain ,\nauthor = { blah blah }; but in principle they could).

The "clean" solution is just above + actually running it:

results = []  # i.e. the first and second line in search palette
for entry in entries[1:]:
splitted = process_entry(entry)
bibkey = splitted[0]
di = extract_fields(splitted, ["title", "author", "journal", "year"])
line1 = "%s (%s)" % (di["title"], bibkey)
try:
line2 = "%s\t\t" % di["author"]
except:
line2 = "(no author provided)"
try:
line2 += "in %s " % di["journal"]
except:
pass
try:
line2 += "(%s)" % di["year"]
except:
pass
results.append((line1, line2))


However, as it is going to touch the same lines (and Pull request - Response time is non-trivial), I don't want to mess up with your commits (fixing a lot of things).

And anyway, we don't need full BibTeX parsing. E.g. I tried https://github.com/ptigas/bibpy but seems to be too slow for our purpose + messes with {/} anyway (but the good thing is that it also extracts author names and surnames).

Could you add support for other cross-ref­er­enc­ing commands like \cref (from the cleveref package), \vref (from the varioref package), or \autoref? I know it would be possible to map one of them to \ref via the \let command from within LaTeX but sometimes a distinction is necessary.

Actually, this branch already does add support for a variety of other cross-referencing commands, including all of the ones you mention.

Specifically, it will recognize and autocomplete with any of:

\pageref
\vref
\Vref
\autoref
\nameref
\cref
\Cref
\cpageref


Let me know if there's any I missed.

This is an awesome addition. Thank you for your work on this.

One very minor observation. I find that the "{" trigger would work better with the "," trigger if, after making a selection from the quick view panel following the "{" trigger, the caret stayed inside the closing bracket, instead of ending up outside of the closing bracket as it currently does. Thus, adding multiple citations requires one to backspace to the end of last citation keyword in order to activate the "," trigger.

Admittedly, people who do not often use multiple citations may not find this behavior desirable. Given that I use a keybinding specifically to step outside of brackets, however, I find it much easier to skip over a closing bracket if I only want a single citation than to jump back to activate the "," trigger if I want multiple citations.

Owner

Has this been merged into a branch, yet? I was just working on a StackOverflow question where the user had "auto_complete_selector": "source, text" and was trying to type the word "reform" but kept getting \ref{ auto-completed whenever "ref" was typed. S/He otherwise liked the auto-complete features and didn't want to disable it. I didn't have time to search the Issues this morning when I answered him, and my only suggested workaround was to completely disable latex_ref_completions.py by renaming it, deleting the .pyc file, and restarting Sublime. If I just change the contents of that file to what's in this commit, will that solve the problem, or are there other things that need to be fixed? Hopefully this can be committed soon! (wink wink nudge nudge)

@Westacular just a quick heads-up: I'm now finally able to work on the plugin some more!

Merging this will be a lot of work, BUT it's actually something I'd very very very much like to use myself! I hope to find some quiet time to do it soon. I may have questions for you. And THANKS!!!

Let me also thank @jlegewie and @yunjing here, and also make a note so I don't forget to thank them in the README once I pull this in.

OK, I couldn't resist and started playing with it :)

Right now I'm having the problem that my bibliography (which has grown "organically" over the years, i.e., it's got all kinds of crap, even though it works fine in both bibtex and LaTeXtools) is not parsed correctly. Too bad! I'll look into it.

Ok @Westacular and @jlegewie , if you check the Westacular-autocomplete branch, you will see my first stab at merging this. The key thing is that my crufty bib file brought up issues with the way we parse bibliographies (you inherited this from my code, so it's my fault...).

I have now modified the code in latex_cite_completion.py so as to parse the bib file differently. Basically, as I read the file, I keep track of matches for title, author, year and editor (in case there is no author---common), and then, as soon as I get an "@" with a keyword, I append the current values of title, etc. onto the corresponding arrays (titles, etc.). This way we are guaranteed to always have complete records, or at least placeholder text for missing entries.

I also had to fix a couple of minor issues with the year and author regexes.

Bottom line: I'd like to experiment with this some more. For instance, the parsing code is now a bit slow (on a slow machine) because it is an explicit iteration. More importantly, I want to use it in my own day-to-day work to see how robust it is. If it passes this "test", I'll merge it into the master branch.

Guys, THANK YOU for this AWESOME contribution! You scratched many of my own itches, and the functionality is outstanding!

Great that you are making progress! Westacular deserves basically all the credit...

Which branch are you using now. I might change to it and test as well. Here are some quick notes:

• We had some discussion about the default. I am not sure what Westacular's current pull request does but my original one left your default in tact. You might still want to consider changing the default. I am using ["{author_short} {year} - {title_short} ({keyword})","{title}"]
• I guess the best way would be to use a real bibtex parser. But that is a more work and probably not the right solution for now. Just something to keep in mind.
• One possible way to resolve the performance issue would be to store the parsing results and only update them when the bibtex file has changed. Each time the user wants to add a citation, LaTexTools would check whether the bibtex file has changed and if not, just use the references in memory. If the file has changed LatexTools could re-process the file.

But that are just some thoughts...

Hi @jlegewie , I followed github's instructions for non-automatically-mergeable patches. I created a local branch on my machine and pulled from

https://github.com/Westacular/LaTeXTools.git

This was two days ago. Then, I had to integrate the code with some fixes that had already gone into the master branch, etc.---that was all pretty straightforward. All the work (late-evening hacking session) went into the new bibtex parsing code.

To see what I've done, get the Westacular-autocomplete branch of the "official" LaTeXTools repository. I'll be working on that one, and if you want to contribute patches, they should go against that. Again, I think it's pretty much in sync with @Westacular 's own fork.

Re. speed, I also thought about caching, but we'll see. It turns out maybe I was too pessimistic. I only noticed a slight delay after e.g. entering \ref{ on my Samsung 7 Slate (pre-Win8 tablet) when in "Power Saving" mode (which is very aggressive), with my old, crufty, and 9401-lines long bib file. On my 2008 Macbook Pro, there is no delay. Even on the Windows tablet it's not so bad. In any case, in the next few weeks the Windows tablet will be my only machine, so I have every incentive to make things work as smoothly as possible!

Re. parser, sure, a complete parser would be nice to have, but not essential ATM. Right now we need to get keyword, title, author and year only. That's easy to do with ad-hoc code. As you will see, the code is pretty easy to read, and it also does the right thing (the old code was a kludge---each line was parsed multiple times, for instance, even in case of a match).

Right now the default is the "fancy" one you guys put in, which I love and which will make several users happy! Not to mention the fact that the multi-cite support totally ROCKS!

Again, I want to "battle test" this for a couple of days, do a few tweaks, and then merge with the master branch. But, feel free to make suggestions, etc. Do please take a look at my code in the Westacular-autocomplete branch.

THANKS!

@msiniscalchi We are happy to have you back.

For me autocomplete from @Westacular works great, even for >1MB bib file with lot of strange things (like LaTeX codes for non-latin characters in authors).

I added a few changes to that branch: #207.
I am curious if you (and @Westacular) like it.

How is performance from my branch with the westacular improvements?

As I said, for me (Mac Book, 2009) it works smoothly on 1MB bib file. But I don't know how it works for others.

...and as a side remark "no bib found" is very annoying when bibliography is in the file (as e.g. it is the almost-final version before sending to a journal or posting on arXiv).

@stared processing in-file bibliographies is going to require separate code, and won't be as neat. The reason is that each \bibitem entry does not follow a pre-specified format, so it's hard (read: impossible) to guess correctly what the author, title, journal, etc. are. However, I guess one can at least list the keywords.

@Westacular @jlegewie I've tweaked the code a bit more and have been testing it. If you can also test it, and no issue arises, I'll merge!

@msiniscalchi I know that the processing is different, so my point is not to implement it (at least for my workflow once bibliography is hardcoded into the tex file, I don't need that much to search it). However, it is ultra-annoying when every time I write \cite{ it raises an alert No bib files found!.

I am thinking what is the best way around (I don't want to silence it in every case - silenced errors are annoying). Any ideas? Maybe error msg only when a shortcut is used?

@stared got it. Now, there is an option to turn of all autocite/autoref completions. I added the following

• separate preferences to automatically fire ref and cite completions: cite_auto_trigger and ref_auto_trigger. By default completion is triggered, but you can change this setting in the usual LaTeXTools Preferences file

• probably more useful to you: you can also toggle autocompletion on and off from the keyboard, without messing with config files. The keybindings are c-l,t,a,c and c-l,t,a,c (where c-l as usual stands for control or command as the case may be). If you type c-l,t,?, the current toggle and pref settings are displayed in the status bar.

This should get rid of your problem. @Westacular and @jlegewie I am thinking about removing the "disable_ref_cite_auto_trigger" setting that you allow for in the keybinding, as the above subsumes it.

@Westacular @msiniscalchi Just now I saw than for some reason wrapping in environment (["super+l","super+n"], and other commands suing super two times) does not work in the Westacular-autocomplete branch. Are you experiencing the same thing?

merged commit 404c0bf into SublimeText:master

@msiniscalchi At least on the main branch everything works (i.e. both bibliography and double super). So it's fine, I am happy (and thankful).

Commits on Dec 8, 2012
1. Westacular authored
…and unsaved
2. Westacular authored
…e ref/cite dispatcher more tolerant of allowed formats
3. Westacular authored
…context is appropriate

i.e., if the cursor is immediately preceded by "\ref" or "\cite" or recognized variants.
4. Westacular authored
… unnamed file scenario
5. Westacular authored
…ommand into a function used by both.
6. Westacular authored
7. Westacular authored
… function. Also, fixed a few corner cases along the way.
8. Westacular authored
…s (shouldn't actually change what is/isn't matched)
9. Westacular authored
… \cite{}.
Commits on Dec 9, 2012
1. Westacular authored
2. Westacular authored
…t#118)
3. Westacular authored
…ept more of the forms that the completion functions internally accept.
Commits on Dec 10, 2012
1. Westacular authored
… typing.
2. Westacular authored
…es as used in the ref and cite completion functions.
3. Westacular authored
…quick panel from working except at the start of a line.
4. Westacular authored
…ef commands
5. Westacular authored
…et to true, stops { and , from automatically opening the ref/cite completion quick panels.
6. Westacular authored
See SublimeText#92
7. Westacular authored
8. Westacular authored
…ows keymaps.
Commits on Dec 11, 2012
1. Westacular authored
…statements)

The Sublime terminal apparently only handles ASCII, so printing unicode text to it causes exceptions. So I've disabled several superfluous print statements that can run into trouble there.

Also, the ref completion would hit an exception if it read a \label from a file that contained a non-ASCII character. I've added code to check for \usepackage[.*]{inputenc} and, if present, read the file using the appropriate decoder.

Similarly, I've added analogous code to the cite completion, although in that case it's only guarding against non-ASCII characters in .bib filenames; the contents of the .bib files are still assumed to be ASCII. This is a tricky problem since the character encoding of .bib files is a messy topic. (And since the bib parsing needs other fixes anyway, it's probably not worth worrying about it too much right now.)
2. Westacular authored
… the completion crashes or fails miserably, at least a { or , should be inserted.
Commits on Dec 12, 2012
1. Westacular authored
…o formatting options added for cite quick panel)
Commits on Dec 13, 2012
1. Westacular authored
…nd and the filled-in keyword unselected after inserting the completion.
Commits on Jan 8, 2013
1. Westacular authored
…pletion to work for things like \citep[14]{
18 Default (Linux).sublime-keymap
 @@ -41,6 +41,24 @@ LaTeX Package keymap for Linux {"key": "selector", "operator": "equal", "operand": "text.tex.latex"}], "command": "latex_ref_cite"}, + { "keys": ["{"], + "context": [ + {"key": "setting.disable_latex_ref_cite_auto_trigger", "operator": "not_equal", "operand": true}, + {"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": "\\\\(((eq|page|v|V|auto|name|c|C|cpage)?ref)|(cite([a-zX*]*?)(\$.*?\$){0,2}))$", "match_all": true}], + "command": "latex_ref_cite", "args": {"insert_char": "{"}}, + + { "keys": [","], + "context": [ + {"key": "setting.disable_latex_ref_cite_auto_trigger", "operator": "not_equal", "operand": true}, + {"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": "latex_ref_cite", "args": {"insert_char": ","}}, + // View PDF, jump to point, toggle editor/viewer focus and syncing behavior { "keys": ["ctrl+l","v"],
18 Default (OSX).sublime-keymap
 @@ -41,6 +41,24 @@ LaTeX Package keymap for OS X {"key": "selector", "operator": "equal", "operand": "text.tex.latex"}], "command": "latex_ref_cite"}, + { "keys": ["{"], + "context": [ + {"key": "setting.disable_latex_ref_cite_auto_trigger", "operator": "not_equal", "operand": true}, + {"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": "\\\\(((eq|page|v|V|auto|name|c|C|cpage)?ref)|(cite([a-zX*]*?)(\$.*?\$){0,2}))$", "match_all": true}], + "command": "latex_ref_cite", "args": {"insert_char": "{"}}, + + { "keys": [","], + "context": [ + {"key": "setting.disable_latex_ref_cite_auto_trigger", "operator": "not_equal", "operand": true}, + {"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": "latex_ref_cite", "args": {"insert_char": ","}}, + // View PDF, jump to point, toggle editor/viewer focus { "keys": ["super+l","v"], "context": [
18 Default (Windows).sublime-keymap
 @@ -41,6 +41,24 @@ LaTeX Package keymap for Windows {"key": "selector", "operator": "equal", "operand": "text.tex.latex"}], "command": "latex_ref_cite"}, + { "keys": ["{"], + "context": [ + {"key": "setting.disable_latex_ref_cite_auto_trigger", "operator": "not_equal", "operand": true}, + {"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": "\\\\(((eq|page|v|V|auto|name|c|C|cpage)?ref)|(cite([a-zX*]*?)(\$.*?\$){0,2}))$", "match_all": true}], + "command": "latex_ref_cite", "args": {"insert_char": "{"}}, + + { "keys": [","], + "context": [ + {"key": "setting.disable_latex_ref_cite_auto_trigger", "operator": "not_equal", "operand": true}, + {"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": "latex_ref_cite", "args": {"insert_char": ","}}, + // View PDF, jump to point, toggle editor/viewer focus { "keys": ["ctrl+l","v"], "context": [
25 LaTeXTools Preferences.sublime-settings
 @@ -9,6 +9,31 @@ // Sync PDF to current editor position after building (true) or not "forward_sync": true, + // Formating of references in quick panel + /* This preference sets the format of the quick panel to select citations using wildcards. + The setting is a list with one or two string using wildcards for author, title, keyword etc. + + Default setting: ["{title} ({keyword})","{author}"] + Format: + Can quantum-mechanical description of physical reality be considered complete? This is an non-existing subtitle to illustrate (einstein1935quantum) + Albert Einstein and B Podolsky and N Rosen + + Alternative: ["{author_short} {year} - {title_short} ({keyword})","{title}"] + Format: + Einstein et al. 1935 - Can quantum-mechanical description of physical reality be considered complete (einstein1935quantum) + Can quantum-mechanical description of physical reality be considered complete? This is an non-existing subtitle to illustrate + + Alternative: ["{author_short} {year} ({keyword})"] + Format: + Einstein et al. 1935 (einstein1935quantum) + + Valid wildcards: keyword, title, author, year, author_short, title_short + */ + "cite_panel_format": ["{author_short} {year} - {title_short} ({keyword})","{title}"], + + // Similarly, the formatting for the autocomplete panel: + "cite_autocomplete_format": "{keyword}: {title}", + // Settings that are specific to Linux platforms "linux": { // Command to invoke Python 2. Useful if you have both Python 2 and Python 3 on your system,
32 getTeXRoot.py
 @@ -2,7 +2,8 @@ # Parse magic comments to retrieve TEX root # Stops searching for magic comments at first non-comment line of file -# Returns root file or current file +# Returns root file or current file or None (if there is no root file, +# and the current buffer is an unnamed unsaved file) # Contributed by Sam Finn @@ -17,9 +18,23 @@ def get_tex_root(view): texFile = view.file_name() - for line in open(texFile, "rU").readlines(): + root = texFile + if texFile is None: + # We are in an unnamed, unsaved file. + # Read from the buffer instead. + if view.substr(0) != '%': + return None + reg = view.find(r"^%[^\n]*(\n%[^\n]*)*", 0) + if not reg: + return None + line_regs = view.lines(reg) + lines = map(view.substr, line_regs) + + else: + lines = open(texFile, "rU") + + for line in lines: if not line.startswith('%'): - root = texFile break else: # We have a comment match; check for a TEX root match @@ -30,9 +45,14 @@ def get_tex_root(view): # Create TEX root file name # If there is a TEX root path, use it # If the path is not absolute and a src path exists, pre-pend it - (texPath, texName) = os.path.split(texFile) - (rootPath, rootName) = os.path.split(mroot.group(1)) - root = os.path.join(texPath,rootPath,rootName) + root = mroot.group(1) + if not os.path.isabs(root) and texFile is not None: + (texPath, texName) = os.path.split(texFile) + root = os.path.join(texPath,root) root = os.path.normpath(root) break + + if isinstance(lines, file): + lines.close() + return root
571 latex_cite_completions.py
 @@ -3,6 +3,19 @@ import re import getTeXRoot + +class UnrecognizedCiteFormatError(Exception): pass +class NoBibFilesError(Exception): pass + +class BibParsingError(Exception): + def __init__(self, filename=""): + self.filename = filename + + +OLD_STYLE_CITE_REGEX = re.compile(r"([^_]*_)?([a-zX*]*?)etic(?:\\|\b)") +NEW_STYLE_CITE_REGEX = re.compile(r"([^{},]*)(?:,[^{},]*)*\{(?:\].*?$){0,2}([a-zX*]*?)etic\\") + + def match(rex, str): m = rex.match(str) if m: @@ -31,6 +44,21 @@ def find_bib_files(rootdir, src, bibfiles): return src_content = re.sub("%.*","",src_file.read()) + src_file.close() + + m = re.search(r"\\usepackage\[(.*?)$\{inputenc\}", src_content) + if m: + import codecs + f = None + try: + f = codecs.open(file_path, "r", m.group(1)) + src_content = re.sub("%.*", "", f.read()) + except: + pass + finally: + if f and not f.closed: + f.close() + bibtags = re.findall(r'\\bibliography\{[^\}]+\}', src_content) # extract absolute filepath for each bib file @@ -48,6 +76,204 @@ def find_bib_files(rootdir, src, bibfiles): input_f = re.search(r'\{([^\}]+)', f).group(1) find_bib_files(rootdir, input_f, bibfiles) + +def get_cite_completions(view, point, autocompleting=False): + line = view.substr(sublime.Region(view.line(point).a, point)) + # print line + + # Reverse, to simulate having the regex + # match backwards (cool trick jps btw!) + line = line[::-1] + #print line + + # Check the first location looks like a cite_, but backward + # NOTE: use lazy match for the fancy cite part!!! + # NOTE2: restrict what to match for fancy cite + rex = OLD_STYLE_CITE_REGEX + expr = match(rex, line) + + # See first if we have a cite_ trigger + if expr: + # Do not match on plain "cite[a-zX*]*?" when autocompleting, + # in case the user is typing something else + if autocompleting and re.match(r"[a-zX*]*etic\\?", expr): + raise UnrecognizedCiteFormatError() + # Return the completions + prefix, fancy_cite = rex.match(expr).groups() + preformatted = False + if prefix: + prefix = prefix[::-1] # reverse + prefix = prefix[1:] # chop off _ + else: + prefix = "" # because this could be a None, not "" + if fancy_cite: + fancy_cite = fancy_cite[::-1] + # fancy_cite = fancy_cite[1:] # no need to chop off? + if fancy_cite[-1] == "X": + fancy_cite = fancy_cite[:-1] + "*" + else: + fancy_cite = "" # again just in case + # print prefix, fancy_cite + + # Otherwise, see if we have a preformatted \cite{} + else: + rex = NEW_STYLE_CITE_REGEX + expr = match(rex, line) + + if not expr: + raise UnrecognizedCiteFormatError() + + preformatted = True + prefix, fancy_cite = rex.match(expr).groups() + if prefix: + prefix = prefix[::-1] + else: + prefix = "" + if fancy_cite: + fancy_cite = fancy_cite[::-1] + if fancy_cite[-1] == "X": + fancy_cite = fancy_cite[:-1] + "*" + else: + fancy_cite = "" + # print prefix, fancy_cite + + # Reverse back expr + expr = expr[::-1] + + post_brace = "}" + + if not preformatted: + # Replace cite_blah with \cite{blah + expr_region = sublime.Region(point - len(expr), point) + #print expr, view.substr(expr_region) + ed = view.begin_edit() + pre_snippet = "\cite" + fancy_cite + "{" + view.replace(ed, expr_region, pre_snippet + prefix) + # save prefix begin and endpoints points + new_point_a = point - len(expr) + len(pre_snippet) + new_point_b = new_point_a + len(prefix) + view.end_edit(ed) + + else: + # Don't include post_brace if it's already present + suffix = view.substr(sublime.Region(point, point + len(post_brace))) + new_point_a = point - len(prefix) + new_point_b = point + if post_brace == suffix: + post_brace = "" + + #### GET COMPLETIONS HERE ##### + + root = getTeXRoot.get_tex_root(view) + + if root is None: + # This is an unnamed, unsaved file + # FIXME: should probably search the buffer instead of giving up + raise NoBibFilesError() + + print "TEX root: " + repr(root) + bib_files = [] + find_bib_files(os.path.dirname(root), root, bib_files) + # remove duplicate bib files + bib_files = list(set(bib_files)) + print "Bib files found: ", + print repr(bib_files) + + if not bib_files: + # sublime.error_message("No bib files found!") # here we can! + raise NoBibFilesError() + + bib_files = ([x.strip() for x in bib_files]) + + print "Files:" + print repr(bib_files) + + completions = [] + kp = re.compile(r'@[^\{]+\{(.+),') + # new and improved regex + # we must have "title" then "=", possibly with spaces + # then either {, maybe repeated twice, or " + # then spaces and finally the title + # We capture till the end of the line as maybe entry is broken over several lines + # and in the end we MAY but need not have }'s and "s + tp = re.compile(r'\btitle\s*=\s*(?:\{+|")\s*(.+)', re.IGNORECASE) # note no comma! + # Tentatively do the same for author + ap = re.compile(r'\bauthor\s*=\s*(?:\{|")\s*(.+)\},?', re.IGNORECASE) + # kp2 = re.compile(r'([^\t]+)\t*') + # and year... + yp = re.compile(r'\byear\s*=\s*(?:\{+|")\s*(\d+)[\}"]?', re.IGNORECASE) + + for bibfname in bib_files: + # # THIS IS NO LONGER NEEDED as find_bib_files() takes care of it + # if bibfname[-4:] != ".bib": + # bibfname = bibfname + ".bib" + # texfiledir = os.path.dirname(view.file_name()) + # # fix from Tobias Schmidt to allow for absolute paths + # bibfname = os.path.normpath(os.path.join(texfiledir, bibfname)) + # print repr(bibfname) + try: + bibf = open(bibfname) + except IOError: + print "Cannot open bibliography file %s !" % (bibfname,) + sublime.status_message("Cannot open bibliography file %s !" % (bibfname,)) + continue + else: + bib = bibf.readlines() + bibf.close() + print "%s has %s lines" % (repr(bibfname), len(bib)) + # note Unicode trickery + keywords = [kp.search(line).group(1).decode('ascii', 'ignore') for line in bib if line[0] == '@'] + titles = [tp.search(line).group(1).decode('ascii', 'ignore') for line in bib if tp.search(line)] + authors = [ap.search(line).group(1).decode('ascii', 'ignore') for line in bib if ap.search(line)] + years = [yp.search(line).group(1).decode('ascii', 'ignore') for line in bib if yp.search(line)] + + # print zip(keywords,titles,authors) + + if not len(keywords) == len(titles) == len(authors) == len(years): + # print "Bibliography " + repr(bibfname) + " is broken!" + raise BibParsingError(bibfname) + + print "Found %d total bib entries" % (len(keywords),) + + # Filter out }'s and ,'s at the end. Ugly! + nobraces = re.compile(r'\s*,*\}*(.+)') + titles = [nobraces.search(t[::-1]).group(1)[::-1] for t in titles] + titles = [t.replace('{\\textquoteright}', '') for t in titles] + + # format author field + def format_author(authors): + # print(authors) + # split authors using ' and ' and get last name for 'last, first' format + authors = [a.split(", ")[0].strip(' ') for a in authors.split(" and ")] + # get last name for 'first last' format (preserve {...} text) + authors = [a.split(" ")[-1] if a[-1] != '}' or a.find('{') == -1 else re.sub(r'{|}', '', a[len(a) - a[::-1].index('{'):-1]) for a in authors] + # authors = [a.split(" ")[-1] for a in authors] + # truncate and add 'et al.' + if len(authors) > 2: + authors = authors[0] + " et al." + else: + authors = ' & '.join(authors) + # return formated string + # print(authors) + return authors + + # format list of authors + authors_short = [format_author(author) for author in authors] + + # short title + sep = re.compile(":|\.|\?") + titles_short = [sep.split(title)[0] for title in titles] + titles_short = [title[0:60] + '...' if len(title) > 60 else title for title in titles_short] + + # completions object + completions += zip(keywords, titles, authors, years, authors_short, titles_short) + + + #### END COMPLETIONS HERE #### + + return completions, prefix, post_brace, new_point_a, new_point_b + + # Based on html_completions.py # see also latex_ref_completions.py # @@ -77,161 +303,31 @@ def on_query_completions(self, view, prefix, locations): "text.tex.latex"): return [] - # Get the contents of line 0, from the beginning of the line to - # the current point - l = locations[0] - line = view.substr(sublime.Region(view.line(l).a, l)) - - - # Reverse, to simulate having the regex - # match backwards (cool trick jps btw!) - line = line[::-1] - #print line - - # Check the first location looks like a ref, but backward - # NOTE: use lazy match for the fancy cite part!!! - # NOTE2: restrict what to match for fancy cite - rex = re.compile("([^_]*_)?([a-zX]*?)etic") - expr = match(rex, line) - #print expr - - # See first if we have a cite_ trigger - if expr: - # Return the completions - prefix, fancy_cite = rex.match(expr).groups() - preformatted = False - post_brace = "}" - if prefix: - prefix = prefix[::-1] # reverse - if prefix[0]=='_': - prefix = prefix[1:] # chop off if there was a _ - else: - prefix = "" # because this could be a None, not "" - if fancy_cite: - fancy_cite = fancy_cite[::-1] - # fancy_cite = fancy_cite[1:] # no need to chop off? - if fancy_cite[-1] == "X": - fancy_cite = fancy_cite[:-1] + "*" - else: - fancy_cite = "" # just in case it's a None - print prefix, fancy_cite - - # Otherwise, see if we have a preformatted \cite{} - else: - rex = re.compile(r"([^{}]*)\{?([a-zX*]*?)etic\\") - expr = match(rex, line) - - if not expr: - return [] - - preformatted = True - post_brace = "" - prefix, fancy_cite = rex.match(expr).groups() - if prefix: - prefix = prefix[::-1] - else: - prefix = "" - if fancy_cite: - fancy_cite = fancy_cite[::-1] - if fancy_cite[-1] == "X": - fancy_cite = fancy_cite[:-1] + "*" - else: - fancy_cite = "" - print prefix, fancy_cite - - # Reverse back expr - expr = expr[::-1] - - if not preformatted: - # Replace cite expression with "C" to save space in drop-down menu - expr_region = sublime.Region(l-len(expr),l) - #print expr, view.substr(expr_region) - ed = view.begin_edit() - expr = "\cite" + fancy_cite + "{" + prefix - view.replace(ed, expr_region, expr) - view.end_edit(ed) - - - completions = ["TEST"] + point = locations[0] - #### GET COMPLETIONS HERE ##### - root = getTeXRoot.get_tex_root(view) - - print "TEX root: " + repr(root) - bib_files = [] - find_bib_files(os.path.dirname(root),root,bib_files) - # remove duplicate bib files - bib_files = list(set(bib_files)) - print "Bib files found: ", - print repr(bib_files) - - if not bib_files: - print "Error!" + try: + completions, prefix, post_brace, new_point_a, new_point_b = get_cite_completions(view, point, autocompleting=True) + except UnrecognizedCiteFormatError: + return [] + except NoBibFilesError: + sublime.status_message("No bib files found!") + return [] + except BibParsingError as e: + sublime.status_message("Bibliography " + e.filename + " is broken!") return [] - bib_files = ([x.strip() for x in bib_files]) - - print "Files:" - print repr(bib_files) - - completions = [] - kp = re.compile(r'@[^\{]+\{(.+),') - # new and improved regex - # we must have "title" then "=", possibly with spaces - # then either {, maybe repeated twice, or " - # then spaces and finally the title - # We capture till the end of the line as maybe entry is broken over several lines - # and in the end we MAY but need not have }'s and "s - tp = re.compile(r'\btitle\s*=\s*(?:\{+|")\s*(.+)', re.IGNORECASE) # note no comma! - kp2 = re.compile(r'([^\t]+)\t*') - - for bibfname in bib_files: - # # THIS IS NO LONGER NEEDED as find_bib_files() takes care of it - # if bibfname[-4:] != ".bib": - # bibfname = bibfname + ".bib" - # texfiledir = os.path.dirname(view.file_name()) - # # fix from Tobias Schmidt to allow for absolute paths - # bibfname = os.path.normpath(os.path.join(texfiledir, bibfname)) - # print repr(bibfname) - try: - bibf = open(bibfname) - except IOError: - print "Cannot open bibliography file %s !" % (bibfname,) - sublime.status_message("Cannot open bibliography file %s !" % (bibfname,)) - continue - else: - bib = bibf.readlines() - bibf.close() - print "%s has %s lines" % (repr(bibfname), len(bib)) - # note Unicode trickery - keywords = [kp.search(line).group(1).decode('ascii','ignore') for line in bib if line[0] == '@'] - titles = [tp.search(line).group(1).decode('ascii','ignore') for line in bib if tp.search(line)] - if len(keywords) != len(titles): - print "Bibliography " + repr(bibfname) + " is broken!" - # Filter out }'s and ,'s at the end. Ugly! - nobraces = re.compile(r'\s*,*\}*(.+)') - titles = [nobraces.search(t[::-1]).group(1)[::-1] for t in titles] - completions += zip(keywords, titles) - - - #### END COMPLETIONS HERE #### - - print "Found %d completions" % (len(completions),) if prefix: - completions = [comp for comp in completions if prefix.lower() in "%s %s" % (comp[0].lower(),comp[1].lower())] + completions = [comp for comp in completions if prefix.lower() in "%s %s" % (comp[0].lower(), comp[1].lower())] + prefix += " " - # popup is 40chars wide... - t_end = 80 - len(expr) - r = [(prefix + " "+title[:t_end], keyword + post_brace) - for (keyword, title) in completions] + # get preferences for formating of autocomplete entries + s = sublime.load_settings("LaTeXTools Preferences.sublime-settings") + cite_autocomplete_format = s.get("cite_autocomplete_format", "{keyword}: {title}") - print "%d bib entries matching %s" % (len(r), prefix) + r = [(prefix + cite_autocomplete_format.format(keyword=keyword, title=title, author=author, year=year, author_short=author_short, title_short=title_short), + keyword + post_brace) for (keyword, title, author, year, author_short, title_short) in completions] - # def on_done(i): - # print "latex_cite_completion called with index %d" % (i,) - # print "selected" + r[i][1] - - # print view.window() + # print "%d bib entries matching %s" % (len(r), prefix) return r @@ -250,171 +346,48 @@ def run(self, edit): "text.tex.latex"): return - # Get the contents of the current line, from the beginning of the line to - # the current point - line = view.substr(sublime.Region(view.line(point).a, point)) - print line - - - # Reverse, to simulate having the regex - # match backwards (cool trick jps btw!) - line = line[::-1] - #print line - - # Check the first location looks like a cite_, but backward - # NOTE: use lazy match for the fancy cite part!!! - # NOTE2: restrict what to match for fancy cite - rex = re.compile("([^_]*_)?([a-zX]*?)etic") - expr = match(rex, line) - - # See first if we have a cite_ trigger - if expr: - # Return the completions - prefix, fancy_cite = rex.match(expr).groups() - preformatted = False - post_brace = "}" - if prefix: - prefix = prefix[::-1] # reverse - prefix = prefix[1:] # chop off - else: - prefix = "" # just in case it's None, though here - # it shouldn't happen! - if fancy_cite: - fancy_cite = fancy_cite[::-1] - # fancy_cite = fancy_cite[1:] # no need to chop off? - if fancy_cite[-1] == "X": - fancy_cite = fancy_cite[:-1] + "*" - else: - fancy_cite = "" # again just in case - print prefix, fancy_cite - - # Otherwise, see if we have a preformatted \cite{} - else: - rex = re.compile(r"([^{}]*)\{?([a-zX*]*?)etic\\") - expr = match(rex, line) - - if not expr: - return [] - - preformatted = True - post_brace = "" - prefix, fancy_cite = rex.match(expr).groups() - if prefix: - prefix = prefix[::-1] - else: - prefix = "" - if fancy_cite: - fancy_cite = fancy_cite[::-1] - if fancy_cite[-1] == "X": - fancy_cite = fancy_cite[:-1] + "*" - else: - fancy_cite = "" - print prefix, fancy_cite - - # Reverse back expr - expr = expr[::-1] - - #### GET COMPLETIONS HERE ##### - - root = getTeXRoot.get_tex_root(view) - - print "TEX root: " + repr(root) - bib_files = [] - find_bib_files(os.path.dirname(root),root,bib_files) - # remove duplicate bib files - bib_files = list(set(bib_files)) - print "Bib files found: ", - print repr(bib_files) - - if not bib_files: - sublime.error_message("No bib files found!") # here we can! - return [] - bib_files = ([x.strip() for x in bib_files]) - - print "Files:" - print repr(bib_files) - - completions = [] - kp = re.compile(r'@[^\{]+\{(.+),') - # new and improved regex - # we must have "title" then "=", possibly with spaces - # then either {, maybe repeated twice, or " - # then spaces and finally the title - # We capture till the end of the line as maybe entry is broken over several lines - # and in the end we MAY but need not have }'s and "s - tp = re.compile(r'\btitle\s*=\s*(?:\{+|")\s*(.+)', re.IGNORECASE) # note no comma! - # Tentatively do the same for author - ap = re.compile(r'\bauthor\s*=\s*(?:\{+|")\s*(.+)', re.IGNORECASE) - kp2 = re.compile(r'([^\t]+)\t*') - - for bibfname in bib_files: - # # NO LONGER NEEDED: see above - # if bibfname[-4:] != ".bib": - # bibfname = bibfname + ".bib" - # texfiledir = os.path.dirname(view.file_name()) - # # fix from Tobias Schmidt to allow for absolute paths - # bibfname = os.path.normpath(os.path.join(texfiledir, bibfname)) - # print repr(bibfname) - try: - bibf = open(bibfname) - except IOError: - print "Cannot open bibliography file %s !" % (bibfname,) - sublime.status_message("Cannot open bibliography file %s !" % (bibfname,)) - continue - else: - bib = bibf.readlines() - bibf.close() - print "%s has %s lines" % (repr(bibfname), len(bib)) - # note Unicode trickery - keywords = [kp.search(line).group(1).decode('ascii','ignore') for line in bib if line[0] == '@'] - titles = [tp.search(line).group(1).decode('ascii','ignore') for line in bib if tp.search(line)] - authors = [ap.search(line).group(1).decode('ascii','ignore') for line in bib if ap.search(line)] - -# print zip(keywords,titles,authors) - - if len(keywords) != len(titles): - print "Bibliography " + repr(bibfname) + " is broken!" - return - - # if len(keywords) != len(authors): - # print "Bibliography " + bibfname + " is broken (authors)!" - # return - - print "Found %d total bib entries" % (len(keywords),) - - # Filter out }'s and ,'s at the end. Ugly! - nobraces = re.compile(r'\s*,*\}*(.+)') - titles = [nobraces.search(t[::-1]).group(1)[::-1] for t in titles] - authors = [nobraces.search(a[::-1]).group(1)[::-1] for a in authors] - completions += zip(keywords, titles, authors) - - #### END COMPLETIONS HERE #### + try: + completions, prefix, post_brace, new_point_a, new_point_b = get_cite_completions(view, point) + except UnrecognizedCiteFormatError: + sublime.error_message("Not a recognized format for citation completion") + return + except NoBibFilesError: + sublime.error_message("No bib files found!") + return + except BibParsingError as e: + sublime.error_message("Bibliography " + e.filename + " is broken!") + return # filter against keyword, title, or author if prefix: completions = [comp for comp in completions if prefix.lower() in "%s %s %s" \ - % (comp[0].lower(),comp[1].lower(), comp[2].lower())] + % (comp[0].lower(), comp[1].lower(), comp[2].lower())] # Note we now generate citation on the fly. Less copying of vectors! Win! def on_done(i): print "latex_cite_completion called with index %d" % (i,) - + # Allow user to cancel if i<0: return - last_brace = "}" if not preformatted else "" - cite = "\\cite" + fancy_cite + "{" + completions[i][0] + last_brace + cite = completions[i][0] + post_brace - print "selected %s:%s by %s" % completions[i] + # print "selected %s:%s by %s" % completions[i][0:3] # Replace cite expression with citation - expr_region = sublime.Region(point-len(expr),point) + expr_region = sublime.Region(new_point_a, new_point_b) ed = view.begin_edit() view.replace(ed, expr_region, cite) view.end_edit(ed) - - - view.window().show_quick_panel([[title + " (" + keyword+ ")", author] \ - for (keyword,title, author) in completions], on_done) - - + # Unselect the replaced region and leave the caret at the end + caret = view.sel()[0].b + view.sel().subtract(view.sel()[0]) + view.sel().add(sublime.Region(caret, caret)) + + # get preferences for formating of quick panel + s = sublime.load_settings("LaTeXTools Preferences.sublime-settings") + cite_panel_format = s.get("cite_panel_format", ["{title} ({keyword})", "{author}"]) + + # show quick + view.window().show_quick_panel([[str.format(keyword=keyword, title=title, author=author, year=year, author_short=author_short, title_short=title_short) for str in cite_panel_format] \ + for (keyword, title, author, year, author_short, title_short) in completions], on_done)
19 latex_ref_cite_completions.py
 @@ -5,11 +5,13 @@ import sublime, sublime_plugin import re +from latex_cite_completions import OLD_STYLE_CITE_REGEX, NEW_STYLE_CITE_REGEX +from latex_ref_completions import OLD_STYLE_REF_REGEX, NEW_STYLE_REF_REGEX class LatexRefCiteCommand(sublime_plugin.TextCommand): # Remember that this gets passed an edit object - def run(self, edit): + def run(self, edit, insert_char=""): # get view and location of first selection, which we expect to be just the cursor position view = self.view point = view.sel()[0].b @@ -20,23 +22,24 @@ def run(self, edit): "text.tex.latex"): return + if insert_char: + ed = view.begin_edit() + point += view.insert(ed, point, insert_char) + view.end_edit(ed) + # Get the contents of the current line, from the beginning of the line to # the current point line = view.substr(sublime.Region(view.line(point).a, point)) - print line + # print line # Reverse line = line[::-1] - rex_ref_new = re.compile(r"[^{]*\{fer") - rex_ref_old = re.compile(r".*_p?fer") - rex_cite_new = re.compile(r".*etic\\") - rex_cite_old = re.compile(r".*_[a-zA-Z]*etic") - if re.match(rex_ref_old, line) or re.match(rex_ref_new, line): + if re.match(OLD_STYLE_REF_REGEX, line) or re.match(NEW_STYLE_REF_REGEX, line): print "Dispatching ref" view.run_command("latex_ref") - elif re.match(rex_cite_old, line) or re.match(rex_cite_new, line): + elif re.match(OLD_STYLE_CITE_REGEX, line) or re.match(NEW_STYLE_CITE_REGEX, line): print "Dispatching cite" view.run_command("latex_cite") else:
295 latex_ref_completions.py
 @@ -4,6 +4,14 @@ import getTeXRoot +class UnrecognizedRefFormatError(Exception): pass + +_ref_special_commands = "|".join(["", "eq", "page", "v", "V", "auto", "name", "c", "C", "cpage"])[::-1] + +OLD_STYLE_REF_REGEX = re.compile(r"([^_]*_)?(p)?fer(" + _ref_special_commands + r")?(?:\\|\b)") +NEW_STYLE_REF_REGEX = re.compile(r"([^{}]*)\{fer(" + _ref_special_commands + r")?\\(\()?") + + def match(rex, str): m = rex.match(str) if m: @@ -27,19 +35,127 @@ def find_labels_in_files(rootdir, src, labels): # read src file and extract all label tags try: - with open(file_path, "r") as src_file: - src_content = re.sub("%.*", "", src_file.read()) - labels += re.findall(r'\\label\{([^\{\}]+)\}', src_content) + src_file = open(file_path, "r") except IOError: sublime.status_message("LaTeXTools WARNING: cannot find included file " + file_path) print "WARNING! I can't find it! Check your \\include's and \\input's." return + src_content = re.sub("%.*", "", src_file.read()) + src_file.close() + + m = re.search(r"\\usepackage$(.*?)$\{inputenc\}", src_content) + if m: + import codecs + f = None + try: + f = codecs.open(file_path, "r", m.group(1)) + src_content = re.sub("%.*", "", f.read()) + except: + pass + finally: + if f and not f.closed: + f.close() + + labels += re.findall(r'\\label\{([^{}]+)\}', src_content) + # search through input tex files recursively for f in re.findall(r'\\(?:input|include)\{([^\{\}]+)\}', src_content): find_labels_in_files(rootdir, f, labels) +# get_ref_completions forms the guts of the parsing shared by both the +# autocomplete plugin and the quick panel command +def get_ref_completions(view, point, autocompleting=False): + # Get contents of line from start up to point + line = view.substr(sublime.Region(view.line(point).a, point)) + # print line + + # Reverse, to simulate having the regex + # match backwards (cool trick jps btw!) + line = line[::-1] + #print line + + # Check the first location looks like a ref, but backward + rex = OLD_STYLE_REF_REGEX + expr = match(rex, line) + # print expr + + if expr: + # Do not match on plain "ref" when autocompleting, + # in case the user is typing something else + if autocompleting and re.match(r"p?fer(?:" + _ref_special_commands + r")?\\?", expr): + raise UnrecognizedRefFormatError() + # Return the matched bits, for mangling + prefix, has_p, special_command = rex.match(expr).groups() + preformatted = False + if prefix: + prefix = prefix[::-1] # reverse + prefix = prefix[1:] # chop off "_" + else: + prefix = "" + #print prefix, has_p, special_command + + else: + # Check to see if the location matches a preformatted "\ref{blah" + rex = NEW_STYLE_REF_REGEX + expr = match(rex, line) + + if not expr: + raise UnrecognizedRefFormatError() + + preformatted = True + # Return the matched bits (barely needed, in this case) + prefix, special_command, has_p = rex.match(expr).groups() + if prefix: + prefix = prefix[::-1] # reverse + else: + prefix = "" + #print prefix, has_p, special_command + + pre_snippet = "\\" + special_command[::-1] + "ref{" + post_snippet = "}" + + if has_p: + pre_snippet = "(" + pre_snippet + post_snippet = post_snippet + ")" + + if not preformatted: + # Replace ref_blah with \ref{blah + expr_region = sublime.Region(point - len(expr), point) + #print expr[::-1], view.substr(expr_region) + ed = view.begin_edit() + view.replace(ed, expr_region, pre_snippet + prefix) + # save prefix begin and endpoints points + new_point_a = point - len(expr) + len(pre_snippet) + new_point_b = new_point_a + len(prefix) + view.end_edit(ed) + + else: + # Don't include post_snippet if it's already present + suffix = view.substr(sublime.Region(point, point + len(post_snippet))) + new_point_a = point - len(prefix) + new_point_b = point + if post_snippet == suffix: + post_snippet = "" + + completions = [] + # Check the file buffer first: + # 1) in case there are unsaved changes + # 2) if this file is unnamed and unsaved, get_tex_root will fail + view.find_all(r'\\label\{([^\{\}]+)\}', 0, '\\1', completions) + + root = getTeXRoot.get_tex_root(view) + if root: + print "TEX root: " + repr(root) + find_labels_in_files(os.path.dirname(root), root, completions) + + # remove duplicates + completions = list(set(completions)) + + return completions, prefix, post_snippet, new_point_a, new_point_b + + # Based on html_completions.py # # It expands references; activated by @@ -66,85 +182,12 @@ def on_query_completions(self, view, prefix, locations): "text.tex.latex"): return [] - # Get the contents of line 0, from the beginning of the line to - # the current point - l = locations[0] - line = view.substr(sublime.Region(view.line(l).a, l)) - - # Reverse, to simulate having the regex - # match backwards (cool trick jps btw!) - line = line[::-1] - #print line - - # Check the first location looks like a ref, but backward - rex = re.compile("([^_]*_)?(p)?fer(qe)?") - expr = match(rex, line) - # print expr - - if expr: - # Return the matched bits, for mangling - prefix, has_p, has_eq = rex.match(expr).groups() - preformatted = False - if prefix: - prefix = prefix[::-1] # reverse - prefix = prefix[1:] # chop off # - else: - prefix = "" - #print prefix, has_p, has_eq - - else: - # Check to see if the location matches a preformatted "\ref{blah" - rex = re.compile(r"([^{}]*)\{fer(qe)?\\(\()?") - expr = match(rex, line) - - if not expr: - return [] - - preformatted = True - # Return the matched bits (barely needed, in this case) - prefix, has_eq, has_p = rex.match(expr).groups() - if prefix: - prefix = prefix[::-1] # reverse - else: - prefix = "" - #print prefix, has_p, has_eq - - if has_p: - pre_snippet = "(\\ref{" - post_snippet = "})" - elif has_eq: - pre_snippet = "\\eqref{" - post_snippet = "}" - else: - pre_snippet = "\\ref{" - post_snippet = "}" - - if not preformatted: - # Replace ref_blah with \ref{blah - expr_region = sublime.Region(l - len(expr), l) - #print expr[::-1], view.substr(expr_region) - ed = view.begin_edit() - view.replace(ed, expr_region, pre_snippet + prefix) - view.end_edit(ed) - - else: - # Don't include post_snippet if it's already present - suffix = view.substr(sublime.Region(l, l + len(post_snippet))) - if post_snippet == suffix: - post_snippet = "" - - completions = [] - # stop matching at FIRST } after \label{ - # I think we don't need to match here, as getTeXroot returns our file name - # if there is no TEX root directive - # view.find_all(r'\\label\{([^\{\}]+)\}', 0, '\\1', completions) + point = locations[0] - root = getTeXRoot.get_tex_root(view) - - print "TEX root: " + repr(root) - find_labels_in_files(os.path.dirname(root), root, completions) - # remove duplicate bib files - completions = list(set(completions)) + try: + completions, prefix, post_snippet, new_point_a, new_point_b = get_ref_completions(view, point, autocompleting=True) + except UnrecognizedRefFormatError: + return [] # r = [(label + "\t\\ref{}", label + post_snippet) for label in completions] r = [(label, label + post_snippet) for label in completions] @@ -168,86 +211,12 @@ def run(self, edit): "text.tex.latex"): return - # Get the contents of the current line, from the beginning of the line to - # the current point - line = view.substr(sublime.Region(view.line(point).a, point)) - print line - - # Reverse, to simulate having the regex - # match backwards (cool trick jps btw!) - line = line[::-1] - #print line - - # Check the first location looks like a ref, but backward - rex = re.compile("([^_]*_)?(p)?fer(qe)?") - expr = match(rex, line) - # print expr - - if expr: - # Return the matched bits, for mangling - prefix, has_p, has_eq = rex.match(expr).groups() - preformatted = False - if prefix: - prefix = prefix[::-1] # reverse - prefix = prefix[1:] # chop off # - else: - prefix = "" - #print prefix, has_p, has_eq - - else: - # Check to see if the location matches a preformatted "\ref{blah" - rex = re.compile(r"([^{}]*)\{fer(qe)?\\(\()?") - expr = match(rex, line) - - if not expr: - return [] - - preformatted = True - # Return the matched bits (barely needed, in this case) - prefix, has_eq, has_p = rex.match(expr).groups() - if prefix: - prefix = prefix[::-1] # reverse - else: - prefix = "" - #print prefix, has_p, has_eq - - if has_p: - pre_snippet = "(\\ref{" - post_snippet = "})" - elif has_eq: - pre_snippet = "\\eqref{" - post_snippet = "}" - else: - pre_snippet = "\\ref{" - post_snippet = "}" - - if not preformatted: - # Replace ref_blah with \ref{blah - expr_region = sublime.Region(point - len(expr), point) - #print expr[::-1], view.substr(expr_region) - ed = view.begin_edit() - view.replace(ed, expr_region, pre_snippet + prefix) - # save prefix begin and endpoints points - new_point_a = point - len(expr) + len(pre_snippet) - new_point_b = new_point_a + len(prefix) - view.end_edit(ed) - - else: - # Don't include post_snippet if it's already present - suffix = view.substr(sublime.Region(point, point + len(post_snippet))) - new_point_a = point - len(prefix) - new_point_b = point - if post_snippet == suffix: - post_snippet = "" - - completions = [] - - root = getTeXRoot.get_tex_root(view) + try: + completions, prefix, post_snippet, new_point_a, new_point_b = get_ref_completions(view, point) + except UnrecognizedRefFormatError: + sublime.error_message("Not a recognized format for reference completion") + return - print "TEX root: " + repr(root) - find_labels_in_files(os.path.dirname(root), root, completions) - # remove duplicate bib files - completions = list(set(completions)) # filter! Note matching is "less fuzzy" than ST2. Room for improvement... completions = [c for c in completions if prefix in c] @@ -266,11 +235,15 @@ def on_done(i): ref = completions[i] + post_snippet - print "selected %s" % completions[i] + # print "selected %s" % completions[i] # Replace ref expression with reference and possibly post_snippet expr_region = sublime.Region(new_point_a,new_point_b) ed = view.begin_edit() view.replace(ed, expr_region, ref) view.end_edit(ed) + # Unselect the replaced region and leave the caret at the end + caret = view.sel()[0].b + view.sel().subtract(view.sel()[0]) + view.sel().add(sublime.Region(caret, caret)) view.window().show_quick_panel(completions, on_done)
Something went wrong with that request. Please try again.