Skip to content
6 changes: 4 additions & 2 deletions README.org
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,10 @@ Such adapters can provide the following capabilities, which one can configure wi

1. ~insert-keys~: to insert citation keys (this may go away though)
2. ~insert-citation~: to insert citations
3. ~local-bib-files~: to find bibliographic files associated with a buffer
4. ~keys-at-point~: returns a list of citekeys at point
3. ~insert-edit~: to insert citations or edit at point
4. ~local-bib-files~: to find bibliographic files associated with a buffer
5. ~key-at-point~: returns the citation key at point
6. ~citation-at-point~: returns the list of keys in the citation at point

Citar currently includes the following such adapters:

Expand Down
77 changes: 65 additions & 12 deletions citar-latex.el
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
(defvar citar-major-mode-functions)

(defcustom citar-latex-cite-commands
'((("cite" "Cite" "citet" "Citet" "parencite"
'((("cite" "Cite" "citet" "Citet" "citep" "Citep" "parencite"
"Parencite" "footcite" "footcitetext" "textcite" "Textcite"
"smartcite" "Smartcite" "cite*" "parencite*" "autocite"
"Autocite" "autocite*" "Autocite*" "citeauthor" "Citeauthor"
Expand Down Expand Up @@ -65,17 +65,63 @@ the point."
(ignore-errors (reftex-get-bibfile-list)))

;;;###autoload
(defun citar-latex-keys-at-point ()
"Return a list of keys at point in a latex buffer."
(unless (fboundp 'TeX-current-macro)
(error "Please install AUCTeX"))
(when (citar-latex-is-a-cite-command (TeX-current-macro))
(split-string (thing-at-point 'list t) "," t "[{} ]+")))
(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-insert-keys (keys)
"Insert comma sperated KEYS in a latex buffer."
(insert (string-join keys ", ")))
(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.")
Expand Down Expand Up @@ -109,13 +155,20 @@ inserted."
(TeX-parse-macro macro
(when citar-latex-prompt-for-extra-arguments
(cdr (citar-latex-is-a-cite-command macro))))))
(citar-latex-insert-keys keys)
(citar--insert-keys-comma-separated 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--extract-keys (citar-select-refs :rebuild-cache arg))))

(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))
citar-latex-cite-commands))

(provide 'citar-latex)
;;; citar-latex.el ends here
97 changes: 75 additions & 22 deletions citar-markdown.el
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
;;; Code:

(require 'citar)
(require 'thingatpt)

(defvar citar-major-mode-functions)

Expand All @@ -39,37 +40,89 @@

;;;###autoload
(defun citar-markdown-insert-keys (keys)
"Insert comma sperated KEYS in a markdown buffer."
"Insert semicolon-separated and @-prefixed KEYS in a markdown buffer."
(insert (mapconcat (lambda (k) (concat "@" k)) keys "; ")))

;;;###autoload
(defun citar-markdown-insert-citation (keys)
"Insert a pandoc-style citation consisting of KEYS."
(let* ((prenote (if citar-markdown-prompt-for-extra-arguments
(read-from-minibuffer "Prenote: ")))
(postnote (if citar-markdown-prompt-for-extra-arguments
(read-from-minibuffer "Postnote: ")))
(prenote (if (string= "" prenote) "" (concat prenote " ")))
(postnote (if (string= "" postnote) "" (concat ", " postnote))))
(insert (format "[%s%s%s]"
prenote
(mapconcat (lambda (k) (concat "@" k)) keys "; ")
postnote))))

(defconst citar-markdown-regex-citation-key
"\\(-?@\\([[:alnum:]_][[:alnum:]_:.#$%&+?<>~/-]*\\)\\)"
;; borrowed from pandoc-mode
"Insert a pandoc-style citation consisting of KEYS.

If the point is inside a citation, add new keys after the current
key.

If point is immediately after the opening \[, add new keys
to the beginning of the citation."
(let* ((citation (citar-markdown-citation-at-point))
(keys (if citation (seq-difference keys (car citation)) keys))
(keyconcat (mapconcat (lambda (k) (concat "@" k)) keys "; ")))
(when keys
(if (or (not citation)
(= (point) (cadr citation))
(= (point) (cddr citation)))
(let* ((prenote (when citar-markdown-prompt-for-extra-arguments
(read-from-minibuffer "Prenote: ")))
(postnote (when citar-markdown-prompt-for-extra-arguments
(read-from-minibuffer "Postnote: ")))
(prenote (if (string= "" prenote) "" (concat prenote " ")))
(postnote (if (string= "" postnote) "" (concat ", " postnote))))
(insert (format "[%s%s%s]" prenote keyconcat postnote)))
(if (= (point) (1+ (cadr citation)))
(save-excursion (insert keyconcat "; "))
(skip-chars-forward "^;]" (cddr citation))
(insert "; " keyconcat))))))

;;;###autoload
(defun citar-markdown-insert-edit (&optional arg)
"Prompt for keys and call `citar-markdown-insert-citation.
With ARG non-nil, rebuild the cache before offering candidates."
(citar-markdown-insert-citation
(citar--extract-keys (citar-select-refs :rebuild-cache arg))))

(defconst citar-markdown-citation-key-regexp
(concat "-?@" ; @ preceded by optional -
"\\(?:"
"{\\(?1:.*?\\)}" ; brace-delimited key
"\\|"
"\\(?1:[[:alnum:]_][[:alnum:]]*\\(?:[:.#$%&+?<>~/-][[:alnum:]]+\\)*\\)"
"\\)")
"Regular expression for a citation key.")

;;;###autoload
(defun citar-markdown-key-at-point ()
"Return a citation key at point for pandoc markdown citations."
"Return citation key at point (with its bounds) for pandoc markdown citations.
Returns (KEY . BOUNDS), where KEY is the citation key at point
and BOUNDS is a pair of buffer positions. Citation keys are
found using `citar-markdown-citation-key-regexp`. Returns nil if
there is no key at point."
(interactive)
(when (thing-at-point-looking-at citar-markdown-regex-citation-key)
(let ((stab (copy-syntax-table)))
(with-syntax-table stab
(modify-syntax-entry ?@ "_")
(cadr (split-string (thing-at-point 'symbol) "[]@;]"))))))
(when (thing-at-point-looking-at citar-markdown-citation-key-regexp)
(cons (match-string-no-properties 1)
(cons (match-beginning 0) (match-end 0)))))

;;;###autoload
(defun citar-markdown-citation-at-point ()
"Return keys of citation at point.
Find balanced expressions starting and ending with square
brackets and containing at least one citation key (matching
`citar-markdown-citation-key-regexp`). 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."
(save-excursion
(cond
((eq ?\[ (char-after)) (forward-char))
((eq ?\] (char-before)) (backward-char)))
(seq-some ; for each opening paren
(lambda (startpos) ; return keys in balanced [ ] expr
(when-let ((endpos (and (eq ?\[ (char-after startpos))
(scan-lists startpos 1 0))))
(let (keys)
(goto-char startpos)
(while (re-search-forward citar-markdown-citation-key-regexp endpos t)
(push (match-string-no-properties 1) keys))
(when keys
(cons (nreverse keys) (cons startpos endpos))))))
(reverse (nth 9 (syntax-ppss))))))

(provide 'citar-markdown)
;;; citar-markdown.el ends here
Loading