Commit f25f5187e845

Vincent Demeester <vincent@sbr.pm>
2022-04-26 17:17:47
tools/emacs: add org-menu to load-path
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
1 parent 7a8aced
Changed files (1)
tools
emacs
tools/emacs/lisp/org-menu.el
@@ -0,0 +1,751 @@
+;;; org-menu.el --- A discoverable menu for org-mode using transient -*- lexical-binding: t; coding: utf-8 -*-
+;;
+;; Copyright 2021 Jan Rehders
+;;
+;; Author: Jan Rehders <nospam@sheijk.net>
+;; Version: 0.1alpha
+;; Package-Requires: ((emacs "26.1") (transient "0.1"))
+;; URL: https://github.com/sheijk/org-menu
+;;
+;; This file is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+;;
+;; This file is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to
+;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+;;
+;;; Commentary:
+;;
+;; Usage:
+;;
+;; Add this to your ~/.emacs to bind the menu to `C-c m':
+;;
+;; (with-eval-after-load 'org
+;;   (require 'org-menu) ;; not needed if installing by package manager
+;;   (define-key org-mode-map (kbd "C-c m") 'org-menu))
+;;
+;; The menu should be pretty self-explanatory.  It is context dependent and
+;; offers different commands for headlines, tables, timestamps, etc.
+;; The task menu provides entry points for task that work from anywhere.
+;;
+;;; Code:
+
+(require 'org)
+(require 'transient)
+
+(defgroup org-menu nil
+  "Options for org-menu"
+  :group 'org)
+
+(defcustom org-menu-use-q-for-quit t
+  "Whether to add a q binding to quit to all menus.
+
+Use this if you prefer to be consistent with magit.  It will also
+change some other bindings to use Q instead of q."
+  :group 'org-menu
+  :type 'boolean)
+
+(defcustom org-menu-global-toc-depth 10
+  "The number of heading levels to show when displaying the global content."
+  :group 'org-menu
+  :type 'integer)
+
+(defun org-menu-heading-navigate-items (check-for-heading &optional cycle-function)
+  "Items to navigate headings.
+
+These will be added to most sub menus.  If `CHECK-FOR-HEADING' is
+true the items will only be added if on a heading.  `CYCLE-FUNCTION' is the
+function to be used to cycle visibility of current element."
+  (setq cycle-function (or cycle-function #'org-cycle))
+  `(["Navigate"
+     ,@(when check-for-heading '(:if org-at-heading-p))
+     ("p" "prev" org-previous-visible-heading :transient t)
+     ("n" "next" org-next-visible-heading :transient t)
+     ("c" "cycle" ,cycle-function :transient t)
+     ("u" "parent" outline-up-heading :transient t)
+     ("M-p" "prev (same level)" org-backward-heading-same-level :transient t)
+     ("M-n" "next (same level)" org-forward-heading-same-level :transient t)
+     ("M-w" "store link" org-store-link :transient t :if-not region-active-p)
+     ("C-_" "undo" undo :transient t)]))
+
+(defun org-menu-show-headline-content ()
+  "Will show the complete content of the current headline and it's children."
+  (interactive)
+  (save-excursion
+    (outline-hide-subtree)
+    (org-show-children 4)
+    (org-goto-first-child)
+    (org-reveal '(4))))
+
+;;;###autoload (autoload 'org-menu-visibility "org-menu" nil t)
+(transient-define-prefix org-menu-visibility ()
+  "A menu to control visibility of org-mode items"
+  ["dummy"])
+
+(transient-insert-suffix 'org-menu-visibility (list 0)
+  `["Visibility"
+    ,@(org-menu-heading-navigate-items nil)
+    ["Visibility"
+     ("a" "all" org-show-subtree :if-not org-at-block-p :transient t)
+     ("a" "all" org-hide-block-toggle :if org-at-block-p :transient t)
+     ("t" "content" org-menu-show-headline-content :if-not org-at-block-p :transient t)
+     ("h" "hide" outline-hide-subtree :if-not org-at-block-p :transient t)
+     ("h" "hide" org-hide-block-toggle :if org-at-block-p :transient t)
+     ("r" "reveal" (lambda () (interactive) (org-reveal t)) :if-not org-at-block-p :transient t)]
+    ["Global"
+     ("C" "cycle global" org-global-cycle :transient t)
+     ("go" "overview" org-overview)
+     ("gt" "content" (lambda () (interactive) (org-content org-menu-global-toc-depth)))
+     ("ga" "all" org-show-all)
+     ("gd" "default" (lambda () (interactive) (org-set-startup-visibility)))]
+    ["Quit"
+     :if-non-nil org-menu-use-q-for-quit
+     ("q" "quit" transient-quit-all)]])
+
+(defun org-menu-eval-src-items ()
+  "Return the items to evaluate a source block."
+  (list
+   ["Source"
+    :if org-in-src-block-p
+    ("e" "run block" org-babel-execute-src-block)
+    ("c" "check headers" org-babel-check-src-block)
+    ("k" "clear results" org-babel-remove-result-one-or-many)
+    ("'" "edit" org-edit-special)]))
+
+;;;###autoload (autoload 'org-menu-eval "org-menu" nil t)
+(transient-define-prefix org-menu-eval ()
+  "A menu to evaluate buffers, tables, etc. in org-mode"
+  ["dummy"])
+
+(defun org-menu-run-gnuplot ()
+  "Will call `org-plot/gnuplot' and update inline images."
+  (interactive)
+  (org-plot/gnuplot)
+  (when org-inline-image-overlays
+    (org-redisplay-inline-images)))
+
+(transient-insert-suffix 'org-menu-eval (list 0)
+  `["Evaluation"
+    ["Table"
+     :if org-at-table-p
+     ("e" "table" (lambda () (interactive) (org-table-recalculate 'iterate)))
+     ("1" "one iteration" (lambda () (interactive) (org-table-recalculate t)))
+     ("l" "line" (lambda () (interactive) (org-table-recalculate nil)))
+     ("f" "format" org-table-align :if org-at-table-p)]
+    ,@(org-menu-eval-src-items)
+    ["Heading"
+     :if-not org-in-src-block-p
+     ("c" "update checkbox count" org-update-checkbox-count)]
+    ["Plot"
+     ("p" "gnuplot" org-menu-run-gnuplot)]
+    ["Quit"
+     :if-non-nil org-menu-use-q-for-quit
+     ("q" "quit" transient-quit-all)]])
+
+(defun org-menu-insert-block (str)
+  "Insert an org mode block of type `STR'."
+  (interactive)
+  (insert (format "#+begin_%s\n#+end_%s\n" str str)))
+
+(defun org-menu-expand-snippet (snippet)
+  "Will expand the given snippet named `SNIPPET'."
+  (interactive)
+  (if (require 'yasnippet nil 'noerror)
+      (progn
+        (insert snippet)
+        (yas-expand))
+    (message "error: yasnippet not installed, could not expand %s" snippet)))
+
+;;;###autoload (autoload 'org-menu-insert-blocks "org-menu" nil t)
+(transient-define-prefix org-menu-insert-blocks ()
+  "A menu to insert new blocks in org-mode"
+  [["Insert block"
+    ("s" "source" (lambda () (interactive) (org-menu-insert-block "src")))
+    ("e" "example" (lambda () (interactive) (org-menu-insert-block "example")))
+    ("v" "verbatim" (lambda () (interactive) (org-menu-insert-block "verbatim")))
+    ("a" "ascii" (lambda () (interactive) (org-menu-insert-block "ascii")))
+    ("q" "quote" (lambda () (interactive) (org-menu-insert-block "quote")) :if-nil org-menu-use-q-for-quit)
+    ("Q" "quote" (lambda () (interactive) (org-menu-insert-block "quote")) :if-non-nil org-menu-use-q-for-quit)
+    ("d" "dynamic block" org-dynamic-block-insert-dblock)]
+   ["Quit"
+    :if-non-nil org-menu-use-q-for-quit
+    ("q" "quit" transient-quit-all)]])
+
+;;;###autoload (autoload 'org-menu-insert-heading "org-menu" nil t)
+(transient-define-prefix org-menu-insert-heading ()
+  "A menu to insert new headings in org-mode"
+  [["Heading"
+    ("h" "heading" org-insert-heading)
+    ("H" "heading (after)" org-insert-heading-after-current)
+    ("T" "todo" org-insert-todo-heading)]
+   ["Items"
+    ("d" "drawer" org-insert-drawer)]
+   ["Quit"
+    :if-non-nil org-menu-use-q-for-quit
+    ("q" "quit" transient-quit-all)]])
+
+;;;###autoload (autoload 'org-menu-insert-template "org-menu" nil t)
+(transient-define-prefix org-menu-insert-template ()
+  "A menu to insert new templates in org-mode"
+  [["Templates"
+    ("S" "structure template" org-insert-structure-template)
+    ("B" "yas blocks" (lambda () (interactive) (org-menu-expand-snippet "beg")))
+    ("O" "yas options" (lambda () (interactive) (org-menu-expand-snippet "opt")))]
+   ["Quit"
+    :if-non-nil org-menu-use-q-for-quit
+    ("q" "quit" transient-quit-all)]])
+
+;;;###autoload (autoload 'org-menu-insert-timestamp "org-menu" nil t)
+(transient-define-prefix org-menu-insert-timestamp ()
+  "A menu to insert timestamps in org-mode"
+  [["Timestamp"
+    ("." "active" org-time-stamp)
+    ("!" "inactive" org-time-stamp-inactive)]
+   ["Now"
+    ("n" "active" (lambda () (interactive) (org-insert-time-stamp (current-time) t)))
+    ("N" "inactive" (lambda () (interactive) (org-insert-time-stamp (current-time) t t)))]
+   ["Today"
+    ("t" "active" (lambda () (interactive) (org-insert-time-stamp (current-time) nil)))
+    ("T" "inactive" (lambda () (interactive) (org-insert-time-stamp (current-time) nil t)))]
+   ["Quit"
+    :if-non-nil org-menu-use-q-for-quit
+    ("q" "quit" transient-quit-all)]])
+
+(defun org-menu-table-insert-row-below ()
+  "Insert a new table column below point."
+  (interactive)
+  (org-table-insert-row '4))
+
+(defun org-menu-table-insert-column-left ()
+  "Insert a new column to the left of point."
+  (interactive)
+  (org-table-insert-column)
+  (org-table-move-column-right))
+
+;;;###autoload (autoload 'org-menu-insert-table "org-menu" nil t)
+(transient-define-prefix org-menu-insert-table ()
+  "A menu to insert table items in org-mode"
+  [["Table"
+    ("t" "table" org-table-create-or-convert-from-region :if-not org-at-table-p)
+    ("i" "import" org-table-import :if-not org-at-table-p)]
+   ["Rows/columns"
+    :if org-at-table-p
+    ("r" "row above" org-table-insert-row :transient t)
+    ("R" "row below" org-menu-table-insert-row-below :transient t)
+    ("c" "column right" org-table-insert-column :transient t)
+    ("C" "column left" org-menu-table-insert-column-left :transient t)
+    ("-" "horiz. line" org-table-insert-hline :transient t)]
+   ["Quit"
+    :if-non-nil org-menu-use-q-for-quit
+    ("q" "quit" transient-quit-all)]])
+
+(defun org-menu-insert-superscript ()
+  "Insert a text with superscript."
+  (interactive)
+  (if (require 'yasnippet nil 'noerror)
+      (yas-expand-snippet "${1:text}^{${2:super}}")
+    (insert "a^b")))
+
+(defun org-menu-insert-subscript ()
+  "Insert a text with subscript."
+  (interactive)
+  (if (require 'yasnippet nil 'noerror)
+      (yas-expand-snippet "${1:text}_{${2:sub}}")
+    (insert "a_b")))
+
+(defun org-menu-parse-formatting (format-char)
+  "Will return the bounds of the format markup `FORMAT-CHAR'."
+  (let ((original-point (point))
+        start end)
+    (ignore-errors
+      (save-excursion
+        (save-restriction
+          (save-match-data
+            (org-narrow-to-element)
+            (goto-char (search-backward (format "%c" format-char)))
+            (setq start (point))
+            (goto-char original-point)
+            (goto-char (search-forward (format "%c" format-char)))
+            (setq end (point))
+            (cons start end)))))))
+
+(defun org-menu-toggle-format (format-char)
+  "Will add/remove the given format wrapped in `FORMAT-CHAR' form the region (or point)."
+  (let ((range (org-menu-parse-formatting format-char))
+        (format-string (format "%c" format-char)))
+    (if (null range)
+        (org-menu-insert-text format-string format-string t)
+      (goto-char (cdr range))
+      (delete-backward-char 1)
+      (goto-char (car range))
+      (delete-char 1))))
+
+;;;###autoload (autoload 'org-menu-insert-list "org-menu" nil t)
+(transient-define-prefix org-menu-insert-list ()
+  "A menu to insert lists"
+  [["List"
+    ("-" "item" (lambda () (interactive) (insert "- ")))
+    ("+" "+" (lambda () (interactive) (insert "+ ")))
+    ("*" "*" (lambda () (interactive) (insert "* ")))
+    ("." "1." (lambda () (interactive) (insert "1. ")))
+    (")" "1)" (lambda () (interactive) (insert "1) ")))]
+   ["Todo"
+    ("t" "todo" (lambda () (interactive) (insert "- [ ] ")))
+    ("d" "done" (lambda () (interactive) (insert "- [X] ")))
+    ("p" "partial" (lambda () (interactive) (insert "- [-] ")))]
+   ["Quit"
+    :if-non-nil org-menu-use-q-for-quit
+    ("q" "quit" transient-quit-all)]])
+
+(defun org-menu-insert-plot ()
+  "Insert a small example plot for `gnu-plot'."
+  (interactive)
+  (beginning-of-line 1)
+  (if (require 'yasnippet nil 'noerror)
+      (yas-expand-snippet
+       "#+plot: type:${1:2d} file:\"${2:plot.svg}\"
+| A |  B |
+|---+----|
+| 1 | 10 |
+| 2 |  8 |
+| 3 |  9 |
+
+#+attr_org: :width ${3:400px}
+[[file:$2]]
+")
+    (insert
+     "#+plot: type:2d file:\"plot.svg\"
+| A |  B |
+|---+----|
+| 1 | 10 |
+| 2 |  8 |
+| 3 |  9 |
+
+#+attr_org: :width 400px
+[[file:plot.svg]]
+")))
+
+;;;###autoload (autoload 'org-menu-insert "org-menu" nil t)
+(transient-define-prefix org-menu-insert ()
+  "A menu to insert new items in org-mode"
+  [["Insert"
+    ("." "time" org-menu-insert-timestamp)
+    ("t" "table" org-menu-insert-table)
+    ("h" "heading" org-menu-insert-heading)
+    ("b" "block" org-menu-insert-blocks)
+    ("T" "templates" org-menu-insert-template)
+    ("l" "link (new)" org-insert-link)
+    ("L" "link (stored)" org-insert-last-stored-link :transient t)
+    ("-" "list" org-menu-insert-list)
+    ("p" "plot" org-menu-insert-plot)]
+   ["Format"
+    ("^" "superscript" org-menu-insert-superscript)
+    ("_" "subscript" org-menu-insert-subscript)]
+   ["Quit"
+    :if-non-nil org-menu-use-q-for-quit
+    ("q" "quit" transient-quit-all)]])
+
+(defun org-menu-comment-line ()
+  "Toggle line comment w/o moving cursor."
+  (interactive)
+  (save-excursion (comment-line 1)))
+
+(defun org-menu-insert-text (left right &optional surround-whitespace)
+  "Will insert left|right and put the curser at |.
+
+If region is active it will be surrounded by `LEFT' and `RIGHT' and
+the point will be at end of region.  Will add spaces before/after text if
+`SURROUND-WHITESPACE' is true and it's needed."
+
+  (let ((start (point))
+        (end (point)))
+    (when (region-active-p)
+      (setq start (region-beginning)
+            end (region-end))
+      (deactivate-mark))
+    (when (> start end)
+      ;; swap variables w/o importing cl-lib
+      (setq start (prog1 end (setq end start))))
+
+    (goto-char start)
+    (when (and surround-whitespace
+               (not (bolp))
+               (not (looking-back " +")))
+      (insert " "))
+    (insert left)
+
+    (forward-char (- end start))
+
+    (save-excursion
+      (insert right)
+      (when (and surround-whitespace
+                 (not (eolp))
+                 (not (looking-at " +")))
+        (insert " ")))))
+
+(defun org-menu-in-time-p ()
+  "Return whether we're at a time stamp or similar.
+
+Adapted from `org-goto-calendar'"
+  (or (org-at-timestamp-p 'lax)
+      (org-match-line (concat ".*" org-ts-regexp))))
+
+;;;###autoload (autoload 'org-menu-goto "org-menu" nil t)
+(transient-define-prefix org-menu-goto ()
+  "Menu to go to different places by name"
+  [["Go to"
+    ("h" "heading" imenu)
+    ("s" "source block" org-babel-goto-named-src-block)
+    ("r" "result block" org-babel-goto-named-result)
+    ("." "calendar" org-goto-calendar :if org-menu-in-time-p)]
+   ["Quit"
+    :if-non-nil org-menu-use-q-for-quit
+    ("q" "quit" transient-quit-all)]])
+
+(defun org-menu-at-text-p ()
+  "Return whether point is at text."
+  (not (or (org-at-heading-p)
+           (org-at-table-p)
+           (org-in-item-p)
+           (org-in-src-block-p))))
+
+(defun org-menu-text-format-items (check-for-table)
+  "Items to format text.
+
+Will add an ':if org-menu-at-text-p' criteria if `CHECK-FOR-TABLE' is true."
+  (list
+   `["Navigate"
+     ,@(when check-for-table '(:if org-menu-at-text-p))
+     ("p" "up" previous-line :transient t)
+     ("n" "down" next-line :transient t)
+     ("b" "left" backward-word :transient t)
+     ("f" "right" forward-word :transient t)
+     ("u" "parent" org-up-element :transient t)
+     ("M-w" "store link" org-store-link :transient t :if-not region-active-p)
+     ("C-_" "undo" undo :transient t)
+     ("SPC" "mark" set-mark-command :transient t)
+     ("C-x C-x" "exchange" exchange-point-and-mark :transient t)]
+   `["Formatting"
+     ,@(when check-for-table '(:if org-menu-at-text-p))
+     ("*" "Bold" (lambda nil (interactive) (org-menu-toggle-format ?*)) :transient t)
+     ("/" "italic" (lambda nil (interactive) (org-menu-toggle-format ?/)) :transient t)
+     ("_" "underline" (lambda nil (interactive) (org-menu-toggle-format ?_)) :transient t)
+     ("+" "strikethrough" (lambda nil (interactive) (org-menu-toggle-format ?+)) :transient t)]
+   `["Source"
+     ,@(when check-for-table '(:if org-menu-at-text-p))
+     ("~" "code" (lambda nil (interactive) (org-menu-toggle-format ?~)) :transient t)
+     ("=" "verbatim" (lambda nil (interactive) (org-menu-toggle-format ?=)) :transient t)]))
+
+;;;###autoload (autoload 'org-menu-text-in-element "org-menu" nil t)
+(transient-define-prefix org-menu-text-in-element ()
+  "Add formatting for text inside other elements like lists and tables"
+  ["dummy"])
+
+(transient-insert-suffix 'org-menu-text-in-element (list 0)
+  `[,@(org-menu-text-format-items nil)
+    ["Quit"
+     :if-non-nil org-menu-use-q-for-quit
+     ("q" "quit" transient-quit-all)]])
+
+;;;###autoload (autoload 'org-menu-options "org-menu" nil t)
+(transient-define-prefix org-menu-options ()
+  "A menu to toggle options"
+  [["Display"
+    ("l" "show links" org-toggle-link-display)
+    ("i" "inline images" org-toggle-inline-images)
+    ("p" "pretty entities" org-toggle-pretty-entities)
+    ("I" "indent by level" org-indent-mode)
+    ("t" "timestamp overlay" org-toggle-time-stamp-overlays)
+    ("n" "numbered headings" org-num-mode)]
+   ["Quit"
+    :if-non-nil org-menu-use-q-for-quit
+    ("q" "quit" transient-quit-all)]])
+
+(defun org-menu-in-link ()
+  "Return whether we are inside a link.
+
+Conditions have been adapted from `org-insert-link'"
+  (or
+   ;; Use variable from org-compat to support Emacs 26
+   (org-in-regexp org-bracket-link-regexp 1)
+   (when (boundp 'org-link-angle-re)
+     (org-in-regexp org-link-angle-re))
+   (when (boundp 'org-link-plain-re)
+     (org-in-regexp org-link-plain-re))))
+
+(defun org-menu-toggle-has-checkbox ()
+  "Toggle whether the current list item has a checkbox."
+  (interactive)
+  (save-excursion
+    (back-to-indentation)
+    (if (not (looking-at "- "))
+        (message "Not at list item")
+      (end-of-line 1)
+      (org-ctrl-c-ctrl-c '(4)))))
+
+(defun org-menu-is-timer-running ()
+  "Return whether a timer is currently running."
+  (and org-timer-start-time
+       (not org-timer-countdown-timer)
+       (not org-timer-pause-time)))
+
+(defun org-menu-is-timer-paused ()
+  "Return whether a timer has been started and is paused."
+  (and org-timer-start-time
+       (not org-timer-countdown-timer)
+       org-timer-pause-time))
+
+;;;###autoload (autoload 'org-menu-clock "org-menu" nil t)
+(transient-define-prefix org-menu-clock ()
+  "Time management using org-modes clock"
+  [["Clock"
+    ("<tab>" "in" org-clock-in :if-not org-clock-is-active)
+    ("TAB" "in" org-clock-in :if-not org-clock-is-active)
+    ("o" "out" org-clock-out :if org-clock-is-active)
+    ("j" "goto" org-clock-goto :if org-clock-is-active)
+    ("q" "cancel" org-clock-cancel
+     :if (lambda () (and (not org-menu-use-q-for-quit)
+                         (org-clock-is-active))))
+    ("Q" "cancel" org-clock-cancel
+     :if (lambda () (and org-menu-use-q-for-quit
+                         (org-clock-is-active))))
+    ("d" "display" org-clock-display :if org-clock-is-active)
+    ("x" "in again" org-clock-in-last :if-not org-clock-is-active)
+    ("z" "resolve" org-resolve-clocks)]
+   ["Timer"
+    ("0" "start" org-timer-start :if-nil org-timer-start-time)
+    ("_" "stop" org-timer-stop :if-non-nil org-timer-start-time)
+    ("." "insert" org-timer :if-non-nil org-timer-start-time)
+    ("-" "... item" org-timer-item :if-non-nil org-timer-start-time)
+    ("," "pause" org-timer-pause-or-continue :if org-menu-is-timer-running)
+    ("," "continue" org-timer-pause-or-continue :if org-menu-is-timer-paused)
+    (";" "countdown" org-timer-set-timer :if-nil org-timer-start-time)]
+   ["Quit"
+    :if-non-nil org-menu-use-q-for-quit
+    ("q" "quit" transient-quit-all)]])
+
+(transient-define-prefix org-menu-search-and-filter ()
+  "A menu to search and filter org-mode documents"
+  ["Search and filter"
+   ["Filter"
+    ("/" "only matching" org-sparse-tree)
+    ("q" "tags" org-tags-sparse-tree :if-nil org-menu-use-q-for-quit)
+    ("Q" "tags" org-tags-sparse-tree :if-non-nil org-menu-use-q-for-quit)
+    ("t" "todos" org-show-todo-tree)
+    ("d" "deadlines" org-check-deadlines)
+    ("b" "before date" org-check-before-date)
+    ("a" "after date" org-check-after-date)
+    ("D" "dates range" org-check-dates-range)]
+   ["Agenda"
+    ("A" "open" org-agenda)]
+   ["Quit"
+    :if-non-nil org-menu-use-q-for-quit
+    ("q" "quit" transient-quit-all)]])
+
+(transient-define-prefix org-menu-attachments ()
+  "A menu to manage attachments"
+  ["Attachments"
+   ["Add"
+    ("a" "file" org-attach-attach)
+    ("c" "copy" org-attach-attach-cp)
+    ("m" "move" org-attach-attach-mv)
+    ("l" "link" org-attach-attach-ln)
+    ("y" "symlink" org-attach-attach-lns)
+    ("u" "download" org-attach-url)
+    ("b" "buffer" org-attach-buffer)
+    ("n" "new" org-attach-new)]
+   ["Open"
+    ("o" "attachment" org-attach-open)
+    ("O" "in Emacs" org-attach-open-in-emacs)
+    ("f" "directory" org-attach-reveal)
+    ("F" "in Emacs" org-attach-reveal-in-emacs)]
+   ["Delete"
+    ("d" "delete" org-attach-delete-one)
+    ("D" "all" org-attach-delete-all)]
+   ["More"
+    ("s" "set directory" org-attach-set-directory)
+    ("S" "unset" org-attach-unset-directory)
+    ("z" "synchronize" org-attach-sync)]])
+
+(transient-define-prefix org-menu-archive ()
+  "A menu to archive items"
+  ["dummy"])
+
+(transient-insert-suffix 'org-menu-archive (list 0)
+  `["Archive"
+    ,@(org-menu-heading-navigate-items nil #'org-force-cycle-archived)
+    ["Archive to"
+     ("t" "tree" org-archive-subtree :transient t)
+     ("s" "sibling" org-archive-to-archive-sibling :transient t)
+     ("Q" "tag" org-toggle-archive-tag :transient t)]])
+
+(defun org-menu-insert-todo-heading-after-current ()
+  "Insert a new todo heading with same level as current, after subtree."
+  (interactive)
+  (org-insert-todo-heading '(16)))
+
+;;;###autoload (autoload 'org-menu "org-menu" nil t)
+(transient-define-prefix org-menu ()
+  "A discoverable menu to edit and view org-mode documents"
+  ["dummy"])
+
+(transient-insert-suffix 'org-menu (list 0)
+  `["Org mode"
+    ;; Items for headings
+    ,@(org-menu-heading-navigate-items t)
+
+    ["Move heading"
+     :if org-at-heading-p
+     ("P" "up" org-metaup :transient t)
+     ("N" "down" org-metadown :transient t)
+     ("B" "left" org-shiftmetaleft :transient t)
+     ("F" "right" org-shiftmetaright :transient t)
+     ("b" "left (line)" org-metaleft :transient t)
+     ("f" "right (line)" org-metaright :transient t)
+     ("r" "refile" org-refile :transient t)]
+    ["Change heading"
+     :if org-at-heading-p
+     ("*" "toggle" org-ctrl-c-star :if-not org-at-table-p :transient t)
+     ("t" "todo" org-todo :transient t)
+     ("q" "tags" org-set-tags-command :transient t :if-nil org-menu-use-q-for-quit)
+     ("Q" "tags" org-set-tags-command :transient t :if-non-nil org-menu-use-q-for-quit)
+     ("y" "property" org-set-property :transient t)
+     ("," "priority" org-priority :transient t)
+     ("A" "archive" org-menu-archive :transient t)
+     ("D" "deadline" org-deadline :transient t)
+     ("S" "schedule" org-schedule :transient t)
+     ("/" "comment" org-toggle-comment :transient t)
+     ("C-w" "cut tree" org-cut-special :transient t)
+     ("C-y" "yank tree" org-paste-special :transient t)]
+    ["Make new/delete"
+     :if org-at-heading-p
+     ("mh" "make heading (before)" org-insert-heading)
+     ("mH" "make heading (after)" org-insert-heading-after-current)
+     ("mt" "make todo (before)" org-insert-todo-heading)
+     ("mT" "make todo (after)" org-menu-insert-todo-heading-after-current)
+     ("dh" "delete heading" org-cut-subtree :transient t)
+     ("dy" "delete property" org-delete-property :transient t)
+     ("a" "attachments" org-menu-attachments :transient t)]
+
+    ;; Items for tables
+    ["Navigate"
+     :if org-at-table-p
+     ("p" "up" previous-line :transient t)
+     ("n" "down" next-line :transient t)
+     ("b" "left" org-table-previous-field :transient t)
+     ("f" "right" org-table-next-field :transient t)
+     ("u" "parent" outline-up-heading :transient t)
+     ("M-w" "store link" org-store-link :transient t :if-not region-active-p)
+     ("C-_" "undo" undo :transient t)]
+    ["Move r/c"
+     :if org-at-table-p
+     ("P" "up" org-table-move-row-up :transient t)
+     ("N" "down" org-table-move-row-down :transient t)
+     ("B" "left" org-table-move-column-left :transient t)
+     ("F" "right" org-table-move-column-right :transient t)]
+    ["Field"
+     :if org-at-table-p
+     ("'" "edit" org-table-edit-field)
+     ("SPC" "blank" org-table-blank-field :transient t)
+     ("RET" "from above" org-table-copy-down :transient t)
+     ("t" "text formatting" org-menu-text-in-element)]
+    ["Formulas"
+     :if org-at-table-p
+     ("E" "edit all" org-table-edit-formulas :transient t)
+     ("=" "field" (lambda () (interactive) (org-table-eval-formula '(4))) :transient t)
+     ("+" "in place" (lambda () (interactive) (org-table-eval-formula '(16))))
+     ("c" "column" org-table-eval-formula :transient t)
+     ("h" "coordinates" org-table-toggle-coordinate-overlays :transient t)
+     ("D" "debug" org-table-toggle-formula-debugger :transient t)]
+    ["Table"
+     :if org-at-table-p
+     ("dr" "delete row" org-shiftmetaup :transient t)
+     ("dc" "delete column" org-shiftmetaleft :transient t)
+     ("m" "make" org-menu-insert-table)
+     ,@(when (fboundp (function org-table-toggle-column-width))
+         (list '("S" "shrink column" org-table-toggle-column-width :transient t)))
+     ("r" "sort" org-table-sort-lines :transient t)
+     ("M-w" "copy rect" org-table-copy-region :transient t :if region-active-p)
+     ("C-w" "cut rect" org-table-cut-region :transient t :if region-active-p)
+     ("C-y" "yank rect" org-table-paste-rectangle :transient t)]
+
+    ;; Items for lists
+    ["Navigate"
+     :if org-in-item-p
+     ("p" "prev" previous-line :transient t)
+     ("n" "next" next-line :transient t)
+     ("c" "cycle" org-cycle :transient t)
+     ("u" "parent" org-up-element :transient t)
+     ("M-p" "prev (same level)" org-backward-element :transient t)
+     ("M-n" "next (same level)" org-forward-element :transient t)
+     ("M-w" "store link" org-store-link :transient t :if-not region-active-p)
+     ("C-_" "undo" undo :transient t)]
+    ["Move list"
+     :if org-in-item-p
+     ("P" "up" org-metaup :transient t)
+     ("N" "down" org-metadown :transient t)
+     ("B" "left" org-shiftmetaleft :transient t)
+     ("F" "right" org-shiftmetaright :transient t)
+     ("b" "left (line)" org-metaleft :transient t)
+     ("f" "right (line)" org-metaright :transient t)]
+    ["List"
+     :if org-in-item-p
+     ("R" "repair" org-list-repair)
+     ("*" "turn into tree" org-list-make-subtree)
+     ("S" "sort" org-sort-list :transient t)
+     ("t" "text formatting" org-menu-text-in-element)]
+    ["Toggle"
+     :if org-in-item-p
+     ("-" "list item" org-toggle-item :if-not org-at-table-p :transient t)
+     ("+" "list style" org-cycle-list-bullet :if-not org-at-table-p :transient t)
+     ("d" "done" org-toggle-checkbox :transient t)
+     ("m" "checkbox" org-menu-toggle-has-checkbox :transient t)]
+
+    ;; Items for text
+    ,@(org-menu-text-format-items t)
+    ["Line"
+     :if org-menu-at-text-p
+     (":" "fixed width" org-toggle-fixed-width :transient t)
+     (";" "comment" org-menu-comment-line :transient t)
+     ("--" "list" org-toggle-item :transient t)
+     ("-*" "heading" org-ctrl-c-star :transient t)]
+
+    ;; Items for source blocks
+    ,@(org-menu-eval-src-items)
+
+    ["Link"
+     :if org-menu-in-link
+     ("e" "edit" org-insert-link :transient t)]
+
+    ["Timestamp"
+     :if org-menu-in-time-p
+     ("." "type" org-toggle-timestamp-type :transient t)
+     ("e" "edit" org-time-stamp :transient t)]
+
+    ["Tasks"
+     ("v" "visibility" org-menu-visibility)
+     ("x" "evaluation" org-menu-eval)
+     ("i" "insert" org-menu-insert)
+     ("g" "go to" org-menu-goto)
+     ("s" "search" org-menu-search-and-filter)
+     ("o" "options" org-menu-options)
+     ("C" "clock (active)" org-menu-clock :if org-clock-is-active)
+     ("C" "clock" org-menu-clock :if-not org-clock-is-active)
+     ,@(when (fboundp #'org-capture-finalize)
+         (list '("C-c C-c" "confirm capture" org-capture-finalize :if-non-nil org-capture-mode)))
+     ,@(when (fboundp #'org-capture-kill)
+         (list '("C-c C-k" "abort capture" org-capture-kill :if-non-nil org-capture-mode)))
+     ("" "" transient-noop)
+     ("q" "quit" transient-quit-all :if-non-nil org-menu-use-q-for-quit)
+     ]])
+
+(provide 'org-menu)
+;;; org-menu.el ends here