system-manager-wakasu
  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