From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!not-for-mail From: Stephen Leake Newsgroups: gmane.emacs.devel Subject: Re: completing-read return meta-information? Date: Sat, 26 Sep 2015 05:54:52 -0500 Message-ID: <86twqh78n7.fsf@stephe-leake.org> References: <86y4g6zcuo.fsf@stephe-leake.org> <7c37cd21-a9e0-48fa-b5a2-a32595c43dda@default> <86twquxnpa.fsf@stephe-leake.org> <861tdrwwhx.fsf@stephe-leake.org> <86oaguv5sp.fsf@stephe-leake.org> NNTP-Posting-Host: plane.gmane.org Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Trace: ger.gmane.org 1443264965 29836 80.91.229.3 (26 Sep 2015 10:56:05 GMT) X-Complaints-To: usenet@ger.gmane.org NNTP-Posting-Date: Sat, 26 Sep 2015 10:56:05 +0000 (UTC) Cc: Stefan Monnier To: emacs-devel@gnu.org Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Sat Sep 26 12:55:54 2015 Return-path: Envelope-to: ged-emacs-devel@m.gmane.org Original-Received: from lists.gnu.org ([208.118.235.17]) by plane.gmane.org with esmtp (Exim 4.69) (envelope-from ) id 1Zfn8s-0002F2-GN for ged-emacs-devel@m.gmane.org; Sat, 26 Sep 2015 12:55:46 +0200 Original-Received: from localhost ([::1]:53239 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Zfn8s-0001mt-5U for ged-emacs-devel@m.gmane.org; Sat, 26 Sep 2015 06:55:46 -0400 Original-Received: from eggs.gnu.org ([2001:4830:134:3::10]:37094) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Zfn8T-0001ls-Cc for emacs-devel@gnu.org; Sat, 26 Sep 2015 06:55:22 -0400 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1Zfn8Q-0003ix-5Y for emacs-devel@gnu.org; Sat, 26 Sep 2015 06:55:21 -0400 Original-Received: from gproxy6-pub.mail.unifiedlayer.com ([67.222.39.168]:33190) by eggs.gnu.org with smtp (Exim 4.71) (envelope-from ) id 1Zfn8P-0003iD-SX for emacs-devel@gnu.org; Sat, 26 Sep 2015 06:55:18 -0400 Original-Received: (qmail 2192 invoked by uid 0); 26 Sep 2015 10:55:12 -0000 Original-Received: from unknown (HELO cmgw4) (10.0.90.85) by gproxy6.mail.unifiedlayer.com with SMTP; 26 Sep 2015 10:55:12 -0000 Original-Received: from host114.hostmonster.com ([74.220.207.114]) by cmgw4 with id Mmv61r00G2UdiVW01mv9Pf; Sat, 26 Sep 2015 04:55:12 -0600 X-Authority-Analysis: v=2.1 cv=IekUBwaa c=1 sm=1 tr=0 a=CQdxDb2CKd3SRg4I0/XZPQ==:117 a=CQdxDb2CKd3SRg4I0/XZPQ==:17 a=DsvgjBjRAAAA:8 a=f5113yIGAAAA:8 a=9i_RQKNPAAAA:8 a=hEr_IkYJT6EA:10 a=x_XPkuGwIRMA:10 a=ff-B7xzCdYMA:10 a=kfrkYPI7CHf0k4ZabtUA:9 a=tv_o5SnFUhpvbxgOBKEA:9 Original-Received: from [76.218.37.33] (port=57746 helo=TAKVER2) by host114.hostmonster.com with esmtpa (Exim 4.84) (envelope-from ) id 1Zfn8E-0004N9-7j; Sat, 26 Sep 2015 04:55:06 -0600 In-Reply-To: <86oaguv5sp.fsf@stephe-leake.org> (Stephen Leake's message of "Tue, 22 Sep 2015 10:21:10 -0500") User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/24.5 (windows-nt) X-Identified-User: {2442:host114.hostmonster.com:stephele:stephe-leake.org} {sentby:smtp auth 76.218.37.33 authed with stephen_leake@stephe-leake.org} X-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x X-Received-From: 67.222.39.168 X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.14 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.org@gnu.org Original-Sender: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Xref: news.gmane.org gmane.emacs.devel:190357 Archived-At: --=-=-= Content-Type: text/plain Stephen Leake writes: > Stefan Monnier writes: > >>> It also achieves the goal of returning an absolute string from >>> completing-read; that required advice on completing-read-default to call >>> the new function completion-get-data-string. >> >> Why can't you make completion-try-completion return an absolute filename >> when there's only one match? > > The string returned from completion-try-completion is displayed in the > minibuffer, as the completion prefix. I don't want that to be the > display string. > > Part of the point of using path completion is that you don't care what > directory the file is in. So having the absolute path displayed as the > final completion feels like the system forcing you to care. > > More importantly, the user can type at any time; then > test-completion should return t when it is passed the abbreviated > display string from the first completion; that is known to be a unique > valid completion. > > So this approach would require the user to always type tab, or go thru > one more confirmation step after typing . I tried this approach > early in this work, and could not make it work consistently. I took another stab at this, and got it to work; attached. The version with advice is cleaner, but not by a whole lot. The completion function calls a deuniquify function, and the top level client code calls `try-completion' after calling `completing-read'. The user sees an absolute filename in the completion buffer only if they type twice on a unique file. -- -- Stephe --=-=-= Content-Type: application/emacs-lisp Content-Disposition: attachment; filename=minibuffer-patches-2.el Content-Transfer-Encoding: quoted-printable ;; new completion style -*- lexical-binding:t -*- ;; There are two driving requirements for this completion style: ;; ;; - Allow the strings entered by the user and displayed in the ;; completion list to be abbreviations of the absolute file name ;; returned by completing-read. ;; ;; - Don't add advice to any existing functions. ;; ;; - Otherwise minize the amount of code in the completion table ;; function that deals with the user string format, ;; There are three string formats involved in completion. For most ;; style categories, they are all the same; the following table ;; describes them for the absfile category. ;; ;; - user ;; ;; The format typed by the user in the minibuffer, and shown in the ;; displayed completion list. ;; ;; The user input is passed to `completion-try-completion', so it must ;; accept this format. ;; ;; The string returned by `completion-try-completion' when it ;; extends the string replaces the string typed by the user. It must ;; be in data format in order to return the data format to the user; ;; see the "data" string format below. Thus the data format is also ;; displayed to the user when they have typed a unique item. ;; ;; The displayed completion list consists of the strings returned by ;; `completion-all-completions' with the common prefix deleted; ;; `completion-all-completions' must return strings in this format. ;; ;; When the user selects a displayed completion, the string is ;; passed to `test-completion'; it must accept strings in user ;; format and data format and return t. ;; ;; For the absfile category, this is a partial or complete file name ;; plus any required uniquifying directories, formatted according to ;; `find-absfile-uniquify-style' ;; ;; - completion table input ;; ;; The string input to the completion table function. ;; ;; We'd like this format to be independent of the uniquification ;; style, in order to eliminate style-specific code in the ;; completion table function. `completion-try-completion' and ;; `completion-all-completions' dispatch via ;; `completion-styles-alist' to a style-specific function. However, ;; `test-completion' does not. Thus `completion-try-completion' and ;; `completion-all-completions' can convert user format strings ;; before passing them to the completion table function, but ;; `test-completion' will pass user format strings. ;; ;; For the absfile category, if the user input string contains any ;; directory portion, or if action is 'lambda (called from ;; test-completion), this contains the complete absolute directory ;; name followed by the partial or complete file name. Otherwise it ;; is just the file name. ;; ;; - data ;; ;; The string format desired as the result of completing-read. ;; ;; The result of completing-read is selected by the user in two ways; ;; ;; - Type input characters and until the desired unique and ;; valid string is present in the buffer, then type . The ;; string is passed to `test-completion' to check it (it must ;; return t), then returned from `completing-read' ;; ;; uses completion-try-completion to extend the string in ;; the buffer; it actually replaces the entire buffer content. ;; ;; - With an incomplete string in the buffer, type ; this ;; selects the first item in the displayed completion list. That ;; string is then passed to `test-completion' and returned. ;; ;; `completion-try-completion' returns the data format when the ;; input identifies a unique item. Thus the data format is displayed ;; in the buffer when the user uses the first option above. ;; ;; When the user uses the second option above, `completing-read' ;; returns the user format; the client code must call ;; `completion-try-completion' with the user format string to ;; convert it to the data format. To avoid the client needing ;; style-specific code, `completion-try-completion' must also accept ;; data format strings and return t. ;; ;; The completion table returns a list of strings in this format ;; when action is t; `completion-all-completions' converts them to ;; user strings. ;; ;; For the absfile category, this is an absolute file name. (defvar find-absfile-uniquify-style 'abbrev "Style used to format uniquifying directories. One of: - 'abbrev : minimal directories required to identify a unique file (may be = empty) - 'full : absolute directory path or empty") (defconst find-absfile-uniquify-regexp "^\\(.*\\)<\\(.*\\)>" "Regexp matching uniqufied file name. Match 1 is the filename, match 2 is the relative directory.") (defun find-absfile-uniquify-conflicts (conflicts) "Subroutine of `find-file-uniquify'." (let ((common-root ;; shared prefix of dirs in conflicts - may be nil (fill-common-string-prefix (file-name-directory (nth 0 conflicts)) (file-= name-directory (nth 1 conflicts))))) (let ((temp (cddr conflicts))) (while (and common-root temp) (setq common-root (fill-common-string-prefix common-root (file-name-direct= ory (pop temp)))))) (when common-root ;; Trim `common-root' back to last '/' (let ((i (1- (length common-root)))) (while (and (> i 0) (not (=3D (aref common-root i) ?/))) (setq i (1- i))) (setq common-root (substring common-root 0 (1+ i))))) (cl-mapcar (lambda (name) (concat (file-name-nondirectory name) "<" (substring (file-name-directory name) (length common-root)) ">"= )) conflicts) )) (defun find-absfile-uniquify (names) "Return a uniquified list of names built from NAMES. NAMES contains absolute file names. The result contains non-directory filenames with partial directory paths appended." (cl-ecase find-absfile-uniquify-style (abbrev (let (result conflicts ;; list of names where all non-directory names are the same. ) ;; Sort names so duplicates are grouped together (setq names (sort names (lambda (a b) (string< (file-name-nondirectory a) (file-name-nondirectory b))))) (while names (setq conflicts (list (pop names))) (while (and names (string=3D (file-name-nondirectory (car conflicts)) (file-name-nondi= rectory (car names)))) (push (pop names) conflicts)) (if (=3D 1 (length conflicts)) (push (file-name-nondirectory (car conflicts)) result) (setq result (append (find-absfile-uniquify-conflicts conflicts) result= ))) ) (nreverse result) )) (full names) )) (defun find-absfile-deuniquify (path name &optional force) "Convert display string NAME to table input string. PATH is used to fill in missing directories. If FORCE is non-nil (default nil), NAME must exist somewhere in PATH; always return absolute filename." (let ((match (string-match find-absfile-uniquify-regexp name))) (if (or match force) (let ((dirname (or (when match (match-string 2 name)) "")) (filename (or (when match (match-string 1 name)) name)) (temp-path path) dir matching-dir) (cond ((and (not force) (=3D 0 (length dirname))) filename) ((or force (< 0 (length dirname))) ;; Extend dirname to absolute directory name (while (and temp-path (not matching-dir)) ;; `path' should only have directories (ie end in slash), but ;; since it is often built by hand, it may not. (setq dir (file-name-as-directory (pop temp-path))) ;; FIXME: not doing completion on partial directory names yet. (cond (match (when (string-match dirname dir) (setq matching-dir dir))) (force (when (file-exists-p (concat dir name));; FIXME: Requires a flat path or = a recursive path iterator (setq matching-dir dir))) ))) ) (concat matching-dir filename)) ;; else not uniquified name))) (defun completion-uniquify-absfile-try-completion (string table pred point) "Implement `completion-try-completion' for uniquify-absfile." ;; Trivial try-completion backend; don't handle boundaries yet (let* ((beforepoint (substring string 0 point)) (completion (try-completion (completion-absfile-deuniquify beforepoint ta= ble pred) table pred))) (cond ((stringp completion) ;; multiple matches, or extending string to a single match (cons completion (length completion))) ((null completion) ;; no match completion) (t (if (string-match find-absfile-uniquify-regexp string) (progn (setq completion (completion-absfile-deuniquify string table pred t)) (cons completion (length completion))) t)) ))) (defun completion-uniquify-absfile-all-completions (string table pred point) "Implement `completion-all-completions' for uniquify-absfile." ;; Trivial all-completion backend; don't handle boundaries yet (find-absfile-uniquify (all-completions (completion-absfile-deuniquify (substring string 0 point) table pred) table pred))) (defun completion-absfile-deuniquify (display-string table pred &optional f= orce) "Implement `completion-get-data-string' for uniquify-absfile." (find-absfile-deuniquify (completion-metadata-get (completion-metadata di= splay-string table pred) 'path) display-string force)) (add-to-list 'completion-category-defaults '(absfile (styles . (uniquify-ab= sfile)))) (add-to-list 'completion-styles-alist '(uniquify-absfile completion-uniquify-absfile-try-completion completion-uniquify-absfile-all-completions "display uniquified filenames.")) (defun find-absfile-completion-table (path string _pred action) "Do completion for file names in `find-absfile'. PATH is a list of strings containing absolute directory names to be searched. STRING is the current user input. PRED is ignored. `completion-ignored-extensions', `completion-regexp-list', `completion-ignore-case' are respected. ACTION is the current completion action; one of: - nil; return common prefix, nil or t; see `try-completion' - t; return all completions; see `all-completions' - lambda; return non-nil if string is a valid completion; see `test-completion'. - '(boundaries . SUFFIX); return the completion region '(boundaries START . END) within STRING; see `completion-boundaries'. - 'metadata; return (metadata . ALIST) as defined by `completion-metadata'. In addition, returns `path' metadata, for use by `completion-absfile-deuniquify'." (cond ((eq (car-safe action) 'boundaries) ;; FIXME: not doing completion on partial directory names yet (cons 'boundaries (cons 0 (length (cdr action))))) ((eq action 'metadata) (cons 'metadata (list '(category . absfile) (cons 'path path)))) ((eq action 'lambda) ;; Called from test-completion (let ((abs-string (find-absfile-deuniquify path string t))) (and (file-name-absolute-p abs-string) (file-exists-p abs-string)))) (t ;; `action' is one of: ;; t - return all completions ;; nil - return common prefix, nil or t (let* ((abs-string (find-absfile-deuniquify path string t)) (dirname (file-name-directory abs-string)) (filename (file-name-nondirectory abs-string)) (result nil)) ;; list of abs filenames (dolist (dir path) ;; `path' is often built by hand, so it may contain names that ;; do not exist on the disk; check for that. It may not end in ;; "/"; file-name-as-directory ensures that. On Windows, it ;; may not contain the leading device (ie "c:"); ;; expand-file-name ensures that. `find-absfile-deuniquify' ;; and `file-name-directory' ensure that `dirname' has the ;; device and ends in "/". (when (file-directory-p dir) (setq dir (expand-file-name (file-name-as-directory dir))) (when (or (=3D 0 (length dirname)) (string-equal dir dirname)) (setq result (append result (cl-mapcar (lambda (file) (if action (concat dir file) file)) (file-name-all-completions filename dir)))) ))) (if action ;; Called from `all-completions' or`test-completion'. result ;; else called from `try-completion' (cond ((null result) nil) ((=3D 1 (length result)) ;; Unique; check for exact. (cond ((and (file-name-absolute-p string) (file-exists-p string)) ;; `string' is in data format; exact t) ((and (file-name-absolute-p abs-string) (file-exists-p abs-string)) ;; `string' is in user format; it is exact, but must be converted to d= ata format abs-string) (t ;; not exact; return extended string (car result)) )) (t (let ((temp (try-completion abs-string result))) (if (eq t temp) ;; If result is all duplicates, `try-completion' returns t; return just o= ne. (list (car result)) temp))) ))) ) )) (defun find-absfile (path) "Find a file with completion in PATH." (let* ((table (apply-partially 'find-absfile-completion-table path)) (string (completing-read "file: " table nil t "")) (comp (completion-try-completion string table nil (length string)))) (unless (eq t comp) ;; `completion-try-completion' returns t when `string' is an absolute= file name (setq string (car (completion-try-completion string table nil (length= string))))) (find-file string) )) (provide 'minibuffer-patches) --=-=-=--