forked from emacs-citar/citar
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcitar-latex.el
207 lines (172 loc) · 7.73 KB
/
citar-latex.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
;;; citar-latex.el --- Latex adapter for citar -*- lexical-binding: t; -*-
;; SPDX-FileCopyrightText: 2021-2022 Bruce D'Arcus
;; SPDX-License-Identifier: GPL-3.0-or-later
;;; Commentary:
;; A small package that provides the functions required to use citar
;; with latex.
;; Loading this file will enable manipulating the citations with
;; commands provided by citar.
;;; Code:
(require 'citar)
(require 'tex nil t)
(require 'reftex-parse)
(require 'reftex-cite)
(defvar TeX-esc)
(declare-function TeX-find-macro-boundaries "ext:tex")
(declare-function TeX-parse-macro "ext:tex")
(defvar citar-major-mode-functions)
(defcustom citar-latex-cite-commands
'((("cite" "Cite" "citet" "Citet" "citep" "Citep" "parencite"
"Parencite" "footcite" "footcitetext" "textcite" "Textcite"
"smartcite" "Smartcite" "cite*" "parencite*" "autocite"
"Autocite" "autocite*" "Autocite*" "citeauthor" "Citeauthor"
"citeauthor*" "Citeauthor*" "citetitle" "citetitle*" "citeyear"
"citeyear*" "citedate" "citedate*" "citeurl" "fullcite"
"footfullcite" "notecite" "Notecite" "pnotecite" "Pnotecite"
"fnotecite") . (["Prenote"] ["Postnote"] t))
(("nocite" "supercite") . nil))
"Citation commands and their argument specs.
The argument spec is the same as the args argument of
`TeX-parse-macro'. When calling `citar-insert-citation' the keys
will be inserted at the position where `TeX-parse-macro' leaves
the point."
:group 'citar-latex
:type '(alist :key-type (repeat string)
:value-type sexp))
(defcustom citar-latex-prompt-for-cite-style t
"Whether to prompt for a citation command when inserting."
:group 'citar
:type '(radio (const :tag "Prompt for a command" t)
(const :tag "Do not prompt for a command" nil))
:safe 'always)
(defcustom citar-latex-default-cite-command "cite"
"Default command for citations.
Must be in `citar-latex-cite-commands'. Used when as a cite
command when prompting for one is disabled, and as the default
entry when it is enabled."
:group 'citar
:type 'string
:safe 'always)
(defcustom citar-latex-prompt-for-extra-arguments t
"Whether to prompt for additional arguments when inserting a citation."
:group 'citar-latex
:type 'boolean)
;;;###autoload
(defun citar-latex-local-bib-files ()
"Local bibliographic for latex retrieved using reftex."
(ignore-errors
(reftex-access-scan-info t)
(reftex-get-bibfile-list)))
;;;###autoload
(defun citar-latex-key-at-point ()
"Return citation key at point with its bounds.
The return value is (KEY . BOUNDS), where KEY is the citation key
at point and BOUNDS is a pair of buffer positions.
Return nil if there is no key at point."
(save-excursion
(when-let* ((bounds (citar-latex--macro-bounds))
(keych "^,{}")
(beg (progn (skip-chars-backward keych (car bounds)) (point)))
(end (progn (skip-chars-forward keych (cdr bounds)) (point)))
(pre (buffer-substring-no-properties (car bounds) beg))
(post (buffer-substring-no-properties end (cdr bounds))))
(and (string-match-p "{\\([^{}]*,\\)?\\'" pre) ; preceded by { ... ,
(string-match-p "\\`\\(,[^{}]*\\)?}" post) ; followed by , ... }
(goto-char beg)
(looking-at (concat "[[:space:]]*\\([" keych "]+?\\)[[:space:]]*[,}]"))
(cons (match-string-no-properties 1)
(cons (match-beginning 1) (match-end 1)))))))
;;;###autoload
(defun citar-latex-citation-at-point ()
"Find citation macro at point and extract keys.
Find brace-delimited strings inside the bounds of the macro,
splits them at comma characters, and trims whitespace.
Return (KEYS . BOUNDS), where KEYS is a list of the found
citation keys and BOUNDS is a pair of buffer positions indicating
the start and end of the citation macro."
(save-excursion
(when-let ((bounds (citar-latex--macro-bounds)))
(let ((keylists nil))
(goto-char (car bounds))
(while (re-search-forward "{\\([^{}]*\\)}" (cdr bounds) 'noerror)
(push (split-string (match-string-no-properties 1) "," t "[[:space:]]*")
keylists))
(cons (apply #'append (nreverse keylists))
bounds)))))
(defun citar-latex--macro-bounds ()
"Return the bounds of the citation macro at point.
Return a pair of buffer positions indicating the beginning and
end of the enclosing citation macro, or nil if point is not
inside a citation macro."
(unless (fboundp 'TeX-find-macro-boundaries)
(error "Please install AUCTeX"))
(save-excursion
(when-let* ((bounds (TeX-find-macro-boundaries))
(macro (progn (goto-char (car bounds))
(looking-at (concat (regexp-quote TeX-esc)
"\\([@A-Za-z]+\\)"))
(match-string-no-properties 1))))
(when (citar-latex--is-a-cite-command macro)
bounds))))
(defvar citar-latex-cite-command-history nil
"Variable for history of cite commands.")
;;;###autoload
(defun citar-latex-insert-citation (keys &optional invert-prompt command)
"Insert a citation consisting of KEYS.
If the command is inside a citation command keys are added to it. Otherwise
a new command is started.
If the optional COMMAND is provided use it (ignoring INVERT-PROMPT).
Otherwise prompt for a citation command, depending on the value of
`citar-latex-prompt-for-cite-style'. If INVERT-PROMPT is non-nil, invert
whether or not to prompt.
The availiable commands and how to provide them arguments are configured
by `citar-latex-cite-commands'.
If `citar-latex-prompt-for-extra-arguments' is nil, every
command is assumed to have a single argument into which keys are
inserted."
(when keys
(if-let ((bounds (citar-latex--macro-bounds)))
(pcase-exhaustive (progn (skip-chars-forward "^,{}" (cdr bounds))
(following-char))
((guard (= (point) (cdr bounds))) ; couldn't find any of ",{}"
(insert "{}")
(backward-char))
((or ?{ ?,) ; insert after following "{" or ","
(forward-char)
(unless (looking-at-p "[[:space:]]*[},]")
(insert ",")
(backward-char)))
(?} ; insert before "}"
(skip-chars-backward "[:space:]")
(unless (member (preceding-char) '(?{ ?,))
(insert ","))))
(let ((macro
(or command
(if (xor invert-prompt citar-latex-prompt-for-cite-style)
(citar-latex--select-command)
citar-latex-default-cite-command))))
(TeX-parse-macro macro
(when citar-latex-prompt-for-extra-arguments
(cdr (citar-latex--is-a-cite-command macro))))))
(insert (string-join keys ","))
(skip-chars-forward "^}") (forward-char 1)))
;;;###autoload
(defun citar-latex-insert-edit (&optional _arg)
"Prompt for keys and call `citar-latex-insert-citation.
With ARG non-nil, rebuild the cache before offering candidates."
(citar-latex-insert-citation (citar-select-refs)))
(defun citar-latex--select-command ()
"Complete a citation command for LaTeX."
(completing-read "Cite command: "
(seq-mapcat #'car citar-latex-cite-commands)
nil nil nil
'citar-latex-cite-command-history
citar-latex-default-cite-command nil))
(defun citar-latex--is-a-cite-command (command)
"Return element of `citar-latex-cite-commands' containing COMMAND."
(seq-find (lambda (x) (member command (car x)))
citar-latex-cite-commands))
;;;###autoload
(defalias 'citar-latex-list-keys #'reftex-all-used-citation-keys)
(provide 'citar-latex)
;;; citar-latex.el ends here