diff options
| author | Dmitry Gutov | 2019-05-14 05:09:19 +0300 |
|---|---|---|
| committer | Dmitry Gutov | 2019-05-14 05:11:18 +0300 |
| commit | e0ee41d155b210327eb9c9ad5334f80ed59439f4 (patch) | |
| tree | 563710e61b14362454a11c33fbb348c0066d9a7a | |
| parent | 9b28a5083edecacfac3c7e16308bd8af3f4773a2 (diff) | |
| download | emacs-e0ee41d155b210327eb9c9ad5334f80ed59439f4.tar.gz emacs-e0ee41d155b210327eb9c9ad5334f80ed59439f4.zip | |
Allow customizing the display of project file names when reading
To hopefully resolve a long-running discussion
(https://lists.gnu.org/archive/html/emacs-devel/2019-05/msg00162.html).
* lisp/progmodes/project.el (project-read-file-name-function):
New variable.
(project--read-file-absolute, project--read-file-cpd-relative):
New functions, possible values for the above.
(project-find-file-in): Use the introduced variable.
(project--completing-read-strict): Retain just the logic that fits
the name.
| -rw-r--r-- | etc/NEWS | 2 | ||||
| -rw-r--r-- | lisp/minibuffer.el | 2 | ||||
| -rw-r--r-- | lisp/progmodes/project.el | 95 |
3 files changed, 57 insertions, 42 deletions
| @@ -1983,6 +1983,8 @@ returns a regexp that never matches anything, which is an identity for | |||
| 1983 | this operation. Previously, the empty string was returned in this | 1983 | this operation. Previously, the empty string was returned in this |
| 1984 | case. | 1984 | case. |
| 1985 | 1985 | ||
| 1986 | ** New variable project-read-file-name-function. | ||
| 1987 | |||
| 1986 | 1988 | ||
| 1987 | * Changes in Emacs 27.1 on Non-Free Operating Systems | 1989 | * Changes in Emacs 27.1 on Non-Free Operating Systems |
| 1988 | 1990 | ||
diff --git a/lisp/minibuffer.el b/lisp/minibuffer.el index dbd24dfa0a3..d11a5cf574d 100644 --- a/lisp/minibuffer.el +++ b/lisp/minibuffer.el | |||
| @@ -846,6 +846,8 @@ styles for specific categories, such as files, buffers, etc." | |||
| 846 | (defvar completion-category-defaults | 846 | (defvar completion-category-defaults |
| 847 | '((buffer (styles . (basic substring))) | 847 | '((buffer (styles . (basic substring))) |
| 848 | (unicode-name (styles . (basic substring))) | 848 | (unicode-name (styles . (basic substring))) |
| 849 | ;; A new style that combines substring and pcm might be better, | ||
| 850 | ;; e.g. one that does not anchor to bos. | ||
| 849 | (project-file (styles . (substring))) | 851 | (project-file (styles . (substring))) |
| 850 | (info-menu (styles . (basic substring)))) | 852 | (info-menu (styles . (basic substring)))) |
| 851 | "Default settings for specific completion categories. | 853 | "Default settings for specific completion categories. |
diff --git a/lisp/progmodes/project.el b/lisp/progmodes/project.el index 7c8ca15868e..ddb4f3354cd 100644 --- a/lisp/progmodes/project.el +++ b/lisp/progmodes/project.el | |||
| @@ -157,19 +157,13 @@ end it with `/'. DIR must be one of `project-roots' or | |||
| 157 | vc-directory-exclusion-list) | 157 | vc-directory-exclusion-list) |
| 158 | grep-find-ignored-files)) | 158 | grep-find-ignored-files)) |
| 159 | 159 | ||
| 160 | (cl-defgeneric project-file-completion-table (project dirs) | 160 | (defun project--file-completion-table (all-files) |
| 161 | "Return a completion table for files in directories DIRS in PROJECT. | 161 | (lambda (string pred action) |
| 162 | DIRS is a list of absolute directories; it should be some | 162 | (cond |
| 163 | subset of the project roots and external roots. | 163 | ((eq action 'metadata) |
| 164 | 164 | '(metadata . ((category . project-file)))) | |
| 165 | The default implementation delegates to `project-files'." | 165 | (t |
| 166 | (let ((all-files (project-files project dirs))) | 166 | (complete-with-action action all-files string pred))))) |
| 167 | (lambda (string pred action) | ||
| 168 | (cond | ||
| 169 | ((eq action 'metadata) | ||
| 170 | '(metadata . ((category . project-file)))) | ||
| 171 | (t | ||
| 172 | (complete-with-action action all-files string pred)))))) | ||
| 173 | 167 | ||
| 174 | (cl-defmethod project-roots ((project (head transient))) | 168 | (cl-defmethod project-roots ((project (head transient))) |
| 175 | (list (cdr project))) | 169 | (list (cdr project))) |
| @@ -470,55 +464,72 @@ recognized." | |||
| 470 | (project-external-roots pr)))) | 464 | (project-external-roots pr)))) |
| 471 | (project-find-file-in (thing-at-point 'filename) dirs pr))) | 465 | (project-find-file-in (thing-at-point 'filename) dirs pr))) |
| 472 | 466 | ||
| 467 | (defcustom project-read-file-name-function #'project--read-file-cpd-relative | ||
| 468 | "Function to call to read a file name from a list. | ||
| 469 | For the arguments list, see `project--read-file-cpd-relative'." | ||
| 470 | :type '(repeat (choice (const :tag "Read with completion from relative names" | ||
| 471 | project--read-file-cpd-relative) | ||
| 472 | (const :tag "Read with completion from absolute names" | ||
| 473 | project--read-file-absolute) | ||
| 474 | (function :tag "custom function" nil)))) | ||
| 475 | |||
| 476 | (defun project--read-file-cpd-relative (prompt | ||
| 477 | all-files &optional predicate | ||
| 478 | hist default) | ||
| 479 | (let* ((common-parent-directory | ||
| 480 | (let ((common-prefix (try-completion "" all-files))) | ||
| 481 | (if (> (length common-prefix) 0) | ||
| 482 | (file-name-directory common-prefix)))) | ||
| 483 | (cpd-length (length common-parent-directory)) | ||
| 484 | (prompt (if (zerop cpd-length) | ||
| 485 | prompt | ||
| 486 | (concat prompt (format " in %s" common-parent-directory)))) | ||
| 487 | (substrings (mapcar (lambda (s) (substring s cpd-length)) all-files)) | ||
| 488 | (new-collection (project--file-completion-table substrings)) | ||
| 489 | (res (project--completing-read-strict prompt | ||
| 490 | new-collection | ||
| 491 | predicate | ||
| 492 | hist default))) | ||
| 493 | (concat common-parent-directory res))) | ||
| 494 | |||
| 495 | (defun project--read-file-absolute (prompt | ||
| 496 | all-files &optional predicate | ||
| 497 | hist default) | ||
| 498 | (project--completing-read-strict prompt | ||
| 499 | (project--file-completion-table all-files) | ||
| 500 | predicate | ||
| 501 | hist default)) | ||
| 502 | |||
| 473 | (defun project-find-file-in (filename dirs project) | 503 | (defun project-find-file-in (filename dirs project) |
| 474 | "Complete FILENAME in DIRS in PROJECT and visit the result." | 504 | "Complete FILENAME in DIRS in PROJECT and visit the result." |
| 475 | (let* ((table (project-file-completion-table project dirs)) | 505 | (let* ((all-files (project-files project dirs)) |
| 476 | (file (project--completing-read-strict | 506 | (file (funcall project-read-file-name-function |
| 477 | "Find file" table nil nil | 507 | "Find file" all-files nil nil |
| 478 | filename))) | 508 | filename))) |
| 479 | (if (string= file "") | 509 | (if (string= file "") |
| 480 | (user-error "You didn't specify the file") | 510 | (user-error "You didn't specify the file") |
| 481 | (find-file file)))) | 511 | (find-file file)))) |
| 482 | 512 | ||
| 483 | (defun project--completing-read-strict (prompt | 513 | (defun project--completing-read-strict (prompt |
| 484 | collection &optional predicate | 514 | collection &optional predicate |
| 485 | hist default inherit-input-method) | 515 | hist default) |
| 486 | ;; Tried both expanding the default before showing the prompt, and | 516 | ;; Tried both expanding the default before showing the prompt, and |
| 487 | ;; removing it when it has no matches. Neither seems natural | 517 | ;; removing it when it has no matches. Neither seems natural |
| 488 | ;; enough. Removal is confusing; early expansion makes the prompt | 518 | ;; enough. Removal is confusing; early expansion makes the prompt |
| 489 | ;; too long. | 519 | ;; too long. |
| 490 | (let* ((common-parent-directory | 520 | (let* ((new-prompt (if default |
| 491 | (let ((common-prefix (try-completion "" collection))) | ||
| 492 | (if (> (length common-prefix) 0) | ||
| 493 | (file-name-directory common-prefix)))) | ||
| 494 | (cpd-length (length common-parent-directory)) | ||
| 495 | (prompt (if (zerop cpd-length) | ||
| 496 | prompt | ||
| 497 | (concat prompt (format " in %s" common-parent-directory)))) | ||
| 498 | ;; XXX: This requires collection to be "flat" as well. | ||
| 499 | (substrings (mapcar (lambda (s) (substring s cpd-length)) | ||
| 500 | (all-completions "" collection))) | ||
| 501 | (new-collection | ||
| 502 | (lambda (string pred action) | ||
| 503 | (cond | ||
| 504 | ((eq action 'metadata) | ||
| 505 | (if (functionp collection) (funcall collection nil nil 'metadata))) | ||
| 506 | (t | ||
| 507 | (complete-with-action action substrings string pred))))) | ||
| 508 | (new-prompt (if default | ||
| 509 | (format "%s (default %s): " prompt default) | 521 | (format "%s (default %s): " prompt default) |
| 510 | (format "%s: " prompt))) | 522 | (format "%s: " prompt))) |
| 511 | (res (completing-read new-prompt | 523 | (res (completing-read new-prompt |
| 512 | new-collection predicate t | 524 | collection predicate t |
| 513 | nil ;; initial-input | 525 | nil ;; initial-input |
| 514 | hist default inherit-input-method))) | 526 | hist default))) |
| 515 | (when (and (equal res default) | 527 | (when (and (equal res default) |
| 516 | (not (test-completion res collection predicate))) | 528 | (not (test-completion res collection predicate))) |
| 517 | (setq res | 529 | (setq res |
| 518 | (completing-read (format "%s: " prompt) | 530 | (completing-read (format "%s: " prompt) |
| 519 | new-collection predicate t res hist nil | 531 | collection predicate t res hist nil))) |
| 520 | inherit-input-method))) | 532 | res)) |
| 521 | (concat common-parent-directory res))) | ||
| 522 | 533 | ||
| 523 | (declare-function fileloop-continue "fileloop" ()) | 534 | (declare-function fileloop-continue "fileloop" ()) |
| 524 | 535 | ||