From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: Philip Kaludercic Newsgroups: gmane.emacs.devel Subject: Re: Stepping Back: A Wealth Of Completion systems Re: [ELPA] New package: vertico Date: Sat, 10 Apr 2021 16:40:41 +0200 Message-ID: <87blamp5hy.fsf@posteo.net> References: <9c9af088-580f-9fb1-4d79-237a74ce605c@inventati.org> <874kgkxxs0.fsf@posteo.net> Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="4301"; mail-complaints-to="usenet@ciao.gmane.io" User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/27.1 (gnu/linux) To: emacs-devel@gnu.org Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Sat Apr 10 16:42:03 2021 Return-path: Envelope-to: ged-emacs-devel@m.gmane-mx.org Original-Received: from lists.gnu.org ([209.51.188.17]) by ciao.gmane.io with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1lVEoF-000113-Fl for ged-emacs-devel@m.gmane-mx.org; Sat, 10 Apr 2021 16:42:03 +0200 Original-Received: from localhost ([::1]:52952 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1lVEoE-0007Sp-HE for ged-emacs-devel@m.gmane-mx.org; Sat, 10 Apr 2021 10:42:02 -0400 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]:41540) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1lVEn6-0006Wm-C2 for emacs-devel@gnu.org; Sat, 10 Apr 2021 10:40:52 -0400 Original-Received: from mout02.posteo.de ([185.67.36.66]:54149) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1lVEn3-0005sJ-R1 for emacs-devel@gnu.org; Sat, 10 Apr 2021 10:40:52 -0400 Original-Received: from submission (posteo.de [89.146.220.130]) by mout02.posteo.de (Postfix) with ESMTPS id BFAD42400E5 for ; Sat, 10 Apr 2021 16:40:42 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=posteo.net; s=2017; t=1618065642; bh=Wm7neL/I9QZQP8Qdzo8X4RP2SfgCmpJQ1LSsyhwNKJE=; h=From:To:Subject:Date:From; b=OxhJESwqeFwuSeQQydix/22XPUpfTnKCYyTHquQr2BqsqxYCLSoCw5WtBHsBeE+JD KI05XE+svtp2H21KZuutE9yS6ZZVtD/w2yzdDCNdUhtrpu734ZZmm+XiCBKWgM5PCt FppiQF8TcY/SSo9MbrVEns4U0gRIiun6ovwtX4FP14tSkwkB4TVP59+ELj0PCdp5iz gh2R5tPZJJ+RddQg028vimmFhlnW45KocLuJ1YqKlId+Y8/TKYZqPfU6h0iTnX+/0w wpBTpHx5apKEfP7KD/I/OvbdzojMwGX176lqr/Gbww8XQWFSDumy5tJP+zLCk3fMJl LzxCukA9Im/JQ== Original-Received: from customer (localhost [127.0.0.1]) by submission (posteo.de) with ESMTPSA id 4FHd2x5v7hz9rxH for ; Sat, 10 Apr 2021 16:40:41 +0200 (CEST) In-Reply-To: <874kgkxxs0.fsf@posteo.net> (Philip Kaludercic's message of "Mon, 05 Apr 2021 22:49:03 +0200") Received-SPF: pass client-ip=185.67.36.66; envelope-from=philipk@posteo.net; helo=mout02.posteo.de X-Spam_score_int: -27 X-Spam_score: -2.8 X-Spam_bar: -- X-Spam_report: (-2.8 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_LOW=-0.7, RCVD_IN_MSPIKE_H4=0.001, RCVD_IN_MSPIKE_WL=0.001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Original-Sender: "Emacs-devel" Xref: news.gmane.io gmane.emacs.devel:267828 Archived-At: --=-=-= Content-Type: text/plain Philip Kaludercic writes: > It might therefore be necessary to actually implement a "selecting-read" > function, that could be used more or less like completing-read, but that > provides a better default UI not based around completing text but > actually selecting objects/items. I attached a primitive version of selecting-read to this message. The UI is horridly primitive, but the basic idea should be understandable. Using generic functions, methods can be defined determining how an object is handled. In this iteration, three generic functions are used: - (selecting-read-represent object) Return a string representing the object. This is the only necessary method. - (selecting-read-properties object) Return a plist denoting properties of the object - (selecting-read-children object) Return a list of children of this object It seems that these three functions are enough, and that adding more would risk becoming too complicated. When evaluating a nonsensical query like (selecting-read '((node "one" "one.1" "one.2" "one.3") (node ("two" :property "propertied" :key "value") "two.1" "two.2") "three")) a child window appears and the user can select an object, that selecting-read returns directly (eq). Additional arguments are passed as keywords. A simple example is :multiple that lets selecting-read give me a list of items that were marked (selecting-read '((node "one" "one.1\n" "one.2" "one.3") (node ("two" :property "propertied" :key "value") "two.1" "two.2") "three") :multiple t) Because I'm not just now primarily concerned with what completing-read might look like, it doesn't do "automatic narrowing" like Helm or Ivy. The framework I sketched here should be flexible enough to support something like that, if preferred. -- Philip K. --=-=-= Content-Type: application/emacs-lisp Content-Disposition: attachment; filename=selecting-read.el Content-Transfer-Encoding: quoted-printable ;;; selecting-read.el --- Utility function for selection -*- lexical-bindi= ng: t; -*- ;; Copyright (C) 2021 Philip Kaludercic ;; Author: Philip Kaludercic ;; Keywords: lisp ;; 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 . ;;; Commentary: ;; Selecting Framework for Emacs. ;;; Code: (eval-when-compile (require 'subr-x)) (require 'cl-lib) (defvar selecting-read-function #'selecting-read-default "Function called by `selecting-read'. This function must accept two variables LIST and OPTIONS. LIST may be a list or an alist. OPTIONS may consist of the following keys: :multiple If non-nil, `selecting-read' will return multiple results in form of a list.") ;;; object framework (cl-defgeneric selecting-read-represent (object) "Return a string to represent OBJECT.") (cl-defgeneric selecting-read-properties (object) "Return a plist of properties for OBJECT." (ignore object)) (cl-defgeneric selecting-read-children (object) "Return a list of children for OBJECT." (ignore object)) ;;;; simple string object (cl-defmethod selecting-read-represent ((str string)) "A string STR represents itself." str) ;;;; generic node object (cl-defmethod selecting-read-represent ((node (head node))) "Return representation for NODE (node OBJECT CHILDREN). OBJECT may be a string or a cons cell (REPRESENTATION . PROPERTIES)." (if (consp (cadr node)) (caadr node) (cadr node))) (cl-defmethod selecting-read-properties ((node (head node))) "Return properties for NODE (node OBJECT CHILDREN). OBJECT may be a string or a cons cell (REPRESENTATION . PROPERTIES)." (and (consp (cadr node)) (cdadr node))) (cl-defmethod selecting-read-children ((node (head node))) "Return children for NODE (node REPRESENTATION CHILDREN)." (cddr node)) ;;; default UI (defvar-local selecting-read-list nil) (defvar-local selecting-read-options nil) (defvar-local selecting-read-selection nil) (defvar-local selecting-read-query nil) (defsubst selecting-read-prop (prop) "Access property PROP." (plist-get selecting-read-options prop)) (defun selecting-read-select () "Select object at point." (interactive) (if-let ((object (get-text-property (point) 'selecting-read-object))) (unless (selecting-read-prop :multiple) (setq selecting-read-selection object)) (user-error "No object to select")) (exit-recursive-edit)) (defun selecting-read-narrow (query) "Narrow listing to options that much regexp QUERY.." (interactive (list (read-regexp "Filter: "))) (setq selecting-read-query query) (selecting-read-redraw)) (defun selecting-read-mark () "Add object at point to selection." (interactive) (unless (selecting-read-prop :multiple) (user-error "Cannot mark multiple elements")) (push (get-text-property (point) 'selecting-read-object) selecting-read-selection)) (defun selecting-read-next () "Move to next object." (interactive) (goto-char (or (next-single-property-change (point) 'selecting-read-objec= t) (point)))) (defun selecting-read-previous () "Move to previous object." (interactive) (goto-char (or (previous-single-property-change (point) 'selecting-read-o= bject) (point-min)))) (defvar selecting-read-mode-map (let ((map (make-sparse-keymap))) (define-key map (kbd "q") #'ignore) (define-key map (kbd "") #'selecting-read-select) (define-key map (kbd "/") #'selecting-read-narrow) (define-key map (kbd "m") #'selecting-read-mark) (define-key map (kbd "n") #'selecting-read-next) (define-key map (kbd "p") #'selecting-read-previous) map)) (define-derived-mode selecting-read-mode special-mode "Selecting Read" "Major mode for Selecting Read buffers." (setq-local mode-line-format (concat " Select an Item with RET"))) (defun selecting-read-draw (list depth) "Insert LIST indented by DEPTH." (dolist (object list) (let ((string (selecting-read-represent object)) (properties (selecting-read-properties object)) (children (selecting-read-children object)) (start (point))) (when (or (not selecting-read-query) (string-match-p selecting-read-query string)) (insert string) (unless (bolp) ;ensure newline (newline)) (cl-loop for (key val) on properties by #'cddr do (insert (symbol-name key) " " (cond ((stringp val) val) ((functionp val) (funcall val object)) ((prin1-to-string val))) "\n")) (indent-rigidly start (point) depth) (set-text-properties start (point) (list 'selecting-read-object object)) (selecting-read-draw children (+ depth 2)))))) (defun selecting-read-redraw () "Redraw current buffer." (let ((use-hard-newlines t) (inhibit-read-only t)) (erase-buffer) (selecting-read-draw selecting-read-list 0) (goto-char (point-min)))) (defun selecting-read-initialize (list options) "Initialize a Selecting Read buffer for LIST. OPTIONS is a plist as specified in `selecting-read-function'." (selecting-read-mode) (setq selecting-read-options options) (setq selecting-read-list list) (selecting-read-redraw)) (defun selecting-read-default (list options) "Default selection function. LIST and OPTIONS are passed directly from `selecting-read'." (with-current-buffer (generate-new-buffer " *Selecting Read*") (selecting-read-initialize list options) (display-buffer-in-side-window (current-buffer) '((window-height . 0.25) (side . bottom))) (select-window (get-buffer-window (current-buffer))) (let (return-value) (unwind-protect (recursive-edit) (setq return-value selecting-read-selection) (kill-buffer)) return-value))) ;;;###autoload (defun selecting-read (list &rest options) "Ask user to select an element from LIST. LIST and the plist OPTIONS are passed to the function designated by `selecting-read-function'." (funcall selecting-read-function list options)) (provide 'selecting-read) ;;; selecting-read.el ends here --=-=-=--