emacs-orgmode@gnu.org archives
 help / color / mirror / code / Atom feed
From: Arthur Miller <arthur.miller@live.com>
To: Max Nikulin <manikulin@gmail.com>
Cc: emacs-orgmode@gnu.org, Ihor Radchenko <yantar92@gmail.com>
Subject: Re: Proposal: 'executable' org-capture-templaes
Date: Wed, 22 Jun 2022 14:13:51 +0200	[thread overview]
Message-ID: <AM9PR09MB49771CF015DAECBF3E5F955E96B29@AM9PR09MB4977.eurprd09.prod.outlook.com> (raw)
In-Reply-To: <t8sp8o$472$1@ciao.gmane.io> (Max Nikulin's message of "Tue, 21 Jun 2022 22:48:18 +0700")

[-- Attachment #1: Type: text/plain, Size: 6581 bytes --]

Max Nikulin <manikulin@gmail.com> writes:

> On 21/06/2022 11:07, Ihor Radchenko wrote:
>> Max Nikulin writes:
>> The other question is altering the org-capture code.
>> I think that it is too early to discuss org-capture part just yet.
>> Lets first finalize the generic library itself and discuss the
>> appropriate adjustments to
>> org-capture/org-agenda/org-export/org-todo/etc code later.
>
> From my point of view changing menu without adjusting calling logic is a kind of
> trade-off with uncertain net result. New UI may significantly affect different
> user expectations. It seems this is the point of our disagreement. You tend to
> consider menu in isolation trying to postpone the question concerning
> interaction of code running before menu creation and handlers of menu items
> (they are not belong to the same call stack any more).
>
> What I am really afraid is words like:
>
>> Sure. That somewhere can be buffer-local variable inside the capture
>> menu buffer. Or global variable. Or closure. How is it relevant to the
>> capture menu?

Yepp, it can't saved in a buffer-local before the buffer is created.

> I am trying to avoid implementation of menu that would make passing state to
> handlers unreliable or prohibitively complicated.

Menu should and application should be separated in my eyes. Menu is just a
graphical/audio? presentation of selectable choice to the user. As such it
should display choices, let user pick a choice, and return to the application
the picked choice. Compare to completing-read, or org-mks. I don't think we
should mingle application states and menu states.

However, there is a slight complication compared to completing-read or org-mks
in regard that those are used to make only one selection and exit, while this
menu is supposed to be used that way, or to be used in a way that it survives
multiple selections.

To me this seems as clearly per menu entry behaviour. For exmaple in org-agenda
menu, some actions will make menu exit, while some will keep the menu alive (for
example '*'). One solution is to have some flag passed per entry to signal to
the menu to kill itself upon selection. The other one, the easier, is to let the
applicaiton code handle the menu kill. I took the second one to minimize on
number of flags that needs to be passed and documented, but I agree it is not
the cleanest way, so I can change to use a flag if it is considered a better
option.

> Global variables are certainly not an option because such approach does not
> allow several instances of menu created in parallel.

Yes, please, lets try to minimize number of globals. Actually I would be
happiest with a functional approach where everything is communicated via
function arguments and return values.

> To use buffer-local variables some support from the side of menu library is
> required. Buffer does not exist when menu function is called. So either some
> callback should be passed to menu to be invoked when a new buffer is created for
> menu or that buffer should be returned from the menu creating function. Unsure
> if (current-buffer) would be a reliable approach.
>
> Closures should work, but would be it convenient enough?

We are using list to pass both menus and menu entries. Application can stick in
some args used by the menu. 

> Maybe some intermediate layer should be introduced to make alternative
> implementation of menu (transient, completing read, etc.) easier.

I don't think it is the topic and scope of this menu. It would be another layer
on its own, and definitely another project, I think.

> I believe, discussing design of menu without evaluation if it is suitable to
> features that should use it (capture, agenda, etc.), there is a risk to create
> unreasonable restrictions on further steps.

I definitely agree. Designing a good API is hard. I personally always think that
frameworks should not be designed in advance. I am more of a pragmatic, and
prefer to see actual working use-cases and design a framework after the
experience, not try to desing a framework by guessing the potential
use-cases. That is why I said that I am not interested in turning this into a
framework. I have try to design here something that can be used to implement
existing org-capture, org-agenda & co on top of it.

> That is why I consider the following aspects of menu design as essential ones:
> - A clearly determined way to pass some state from a function that requests
>  creation of menu to handlers of menu items.

Menu entries (lists)

> - When a non-blocking technique to wait user decision is used, multiple
>   instances of the same menu should be allowed.

Still have to be worked on.

> - Defined extension points for alternative implementations to avoid code
>   duplication and divergence of behavior.

That would be some framework layer.

> My fear is that if such points are ignored in the beginning, it may become
> impossible later.

It's lisp, anything is possible ^^ ;-).

No, but serioiusly, there is not much of extension points here. It is a keymap
and buffer text generated automatically from a list of templates and few
"visual" options. An application can even discard entire buffer and draw its own
text out of templates as it pleases, it wouldn't matter.

My goal was to create something very simple to use from a programming
perspective. Compared to other similar options like make-help-screen and org-mks
this puts both key handling, actions and labels into same spot, so there is only
one place to maintain later on, and client code needs needs just to specify the
most minimal lisp one is interested to execute, ie. I don't need to create
lambdas if some function is not already defined. Sort of, that is how at least I
would like to use it. I don't know if that is good or bad practice, or how
robust the code is, I am not super familiar with lisp and org-mode to be honest.

Attached is another experiment. In this version each entry can have attached
handler, and I provide two simple handers: run-once and run-multiple times. For
applications that need more advanced handling, they are supposed to provide own
handler and to take care of killing the menu buffer.

I do have thoughts to rework the drawing code once more to allow entries in form
of: (:separator sepeartor-string), similar as it takes group entries for
submenus. It would allow for mixing horizontal and vertical layouts, but I am
not sure if that is a good feature to have. It certainly isn't needed.

I have reworked a bit org-capture, but I haven't yet worked on org-agenda
restrictions and other details.


[-- Attachment #2: org-select.el --]
[-- Type: text/plain, Size: 24253 bytes --]

;;; org-select.el --- Build custom menus from declarative templates  -*- lexical-binding: t; -*-

;; Copyright (C) 2022  Arthur Miller

;; Author: Arthur Miller <arthur.miller@live.com>
;; Keywords: tools

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;; Org-select is a selection framework meant to be simple and easy to use. Its
;; job is to display a list of choices for a user to pick from and to hand in
;; the selection to the client application. Org-select can be used to allow
;; either a single selection at a time, or for repeated selections from a
;; menu-like text-buffer. 

;; One of goals with this framework is to be easy to setup for the client
;; code. For that reason, org-select uses simple template language modeled after
;; org-capture templates.

;;; Code:
\f
(require 'org-macs)

;;; User vars
\f
(defgroup org-select nil
  "Create menus from declarative templates."
  :prefix "org-select-"
  :prefix "osl--"
  :tag "Org Select"
  :group 'org)

(defcustom org-select-back-key [f10]
  "Used to render string for the horizontal separator."
  :type 'character
  :group 'org-select)

(defcustom org-select-horizontal-separator "|"
  "Used to render string for the horizontal separator."
  :type 'string
  :group 'org-select)

(defcustom org-select-vertical-separator "-"
  "Used to render string for the vetical separator."
  :type 'string
  :group 'org-select)

(defcustom org-select-key-decorator-chars ""
  "Characters used to decorate shortcut keys.

This string should contain only two characters, the first one for the left
decorator and the second one for the right decorator.

Example: string \"[]\" will render key \"C\" as \"[C]\"."
  :type 'string
  :group 'org-select)

(defcustom org-select-label-decorators (cons "..." "...")
  "Used to render string for the vetical separator."
  :type 'cons
  :group 'org-select)
\f
;;; Implementation
\f
(defvar-local osl--init nil)
(defvar-local osl--args nil)
(defvar-local osl--buffer nil)
(defvar-local osl--menu-begin nil)
(defvar-local osl--buffer-menu nil)
(defvar-local osl--longest-label 0)
(defvar-local osl--buffer-window nil)
(defvar-local org-select-mode-map nil)
(defvar-local osl--horizontal-layout nil)
(defvar-local osl--current-menu-column nil)

(defvar-local osl--handler-fn nil
  "The handler invoked when per-menu handler is not specified.

The default one is org-select-run-once.")

(define-minor-mode org-select-mode ""
  :interactive nil :global nil)

;;;; Help-functions
(defun osl--arg (key)
  (plist-get osl--args key))

(defun osl--init ()
  (buffer-local-value 'osl--init (current-buffer)))

(defun osl--prop (property list)
  "Return value of PROPERTY from irregular plist LIST."
  (cadr (member property list)))

(defun osl--ignore-key ()
  (interactive)
  (message "Invalid key %S"
           ;; I am not happy but it works somewhat
           (edmacro-format-keys (vector last-input-event))))

(defun org-select-quit (&optional abort-message buffer-or-name)
  "Callback to quit an org-select buffer.

If given, and optional ABORT-MESSAGE will be printed instead of the default
one. BUFFER-NAME can be used to quit org-select mode from a non org-select
buffer."
  (interactive)
  (let ((window (if buffer-or-name
                    (get-buffer-window buffer-or-name)
                  osl--buffer-window)))
    (when (window-live-p window)
      (select-window window)
      (quit-window (buffer-live-p buffer-or-name) window))
    (message (or abort-message "Org Select Quit"))))

(defun osl--make-mode-map ()
  (let ((map (make-sparse-keymap)))
    (define-key map [?q] #'org-select-quit)
    (define-key map [?\C-g] #'org-select-abort)
    (define-key map [?\C-p] #'osl--back)
    (define-key map [remap newline] #'osl--ignore-key)
    (define-key map [remap self-insert-command] #'osl--ignore-key)
    (setq org-select-mode-map map)
    (use-local-map org-select-mode-map)))

(defun org-select-abort ()
  (interactive)
  (org-select-quit "Aborted"))

(defun osl--back ()
  (interactive)
  (when (bound-and-true-p org-select-mode)
    (osl--make-mode-map)
    (osl--draw)))

(defun osl--longest-line ()
  "Return the length of the longest line in current buffer."
  (let ((n 1) (L 0) (e 0) (E (point-max)) l)
    (while (< e E)
      (setq e (line-end-position n)
            l (- e (line-beginning-position n))
            n (1+ n))
      (if (> l L) (setq L l)))
    L))

(defun osl--decorate-key (key)
  "Place string KEY between characters specified in DECORATOR string."
  (let ((kd (if (> (length org-select-key-decorator-chars) 0)
                org-select-key-decorator-chars
              (osl--arg :key-decorator))))
    (if (= (length kd) 2)
        (concat (substring kd 0 1) key (substring kd 1))
      key)))

(defun osl--decorate-label (entry)
  (let ((left (car org-select-label-decorators))
        (right (cdr org-select-label-decorators)))
    (if (= (length entry) 2)
        (concat left (cadr entry) right)
      (cadr entry))))

(defun osl--make-separator (&optional marker length)
  (let ((len (or length (osl--longest-line)))
        (sep (if (osl--arg :horizontal)
                 org-select-horizontal-separator
               org-select-vertical-separator)))
    (if marker
        (concat "sep" sep)
      (make-string len (string-to-char sep)))))

(defun osl--insert-horizontal-separator (sep)
  (goto-char 1)
  (let ((lol (osl--longest-line))
        (sep (or org-select-horizontal-separator sep)))
    (while (not (eobp))
      (let* ((eol (line-end-position))
             (bol (line-beginning-position))
             (fill (- (+ bol lol) eol)))
        (goto-char eol)
        (if (> fill 0)
            (while (> fill 0) (insert " ") (setq fill (1- fill)))
          (while (> 0 fill) (delete-char 1) (setq fill (1+ fill))))
        (insert " " sep " "))
      (forward-line))
    (setq osl--current-menu-column (+ lol (length sep) 2))))

(defun osl--insert-separator (sep)
  (if (osl--arg :horizontal)
      (osl--insert-horizontal-separator sep)
    (insert sep)))

(defun osl--insert (&rest strings)
  (cond
   ((and (osl--arg :horizontal)
         (> osl--current-menu-column 0))
    (goto-char (+ (line-beginning-position)
                  osl--current-menu-column))
    (apply #'insert strings)
    (if (char-after)
        (forward-line)
      (insert "\n")))
   (t 
    (apply #'insert strings)
    (insert "\n"))))

(defun osl--forward-menu ()
  (cond
   ((osl--arg :horizontal)
    (goto-char (point-min))
    (goto-char (line-end-position))
    (setq osl--current-menu-column
          (- (point) (line-beginning-position))))
   (t (insert "\n"))))

;;;; Menu drawing
(defun osl--setup-buffer (tables args)
  "Setup buffer local variables needed for an org-select buffer."
  (let* ((buffer (or (plist-get args :buffer-name) "*Org-select: "))
         (window (get-buffer-window buffer)))
      (if window
          (select-window window)
        (org-switch-to-buffer-other-window buffer))
    (with-current-buffer (get-buffer buffer)
      (special-mode)
      (setq cursor-type nil)
      (org-select-mode)
      (osl--make-mode-map)
      (setq osl--args args
            osl--buffer-menu tables
            osl--current-menu-column 0
            osl--buffer (current-buffer)
            osl--buffer-window (get-buffer-window)))))

(defun osl--draw ()
  "Starts menu parsing."
  (with-silent-modifications
    (erase-buffer)
    (setq osl--init nil)
    (let ((marker (osl--make-separator 'marker))
          (text (osl--arg :text))
          (menus (buffer-local-value
                  'osl--buffer-menu (current-buffer))))
      (setq osl--menu-begin (point))
      ;; given a list of menus, display one menu at a time
      (dolist (menu menus)
        (cond ((symbolp menu) (setq menu (eval menu)))
              ((symbolp (car menu)) (setq menu (eval (car menu)))))
        (let ((handler (osl--prop :org-select-handler menu)))
          (when handler
            (setq menu (delete :org-select-handler (delete handler menu))))
          (osl--do-menu menu (or handler #'org-select-run-once)))
        (setq menus (cdr menus))
        (when menus
          (osl--insert-separator marker)
          (osl--forward-menu)))
      ;; redraw markers with real separator strings
      (goto-char 1)
      (let ((sep (osl--make-separator nil (osl--longest-line))))
        (while (search-forward marker nil t)
          (replace-match "")
          (osl--insert-separator sep)))
      ;; insert info text if any
      (when text
        (goto-char 1)
        (insert "\n" text "\n"))
      (org-fit-window-to-buffer)
      (setq osl--init t)
      (goto-char 1)))) ; unnecessary but prettier if beacon-mode is active

;; iterate through menu and render a single entry or a group of entries on each
;; iteration
(defun osl--do-menu (menu handler)
  "Insert one menu at a time."
  (while menu
    (let ((entry (car menu)))
      (setq menu
            (if (> (length entry) 2)
                (osl--do-entry menu handler)
              (osl--do-group menu handler))))))

(defun osl--do-group (menu handler)
  "Do a menu with group nodes."
  (let ((group (car menu))
        newmenu)
    (osl--do-entry menu handler)
    (while (> (length (cadr menu)) 2)
      (let (entry newentry key)
        (setq menu (cdr menu) entry (car menu))
        (setq key (substring (car entry) 1))
        (push key newentry)
        (dolist (elt (cdr entry)) (push elt newentry))
        (push (nreverse newentry) newmenu)))
    (setq newmenu (nreverse newmenu))
    (define-key org-select-mode-map (kbd (car group))
                (lambda ()
                  (interactive)
                  (with-silent-modifications
                    (erase-buffer)
                    (setq osl--current-menu-column 0)
                    (osl--do-menu newmenu handler))))
    (cdr menu))) ;; return next group in chain

;; we send in the entire menu so we can return next piece in chain,
;; but *the* entry we work with is just the very first one (car menu)
(defun osl--do-entry (menu handler)
  "Display a single entry in the buffer."
  (let* ((entry (car menu))
         (key (car entry))
         (line-length 0)
         (handler (or (osl--prop :org-select-handler entry) handler)))
    (define-key org-select-mode-map (kbd key)
                (lambda ()
                  (interactive)
                  (let ((label (nth 1 entry))
                        (init (buffer-local-value 'osl--init osl--buffer)))
                    (and init handler
                         (message (or (funcall handler entry (current-buffer))
                                      label))))))
    (osl--insert (osl--decorate-key key) "    " (osl--decorate-label entry))
    (setq line-length (- (line-end-position) (line-beginning-position)))
    (if (> line-length osl--longest-label)
        (setq osl--longest-label line-length))
    (cdr menu)))

(defun org-select-run (entry &optional _org-select-buffer)
  "Try to execute form found in ENTRY if any leave ORG-SELECT-BUFFER live.

This handler provides an easy way to use the framework for the simple
use-cases for multiple choices. It relies on the user to press built-in choice
`q' or `C-g' to exit the menu."
  (let* ((form (nth 2 entry))
         (message
          (cond
           ((commandp form)
            (call-interactively form))
           ((functionp form)
            (apply form (cddr entry)))
           (t (eval form)))))
    (if (stringp message) message)))

(defun org-select-run-once (entry &optional org-select-buffer)
  "Try to execute form found in ENTRY if any and kill ORG-SELECT-BUFFER.

Provides an easy way to use the framework for the simple use-cases for multiple
choices. It relies on the user to press built-in choice `q' or `C-g' to exit the
menu."
  (if org-select-buffer (org-select-quit ""))
  (let* ((form (nth 2 entry))
         (message
          (cond
           ((commandp form)
            (call-interactively form))
           ((functionp form)
            (apply form (cddr entry)))
           (t (eval form)))))
    (if (stringp message) message)))
\f
;;; API
\f
(defun org-select (menus &rest args)
  "Select a member of an alist with multiple keys.

MENUS is a list of menus which themselves are lists containing entries in one of
following two formats:

1. prefix descriptions like (\"a\" \"Description\")
   This indicates that `a' is a prefix key for multi-letter selection, and
   that there are entries following with keys like \"ab\", \"ax\"...

2. Select-able members must have more than two elements, with the first
   being the string of keys that lead to selecting it, and the second a
   short description string of the item. 

The command will then make a temporary buffer listing all entries
that can be selected with a single key, and all the single key
prefixes.  When you press the key for a single-letter entry, it is selected.
When you press a prefix key, the commands (and maybe further prefixes)
under this key will be shown and offered for selection.

Each menu can be followed by some properties in form of a keu-value pair. The
entire menu or entry does not need to be a regular plist. Following keys are
recognized:

:org-select-pin     Pin this menu in org-select buffer. If group nodes are used,
                    when this option is `t', keep this menu visible even when
                    descending into a submenu. ;; FIXME Not implemented yet.
:org-select-handler Use this function to handle this particular menu or
                    entry. When none is specified, org-select uses
                    `org-select-run-once' to hande the menu. Entry handler
                    takes precedence over menu handler.

If there are more than one menus, they will be separated by a separator line
rendered with character as specified in `org-select-horizontal-separator'.

ARGS is a org-select buffer or entry property list containing following members:

:text          a string placed over selections in the buffer.
:buffer-name   a string used for the selections buffer name.
:key-decorator a two-character string used to decorate command characters. A
               menu can specify this string, but the precedence will be given
               the global variable `org-select-key-decorator-chars'. This to
               ensure that users can customize the appearance of the menus.

Properties in ARGS list are global for the entire org-select buffer."
  
  (osl--setup-buffer menus args)
  (osl--draw))
\f
;;; Demo
\f
;;;; org-capture
\f
(require 'org)
(require 'org-capture)

(defvar org-capture--current-goto nil)
(defvar org-capture--current-keys nil)
(defvar org-capture--old-window-config nil)

(defun org-capture-test (&optional goto keys)
  "Simple illustration to recreate org-capture menu (visually only)."
  (interactive "P")
  (let ((org-select-vertical-separator "-")
        (org-capture-templates
	   (or (org-contextualize-keys
	        (org-capture-upgrade-templates org-capture-templates)
	        org-capture-templates-contexts)
	       '(("t" "Task" entry (file+headline "" "Tasks")
		  "* TODO %?\n  %u\n  %a")))))
      (if keys
	  (or (assoc keys org-capture-templates)
	      (error
               "No capture template referred to by \"%s\" keys" keys)))
  (cond
   ((equal goto '(4))  (org-capture-goto-target keys))
   ((equal goto '(16)) (org-capture-goto-last-stored))
   (t
    (if goto (setq org-capture--current-goto goto))
    (push :org-select-handler org-capture-templates)
    (push #'org-capture--handler org-capture-templates)
    (org-select
     ;; tables
     `(,(nreverse org-capture-templates)
       (("C" "Customize org-capture-templates"
         (customize-variable 'org-capture-templates))
        ("q" "Abort" (org-select-quit "Abort"))))
     ;; description
     :buffer-name "*Capture*" :key-decorator "[]"
     :text "Select a capture template\n========================="))))
  (message "Org Capture"))
;;(define-key global-map (kbd "C-v c") #'org-capture-test)

(defun org-capture--handler (entry org-select-buf)
  (org-select-quit "" org-select-buf)
  (let* ((capture-buf (generate-new-buffer "*Capture*"))
         (annotation (if (and (boundp 'org-capture-link-is-already-stored)
			      org-capture-link-is-already-stored)
			 (plist-get org-store-link-plist :annotation)
		       (ignore-errors (org-store-link nil))))
	 (entry (or org-capture-entry entry))
         (goto org-capture--current-goto)
         (inhibit-read-only t)
	 initial)
    (setq initial (or org-capture-initial
		      (and (org-region-active-p)
			   (buffer-substring (point) (mark)))))
    (when (stringp initial)
      (remove-text-properties 0 (length initial) '(read-only t) initial))
    (when (stringp annotation)
      (remove-text-properties 0 (length annotation)
			      '(read-only t) annotation))
    (org-capture-set-plist entry)
    (org-capture-get-template)
    (org-capture-put :original-buffer capture-buf
		     :original-file (or (buffer-file-name capture-buf)
					(and (featurep 'dired)
					     (car (rassq capture-buf
							 dired-buffers))))
		     :original-file-nondirectory
		     (and (buffer-file-name capture-buf)
			  (file-name-nondirectory
			   (buffer-file-name capture-buf)))
		     :annotation annotation
		     :initial initial
		     :return-to-wconf (current-window-configuration)
		     :default-time (or org-overriding-default-time
				       (org-current-time)))
    (org-capture-set-target-location (and (equal goto 0) 'here))
    (condition-case error
	(org-capture-put :template (org-capture-fill-template))
      ((error quit)
       (if (get-buffer capture-buf) (kill-buffer capture-buf))
       (error "Capture abort: %s" (error-message-string error))))
    (setq org-capture-clock-keep (org-capture-get :clock-keep))
    (condition-case error
	(org-capture-place-template
	 (eq (car (org-capture-get :target)) 'function))
      ((error quit)
       (when (and (buffer-base-buffer (current-buffer))
		  (string-prefix-p "CAPTURE-" (buffer-name)))
	 (kill-buffer (current-buffer)))
       (set-window-configuration (org-capture-get :return-to-wconf))
       (error "Capture template `%s': %s"
	      (org-capture-get :key)
	      (error-message-string error))))
    (when (and (derived-mode-p 'org-mode) (org-capture-get :clock-in))
      (condition-case nil
	  (progn
	    (when (org-clock-is-active)
	      (org-capture-put :interrupted-clock
			       (copy-marker org-clock-marker)))
	    (org-clock-in)
	    (setq-local org-capture-clock-was-started t))
	(error "Could not start the clock in this capture buffer")))
    (when (org-capture-get :immediate-finish)
      (org-capture-finalize))))
\f
;;;; Org Agenda
\f
(require 'org-agenda)
(defvar org-agenda--arg nil)
(defvar org-agenda--keys nil)
(defvar org-agenda--restriction nil)

(defun org-agenda-test (&optional _arg _keys _restriction)
  (interactive "P")
  (let ((org-select-horizontal-separator " "))
    (org-select
     '((("a" "Agenda for current week or day" org-agenda-list)
        ("t" "List of all TODO entries"       org-todo-list)
        ("m" "Match a TAGS/PROP/TODO query"   org-tags-view)
        ("s" "Search for keywords"            org-search-view)
        ("/" "Multi-occur"                    (call-interactively
                                               'org-occur-in-agenda-files)
                                               :org-select-inhibit-transient t)
        ("?" "Find :FLAGGED: entries"         (org-tags-view
                                               nil "+FLAGGED"))
        ("*" "Toggle sticky agenda views"     org-toggle-sticky-agenda
                                              :org-select-handler org-select-run))
       (("<" "Buffer, subtree/region restriction" ignore)
        (">" "Remove restriction"             ignore)
        ("e" "Export agenda views"            org-store-agenda-views)
        ("T" "Entries with special TODO kwd" (org-call-with-arg
                                              'org-todo-list
                                              (or org-agenda--arg '(4))))
        ("M" "Like m, but only TODO entries" (org-call-with-arg
                                              'org-tags-view
                                              (or org-agenda--arg '(4))))
        ("S" "Like s, but only TODO entries" (org-call-with-arg
                                              'org-search-view
                                              (or org-agenda--arg '(4))))
        ("C" "Configure custom agenda commands"
         (customize-variable 'org-agenda-custom-commands))
        ("#" "List stuck projects" (org-agenda--exec
                                    'org-agenda-list-stuck-projects))
        ("!" "Configure stuck projects"
         (customize-variable 'org-stuck-projects))))
    :text
    "Press key for an agenda command:
--------------------------------\n"
    :horizontal t :buffer-name "*Agenda Commands*")))
\f
;;;; Various tests
\f
(defun test1 ()
  "Stays after a choice is made."
  (interactive)
  (let ((org-select-horizontal-separator "│"))
    (org-select
     ;; table
     '((("1" "One" (message "One!"))
        ("2" "Two" (message "Two!!"))
        ("3" "Three" (message "Three!!!")))
       (("C-4" "Four" (message "Four!!!!"))
        ("C-5" "Five" (message "Five!!!!!"))
        ("C-6" "six" (message "Six!")))
       (("M-7" "Seven" (message "Seven!"))
        ("M-8" "Eight" (message "Eight!"))
        ("M-9" "Nine" (message "Nine!"))))
     ;; description
     :horizontal t :key-decorator "<>")))

(defun test2 ()
  "Dissapears after a choice is made."
  (interactive)
  (let ((org-select-horizontal-separator "│"))
    (org-select
     ;; menus
     '((("h" "Hello, World!" (message "Hello, World!"))
        ("b" "Bar" (message "Hello, Bar!")))
       (("f" "Find File" find-file)
        ("o" "Open File" (flet ((next-read-file-uses-dialog-p () t))
                           (call-interactively #'find-file)))))
     ;; description
     :key-decorator "\"\"" :transient t)
      ;; Hints
      (setq header-line-format
	    (if (not (pos-visible-in-window-p (point-max)))
	        "Use C-v, M-v, C-n or C-p to navigate. C-g, q to quit."
              "Use C-p/Left to go back, C-g, q to quit."))))

(defun test3 ()
  "Illustrate nested menus, unicode separator and alternative decorator."
  (interactive)
  (let ((org-select-vertical-separator "─"))
    (org-select
     ;; tables
     '((("g" "Greetings")
        ("gh" "Hello, World!" (message "Hello, World!"))
        ("gb" "Bar" (message "Hello, Bar!")))
       (("f" "Functions")
        ("ff" "Find File" find-file)
        ("fo" "Open File" (flet ((next-read-file-uses-dialog-p () t))
                            (call-interactively #'find-file)))))))
      ;; Hints
      (setq header-line-format
	    (if (not (pos-visible-in-window-p (point-max)))
	        "Use C-v, M-v, C-n or C-p to navigate. C-g, q to quit."
              "Use C-p/Left to go back, C-g, q to quit.")))

  (provide 'org-select)

;;; org-select.el ends here

  reply	other threads:[~2022-06-22 12:20 UTC|newest]

Thread overview: 59+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-05-26 15:27 Proposal: 'executable' org-capture-templaes Arthur Miller
2022-05-27  5:27 ` Ihor Radchenko
2022-05-27 12:17   ` Arthur Miller
2022-05-27 14:35     ` Max Nikulin
2022-05-28  3:51     ` Ihor Radchenko
2022-05-30  2:04       ` Arthur Miller
2022-05-30  5:05         ` Ihor Radchenko
2022-05-30 12:40           ` Arthur Miller
2022-05-31  4:58             ` Ihor Radchenko
2022-05-31 14:46               ` Arthur Miller
2022-06-04 15:35               ` Arthur Miller
2022-06-05  0:04                 ` Ihor Radchenko
2022-06-05 15:16                   ` Arthur Miller
2022-06-05 23:05                     ` Tim Cross
2022-06-08 12:43                       ` Ihor Radchenko
2022-06-08 21:13                         ` Tim Cross
2022-06-09  4:00                           ` Ihor Radchenko
2022-06-17  4:40                         ` Arthur Miller
2022-06-18  4:03                           ` Ihor Radchenko
2022-06-18  4:26                             ` Tim Cross
2022-06-18 12:25                       ` Max Nikulin
2022-06-08 12:24                     ` Ihor Radchenko
2022-06-05  7:36                 ` Max Nikulin
2022-06-05 15:07                   ` Arthur Miller
2022-06-06 17:06                     ` Max Nikulin
2022-06-07  3:09                       ` Samuel Wales
2022-06-07  3:16                         ` Samuel Wales
2022-06-08 12:48                           ` Ihor Radchenko
2022-06-10 16:53                         ` Max Nikulin
2022-06-11  5:26                           ` Ihor Radchenko
2022-06-18  8:18                             ` Max Nikulin
2022-06-18  8:25                               ` Ihor Radchenko
2022-06-19 11:20                                 ` Max Nikulin
2022-06-20 12:10                                   ` Ihor Radchenko
2022-06-20 17:24                                     ` Max Nikulin
2022-06-21  4:07                                       ` Ihor Radchenko
2022-06-21  7:38                                         ` Arthur Miller
2022-06-21 15:48                                         ` Max Nikulin
2022-06-22 12:13                                           ` Arthur Miller [this message]
2022-06-22 16:29                                             ` Max Nikulin
2022-06-26  4:50                                               ` Arthur Miller
2022-06-29 17:02                                                 ` Max Nikulin
2022-06-30 23:30                                                   ` Arthur Miller
2022-07-01 15:53                                                     ` Proposal: 'executable' org-capture-templates Max Nikulin
2022-06-25  7:32                                             ` Proposal: 'executable' org-capture-templaes Ihor Radchenko
2022-06-26  4:25                                               ` Arthur Miller
2022-06-26  4:37                                                 ` Ihor Radchenko
2022-06-26  4:52                                                   ` Arthur Miller
2022-06-21  7:37                                       ` Arthur Miller
2022-07-02 11:31                                         ` Max Nikulin
2022-07-03 15:12                                           ` Arthur Miller
2022-07-07 16:14                                             ` Proposal: 'executable' org-capture-templates Max Nikulin
2022-06-18 15:05                               ` Proposal: 'executable' org-capture-templaes Arthur Miller
2022-06-19 10:53                                 ` Max Nikulin
2022-06-19 15:34                                   ` Arthur Miller
2022-07-03  3:32                                     ` Max Nikulin
2022-06-08 12:35                     ` Ihor Radchenko
2022-05-31 16:37         ` Max Nikulin
2022-06-01  1:45           ` arthur miller

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.orgmode.org/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=AM9PR09MB49771CF015DAECBF3E5F955E96B29@AM9PR09MB4977.eurprd09.prod.outlook.com \
    --to=arthur.miller@live.com \
    --cc=emacs-orgmode@gnu.org \
    --cc=manikulin@gmail.com \
    --cc=yantar92@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).