-
-
Notifications
You must be signed in to change notification settings - Fork 47
/
org-roam-bibtex.el
1172 lines (1052 loc) · 48.8 KB
/
org-roam-bibtex.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-roam-bibtex.el --- Org Roam meets BibTeX -*- lexical-binding: t -*-
;; Copyright © 2020-2022 Mykhailo Shevchuk
;; Copyright © 2020 Leo Vivier
;; Author: Mykhailo Shevchuk <mail@mshevchuk.com>
;; Leo Vivier <leo.vivier+dev@gmail.com>
;; URL: https://github.com/org-roam/org-roam-bibtex
;; Keywords: bib hypermedia outlines wp
;; Version: 0.6.1
;; Package-Requires: ((emacs "27.1") (org-roam "2.2.0") (bibtex-completion "2.0.0"))
;; This file is NOT part of GNU Emacs.
;; 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, 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; see the file LICENSE. If not, visit
;; <https://www.gnu.org/licenses/>.
;;; Commentary:
;;
;; Org-roam-bibtex, ORB for short, offers integration of Org-roam with BibTeX
;; Emacs software: Org-ref, Helm/Ivy-bibtex and Citar. The main task of ORB is
;; to seamlessly expose Org-roam as a note management solution to these
;; packages, shadowing their native facilities for taking bibliographic
;; notes. As its main feature, ORB enables expansion of BibTeX keywords in
;; Org-roam templates.
;;
;; Main usage:
;;
;; Call interactively `org-roam-bibtex-mode' or call (org-roam-bibtex-mode +1)
;; from Lisp. Enabling `org-roam-bitex-mode' sets appropriate functions for
;; creating and retrieving Org-roam notes from Org-ref, Helm/Ivy-bibtex and
;; Citar.
;;
;; Other commands:
;;
;; - `orb-insert-link': insert a link or citation to an Org-roam note that has
;; an associated BibTeX entry
;; - `orb-note-actions': call a dispatcher of useful note actions
;;
;; Soft dependencies: Org-ref, Citar, Helm, Ivy, Hydra, Projectile, Persp-mode
;;; Code:
;; ============================================================================
;;;; Dependencies
;; ============================================================================
(require 'orb-core)
(eval-when-compile
(require 'subr-x)
(require 'cl-lib))
;; declare own functions and variables
(declare-function orb-helm-insert "orb-helm")
(declare-function orb-note-actions-helm "orb-helm")
(declare-function orb-ivy-insert "orb-ivy")
(declare-function orb-note-actions-ivy "orb-ivy")
(declare-function orb-pdf-scrapper-run "orb-pdf-scrapper" (key))
;; declare external functions and variables
;; Projectile, Perspective-mode
(declare-function projectile-relevant-open-projects "ext:projectile")
(declare-function persp-switch "ext:persp-mode")
(declare-function persp-names "ext:persp-mode")
;; Org-ref
(defvar org-ref-notes-function)
(declare-function org-ref-find-bibliography "ext:org-ref-core")
;;
;; Citar
(defvar citar-open-note-function)
;; Hydra
(declare-function defhydra "ext:hydra")
;; ============================================================================
;;;; Customize definitions
;; ============================================================================
(defcustom orb-preformat-templates t
"Non-nil to enable template pre-expanding.
See `orb-edit-note' for details."
:type '(choice
(const :tag "Yes" t)
(const :tag "No" nil))
:group 'org-roam-bibtex)
(defcustom orb-preformat-keywords
'("citekey" "entry-type" "date" "pdf?" "note?" "file"
"author" "editor" "author-abbrev" "editor-abbrev"
"author-or-editor-abbrev")
"A list of template placeholders for pre-expanding.
Any BibTeX field can be set for pre-expanding including
Bibtex-completion virtual fields such as '=key=' and '=type='.
BibTeX fields can be referred to by means of their aliases
defined in `orb-bibtex-field-aliases'.
Usage example:
\(setq orb-preformat-keywords '(\"citekey\" \"author\" \"date\"))
\(setq orb-templates
'((\"r\" \"reference\" plain
\"#+ROAM_KEY: %^{citekey}%?
%^{author} published %^{entry-type} in %^{date}: fullcite:%\\1.\"
:target
(file+head \"references/${citekey.org}\" \"#+title: ${title}\n\")
:unnarrowed t)))
Special cases:
The \"file\" keyword will be treated specially if the value of
`orb-process-file-keyword' is non-nil. See its docstring for an
explanation.
This variable takes effect when `orb-preformat-templates' is set
to t (default). See also `orb-edit-note' for further details.
Consult Bibtex-completion documentation for additional
information on BibTeX field names."
:type '(repeat :tag "BibTeX field names" string)
:group 'org-roam-bibtex)
(defcustom orb-process-file-keyword t
"Whether to treat the file keyword specially during template pre-expanding.
When this variable is non-nil, the \"%^{file}\" and \"${file}\"
wildcards will be processed by `org-process-file-field' rather
than simply replaced with the field value. This may be useful in
situations when the file field contains several file names and
only one file name is desirable for retrieval. The \"file\"
keyword must be set for pre-expanding in `orb-preformat-keywords'
as usual.
If this variable is `string', for example \"my-file\", use its
value as the wildcard keyword instead of the default \"file\"
keyword. Thus, it will be possible to get both the raw file
field value by expanding the %^{file} and ${file} wildcards and a
single file name by expanding the %^{my-file} and ${my-file}
wildcards. The keyword, e.g. \"my-file\", must be set for
pre-expanding in `orb-preformat-keywords' as usual.
The variable `orb-attached-file-extensions' controls filtering of
file names based on file extensions."
;; TODO: check if a custom string is really working as described
:group 'org-roam-bibtex
:type '(choice
(const :tag "Yes" t)
(const :tag "No" nil)
(string :tag "Custom wildcard keyword")))
(defcustom orb-roam-ref-format 'org-ref-v2
"Defines the format of citation key in the `ROAM_REFS' property.
Should be one of the following symbols:
- `org-ref-v2': Old Org-ref `cite:links'
- `org-ref-v3': New Org-ref `cite:&links'
- `org-cite' : Org-cite `@elements'
This can also be a custom `format' string with a single `%s' specifier."
:type '(radio
(const :tag "Org-ref v2" org-ref-v2)
(const :tag "Org-ref v3" org-ref-v3)
(const :tag "Org-cite" org-cite)
(string :tag "Custom format string"))
:group 'org-roam-bibtex)
(defcustom orb-bibtex-entry-get-value-function #'bibtex-completion-apa-get-value
"Function to be used by ORB for values from a BibTeX entry.
The default value of this variable is `bibtex-completion-apa-get-value',
which offers some post-formatting for author fields.
Another possible choice available out of the box is
`bibtex-completion-get-value', which returns a verbatim value.
Set this to a custom function if you need more flexibility.
This function should take two arguments FIELD-NAME and ENTRY.
FIELD-NAME is the name of the field whose value should be retrieved.
ENTRY is a BibTeX entry as returned by `bibtex-completion-get-entry'."
:risky t
:group 'org-roam-bibtex
:type '(radio (function-item bibtex-completion-apa-get-value)
(function-item bibtex-completion-get-value)
(function :tag "Custom function")))
(defcustom orb-persp-project `("notes" . ,org-roam-directory)
"Perspective name and path to the project with bibliography notes.
A cons cell (PERSP-NAME . PROJECT-PATH). Only relevant when
`orb-switch-persp' is set to t.
PERSP-NAME should be a valid Perspective name, PROJECT-PATH should be
an open Projectile project.
See `orb-edit-note' for details"
:type '(cons (string :tag "Perspective name")
(directory :tag "Projectile directory"))
:group 'org-roam-bibtex)
(defcustom orb-switch-persp nil
"Non-nil to enable switching to the notes perspective.
Set the name of the perspective and the path to the notes project
in `orb-persp-project' for this to take effect.
Perspective switching works with Pers-mode and Projectile."
:type '(choice
(const :tag "Yes" t)
(const :tag "No" nil))
:group 'org-roam-bibtex)
(defcustom orb-ignore-bibtex-store-link-functions
'(org-bibtex-store-link)
"Functions to override with `ignore' during note creation process.
Org Ref defines function `org-ref-bibtex-store-link' to store
links to a BibTeX buffer, e.g. with `org-store-link'. At the
same time, Org ref requires `ol-bibtex' library, which defines
`org-bibtex-store-link' to do the same. When creating a note
with `orb-edit-note' from a BibTeX buffer, for example by calling
`org-ref-open-bibtex-notes', the initiated `org-capture' process
implicitly calls `org-store-link'. The latter loops through all
the functions for storing links, and if more than one function
can store links to the location, the BibTeX buffer in this
particular case, the user will be prompted to choose one. This
is definitely annoying, hence ORB will advise all functions in
this list to return nil to trick `org-capture' and get rid of the
prompt.
The default value is `(org-bibtex-store-link)', which means this
function will be ignored and `org-ref-bibtex-store-link' will be
used to store a link to the BibTeX buffer. See
`org-capture-templates' on how to use the link in your templates."
:type '(repeat (function))
:risky t
:group 'org-roam-bibtex)
(defcustom orb-insert-interface 'generic
"Interface frontend to use with `orb-insert-link'.
Possible values are the symbols `helm-bibtex', `ivy-bibtex', or
`generic' (default). In the first two cases the respective
commands will be used, while in the latter case the command
`orb-insert-generic' will be used.
When using `helm-bibtex' or `ivy-bibtex' as `orb-insert-interface',
choosing the action \"Edit note & insert a link\" will insert the
desired link. For convenience, this action is made default for
the duration of an `orb-insert-link' session. It will not
persist when `helm-bibtex' or `ivy-bibtex' proper are run.
Otherwise, the command is just the usual `helm-bibtex'/`ivy-bibtex'.
For example, it is possible to run other `helm-bibtex' or
`ivy-bibtex' actions. When action other than \"Edit note &
insert a link\" is run, no link will be inserted, although the
session can be resumed later with `helm-resume' or `ivy-resume',
respectively, where it will be possible to select the \"Edit note
& insert a link\" action.
When using the `generic' interface, a simple list of available
citation keys is presented using `completion-read' and after
choosing a candidate the appropriate link will be inserted.
Please note that this variable should be set using the Customize
interface, `use-package''s `:custom' keyword, or Doom's `setq!'
macro. Simple `setq' will not work."
:group 'org-roam-bibtex
:type '(radio
(const helm-bibtex)
(const ivy-bibtex)
(const generic))
:set (lambda (var value)
(cond
((eq value 'ivy-bibtex)
(require 'orb-ivy))
((eq value 'helm-bibtex)
(require 'orb-helm)))
(set-default var value)))
(defcustom orb-insert-link-description 'title
"Link description format for links created with `orb-insert-link'.
The command `orb-insert-link' can be used to create Org links
to bibliographic notes of type [[id:note_id][Description]].
This variable determines the 'Description' part from the example
above. It is an `s-format' string, where special placeholders of
form '${field}' will be expanded with data from the respective
BibTeX field of the associated BibTeX entry. If the field's
value cannot be retrieved, the user will be prompted to input a
value interactively. When retrieving BibTeX data, the user
options `orb-bibtex-field-aliases' and
`orb-bibtex-entry-get-value-function' are respected.
This variable can also be one of the following symbols:
`title' - equivalent to \"${title}\"
`citekey' - equivalent to \"${citekey}\"
`citation-org-ref-2' - create Org-ref v2 'cite:citekey' citation instead
`citation-org-ref-3' - create Org-ref v3 'cite:&citekey' citation instead
`citation-org-cite' - create Org-cite '[cite:@citekey]' citation instead
The default value set by this variable can be overriden by
calling `orb-insert-link' with an appropriated numerical prefix
argument. See its docstring for more information."
:group 'org-roam-bibtex
:type '(choice
(string :tag "Format string")
(const :tag "Title" title)
(const :tag "Citation key" citekey)
(const :tag "Citation link" citation-org-ref-2)
(const :tag "Citation link" citation-org-ref-3)
(const :tag "Citation link" citation-org-cite)))
(defcustom orb-insert-follow-link nil
"Whether to follow a newly inserted link."
:group 'orb-roam-bibtex
:type '(choice
(const :tag "Yes" t)
(const :tag "No" nil)))
(defcustom orb-insert-generic-candidates-format 'key
"Format of selection candidates for `orb-insert-generic' interface.
Possible values are `key' and `entry'."
:group 'org-roam-bibtex
:type '(choice
(const key)
(const entry)))
(defcustom orb-note-actions-interface 'default
"Interface frontend for `orb-note-actions'.
Supported values (interfaces) are symbols `default', `ido',
`hydra', `ivy' and `helm'.
Alternatively, it can be set to a function, in which case the
function should expect one argument CITEKEY, which is a list
whose car is the citation key associated with the org-roam note
the current buffer is visiting. Also, it should ideally make use
of `orb-note-actions-default', `orb-note-actions-extra' and
`orb-note-actions-user' for providing an interactive interface,
through which the combined set of note actions is presented as a
list of candidates and the function associated with the candidate
is executed upon selecting it.
This variable should be set using the Customize interface,
`use-package''s `:custom' keyword, or Doom's `setq!' macro.
Simple `setq' will not work."
:risky t
:type '(radio
(const :tag "Default" default)
(const :tag "Ido" ido)
(const :tag "Hydra" hydra)
(const :tag "Ivy" ivy)
(const :tag "Helm" helm)
(function :tag "Custom function"))
:set (lambda (var value)
(cond
((eq value 'ivy)
(require 'orb-ivy))
((eq value 'helm)
(require 'orb-helm))
((eq value 'hydra)
(require 'hydra)))
(set-default var value))
:group 'orb-note-actions)
(defcustom orb-note-actions-default
'(("Open PDF file(s)" . orb-open-attached-file)
("Add PDF to library" . bibtex-completion-add-pdf-to-library)
("Open URL or DOI in browser" . bibtex-completion-open-url-or-doi)
("Show record in the bibtex file" . bibtex-completion-show-entry))
"Default actions for `orb-note-actions'.
Each action is a cons cell DESCRIPTION . FUNCTION."
:risky t
:type '(alist
:tag "Default actions for `orb-note-actions'"
:key-type (string :tag "Description")
:value-type (function :tag "Function"))
:group 'orb-note-actions)
(defcustom orb-note-actions-extra
'(("Save citekey to kill-ring and clipboard" . orb-note-actions-copy-citekey)
("Run Orb PDF Scrapper" . orb-note-actions-scrap-pdf))
"Extra actions for `orb-note-actions'.
Each action is a cons cell DESCRIPTION . FUNCTION."
:risky t
:type '(alist
:tag "Extra actions for `orb-note-actions'"
:key-type (string :tag "Description")
:value-type (function :tag "Function"))
:group 'orb-note-actions)
(defcustom orb-note-actions-user nil
"User actions for `orb-note-actions'.
Each action is a cons cell DESCRIPTION . FUNCTION."
:risky t
:type '(alist
:tag "User actions for `orb-note-actions'"
:key-type (string :tag "Description")
:value-type (function :tag "Function"))
:group 'orb-note-actions)
;; ============================================================================
;;;; Orb edit notes
;; ============================================================================
(defun orb--switch-perspective ()
"Helper function for `orb-edit-note'."
(when (and (require 'projectile nil t)
(require 'persp-mode nil t))
(let ((notes-project (cdr orb-persp-project))
(projects (projectile-relevant-open-projects))
openp)
(dolist (project projects openp)
(setq openp (or (f-equal? project notes-project) openp)))
(when openp
(let ((p-names (cdr (persp-names))))
(dolist (p-name p-names)
(when (s-equals? p-name (car orb-persp-project))
(persp-switch p-name))))))))
(defun orb--store-link-functions-advice (action)
"Add or remove advice for each of `orb-ignore-bibtex-store-link-functions'.
ACTION should be a symbol `add' or `remove'. A piece of advice
is the function `ignore', it is added as `:override'."
(when orb-ignore-bibtex-store-link-functions
(let ((advice-func (intern (format "advice-%s" action)))
(advice (cl-case action
(add (list :override #'ignore))
(remove (list #'ignore))
(t (user-error "Action type not recognised: %s" action)))))
(dolist (advisee orb-ignore-bibtex-store-link-functions)
(apply advice-func (push advisee advice))))))
(defun orb--pre-expand-template (template entry)
"Helper function for `orb--new-note'.
TEMPLATE is an element of `org-roam-capture-templates' and ENTRY
is a BibTeX entry as returned by `bibtex-completion-get-entry'."
;; Handle org-roam-capture part
(letrec (;; Org-capture templates: handle different types of
;; org-capture-templates: string, file and function; this is
;; a stripped down version of `org-capture-get-template'
(org-template
(pcase (nth 3 template) ; org-capture template is here
(`nil 'nil)
((and (pred stringp) tmpl) tmpl)
(`(file ,file)
(let ((flnm (expand-file-name file org-directory)))
(if (file-exists-p flnm) (f-read-text flnm)
(format "Template file %S not found" file))))
(`(function ,fun)
(if (functionp fun) (funcall fun)
(format "Template function %S not found" fun)))
(_ (user-error "ORB: Invalid capture template"))))
;; org-roam capture properties are here
(plst (cddddr template))
;; regexp for org-capture prompt wildcard
(rx "\\(%\\^{[[:alnum:]-_]*}\\)")
(file-keyword (when orb-process-file-keyword
(or (and (stringp orb-process-file-keyword)
orb-process-file-keyword)
"file")))
;; inline function to handle :target list expansion
(expand-roam-template
(lambda (roam-template-list old new)
(let (elements)
(dolist (el roam-template-list)
(if (listp el)
(setq elements
(nreverse
(append elements
(list (funcall expand-roam-template
el old new)))))
(push (s-replace old new el) elements)))
(nreverse elements))))
(lst nil))
;; First run:
;; 1) Make a list of (org-wildcard field-value match-position) for the
;; second run
;; 2) replace org-roam-capture wildcards
(dolist (keyword orb-preformat-keywords)
(let* (;; prompt wildcard keyword
(keyword (cond
;; for some backward compatibility with old
;; `orb-preformat-keywords'
((consp keyword) (car keyword))
((stringp keyword) keyword)
(t (user-error "Error in `orb-preformat-keywords': \
Keyword \"%s\" has invalid type (string was expected)" keyword))))
;; bibtex field name
(field-name (orb-resolve-field-alias keyword))
;; get the bibtex field value
(field-value
;; maybe process file keyword
(or (if (and file-keyword (string= field-name file-keyword))
(prog1
(orb-get-attached-file
(funcall orb-bibtex-entry-get-value-function "=key=" entry))
;; we're done so don't even compare file-name with
;; file-keyword in the successive cycles
(setq file-keyword nil))
;; do the usual processing otherwise
;; condition-case to temporary workaround an upstream bug
(condition-case nil
(funcall orb-bibtex-entry-get-value-function field-name entry)
(error "")))
""))
;; org-capture prompt wildcard
(org-wildcard (concat "%^{" (or keyword "citekey") "}"))
;; org-roam-capture prompt wildcard
(roam-wildcard (concat "${" (or keyword "citekey") "}"))
;; org-roam-capture :target property
(roam-template (or (plist-get plst :if-new) (plist-get plst :target)))
(i 1) ; match counter
pos)
;; Search for org-wildcard, set flag m if found
(when org-template
(while (string-match rx org-template pos)
(if (string= (match-string 1 org-template) org-wildcard)
(progn
(setq pos (length org-template))
(cl-pushnew (list org-wildcard field-value i) lst ))
(setq pos (match-end 1)
i (1+ i)))))
;; Replace placeholders in org-roam-capture-templates :target property
(when roam-template
(setcdr roam-template
(funcall expand-roam-template
(cdr roam-template) roam-wildcard field-value)))))
;; Second run: replace prompts and prompt matches in org-capture
;; template string
(dolist (l lst)
(when (and org-template (nth 1 l))
(let ((pos (concat "%\\" (number-to-string (nth 2 l)))))
;; replace prompt match wildcards with prompt wildcards
;; replace prompt wildcards with BibTeX field value
(setq org-template (s-replace pos (car l) org-template)
org-template (s-replace (car l) (nth 1 l) org-template))))
(setf (nth 3 template) org-template))
template))
(defun orb--new-note (citekey &optional props)
"Process templates and run `org-roam-capture-'.
CITEKEY is the citation key of an entry for which the note is
created. PROPS are additional properties for `org-roam-capture-'."
;; Check if the requested BibTeX entry actually exists and fail
;; gracefully otherwise
(if-let* ((entry (or (bibtex-completion-get-entry citekey)
(orb-warning
"Could not find the BibTeX entry" citekey)))
;; Depending on the templates used: run
;; `org-roam-capture--capture' or call `org-roam-node-find'
(org-capture-templates org-roam-capture-templates)
;; hijack org-capture-templates
;; entry is our bibtex entry, it just happens that
;; `org-capture' calls a single template entry "entry";
(template (--> (if (null (cdr org-capture-templates))
;; if only one template is defined, use it
(car org-capture-templates)
(org-capture-select-template))
(when (listp it)
(copy-tree it))
;; optionally pre-expand templates
(if (and it orb-preformat-templates)
(orb--pre-expand-template it entry)
it)))
;; pretend we had only one template
;; `org-roam-capture--capture' behaves specially in this case
;; NOTE: this circumvents using functions other than
;; `org-capture', see `org-roam-capture-function'.
;; If the users start complaining, we may revert previous
;; implementation
(org-roam-capture-templates (list template))
;; Org-roam coverts the templates to its own syntax;
;; since we are telling `org-capture' to use the template entry
;; (by setting `org-capture-entry'), and Org-roam converts the
;; whole template list, we must do the conversion of the entry
;; ourselves
(props (--> (or props (list :finalize 'find-file))
(plist-put it :call-location (point-marker))))
(org-capture-entry
(org-roam-capture--convert-template template props))
(citekey-ref (format
(pcase orb-roam-ref-format
('org-ref-v2 "cite:%s")
('org-ref-v3 "cite:&%s")
('org-cite "@%s")
((pred stringp) orb-roam-ref-format)
(_ (user-error "Invalid format `orb-roam-ref-format'")))
citekey))
(title
(or (funcall orb-bibtex-entry-get-value-function "title" entry)
(and
(orb-warning "Title not found for this entry")
;; this is not critical, the user may input their own
;; title
"No title")))
(node (org-roam-node-create :title title)))
(org-roam-capture-
:node node
:info (list :ref citekey-ref))
(user-error "Abort")))
;;;###autoload
(defun orb-edit-note (citekey)
"Open an Org-roam note associated with the CITEKEY or create a new one.
This function allows to use Org-roam as a backend for managing
bibliography notes. It relies on `bibtex-completion' to get
retrieve bibliographic information from a BibTeX file.
Implementation details and features:
1. This function first tries to find the note file associated
with the citation key CITEKEY. A citation key is an Org-roam
'ref' set with the '#+ROAM_KEY:' in-buffer keyword or
':ROAM_REFS:' headline property. Three types of Org-roam 'ref's
are recognized by ORB: Org-ref v2 'cite:citekey' and Org-ref v3
'cite:&citekey' links, and Org-cite '[cite:@citekey]' citations.
2. If the Org-roam reference was found, the function calls
`org-roam-node-find' passing to it the title associated with the
CITEKEY as retrieved by `bibtex-completion-get-entry'. The
prompt presented by `org-roam-node-find' will thus be
pre-populated with the record title.
3. Optionally, when `orb-preformat-templates' is non-nil, any
prompt wildcards in `orb-templates' or
`org-roam-capture-templates', associated with the bibtex record
fields as specified in `orb-preformat-templates', will be
preformatted. Both `org-capture-templates' (%^{}) and
`org-roam-capture-templates' (`s-format', ${}) prompt syntaxes
are supported.
See `orb-preformat-keywords' for more details on how
to properly specify prompts for replacement.
Please pay attention when using this feature that by setting
title for preformatting, it will be impossible to change it in
the `org-roam-node-find' interactive prompt since all the
template expansions will have taken place by then. All the title
wildcards will be replace with the BibTeX field value.
4. Optionally, if you are using Projectile and Persp-mode and
have a dedicated workspace to work with your Org-roam collection,
you may want to set the perspective name and project path in
`orb-persp-project' and `orb-switch-persp' to t. In this case,
the perspective will be switched to the Org-roam notes project
before calling any Org-roam functions.
If optional argument ENTRY is non-nil, use it to fetch the
bibliographic information."
;; Optionally switch to the notes perspective
(when orb-switch-persp
(orb--switch-perspective))
(orb-make-notes-cache)
(if-let ((node (orb-note-exists-p citekey)))
(ignore-errors (org-roam-node-visit node))
;; fix some Org-ref related stuff
(orb--store-link-functions-advice 'add)
;; TODO: consider using unwind-protect and let the errors through
(condition-case error-msg
(orb--new-note citekey)
((debug error)
(orb--store-link-functions-advice 'remove)
(message "%s"
(concat (and (eq (car error-msg) 'error)
"orb-edit-note caught an error during capture: ")
(error-message-string error-msg)))))))
;; FIXME: this does not work anymore
;; (defun orb--get-non-ref-path-completions ()
;; "Return a list of cons for titles of non-ref notes to absolute path.
;; CANDIDATES is a an alist of candidates to consider. Defaults to
;; `org-roam--get-title-path-completions' otherwise."
;; (let* ((rows (org-roam-db-query
;; [:select [titles:file titles:title tags:tags]
;; :from titles
;; :left :join tags
;; :on (= titles:file tags:file)
;; :left :join refs :on (= titles:file refs:file)
;; :where refs:file :is :null]))
;; completions)
;; (dolist (row rows completions)
;; (pcase-let ((`(,file-path ,title ,tags) row))
;; (let ((title (or title
;; (list (org-roam--path-to-slug file-path)))))
;; (let ((k (concat
;; (when tags
;; (format "(%s) " (s-join org-roam-tag-separator tags)))
;; title))
;; (v (list :path file-path :title title)))
;; (push (cons k v) completions)))))))
;;;###autoload
(defun orb-edit-citation-note (&optional element)
"Edit a note for current Org-cite citation or reference.
If the note does not exist, create a new one.
When used from LISP, if optional ELEMENT is non-nil, use it
instead of the element at point. ELEMENT should be the Org-cite
citation or reference element. Providing it allows for quicker
computation."
(interactive)
(cond
((derived-mode-p 'org-mode)
(let* ((around-point (not element))
(element (if around-point (org-element-context) element))
(type (org-element-type element))
(citekey
(cond
((eq type 'citation)
(org-element-property
:key (car (org-cite-get-references element))))
((eq type 'citation-reference)
(org-element-property :key element))
(around-point
(user-error "Cursor not in an Org-cite element"))
(t
(user-error "Invalid optional argument ELEMENT: %s. Org-cite\
citation or reference expected" element)))))
(orb-edit-note citekey)))
(t
(user-error "This function works only in Org mode"))))
;; ============================================================================
;;;; Orb insert
;; ============================================================================
(defvar orb-insert-lowercase nil
"Internal. Dynamic variable for `orb-insert-link' and `orb-insert--link'.")
(defun orb-insert--link (node info)
"Insert a link to NODE.
INFO contains additional information."
;; citekey &optional description lowercase region-text beg end
(-let (((&plist :region :orb-link-description :orb-citekey :orb-entry) info))
(when region
(org-roam-unshield-region (car region) (cdr region))
(delete-region (car region) (cdr region))
(set-marker (car region) nil)
(set-marker (cdr region) nil))
(pcase orb-link-description
((pred stringp)
(let ((description
(-->
(s-format orb-link-description
(lambda (template entry)
(funcall orb-bibtex-entry-get-value-function
(orb-resolve-field-alias template) entry))
orb-entry)
(if (and it orb-insert-lowercase) (downcase it) it))))
(insert (org-link-make-string
(concat "id:" (org-roam-node-id node)) description))))
(`citation-org-cite
(insert (format "[cite:@%s]" orb-citekey)))
(ref-format
(let ((cite-link (if (boundp 'org-ref-default-citation-link)
(concat org-ref-default-citation-link ":")
"cite:")))
(insert (concat cite-link
(when (eq ref-format 'citation-org-ref-3) "&")
orb-citekey)))))))
(defun orb--finalize-insert-link ()
"Insert a link to a just captured note.
This function is used by ORB calls to `org-roam-capture-' instead
of `org-roam-capture--finalize-insert-link'."
(let* ((mkr (org-roam-capture--get :call-location))
(buf (marker-buffer mkr))
(region (org-roam-capture--get :region))
(node (org-roam-populate (org-roam-node-create :id (org-roam-capture--get :id))))
(citekey (org-capture-get :orb-citekey))
(entry (bibtex-completion-get-entry citekey)))
(with-current-buffer buf
(org-with-point-at mkr
(orb-insert--link node (list
:region region
:orb-citekey citekey
:orb-entry entry
:orb-link-description (org-capture-get :orb-link-description)))))))
(defun orb--insert-captured-ref-h ()
"Insert value of `:ref' key from `org-roam-capture--info'.
Internal function. To be installed in `org-roam-capture-new-node-hook'."
(when-let ((ref (plist-get org-roam-capture--info :ref)))
(org-roam-ref-add ref)))
(defun orb-insert-edit-note (citekey)
"Insert a link to a note with citation key CITEKEY.
Capture a new note if it does not exist yet.
CITEKEY can be a list of citation keys (for compatibility with
Bibtex-completion), in which case only the first element of that
list is used."
(unwind-protect
;; Group functions together to avoid inconsistent state on quit
(atomic-change-group
(let* ((citekey (cl-typecase citekey
(string citekey)
(list (car citekey))
(t (user-error "Invalid citation key data type: %s. \
String or list of strings expected" citekey))))
(entry (bibtex-completion-get-entry citekey))
(title
(funcall orb-bibtex-entry-get-value-function "title" entry ""))
(node (or (orb-note-exists-p citekey)
(org-roam-node-create :title title)))
region-text
beg end
(_ (when (region-active-p)
(setq beg (set-marker (make-marker) (region-beginning)))
(setq end (set-marker (make-marker) (region-end)))
(setq region-text
(buffer-substring-no-properties beg end))))
(description (--> orb-insert-link-description
(cl-case it
(title "${title}")
(citekey "${citekey}")
(t it))
(or region-text it)))
(info (--> (list :orb-link-description description
:orb-citekey citekey
:orb-entry entry
:finalize #'orb--finalize-insert-link)
(if (and beg end)
(append it (list :region (cons beg end)))
it))))
(if (org-roam-node-id node)
(orb-insert--link node info)
(orb--new-note citekey info)))
(deactivate-mark)))
(when (and orb-insert-follow-link
(looking-at org-link-any-re))
(org-open-at-point)))
(defun orb-insert-generic (&optional arg)
"Present a list of BibTeX entries for completion.
This is a generic completion function for `orb-insert-link', which
runs `orb-insert-edit-note' on the selected entry. The list is
made by `bibtex-completion-candidates'.
The appearance of selection candidates is determined by
`orb-insert-generic-candidates-format'.
This function is not interactive, set `orb-insert-interface' to
`generic' and call `orb-insert-link' interactively instead.
If ARG is non-nil, rebuild `bibtex-completion-cache'."
(when arg
(bibtex-completion-clear-cache))
(bibtex-completion-init)
(let* ((candidates (bibtex-completion-candidates))
(candidates2
(if (eq orb-insert-generic-candidates-format 'key)
(mapcar (lambda (item)
(alist-get "=key=" (cdr item) nil nil #'equal))
candidates)
(mapcar #'car candidates)))
(selection (completing-read "BibTeX entry:" candidates2 nil t))
(citekey (if (eq orb-insert-generic-candidates-format 'key)
selection
(--> (alist-get selection candidates nil nil #'equal)
(cdr it)
(alist-get "=key=" it nil nil #'equal)))))
(orb-insert-edit-note citekey)))
;;;###autoload
(defun orb-insert-link (&optional arg)
"Insert a link to an Org-roam bibliography note.
If the note does not exist yet, it will be created using
`orb-edit-note' function.
\\<universal-argument-map>\\<org-roam-bibtex-mode-map> The
customization option `orb-insert-link-description' determines
what will be used as the link's description. It is possible to
override the default value of the variable with a numerical
prefix ARG:
`C-1' \\[orb-insert-link] will force `title'
`C-2' \\[orb-insert-link] will force `citekey'
`C-0' \\[orb-insert-link] will force `citation-org-ref-2'
`C-9' \\[orb-insert-link] will force `citation-org-ref-3'
`C-8' \\[orb-insert-link] will force `citation-org-cite'
If a region of text is active (selected) when calling `orb-insert-link',
the text in the region will be replaced with the link and the
text string will be used as the link's description — similar to
`org-roam-node-insert'.
Normally, the case of the link description will be preserved. It
is possible to force lowercase by supplying either one or three
universal arguments `\\[universal-argument]'.
Finally, `bibtex-completion-cache' will be re-populated if either
two or three universal arguments `\\[universal-argument]' are supplied.
The customization option `orb-insert-interface' allows to set the
completion interface backend for the candidates list."
(interactive "P")
;; parse arg
;; C-u or C-u C-u C-u => force lowercase
;; C-u C-u or C-u C-u C-u => force `bibtex-completion-clear-cache'
;; C-1 force title in description
;; C-2 force citekey in description
;; C-0,C-9,C-8 force inserting the link as Org-ref org Org-cite citation
(let* ((lowercase (or (equal arg '(4))
(equal arg '(64))))
(clear-cache (or (equal arg '(16))
(equal arg '(64))))
(link-type (cl-case arg
(1 'title)
(2 'citekey)
(0 'citation-org-ref-2)
(9 'citation-org-ref-3)
(8 'citation-org-cite)
(t nil)))
(orb-insert-link-description
(or link-type orb-insert-link-description))
(orb-insert-lowercase (or lowercase orb-insert-lowercase)))
(orb-make-notes-cache)
(cl-case orb-insert-interface
(helm-bibtex
(cond
((fboundp 'orb-helm-insert)
(orb-helm-insert clear-cache))
(t
(orb-warning "helm-bibtex not available; using generic completion")
(orb-insert-generic clear-cache))))
(ivy-bibtex
(cond
((fboundp 'orb-ivy-insert)
(orb-ivy-insert clear-cache))
(t
(orb-warning "ivy-bibtex not available; using generic completion")
(orb-insert-generic clear-cache))))
(t
(orb-insert-generic clear-cache)))))
;; ============================================================================
;;;; Non-ref functions
;; ============================================================================
;; ;;;###autoload
;; (defun orb-find-non-ref-file (&optional initial-prompt)
;; "Find and open an Org-roam, non-ref file.
;; INITIAL-PROMPT is the initial title prompt.
;; See `org-roam-node-finds' and
;; `orb--get-non-ref-path-completions' for details."
;; (interactive)
;; (org-roam-node-find initial-prompt
;; (orb--get-non-ref-path-completions)))
;; ;;;###autoload
;; (defun orb-insert-non-ref ()
;; "Find a non-ref Org-roam file, and insert a relative org link to it at point.
;; If PREFIX, downcase the title before insertion. See
;; `org-roam-insert' and `orb--get-non-ref-path-completions' for
;; details."
;; (interactive)
;; ;; FIXME: this is not correct
;; (org-roam-node-insert (orb--get-non-ref-path-completions)))
;; ============================================================================
;;;; Orb note actions
;; ============================================================================
(orb-note-actions-defun default
(let ((f (cdr (assoc (completing-read name candidates) candidates))))
(funcall f (list citekey))))
(orb-note-actions-defun ido
(let* ((c (cl-map 'list 'car candidates))
(f (cdr (assoc (ido-completing-read name c) candidates))))
(funcall f (list citekey))))
(declare-function orb-note-actions-hydra/body "org-roam-bibtex" nil t)
(orb-note-actions-defun hydra
;; we don't use candidates here because for a nice hydra we need each
;; group of completions separately (default, extra, user), so just
;; silence the compiler
(ignore candidates)
(let ((k ?a)
actions)
(dolist (type (list "Default" "Extra" "User"))
(let ((actions-var
(intern (concat "orb-note-actions-" (downcase type)))))
(dolist (action (symbol-value actions-var))
;; this makes defhydra HEADS list of the form:
;; ("a" (some-action citekey-value) "Some-action description"
;; :column "Type")
(cl-pushnew
`(,(format "%c" k) (,(cdr action) (list ,citekey))
,(car action) :column ,(concat type " actions"))