-
-
Notifications
You must be signed in to change notification settings - Fork 243
/
org-ref-citation-links.el
1429 lines (1244 loc) · 50.7 KB
/
org-ref-citation-links.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
;;; org-ref-citation-links.el --- citation links for org-ref -*- lexical-binding: t; -*-
;;
;; Copyright (C) 2024 John Kitchin
;; Author: John Kitchin <[email protected]>
;; Keywords: convenience
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;
;;; Commentary:
;;
;; This library provides hyper-functional citation links. These links can
;; contain common pre/post notes, and multiple citation keys that each have
;; their own pre/postnotes.
;;
;; These links are fontified to indicate if the citation keys are valid, and to
;; indicate the pre/post-note structure. They also have tooltips that show
;; information from the bibtex entry.
;;
;; Each link is functional, and clicking on one will open a hydra menu
;; `org-ref-citation-hydra/body' of actions that range from opening the bibtex
;; entry, notes, pdf or associated URL, to searching the internet for related
;; articles.
;;
;; Each citation link also has a local keymap on it, which provides keyboard
;; shortcuts for some actions like sorting, rearranging and navigating citation
;; links. See `org-ref-cite-keymap' for the key bindings.
;;
;; Each link exports to a corresponding LaTeX citation command, or can be
;; rendered with CSL for other kinds of exports like HTML, markdown, or ODT.
;;
;; This library also provides a minimal set of insertion functions that use
;; completion. You can also use the org link completion mechanism to insert a
;; citation.
;;
;; natmove like preprocessing is provided with `org-ref-cite-natmove'.
;;
;;; Code:
(require 'org-keys)
(require 'hydra)
(require 'xref)
(eval-when-compile (require 'subr-x))
(defvar bibtex-completion-cache)
(declare-function bibtex-completion-format-citation-cite "bibtex-completion")
(declare-function bibtex-completion-show-entry "bibtex-completion")
(defgroup org-ref-faces nil
"A group for faces in `org-ref'."
:group 'org-ref-faces)
(defface org-ref-cite-face
`((t (:inherit org-link
:foreground "forest green")))
"Color for cite-like links in org-ref."
:group 'org-ref-faces)
(defface org-ref-bad-cite-key-face
`((t (:inherit org-ref-cite-face
:foreground "red")))
"Color for bad cite keys in org-ref."
:group 'org-ref-faces)
(defface org-ref-cite-global-prefix/suffix-face
`((t (:inherit org-ref-cite-face :weight bold)))
"Face for global prefix/suffix in a cite link."
:group 'org-ref-faces)
(defface org-ref-cite-&-face
`((t (:inherit org-ref-cite-face :weight bold)))
"Face for the starting & in a cite key."
:group 'org-ref-faces)
(defface org-ref-cite-local-prefix/suffix-face
`((t (:inherit org-ref-cite-face :slant italic)))
"Face for local prefix/suffix in a cite link."
:group 'org-ref-faces)
(defface org-ref-cite-invalid-local-prefix/suffix-face
`((t (:inherit org-ref-cite-face :foreground "red")))
"Face for invalid local prefix/suffix in a cite link.
This is mostly for multicites and natbib."
:group 'org-ref-faces)
(defcustom org-ref-activate-cite-links t
"If non-nil use font-lock to activate citations.
In large documents with many citations activation can be slow.
Set this to nil to turn that off, which increase performance."
:type 'boolean
:group 'org-ref)
(defcustom org-ref-default-citation-link
"cite"
"The default type of citation link to use."
:type 'string
:group 'org-ref)
(defcustom org-ref-natbib-types
'(("cite" "basic citation")
("nocite" "add key to bibliography, but do not cite it in the text")
("citet" "textual, Jones et al. (1990)")
("citet*" "textual, full author list Jones, Baker, and Williams (1990)")
("citep" "parenthetical citation (Jones et al. (1990))")
("citep*" "parenthetical, full author list, (Jones, Baker, and Williams, 1990)")
("citealt" "same as citet, but without parentheses")
("citealt*" "same as citet, with full author list but without parentheses")
("citealp" "same as citep, but without parentheses")
("citealp*" "same as citep, with full author list, but without parentheses")
("citenum" "The number of the citation in the bibliography, e.g. 11")
("citetext" "text inserted in citation in the document")
("citeauthor" "Only the author, Jones et al.")
("citeauthor*" "The full author list, Jones, Baker, and Williams")
("citeyear" "The year of the citation, 2021")
("citeyearpar" "The year in parentheses (2021)")
("Citet" "like citet, but with forced capitalization for starting sentences")
("Citep" "like citep, but with forced capitalization for starting sentences")
("Citealt" "like citet, but with forced capitalization and no parentheses")
("Citealp" "like citep, but with forced capitalization and no parentheses")
("Citeauthor" "like citeauthor with forced capitalization")
("Citet*" "like citet, with full author list and forced capitalization")
("Citep*" "like citep, with full author list and forced capitalization")
("Citealt*" "like citet, with full author list, forced capitalization and no parentheses")
("Citealp*" "like citep, with full author list, forced capitalization and no parentheses")
("Citeauthor*" "like citeauthor with forced capitalization"))
"Natbib commands can have many references, and global prefix/suffix text.
For natbib cite commands see
http://tug.ctan.org/macros/latex/contrib/natbib/natnotes.pdf"
:type '(repeat :tag "List of citation types" (list string string))
:group 'org-ref)
(defcustom org-ref-biblatex-types
'(("Cite" "basic citation with capitalization")
("parencite" "similar to cite with parentheses")
("Parencite" "similar to cite with parentheses and capitalization")
("footcite" "Put the citation in a footnote")
("footcitetext" "Put the citation in a footnote using \footnotetext")
("textcite" "print the authors or editors as a subject of the sentence")
("Textcite" "print the authors or editors as a subject of the sentence with capitalization")
("smartcite" "like parencite in a footnote, and footcite in the body")
("Smartcite" "like parencite in a footnote, and footcite in the body with capitalization")
("cite*" "similar to cite, but prints the year or title")
("parencite*" "similar to parencite, but prints the year or title")
("supercite" "superscripted numeric citation (only in numeric styles)")
("autocite" "handles some punctuation nuances")
("Autocite" "handles some punctuation nuances with punctuation")
("autocite*" "same as autocite but * is passed to the backend")
("Autocite*" "same as Autocite but * is passed to the backend")
("citetitle" "the shorttitle or title field")
("citetitle*" "the full title")
("citeyear" "the year field")
("citeyear*" "the year field and extradate information if available")
("citedate" "the full date or year")
("citedate*" "the full date or year, including extradate information if available")
("citeurl" "the url field")
("fullcite" "create a full citation similar to what is in the bibliography")
("footfullcite" "create a full citation as a footnote")
;; "volcite" "Volcite" cannot support the syntax
("notecite" "print prenote and postnote, but no citation")
("Notecite" "print prenote and postnote, but no citation with capitalization")
("pnotecite" "similar to notecite with parentheses")
("Pnotecite" "similar to Notecite with parentheses")
("fnotecite" "similar to notecite in a footnote"))
"biblatex commands.
Biblatex commands
https://mirrors.ibiblio.org/CTAN/macros/latex/contrib/biblatex/doc/biblatex.pdf"
:type '(repeat :tag "List of citation types" string)
:group 'org-ref)
(defcustom org-ref-biblatex-multitypes
'(("cites" "multicite version of cite")
("Cites" "multicite version of Cite")
("parencites" "multicite version of parencite")
("Parencites" "multicite version of Parencite")
("footcites" "multicite version of footcite")
("footcitetexts" "multicite version of footcitetext")
("smartcites" "multicite version of smartcite")
("Smartcites" "multicite version of Smartcite")
("textcites" "multicite version of textcite")
("Textcites" "multicite version of Textcite")
("supercites" "multicite version of supercite")
("autocites" "multicite version of autocite")
("Autocites" "multicite version of Autocite"))
"Multicite link types"
:type '(repeat :tag "List of citation types" string)
:group 'org-ref)
(defcustom org-ref-cite-types
(append
org-ref-natbib-types
org-ref-biblatex-types
org-ref-biblatex-multitypes
;; for the bibentry package
'(("bibentry" "Insert the bibtex entry")))
"List of citation types known in `org-ref'."
:type '(repeat :tag "List of citation types (type description)" (list string string))
:group 'org-ref)
(defvar org-ref-insert-cite-function)
(defcustom org-ref-cite-keymap
(let ((map (copy-keymap org-mouse-map)))
;; Navigation keys
(define-key map (kbd "C-<left>") 'org-ref-previous-key)
(define-key map (kbd "C-<right>") 'org-ref-next-key)
;; rearrangement keys
(define-key map (kbd "S-<left>") (lambda () (interactive) (org-ref-swap-citation-link -1)))
(define-key map (kbd "S-<right>") (lambda () (interactive) (org-ref-swap-citation-link 1)))
(define-key map (kbd "S-<up>") 'org-ref-sort-citation-link)
(define-key map (kbd "<tab>") (lambda ()
(interactive)
(funcall org-ref-insert-cite-function)))
;; xref navigation
(define-key map (kbd "M-.") (lambda ()
(interactive)
(xref-push-marker-stack)
(org-ref-open-citation-at-point)))
map)
"Keymap for cite links."
:type 'symbol
:group 'org-ref)
(defcustom org-ref-cite-insert-version 3
"Default version to insert citations with.
The default is 3. In legacy documents you might prefer 2 though,
so this variable can be buffer- or directory local if you want.
version 2 means the links are not bracketed, and comma-separated keys.
version 3 means the links are bracketed, with semicolon-separated
&keys."
:type 'number
:group 'org-ref)
(defvar org-ref-citation-key-re
(rx-to-string
'(seq "&" (group-n 1 (one-or-more (any word "-.:?!`'/*@+|(){}<>&_^$#%~")))))
"Numbered regular expression for a version 3 cite key.
Key is in group 1.
Adapted from the expression in org-cite.")
(defun org-ref-cite-version (path)
"Return the version for PATH.
PATH is from a cite link.
Version 2 is separated by commas and uses plain keys.
Version 3 is separated by semicolons and uses &keys.
I think that if there is a & in path, it must be version 3."
(if (string-match "&" path)
3
2))
;; * Parsing/interpreting a citation path
(defun org-ref-parse-cite-path (path)
"Return a data structure representing the PATH.
the data structure is a plist with (:version :prefix :suffix :references).
Each reference is a plist with (:key :prefix :suffix)."
(pcase (org-ref-cite-version path)
(2
;; This will not have any prefix or suffix, since that was previously done in the desc.
(list :version 2 :references (cl-loop for key in (split-string path ",") collect
(list :key (string-trim key)))))
(3 (let ((citation-references (split-string path ";"))
(these-results '(:version 3)))
;; if the first ref doesn't match a key, it must be a global prefix
;; this pops the reference off.
(when (null (string-match org-ref-citation-key-re (cl-first citation-references)))
(setq these-results (append these-results (list :prefix (cl-first citation-references)))
citation-references (cdr citation-references)))
;; if the last ref doesn't match a key, then it is a global suffix
;; we remove the last one if this is true after getting the suffix.
(when (null (string-match org-ref-citation-key-re (car (last citation-references))))
(setq these-results (append these-results (list :suffix (car (last citation-references))))
citation-references (butlast citation-references)))
(setq these-results
(append these-results
(list
:references
(cl-loop for s in citation-references collect
(if (null (string-match org-ref-citation-key-re s))
(error "No matching key found in %s" s)
(let* ((key (match-string-no-properties 1 s))
(key-start (match-beginning 0))
(key-end (match-end 0))
(prefix (let ((p (substring s 0 key-start)))
(if (string= "" (string-trim p))
nil
p)))
(suffix (let ((s (substring s key-end)))
(if (string= "" (string-trim s))
nil
s))))
(list :key key :prefix prefix :suffix suffix)))))))))))
(defun org-ref-interpret-cite-data (data)
"Interpret the DATA structure from `org-ref-parse-cite-path' back
to a path string."
(pcase (plist-get data :version)
(2
(string-join (cl-loop for ref in (plist-get data :references) collect (plist-get ref :key)) ","))
(3
(concat
(when-let (prefix (plist-get data :prefix)) (concat prefix ";"))
(string-join (cl-loop for ref in (plist-get data :references) collect
(concat
(plist-get ref :prefix)
"&" (plist-get ref :key)
(plist-get ref :suffix)))
";")
(when-let (suffix (plist-get data :suffix)) (concat ";" suffix))))))
;; * Activating citation links
;;
;; We use the activate-func for fontification of pieces of each link.
(declare-function bibtex-completion-candidates "bibtex-completion")
(declare-function bibtex-completion-init "bibtex-completion")
(defvar bibtex-completion-bibliography)
(defvar bibtex-completion-display-formats-internal)
;; (defun org-ref-valid-keys ()
;; "Return a list of valid bibtex keys for this buffer.
;; This is used a lot in `org-ref-cite-activate' so it needs to be
;; fast, but also up to date."
;; ;; this seems to be needed, but we don't want to do this every time
;; (unless bibtex-completion-display-formats-internal
;; (bibtex-completion-init))
;; (let ((bibtex-completion-bibliography (org-ref-find-bibliography)))
;; (cl-loop for entry in (bibtex-completion-candidates)
;; collect
;; (cdr (assoc "=key=" (cdr entry))))))
(defun org-ref-valid-keys ()
"Return a list of valid bibtex keys for this buffer.
This is used a lot in `org-ref-cite-activate' so it needs to be
fast, but also up to date."
;; this seems to be needed, but we don't want to do this every time
(unless bibtex-completion-display-formats-internal
(bibtex-completion-init))
(let* ((files (org-ref-find-bibliography)))
(if (seq-every-p 'identity
(cl-loop for file in files
collect (assoc file bibtex-completion-cache)))
;; We have a cache for each file
;; bibtex-completion-cache contains (filename md5hash entries)
(cl-loop for entry in
(cl-loop
for file in files
append (cddr (assoc file bibtex-completion-cache)))
collect (cdr (assoc "=key=" (cdr entry))))
;; you need to get a cache because one or more of the files was not in the cache.
(let ((bibtex-completion-bibliography files))
(cl-loop for entry in (bibtex-completion-candidates)
collect
(cdr (assoc "=key=" (cdr entry))))))))
(defvar-local org-ref-valid-keys-hashes nil)
(defvar-local org-ref-valid-keys-cache nil)
(defun org-ref-valid-keys-cached ()
"Update `org-ref-valid-keys-cache` only when files changed."
(let ((local-hashes (cons bibtex-completion-bibliography
(mapcar 'cadr bibtex-completion-cache))))
(when (not (equal local-hashes org-ref-valid-keys-hashes))
(setq-local org-ref-valid-keys-hashes local-hashes)
(setq-local org-ref-valid-keys-cache (make-hash-table :test 'equal))
(cl-loop for entry in (org-ref-valid-keys)
do
(puthash entry t org-ref-valid-keys-cache))))
org-ref-valid-keys-cache)
(defun org-ref-cite-activate (start end path _bracketp)
"Activation function for a cite link.
START and END are the bounds of the link.
PATH has the citations in it."
(when (and org-ref-activate-cite-links
;; Try avoid fontifying org-cite elements. this is based on the
;; path containing @ which makes it likely to be an org-cite. Maybe
;; a text property is better, in case this is an issue in the
;; future.
(not (s-contains-p "@" path)))
(let* ((valid-keys (org-ref-valid-keys))
valid-key
substrings)
(goto-char start)
(pcase (org-ref-cite-version path)
(2
;; This makes the brackets visible, but we only need it when there is a
;; description.
(when (looking-at "\\[\\[\\(.*\\)\\]\\[\\(.*\\)\\]\\]")
(remove-text-properties start end '(invisible nil)))
(setq substrings (split-string path ","))
(cl-loop for key in substrings
do
;; get to the substring
(search-forward key end)
(put-text-property (match-beginning 0)
(match-end 0)
'keymap
org-ref-cite-keymap)
(put-text-property (match-beginning 0)
(match-end 0)
'cite-key
key)
(unless (member (string-trim key) valid-keys)
(put-text-property (match-beginning 0)
(match-end 0)
'face 'org-ref-bad-cite-key-face)
(put-text-property (match-beginning 0)
(match-end 0)
'help-echo "Key not found"))))
(3
(setq substrings (split-string path ";"))
(cl-loop for i from 0 for s in substrings
do
;; get to the substring
(search-forward s end)
(put-text-property (match-beginning 0)
(match-end 0)
'keymap
org-ref-cite-keymap)
(let* (key-begin
key-end
key)
;; Look for a key. common pre/post notes do not have keys in them.
(save-match-data
(when (string-match org-ref-citation-key-re s)
(setq key (match-string-no-properties 1 s)
valid-key (member key valid-keys))))
;; these are global prefix/suffixes
(when (and (or (= i 0)
(= i (- (length substrings) 1)))
(null key))
(put-text-property (match-beginning 0) (match-end 0)
'face 'org-ref-cite-global-prefix/suffix-face)
(put-text-property (match-beginning 0) (match-end 0)
'help-echo "Global prefix/suffix"))
;; we have a key. we have to re-search to get its position
(when key
(save-excursion
(save-match-data
(search-backward (concat "&" key))
(setq key-begin (match-beginning 0)
key-end (match-end 0))))
;; mark the &
(put-text-property key-begin (+ 1 key-begin)
'face 'org-ref-cite-&-face)
;; store key on the whole thing
(put-text-property (match-beginning 0)
(match-end 0)
'cite-key
key)
;; fontify any prefix /suffix text
(put-text-property (match-beginning 0) key-begin
'face 'org-ref-cite-local-prefix/suffix-face)
(put-text-property key-end (match-end 0)
'face 'org-ref-cite-local-prefix/suffix-face)
;; bad key activation
(unless valid-key
(put-text-property key-begin key-end
'face 'font-lock-warning-face)
(put-text-property key-begin key-end
'help-echo "Key not found"))))))))))
;; * Following citation links
(declare-function org-ref-get-bibtex-key-and-file "org-ref-core")
(defhydra org-ref-citation-hydra (:color blue :hint nil)
"Citation actions
"
("o" org-ref-open-citation-at-point "Bibtex" :column "Open")
("p" org-ref-open-pdf-at-point "PDF" :column "Open")
("n" org-ref-open-notes-at-point "Notes" :column "Open")
("u" org-ref-open-url-at-point "URL" :column "Open")
;; WWW actions
("ww" org-ref-wos-at-point "WOS" :column "WWW")
("wr" org-ref-wos-related-at-point "WOS related" :column "WWW")
("wc" org-ref-wos-citing-at-point "WOS citing" :column "WWW")
("wg" org-ref-google-scholar-at-point "Google Scholar" :column "WWW")
("wp" org-ref-pubmed-at-point "Pubmed" :column "WWW")
("wf" org-ref-crossref-at-point "Crossref" :column "WWW")
("wb" org-ref-biblio-at-point "Biblio" :column "WWW")
("e" org-ref-email-at-point "Email" :column "WWW")
;; Copyish actions
("K" (save-window-excursion
(let ((bibtex-completion-bibliography (org-ref-find-bibliography)))
(bibtex-completion-show-entry (list (org-ref-get-bibtex-key-under-cursor)))
(bibtex-copy-entry-as-kill)
(kill-new (pop bibtex-entry-kill-ring))))
"Copy bibtex" :column "Copy")
("a" org-ref-add-pdf-at-point "add pdf to library" :column "Copy")
("k" (kill-new (car (org-ref-get-bibtex-key-and-file))) "Copy key" :column "Copy")
("f" (kill-new (bibtex-completion-apa-format-reference
(org-ref-get-bibtex-key-under-cursor)))
"Copy formatted" :column "Copy")
("h" (kill-new
(format "* %s\n\n cite:&%s"
(bibtex-completion-apa-format-reference
(org-ref-get-bibtex-key-under-cursor))
(car (org-ref-get-bibtex-key-and-file))))
"Copy org heading"
:column "Copy")
;; Editing actions
("<left>" org-ref-cite-shift-left "Shift left" :color red :column "Edit")
("<right>" org-ref-cite-shift-right "Shift right" :color red :column "Edit")
("<up>" org-ref-sort-citation-link "Sort by year" :column "Edit")
("i" (funcall org-ref-insert-cite-function) "Insert cite" :column "Edit")
("t" org-ref-change-cite-type "Change cite type" :column "Edit")
("d" org-ref-delete-citation-at-point "Delete at point" :column "Edit")
("r" org-ref-replace-citation-at-point "Replace cite" :column "Edit")
("P" org-ref-edit-pre-post-notes "Edit pre/suffix" :column "Edit")
;; Navigation
("[" org-ref-previous-key "Previous key" :column "Navigation" :color red)
("]" org-ref-next-key "Next key" :column "Navigation" :color red)
("v" org-ref-jump-to-visible-key "Visible key" :column "Navigation" :color red)
("q" nil "Quit"))
(defun org-ref-cite-follow (_path)
"Follow a cite link."
(org-ref-citation-hydra/body))
;; * Citation links tooltips
(defvar bibtex-completion-bibliography)
(defvar bibtex-completion-pdf-symbol)
(defvar bibtex-completion-notes-symbol)
(defvar bibtex-completion-find-note-functions)
(declare-function org-ref-find-bibliography "org-ref-core")
(declare-function bibtex-completion-find-pdf "bibtex-completion")
(declare-function bibtex-completion-apa-format-reference "bibtex-completion")
(defun org-ref-cite-tooltip (_win _obj position)
"Get a tooltip for the cite at POSITION."
(let ((key (get-text-property position 'cite-key)))
(when key
(let* ((bibtex-completion-bibliography (org-ref-find-bibliography))
(has-pdf (when (bibtex-completion-find-pdf key) bibtex-completion-pdf-symbol))
(has-notes (when (cl-some #'identity
(mapcar (lambda (fn)
(funcall fn key))
bibtex-completion-find-note-functions))
bibtex-completion-notes-symbol)))
(format "%s%s %s" (or has-pdf "") (or has-notes "")
(bibtex-completion-apa-format-reference key))))))
;; * Exporting citation links
(defun org-ref-cite-export (cmd path desc backend)
"Export a cite link.
This supports the syntax: \\cmd[optional prefix][optional suffix]{keys}
The prefix and suffix must be the global version. Local prefix/suffixes are ignored.
PATH contains the link path.
BACKEND is the export backend.
Use with apply-partially."
(pcase backend
('latex
(let* ((cite (org-ref-parse-cite-path path))
(references (plist-get cite :references))
(keys (cl-loop for ref in references collect
(plist-get ref :key))))
(pcase (org-ref-cite-version path)
(2
(let* ((prefix-suffix (split-string (or desc "") "::"))
(prefix (cond
((and (cl-first prefix-suffix) (not (string= "" (cl-first prefix-suffix))))
(format "[%s]" (cl-first prefix-suffix)))
((cl-second prefix-suffix)
"[]")
(t
"")))
(suffix (cond
((cl-second prefix-suffix)
(format "[%s]" (cl-second prefix-suffix)))
(t
""))))
(s-format "\\${cmd}${prefix}${suffix}{${keys}}" 'aget
`(("cmd" . ,cmd)
("prefix" . ,(string-trim prefix))
("suffix" . ,(string-trim suffix))
("keys" . ,(string-join keys ","))))))
(3
(s-format "\\${cmd}${prefix}${suffix}{${keys}}" 'aget
`(("cmd" . ,cmd)
;; if there is more than one key, we only do global
;; prefix/suffix But for one key, we should allow local
;; prefix and suffix or the global one.
("prefix" . ,(if (= 1 (length references))
;; single reference
(cond
;; global and common prefixes exist, combine them
((and (plist-get cite :prefix)
(plist-get (car references) :prefix))
(concat "["
(plist-get cite :prefix)
";" ;; add this back as a separator
(plist-get (car references) :prefix)
"]"))
;; local prefix is not empty, we use it.
((plist-get (car references) :prefix)
(concat "["
(string-trim (plist-get (car references) :prefix))
"]"))
;; local prefix is empty, but global one
;; is not, so we use it
((plist-get cite :prefix)
(concat "["
(string-trim (plist-get cite :prefix))
"]"))
;; if you have a suffix, you need an empty prefix
((plist-get cite :suffix)
"[]")
(t
""))
;; Multiple references
(cond
;; Check the common prefix
((plist-get cite :prefix)
(concat "["
(string-trim (plist-get cite :prefix))
"]"))
;; Check the prefix in the first cite
((plist-get (car references) :prefix)
(concat "["
(string-trim (plist-get (car references) :prefix))
"]"))
;; if you get here, the prefix is empty.
;; if you have a suffix, you need an empty prefix placeholder
((plist-get cite :suffix)
"[]")
(t
""))))
("suffix" . ,(if (= 1 (length references))
;; Single reference
(cond
;; local suffix is not empty, so use it
((plist-get (car references) :suffix)
(format "[%s]"
(string-trim (plist-get (car references) :suffix))))
;; global suffix is not empty
((plist-get cite :suffix)
(format "[%s]" (string-trim (plist-get cite :suffix))))
(t
;; If there is a prefix, then this should
;; be an empty bracket, and if not it
;; should am empty string. You need an
;; empty bracket, at least for biblatex
;; commands. With just one set of
;; brackets it is interpreted as a
;; suffix.
(if (or (plist-get cite :prefix)
(plist-get (car references) :prefix))
"[]"
"")))
;; Multiple references
(cond
;; this is a common suffix
((plist-get cite :suffix)
(format "[%s]" (string-trim (plist-get cite :suffix))))
;; last reference has a suffix
((plist-get (car (last references)) :suffix)
(format "[%s]" (string-trim (plist-get (car (last references)) :suffix))))
(t
;; If there is a prefix, then this should
;; be an empty bracket, and if not it
;; should am empty string. You need an
;; empty bracket, at least for biblatex
;; commands. With just one set of
;; brackets it is interpreted as a
;; suffix.
(if (or (plist-get cite :prefix)
(plist-get (car references) :prefix))
"[]"
"")))))
("keys" . ,(string-join keys ","))))))))))
(defun org-ref-multicite-export (cmd path _desc backend)
"Export a multicite link.
This supports the syntax: \\cmd(multiprenote)(multipostnote)[prenote][postnote]{key1}...[prenote][postnote]{key}
PATH contains the link path.
BACKEND is the export backend.
Use with apply-partially."
(pcase backend
('latex
(let ((cite (org-ref-parse-cite-path path)))
(s-format "\\${cmd}${global-prefix}${global-suffix}${keys}" 'aget
`(("cmd" . ,cmd)
("global-prefix" . ,(cond
((plist-get cite :prefix)
(concat "(" (plist-get cite :prefix) ")"))
;; if you have a suffix, you need an empty prefix
((plist-get cite :suffix)
"()")
(t
"")))
("global-suffix" . ,(if (not (string= "" (or (plist-get cite :suffix) "")))
(format "(%s)" (plist-get cite :suffix))
""))
("keys" . ,(string-join
(cl-loop for ref in (plist-get cite :references)
collect
(format "%s%s{%s}"
(cond
;; we have a prefix, stick it in
((not (string= ""
(or (plist-get ref :prefix) "")))
(concat "[" (plist-get ref :prefix) "]"))
;; no prefix, but a suffix, so
;; empty prefix for placeholder
((not (string= ""
(or (plist-get ref :suffix) "")))
"[]")
(t
""))
(cond
((not (string= ""
(or (plist-get ref :suffix) "")))
(concat "[" (plist-get ref :suffix) "]"))
(t
""))
(plist-get ref :key)))))))))))
;; * Completion for citation links
;;
;; This allows you to type C-c l, choose a cite link type, and then insert a key.
(defun org-ref-cite-link-complete (cmd &optional _arg)
"Cite link completion for CMD."
(concat
cmd ":"
"&" (org-ref-read-key)))
;; * Generate all the links
;;
;; We loop on the three categories because there are some differences between
;; them, mostly in the multitypes.
(cl-loop for (cmd _desc) in (append org-ref-natbib-types
org-ref-biblatex-types)
do
(org-link-set-parameters
cmd
:complete (apply-partially #'org-ref-cite-link-complete cmd)
:follow #'org-ref-cite-follow
:face 'org-ref-cite-face
:help-echo #'org-ref-cite-tooltip
:export (apply-partially 'org-ref-cite-export cmd)
:activate-func #'org-ref-cite-activate))
(cl-loop for (cmd _desc) in org-ref-biblatex-multitypes do
(org-link-set-parameters
cmd
:complete (apply-partially #'org-ref-cite-link-complete cmd)
:follow #'org-ref-cite-follow
:face 'org-ref-cite-face
:help-echo #'org-ref-cite-tooltip
:export (apply-partially 'org-ref-multicite-export cmd)
:activate-func #'org-ref-cite-activate))
(org-link-set-parameters
"bibentry"
:complete (apply-partially #'org-ref-cite-link-complete "bibentry")
:follow #'org-ref-cite-follow
:face 'org-ref-cite-face
:help-echo #'org-ref-cite-tooltip
:export (apply-partially 'org-ref-cite-export "bibentry")
:activate-func #'org-ref-cite-activate)
;; * Cite link utilities
;;;###autoload
(defun org-ref-delete-citation-at-point ()
"Delete the citation or reference at point."
(interactive)
(let* ((object (org-element-context))
(type (org-element-property :type object))
(begin (org-element-property :begin object))
(end (org-element-property :end object))
(link-string (org-element-property :path object))
(data (org-ref-parse-cite-path link-string))
(references (plist-get data :references))
(cp (point))
key i)
;; We only want this to work on citation links
(when (assoc type org-ref-cite-types)
(setq key (org-ref-get-bibtex-key-under-cursor))
(if (null key)
;; delete the whole cite
(cl--set-buffer-substring begin end "")
(setq i (seq-position references key (lambda (el key)
(string= key (plist-get el :key)))))
;; delete i'th reference
(setq references (-remove-at i references))
(setq data (plist-put data :references references))
(save-excursion
(goto-char begin)
(re-search-forward link-string)
(replace-match (org-ref-interpret-cite-data data)))
(goto-char cp)))))
;;;###autoload
(defun org-ref-replace-citation-at-point ()
"Replace the citation at point."
(interactive)
(let* ((object (org-element-context))
(type (org-element-property :type object))
(begin (org-element-property :begin object))
(end (org-element-property :end object))
(link-string (org-element-property :path object))
(data (org-ref-parse-cite-path link-string))
(references (plist-get data :references))
(cp (point))
key i)
;; We only want this to work on citation links
(when (assoc type org-ref-cite-types)
(setq key (org-ref-get-bibtex-key-under-cursor))
(if (null key)
;; delete the whole cite
(cl--set-buffer-substring begin end "")
(setq i (seq-position references key (lambda (el key) (string= key (plist-get el :key))))) ;; defined in org-ref
(setf (plist-get (nth i references) :key) (org-ref-read-key))
(setq data (plist-put data :references references))
(save-excursion
(goto-char begin)
(re-search-forward link-string)
(replace-match (org-ref-interpret-cite-data data)))
(goto-char cp)))))
;;;###autoload
(defun org-ref-edit-pre-post-notes (&optional common)
"Edit the pre/post notes at point.
if you are not on a key, or with optional prefix
arg COMMON, edit the common prefixes instead."
(interactive "P")
;; find out what the point is on.
(let* ((key (get-text-property (point) 'cite-key))
(cp (point))
(cite (org-element-context))
(type (org-element-property :type cite))
(data (org-ref-parse-cite-path (org-element-property :path cite)))
prefix suffix
(delta 0))
(if (or (null key) common)
(progn
(setq prefix (read-string "prenote: " (plist-get data :prefix))
suffix (read-string "postnote: " (plist-get data :suffix))
delta (- (length (plist-get data :prefix)) (length prefix)))
(plist-put data :prefix (if (string= "" prefix)
nil prefix))
(plist-put data :suffix (if (string= "" suffix)
nil suffix)))
;; On a key
(let ((index (seq-position (plist-get data :references)
key
(lambda (el1 key-at-point)
(string= key-at-point (plist-get el1 :key))))))
;; Pad with spaces after prefix and before suffix
(setq prefix (concat
(read-string "prenote: "
(string-trim
(or
(plist-get
(nth index (plist-get data :references))
:prefix)
"")))
" ")
suffix (concat " "
(read-string "postnote: "
(string-trim
(or
(plist-get
(nth index (plist-get data :references))
:suffix)
""))))
delta (- (length (plist-get
(nth index (plist-get data :references))
:prefix))
(length prefix)))
(plist-put
(nth index (plist-get data :references))
:prefix (if (string= "" prefix)
nil prefix))
(plist-put
(nth index (plist-get data :references))
:suffix (if (string= "" suffix)
nil suffix))))
(cl--set-buffer-substring (org-element-property :begin cite) (org-element-property :end cite)
(format "[[%s:%s]]" type (org-ref-interpret-cite-data data)))
;; This doesn't exactly save the point. I need a fancier calculation for
;; that I think that accounts for the change due to the prefix change. e.g.
;; you might add or subtract from the prefix.
(goto-char (- cp delta))))
(declare-function org-element-create "org-element")
;;;###autoload
(defun org-ref-change-cite-type ()
"Change the cite type of citation link at point."
(interactive)
(let* ((type-annotation (lambda (s)
(let ((item (assoc s minibuffer-completion-table)))
(when item (concat
(make-string (- 12 (length s)) ? )
"-- "
(cl-second item))))))
(completion-extra-properties `(:annotation-function ,type-annotation))
(new-type (completing-read "Type: " org-ref-cite-types))
(cite-link (org-element-context))
(cp (point)))
(cl--set-buffer-substring
(org-element-property :begin cite-link)
(org-element-property :end cite-link)
(org-element-interpret-data
(org-element-create 'link
`(:type ,new-type
:path ,(org-element-property :path cite-link)
:contents-begin ,(org-element-property :contents-begin cite-link)
:contents-end ,(org-element-property :contents-end cite-link)))))