update packages
This commit is contained in:
@@ -86,6 +86,7 @@
|
||||
(require 'org-element)
|
||||
(require 'org-ref-utils)
|
||||
(require 'ox)
|
||||
(require 'org-ref-ref-links) ; For multi-file support utilities
|
||||
|
||||
;;; Code:
|
||||
(defgroup org-ref-glossary nil
|
||||
@@ -102,6 +103,28 @@ This is not always fast, so we provide a way to disable it."
|
||||
:group 'org-ref-glossary)
|
||||
|
||||
|
||||
(defcustom org-ref-glossary-show-tooltips t
|
||||
"If non-nil, show tooltips when hovering over glossary and acronym links.
|
||||
When nil, tooltips are disabled entirely for glossary links, which can
|
||||
improve responsiveness if you find the tooltips distracting or slow.
|
||||
|
||||
This is separate from `org-ref-activate-glossary-links' which controls
|
||||
whether links are fontified and clickable."
|
||||
:type 'boolean
|
||||
:group 'org-ref-glossary)
|
||||
|
||||
|
||||
(defcustom org-ref-glossary-enable-multi-file t
|
||||
"Enable scanning #+INCLUDE'd files for glossary/acronym definitions.
|
||||
When non-nil, glossary and acronym lookups will search in files included
|
||||
via #+INCLUDE directives, enabling multi-file document support.
|
||||
|
||||
Uses timestamp-based caching to maintain performance. Only files that have
|
||||
changed since the last scan are re-parsed."
|
||||
:type 'boolean
|
||||
:group 'org-ref-glossary)
|
||||
|
||||
|
||||
(defcustom org-ref-glsentries '()
|
||||
"Variable to hold locations of glsentries load files.")
|
||||
|
||||
@@ -114,6 +137,18 @@ This is not always fast, so we provide a way to disable it."
|
||||
"Buffer-local variable for acronym entry cache.")
|
||||
|
||||
|
||||
(defvar org-ref-glossary-file-cache (make-hash-table :test 'equal)
|
||||
"Global cache of glossary entries per file.
|
||||
Maps file paths to lists of glossary entry plists.
|
||||
Used when `org-ref-glossary-enable-multi-file' is non-nil.")
|
||||
|
||||
|
||||
(defvar org-ref-acronym-file-cache (make-hash-table :test 'equal)
|
||||
"Global cache of acronym entries per file.
|
||||
Maps file paths to lists of acronym entry plists.
|
||||
Used when `org-ref-glossary-enable-multi-file' is non-nil.")
|
||||
|
||||
|
||||
(defun or-find-closing-curly-bracket (&optional limit)
|
||||
"Find closing bracket for the bracket at point and move point to it.
|
||||
Go up to LIMIT or `point-max'. This is a parsing function. I
|
||||
@@ -160,12 +195,12 @@ changes."
|
||||
(let* (end-of-entry
|
||||
data
|
||||
(external (when (re-search-forward
|
||||
"\\loadglsentries\\(\\[.*\\]\\){\\(?1:.*\\)}" nil t)
|
||||
"\\\\loadglsentries\\(\\[.*\\]\\)?{\\(?1:.*\\)}" nil t)
|
||||
(match-string 1)))
|
||||
(glsentries (and external
|
||||
(or (cdr (assoc external org-ref-glsentries))
|
||||
(progn
|
||||
(cl-pushnew (cons external (s-trim
|
||||
(cl-pushnew (cons external (string-trim
|
||||
(shell-command-to-string
|
||||
(format "kpsewhich tex %s"
|
||||
external))))
|
||||
@@ -268,6 +303,131 @@ changes."
|
||||
data))))
|
||||
|
||||
|
||||
;;** Multi-file glossary support
|
||||
|
||||
(defun or-scan-file-for-glossary-table (file)
|
||||
"Scan FILE and return list of glossary entries from #+name: glossary table.
|
||||
Returns list of plists with :label, :name, :description, :file.
|
||||
Returns nil if no glossary table is found in FILE."
|
||||
(when (file-exists-p file)
|
||||
(with-temp-buffer
|
||||
(insert-file-contents file)
|
||||
(org-mode)
|
||||
(let ((entries '()))
|
||||
(or (catch 'found
|
||||
(org-element-map
|
||||
(org-element-parse-buffer)
|
||||
'table
|
||||
(lambda (el)
|
||||
(when (string= "glossary" (org-element-property :name el))
|
||||
(goto-char (org-element-property :contents-begin el))
|
||||
(let ((table-data (nthcdr 2 (org-babel-read-table))))
|
||||
;; Convert table rows to plists
|
||||
(dolist (row table-data)
|
||||
(when (and (listp row) (= 3 (length row)))
|
||||
(push (list :label (nth 0 row)
|
||||
:name (nth 1 row)
|
||||
:description (nth 2 row)
|
||||
:file file)
|
||||
entries)))
|
||||
(throw 'found (nreverse entries)))))))
|
||||
entries)))))
|
||||
|
||||
|
||||
(defun or-scan-file-for-acronym-table (file)
|
||||
"Scan FILE and return list of acronym entries from #+name: acronyms table.
|
||||
Returns list of plists with :label, :abbrv, :full, :file.
|
||||
Returns nil if no acronym table is found in FILE."
|
||||
(when (file-exists-p file)
|
||||
(with-temp-buffer
|
||||
(insert-file-contents file)
|
||||
(org-mode)
|
||||
(let ((entries '()))
|
||||
(or (catch 'found
|
||||
(org-element-map
|
||||
(org-element-parse-buffer)
|
||||
'table
|
||||
(lambda (el)
|
||||
(when (string= "acronyms" (org-element-property :name el))
|
||||
(goto-char (org-element-property :contents-begin el))
|
||||
(let ((table-data (nthcdr 2 (org-babel-read-table))))
|
||||
;; Convert table rows to plists
|
||||
(dolist (row table-data)
|
||||
(when (and (listp row) (= 3 (length row)))
|
||||
(push (list :label (nth 0 row)
|
||||
:abbrv (nth 1 row)
|
||||
:full (nth 2 row)
|
||||
:file file)
|
||||
entries)))
|
||||
(throw 'found (nreverse entries)))))))
|
||||
entries)))))
|
||||
|
||||
|
||||
(defun or-parse-glossary-entry-multi-file (label)
|
||||
"Find glossary LABEL in current file or included files.
|
||||
Uses timestamp-based caching to avoid re-scanning unchanged files.
|
||||
Returns plist with :label, :name, :description, :file."
|
||||
(when (and (boundp 'org-ref-glossary-enable-multi-file)
|
||||
org-ref-glossary-enable-multi-file
|
||||
(boundp 'org-ref-glossary-file-cache)
|
||||
(buffer-file-name))
|
||||
(cl-block or-parse-glossary-entry-multi-file
|
||||
(let* ((current-file (buffer-file-name))
|
||||
(included-files (org-ref-get-included-files))
|
||||
(all-files (cons current-file included-files)))
|
||||
|
||||
;; Scan each file (with caching)
|
||||
(dolist (file all-files)
|
||||
;; Scan if file changed OR if not in cache yet
|
||||
(when (or (org-ref-file-changed-p file)
|
||||
(not (gethash file org-ref-glossary-file-cache)))
|
||||
;; File changed or not cached, scan it
|
||||
(let ((file-entries (or-scan-file-for-glossary-table file)))
|
||||
(puthash file file-entries org-ref-glossary-file-cache)
|
||||
(org-ref-mark-file-scanned file)))
|
||||
|
||||
;; Look for label in this file's cache
|
||||
(let ((entries (gethash file org-ref-glossary-file-cache)))
|
||||
(when entries
|
||||
(let ((entry (cl-find label entries
|
||||
:key (lambda (e) (plist-get e :label))
|
||||
:test 'string=)))
|
||||
(when entry
|
||||
(cl-return-from or-parse-glossary-entry-multi-file entry))))))))))
|
||||
|
||||
|
||||
(defun or-parse-acronym-entry-multi-file (label)
|
||||
"Find acronym LABEL in current file or included files.
|
||||
Uses timestamp-based caching to avoid re-scanning unchanged files.
|
||||
Returns plist with :label, :abbrv, :full, :file."
|
||||
(when (and (boundp 'org-ref-glossary-enable-multi-file)
|
||||
org-ref-glossary-enable-multi-file
|
||||
(boundp 'org-ref-acronym-file-cache)
|
||||
(buffer-file-name))
|
||||
(cl-block or-parse-acronym-entry-multi-file
|
||||
(let* ((current-file (buffer-file-name))
|
||||
(included-files (org-ref-get-included-files))
|
||||
(all-files (cons current-file included-files)))
|
||||
|
||||
;; Scan each file (with caching)
|
||||
(dolist (file all-files)
|
||||
;; Scan if file changed OR if not in cache yet
|
||||
(when (or (org-ref-file-changed-p file)
|
||||
(not (gethash file org-ref-acronym-file-cache)))
|
||||
;; File changed or not cached, scan it
|
||||
(let ((file-entries (or-scan-file-for-acronym-table file)))
|
||||
(puthash file file-entries org-ref-acronym-file-cache)
|
||||
(org-ref-mark-file-scanned file)))
|
||||
|
||||
;; Look for label in this file's cache
|
||||
(let ((entries (gethash file org-ref-acronym-file-cache)))
|
||||
(when entries
|
||||
(let ((entry (cl-find label entries
|
||||
:key (lambda (e) (plist-get e :label))
|
||||
:test 'string=)))
|
||||
(when entry
|
||||
(cl-return-from or-parse-acronym-entry-multi-file entry))))))))))
|
||||
|
||||
|
||||
;;** Glossary links
|
||||
|
||||
@@ -276,13 +436,22 @@ changes."
|
||||
set data on text with properties
|
||||
Set face property, and help-echo."
|
||||
(let ((data (or (or-parse-glossary-entry path)
|
||||
(or-parse-acronym-entry path))))
|
||||
(or-parse-acronym-entry path)
|
||||
;; Try multi-file lookup if enabled and not found in current buffer
|
||||
(or-parse-glossary-entry-multi-file path)
|
||||
(or-parse-acronym-entry-multi-file path))))
|
||||
(add-text-properties
|
||||
start end
|
||||
(list 'or-glossary data
|
||||
'face (if data
|
||||
'org-ref-glossary-face
|
||||
'font-lock-warning-face)))))
|
||||
'font-lock-warning-face)
|
||||
;; Suppress spell-checking with nospell property.
|
||||
;; For jinx users: add 'nospell to jinx-exclude-properties:
|
||||
;; (setq jinx-exclude-properties '((org-mode read-only nospell)))
|
||||
;; Or exclude by face using jinx-exclude-faces:
|
||||
;; (add-to-list 'jinx-exclude-faces 'org-ref-glossary-face)
|
||||
'nospell t))))
|
||||
|
||||
(defface org-ref-glossary-face
|
||||
`((t (:inherit org-link :foreground "Mediumpurple3")))
|
||||
@@ -290,28 +459,53 @@ Set face property, and help-echo."
|
||||
|
||||
|
||||
(defun or-follow-glossary (entry)
|
||||
"Goto beginning of the glossary ENTRY."
|
||||
"Goto beginning of the glossary ENTRY.
|
||||
If entry is in an included file, opens that file and navigates to the glossary table."
|
||||
(org-mark-ring-push)
|
||||
(goto-char (plist-get (get-text-property (point) 'or-glossary) :position)))
|
||||
(let* ((data (get-text-property (point) 'or-glossary))
|
||||
(file (plist-get data :file))
|
||||
(label (plist-get data :label))
|
||||
(position (plist-get data :position)))
|
||||
(cond
|
||||
;; Entry in current buffer (has position)
|
||||
(position
|
||||
(goto-char position))
|
||||
;; Entry in external file
|
||||
(file
|
||||
(find-file file)
|
||||
(goto-char (point-min))
|
||||
(when (re-search-forward "^[ \t]*#\\+name:[ \t]+\\(glossary\\|acronyms\\)" nil t)
|
||||
(when (re-search-forward (regexp-quote label) nil t)
|
||||
(goto-char (line-beginning-position)))))
|
||||
;; Fallback: search in current buffer
|
||||
(t
|
||||
(goto-char (point-min))
|
||||
(when (re-search-forward "^[ \t]*#\\+name:[ \t]+\\(glossary\\|acronyms\\)" nil t)
|
||||
(when (re-search-forward (regexp-quote label) nil t)
|
||||
(goto-char (line-beginning-position))))))))
|
||||
|
||||
|
||||
(defun or-glossary-tooltip (_window buffer position)
|
||||
"Return tooltip for the glossary entry.
|
||||
The entry is in WINDOW and OBJECT at POSITION.
|
||||
Used in fontification."
|
||||
(with-current-buffer buffer
|
||||
(let* ((data (get-text-property position 'or-glossary))
|
||||
(name (or (plist-get data :name)
|
||||
(plist-get data :abbrv)))
|
||||
(description (or (plist-get data :description)
|
||||
(plist-get data :full))))
|
||||
(format
|
||||
"%s: %s"
|
||||
name
|
||||
(with-temp-buffer
|
||||
(insert (concat description "."))
|
||||
(fill-paragraph)
|
||||
(buffer-string))))))
|
||||
Used in fontification."
|
||||
(when org-ref-glossary-show-tooltips
|
||||
(with-current-buffer buffer
|
||||
(let ((data (get-text-property position 'or-glossary)))
|
||||
(if data
|
||||
(let ((name (or (plist-get data :name)
|
||||
(plist-get data :abbrv)))
|
||||
(description (or (plist-get data :description)
|
||||
(plist-get data :full))))
|
||||
(when (and name description)
|
||||
(format "%s: %s"
|
||||
name
|
||||
(with-temp-buffer
|
||||
(insert (concat description "."))
|
||||
(fill-paragraph)
|
||||
(buffer-string)))))
|
||||
;; No data found - return helpful message or nil
|
||||
"This is not defined in this file.")))))
|
||||
|
||||
|
||||
(defvar org-ref-glossary-gls-commands
|
||||
@@ -431,13 +625,13 @@ The plist maps to \newacronym{<label>}{<abbrv>}{<full>}"
|
||||
(goto-char (point-min))
|
||||
(let* (abbrv
|
||||
full p1
|
||||
(external (when (re-search-forward "\\loadglsentries\\(\\[.*\\]\\){\\(?1:.*\\)}" nil t)
|
||||
(external (when (re-search-forward "\\\\loadglsentries\\(\\[.*\\]\\)?{\\(?1:.*\\)}" nil t)
|
||||
(match-string 1)))
|
||||
(glsentries (and external
|
||||
(or (cdr (assoc external org-ref-glsentries))
|
||||
(progn
|
||||
(cl-pushnew (cons external
|
||||
(s-trim (shell-command-to-string
|
||||
(string-trim (shell-command-to-string
|
||||
(format "kpsewhich tex %s" external))))
|
||||
org-ref-glsentries)
|
||||
(cdr (assoc external org-ref-glsentries))))))
|
||||
@@ -502,30 +696,50 @@ The plist maps to \newacronym{<label>}{<abbrv>}{<full>}"
|
||||
"Activate function for an acronym link.
|
||||
set data on text with properties
|
||||
Set face property, and help-echo."
|
||||
(let ((data (or-parse-acronym-entry path)))
|
||||
(let ((data (or (or-parse-acronym-entry path)
|
||||
;; Try multi-file lookup if enabled and not found in current buffer
|
||||
(or-parse-acronym-entry-multi-file path))))
|
||||
(add-text-properties
|
||||
start end
|
||||
(list 'or-glossary data
|
||||
'face (if data
|
||||
'org-ref-acronym-face
|
||||
'font-lock-warning-face)))))
|
||||
'font-lock-warning-face)
|
||||
;; Suppress spell-checking with nospell property.
|
||||
;; For jinx users: add 'nospell to jinx-exclude-properties:
|
||||
;; (setq jinx-exclude-properties '((org-mode read-only nospell)))
|
||||
;; Or exclude by face using jinx-exclude-faces:
|
||||
;; (add-to-list 'jinx-exclude-faces 'org-ref-acronym-face)
|
||||
'nospell t))))
|
||||
|
||||
|
||||
(defun or-follow-acronym (label)
|
||||
"Go to the definition of the acronym LABEL."
|
||||
"Go to the definition of the acronym LABEL.
|
||||
If entry is in an included file, opens that file and navigates to the acronym table."
|
||||
(org-mark-ring-push)
|
||||
(cond
|
||||
;; table first
|
||||
((progn (goto-char (point-min))
|
||||
(and (re-search-forward "#\\+name: acronyms" nil t)
|
||||
(re-search-forward label nil t)))
|
||||
nil)
|
||||
|
||||
((progn (goto-char (point-min)) (re-search-forward (format "\\newacronym{%s}" label) nil t))
|
||||
(goto-char (match-beginning 0)))
|
||||
|
||||
(t
|
||||
(message "no entry found for %s" label))))
|
||||
(let* ((data (get-text-property (point) 'or-glossary))
|
||||
(file (plist-get data :file))
|
||||
(entry-label (plist-get data :label)))
|
||||
(cond
|
||||
;; Entry in external file
|
||||
(file
|
||||
(find-file file)
|
||||
(goto-char (point-min))
|
||||
(if (and (re-search-forward "^[ \t]*#\\+name:[ \t]+acronyms" nil t)
|
||||
(re-search-forward (regexp-quote entry-label) nil t))
|
||||
(goto-char (line-beginning-position))
|
||||
(message "Entry %s not found in %s" entry-label file)))
|
||||
;; Entry in current buffer - try table first
|
||||
((progn (goto-char (point-min))
|
||||
(and (re-search-forward "#\\+name: acronyms" nil t)
|
||||
(re-search-forward label nil t)))
|
||||
nil)
|
||||
;; Try LaTeX format
|
||||
((progn (goto-char (point-min))
|
||||
(re-search-forward (format "\\newacronym{%s}" label) nil t))
|
||||
(goto-char (match-beginning 0)))
|
||||
(t
|
||||
(message "no entry found for %s" label)))))
|
||||
|
||||
|
||||
;;** Tooltips on acronyms
|
||||
@@ -539,17 +753,17 @@ Set face property, and help-echo."
|
||||
The entry is in WINDOW and OBJECT at POSITION.
|
||||
Used in fontification.
|
||||
WINDOW and OBJECT are ignored."
|
||||
(with-current-buffer buffer
|
||||
(save-excursion
|
||||
(goto-char position)
|
||||
(let* ((acronym-data (get-text-property position 'or-glossary))
|
||||
(abbrv (plist-get acronym-data :abbrv))
|
||||
(full (plist-get acronym-data :full)))
|
||||
(if acronym-data
|
||||
(format
|
||||
"%s: %s"
|
||||
abbrv full)
|
||||
(format "This is not defined in this file."))))))
|
||||
(when org-ref-glossary-show-tooltips
|
||||
(with-current-buffer buffer
|
||||
(save-excursion
|
||||
(goto-char position)
|
||||
(let ((acronym-data (get-text-property position 'or-glossary)))
|
||||
(if acronym-data
|
||||
(let ((abbrv (plist-get acronym-data :abbrv))
|
||||
(full (plist-get acronym-data :full)))
|
||||
(when (and abbrv full)
|
||||
(format "%s: %s" abbrv full)))
|
||||
"This is not defined in this file."))))))
|
||||
|
||||
|
||||
(defvar org-ref-acronym-types
|
||||
@@ -934,5 +1148,40 @@ Meant for non-LaTeX exports."
|
||||
(make-string (org-element-property :post-blank lnk) ? )))))))
|
||||
|
||||
|
||||
;;** Jinx spell-checker integration
|
||||
|
||||
;; Automatically configure jinx to exclude glossary and acronym links from
|
||||
;; spell-checking if jinx is loaded. This prevents jinx from marking these
|
||||
;; links as typos.
|
||||
;;
|
||||
;; This configuration applies immediately if jinx is already loaded, or will
|
||||
;; take effect when jinx is loaded in the future.
|
||||
(defun org-ref-glossary--configure-jinx ()
|
||||
"Add glossary and acronym faces to jinx-exclude-faces for org-mode."
|
||||
(when (boundp 'jinx-exclude-faces)
|
||||
;; jinx-exclude-faces is an alist: ((mode face1 face2 ...) ...)
|
||||
;; We need to add our faces to the org-mode entry
|
||||
(let ((org-entry (assq 'org-mode jinx-exclude-faces)))
|
||||
(if org-entry
|
||||
;; org-mode entry exists, add our faces to it
|
||||
(progn
|
||||
(unless (memq 'org-ref-glossary-face org-entry)
|
||||
(setcdr org-entry (cons 'org-ref-glossary-face (cdr org-entry))))
|
||||
(unless (memq 'org-ref-acronym-face org-entry)
|
||||
(setcdr org-entry (cons 'org-ref-acronym-face (cdr org-entry)))))
|
||||
;; org-mode entry doesn't exist, create it
|
||||
(push '(org-mode org-ref-glossary-face org-ref-acronym-face)
|
||||
jinx-exclude-faces)))
|
||||
(message "org-ref-glossary: Configured jinx to exclude glossary/acronym faces in org-mode")))
|
||||
|
||||
;; Configure immediately if jinx is already loaded
|
||||
(when (featurep 'jinx)
|
||||
(org-ref-glossary--configure-jinx))
|
||||
|
||||
;; Also configure when jinx loads in the future
|
||||
(with-eval-after-load 'jinx
|
||||
(org-ref-glossary--configure-jinx))
|
||||
|
||||
|
||||
(provide 'org-ref-glossary)
|
||||
;;; org-ref-glossary.el ends here
|
||||
|
||||
Reference in New Issue
Block a user