Skip to content

Commit 93ffdcb

Browse files
committed
Add: helm-org-ql
Squashed commit of the following: commit 22f0315 Author: Adam Porter <[email protected]> Date: Mon Sep 9 16:25:46 2019 -0500 WIP: Update docs and add screenshot commit d8aa8a5 Author: Adam Porter <[email protected]> Date: Mon Sep 9 16:25:18 2019 -0500 WIP: Ignore all query errors Partially typed queries might cause parsing errors. commit 782a830 Author: Adam Porter <[email protected]> Date: Mon Sep 9 16:24:57 2019 -0500 WIP: (deadline) and (priority) commit d32e3e3 Author: Adam Porter <[email protected]> Date: Mon Sep 9 14:26:54 2019 -0500 WIP: Compatibility Trying to make it install and run cleanly even when Helm isn't installed. commit 4bd9943 Author: Adam Porter <[email protected]> Date: Mon Sep 9 13:47:41 2019 -0500 Add: (helm-org-ql-org-directory) commit d9a04e1 Author: Adam Porter <[email protected]> Date: Mon Sep 2 12:23:51 2019 -0500 WIP: Add helm-org-ql.el
1 parent 1ff21dc commit 93ffdcb

File tree

4 files changed

+262
-42
lines changed

4 files changed

+262
-42
lines changed

README.org

