如何通过 completing-read 返回值而不是键名

6
(completing-read
 "Complete a foo: "
 '(("foobar1" "~/foobar1/") ("barfoo" "/usr/barfoo/") ("foobaz" "/hello/")))

如上所示,我想提示输入"foobar1""barfoo""foobaz",但返回配对的目录。

此外,如果我有一个类似于这样的哈希表

(cl-defstruct person ID name)
(setq person-object (make-person :ID 123 :name "foo"))
(setq person-table (make-hash-table))
(pushash (person-ID person-object) person-object person-table)

如何提示输入人名并返回其ID?

3个回答

8

无法让 completing-read 返回值而不是键名,因此您需要自行查找:

(let ((completions '(("foobar1" "~/foobar1/") ("barfoo" "/usr/barfoo/") ("foobaz" "/hello/"))))
  (cadr (assoc (completing-read "Complete a foo: " completions) completions)))

关于哈希表,由于名称不是键,您需要使用 maphash 迭代哈希表中的每个对象来查找它。 既然在找到您要查找的内容后继续迭代会浪费时间,因此您可以像这样使用 catchthrow

(catch 'found-it
  (maphash
   (lambda (key value)
     (when (equal (person-name value) desired-name)
       (throw 'found-it key)))
   person-table))

这将返回该人的ID,如果没有名字等于desired-name的人,则返回nil


6
@legoscia提供了很好的答案:completing-read不会提供使用于完成操作的键所关联的值。例如,对于alist参数COLLECTION,它不会提供已选的alist键的cdr访问权限。
对于alist,可以使用assoc获取第一个匹配的alist元素,对于哈希表,可以使用maphash或执行get操作。
但是这些方法排除了在存在重复项(即多个候选项具有相同的键或名称)时获取特定值特定选择的键的相关值的可能性。

您无法获取第二个匹配元素或第13个匹配元素。事实上,原始Emacs版本completing-read消除了具有相同键(名称)的完成候选项的重复。对于原始Emacs,alist条目的cdr中的任何信息都是浪费的。如果已经拥有alist,则可以出于方便使用,但如果没有,则最好只使用名称列表(字符串或符号),而不是conses。
如果您使用Icicles,则alist条目不会被浪费。检索cdr值没有问题。完成completing-read后,您可以轻松地获取所选候选项的完整信息。 Icicles通过使用带有属性的字符串作为候选项,并增强completing-read,以便它可以返回用户选择的完整字符串(包括属性),来解决这一问题。您可以从返回的带有属性的字符串中恢复完整的alist条目。 何时有重要意义能够拥有并使用具有不同相关值的重复键?如果它们是重复的,用户如何区分它们(例如,在*Completions*中)? 示例:
  • 拥有相同名称但指向不同目标的书签 - 例如,不同目录中具有相同相对名称的文件。

  • 在缓冲区中与模式匹配或包含标记的行或其他文本。这包括Icicles搜索中的匹配项,其中您可以按照自己喜欢的方式定义搜索上下文(不仅仅是行)。它还包括缓冲区区域(包括限制,也称为缩小)和缓冲区位置(标记)。

  • 具有相同文本但注释不同的候选项。(用户输入不会与*Completions*中显示的注释匹配。)

  • 具有相同名称的Imenu项,例如,具有相同名称的对象(例如函数)的多个定义。

  • 具有相同名称的已标记项(例如函数)。

  • 其他Lisp对象的候选项,例如框架,可能具有相同的名称。

Icicles中,用户如何在具有相同名称的多个完成候选项中选择一个?

  • 用户可以控制候选项的顺序(排序),包括在运行时更改顺序。 *Completions*以特定顺序向您显示它们。您可以在候选项之间循环或直接选择任何一个。您不仅限于匹配来进行选择。(对于重复的候选项,匹配可能不足以使您只获得其中一个。)

  • *Completions*还可以向您显示有关候选项的其他信息,即使它们具有相同的名称/文本也可以区分它们。此类信息可能是周围文本(如果候选项与缓冲区文本匹配)或候选项元数据(例如文件或书签属性)。

  • 您还可以在模式行中查看当前候选项的重要附加信息(例如,在循环期间)。

  • 您可以通过按键随时获取有关当前候选项的其他信息(完整的*Help*)。

为了能够利用此Icicles功能,您需要做什么才能在自己的代码中使用?

请参见定义Tripping命令,了解如何定义自己的命令,让用户浏览可能具有相关位置或其他导航信息的候选项。(参见Tripping以获取预定义的Icicles tripping命令。)

以下是在命令中要执行的简要概述:

  1. 将变量icicle-whole-candidate-as-text-prop-p绑定到非nil值。

  2. 将变量icicle-candidates-alist设置为您传递给completing-read的alist。这将作为一个文本属性编码到候选显示字符串中,对应于原始alist条目的全部内容。

  3. 在调用completing-read之后使用icicle-get-alist-candidate来恢复用户选择的候选项的完整信息,即完整的alist元素,包括cdr。

(另请参见:使用Icicles的程序员注意事项。)


@legoscia提供了我需要的所有信息。然而,我选择这个答案是因为它提醒了我重复问题。 - tom

1
你正在寻找alt-completing-read!更简单的版本 http://www.howardism.org/Technical/Emacs/alt-completing-read.html
(let ((choices '(("First"  . 'first-choice)
                 ("Second" . 'second-choice)
                 ("Third"  . 'third-choice))))
  (alist-get
   (completing-read "Choose: " choices)
   choices nil nil 'equal))

复杂版本

(defun alt-completing-read (prompt collection &optional predicate require-match initial-input hist def inherit-input-method)
  "Calls `completing-read' but returns the value from COLLECTION.

Simple wrapper around the `completing-read' function that assumes
the collection is either an alist, or a hash-table, and returns
the _value_ of the choice, not the selected choice. For instance,
give a variable of choices like:

    (defvar favorite-hosts '((\"Glamdring\" . \"192.168.5.12\")
                             (\"Orcrist\"   . \"192.168.5.10\")
                             (\"Sting\"     . \"192.168.5.220\")
                             (\"Gungnir\"   . \"192.168.5.25\")))

We can use this function to `interactive' without needing to call
`alist-get' afterwards:

    (defun favorite-ssh (hostname)
      \"Start a SSH session to a given HOSTNAME.\"
      (interactive (list (alt-completing-read \"Host: \" favorite-hosts)))
      (message \"Rockin' and rollin' to %s\" hostname))"

  ;; Yes, Emacs really should have an `alistp' predicate to make this code more readable:
  (cl-flet ((assoc-list-p (obj) (and (listp obj) (consp (car obj)))))

    (let* ((choice
            (completing-read prompt collection predicate require-match initial-input hist def inherit-input-method))
           (results (cond
                     ((hash-table-p collection) (gethash choice collection))
                     ((assoc-list-p collection) (alist-get choice collection def nil 'equal))
                     (t                         choice))))
      (if (listp results) (first results) results))))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接