Compare commits
8 commits
master
...
wip/search
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed73a15c69 | ||
|
|
2c2d2a1aee | ||
|
|
c6348e622c | ||
|
|
71c8079e22 | ||
|
|
1fce6cc364 | ||
|
|
23e44cb6f1 | ||
|
|
9237f0d93b | ||
|
|
e57d50b81c |
3 changed files with 150 additions and 95 deletions
|
|
@ -147,19 +147,20 @@ Runs `org-occur-hook' after making the sparse tree."
|
||||||
num-results)))
|
num-results)))
|
||||||
|
|
||||||
;;;###autoload
|
;;;###autoload
|
||||||
(cl-defun org-ql-search (buffers-files query &key narrow super-groups sort title
|
(cl-defun org-ql-search (in query &key narrow super-groups sort title
|
||||||
(buffer org-ql-view-buffer))
|
(buffer org-ql-view-buffer))
|
||||||
"Search for QUERY with `org-ql'.
|
"Search for QUERY with `org-ql'.
|
||||||
Interactively, prompt for these variables:
|
Interactively, prompt for these variables:
|
||||||
|
|
||||||
BUFFERS-FILES: A list of buffers and/or files to search.
|
IN: Passed to `org-ql-select', which see. Interactively, may
|
||||||
Interactively, may also be:
|
also be:
|
||||||
|
|
||||||
- `buffer': search the current buffer
|
- `buffer': search the current buffer
|
||||||
- `all': search all Org buffers
|
- `all': search all Org buffers
|
||||||
- `agenda': search buffers returned by the function `org-agenda-files'
|
- `agenda': search buffers returned by the function
|
||||||
|
`org-agenda-files'
|
||||||
- `directory': search Org files in `org-directory'
|
- `directory': search Org files in `org-directory'
|
||||||
- A space-separated list of file or buffer names
|
- A space-separated list of file/buffer names or Org heading IDs
|
||||||
|
|
||||||
QUERY: An `org-ql' query in either sexp or non-sexp form (see
|
QUERY: An `org-ql' query in either sexp or non-sexp form (see
|
||||||
Info node `(org-ql)Queries').
|
Info node `(org-ql)Queries').
|
||||||
|
|
@ -168,8 +169,9 @@ SUPER-GROUPS: An `org-super-agenda' group set. See variable
|
||||||
`org-super-agenda-groups' and Info node `(org-super-agenda)Group
|
`org-super-agenda-groups' and Info node `(org-super-agenda)Group
|
||||||
selectors'.
|
selectors'.
|
||||||
|
|
||||||
NARROW: When non-nil, don't widen buffers before
|
NARROW: Passed to `org-ql-select', but set automatically when
|
||||||
searching. Interactively, with prefix, leave narrowed.
|
called interactively based on the value of BUFFERS-FILES; the
|
||||||
|
user should not need to pass this argument.
|
||||||
|
|
||||||
SORT: One or a list of `org-ql' sorting functions, like `date' or
|
SORT: One or a list of `org-ql' sorting functions, like `date' or
|
||||||
`priority' (see Info node `(org-ql)Listing / acting-on results').
|
`priority' (see Info node `(org-ql)Listing / acting-on results').
|
||||||
|
|
@ -181,12 +183,19 @@ display the results. By default, the value of
|
||||||
`org-ql-view-buffer' is used, and a new buffer is created if
|
`org-ql-view-buffer' is used, and a new buffer is created if
|
||||||
necessary."
|
necessary."
|
||||||
(declare (indent defun))
|
(declare (indent defun))
|
||||||
(interactive (list (org-ql-view--complete-buffers-files)
|
(interactive
|
||||||
(read-string "Query: " (when org-ql-view-query
|
(let* ((in (org-ql-view--complete-buffers-files))
|
||||||
(format "%S" org-ql-view-query)))
|
(query (read-string "Query: " (when org-ql-view-query
|
||||||
:narrow (or org-ql-view-narrow (eq current-prefix-arg '(4)))
|
(format "%S" org-ql-view-query))))
|
||||||
|
(narrow (if (equal in (current-buffer))
|
||||||
|
(or org-ql-view-narrow
|
||||||
|
(when (buffer-narrowed-p)
|
||||||
|
(setf in (or (org-id-get (point-min))
|
||||||
|
(copy-marker (point-min))))))
|
||||||
|
org-ql-view-narrow)))
|
||||||
|
(list in query :narrow narrow
|
||||||
:super-groups (org-ql-view--complete-super-groups)
|
:super-groups (org-ql-view--complete-super-groups)
|
||||||
:sort (org-ql-view--complete-sort)))
|
:sort (org-ql-view--complete-sort))))
|
||||||
;; NOTE: Using `with-temp-buffer' is a hack to work around the fact that `make-local-variable'
|
;; NOTE: Using `with-temp-buffer' is a hack to work around the fact that `make-local-variable'
|
||||||
;; does not work reliably from inside a `let' form when the target buffer is current on entry
|
;; does not work reliably from inside a `let' form when the target buffer is current on entry
|
||||||
;; to or exit from the `let', even though `make-local-variable' is actually done in
|
;; to or exit from the `let', even though `make-local-variable' is actually done in
|
||||||
|
|
@ -200,16 +209,16 @@ necessary."
|
||||||
;; Parse non-sexp query into sexp query.
|
;; Parse non-sexp query into sexp query.
|
||||||
(org-ql--query-string-to-sexp query)))
|
(org-ql--query-string-to-sexp query)))
|
||||||
(list query)))
|
(list query)))
|
||||||
(results (org-ql-select buffers-files query
|
(results (org-ql-select in query
|
||||||
:action 'element-with-markers
|
:action 'element-with-markers
|
||||||
:narrow narrow
|
:narrow narrow
|
||||||
:sort sort))
|
:sort sort))
|
||||||
(strings (-map #'org-ql-view--format-element results))
|
(strings (-map #'org-ql-view--format-element results))
|
||||||
(buffer (or buffer (format "%s %s*" org-ql-view-buffer-name-prefix (or title query))))
|
(buffer (or buffer (format "%s %s*" org-ql-view-buffer-name-prefix (or title query))))
|
||||||
(header (org-ql-view--header-line-format
|
(header (org-ql-view--header-line-format
|
||||||
:buffers-files buffers-files :query query :title title))
|
:buffers-files in :query query :title title))
|
||||||
;; Bind variables for `org-ql-view--display' to set.
|
;; Bind variables for `org-ql-view--display' to set.
|
||||||
(org-ql-view-buffers-files buffers-files)
|
(org-ql-view-buffers-files in)
|
||||||
(org-ql-view-query query)
|
(org-ql-view-query query)
|
||||||
(org-ql-view-sort sort)
|
(org-ql-view-sort sort)
|
||||||
(org-ql-view-narrow narrow)
|
(org-ql-view-narrow narrow)
|
||||||
|
|
|
||||||
|
|
@ -1044,38 +1044,60 @@ property."
|
||||||
|
|
||||||
(declare-function org-ql-search-directories-files "org-ql-search" t)
|
(declare-function org-ql-search-directories-files "org-ql-search" t)
|
||||||
|
|
||||||
(defun org-ql-view--contract-buffers-files (buffers-files)
|
(defun org-ql-view--contract-buffers-files (in)
|
||||||
"Return BUFFERS-FILES in its \"contracted\" form.
|
"Return IN in its \"contracted\" form.
|
||||||
The contracted form is \"org-agenda-files\" if BUFFERS-FILES
|
The contracted form is \"org-agenda-files\" if IN matches the
|
||||||
matches the value of `org-agenda-files' (either the function or
|
value of `org-agenda-files' (either the function or the
|
||||||
the variable), \"org-directory\" if it matches the value of
|
variable), \"org-directory\" if it matches the value of
|
||||||
`org-ql-search-directories-files', or \"buffer\" if it is the
|
`org-ql-search-directories-files', or \"buffer\" if it is the
|
||||||
current buffer. Otherwise BUFFERS-FILES is returned unchanged."
|
current buffer. Otherwise IN is returned unchanged."
|
||||||
;; Used in `org-ql-view--complete-buffers-files' and
|
;; Used in `org-ql-view--complete-buffers-files' and
|
||||||
;; `org-ql-view--header-line-format'.
|
;; `org-ql-view--header-line-format'.
|
||||||
(cl-labels ((expand-files (list)
|
(cl-labels ((expand-elt (it)
|
||||||
(--map (cl-typecase it
|
(cl-typecase it
|
||||||
(string (expand-file-name it))
|
(string (or (expanded it)
|
||||||
(otherwise it))
|
(location-of it)
|
||||||
list)))
|
(error "Unknown expansion for %S" it)))
|
||||||
|
(marker (or (location-of it)
|
||||||
|
(error "Unknown location for %S" it)))
|
||||||
|
(otherwise it)))
|
||||||
|
(expanded (filename)
|
||||||
|
(when-let ((expanded (expand-file-name filename))
|
||||||
|
((file-readable-p expanded)))
|
||||||
|
expanded))
|
||||||
|
(location-of (place)
|
||||||
|
(when-let ((marker (cl-etypecase place
|
||||||
|
(marker place)
|
||||||
|
(string (org-id-find place 'marker))))
|
||||||
|
(heading (org-link-display-format (org-entry-get marker "ITEM"))))
|
||||||
|
(setf heading (if (string-empty-p heading)
|
||||||
|
place
|
||||||
|
(or (org-id-get marker)
|
||||||
|
(string-join (org-with-point-at marker
|
||||||
|
(org-get-outline-path t))
|
||||||
|
" › ")))
|
||||||
|
;; FIXME: The help-echo gets overridden elsewhere.
|
||||||
|
heading (propertize heading 'help-echo (format "%s" place)))
|
||||||
|
(format "\"%s\" in %S"
|
||||||
|
heading (abbreviate-file-name (buffer-file-name (marker-buffer marker)))))))
|
||||||
;; TODO: Test this more exhaustively.
|
;; TODO: Test this more exhaustively.
|
||||||
(pcase buffers-files
|
(pcase in
|
||||||
((pred listp)
|
((pred listp)
|
||||||
(pcase (expand-files buffers-files)
|
(pcase (mapcar #'expand-elt in)
|
||||||
((pred (seq-set-equal-p (mapcar #'expand-file-name (org-agenda-files))))
|
((pred (seq-set-equal-p (mapcar #'expand-file-name (org-agenda-files))))
|
||||||
"org-agenda-files")
|
"org-agenda-files")
|
||||||
((and (guard (file-exists-p org-directory))
|
((and (guard (file-exists-p org-directory))
|
||||||
(pred (seq-set-equal-p (org-ql-search-directories-files
|
(pred (seq-set-equal-p (org-ql-search-directories-files
|
||||||
:directories (list org-directory)))))
|
:directories (list org-directory)))))
|
||||||
"org-directory")
|
"org-directory")
|
||||||
(_ buffers-files)))
|
(_ in)))
|
||||||
((pred (equal (current-buffer)))
|
((pred (equal (current-buffer)))
|
||||||
"buffer")
|
(buffer-name (current-buffer)))
|
||||||
((or 'org-agenda-files '(function org-agenda-files))
|
((or 'org-agenda-files '(function org-agenda-files))
|
||||||
"org-agenda-files")
|
"org-agenda-files")
|
||||||
((and (pred bufferp) (guard (buffer-file-name buffers-files)))
|
((and (pred bufferp) (guard (buffer-file-name in)))
|
||||||
(buffer-file-name buffers-files))
|
(buffer-file-name in))
|
||||||
(_ buffers-files))))
|
(_ (expand-elt in)))))
|
||||||
|
|
||||||
(defun org-ql-view--complete-buffers-files ()
|
(defun org-ql-view--complete-buffers-files ()
|
||||||
"Return value for `org-ql-view-buffers-files' using completion."
|
"Return value for `org-ql-view-buffers-files' using completion."
|
||||||
|
|
|
||||||
92
org-ql.el
92
org-ql.el
|
|
@ -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,27 @@ 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* ((sources (->> (cl-typecase in
|
||||||
(null (list (current-buffer)))
|
(null (list (current-buffer)))
|
||||||
(function (funcall buffers-or-files))
|
(list in)
|
||||||
(list buffers-or-files)
|
(otherwise (list in)))
|
||||||
(otherwise (list buffers-or-files)))
|
(--map (pcase-exhaustive it
|
||||||
(--map (cl-etypecase it
|
;; NOTE: This exhaustive pcase is essential to opening links
|
||||||
;; NOTE: This etypecase is essential to opening links safely,
|
;; safely, as it rejects, e.g. lambdas in the IN argument.
|
||||||
;; as it rejects, e.g. lambdas in the buffers-files argument.
|
((cl-type buffer) it)
|
||||||
(buffer it)
|
((cl-type marker) it)
|
||||||
(string (or (find-buffer-visiting it)
|
((and (cl-type string)
|
||||||
|
(pred file-readable-p))
|
||||||
|
(or (find-buffer-visiting it)
|
||||||
(when (file-readable-p it)
|
(when (file-readable-p it)
|
||||||
;; It feels unintuitive that `find-file-noselect' returns
|
|
||||||
;; a buffer if the filename doesn't exist.
|
|
||||||
(find-file-noselect it))
|
(find-file-noselect it))
|
||||||
(display-warning 'org-ql-select (format "Can't open file: %s" it) :error)))))
|
(display-warning 'org-ql-select (format "Can't open file: %s" it) :error)))
|
||||||
|
((cl-type string)
|
||||||
|
(org-id-find it 'marker))
|
||||||
|
((cl-type function)
|
||||||
|
(funcall it))))
|
||||||
;; Ignore special/hidden buffers.
|
;; Ignore special/hidden buffers.
|
||||||
(--remove (string-prefix-p " " (buffer-name it)))))
|
(--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))
|
||||||
|
|
@ -414,6 +419,18 @@ each priority the newest items would appear first."
|
||||||
,action)))
|
,action)))
|
||||||
(_ (user-error "Invalid action form: %s" action))))
|
(_ (user-error "Invalid action form: %s" action))))
|
||||||
(org-ql--today (ts-now))
|
(org-ql--today (ts-now))
|
||||||
|
(select-in (lambda (it)
|
||||||
|
(let* ((marker)
|
||||||
|
(buffer (cl-etypecase it
|
||||||
|
(buffer it)
|
||||||
|
(marker (marker-buffer (setf marker it))))))
|
||||||
|
(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
|
||||||
|
:narrow (or marker narrow))))))
|
||||||
(items (let (orig-fns)
|
(items (let (orig-fns)
|
||||||
(unwind-protect
|
(unwind-protect
|
||||||
(progn
|
(progn
|
||||||
|
|
@ -424,14 +441,8 @@ each priority the newest items would appear first."
|
||||||
(push (list :name name :fn (symbol-function name)) orig-fns)
|
(push (list :name name :fn (symbol-function name)) orig-fns)
|
||||||
;; Temporarily set new function definition.
|
;; Temporarily set new function definition.
|
||||||
(fset name fn)))
|
(fset name fn)))
|
||||||
;; Run query on buffers.
|
;; Collect results.
|
||||||
(->> buffers
|
(-flatten-n 1 (mapcar select-in sources)))
|
||||||
(--map (with-current-buffer it
|
|
||||||
(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 :narrow narrow)))
|
|
||||||
(-flatten-n 1)))
|
|
||||||
(--each orig-fns
|
(--each orig-fns
|
||||||
;; Restore original function mappings.
|
;; Restore original function mappings.
|
||||||
(-let (((&plist :name :fn) it))
|
(-let (((&plist :name :fn) it))
|
||||||
|
|
@ -466,8 +477,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 +501,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,10 +541,18 @@ 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.
|
||||||
|
(let (old-restriction)
|
||||||
(save-excursion
|
(save-excursion
|
||||||
(save-restriction
|
(save-restriction
|
||||||
(unless narrow
|
(pcase narrow
|
||||||
(widen))
|
((cl-type marker)
|
||||||
|
(switch-to-buffer (marker-buffer narrow)) ;; Can change buffer!
|
||||||
|
(setf old-restriction (if (buffer-narrowed-p)
|
||||||
|
(cons (point-min) (point-max))
|
||||||
|
t))
|
||||||
|
(goto-char narrow)
|
||||||
|
(org-narrow-to-subtree))
|
||||||
|
(`nil (widen)))
|
||||||
(goto-char (point-min))
|
(goto-char (point-min))
|
||||||
(when (org-before-first-heading-p)
|
(when (org-before-first-heading-p)
|
||||||
(outline-next-heading))
|
(outline-next-heading))
|
||||||
|
|
@ -546,6 +566,7 @@ PREAMBLE-CASE-FOLD."
|
||||||
nil)
|
nil)
|
||||||
;; Find matching entries.
|
;; Find matching entries.
|
||||||
;; TODO: Bind `case-fold-search' around the preamble loop.
|
;; TODO: Bind `case-fold-search' around the preamble loop.
|
||||||
|
(unwind-protect
|
||||||
(cond (preamble (cl-loop while (let ((case-fold-search preamble-case-fold))
|
(cond (preamble (cl-loop while (let ((case-fold-search preamble-case-fold))
|
||||||
(re-search-forward preamble nil t))
|
(re-search-forward preamble nil t))
|
||||||
do (outline-back-to-heading 'invisible-ok)
|
do (outline-back-to-heading 'invisible-ok)
|
||||||
|
|
@ -554,7 +575,10 @@ PREAMBLE-CASE-FOLD."
|
||||||
do (outline-next-heading)))
|
do (outline-next-heading)))
|
||||||
(t (cl-loop when (funcall predicate)
|
(t (cl-loop when (funcall predicate)
|
||||||
collect (funcall action)
|
collect (funcall action)
|
||||||
while (outline-next-heading))))))))
|
while (outline-next-heading))))
|
||||||
|
(pcase old-restriction
|
||||||
|
(`t (widen))
|
||||||
|
(`(,start . ,end) (narrow-to-region start end)))))))))
|
||||||
|
|
||||||
;;;;; Helpers
|
;;;;; Helpers
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue