Skip to content

Commit 4f5fbc4

Browse files
committed
Add: (org-ql-find)
1 parent 8933f88 commit 4f5fbc4

File tree

4 files changed

+496
-265
lines changed

4 files changed

+496
-265
lines changed

README.org

+17-3
Original file line numberDiff line numberDiff line change
@@ -94,15 +94,28 @@ Lisp code examples are in [[examples.org]].
9494
:TOC: ignore-children
9595
:END:
9696

97+
+ *Jumping to an entry:*
98+
- [[#org-ql-find][org-ql-find]] (command)
99+
- [[#helm-org-ql][helm-org-ql]] (command)
97100
+ *Showing an agenda-like view:*
98101
- [[#org-ql-search][org-ql-search]] (command)
99102
- [[#org-ql-view][org-ql-view]] (command)
100103
- [[#org-ql-view-sidebar][org-ql-view-sidebar]] (command)
101104
- [[#org-ql-view-recent-items][org-ql-view-recent-items]] (command)
102105
+ *Showing a tree in a buffer:*
103106
- [[#org-ql-sparse-tree][org-ql-sparse-tree]] (command)
104-
+ *Showing results with Helm*:
105-
- [[#helm-org-ql][helm-org-ql]] (command)
107+
108+
*** org-ql-find
109+
110+
/Note: These commands use [[#non-sexp-query-syntax][non-sexp queries]]./
111+
112+
These commands jump to a heading selected using Emacs's built-in completion facilities with an Org QL query:
113+
114+
- ~org-ql-find~ searches all entry content.
115+
- ~org-ql-find-path~ searches only headings (using the ~outline-path:~ predicate).
116+
- ~org-ql-find-heading~ searches only headings (using the ~heading:~ predicate).
117+
118+
[[images/org-ql-find.png]]
106119

107120
*** org-ql-search
108121

@@ -524,7 +537,8 @@ Simple links may also be written manually in either sexp or non-sexp form, like:
524537

525538
** 0.7-pre
526539

527-
Nothing new yet.
540+
*Added*
541+
+ Commands ~org-ql-find~, ~org-ql-find-heading~, and ~org-ql-find-path~, which jump to entries selected using Emacs's built-in completion facilities and Org QL queries (like ~helm-org-ql~, but doesn't require Helm.).
528542

529543
** 0.6.2
530544

images/org-ql-find.png

85 KB
Loading

org-ql-find.el

+178
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
;;; org-ql-find.el --- Find headings with completion using org-ql -*- lexical-binding: t; -*-
2+
3+
;; Copyright (C) 2022 Adam Porter
4+
5+
;; Author: Adam Porter <[email protected]>
6+
7+
;; This program is free software; you can redistribute it and/or modify
8+
;; it under the terms of the GNU General Public License as published by
9+
;; the Free Software Foundation, either version 3 of the License, or
10+
;; (at your option) any later version.
11+
12+
;; This program is distributed in the hope that it will be useful,
13+
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
;; GNU General Public License for more details.
16+
17+
;; You should have received a copy of the GNU General Public License
18+
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
19+
20+
;;; Commentary:
21+
22+
;; This library provides a way to quickly find and go to Org entries
23+
;; selected with Emacs's built-in completions API (so it works with
24+
;; packages that extend it, like Vertico, Marginalia, etc). It works
25+
;; like `helm-org-ql' but does not require Helm.
26+
27+
;;; Code:
28+
29+
(require 'cl-lib)
30+
31+
(require 'org)
32+
(require 'org-ql)
33+
34+
;;;; Customization
35+
36+
(defgroup org-ql-find nil
37+
"Options for `org-ql-find'."
38+
:group 'org-ql)
39+
40+
(defcustom org-ql-find-reverse-paths t
41+
"Whether to reverse Org outline paths in `org-ql-find' results."
42+
:type 'boolean)
43+
44+
(defcustom org-ql-find-goto-hook '(org-show-entry)
45+
"Functions called when selecting an entry."
46+
:type 'hook)
47+
48+
(defface org-ql-find-snippet '((t (:inherit font-lock-comment-face)))
49+
"Snippets.")
50+
51+
;;;; Functions
52+
53+
;;;###autoload
54+
(cl-defun org-ql-find (buffers-files &key query-prefix
55+
(prompt "Find entry: "))
56+
"Go to an Org entry in BUFFERS-FILES selected by searching entries with `org-ql'.
57+
Interactively, with universal prefix, select multiple buffers to
58+
search with completion.
59+
60+
If QUERY-PREFIX, prepend it to the query (e.g. use \"heading:\"
61+
to only search headings, easily creating a custom command that
62+
saves the user from having to type it)."
63+
(interactive
64+
(list (if current-prefix-arg
65+
(mapcar #'get-buffer
66+
(completing-read-multiple
67+
"Buffers: "
68+
(mapcar #'buffer-name
69+
(cl-remove-if-not (lambda (buffer)
70+
(eq 'org-mode (buffer-local-value 'major-mode buffer)))
71+
(buffer-list))) nil t))
72+
(current-buffer))))
73+
;; Emacs's completion API is not always easy to understand,
74+
;; especially when using "programmed completion." This code was
75+
;; made possible by the example Clemens Radermacher shared at
76+
;; <https://github.com/radian-software/selectrum/issues/114#issuecomment-744041532>.
77+
(let ((table (make-hash-table :test #'equal))
78+
(window-width (window-width)))
79+
(cl-labels ((action
80+
() (font-lock-ensure (point-at-bol) (point-at-eol))
81+
(let* ((path (thread-first (org-get-outline-path t t)
82+
(org-format-outline-path window-width nil "")
83+
(org-split-string "")))
84+
(path (if org-ql-find-reverse-paths
85+
(string-join (nreverse path) "\\")
86+
(string-join path "/"))))
87+
(puthash path (point-marker) table)
88+
path))
89+
(affix (completions)
90+
(cl-loop for completion in completions
91+
for marker = (gethash completion table)
92+
for todo-state = (if-let (it (org-entry-get marker "TODO"))
93+
(concat (propertize it
94+
'face (org-get-todo-face it))
95+
" ")
96+
"")
97+
for snippet = (if-let (it (snippet marker))
98+
(propertize (concat " " it)
99+
'face 'org-ql-find-snippet)
100+
"")
101+
collect (list completion todo-state snippet)))
102+
(annotate (candidate)
103+
(or (snippet (gethash candidate table)) ""))
104+
(snippet (marker)
105+
(org-with-point-at marker
106+
(org-end-of-meta-data t)
107+
(unless (org-at-heading-p)
108+
(let ((end (min (+ (point) 51)
109+
(org-entry-end-position))))
110+
(truncate-string-to-width
111+
(replace-regexp-in-string "\n" " " (buffer-substring (point) end)
112+
t t)
113+
50 nil nil t)))))
114+
(group (candidate transform)
115+
(pcase transform
116+
(`nil (buffer-name (marker-buffer (gethash candidate table))))
117+
(_ candidate)))
118+
(try (string _table _pred point &optional _metadata)
119+
(cons string point))
120+
(all (string table pred _point)
121+
(all-completions string table pred))
122+
(collection (str _pred flag)
123+
(pcase flag
124+
('metadata (list 'metadata
125+
(cons 'group-function #'group)
126+
(cons 'affixation-function #'affix)
127+
(cons 'annotation-function #'annotate)))
128+
(`t (unless (string-empty-p str)
129+
(org-ql-select buffers-files (org-ql--query-string-to-sexp (concat query-prefix str))
130+
:action #'action))))))
131+
(let* ((completion-styles '(org-ql-find))
132+
(completion-styles-alist (list (list 'org-ql-find #'try #'all "Org QL Find")))
133+
(selected (completing-read prompt #'collection nil))
134+
(marker (gethash selected table)))
135+
(with-current-buffer (marker-buffer marker)
136+
(goto-char marker)
137+
(display-buffer (current-buffer))
138+
(run-hook-with-args 'org-ql-find-goto-hook))))))
139+
140+
;;;###autoload
141+
(defun org-ql-find-heading (buffers-files)
142+
"Go to an Org entry in BUFFERS-FILES selected by searching with `org-ql'.
143+
Only headings are searched (using the \"heading:\" predicate).
144+
Interactively, with universal prefix, select multiple buffers to
145+
search with completion."
146+
(interactive
147+
(list (if current-prefix-arg
148+
(mapcar #'get-buffer
149+
(completing-read-multiple
150+
"Buffers: "
151+
(mapcar #'buffer-name
152+
(cl-remove-if-not (lambda (buffer)
153+
(eq 'org-mode (buffer-local-value 'major-mode buffer)))
154+
(buffer-list))) nil t))
155+
(current-buffer))))
156+
(org-ql-find buffers-files :prompt "Find heading: " :query-prefix "heading:"))
157+
158+
;;;###autoload
159+
(defun org-ql-find-path (buffers-files)
160+
"Go to an Org entry in BUFFERS-FILES selected by searching with `org-ql'.
161+
Only outline paths are searched (using the \"outline-path:\"
162+
predicate). Interactively, with universal prefix, select
163+
multiple buffers to search with completion."
164+
(interactive
165+
(list (if current-prefix-arg
166+
(mapcar #'get-buffer
167+
(completing-read-multiple
168+
"Buffers: "
169+
(mapcar #'buffer-name
170+
(cl-remove-if-not (lambda (buffer)
171+
(eq 'org-mode (buffer-local-value 'major-mode buffer)))
172+
(buffer-list))) nil t))
173+
(current-buffer))))
174+
(org-ql-find buffers-files :prompt "Find outline path: " :query-prefix "outline-path:"))
175+
176+
(provide 'org-ql-find)
177+
178+
;;; org-ql-find.el ends here

0 commit comments

Comments
 (0)