1

I have a list of objects and I want the user to pick one of them (with completion). But these objects are a priori not strings, so I can't use completing-read. What should I do ?

Example In the following example I want to write the interactive spec for killing a process, so I want the user to pick a process to kill.

(defun yf/kill-process (process)
  (interactive (list (let ((list
                            (mapcar
                             (lambda (proc)
                               (cons (format "%s" proc) proc))
                             (process-list))))
                       (cdr
                        (assoc (ido-completing-read "Process: "
                                                    (mapcar #'car list))
                               list)))))
  (when (yes-or-no-p (format "You are about to kill process \"%s\", continue? " process))
    (delete-process process)))

This is something I have to do often, so I'm wondering if there's a standard way to achieve this functionality (or if I should write my own... which I have done already, but it's ugly).

1

You can change your code to get the process's name with process-name, so your let block:

(let ((list
       (mapcar
        (lambda (proc)
          (cons (format "%s" proc) proc))
        (process-list))))
        ...

becomes:

  (let ((list (mapcar #'process-name (process-list))))
    ...)

There's no need to give delete-process both the string name and the name, because, it will work with just the string. According to the docstring:

Delete PROCESS: kill it and forget about it immediately. PROCESS may be a process, a buffer, the name of a process or buffer, or nil, indicating the current buffer's process.

  • Thanks. Indeed in this specific example, I can use the name as a unique identifier, but I'd like a general solution. – YoungFrog Aug 24 '16 at 14:11
1

Writing your own is probably your best bet, you can likely clean up your own version using a supplied function to determine what information to prompt the user with. In this case using a hash table as your collection type may be cleaner.

Consider the solution below as a generic way to prompt for objects:

(defun completing-read-by (fn prompt list &rest args)
  "Apply FN to each element of LIST and prompt the user to select a resulting value.
The output of the function will be the corresponding element of LIST

ARGS will be passed to `completing-read' after the PROMPT and COLLECTION arguments."
  (let ((hash (make-hash-table :test 'equal)))
    (mapc (lambda (elem) (puthash (funcall fn elem) elem hash)) list)
    (gethash (apply 'completing-read prompt hash args) hash)))


;; read process by name
(completing-read-by 'process-name "Process: " (process-list) nil t)

;; read a list of structs by property
(defstruct person name age)

(let ((people (list (make-person :name "Bob" :age 22) 
                    (make-person :name "Tom" :age 33)
                    (make-person :name "Jan" :age 34))))
  (completing-read-by 'person-name "Person: " people))
  • Indeed that works, and I guess a hash table is better than an alist. However I'm wondering if there's a variant on completing-read which returns an arbitrary object. It would be nice to support the same range of COLLECTION types as completing-read does (though I have no usecase for it beyond plain lists, so I guess IAGNI). – YoungFrog Aug 24 '16 at 14:15
1

The general idea is this, for prompting a user to choose an object other than a string: Use a name for the object.

  1. Pass an alist COLLECTION argument to completing-read (you can alternatively use another kind of COLLECTION argument - the same approach applies). The cars of the alist entries are the object names; the cdrs are the complete objects (or any parts of them).

  2. After the user makes a choice of an object name, completing-read returns it, as a string. You can use the string to look in the alist for the associated object.

In this case, you can use completing-read with the process names, and you can keep other process info associated with the names. For example, use the name, user, and pid in a list as an alist entry:

(setq my-alist
      (mapcar (lambda (pid)
                 (let ((ats  (process-attributes pid)))
                  `(,(cdr (assoc 'comm ats))
                    ,(cdr (assoc 'user ats))
                    ,(number-to-string pid))))
                 (list-system-processes)))

That alist lets you use the process names (commands) as completion candidates. You can then look up the associated information (the user and the pid).

(let* ((name    (completing-read "Choose process: " my-alist))
       (object  (assoc name my-alist))
       (user    (cadr object))
       (pid     (caddr object)))
  (message "Name: %s, User: %s, PID: %s" name user pid))

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.