summaryrefslogtreecommitdiff
path: root/lisp/citre-backend.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/citre-backend.el')
-rw-r--r--lisp/citre-backend.el138
1 files changed, 138 insertions, 0 deletions
diff --git a/lisp/citre-backend.el b/lisp/citre-backend.el
new file mode 100644
index 0000000..8c50c6d
--- /dev/null
+++ b/lisp/citre-backend.el
@@ -0,0 +1,138 @@
+;;; citre-backend.el --- Live and persistent tags through Citre -*- lexical-binding: t; -*-
+
+(require 'citre)
+(require 'citre-tags)
+(require 'citre-index)
+
+(defvar company--capf-cache)
+(defvar-local my/citre-backend--completion-cache nil)
+
+;; Retire the popup restart timer when reloading an earlier version of this module.
+(when (boundp 'my/citre-backend--company-timer)
+ (my/citre-index--cancel-timer (symbol-value 'my/citre-backend--company-timer))
+ (set 'my/citre-backend--company-timer nil))
+
+(defun my/citre-backend--sources ()
+ "Return (TAGS-FILE . LIVE-SOURCE) pairs in priority order."
+ (if-let* ((state my/citre-index--state))
+ (let* ((entries (my/citre-index-live-entries state))
+ (current (my/citre-index--path))
+ (live (sort entries (lambda (a b) (and (equal (car a) current) (not (equal (car b) current)))))))
+ (append
+ (mapcar (lambda (entry) (cons (plist-get (cdr entry) :file) (car entry))) live)
+ (let ((project (expand-file-name "project.tags" (my/citre-index-state-cache state))))
+ (if (file-readable-p project)
+ (list (cons project nil))
+ (when-let* ((legacy (citre-tags-file-path))) (list (cons legacy nil)))))
+ (let ((dependencies (expand-file-name "dependencies.tags" (my/citre-index-state-cache state))))
+ (when (file-readable-p dependencies) (list (cons dependencies nil))))))
+ (when-let* ((file (citre-tags-file-path))) (list (cons file nil)))))
+
+(defun my/citre-backend--query (operation &optional identifier)
+ "Merge tags for OPERATION, optionally using IDENTIFIER, while masking superseded files."
+ (let* ((sources (my/citre-backend--sources))
+ (masked (delq nil (mapcar #'cdr sources)))
+ (seen (make-hash-table :test #'equal))
+ result)
+ (dolist (source sources)
+ (let* ((file (car source))
+ (symbol (or (and identifier
+ (citre-put-property (copy-sequence identifier)
+ 'file-path buffer-file-name 'tags-file file))
+ (and (not (memq operation '(identifiers imenu)))
+ (citre-tags-get-symbol file))))
+ (filter
+ (pcase operation
+ ('completion (when symbol (or (citre-tags--get-value-in-language-alist :completion-filter symbol)
+ (citre-tags-completion-default-filter symbol))))
+ ('definition (when symbol (or (citre-tags--get-value-in-language-alist :definition-filter symbol)
+ (citre-tags-definition-default-filter symbol))))
+ ('imenu (list 'and (citre-readtags-filter-input (my/citre-index--path) file)
+ (list 'not citre-tags-filter-file-tags)))
+ (_ (list 'not citre-tags-filter-file-tags))))
+ (sorter
+ (pcase operation
+ ('completion (when symbol (or (citre-tags--get-value-in-language-alist :completion-sorter symbol)
+ citre-tags-completion-default-sorter)))
+ ('imenu (citre-readtags-sorter 'line))
+ (_ citre-tags-definition-default-sorter)))
+ (tags (when (or symbol (memq operation '(identifiers imenu)))
+ (citre-tags-get-tags
+ file symbol (if (eq operation 'completion) (if citre-tags-substr-completion 'substr 'prefix) 'exact)
+ :filter filter :sorter sorter
+ :require '(name ext-abspath)
+ :optional '(pattern line end ext-kind-full signature scope typeref extras language)))))
+ (dolist (tag tags)
+ (let* ((path (citre-get-tag-field 'ext-abspath tag))
+ (key (mapcar (lambda (field) (citre-get-tag-field field tag))
+ '(name ext-abspath line scope ext-kind-full))))
+ (unless (or (and (not (cdr source)) (member path masked)) (gethash key seen))
+ (puthash key t seen)
+ (push tag result))))))
+ (nreverse result)))
+
+(defun my/citre-backend-completions ()
+ "Return current Citre completion bounds and tags, reusing a narrowing prefix."
+ (when-let* ((source (car (my/citre-backend--sources)))
+ (symbol (citre-tags-get-symbol (car source)))
+ (bounds (citre-get-property 'bounds symbol)))
+ (let* ((revision (and my/citre-index--state (my/citre-index-state-revision my/citre-index--state)))
+ (context (list revision (car bounds) major-mode citre-tags-substr-completion
+ (buffer-substring-no-properties (line-beginning-position) (car bounds))))
+ (cache my/citre-backend--completion-cache)
+ (tags (if (and revision (equal context (plist-get cache :context))
+ (string-prefix-p (plist-get cache :prefix) symbol))
+ (plist-get cache :tags)
+ (let ((tags (my/citre-backend--query 'completion)))
+ (setq my/citre-backend--completion-cache
+ (list :context context :prefix (substring-no-properties symbol) :tags tags))
+ tags))))
+ (when tags (list (car bounds) (cdr bounds) tags)))))
+
+(defun my/citre-backend-definitions ()
+ "Find definitions in the current live and persistent indexes."
+ (my/citre-backend--query 'definition))
+
+(defun my/citre-backend-definitions-of-id (identifier)
+ "Find definitions of IDENTIFIER in the current project."
+ (my/citre-backend--query 'definition identifier))
+
+(defun my/citre-backend-identifiers ()
+ "Return all current identifiers for Citre's query UI."
+ (delete-dups (mapcar (lambda (tag) (citre-get-tag-field 'name tag))
+ (my/citre-backend--query 'identifiers))))
+
+(defun my/citre-backend-imenu ()
+ "Return the current file's symbols for Imenu, including unsaved changes."
+ (my/citre-backend--query 'imenu))
+
+(defun my/citre-backend-usable-p ()
+ "Whether the current buffer has an automatic or conventional tags index."
+ (or my/citre-index--state (citre-tags-usable-p)))
+
+(defun my/citre-backend--updated (state)
+ "Invalidate data for STATE without restarting or rearranging an open Company menu."
+ (dolist (buffer (my/citre-index--buffers state))
+ (with-current-buffer buffer
+ (setq my/citre-backend--completion-cache nil)
+ (when (boundp 'imenu--index-alist) (setq imenu--index-alist nil))
+ ;; Fresh data is consumed on the next edit or completion request. Navigation keeps its current candidates and selection.
+ (when (and (boundp 'company--capf-cache)
+ (eq (car company--capf-cache) buffer))
+ (setq company--capf-cache nil)))))
+
+(citre-register-backend
+ 'my-index
+ (citre-backend-create :usable-probe #'my/citre-backend-usable-p
+ :symbol-at-point-fn #'citre-tags-symbol-at-point
+ :completions-fn #'my/citre-backend-completions
+ :defs-fn #'my/citre-backend-definitions
+ :defs-of-id-fn #'my/citre-backend-definitions-of-id
+ :id-list-fn #'my/citre-backend-identifiers
+ :tags-in-buffer-fn #'my/citre-backend-imenu
+ :after-jump-fn #'my/citre-index-attach))
+
+(add-hook 'my/citre-index-updated-hook #'my/citre-backend--updated)
+
+(provide 'citre-backend)
+;;; citre-backend.el ends here