[Top][All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: completing-read return meta-information?

From: Stephen Leake
Subject: Re: completing-read return meta-information?
Date: Wed, 16 Sep 2015 12:45:37 -0500
User-agent: Gnus/5.13 (Gnus v5.13) Emacs/24.5 (windows-nt)

Drew Adams <address@hidden> writes:

>> I tried storing the directory info in a text property of the
>> completion string; that was not returned.
> Here is a thread about this from 2008, for instance:
> http://lists.gnu.org/archive/html/emacs-devel/2008-06/msg01503.html

That thread includes a request from Stefan for a clear use case for
this. So let me make my case clear.

I'm implementing file name completion in projects. The details vary with
each backend. Two backends I've implemented so far look like this:

(cl-defgeneric project-find-file (prj filename)
  "Find FILENAME with completion in current project PRJ."
  (let* ((flat-path (project-flat-search-path prj))
         (regexp (project-ignore-files-regexp prj))
          (lambda (filename)
            ;; FIXME: should call project-ignores here with each directory
            (not (string-match regexp filename)))))

    ;; (project-ignore-files-regexp prj) matches filenames, not
    ;; uniquified filenames. So it must be applied in
    ;; `find-file-path-completion-table', not `completing-read'.
    (setq filename
           "file: " ;; prompt
           (completion-table-dynamic (apply-partially
              flat-path predicate))
           t ;; require match

    ;; We construct a relative path to ensure the filename is found on
    ;; `flat-path'.
    (when (string-match find-file-uniquify-regexp filename)
        (let ((dir (match-string 2 filename))
              (prefix "../")
              (i 0))

          (while (< i (length dir))
            (when (= (aref dir i) ?/)
              (setq prefix (concat prefix "../")))
            (setq i (1+ i)))

          (setq filename
                (concat prefix
                        (match-string 1 filename)))

    (let ((absfilename (locate-file filename flat-path nil)))
      (if absfilename
          (find-file absfilename)
        ;; FIXME: need human-readable name for project
        (error "'%s' not found in project." filename)))

(defun find-file-complete-global (filename)
  "Prompt for completion of FILENAME in a Gnu global project."
    (setq filename
           "file: " ;; prompt
           (completion-table-with-cache #'find-file-complete-global-table) ;; 
           nil ;; predicate
           t ;; require match

    (when (string-match find-file-uniquify-regexp filename)
      ;; Get partial dir from conflict
      (setq filename (concat (match-string 2 filename) (match-string 1 

    ;; If there are two files like:
    ;; src/keyboard.c
    ;; test/etags/c-src/emacs/src/keyboard.c
    ;; and the user completes to the first, the following global call
    ;; will return both. The desired result is always the shortest.
    (with-current-buffer (cedet-gnu-global-call (list "--ignore-case" "-Pa" 
      (let ((paths (split-string (buffer-substring (point-min) (point-max)) 
"\n" t)))
        (setq paths (sort paths (lambda (a b) (< (length a) (length b)))))
        (car paths)))


There is a desire to refactor this so that the only difference is inside
the completion table.

Currently, that is not possible, because completing-read does not return
the absolute path of the file that the user selected. Therefore the
calling code must do additional work, that is different for the two
backends. That work typically duplicates some of what the completion
table has already done.

The solution is to make completing-read return more information.

One way to do this with the current completing read code is to add the
entire path to the completion string, as a suffix. But that results in a
horrible user experience; in the Emacs project on my disk, "locate"
would complete to:


instead of the mininmal:

locate{.rnc .el<lisp/cedet/ede/> .elc<lisp/cedet/ede/> .el<lisp/> .elc</lisp/>}

So a better solution is to allow completing-read to return the
additional information in some other way.

Three ways seem straightforward:

1) The completion table can add the absolute path in an `abspath' text
   property of the completion string.

    The rest of the completion functions must preserve the text

    This may need a new optional arg to completing-read, if current code
    relies on text properties being discarded.

2) The completion table can return an alist (note this is already
   supported), with the cdr of each element being the absolute path. In
   this case, completing-read (and other completion functions?) returns
   the selected cons.

    To avoid breaking current alist completion-tables, this needs a new
    optional argument to completing-read (and other completion
    functions); cons-result, nil by default.

    Currently, when a completion-table returns an alist, the predicate
    supplied to completing-read is applied to the cdr of the elements.
    That could be useful for the file completion case as well, although
    it will probably be more efficient to apply the predicate inside the
    table, to prune the file tree.
3) Extend completion-metadata to cover this usage; then the calling code
   calls completion-metadata with the completing-read string result to
   retrieve the absolute path.

    This has the downside that the completion table code must repeat
    work done previously to recompute the metadata.

The calling code then has a standard way of retrieving the absolute path
from the result of completing-read (either get the `abspath' text
property, take the cdr, or call completion-metadata), and can be
independent of the completion backend.

I think the alist approach is more in keeping with general Emacs style,
although xref-read-identifier is one recent precendent for using text
properties in a similar way.

Which is the best approach depends mostly on how much code inside the
completion functions must be changed.

-- Stephe

reply via email to

[Prev in Thread] Current Thread [Next in Thread]