home

My NixOS systems configurations.
Log | Files | Refs | LICENSE

org-menu.el (28739B)


      1 ;;; org-menu.el --- A discoverable menu for org-mode using transient -*- lexical-binding: t; coding: utf-8 -*-
      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 
     44 (defgroup org-menu nil
     45   "Options for org-menu"
     46   :group 'org)
     47 
     48 (defcustom org-menu-use-q-for-quit t
     49   "Whether to add a q binding to quit to all menus.
     50 
     51 Use this if you prefer to be consistent with magit.  It will also
     52 change some other bindings to use Q instead of q."
     53   :group 'org-menu
     54   :type 'boolean)
     55 
     56 (defcustom org-menu-global-toc-depth 10
     57   "The number of heading levels to show when displaying the global content."
     58   :group 'org-menu
     59   :type 'integer)
     60 
     61 (defun org-menu-heading-navigate-items (check-for-heading &optional cycle-function)
     62   "Items to navigate headings.
     63 
     64 These will be added to most sub menus.  If `CHECK-FOR-HEADING' is
     65 true the items will only be added if on a heading.  `CYCLE-FUNCTION' is the
     66 function to be used to cycle visibility of current element."
     67   (setq cycle-function (or cycle-function #'org-cycle))
     68   `(["Navigate"
     69      ,@(when check-for-heading '(:if org-at-heading-p))
     70      ("p" "prev" org-previous-visible-heading :transient t)
     71      ("n" "next" org-next-visible-heading :transient t)
     72      ("c" "cycle" ,cycle-function :transient t)
     73      ("u" "parent" outline-up-heading :transient t)
     74      ("M-p" "prev (same level)" org-backward-heading-same-level :transient t)
     75      ("M-n" "next (same level)" org-forward-heading-same-level :transient t)
     76      ("M-w" "store link" org-store-link :transient t :if-not region-active-p)
     77      ("C-_" "undo" undo :transient t)]))
     78 
     79 (defun org-menu-show-headline-content ()
     80   "Will show the complete content of the current headline and it's children."
     81   (interactive)
     82   (save-excursion
     83     (outline-hide-subtree)
     84     (org-show-children 4)
     85     (org-goto-first-child)
     86     (org-reveal '(4))))
     87 
     88 ;;;###autoload (autoload 'org-menu-visibility "org-menu" nil t)
     89 (transient-define-prefix org-menu-visibility ()
     90   "A menu to control visibility of org-mode items"
     91   ["dummy"])
     92 
     93 (transient-insert-suffix 'org-menu-visibility (list 0)
     94   `["Visibility"
     95     ,@(org-menu-heading-navigate-items nil)
     96     ["Visibility"
     97      ("a" "all" org-show-subtree :if-not org-at-block-p :transient t)
     98      ("a" "all" org-hide-block-toggle :if org-at-block-p :transient t)
     99      ("t" "content" org-menu-show-headline-content :if-not org-at-block-p :transient t)
    100      ("h" "hide" outline-hide-subtree :if-not org-at-block-p :transient t)
    101      ("h" "hide" org-hide-block-toggle :if org-at-block-p :transient t)
    102      ("r" "reveal" (lambda () (interactive) (org-reveal t)) :if-not org-at-block-p :transient t)]
    103     ["Global"
    104      ("C" "cycle global" org-global-cycle :transient t)
    105      ("go" "overview" org-overview)
    106      ("gt" "content" (lambda () (interactive) (org-content org-menu-global-toc-depth)))
    107      ("ga" "all" org-show-all)
    108      ("gd" "default" (lambda () (interactive) (org-set-startup-visibility)))]
    109     ["Quit"
    110      :if-non-nil org-menu-use-q-for-quit
    111      ("q" "quit" transient-quit-all)]])
    112 
    113 (defun org-menu-eval-src-items ()
    114   "Return the items to evaluate a source block."
    115   (list
    116    ["Source"
    117     :if org-in-src-block-p
    118     ("e" "run block" org-babel-execute-src-block)
    119     ("c" "check headers" org-babel-check-src-block)
    120     ("k" "clear results" org-babel-remove-result-one-or-many)
    121     ("'" "edit" org-edit-special)]))
    122 
    123 ;;;###autoload (autoload 'org-menu-eval "org-menu" nil t)
    124 (transient-define-prefix org-menu-eval ()
    125   "A menu to evaluate buffers, tables, etc. in org-mode"
    126   ["dummy"])
    127 
    128 (defun org-menu-run-gnuplot ()
    129   "Will call `org-plot/gnuplot' and update inline images."
    130   (interactive)
    131   (org-plot/gnuplot)
    132   (when org-inline-image-overlays
    133     (org-redisplay-inline-images)))
    134 
    135 (transient-insert-suffix 'org-menu-eval (list 0)
    136   `["Evaluation"
    137     ["Table"
    138      :if org-at-table-p
    139      ("e" "table" (lambda () (interactive) (org-table-recalculate 'iterate)))
    140      ("1" "one iteration" (lambda () (interactive) (org-table-recalculate t)))
    141      ("l" "line" (lambda () (interactive) (org-table-recalculate nil)))
    142      ("f" "format" org-table-align :if org-at-table-p)]
    143     ,@(org-menu-eval-src-items)
    144     ["Heading"
    145      :if-not org-in-src-block-p
    146      ("c" "update checkbox count" org-update-checkbox-count)]
    147     ["Plot"
    148      ("p" "gnuplot" org-menu-run-gnuplot)]
    149     ["Quit"
    150      :if-non-nil org-menu-use-q-for-quit
    151      ("q" "quit" transient-quit-all)]])
    152 
    153 (defun org-menu-insert-block (str)
    154   "Insert an org mode block of type `STR'."
    155   (interactive)
    156   (insert (format "#+begin_%s\n#+end_%s\n" str str)))
    157 
    158 (defun org-menu-expand-snippet (snippet)
    159   "Will expand the given snippet named `SNIPPET'."
    160   (interactive)
    161   (if (require 'yasnippet nil 'noerror)
    162       (progn
    163         (insert snippet)
    164         (yas-expand))
    165     (message "error: yasnippet not installed, could not expand %s" snippet)))
    166 
    167 ;;;###autoload (autoload 'org-menu-insert-blocks "org-menu" nil t)
    168 (transient-define-prefix org-menu-insert-blocks ()
    169   "A menu to insert new blocks in org-mode"
    170   [["Insert block"
    171     ("s" "source" (lambda () (interactive) (org-menu-insert-block "src")))
    172     ("e" "example" (lambda () (interactive) (org-menu-insert-block "example")))
    173     ("v" "verbatim" (lambda () (interactive) (org-menu-insert-block "verbatim")))
    174     ("a" "ascii" (lambda () (interactive) (org-menu-insert-block "ascii")))
    175     ("q" "quote" (lambda () (interactive) (org-menu-insert-block "quote")) :if-nil org-menu-use-q-for-quit)
    176     ("Q" "quote" (lambda () (interactive) (org-menu-insert-block "quote")) :if-non-nil org-menu-use-q-for-quit)
    177     ("d" "dynamic block" org-dynamic-block-insert-dblock)]
    178    ["Quit"
    179     :if-non-nil org-menu-use-q-for-quit
    180     ("q" "quit" transient-quit-all)]])
    181 
    182 ;;;###autoload (autoload 'org-menu-insert-heading "org-menu" nil t)
    183 (transient-define-prefix org-menu-insert-heading ()
    184   "A menu to insert new headings in org-mode"
    185   [["Heading"
    186     ("h" "heading" org-insert-heading)
    187     ("H" "heading (after)" org-insert-heading-after-current)
    188     ("T" "todo" org-insert-todo-heading)]
    189    ["Items"
    190     ("d" "drawer" org-insert-drawer)]
    191    ["Quit"
    192     :if-non-nil org-menu-use-q-for-quit
    193     ("q" "quit" transient-quit-all)]])
    194 
    195 ;;;###autoload (autoload 'org-menu-insert-template "org-menu" nil t)
    196 (transient-define-prefix org-menu-insert-template ()
    197   "A menu to insert new templates in org-mode"
    198   [["Templates"
    199     ("S" "structure template" org-insert-structure-template)
    200     ("B" "yas blocks" (lambda () (interactive) (org-menu-expand-snippet "beg")))
    201     ("O" "yas options" (lambda () (interactive) (org-menu-expand-snippet "opt")))]
    202    ["Quit"
    203     :if-non-nil org-menu-use-q-for-quit
    204     ("q" "quit" transient-quit-all)]])
    205 
    206 ;;;###autoload (autoload 'org-menu-insert-timestamp "org-menu" nil t)
    207 (transient-define-prefix org-menu-insert-timestamp ()
    208   "A menu to insert timestamps in org-mode"
    209   [["Timestamp"
    210     ("." "active" org-time-stamp)
    211     ("!" "inactive" org-time-stamp-inactive)]
    212    ["Now"
    213     ("n" "active" (lambda () (interactive) (org-insert-time-stamp (current-time) t)))
    214     ("N" "inactive" (lambda () (interactive) (org-insert-time-stamp (current-time) t t)))]
    215    ["Today"
    216     ("t" "active" (lambda () (interactive) (org-insert-time-stamp (current-time) nil)))
    217     ("T" "inactive" (lambda () (interactive) (org-insert-time-stamp (current-time) nil t)))]
    218    ["Quit"
    219     :if-non-nil org-menu-use-q-for-quit
    220     ("q" "quit" transient-quit-all)]])
    221 
    222 (defun org-menu-table-insert-row-below ()
    223   "Insert a new table column below point."
    224   (interactive)
    225   (org-table-insert-row '4))
    226 
    227 (defun org-menu-table-insert-column-left ()
    228   "Insert a new column to the left of point."
    229   (interactive)
    230   (org-table-insert-column)
    231   (org-table-move-column-right))
    232 
    233 ;;;###autoload (autoload 'org-menu-insert-table "org-menu" nil t)
    234 (transient-define-prefix org-menu-insert-table ()
    235   "A menu to insert table items in org-mode"
    236   [["Table"
    237     ("t" "table" org-table-create-or-convert-from-region :if-not org-at-table-p)
    238     ("i" "import" org-table-import :if-not org-at-table-p)]
    239    ["Rows/columns"
    240     :if org-at-table-p
    241     ("r" "row above" org-table-insert-row :transient t)
    242     ("R" "row below" org-menu-table-insert-row-below :transient t)
    243     ("c" "column right" org-table-insert-column :transient t)
    244     ("C" "column left" org-menu-table-insert-column-left :transient t)
    245     ("-" "horiz. line" org-table-insert-hline :transient t)]
    246    ["Quit"
    247     :if-non-nil org-menu-use-q-for-quit
    248     ("q" "quit" transient-quit-all)]])
    249 
    250 (defun org-menu-insert-superscript ()
    251   "Insert a text with superscript."
    252   (interactive)
    253   (if (require 'yasnippet nil 'noerror)
    254       (yas-expand-snippet "${1:text}^{${2:super}}")
    255     (insert "a^b")))
    256 
    257 (defun org-menu-insert-subscript ()
    258   "Insert a text with subscript."
    259   (interactive)
    260   (if (require 'yasnippet nil 'noerror)
    261       (yas-expand-snippet "${1:text}_{${2:sub}}")
    262     (insert "a_b")))
    263 
    264 (defun org-menu-parse-formatting (format-char)
    265   "Will return the bounds of the format markup `FORMAT-CHAR'."
    266   (let ((original-point (point))
    267         start end)
    268     (ignore-errors
    269       (save-excursion
    270         (save-restriction
    271           (save-match-data
    272             (org-narrow-to-element)
    273             (goto-char (search-backward (format "%c" format-char)))
    274             (setq start (point))
    275             (goto-char original-point)
    276             (goto-char (search-forward (format "%c" format-char)))
    277             (setq end (point))
    278             (cons start end)))))))
    279 
    280 (defun org-menu-toggle-format (format-char)
    281   "Will add/remove the given format wrapped in `FORMAT-CHAR' form the region (or point)."
    282   (let ((range (org-menu-parse-formatting format-char))
    283         (format-string (format "%c" format-char)))
    284     (if (null range)
    285         (org-menu-insert-text format-string format-string t)
    286       (goto-char (cdr range))
    287       (delete-backward-char 1)
    288       (goto-char (car range))
    289       (delete-char 1))))
    290 
    291 ;;;###autoload (autoload 'org-menu-insert-list "org-menu" nil t)
    292 (transient-define-prefix org-menu-insert-list ()
    293   "A menu to insert lists"
    294   [["List"
    295     ("-" "item" (lambda () (interactive) (insert "- ")))
    296     ("+" "+" (lambda () (interactive) (insert "+ ")))
    297     ("*" "*" (lambda () (interactive) (insert "* ")))
    298     ("." "1." (lambda () (interactive) (insert "1. ")))
    299     (")" "1)" (lambda () (interactive) (insert "1) ")))]
    300    ["Todo"
    301     ("t" "todo" (lambda () (interactive) (insert "- [ ] ")))
    302     ("d" "done" (lambda () (interactive) (insert "- [X] ")))
    303     ("p" "partial" (lambda () (interactive) (insert "- [-] ")))]
    304    ["Quit"
    305     :if-non-nil org-menu-use-q-for-quit
    306     ("q" "quit" transient-quit-all)]])
    307 
    308 (defun org-menu-insert-plot ()
    309   "Insert a small example plot for `gnu-plot'."
    310   (interactive)
    311   (beginning-of-line 1)
    312   (if (require 'yasnippet nil 'noerror)
    313       (yas-expand-snippet
    314        "#+plot: type:${1:2d} file:\"${2:plot.svg}\"
    315 | A |  B |
    316 |---+----|
    317 | 1 | 10 |
    318 | 2 |  8 |
    319 | 3 |  9 |
    320 
    321 #+attr_org: :width ${3:400px}
    322 [[file:$2]]
    323 ")
    324     (insert
    325      "#+plot: type:2d file:\"plot.svg\"
    326 | A |  B |
    327 |---+----|
    328 | 1 | 10 |
    329 | 2 |  8 |
    330 | 3 |  9 |
    331 
    332 #+attr_org: :width 400px
    333 [[file:plot.svg]]
    334 ")))
    335 
    336 ;;;###autoload (autoload 'org-menu-insert "org-menu" nil t)
    337 (transient-define-prefix org-menu-insert ()
    338   "A menu to insert new items in org-mode"
    339   [["Insert"
    340     ("." "time" org-menu-insert-timestamp)
    341     ("t" "table" org-menu-insert-table)
    342     ("h" "heading" org-menu-insert-heading)
    343     ("b" "block" org-menu-insert-blocks)
    344     ("T" "templates" org-menu-insert-template)
    345     ("l" "link (new)" org-insert-link)
    346     ("L" "link (stored)" org-insert-last-stored-link :transient t)
    347     ("-" "list" org-menu-insert-list)
    348     ("p" "plot" org-menu-insert-plot)]
    349    ["Format"
    350     ("^" "superscript" org-menu-insert-superscript)
    351     ("_" "subscript" org-menu-insert-subscript)]
    352    ["Quit"
    353     :if-non-nil org-menu-use-q-for-quit
    354     ("q" "quit" transient-quit-all)]])
    355 
    356 (defun org-menu-comment-line ()
    357   "Toggle line comment w/o moving cursor."
    358   (interactive)
    359   (save-excursion (comment-line 1)))
    360 
    361 (defun org-menu-insert-text (left right &optional surround-whitespace)
    362   "Will insert left|right and put the curser at |.
    363 
    364 If region is active it will be surrounded by `LEFT' and `RIGHT' and
    365 the point will be at end of region.  Will add spaces before/after text if
    366 `SURROUND-WHITESPACE' is true and it's needed."
    367 
    368   (let ((start (point))
    369         (end (point)))
    370     (when (region-active-p)
    371       (setq start (region-beginning)
    372             end (region-end))
    373       (deactivate-mark))
    374     (when (> start end)
    375       ;; swap variables w/o importing cl-lib
    376       (setq start (prog1 end (setq end start))))
    377 
    378     (goto-char start)
    379     (when (and surround-whitespace
    380                (not (bolp))
    381                (not (looking-back " +")))
    382       (insert " "))
    383     (insert left)
    384 
    385     (forward-char (- end start))
    386 
    387     (save-excursion
    388       (insert right)
    389       (when (and surround-whitespace
    390                  (not (eolp))
    391                  (not (looking-at " +")))
    392         (insert " ")))))
    393 
    394 (defun org-menu-in-time-p ()
    395   "Return whether we're at a time stamp or similar.
    396 
    397 Adapted from `org-goto-calendar'"
    398   (or (org-at-timestamp-p 'lax)
    399       (org-match-line (concat ".*" org-ts-regexp))))
    400 
    401 ;;;###autoload (autoload 'org-menu-goto "org-menu" nil t)
    402 (transient-define-prefix org-menu-goto ()
    403   "Menu to go to different places by name"
    404   [["Go to"
    405     ("h" "heading" imenu)
    406     ("s" "source block" org-babel-goto-named-src-block)
    407     ("r" "result block" org-babel-goto-named-result)
    408     ("." "calendar" org-goto-calendar :if org-menu-in-time-p)]
    409    ["Quit"
    410     :if-non-nil org-menu-use-q-for-quit
    411     ("q" "quit" transient-quit-all)]])
    412 
    413 (defun org-menu-at-text-p ()
    414   "Return whether point is at text."
    415   (not (or (org-at-heading-p)
    416            (org-at-table-p)
    417            (org-in-item-p)
    418            (org-in-src-block-p))))
    419 
    420 (defun org-menu-text-format-items (check-for-table)
    421   "Items to format text.
    422 
    423 Will add an ':if org-menu-at-text-p' criteria if `CHECK-FOR-TABLE' is true."
    424   (list
    425    `["Navigate"
    426      ,@(when check-for-table '(:if org-menu-at-text-p))
    427      ("p" "up" previous-line :transient t)
    428      ("n" "down" next-line :transient t)
    429      ("b" "left" backward-word :transient t)
    430      ("f" "right" forward-word :transient t)
    431      ("u" "parent" org-up-element :transient t)
    432      ("M-w" "store link" org-store-link :transient t :if-not region-active-p)
    433      ("C-_" "undo" undo :transient t)
    434      ("SPC" "mark" set-mark-command :transient t)
    435      ("C-x C-x" "exchange" exchange-point-and-mark :transient t)]
    436    `["Formatting"
    437      ,@(when check-for-table '(:if org-menu-at-text-p))
    438      ("*" "Bold" (lambda nil (interactive) (org-menu-toggle-format ?*)) :transient t)
    439      ("/" "italic" (lambda nil (interactive) (org-menu-toggle-format ?/)) :transient t)
    440      ("_" "underline" (lambda nil (interactive) (org-menu-toggle-format ?_)) :transient t)
    441      ("+" "strikethrough" (lambda nil (interactive) (org-menu-toggle-format ?+)) :transient t)]
    442    `["Source"
    443      ,@(when check-for-table '(:if org-menu-at-text-p))
    444      ("~" "code" (lambda nil (interactive) (org-menu-toggle-format ?~)) :transient t)
    445      ("=" "verbatim" (lambda nil (interactive) (org-menu-toggle-format ?=)) :transient t)]))
    446 
    447 ;;;###autoload (autoload 'org-menu-text-in-element "org-menu" nil t)
    448 (transient-define-prefix org-menu-text-in-element ()
    449   "Add formatting for text inside other elements like lists and tables"
    450   ["dummy"])
    451 
    452 (transient-insert-suffix 'org-menu-text-in-element (list 0)
    453   `[,@(org-menu-text-format-items nil)
    454     ["Quit"
    455      :if-non-nil org-menu-use-q-for-quit
    456      ("q" "quit" transient-quit-all)]])
    457 
    458 ;;;###autoload (autoload 'org-menu-options "org-menu" nil t)
    459 (transient-define-prefix org-menu-options ()
    460   "A menu to toggle options"
    461   [["Display"
    462     ("l" "show links" org-toggle-link-display)
    463     ("i" "inline images" org-toggle-inline-images)
    464     ("p" "pretty entities" org-toggle-pretty-entities)
    465     ("I" "indent by level" org-indent-mode)
    466     ("t" "timestamp overlay" org-toggle-time-stamp-overlays)
    467     ("n" "numbered headings" org-num-mode)]
    468    ["Quit"
    469     :if-non-nil org-menu-use-q-for-quit
    470     ("q" "quit" transient-quit-all)]])
    471 
    472 (defun org-menu-in-link ()
    473   "Return whether we are inside a link.
    474 
    475 Conditions have been adapted from `org-insert-link'"
    476   (or
    477    ;; Use variable from org-compat to support Emacs 26
    478    (org-in-regexp org-bracket-link-regexp 1)
    479    (when (boundp 'org-link-angle-re)
    480      (org-in-regexp org-link-angle-re))
    481    (when (boundp 'org-link-plain-re)
    482      (org-in-regexp org-link-plain-re))))
    483 
    484 (defun org-menu-toggle-has-checkbox ()
    485   "Toggle whether the current list item has a checkbox."
    486   (interactive)
    487   (save-excursion
    488     (back-to-indentation)
    489     (if (not (looking-at "- "))
    490         (message "Not at list item")
    491       (end-of-line 1)
    492       (org-ctrl-c-ctrl-c '(4)))))
    493 
    494 (defun org-menu-is-timer-running ()
    495   "Return whether a timer is currently running."
    496   (and org-timer-start-time
    497        (not org-timer-countdown-timer)
    498        (not org-timer-pause-time)))
    499 
    500 (defun org-menu-is-timer-paused ()
    501   "Return whether a timer has been started and is paused."
    502   (and org-timer-start-time
    503        (not org-timer-countdown-timer)
    504        org-timer-pause-time))
    505 
    506 ;;;###autoload (autoload 'org-menu-clock "org-menu" nil t)
    507 (transient-define-prefix org-menu-clock ()
    508   "Time management using org-modes clock"
    509   [["Clock"
    510     ("<tab>" "in" org-clock-in :if-not org-clock-is-active)
    511     ("TAB" "in" org-clock-in :if-not org-clock-is-active)
    512     ("o" "out" org-clock-out :if org-clock-is-active)
    513     ("j" "goto" org-clock-goto :if org-clock-is-active)
    514     ("q" "cancel" org-clock-cancel
    515      :if (lambda () (and (not org-menu-use-q-for-quit)
    516                          (org-clock-is-active))))
    517     ("Q" "cancel" org-clock-cancel
    518      :if (lambda () (and org-menu-use-q-for-quit
    519                          (org-clock-is-active))))
    520     ("d" "display" org-clock-display :if org-clock-is-active)
    521     ("x" "in again" org-clock-in-last :if-not org-clock-is-active)
    522     ("z" "resolve" org-resolve-clocks)]
    523    ["Timer"
    524     ("0" "start" org-timer-start :if-nil org-timer-start-time)
    525     ("_" "stop" org-timer-stop :if-non-nil org-timer-start-time)
    526     ("." "insert" org-timer :if-non-nil org-timer-start-time)
    527     ("-" "... item" org-timer-item :if-non-nil org-timer-start-time)
    528     ("," "pause" org-timer-pause-or-continue :if org-menu-is-timer-running)
    529     ("," "continue" org-timer-pause-or-continue :if org-menu-is-timer-paused)
    530     (";" "countdown" org-timer-set-timer :if-nil org-timer-start-time)]
    531    ["Quit"
    532     :if-non-nil org-menu-use-q-for-quit
    533     ("q" "quit" transient-quit-all)]])
    534 
    535 (transient-define-prefix org-menu-search-and-filter ()
    536   "A menu to search and filter org-mode documents"
    537   ["Search and filter"
    538    ["Filter"
    539     ("/" "only matching" org-sparse-tree)
    540     ("q" "tags" org-tags-sparse-tree :if-nil org-menu-use-q-for-quit)
    541     ("Q" "tags" org-tags-sparse-tree :if-non-nil org-menu-use-q-for-quit)
    542     ("t" "todos" org-show-todo-tree)
    543     ("d" "deadlines" org-check-deadlines)
    544     ("b" "before date" org-check-before-date)
    545     ("a" "after date" org-check-after-date)
    546     ("D" "dates range" org-check-dates-range)]
    547    ["Agenda"
    548     ("A" "open" org-agenda)]
    549    ["Quit"
    550     :if-non-nil org-menu-use-q-for-quit
    551     ("q" "quit" transient-quit-all)]])
    552 
    553 (transient-define-prefix org-menu-attachments ()
    554   "A menu to manage attachments"
    555   ["Attachments"
    556    ["Add"
    557     ("a" "file" org-attach-attach)
    558     ("c" "copy" org-attach-attach-cp)
    559     ("m" "move" org-attach-attach-mv)
    560     ("l" "link" org-attach-attach-ln)
    561     ("y" "symlink" org-attach-attach-lns)
    562     ("u" "download" org-attach-url)
    563     ("b" "buffer" org-attach-buffer)
    564     ("n" "new" org-attach-new)]
    565    ["Open"
    566     ("o" "attachment" org-attach-open)
    567     ("O" "in Emacs" org-attach-open-in-emacs)
    568     ("f" "directory" org-attach-reveal)
    569     ("F" "in Emacs" org-attach-reveal-in-emacs)]
    570    ["Delete"
    571     ("d" "delete" org-attach-delete-one)
    572     ("D" "all" org-attach-delete-all)]
    573    ["More"
    574     ("s" "set directory" org-attach-set-directory)
    575     ("S" "unset" org-attach-unset-directory)
    576     ("z" "synchronize" org-attach-sync)]])
    577 
    578 (transient-define-prefix org-menu-archive ()
    579   "A menu to archive items"
    580   ["dummy"])
    581 
    582 (transient-insert-suffix 'org-menu-archive (list 0)
    583   `["Archive"
    584     ,@(org-menu-heading-navigate-items nil #'org-force-cycle-archived)
    585     ["Archive to"
    586      ("t" "tree" org-archive-subtree :transient t)
    587      ("s" "sibling" org-archive-to-archive-sibling :transient t)
    588      ("Q" "tag" org-toggle-archive-tag :transient t)]])
    589 
    590 (defun org-menu-insert-todo-heading-after-current ()
    591   "Insert a new todo heading with same level as current, after subtree."
    592   (interactive)
    593   (org-insert-todo-heading '(16)))
    594 
    595 ;;;###autoload (autoload 'org-menu "org-menu" nil t)
    596 (transient-define-prefix org-menu ()
    597   "A discoverable menu to edit and view org-mode documents"
    598   ["dummy"])
    599 
    600 (transient-insert-suffix 'org-menu (list 0)
    601   `["Org mode"
    602     ;; Items for headings
    603     ,@(org-menu-heading-navigate-items t)
    604 
    605     ["Move heading"
    606      :if org-at-heading-p
    607      ("P" "up" org-metaup :transient t)
    608      ("N" "down" org-metadown :transient t)
    609      ("B" "left" org-shiftmetaleft :transient t)
    610      ("F" "right" org-shiftmetaright :transient t)
    611      ("b" "left (line)" org-metaleft :transient t)
    612      ("f" "right (line)" org-metaright :transient t)
    613      ("r" "refile" org-refile :transient t)]
    614     ["Change heading"
    615      :if org-at-heading-p
    616      ("*" "toggle" org-ctrl-c-star :if-not org-at-table-p :transient t)
    617      ("t" "todo" org-todo :transient t)
    618      ("q" "tags" org-set-tags-command :transient t :if-nil org-menu-use-q-for-quit)
    619      ("Q" "tags" org-set-tags-command :transient t :if-non-nil org-menu-use-q-for-quit)
    620      ("y" "property" org-set-property :transient t)
    621      ("," "priority" org-priority :transient t)
    622      ("A" "archive" org-menu-archive :transient t)
    623      ("D" "deadline" org-deadline :transient t)
    624      ("S" "schedule" org-schedule :transient t)
    625      ("/" "comment" org-toggle-comment :transient t)
    626      ("C-w" "cut tree" org-cut-special :transient t)
    627      ("C-y" "yank tree" org-paste-special :transient t)]
    628     ["Make new/delete"
    629      :if org-at-heading-p
    630      ("mh" "make heading (before)" org-insert-heading)
    631      ("mH" "make heading (after)" org-insert-heading-after-current)
    632      ("mt" "make todo (before)" org-insert-todo-heading)
    633      ("mT" "make todo (after)" org-menu-insert-todo-heading-after-current)
    634      ("dh" "delete heading" org-cut-subtree :transient t)
    635      ("dy" "delete property" org-delete-property :transient t)
    636      ("a" "attachments" org-menu-attachments :transient t)]
    637 
    638     ;; Items for tables
    639     ["Navigate"
    640      :if org-at-table-p
    641      ("p" "up" previous-line :transient t)
    642      ("n" "down" next-line :transient t)
    643      ("b" "left" org-table-previous-field :transient t)
    644      ("f" "right" org-table-next-field :transient t)
    645      ("u" "parent" outline-up-heading :transient t)
    646      ("M-w" "store link" org-store-link :transient t :if-not region-active-p)
    647      ("C-_" "undo" undo :transient t)]
    648     ["Move r/c"
    649      :if org-at-table-p
    650      ("P" "up" org-table-move-row-up :transient t)
    651      ("N" "down" org-table-move-row-down :transient t)
    652      ("B" "left" org-table-move-column-left :transient t)
    653      ("F" "right" org-table-move-column-right :transient t)]
    654     ["Field"
    655      :if org-at-table-p
    656      ("'" "edit" org-table-edit-field)
    657      ("SPC" "blank" org-table-blank-field :transient t)
    658      ("RET" "from above" org-table-copy-down :transient t)
    659      ("t" "text formatting" org-menu-text-in-element)]
    660     ["Formulas"
    661      :if org-at-table-p
    662      ("E" "edit all" org-table-edit-formulas :transient t)
    663      ("=" "field" (lambda () (interactive) (org-table-eval-formula '(4))) :transient t)
    664      ("+" "in place" (lambda () (interactive) (org-table-eval-formula '(16))))
    665      ("c" "column" org-table-eval-formula :transient t)
    666      ("h" "coordinates" org-table-toggle-coordinate-overlays :transient t)
    667      ("D" "debug" org-table-toggle-formula-debugger :transient t)]
    668     ["Table"
    669      :if org-at-table-p
    670      ("dr" "delete row" org-shiftmetaup :transient t)
    671      ("dc" "delete column" org-shiftmetaleft :transient t)
    672      ("m" "make" org-menu-insert-table)
    673      ,@(when (fboundp (function org-table-toggle-column-width))
    674          (list '("S" "shrink column" org-table-toggle-column-width :transient t)))
    675      ("r" "sort" org-table-sort-lines :transient t)
    676      ("M-w" "copy rect" org-table-copy-region :transient t :if region-active-p)
    677      ("C-w" "cut rect" org-table-cut-region :transient t :if region-active-p)
    678      ("C-y" "yank rect" org-table-paste-rectangle :transient t)]
    679 
    680     ;; Items for lists
    681     ["Navigate"
    682      :if org-in-item-p
    683      ("p" "prev" previous-line :transient t)
    684      ("n" "next" next-line :transient t)
    685      ("c" "cycle" org-cycle :transient t)
    686      ("u" "parent" org-up-element :transient t)
    687      ("M-p" "prev (same level)" org-backward-element :transient t)
    688      ("M-n" "next (same level)" org-forward-element :transient t)
    689      ("M-w" "store link" org-store-link :transient t :if-not region-active-p)
    690      ("C-_" "undo" undo :transient t)]
    691     ["Move list"
    692      :if org-in-item-p
    693      ("P" "up" org-metaup :transient t)
    694      ("N" "down" org-metadown :transient t)
    695      ("B" "left" org-shiftmetaleft :transient t)
    696      ("F" "right" org-shiftmetaright :transient t)
    697      ("b" "left (line)" org-metaleft :transient t)
    698      ("f" "right (line)" org-metaright :transient t)]
    699     ["List"
    700      :if org-in-item-p
    701      ("R" "repair" org-list-repair)
    702      ("*" "turn into tree" org-list-make-subtree)
    703      ("S" "sort" org-sort-list :transient t)
    704      ("t" "text formatting" org-menu-text-in-element)]
    705     ["Toggle"
    706      :if org-in-item-p
    707      ("-" "list item" org-toggle-item :if-not org-at-table-p :transient t)
    708      ("+" "list style" org-cycle-list-bullet :if-not org-at-table-p :transient t)
    709      ("d" "done" org-toggle-checkbox :transient t)
    710      ("m" "checkbox" org-menu-toggle-has-checkbox :transient t)]
    711 
    712     ;; Items for text
    713     ,@(org-menu-text-format-items t)
    714     ["Line"
    715      :if org-menu-at-text-p
    716      (":" "fixed width" org-toggle-fixed-width :transient t)
    717      (";" "comment" org-menu-comment-line :transient t)
    718      ("--" "list" org-toggle-item :transient t)
    719      ("-*" "heading" org-ctrl-c-star :transient t)]
    720 
    721     ;; Items for source blocks
    722     ,@(org-menu-eval-src-items)
    723 
    724     ["Link"
    725      :if org-menu-in-link
    726      ("e" "edit" org-insert-link :transient t)]
    727 
    728     ["Timestamp"
    729      :if org-menu-in-time-p
    730      ("." "type" org-toggle-timestamp-type :transient t)
    731      ("e" "edit" org-time-stamp :transient t)]
    732 
    733     ["Tasks"
    734      ("v" "visibility" org-menu-visibility)
    735      ("x" "evaluation" org-menu-eval)
    736      ("i" "insert" org-menu-insert)
    737      ("g" "go to" org-menu-goto)
    738      ("s" "search" org-menu-search-and-filter)
    739      ("o" "options" org-menu-options)
    740      ("C" "clock (active)" org-menu-clock :if org-clock-is-active)
    741      ("C" "clock" org-menu-clock :if-not org-clock-is-active)
    742      ,@(when (fboundp #'org-capture-finalize)
    743          (list '("C-c C-c" "confirm capture" org-capture-finalize :if-non-nil org-capture-mode)))
    744      ,@(when (fboundp #'org-capture-kill)
    745          (list '("C-c C-k" "abort capture" org-capture-kill :if-non-nil org-capture-mode)))
    746      ("" "" transient-noop)
    747      ("q" "quit" transient-quit-all :if-non-nil org-menu-use-q-for-quit)
    748      ]])
    749 
    750 (provide 'org-menu)
    751 ;;; org-menu.el ends here