WIP: Accept entry IDs as IN argument

This commit is contained in:
Adam Porter 2023-12-14 17:53:02 -06:00
parent 39394c831a
commit e57d50b81c

144
org-ql.el
View file

@ -333,11 +333,12 @@ See Info node `(org-ql)Queries'."
(sxhash-equal (prin1-to-string args)))) (sxhash-equal (prin1-to-string args))))
;;;###autoload ;;;###autoload
(cl-defun org-ql-select (buffers-or-files query &key action narrow sort) (cl-defun org-ql-select (in query &key action narrow sort)
"Return items matching QUERY in BUFFERS-OR-FILES. "Return items matching QUERY in IN.
BUFFERS-OR-FILES is a file or buffer, a list of files and/or IN is a buffer, file, or Org entry ID string (i.e. findable with
buffers, or a function which returns such a list. `org-id-goto'), or a list of one or more of such items, or a
function which returns such a list.
QUERY is an `org-ql' query sexp (quoted, since this is a QUERY is an `org-ql' query sexp (quoted, since this is a
function). function).
@ -374,23 +375,28 @@ would appear first. In contrast, `(date reverse priority)' would
also present items with the highest priority first, but within also present items with the highest priority first, but within
each priority the newest items would appear first." each priority the newest items would appear first."
(declare (indent defun)) (declare (indent defun))
(-let* ((buffers (->> (cl-typecase buffers-or-files (-let* ((in (->> (cl-typecase in
(null (list (current-buffer))) (null (list (current-buffer)))
(function (funcall buffers-or-files)) (function (funcall in))
(list buffers-or-files) (list in)
(otherwise (list buffers-or-files))) (otherwise (list in)))
(--map (cl-etypecase it (--map (pcase-exhaustive it
;; NOTE: This etypecase is essential to opening links safely, ;; NOTE: This exhaustive pcase is essential to opening links safely,
;; as it rejects, e.g. lambdas in the buffers-files argument. ;; as it rejects, e.g. lambdas in the buffers-files argument.
(buffer it) ((cl-type buffer) it)
(string (or (find-buffer-visiting it) ((and (cl-type string)
(when (file-readable-p it) (pred file-readable-p))
;; It feels unintuitive that `find-file-noselect' returns (or (find-buffer-visiting it)
;; a buffer if the filename doesn't exist. (when (file-readable-p it)
(find-file-noselect it)) ;; It feels unintuitive that `find-file-noselect' returns
(display-warning 'org-ql-select (format "Can't open file: %s" it) :error))))) ;; a buffer if the filename doesn't exist.
;; Ignore special/hidden buffers. (find-file-noselect it))
(--remove (string-prefix-p " " (buffer-name it))))) (display-warning 'org-ql-select (format "Can't open file: %s" it) :error)))
((cl-type string)
;; Assumed to be an Org ID string (without the "id:" prefix).
it)))
;; Ignore special/hidden buffers.
(--remove (and (bufferp it) (string-prefix-p " " (buffer-name it))))))
(query (org-ql--normalize-query query)) (query (org-ql--normalize-query query))
((&plist :query :preamble :preamble-case-fold) (org-ql--query-preamble query)) ((&plist :query :preamble :preamble-case-fold) (org-ql--query-preamble query))
(predicate (org-ql--query-predicate query)) (predicate (org-ql--query-predicate query))
@ -425,12 +431,19 @@ each priority the newest items would appear first."
;; Temporarily set new function definition. ;; Temporarily set new function definition.
(fset name fn))) (fset name fn)))
;; Run query on buffers. ;; Run query on buffers.
(->> buffers (->> in
(--map (with-current-buffer it (--map (let* ((marker)
(unless (derived-mode-p 'org-mode) (buffer (cl-etypecase it
(display-warning 'org-ql-select (format "Not an Org buffer: %s" (buffer-name)) :error)) (buffer it)
(org-ql--select-cached :query query :preamble preamble :preamble-case-fold preamble-case-fold (string (marker-buffer
:predicate predicate :action action :narrow narrow))) (setf marker (org-id-find it 'as-marker)))))))
(with-current-buffer buffer
(unless (derived-mode-p 'org-mode)
(display-warning 'org-ql-select (format "Not an Org buffer: %s" (buffer-name)) :error))
(org-ql--select-cached :query query :preamble preamble :preamble-case-fold preamble-case-fold
:predicate predicate :action action
;; FIXME: Is it okay to use a marker here, or do we need to use the ID and get a new position each time?
:narrow (or marker narrow)))))
(-flatten-n 1))) (-flatten-n 1)))
(--each orig-fns (--each orig-fns
;; Restore original function mappings. ;; Restore original function mappings.
@ -466,8 +479,7 @@ are returned by this function. It may be:
- A function symbol. - A function symbol.
FROM corresponds to the `org-ql-select' argument BUFFERS-OR-FILES. FROM corresponds to the `org-ql-select' argument IN, which see.
It may be one or a list of file paths and/or buffers.
WHERE corresponds to the `org-ql-select' argument QUERY. It WHERE corresponds to the `org-ql-select' argument QUERY. It
should be an `org-ql' query sexp. should be an `org-ql' query sexp.
@ -491,10 +503,12 @@ NARROW corresponds to the `org-ql-select' argument NARROW."
;; The key must include the preamble, because some queries are replaced by ;; The key must include the preamble, because some queries are replaced by
;; the preamble, leaving a nil query, which would make the key ambiguous. ;; the preamble, leaving a nil query, which would make the key ambiguous.
(list :query query :preamble preamble :action action :preamble-case-fold preamble-case-fold (list :query query :preamble preamble :action action :preamble-case-fold preamble-case-fold
(if narrow :narrow (pcase-exhaustive narrow
;; Use bounds of narrowed portion of buffer. ((cl-type string) narrow)
(cons (point-min) (point-max)) ((cl-type marker) narrow)
nil)))) (`t ;; Use bounds of narrowed portion of buffer.
(cons (point-min) (point-max)))
(`nil nil)))))
(if-let* ((buffer-cache (gethash (current-buffer) org-ql-cache)) (if-let* ((buffer-cache (gethash (current-buffer) org-ql-cache))
(query-cache (cadr buffer-cache)) (query-cache (cadr buffer-cache))
(modified-tick (car buffer-cache)) (modified-tick (car buffer-cache))
@ -529,32 +543,44 @@ PREAMBLE-CASE-FOLD."
;; can't be used, so we do it manually (this is same as the equivalent `flet' expansion). ;; can't be used, so we do it manually (this is same as the equivalent `flet' expansion).
;; Mappings are stored in the variable because it allows predicates to be defined with a ;; Mappings are stored in the variable because it allows predicates to be defined with a
;; macro, which allows documentation to be easily generated for them. ;; macro, which allows documentation to be easily generated for them.
(save-excursion (let (old-restriction)
(save-restriction (save-excursion
(unless narrow (save-restriction
(widen)) (pcase narrow
(goto-char (point-min)) ((cl-type marker)
(when (org-before-first-heading-p) (switch-to-buffer (marker-buffer narrow)) ;; Can change buffer!
(outline-next-heading)) (setf old-restriction (if (buffer-narrowed-p)
(if (not (org-at-heading-p)) (cons (point-min) (point-max))
(progn t))
;; No headings in buffer: return nil. (goto-char narrow)
(unless (string-prefix-p " " (buffer-name)) (org-narrow-to-subtree))
;; Not a special, hidden buffer: show message, because if a user accidentally (`nil (widen)))
;; searches a buffer without headings, he might be confused. (goto-char (point-min))
(message "org-ql: No headings in buffer: %s" (current-buffer))) (when (org-before-first-heading-p)
nil) (outline-next-heading))
;; Find matching entries. (if (not (org-at-heading-p))
;; TODO: Bind `case-fold-search' around the preamble loop. (progn
(cond (preamble (cl-loop while (let ((case-fold-search preamble-case-fold)) ;; No headings in buffer: return nil.
(re-search-forward preamble nil t)) (unless (string-prefix-p " " (buffer-name))
do (outline-back-to-heading 'invisible-ok) ;; Not a special, hidden buffer: show message, because if a user accidentally
when (funcall predicate) ;; searches a buffer without headings, he might be confused.
collect (funcall action) (message "org-ql: No headings in buffer: %s" (current-buffer)))
do (outline-next-heading))) nil)
(t (cl-loop when (funcall predicate) ;; Find matching entries.
collect (funcall action) ;; TODO: Bind `case-fold-search' around the preamble loop.
while (outline-next-heading)))))))) (unwind-protect
(cond (preamble (cl-loop while (let ((case-fold-search preamble-case-fold))
(re-search-forward preamble nil t))
do (outline-back-to-heading 'invisible-ok)
when (funcall predicate)
collect (funcall action)
do (outline-next-heading)))
(t (cl-loop when (funcall predicate)
collect (funcall action)
while (outline-next-heading))))
(pcase old-restriction
(`t (widen))
(`(,start . ,end) (narrow-to-region start end)))))))))
;;;;; Helpers ;;;;; Helpers