+13-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
[[https://melpa.org/#/org-ql][file:https://melpa.org/packages/org-ql-badge.svg]] [[https://stable.melpa.org/#/org-ql][file:https://stable.melpa.org/packages/org-ql-badge.svg]]
88

9-
~org-ql~ is a lispy query language for Org files. It allows you to find Org entries matching certain criteria and return a list of them or perform actions on them. Commands are also provided which display a buffer with matching results, similar to an Org Agenda buffer.
9+
~org-ql~ is a lispy query language for Org files. It allows you to find Org entries matching certain criteria and return a list of them or perform actions on them. Commands are also provided which display matching results.
1010

1111
* Contents
1212
:PROPERTIES:
@@ -120,7 +120,7 @@ Installing with [[https://framagit.org/steckerhalter/quelpa][Quelpa]] is easy:
120120

121121
The functionality provided may be grouped by:
122122

123-
+ *Interactive commands:* ~org-ql-search~, ~org-ql-view~, =org-ql-sparse-tree=.
123+
+ *Interactive commands:* ~org-ql-search~, ~org-ql-view~, =org-ql-sparse-tree=, =helm-org-ql=.
124124
+ *Non-interactive functions and macros:*
125125
- ~org-ql~ (macro)
126126
- ~org-ql-select~ (function)
@@ -137,6 +137,8 @@ Alternatively, they may be grouped by:
137137
- ~org-ql-agenda~ (macro)
138138
+ *Showing a tree in a buffer:*
139139
- =org-ql-sparse-tree= (command)
140+
+ *Showing results with Helm*:
141+
- =helm-org-ql= (command)
140142
+ *Returning a list of matches or acting on them:*
141143
- ~org-ql~ (macro)
142144
- ~org-ql-select~ (function)
@@ -177,6 +179,14 @@ Here's an example of using it to generate an agenda-like view for certain files
177179

178180
[[images/org-ql-search-snippet.png]]
179181

182+
*** helm-org-ql
183+
184+
This command displays matches with Helm. *Note:* Helm is not a package dependency, so this command only works if the package =helm-org= is installed.
185+
186+
Note also that queries in this command are specially handled so that quotes around strings may be omitted for ease of typing.
187+
188+
[[images/helm-org-ql.gif]]
189+
180190
*** org-ql-view
181191

182192
Choose and display a view stored in ~org-ql-views~.
@@ -462,6 +472,7 @@ Expands into a call to ~org-ql-select~ with the same arguments. For convenience
462472
** 0.3-pre
463473

464474
*Added*
475+
+ Command =helm-org-ql=.
465476
+ Command =org-ql-sparse-tree=, like =org-sparse-tree= for =org-ql= queries. (Thanks to [[https://github.com/akirak][Akira Komamura]].)
466477
+ Per-buffer, per-heading tag caching, which increases the speed of tags-related queries by 6-7x.
467478
+ More tags-related predicates and aliases:

helm-org-ql.el

+249
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
;;; helm-org-ql.el --- Helm commands for org-ql -*- lexical-binding: t; -*-
2+
3+
;; Author: Adam Porter <[email protected]>
4+
;; URL: https://github.com/alphapapa/org-ql
5+
6+
;;; Commentary:
7+
8+
;; This library includes Helm commands for `org-ql'. Note that Helm
9+
;; is not declared as a package dependency, so this does not cause
10+
;; Helm to be installed. In the future, this file may have its own
11+
;; package recipe, which would allow it to be installed separately and
12+
;; declare a dependency on Helm.
13+
14+
;;; License:
15+
16+
;; This program is free software; you can redistribute it and/or modify
17+
;; it under the terms of the GNU General Public License as published by
18+
;; the Free Software Foundation, either version 3 of the License, or
19+
;; (at your option) any later version.
20+
21+
;; This program is distributed in the hope that it will be useful,
22+
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
23+
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24+
;; GNU General Public License for more details.
25+
26+
;; You should have received a copy of the GNU General Public License
27+
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
28+
29+
;;; Code:
30+
31+
;;;; Requirements
32+
33+
(require 'org)
34+
35+
(require 'org-ql)
36+
37+
;; (require 'helm)
38+
;; (require 'helm-org)
39+
40+
;;;; Compatibility
41+
42+
;; Declare Helm functions since Helm may not be installed.
43+
(declare-function helm "ext:helm")
44+
(declare-function helm-run-after-exit "ext:helm")
45+
(declare-function helm-buffer-get "ext:helm-lib")
46+
(declare-function helm-make-source "ext:helm-source")
47+
(declare-function helm-org-goto-marker "ext:helm-org")
48+
49+
;; Silence byte-compiler about variables.
50+
(defvar helm-map)
51+
(defvar helm-pattern)
52+
(defvar helm-input-idle-delay)
53+
54+
;;;; Variables
55+
56+
(defvar helm-org-ql-map
57+
(let ((map (copy-keymap helm-map))
58+
(mappings '(
59+
"C-x C-s" helm-org-ql-save
60+
)))
61+
(cl-loop for (key fn) on mappings by #'cddr
62+
do (define-key map (kbd key) fn))
63+
map)
64+
"Keymap for `helm-org-ql' sessions.
65+
Based on `helm-map'.")
66+
67+
(defvar-local helm-org-ql-buffers-files nil
68+
"Used for `helm-org-ql-save'.")
69+
70+
;;;; Customization
71+
72+
(defgroup helm-org-ql nil
73+
"Options for `helm-org-ql'."
74+
:group 'org-ql)
75+
76+
(defcustom helm-org-ql-reverse-paths t
77+
"Whether to reverse Org outline paths in `helm-org-ql' results."
78+
:type 'boolean)
79+
80+
(defcustom helm-org-ql-input-idle-delay 0.25
81+
"Seconds to wait after typing stops before running query."
82+
:type 'number)
83+
84+
(defcustom helm-org-ql-actions
85+
(list (cons "Show heading in source buffer" 'helm-org-ql-show-marker)
86+
(cons "Show heading in indirect buffer" 'helm-org-ql-show-marker-indirect))
87+
"Alist of actions for `helm-org-ql' commands."
88+
:type '(alist :key-type (string :tag "Description")
89+
:value-type (function :tag "Command")))
90+
91+
;;;; Commands
92+
93+
;;;###autoload
94+
(cl-defun helm-org-ql (buffers-files &optional (no-and current-prefix-arg))
95+
"Display results in BUFFERS-FILES for an `org-ql' query using Helm.
96+
Interactively, search the current buffer.
97+
98+
NOTE: Atoms in the query are turned into strings where
99+
appropriate, which makes it unnecessary to type quotation marks
100+
around words that are intended to be searched for as indepenent
101+
strings.
102+
103+
Also, unless NO-AND is non-nil (interactively, with prefix), all
104+
query tokens are wrapped in an implied (and) form. This is
105+
because a query must be a sexp, so when typing multiple clauses,
106+
either (and) or (or) would be required around them, and (and) is
107+
typically more useful, because it narrows down results.
108+
109+
For example, this raw input:
110+
111+
Emacs git
112+
113+
Is transformed into this query:
114+
115+
(and \"Emacs\" \"git\")
116+
117+
However, quoted strings remain quoted, so this input:
118+
119+
\"something else\" (tags \"funny\")
120+
121+
Is transformed into this query:
122+
123+
(and \"something else\" (tags \"funny\"))"
124+
(interactive (list (current-buffer)))
125+
(let ((helm-input-idle-delay helm-org-ql-input-idle-delay))
126+
(helm :prompt (format "Query (boolean %s): " (if no-and
127+
"OR"
128+
"AND"))
129+
:sources
130+
;; Expansion of `helm-build-sync-source' macro.
131+
(helm-make-source "helm-org-ql-agenda-files" 'helm-source-sync
132+
:candidates #'(lambda nil
133+
(let* ((query (helm-org-ql--input-to-query helm-pattern no-and))
134+
(window-width (window-width (helm-window))))
135+
(when query
136+
(with-current-buffer (helm-buffer-get)
137+
(setq helm-org-ql-buffers-files buffers-files))
138+
(ignore-errors
139+
;; Ignore errors that might be caused by partially typed queries.
140+
(org-ql-select buffers-files query
141+
:action (list 'helm-org-ql--heading window-width))))))
142+
:match #'identity
143+
:fuzzy-match nil
144+
:multimatch nil
145+
:volatile t
146+
:keymap helm-org-ql-map
147+
:action helm-org-ql-actions))))
148+
149+
;;;###autoload
150+
(defun helm-org-ql-agenda-files ()
151+
"Search agenda files with `helm-org-ql', which see."
152+
(interactive)
153+
(helm-org-ql (org-agenda-files)))
154+
155+
;;;###autoload
156+
(defun helm-org-ql-org-directory ()
157+
"Search Org files in `org-directory' with `helm-org-ql'."
158+
(interactive)
159+
(helm-org-ql (directory-files org-directory 'full
160+
(rx ".org" eos))))
161+
162+
(defun helm-org-ql-show-marker (marker)
163+
"Show heading at MARKER."
164+
(interactive)
165+
;; This function is necessary because `helm-org-goto-marker' calls
166+
;; `re-search-backward' to go backward to the start of a heading,
167+
;; which, when the marker is already at the desired heading, causes
168+
;; it to go to the previous heading. I don't know why it does that.
169+
(switch-to-buffer (marker-buffer marker))
170+
(goto-char marker)
171+
(org-show-entry))
172+
173+
(defun helm-org-ql-show-marker-indirect (marker)
174+
"Show heading at MARKER with `org-tree-to-indirect-buffer'."
175+
(interactive)
176+
(helm-org-ql-show-marker marker)
177+
(org-tree-to-indirect-buffer))
178+
179+
(defun helm-org-ql-save ()
180+
"Show `helm-org-ql' search in an `org-ql-search' buffer."
181+
(interactive)
182+
(let ((buffers-files (with-current-buffer (helm-buffer-get)
183+
helm-org-ql-buffers-files))
184+
(query (helm-org-ql--input-to-query helm-pattern)))
185+
(helm-run-after-exit #'org-ql-search buffers-files query)))
186+
187+
;;;; Functions
188+
189+
(defun helm-org-ql--input-to-query (input &optional no-and)
190+
"Return `org-ql' query sexp for string INPUT.
191+
Unless NO-AND is non-nil (interactively, with prefix), all query
192+
tokens are wrapped in an implied (and) form, and plain
193+
symbols (except at the beginning of a sexp) are replaced with
194+
strings."
195+
(unless (s-blank-str? input)
196+
(setf input (format "(%s %s)" (if no-and "or" "and") input))
197+
(when-let* ((query (ignore-errors
198+
;; Ignore errors in case input is an
199+
;; incomplete string or sexp.
200+
(read input))))
201+
(cl-labels ((rec (form)
202+
;; Replace some symbols with strings so users don't
203+
;; have to type quotation marks around all strings.
204+
;; Not perfect, but should be more useful.
205+
(pcase-exhaustive form
206+
((pred stringp) form)
207+
(`(deadline auto) form)
208+
((or '> '>= '< '<= '=)
209+
;; Comparators, probably for (priority).
210+
form)
211+
((guard (string-match (rx bos ":" (1+ anything) ":" eos)
212+
(prin1-to-string form)))
213+
;; An Org tag, not a Lisp keyword.
214+
(prin1-to-string form))
215+
((pred keywordp) form)
216+
((pred numberp) form)
217+
((guard (string-prefix-p "!" (prin1-to-string form)))
218+
;; Negation of a string.
219+
`(not ,(substring (prin1-to-string form) 1)))
220+
((pred atom) (prin1-to-string form))
221+
((pred listp) `(,(car form)
222+
,@(mapcar #'rec (cdr form)))))))
223+
(rec query)))))
224+
225+
(defun helm-org-ql--heading (window-width)
226+
"Return string for Helm for heading at point.
227+
WINDOW-WIDTH should be the width of the Helm window."
228+
(font-lock-ensure (point-at-bol) (point-at-eol))
229+
;; TODO: It would be better to avoid calculating the prefix and width
230+
;; at each heading, but there's no easy way to do that once in each
231+
;; buffer, unless we manually called `org-ql' in each buffer, which
232+
;; I'd prefer not to do. Maybe I should add a feature to `org-ql' to
233+
;; call a setup function in a buffer before running queries.
234+
(let* ((prefix (concat (buffer-name) ":"))
235+
(width (- window-width (length prefix)))
236+
(path (org-split-string (org-format-outline-path (org-get-outline-path)
237+
width nil "")
238+
""))
239+
(heading (org-get-heading t))
240+
(path (if helm-org-ql-reverse-paths
241+
(concat heading "\\" (s-join "\\" (nreverse path)))
242+
(concat (s-join "/" path) "/" heading))))
243+
(cons (concat prefix path) (point-marker))))
244+
245+
;;;; Footer
246+
247+
(provide 'helm-org-ql)
248+
249+
;;; helm-org-ql.el ends here

images/helm-org-ql.gif

1.02 MB
Loading

notes.org

-40
Original file line numberDiff line numberDiff line change
@@ -737,46 +737,6 @@ Also, maybe instead of having a single =date= selector, I should have =scheduled
737737
(not (done)))))
738738
#+END_SRC
739739

740-
** Helm
741-
742-
#+BEGIN_SRC elisp
743-
(defun helm-org-ql-heading ()
744-
(let* ((path (mapconcat 'identity
745-
(nreverse (org-split-string (org-format-outline-path (org-get-outline-path)
746-
1000 nil "")
747-
""))
748-
org-sticky-header-outline-path-reversed-separator))
749-
(s (concat (org-sticky-header--get-prefix)
750-
(org-get-heading)
751-
org-sticky-header-outline-path-reversed-separator
752-
path)))
753-
(remove-list-of-text-properties 0 (length s) '(line-prefix) s)
754-
(s-trim (if (> (length s) (window-width))
755-
(concat (substring s 0 (- (window-width) 2))
756-
"..")
757-
s))))
758-
759-
(defun helm-org-ql-next ()
760-
(interactive)
761-
(helm :sources (list (helm-build-sync-source "helm-org-ql"
762-
;; :after-init-hook helm-org-rifle-after-init-hook
763-
:candidates (lambda ()
764-
(or (when-let* ((items (org-ql-select (org-agenda-files)
765-
'(todo "NEXT")
766-
:action 'element-with-markers
767-
:sort '(priority date))))
768-
(--map (let* ((marker (org-element-property :org-marker it)))
769-
(org-with-point-at marker
770-
(cons (helm-org-ql-heading) marker)))
771-
items))
772-
(list "NONE")))
773-
:match 'identity
774-
:multiline nil
775-
:volatile t
776-
:action 'helm-org-rifle-actions
777-
:keymap helm-org-rifle-map))))
778-
#+END_SRC
779-
780740
* In the wild
781741

782742
** [[https://github.com/AloisJanicek/.doom.d-2nd][Alois Janicek]]

0 commit comments

Comments
 (0)