all messages for Emacs-related lists mirrored at yhetil.org
 help / color / mirror / code / Atom feed
From: Stephen Leake <stephen_leake@stephe-leake.org>
To: emacs-devel@gnu.org
Subject: Re: completing-read return meta-information?
Date: Wed, 16 Sep 2015 12:45:37 -0500	[thread overview]
Message-ID: <86twquxnpa.fsf@stephe-leake.org> (raw)
In-Reply-To: <7c37cd21-a9e0-48fa-b5a2-a32595c43dda@default> (Drew Adams's message of "Wed, 16 Sep 2015 08:00:39 -0700 (PDT)")

Drew Adams <drew.adams@oracle.com> 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))
	 (predicate
	  (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
	  (completing-read
	   "file: " ;; prompt
	   (completion-table-dynamic (apply-partially
              'find-file-path-completion-table
              flat-path predicate))
	   nil
	   t ;; require match
	   filename
	   ))

    ;; 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
			dir
			"/"
			(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
	  (completing-read
	   "file: " ;; prompt
	   (completion-table-with-cache #'find-file-complete-global-table) ;; collection
	   nil ;; predicate
	   t ;; require match
	   filename
	   ))

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

    ;; 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" filename))
      (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:

locate{.rnc</Projects/emacs/master/etc/schema/>
.el</Projects/emacs/master/lisp/cedet/ede/>
.elc</Projects/emacs/master/lisp/cedet/ede/>
.el</Projects/emacs/master/lisp/>
.elc</Projects/emacs/master/lisp/>}

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
    property.

    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



  parent reply	other threads:[~2015-09-16 17:45 UTC|newest]

Thread overview: 21+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2015-09-16 13:57 completing-read return meta-information? Stephen Leake
2015-09-16 15:00 ` Drew Adams
2015-09-16 17:06   ` Stephen Leake
2015-09-16 17:28     ` Drew Adams
2015-09-16 17:45   ` Stephen Leake [this message]
2015-09-17  1:38     ` Stefan Monnier
2015-09-17 12:52       ` Stephen Leake
2015-09-17 13:24         ` Stefan Monnier
2015-09-17 18:14           ` Dmitry Gutov
2015-09-18  0:54             ` Stefan Monnier
2015-09-21 16:46       ` Stephen Leake
2015-09-21 19:12         ` Stephen Leake
2015-09-21 19:51         ` Stefan Monnier
2015-09-22 15:21           ` Stephen Leake
2015-09-26 10:54             ` Stephen Leake
2015-09-27 15:45               ` Stefan Monnier
2015-09-28  5:50                 ` Dmitry Gutov
2015-09-28 15:59                 ` Stephen Leake
2015-09-28 16:13                 ` Stephen Leake
2015-09-28 19:42                   ` Stefan Monnier
2015-09-28  5:58             ` Dmitry Gutov

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

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

  git send-email \
    --in-reply-to=86twquxnpa.fsf@stephe-leake.org \
    --to=stephen_leake@stephe-leake.org \
    --cc=emacs-devel@gnu.org \
    /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 external index

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

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.