nftable-migration
1;;; org-menu.el --- A discoverable menu for org-mode using transient -*- lexical-binding: t -*-
2;;
3;; Copyright 2021 Jan Rehders
4;;
5;; Author: Jan Rehders <nospam@sheijk.net>
6;; Version: 0.1alpha
7;; Package-Requires: ((emacs "26.1") (transient "0.1"))
8;; URL: https://github.com/sheijk/org-menu
9;;
10;; This file is free software; you can redistribute it and/or modify
11;; it under the terms of the GNU General Public License as published by
12;; the Free Software Foundation; either version 2, or (at your option)
13;; any later version.
14;;
15;; This file is distributed in the hope that it will be useful,
16;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18;; GNU General Public License for more details.
19;;
20;; You should have received a copy of the GNU General Public License
21;; along with GNU Emacs; see the file COPYING. If not, write to
22;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23;; Boston, MA 02111-1307, USA.
24;;
25;;; Commentary:
26;;
27;; Usage:
28;;
29;; Add this to your ~/.emacs to bind the menu to `C-c m':
30;;
31;; (with-eval-after-load 'org
32;; (require 'org-menu) ;; not needed if installing by package manager
33;; (define-key org-mode-map (kbd "C-c m") #'org-menu))
34;;
35;; The menu should be pretty self-explanatory. It is context dependent and
36;; offers different commands for headlines, tables, timestamps, etc.
37;; The task menu provides entry points for task that work from anywhere.
38
39;;; Code:
40
41(require 'org)
42(require 'transient)
43(require 'org-capture)
44(require 'org-timer)
45(require 'cl-lib)
46
47(defgroup org-menu nil
48 "Options for `org-menu'."
49 :group 'org)
50
51(defcustom org-menu-use-q-for-quit t
52 "Whether to add a q binding to quit to all menus.
53
54Use this if you prefer to be consistent with magit. It will also
55change some other bindings to use Q instead of q."
56 :type 'boolean)
57
58(defcustom org-menu-global-toc-depth 10
59 "The number of heading levels to show when displaying the global content."
60 :type 'natnum)
61
62(defcustom org-menu-expand-snippet-function #'org-menu-expand-snippet-default
63 "The function used to expand a snippet.
64
65See `org-menu-expand-snippet-default' for a list of snippet ids
66which need to be supported. `org-menu-expand-snippet-yasnippet'
67shows how to invoke snippets."
68 :type 'function)
69
70(defun org-menu-show-columns-view-options-p ()
71 "Return whether `org-columns' mode is active."
72 (bound-and-true-p org-columns-overlays))
73
74(defun org-menu-show-heading-options-p ()
75 "Whether to show commands operating on headings."
76 (unless (org-menu-show-columns-view-options-p)
77 (org-at-heading-p)))
78
79(defun org-menu-show-table-options-p ()
80 "Whether to show commands operating on tables."
81 (unless (org-menu-show-columns-view-options-p)
82 (org-at-table-p)))
83
84(defun org-menu-show-list-options-p ()
85 "Whether to show commands operating on lists."
86 (unless (org-menu-show-columns-view-options-p)
87 (org-at-item-p)))
88
89(defun org-menu-show-text-options-p ()
90 "Whether to show commands operating on text."
91 (not (or (org-menu-show-columns-view-options-p)
92 (org-at-heading-p)
93 (org-at-table-p)
94 (org-in-item-p)
95 (org-in-src-block-p))))
96
97(defun org-menu-show-src-options-p ()
98 "Whether to show commands operating on src blocks."
99 (unless (org-menu-show-columns-view-options-p)
100 (org-in-src-block-p)))
101
102(defun org-menu-show-link-options-p ()
103 "Whether to show commands operating on links.
104
105Conditions have been adapted from `org-insert-link'"
106 (unless (org-menu-show-columns-view-options-p)
107 (or
108 ;; Use variable from org-compat to support Emacs 26
109 (org-in-regexp (symbol-value 'org-bracket-link-regexp) 1)
110 (when (boundp 'org-link-angle-re)
111 (org-in-regexp org-link-angle-re))
112 (when (boundp 'org-link-plain-re)
113 (org-in-regexp org-link-plain-re)))))
114
115(defun org-menu-show-timestamp-options-p ()
116 "Whether to show commands operating on timestamps."
117 (unless (org-menu-show-columns-view-options-p)
118 (org-at-timestamp-p 'lax)))
119
120(defun org-menu-show-footnote-options-p ()
121 "Whether to show commands operating on footnotes."
122 (unless (org-menu-show-columns-view-options-p)
123 (or (org-footnote-at-definition-p)
124 (org-footnote-at-reference-p))))
125
126(defun org-menu-heading-navigate-items (check-for-heading &optional cycle-function)
127 "Items to navigate headings.
128
129These will be added to most sub menus. If `CHECK-FOR-HEADING' is
130true the items will only be added if on a heading. `CYCLE-FUNCTION' is the
131function to be used to cycle visibility of current element."
132 (setq cycle-function (or cycle-function #'org-cycle))
133 `(["Navigate"
134 :pad-keys t
135 ,@(and check-for-heading '(:if org-menu-show-heading-options-p))
136 ("p" "prev" org-previous-visible-heading :transient t)
137 ("n" "next" org-next-visible-heading :transient t)
138 ("c" "cycle" ,cycle-function :transient t)
139 ("u" "parent" outline-up-heading :transient t)
140 ("M-p" "prev (same level)" org-backward-heading-same-level :transient t)
141 ("M-n" "next (same level)" org-forward-heading-same-level :transient t)
142 ("M-w" "store link" org-store-link :transient t :if-not region-active-p)
143 ("C-_" "undo" undo :transient t)]))
144
145(defun org-menu-expand-snippet-default (snippet-id)
146 "Insert a fixed text for each `SNIPPET-ID'."
147 (pcase snippet-id
148 ('block (insert "#+BEGIN:\n#+END:\n"))
149 ('option (insert "#+"))
150 ('subscript (insert "a_b"))
151 ('superscript (insert "a^b"))
152 ('plot
153 (insert
154 "#+plot: type:2d file:\"plot.svg\"
155| A | B |
156|---+----|
157| 1 | 10 |
158| 2 | 8 |
159| 3 | 9 |
160
161#+attr_org: :width 400px
162[[file:plot.svg]]
163"))
164 (_ (error "Unknown snippet type %s" snippet-id))))
165
166(autoload 'yas-expand-snippet "yasnippet")
167(autoload 'yas-expand-from-trigger-key "yasnippet")
168
169(defun org-menu-expand-snippet-yasnippet (snippet-id)
170 "Expand a yasnippet for each `SNIPPET-ID'."
171 (unless (require 'yasnippet nil 'noerror)
172 (error "Yasnippet not installed, could not expand %s" snippet-id))
173 (pcase snippet-id
174 ('block
175 (insert "beg")
176 (yas-expand-from-trigger-key))
177 ('option
178 (insert "opt")
179 (yas-expand-from-trigger-key))
180 ('subscript
181 (yas-expand-snippet "${1:text}_{${2:sub}}"))
182 ('superscript
183 (yas-expand-snippet "${1:text}^{${2:super}}"))
184 ('plot
185 (yas-expand-snippet
186 "#+plot: type:${1:2d} file:\"${2:plot.svg}\"
187| A | B |
188|---+----|
189| 1 | 10 |
190| 2 | 8 |
191| 3 | 9 |
192
193#+attr_org: :width ${3:400px}
194[[file:$2]]
195"))
196 (_
197 (error "Unknown snippet type %s" snippet-id))))
198
199;; If yasnippet gets loaded it will be used automatically
200(with-eval-after-load 'yasnippet
201 (unless (equal org-menu-expand-snippet-function #'org-menu-expand-snippet-default)
202 (setq org-menu-expand-snippet-function #'org-menu-expand-snippet-yasnippet)))
203
204(defun org-menu-expand-snippet (snippet-id)
205 "Will expand the given snippet named `SNIPPET-ID' with `ARGS'."
206 (funcall org-menu-expand-snippet-function snippet-id))
207
208(defun org-menu-show-headline-content ()
209 "Will show the complete content of the current headline and it's children."
210 (interactive)
211 (save-excursion
212 (outline-hide-subtree)
213 (with-no-warnings
214 (org-show-children 4))
215 (org-goto-first-child)
216 (org-reveal '(4))))
217
218;;;###autoload (autoload 'org-menu-visibility "org-menu" nil t)
219(transient-define-prefix org-menu-visibility ()
220 "A menu to control visibility of `org-mode' items."
221 ["Visibility"
222 ["Heading"
223 ("a" "all" org-show-subtree :if-not org-at-block-p :transient t)
224 ("a" "all" org-hide-block-toggle :if org-at-block-p :transient t)
225 ("c" "cycle" org-cycle :transient t)
226 ("t" "content" org-menu-show-headline-content :if-not org-at-block-p :transient t)
227 ("h" "hide" outline-hide-subtree :if-not org-at-block-p :transient t)
228 ("h" "hide" org-hide-block-toggle :if org-at-block-p :transient t)
229 ("r" "reveal" (lambda () (interactive) (org-reveal t)) :if-not org-at-block-p :transient t)]
230 ["Global"
231 :pad-keys t
232 ("C" "cycle global" org-global-cycle :transient t)
233 ("go" "overview" org-overview)
234 ("gt" "content" (lambda () (interactive) (org-content org-menu-global-toc-depth)))
235 ("ga" "all" org-show-all)
236 ("gd" "default" (lambda () (interactive) (org-set-startup-visibility)))]
237 ["Narrow"
238 :pad-keys t
239 ("nn" "toggle" org-toggle-narrow-to-subtree)
240 ("nb" "to block" org-narrow-to-block :if org-at-block-p)
241 ("ns" "to sub tree" org-narrow-to-subtree)
242 ("ne" "to element" org-narrow-to-element)
243 ("w" "widen" widen)]
244 ["Quit"
245 :if-non-nil org-menu-use-q-for-quit
246 ("q" "quit" transient-quit-all)]])
247
248(transient-define-prefix org-menu-visibility-columns ()
249 "A menu to control visibility of `org-mode' items in `org-columns' mode."
250 ["Visibility"
251 ["Columns view"
252 :if org-menu-show-columns-view-options-p
253 ("t" "content" org-columns-content :transient t)
254 ("o" "overview" org-overview :transient t)
255 ("g" "refresh" org-columns-redo :transient t)]
256 ["Quit"
257 :if-non-nil org-menu-use-q-for-quit
258 ("q" "quit" transient-quit-all)]])
259
260(defun org-menu-eval-src-items ()
261 "Return the items to evaluate a source block."
262 (list
263 ["Source"
264 :if org-menu-show-src-options-p
265 ("e" "run block" org-babel-execute-src-block)
266 ("c" "check headers" org-babel-check-src-block)
267 ("k" "clear results" org-babel-remove-result-one-or-many)
268 ("'" "edit" org-edit-special)]))
269
270;;;###autoload (autoload 'org-menu-eval "org-menu" nil t)
271(transient-define-prefix org-menu-eval ()
272 "A menu to evaluate buffers, tables, etc. in `org-mode'."
273 ["dummy"])
274
275(defun org-menu-run-gnuplot ()
276 "Will call `org-plot/gnuplot' and update inline images."
277 (interactive)
278 (org-plot/gnuplot)
279 (when org-inline-image-overlays
280 (org-redisplay-inline-images)))
281
282(transient-insert-suffix 'org-menu-eval (list 0)
283 `["Evaluation"
284 ["Table"
285 :if org-at-table-p
286 ("e" "table" (lambda () (interactive) (org-table-recalculate 'iterate)))
287 ("1" "one iteration" (lambda () (interactive) (org-table-recalculate t)))
288 ("l" "line" (lambda () (interactive) (org-table-recalculate nil)))
289 ("f" "format" org-table-align :if org-at-table-p)]
290 ,@(org-menu-eval-src-items)
291 ["Heading"
292 :if-not org-in-src-block-p
293 ("c" "update checkbox count" org-update-checkbox-count)]
294 ["Plot"
295 ("p" "gnuplot" org-menu-run-gnuplot)]
296 ["Export"
297 ("t" "tangle source files" org-babel-tangle)
298 ("x" "export" org-export-dispatch)]
299 ["Quit"
300 :if-non-nil org-menu-use-q-for-quit
301 ("q" "quit" transient-quit-all)]])
302
303(defun org-menu-insert-block (str)
304 "Insert an org mode block of type `STR'."
305 (interactive)
306 (insert (format "#+begin_%s\n#+end_%s\n" str str)))
307
308(defun org-menu-insert-horizontal-rule ()
309 "Insert a horizontal rule."
310 (interactive)
311 (insert "-----"))
312
313;;;###autoload (autoload 'org-menu-insert-blocks "org-menu" nil t)
314(transient-define-prefix org-menu-insert-blocks ()
315 "A menu to insert new blocks in `org-mode'."
316 [["Insert block"
317 ("s" "source" (lambda () (interactive) (org-menu-insert-block "src")))
318 ("e" "example" (lambda () (interactive) (org-menu-insert-block "example")))
319 ("v" "verbatim" (lambda () (interactive) (org-menu-insert-block "verbatim")))
320 ("a" "ascii" (lambda () (interactive) (org-menu-insert-block "ascii")))
321 ("q" "quote" (lambda () (interactive) (org-menu-insert-block "quote")) :if-nil org-menu-use-q-for-quit)
322 ("Q" "quote" (lambda () (interactive) (org-menu-insert-block "quote")) :if-non-nil org-menu-use-q-for-quit)
323 ("d" "dynamic block" org-dynamic-block-insert-dblock)]
324 ["Quit"
325 :if-non-nil org-menu-use-q-for-quit
326 ("q" "quit" transient-quit-all)]])
327
328;;;###autoload (autoload 'org-menu-insert-heading "org-menu" nil t)
329(transient-define-prefix org-menu-insert-heading ()
330 "A menu to insert new headings in `org-mode'."
331 [["Heading"
332 ("h" "heading" org-insert-heading)
333 ("H" "heading (after)" org-insert-heading-after-current)
334 ("T" "todo" org-insert-todo-heading)]
335 ["Items"
336 ("d" "drawer" org-insert-drawer)]
337 ["Quit"
338 :if-non-nil org-menu-use-q-for-quit
339 ("q" "quit" transient-quit-all)]])
340
341;;;###autoload (autoload 'org-menu-insert-template "org-menu" nil t)
342(transient-define-prefix org-menu-insert-template ()
343 "A menu to insert new templates in `org-mode'."
344 [["Templates"
345 ("S" "structure template" org-insert-structure-template)
346 ("B" "blocks" (lambda () (interactive) (org-menu-expand-snippet 'block)))
347 ("O" "options" (lambda () (interactive) (org-menu-expand-snippet 'option)))]
348 ["Quit"
349 :if-non-nil org-menu-use-q-for-quit
350 ("q" "quit" transient-quit-all)]])
351
352;;;###autoload (autoload 'org-menu-insert-timestamp "org-menu" nil t)
353(transient-define-prefix org-menu-insert-timestamp ()
354 "A menu to insert timestamps in Org Mode."
355 [["Active"
356 ("." "Time stamp" org-time-stamp)
357 ("t" "Today" (lambda () (interactive) (org-insert-time-stamp (current-time) nil nil)))
358 ("n" "Today + time" (lambda () (interactive) (org-insert-time-stamp (current-time) t nil)))]
359 ["Inactive"
360 ("!" "Time stamp (i)" org-time-stamp-inactive)
361 ("T" "Today (i)" (lambda () (interactive) (org-insert-time-stamp (current-time) nil t)))
362 ("N" "Today + time (i)" (lambda () (interactive) (org-insert-time-stamp (current-time) t t)))]
363 ["Quit"
364 :if-non-nil org-menu-use-q-for-quit
365 ("q" "quit" transient-quit-all)]])
366
367(defun org-menu-table-insert-row-below ()
368 "Insert a new table column below point."
369 (interactive)
370 (org-table-insert-row '4))
371
372(defun org-menu-table-insert-column-left ()
373 "Insert a new column to the left of point."
374 (interactive)
375 (org-table-insert-column)
376 (org-table-move-column-right))
377
378;;;###autoload (autoload 'org-menu-insert-table "org-menu" nil t)
379(transient-define-prefix org-menu-insert-table ()
380 "A menu to insert table items in `org-mode'."
381 [["Table"
382 ("t" "table" org-table-create-or-convert-from-region :if-not org-at-table-p)
383 ("i" "import" org-table-import :if-not org-at-table-p)]
384 ["Rows/columns"
385 :if org-at-table-p
386 ("r" "row above" org-table-insert-row :transient t)
387 ("R" "row below" org-menu-table-insert-row-below :transient t)
388 ("c" "column left" org-table-insert-column :transient t)
389 ("C" "column right" org-menu-table-insert-column-left :transient t)
390 ("-" "horiz. line" org-table-insert-hline :transient t)]
391 ["Quit"
392 :if-non-nil org-menu-use-q-for-quit
393 ("q" "quit" transient-quit-all)]])
394
395(defun org-menu-insert-superscript ()
396 "Insert a text with superscript."
397 (interactive)
398 (org-menu-expand-snippet 'superscript))
399
400(defun org-menu-insert-subscript ()
401 "Insert a text with subscript."
402 (interactive)
403 (org-menu-expand-snippet 'subscript))
404
405(defun org-menu-parse-formatting (format-char)
406 "Will return the bounds of the format markup `FORMAT-CHAR'."
407 (let ((original-point (point))
408 start end)
409 (ignore-errors
410 (save-excursion
411 (save-restriction
412 (save-match-data
413 (org-narrow-to-element)
414 (goto-char (search-backward (format "%c" format-char)))
415 (setq start (point))
416 (goto-char original-point)
417 (goto-char (search-forward (format "%c" format-char)))
418 (setq end (point))
419 (cons start end)))))))
420
421(defun org-menu-toggle-format (format-char)
422 "Will either remove `FORMAT-CHAR' or add it around region/point."
423 (let ((range (org-menu-parse-formatting format-char))
424 (format-string (format "%c" format-char)))
425 (if (null range)
426 (org-menu-insert-text format-string format-string t)
427 (goto-char (cdr range))
428 (delete-char -1)
429 (goto-char (car range))
430 (delete-char 1))))
431
432;;;###autoload (autoload 'org-menu-insert-list "org-menu" nil t)
433(transient-define-prefix org-menu-insert-list ()
434 "A menu to insert lists."
435 [["List"
436 ("-" "item" (lambda () (interactive) (insert "- ")))
437 ("+" "+" (lambda () (interactive) (insert "+ ")))
438 ("*" "*" (lambda () (interactive) (insert "* ")))
439 ("." "1." (lambda () (interactive) (insert "1. ")))
440 (")" "1)" (lambda () (interactive) (insert "1) ")))]
441 ["Todo"
442 ("t" "todo" (lambda () (interactive) (insert "- [ ] ")))
443 ("d" "done" (lambda () (interactive) (insert "- [X] ")))
444 ("p" "partial" (lambda () (interactive) (insert "- [-] ")))]
445 ["Quit"
446 :if-non-nil org-menu-use-q-for-quit
447 ("q" "quit" transient-quit-all)]])
448
449(defun org-menu-insert-plot ()
450 "Insert a small example plot for `gnu-plot'."
451 (interactive)
452 (beginning-of-line 1)
453 (org-menu-expand-snippet 'plot))
454
455(defun org-menu-insert-option-line-smart (line)
456 "Insert `LINE'. If inside a block move to right before it."
457 (beginning-of-line 1)
458 (insert line "\n"))
459
460(defun org-menu-insert-name (name)
461 "Insert a #+NAME for the next element."
462 (interactive "MName? ")
463 (org-menu-insert-option-line-smart (format "#+NAME: %s" name)))
464
465(defun org-menu-insert-caption (caption)
466 "Insert a #+CAPTION for the next element."
467 (interactive "MCaption? ")
468 (org-menu-insert-option-line-smart (format "#+CAPTION: %s" caption)))
469
470(defun org-menu-insert-startup-setting (setting)
471 "Insert a buffer `SETTING'."
472 (interactive (list (completing-read "Startup setting? "
473 (mapcar 'car org-startup-options))))
474 (org-menu-insert-option-line-smart (format "#+STARTUP: %s" setting)))
475
476(defun org-menu-insert-buffer-setting (setting)
477 "Insert a buffer `SETTING'."
478 (interactive (list (completing-read "Buffer setting? " org-options-keywords)))
479 (insert (format "#+%s " setting)))
480
481(defun org-menu-insert-footnote-definition (name definition)
482 "Insert a definition for a footnote.
483
484Named `NAME' using `DEFINITION'."
485 (interactive "MName? \nMDefinition? ")
486 (org-menu-insert-option-line-smart (format "[fn:%s] %s" name definition)))
487
488(defun org-menu-insert-footnote-inline (name definition)
489 "Insert a definition for an inline footnote.
490
491Named `NAME' with `DEFINITION'."
492 (interactive "MName? \nMDefinition? ")
493 (insert (format "[fn:%s: %s]" name definition)))
494
495;;;###autoload (autoload 'org-menu-insert "org-menu" nil t)
496(transient-define-prefix org-menu-insert ()
497 "A menu to insert new items in `org-mode'."
498 ["Insert"
499 ["Element"
500 ("." "time" org-menu-insert-timestamp)
501 ("l" "link (new)" org-insert-link)
502 ("L" "link (stored)" org-insert-last-stored-link :transient t)
503 ("T" "templates" org-menu-insert-template)]
504 ["Structure"
505 ("h" "heading" org-menu-insert-heading)
506 ("-" "list" org-menu-insert-list)
507 ("H" "hor. rule" org-menu-insert-horizontal-rule)]
508 ["Block/table"
509 ("b" "block" org-menu-insert-blocks)
510 ("t" "table" org-menu-insert-table)
511 ("p" "plot" org-menu-insert-plot)]
512 ["Format"
513 ("^" "superscript" org-menu-insert-superscript)
514 ("_" "subscript" org-menu-insert-subscript)]
515 ["Footnotes"
516 ("fd" "define" org-menu-insert-footnote-definition)
517 ("fi" "inline" org-menu-insert-footnote-inline)]
518 ["Options"
519 ("n" "name" org-menu-insert-name)
520 ("c" "caption" org-menu-insert-caption)
521 ("s" "startup option" org-menu-insert-startup-setting)
522 ("o" "buffer option" org-menu-insert-buffer-setting)]
523 ["Quit"
524 :if-non-nil org-menu-use-q-for-quit
525 ("q" "quit" transient-quit-all)]])
526
527(defun org-menu-comment-line ()
528 "Toggle line comment w/o moving cursor."
529 (interactive)
530 (save-excursion (comment-line 1)))
531
532(defun org-menu-fix-timestamp ()
533 "Fix the timestamp at `(point)'."
534 (interactive)
535 (org-timestamp-change 0 'day))
536
537(defun org-menu-insert-text (left right &optional surround-whitespace)
538 "Will insert left|right and put the curser at |.
539
540If region is active it will be surrounded by `LEFT' and `RIGHT' and
541the point will be at end of region. Will add spaces before/after text if
542`SURROUND-WHITESPACE' is true and it's needed."
543 (let ((start (point))
544 (end (point)))
545 (when (region-active-p)
546 (setq start (region-beginning)
547 end (region-end))
548 (deactivate-mark))
549 (when (> start end)
550 (cl-psetq start end
551 end start))
552
553 (goto-char start)
554 (when (and surround-whitespace
555 (not (bolp))
556 (not (looking-back " +" nil)))
557 (insert " "))
558 (insert left)
559
560 (forward-char (- end start))
561
562 (save-excursion
563 (insert right)
564 (when (and surround-whitespace
565 (not (eolp))
566 (not (looking-at " +")))
567 (insert " ")))))
568
569;;;###autoload (autoload 'org-menu-goto "org-menu" nil t)
570(transient-define-prefix org-menu-goto ()
571 "Menu to go to different places by name."
572 [["Go to"
573 ("h" "heading" imenu)
574 ("s" "source block" org-babel-goto-named-src-block)
575 ("r" "result block" org-babel-goto-named-result)
576 ("." "calendar" org-goto-calendar :if org-menu-show-timestamp-options-p)]
577 ["Quit"
578 :if-non-nil org-menu-use-q-for-quit
579 ("q" "quit" transient-quit-all)]])
580
581(defun org-menu-toggle-zwspace ()
582 "Will remove zero-width space before/after point or insert it if none found."
583 (interactive)
584 (let ((zww (string ?\N{ZERO WIDTH SPACE})))
585 (save-excursion
586 (skip-chars-backward zww)
587 (if (looking-at (rx (+ (literal zww))))
588 (replace-match "")
589 (insert zww)))))
590
591(defun org-menu-text-format-items (check-for-table)
592 "Items to format text.
593
594Will add an ':if org-menu-show-text-options-p' criteria if
595`CHECK-FOR-TABLE' is true."
596 (list
597 `["Navigate"
598 ,@(when check-for-table '(:if org-menu-show-text-options-p))
599 :pad-keys t
600 ("p" "up" previous-line :transient t)
601 ("n" "down" next-line :transient t)
602 ("b" "left" backward-word :transient t)
603 ("f" "right" forward-word :transient t)
604 ("u" "parent" org-up-element :transient t)
605 ("M-w" "store link" org-store-link :transient t :if-not region-active-p)
606 ("C-_" "undo" undo :transient t)
607 ("SPC" "mark" set-mark-command :transient t)
608 ("C-x C-x" "exchange" exchange-point-and-mark :transient t)]
609 `["Formatting"
610 ,@(when check-for-table '(:if org-menu-show-text-options-p))
611 :pad-keys t
612 ("*" "Bold" (lambda nil (interactive) (org-menu-toggle-format ?*)) :transient t)
613 ("/" "italic" (lambda nil (interactive) (org-menu-toggle-format ?/)) :transient t)
614 ("_" "underline" (lambda nil (interactive) (org-menu-toggle-format ?_)) :transient t)
615 ("+" "strikethrough" (lambda nil (interactive) (org-menu-toggle-format ?+)) :transient t)
616 ("S-SPC" "zero-width space" org-menu-toggle-zwspace :transient t)]
617 `["Source"
618 ,@(when check-for-table '(:if org-menu-show-text-options-p))
619 ("~" "code" (lambda nil (interactive) (org-menu-toggle-format ?~)) :transient t)
620 ("=" "verbatim" (lambda nil (interactive) (org-menu-toggle-format ?=)) :transient t)]))
621
622;;;###autoload (autoload 'org-menu-text-in-element "org-menu" nil t)
623(transient-define-prefix org-menu-text-in-element ()
624 "Add formatting for text inside other elements like lists and tables."
625 ["dummy"])
626
627(transient-insert-suffix 'org-menu-text-in-element (list 0)
628 `[,@(org-menu-text-format-items nil)
629 ["Quit"
630 :if-non-nil org-menu-use-q-for-quit
631 ("q" "quit" transient-quit-all)]])
632
633;;;###autoload (autoload 'org-menu-options "org-menu" nil t)
634(transient-define-prefix org-menu-options ()
635 "A menu to toggle options."
636 [["Display"
637 ("l" "show links" org-toggle-link-display)
638 ("i" "inline images" org-toggle-inline-images)
639 ("p" "pretty entities" org-toggle-pretty-entities)
640 ("I" "indent by level" org-indent-mode)
641 ("t" "timestamp overlay" org-toggle-time-stamp-overlays)
642 ("n" "numbered headings" org-num-mode)]
643 ["Quit"
644 :if-non-nil org-menu-use-q-for-quit
645 ("q" "quit" transient-quit-all)]])
646
647(defun org-menu-toggle-has-checkbox ()
648 "Toggle whether the current list item has a checkbox."
649 (interactive)
650 (save-excursion
651 (back-to-indentation)
652 (if (not (looking-at "- "))
653 (message "Not at list item")
654 (end-of-line 1)
655 (org-ctrl-c-ctrl-c '(4)))))
656
657(defun org-menu-is-timer-running ()
658 "Return whether a timer is currently running."
659 (and org-timer-start-time
660 (not org-timer-countdown-timer)
661 (not org-timer-pause-time)))
662
663(defun org-menu-is-timer-paused ()
664 "Return whether a timer has been started and is paused."
665 (and org-timer-start-time
666 (not org-timer-countdown-timer)
667 org-timer-pause-time))
668
669;;;###autoload (autoload 'org-menu-clock "org-menu" nil t)
670(transient-define-prefix org-menu-clock ()
671 "Time management using org-modes clock."
672 [["Clock"
673 :pad-keys t
674 ("<tab>" "in" org-clock-in :if-not org-clock-is-active)
675 ("TAB" "in" org-clock-in :if-not org-clock-is-active)
676 ("o" "out" org-clock-out :if org-clock-is-active)
677 ("j" "goto" org-clock-goto :if org-clock-is-active)
678 ("q" "cancel" org-clock-cancel
679 :if (lambda () (and (not org-menu-use-q-for-quit)
680 (org-clock-is-active))))
681 ("Q" "cancel" org-clock-cancel
682 :if (lambda () (and org-menu-use-q-for-quit
683 (org-clock-is-active))))
684 ("d" "display" org-clock-display :if org-clock-is-active)
685 ("x" "in again" org-clock-in-last :if-not org-clock-is-active)
686 ("z" "resolve" org-resolve-clocks)]
687 ["Timer"
688 ("0" "start" org-timer-start :if-nil org-timer-start-time)
689 ("_" "stop" org-timer-stop :if-non-nil org-timer-start-time)
690 ("." "insert" org-timer :if-non-nil org-timer-start-time)
691 ("-" "... item" org-timer-item :if-non-nil org-timer-start-time)
692 ("," "pause" org-timer-pause-or-continue :if org-menu-is-timer-running)
693 ("," "continue" org-timer-pause-or-continue :if org-menu-is-timer-paused)
694 (";" "countdown" org-timer-set-timer :if-nil org-timer-start-time)]
695 ["Effort"
696 ("e" "set effort" org-set-effort)
697 ("E" "increase" org-inc-effort)]
698 ["Quit"
699 :if-non-nil org-menu-use-q-for-quit
700 ("q" "quit" transient-quit-all)]])
701
702(defun org-menu-columns-globally ()
703 "Turn on `org-columns' globally."
704 (interactive)
705 (org-columns t))
706
707(transient-define-prefix org-menu-search-and-filter ()
708 "A menu to search and filter `org-mode' documents."
709 ["Search and filter"
710 ["Filter"
711 ("/" "only matching" org-sparse-tree)
712 ("q" "tags" org-tags-sparse-tree :if-nil org-menu-use-q-for-quit)
713 ("Q" "tags" org-tags-sparse-tree :if-non-nil org-menu-use-q-for-quit)
714 ("t" "todos" org-show-todo-tree)
715 ("d" "deadlines" org-check-deadlines)
716 ("r" "remove highlights" org-remove-occur-highlights :if-non-nil org-occur-highlights)]
717 ["Dates"
718 ("b" "before" org-check-before-date)
719 ("a" "after" org-check-after-date)
720 ("D" "range" org-check-dates-range)]
721 ["Views"
722 ("A" "agenda" org-agenda)
723 ("c" "columns" org-columns :if-nil org-columns-current-fmt)
724 ("c" "columns off" org-columns-quit :if-non-nil org-columns-current-fmt)
725 ("gc" "whole buffer" org-menu-columns-globally :if-nil org-columns-current-fmt)]
726 ["Quit"
727 :if-non-nil org-menu-use-q-for-quit
728 ("q" "quit" transient-quit-all)]])
729
730(transient-define-prefix org-menu-attachments ()
731 "A menu to manage attachments."
732 ["Attachments"
733 ["Add"
734 ("a" "file" org-attach-attach)
735 ("c" "copy" org-attach-attach-cp)
736 ("m" "move" org-attach-attach-mv)
737 ("l" "link" org-attach-attach-ln)
738 ("y" "symlink" org-attach-attach-lns)
739 ("u" "download" org-attach-url)
740 ("b" "buffer" org-attach-buffer)
741 ("n" "new" org-attach-new)]
742 ["Open"
743 ("o" "attachment" org-attach-open)
744 ("O" "in Emacs" org-attach-open-in-emacs)
745 ("f" "directory" org-attach-reveal)
746 ("F" "in Emacs" org-attach-reveal-in-emacs)]
747 ["Delete"
748 ("d" "delete" org-attach-delete-one)
749 ("D" "all" org-attach-delete-all)]
750 ["More"
751 ("s" "set directory" org-attach-set-directory)
752 ("S" "unset" org-attach-unset-directory)
753 ("z" "synchronize" org-attach-sync)]]
754 (interactive)
755 (require 'org-attach)
756 (transient-setup 'org-menu-attachments))
757
758(transient-define-prefix org-menu-archive ()
759 "A menu to archive items."
760 ["dummy"])
761
762(defun org-menu-force-cycle-archived ()
763 "Wrapper around deprecated `org-force-cycle-archived' to fix warning."
764 (interactive)
765 (with-no-warnings
766 (org-force-cycle-archived)))
767
768(transient-insert-suffix 'org-menu-archive (list 0)
769 `["Archive"
770 ,@(org-menu-heading-navigate-items nil #'org-menu-force-cycle-archived)
771 ["Archive to"
772 ("t" "tree" org-archive-subtree :transient t)
773 ("s" "sibling" org-archive-to-archive-sibling :transient t)
774 ("Q" "tag" org-toggle-archive-tag :transient t)]])
775
776(defun org-menu-insert-todo-heading-after-current ()
777 "Insert a new todo heading with same level as current, after subtree."
778 (interactive)
779 (org-insert-todo-heading '(16)))
780
781;;;###autoload (autoload 'org-menu "org-menu" nil t)
782(transient-define-prefix org-menu ()
783 "A discoverable menu to edit and view `org-mode' documents."
784 ["dummy"])
785
786(transient-insert-suffix 'org-menu (list 0)
787 `["Org mode"
788 ;; Items for headings
789 ,@(org-menu-heading-navigate-items t)
790
791 ["Move heading"
792 :if org-menu-show-heading-options-p
793 ("P" "up" org-metaup :transient t)
794 ("N" "down" org-metadown :transient t)
795 ("B" "left" org-shiftmetaleft :transient t)
796 ("F" "right" org-shiftmetaright :transient t)
797 ("b" "left (line)" org-metaleft :transient t)
798 ("f" "right (line)" org-metaright :transient t)
799 ("r" "refile" org-refile :transient t)]
800 ["Change heading"
801 :if org-menu-show-heading-options-p
802 ("*" "toggle" org-ctrl-c-star :if-not org-at-table-p :transient t)
803 ("t" "todo" org-todo :transient t)
804 ("q" "tags" org-set-tags-command :transient t :if-nil org-menu-use-q-for-quit)
805 ("Q" "tags" org-set-tags-command :transient t :if-non-nil org-menu-use-q-for-quit)
806 ("y" "property" org-set-property :transient t)
807 ("," "priority" org-priority :transient t)
808 ("A" "archive" org-menu-archive :transient t)
809 ("D" "deadline" org-deadline :transient t)
810 ("S" "schedule" org-schedule :transient t)
811 ("/" "comment" org-toggle-comment :transient t)
812 ("mn" "add note" org-add-note)]
813 ["Make new/delete"
814 :if org-menu-show-heading-options-p
815 :pad-keys t
816 ("mh" "make heading (before)" org-insert-heading)
817 ("mH" "make heading (after)" org-insert-heading-after-current)
818 ("mt" "make todo (before)" org-insert-todo-heading)
819 ("mT" "make todo (after)" org-menu-insert-todo-heading-after-current)
820 ("mc" "clone with time shift" org-clone-subtree-with-time-shift)
821 ("dh" "delete heading" org-cut-subtree :transient t)
822 ("dy" "delete property" org-delete-property :transient t)
823 ("a" "attachments" org-menu-attachments)
824 ("C-w" "cut tree" org-cut-special :transient t)
825 ("C-y" "yank tree" org-paste-special :transient t)]
826
827 ;; Items for tables
828 ["Navigate"
829 :if org-menu-show-table-options-p
830 :pad-keys t
831 ("p" "up" previous-line :transient t)
832 ("n" "down" next-line :transient t)
833 ("b" "left" org-table-previous-field :transient t)
834 ("f" "right" org-table-next-field :transient t)
835 ("u" "parent" outline-up-heading :transient t)
836 ("M-w" "store link" org-store-link :transient t :if-not region-active-p)
837 ("C-_" "undo" undo :transient t)]
838 ["Move r/c"
839 :if org-menu-show-table-options-p
840 ("P" "up" org-table-move-row-up :transient t)
841 ("N" "down" org-table-move-row-down :transient t)
842 ("B" "left" org-table-move-column-left :transient t)
843 ("F" "right" org-table-move-column-right :transient t)]
844 ["Field"
845 :if org-menu-show-table-options-p
846 :pad-keys t
847 ("'" "edit" org-table-edit-field)
848 ("SPC" "blank" org-table-blank-field :transient t)
849 ("RET" "from above" org-table-copy-down :transient t)
850 ("t" "text formatting" org-menu-text-in-element)]
851 ["Formulas"
852 :if org-menu-show-table-options-p
853 ("E" "edit all" org-table-edit-formulas :transient t)
854 ("=" "field" (lambda () (interactive) (org-table-eval-formula '(4))) :transient t)
855 ("+" "in place" (lambda () (interactive) (org-table-eval-formula '(16))))
856 ("c" "column" org-table-eval-formula :transient t)
857 ("h" "coordinates" org-table-toggle-coordinate-overlays :transient t)
858 ("D" "debug" org-table-toggle-formula-debugger :transient t)]
859 ["Table"
860 :if org-menu-show-table-options-p
861 :pad-keys t
862 ("dr" "delete row" org-shiftmetaup :transient t)
863 ("dc" "delete column" org-shiftmetaleft :transient t)
864 ("m" "make" org-menu-insert-table)
865 ,@(when (fboundp (function org-table-toggle-column-width))
866 ;; This will emit a warning during byte compilation. We can ignore it
867 (list '("S" "shrink column" org-table-toggle-column-width :transient t)))
868 ("r" "sort" org-table-sort-lines :transient t)
869 ("M-w" "copy rect" org-table-copy-region :transient t :if region-active-p)
870 ("C-w" "cut rect" org-table-cut-region :transient t :if region-active-p)
871 ("C-y" "yank rect" org-table-paste-rectangle :transient t)]
872
873 ;; Items for lists
874 ["Navigate"
875 :if org-menu-show-list-options-p
876 :pad-keys t
877 ("p" "prev" previous-line :transient t)
878 ("n" "next" next-line :transient t)
879 ("c" "cycle" org-cycle :transient t)
880 ("u" "parent" org-up-element :transient t)
881 ("M-p" "prev (same level)" org-backward-element :transient t)
882 ("M-n" "next (same level)" org-forward-element :transient t)
883 ("M-w" "store link" org-store-link :transient t :if-not region-active-p)
884 ("C-_" "undo" undo :transient t)]
885 ["Move list"
886 :if org-menu-show-list-options-p
887 ("P" "up" org-metaup :transient t)
888 ("N" "down" org-metadown :transient t)
889 ("B" "left" org-shiftmetaleft :transient t)
890 ("F" "right" org-shiftmetaright :transient t)
891 ("b" "left (line)" org-metaleft :transient t)
892 ("f" "right (line)" org-metaright :transient t)]
893 ["List"
894 :if org-menu-show-list-options-p
895 ("R" "repair" org-list-repair)
896 ("*" "turn into tree" org-list-make-subtree)
897 ("S" "sort" org-sort-list :transient t)
898 ("t" "text formatting" org-menu-text-in-element)]
899 ["Toggle"
900 :if org-menu-show-list-options-p
901 ("-" "list item" org-toggle-item :if-not org-at-table-p :transient t)
902 ("+" "list style" org-cycle-list-bullet :if-not org-at-table-p :transient t)
903 ("d" "done" org-toggle-checkbox :transient t)
904 ("h" "half-done"
905 (lambda () (interactive) (org-toggle-checkbox '(16)))
906 :transient t)
907 ("m" "checkbox" org-menu-toggle-has-checkbox :transient t)]
908
909 ;; Items for text
910 ,@(org-menu-text-format-items t)
911 ["Line"
912 :if org-menu-show-text-options-p
913 :pad-keys t
914 (":" "fixed width" org-toggle-fixed-width :transient t)
915 (";" "comment" org-menu-comment-line :transient t)
916 ("--" "list" org-toggle-item :transient t)
917 ("-*" "heading" org-ctrl-c-star :transient t)]
918
919 ;; Items for source blocks
920 ,@(org-menu-eval-src-items)
921
922 ["Link"
923 :if org-menu-show-link-options-p
924 ("e" "edit" org-insert-link :transient t)]
925
926 ["Timestamp"
927 :if org-menu-show-timestamp-options-p
928 ("." "type" org-toggle-timestamp-type :transient t)
929 ("e" "edit" org-time-stamp :transient t)
930 ("R" "repair" org-menu-fix-timestamp :transient t)]
931
932 ["Footnote"
933 :if org-menu-show-footnote-options-p
934 ("ed" "delete" (lambda () (interactive) (org-footnote-delete)))
935 ("es" "sort" (lambda () (interactive) (org-footnote-sort)))
936 ("er" "renumber" (lambda () (interactive) (org-footnote-renumber-fn:N)))
937 ("eS" "sort+renumber" (lambda () (interactive)
938 (org-footnote-renumber-fn:N)
939 (org-footnote-sort)))
940 ("en" "normalize" (lambda () (interactive) (org-footnote-normalize)))]
941
942 ;; Items for column view
943 ["Navigate"
944 :if org-menu-show-columns-view-options-p
945 :pad-keys t
946 ("p" "prev" org-columns-move-up :transient t)
947 ("n" "next" org-columns-move-down :transient t)
948 ("f" "forward" forward-char :transient t)
949 ("b" "backward" backward-char :transient t)
950 ("M-w" "store link" org-store-link :transient t :if-not region-active-p)
951 ("C-_" "undo" undo :transient t)]
952 ["Value"
953 :if org-menu-show-columns-view-options-p
954 :pad-keys t
955 ("e" "edit" org-columns-edit-value :transient t)
956 ("V" "show" org-columns-show-value :transient t)
957 ("M-n" "next" org-columns-next-allowed-value :transient t)
958 ("M-p" "previous" org-columns-previous-allowed-value :transient t)
959 ("a" "edit allowed" org-columns-edit-allowed :transient t)]
960 ["Column"
961 :if org-menu-show-columns-view-options-p
962 :pad-keys t
963 ("E" "edit column" org-columns-edit-attributes :transient t)
964 ("{" "narrow" org-columns-narrow :transient t)
965 ("}" "widen" org-columns-widen :transient t)
966 ("M-<right>" "move right" org-columns-move-right :transient t)
967 ("M-<left>" "move left" org-columns-move-left :transient t)
968 ("M-S-<right>" "new" org-columns-new :transient t)
969 ("M-S-<left>" "delete" org-columns-delete :transient t)]
970
971 ["Tasks"
972 ("v" "visibility" org-menu-visibility :if-not org-menu-show-columns-view-options-p)
973 ("v" "visibility" org-menu-visibility-columns :if org-menu-show-columns-view-options-p)
974 ("x" "evaluation" org-menu-eval)
975 ("i" "insert" org-menu-insert)
976 ("g" "go to" org-menu-goto)
977 ("s" "search" org-menu-search-and-filter)
978 ("o" "options" org-menu-options)
979 ("C" "clock (active)" org-menu-clock :if org-clock-is-active)
980 ("C" "clock" org-menu-clock :if-not org-clock-is-active)
981 ,@(when (fboundp #'org-capture-finalize)
982 (list '("C-c C-c" "confirm capture" org-capture-finalize :if-non-nil org-capture-mode)))
983 ,@(when (fboundp #'org-capture-kill)
984 (list '("C-c C-k" "abort capture" org-capture-kill :if-non-nil org-capture-mode)))
985 ""
986 ("q" "quit" transient-quit-all :if-non-nil org-menu-use-q-for-quit)]])
987
988(provide 'org-menu)
989;;; org-menu.el ends here