-
-
Notifications
You must be signed in to change notification settings - Fork 109
/
consult-org.el
144 lines (124 loc) · 5.67 KB
/
consult-org.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
;;; consult-org.el --- Consult commands for org-mode -*- lexical-binding: t -*-
;; Copyright (C) 2021-2024 Free Software Foundation, Inc.
;; This file is part of GNU Emacs.
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;; Provides a `completing-read' interface for Org mode navigation.
;; This is an extra package, to allow lazy loading of Org.
;;; Code:
(require 'consult)
(require 'org)
(defvar consult-org--history nil)
(defun consult-org--narrow ()
"Narrowing configuration for `consult-org' commands."
(let ((todo-kws
(seq-filter
(lambda (x) (<= ?a (car x) ?z))
(mapcar (lambda (s)
(pcase-let ((`(,a ,b) (split-string s "(")))
(cons (downcase (string-to-char (or b a))) a)))
(apply #'append (mapcar #'cdr org-todo-keywords))))))
(list :predicate
(lambda (cand)
(pcase-let ((`(,level ,todo ,prio . ,_)
(get-text-property 0 'consult-org--heading cand)))
(cond
((<= ?1 consult--narrow ?9) (<= level (- consult--narrow ?0)))
((<= ?A consult--narrow ?Z) (eq prio consult--narrow))
(t (equal todo (alist-get consult--narrow todo-kws))))))
:keys
(nconc (mapcar (lambda (c) (cons c (format "Level %c" c)))
(number-sequence ?1 ?9))
(mapcar (lambda (c) (cons c (format "Priority %c" c)))
(number-sequence (max ?A org-highest-priority)
(min ?Z org-lowest-priority)))
todo-kws))))
(defun consult-org--headings (prefix match scope &rest skip)
"Return a list of Org heading candidates.
If PREFIX is non-nil, prefix the candidates with the buffer name.
MATCH, SCOPE and SKIP are as in `org-map-entries'."
(let (buffer (idx 0))
(apply
#'org-map-entries
(lambda ()
;; Reset the cache when the buffer changes, since `org-get-outline-path' uses the cache
(unless (eq buffer (buffer-name))
(setq buffer (buffer-name)
org-outline-path-cache nil))
(pcase-let* ((`(_ ,level ,todo ,prio ,_hl ,tags) (org-heading-components))
(tags (if org-use-tag-inheritance
(when-let ((tags (org-get-tags)))
(concat ":" (string-join tags ":") ":"))
tags))
(cand (org-format-outline-path
(org-get-outline-path 'with-self 'use-cache)
most-positive-fixnum)))
(when todo
(put-text-property 0 (length todo) 'face (org-get-todo-face todo) todo))
(when tags
(put-text-property 0 (length tags) 'face 'org-tag tags))
(setq cand (concat (and prefix buffer) (and prefix " ") cand (and tags " ")
tags (consult--tofu-encode idx)))
(cl-incf idx)
(add-text-properties 0 1
`(org-marker ,(point-marker)
consult-org--heading (,level ,todo ,prio . ,buffer))
cand)
cand))
match scope skip)))
(defun consult-org--annotate (cand)
"Annotate CAND for `consult-org-heading'."
(pcase-let ((`(,_level ,todo ,prio . ,_)
(get-text-property 0 'consult-org--heading cand)))
(consult--annotate-align
cand
(concat todo
(and prio (format #(" [#%c]" 1 6 (face org-priority)) prio))))))
(defun consult-org--group (cand transform)
"Return title for CAND or TRANSFORM the candidate."
(pcase-let ((`(,_level ,_todo ,_prio . ,buffer)
(get-text-property 0 'consult-org--heading cand)))
(if transform (substring cand (1+ (length buffer))) buffer)))
;;;###autoload
(defun consult-org-heading (&optional match scope)
"Jump to an Org heading.
MATCH and SCOPE are as in `org-map-entries' and determine which
entries are offered. By default, all entries of the current
buffer are offered."
(interactive (unless (derived-mode-p #'org-mode)
(user-error "Must be called from an Org buffer")))
(let ((prefix (not (memq scope '(nil tree region region-start-level file)))))
(consult--read
(consult--slow-operation "Collecting headings..."
(or (consult-org--headings prefix match scope)
(user-error "No headings")))
:prompt "Go to heading: "
:category 'org-heading
:sort nil
:require-match t
:history '(:input consult-org--history)
:narrow (consult-org--narrow)
:state (consult--jump-state)
:annotate #'consult-org--annotate
:group (and prefix #'consult-org--group)
:lookup (apply-partially #'consult--lookup-prop 'org-marker))))
;;;###autoload
(defun consult-org-agenda (&optional match)
"Jump to an Org agenda heading.
By default, all agenda entries are offered. MATCH is as in
`org-map-entries' and can used to refine this."
(interactive)
(unless org-agenda-files
(user-error "No agenda files"))
(consult-org-heading match 'agenda))
(provide 'consult-org)
;;; consult-org.el ends here