all messages for Emacs-related lists mirrored at yhetil.org
 help / color / mirror / code / Atom feed
* find-file-project
@ 2015-09-15 20:23 Stephen Leake
  2015-09-15 22:21 ` find-file-project Dmitry Gutov
  0 siblings, 1 reply; 162+ messages in thread
From: Stephen Leake @ 2015-09-15 20:23 UTC (permalink / raw)
  To: emacs-devel

[-- Attachment #1: Type: text/plain, Size: 604 bytes --]

Attached is a patch that implements find-file-project, with completion
of file-name on the project search path. It handles duplicate filenames
by uniquifying them witht trailing directory names.

The patch also adds small projects for elisp and global, to show that
this approach works for multiple backends.

Comments?

I can break this into smaller commits on master, if that seems like a
good idea.

I didn't add a NEWS entry. I don't think we are putting project related
changes in NEWS yet, since it is all new in Emacs 25. But the
file-name-all-completion change needs a NEWS entry.

-- 
-- Stephe

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: find-file-project.diff --]
[-- Type: text/x-patch, Size: 35919 bytes --]

diff --git a/lisp/cedet/cedet-global.el b/lisp/cedet/cedet-global.el
index 3773ba0..9fb46a0 100644
--- a/lisp/cedet/cedet-global.el
+++ b/lisp/cedet/cedet-global.el
@@ -120,6 +120,60 @@ Return a fully qualified filename."
 	(error "No file found")))
     ans))
 
+(defun find-file-complete-global-table (prefix)
+  "Do completion for file names in `find-file-complete-global'"
+  ;; Returned paths are relative to default-directory
+  (cond
+   ((string-match find-file-uniquify-regexp prefix)
+    ;; User has selected one match; return it.
+    (list prefix))
+
+   (t
+    (let* ((paths ;; Matching relative paths, as returned by global.
+	    (with-current-buffer (cedet-gnu-global-call (list "--ignore-case" "-P" prefix))
+	      (split-string (buffer-substring (point-min) (point-max)) "\n" t)))
+	   (dir-names
+	    (cl-mapcar (lambda (path) (cons (file-name-directory path) (file-name-nondirectory path)))
+		       paths))
+	   )
+
+      ;; "global -P `prefix'" matches in middle of the file name, and
+      ;; in the directory portion. The calling completion function
+      ;; rejects any completions that don't start with `prefix'.
+
+      (find-file-uniquify dir-names)
+      ))
+   ))
+
+(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)))
+
+    )
+
 (defun cedet-gnu-global-show-root ()
   "Show the root of a GNU Global area under the current buffer."
   (interactive)
@@ -193,6 +247,19 @@ If a database already exists, then just update it."
       )
     ))
 
+;;; project.el integration
+
+(defun project-try-global (dir)
+  (when (cedet-gnu-global-version-check t)
+    (let ((root (locate-dominating-file dir "GTAGS")))
+      (when root
+	(list 'global root)))))
+
+(cl-defmethod project-find-file ((prj (head global)) filename)
+  (let ((default-directory (file-name-as-directory (nth 1 prj))))
+    (find-file (find-file-complete-global filename))))
+
+
 (provide 'cedet-global)
 
 ;;; cedet-global.el ends here
diff --git a/lisp/files.el b/lisp/files.el
index c309f86..ad4fb4b 100644
--- a/lisp/files.el
+++ b/lisp/files.el
@@ -1691,6 +1691,85 @@ killed."
 	;; We already ran these; don't run them again.
 	(let (kill-buffer-query-functions kill-buffer-hook)
 	  (kill-buffer obuf))))))
+
+(defconst find-file-uniquify-regexp "^\\(.*\\)<\\(.*\\)>"
+  "Regexp matching uniqufied file name.
+Match 1 is the filename, match 2 is the relative directory.")
+
+(defun find-file-uniquify-conflicts (conflicts)
+  "Subroutine of `find-file-uniquify'."
+  (let ((common-root ;; shared prefix of dirs in conflicts - may be nil
+	 (fill-common-string-prefix (car (nth 0 conflicts)) (car (nth 1 conflicts)))))
+
+    (let ((temp (cddr conflicts))
+	  dir-name)
+      (while (and common-root
+		  temp)
+	(setq dir-name (pop temp))
+	(setq common-root (fill-common-string-prefix common-root (car dir-name)))))
+
+    (when common-root
+      ;; Trim `common-root' back to last '/'
+      (let ((i (1- (length common-root))))
+	(while (and (> i 0)
+		    (not (= (aref common-root i) ?/)))
+	  (setq i (1- i)))
+	(setq common-root (substring common-root 0 (1+ i)))))
+
+    (cl-mapcar
+     (lambda (dir-name)
+       (concat (cdr dir-name)
+	       "<" (substring (car dir-name) (length common-root)) ">"))
+     conflicts)
+    ))
+
+(defun find-file-uniquify (dir-names)
+  "Return a flat list of names from DIR-NAMES with duplicate filenames extended by directories.
+DIR-NAMES is a list of (dir . name)."
+  (let (result
+	conflicts ;; list of (dir . name) where all `name' are the same.
+	)
+
+    ;; Sort dir-names so duplicates are grouped together
+    (setq dir-names (sort dir-names (lambda (a b)
+				      (string< (cdr a) (cdr b)))))
+
+    (while dir-names
+      (setq conflicts (list (pop dir-names)))
+      (while (string= (cdr (car conflicts)) (cdr (car dir-names)))
+	(push (pop dir-names) conflicts))
+
+      (if (= 1 (length conflicts))
+	  (push (cdr (car conflicts)) result)
+	(setq result (append (find-file-uniquify-conflicts conflicts) result)))
+      )
+    (nreverse result)
+    ))
+
+(defun find-file-path-completion-table (path predicate prefix)
+  "Do completion for file names in `find-file-project'."
+  (cond
+   ((string-match find-file-uniquify-regexp prefix)
+    ;; User has selected one match; return it.
+    (list prefix))
+
+   ;; FIXME: handle prefix = "Makefile<dvc"; show /Projects/org.emacs.dvc/lisp/Makefile
+
+   (t
+    (let ((dir-names nil)) ;; list of (dir . name), where `name' is a completion
+      (dolist (dir path)
+        (when (file-directory-p dir)
+	  ;; `file-name-all-completions' applies
+	  ;; `completion-ignored-extensions' and `completion-regexp-list'
+          (setq dir-names
+		(append dir-names
+			(cl-mapcar (lambda (filename) (cons dir filename))
+				   (file-name-all-completions prefix dir predicate))))
+	  ))
+
+      (find-file-uniquify dir-names)))
+   ))
+
 \f
 ;; FIXME we really need to fold the uniquify stuff in here by default,
 ;; not using advice, and add it to the doc string.
diff --git a/lisp/progmodes/project.el b/lisp/progmodes/project.el
index 186840a..e9abf17 100644
--- a/lisp/progmodes/project.el
+++ b/lisp/progmodes/project.el
@@ -76,6 +76,101 @@ should take into account the value returned by
     (project-roots project)
     (funcall project-search-path-function))))
 
+;; Conversion between recursive and flat paths.
+(defun project--directory-directories-1 (dir ignore-regexp)
+  "Return a list of all directories in DIR (non-recursively).
+Ignores directories that match IGNORE-REGEXP (a regular expression).
+DIR must not end in ?/."
+  (let ((dirs (directory-files dir t nil t))
+	(dir-dot (concat dir "/."))
+	(dir-dotdot (concat dir "/.."))
+	result)
+    (while dirs
+      (let ((dir1 (pop dirs)))
+	(when (file-directory-p dir1)
+	  (unless (or
+		   (string= dir-dot dir1)
+		   (string= dir-dotdot dir1)
+		   (and ignore-regexp
+			(string-match ignore-regexp dir1)))
+	    (push dir1 result)))))
+    result))
+
+
+(defun project--directory-directories-recurse (dir ignore-regexp)
+  "Return a list of all directories in DIR, recursively.
+Ignores directories that match IGNORE-REGEXP (a regular expression).
+DIR must not end in ?/."
+  (let ((dirs (project--directory-directories-1 dir ignore-regexp))
+	result dir)
+    (while dirs
+      (setq dir (pop dirs))
+      (push dir result)
+      (setq result (append (project--directory-directories-recurse dir ignore-regexp) result)))
+
+    result))
+
+(defun project-recursive-ignores-to-flat (recursive-path ignore-dirs)
+  "Return a flat path constructed from RECURSIVE-PATH and IGNORE-DIRS."
+  (let ((dirs recursive-path)
+	(ignore-regexp (regexp-opt ignore-dirs))
+	result dir)
+
+    (while dirs
+      (setq dir (pop dirs))
+      (push dir result)
+      (setq result (append (project--directory-directories-recurse (directory-file-name dir) ignore-regexp) result)))
+
+    result))
+
+(defun project-flat-to-recursive-ignores (flat-path)
+  "Return a cons (RECURSIVE-PATH . IGNORE-DIRS) computed from FLAT-PATH."
+  (let* ((dirs (mapcar
+                 (lambda (dir)
+                   (file-name-as-directory (expand-file-name dir)))
+                 flat-path))
+	 search-path
+	 include-dirs
+	 ignore-dirs
+	 root)
+
+    ;; FIXME: combine with above loop?
+    (cl-delete-if-not #'file-exists-p dirs)
+
+    ;; Recursively delete subdirectories under a parent that is also
+    ;; in dirs. Add remaining to search-path, add unmentioned dirs to
+    ;; ignores.
+    ;;
+    ;; Start by sorting by name, so the roots that are
+    ;; present are just before their children.
+    (setq dirs (sort dirs (lambda (a b) (string< a b))))
+
+    (while (setq root (pop dirs))
+      (setq include-dirs nil)
+      (while (string-prefix-p root (car dirs))
+	(push (pop dirs) include-dirs))
+      (push root search-path)
+      (dolist (subdir (directory-files root t))
+	(when (file-directory-p subdir)
+	  (unless (or
+		   (string= (substring subdir -1) ".")
+		   (string= (substring subdir -2) "..")
+		   (member (file-name-as-directory subdir) include-dirs))
+	    (push (file-name-as-directory subdir) ignore-dirs)))))
+
+    (cons search-path ignore-dirs)
+    ))
+
+(cl-defgeneric project-ignore-dirs (_prj)
+  "Return list of absolute directory names that should be ignored.
+The names should be matched to directories when searching in
+`project-search-path'."
+  nil)
+
+(cl-defgeneric project-flat-search-path (prj)
+  "Return a flat search path for PRJ."
+  (project-recursive-ignores-to-flat (project-search-path prj) (project-ignore-dirs prj)))
+
 (cl-defgeneric project-roots (project)
   "Return the list of directory roots related to the current project.
 It should include the current project root, as well as the roots
@@ -83,6 +178,7 @@ of any other currently open projects, if they're meant to be
 edited together.  The directory names should be absolute.")
 
 (cl-defgeneric project-ignores (_project _dir)
+  ;; FIXME: do we need both project-ignores and project-ignore-files-globs?
   "Return the list of glob patterns to ignore inside DIR.
 Patterns can match both regular files and directories.
 To root an entry, start it with `./'.  To match directories only,
@@ -97,6 +193,79 @@ an element of `project-search-path'."
     vc-directory-exclusion-list)
    grep-find-ignored-files))
 
+(cl-defgeneric project-ignore-files-globs (_prj)
+  "Return list of file glob patterns that should be ignored in all directories.
+The globs should be matched to file and directory names sans path
+when searching in `project-search-path'."
+  (require 'grep)
+  (defvar grep-find-ignored-files)
+  (append
+    vc-directory-exclusion-list ;; preloaded
+    grep-find-ignored-files))
+
+(cl-defgeneric project-ignore-files-regexp (prj)
+  "Return a regular expression equivalent to `project-ignore-files-globs'."
+  (let ((globs (project-ignore-files-globs prj)))
+    (cond
+     ((= 1 (length globs))
+      (dired-glob-regexp (car globs)))
+
+     (t
+      (concat "\\(" (mapconcat #'dired-glob-regexp globs "\\)\\|\\(") "\\)"))
+     )))
+
+(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)
+	    (not (string-match regexp filename)))))
+
+    (setq filename
+	  (completing-read
+	   "file: " ;; prompt
+	   (completion-table-dynamic (apply-partially 'find-file-path-completion-table flat-path predicate))
+	   nil
+	   t ;; require match
+	   filename
+	   ))
+
+    ;; If text properties were preserved by completing-read, we could
+    ;; store the full directory in a text property on the
+    ;; conflicts. But they are not, so 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-project (filename)
+  "Find FILENAME (default prompt) with completion in current project.
+With prefix arg, FILENAME defaults to filename at point."
+  (interactive (list (when current-prefix-arg (thing-at-point 'filename))))
+  (project-find-file (project-current) filename))
+
+
 (defgroup project-vc nil
   "Project implementation using the VC package."
   :group 'tools)
diff --git a/src/dired.c b/src/dired.c
index 9773667..5bb7c80 100644
--- a/src/dired.c
+++ b/src/dired.c
@@ -349,7 +349,7 @@ If NOSORT is non-nil, the list is not sorted--its order is unpredictable.
   handler = Ffind_file_name_handler (directory, Qdirectory_files);
   if (!NILP (handler))
     return call5 (handler, Qdirectory_files, directory,
-                  full, match, nosort);
+		  full, match, nosort);
 
   return directory_files_internal (directory, full, match, nosort, 0, Qnil);
 }
@@ -420,10 +420,12 @@ determined by the variable `completion-ignored-extensions', which see.  */)
 }
 
 DEFUN ("file-name-all-completions", Ffile_name_all_completions,
-       Sfile_name_all_completions, 2, 2, 0,
+       Sfile_name_all_completions, 2, 3, 0,
        doc: /* Return a list of all completions of file name FILE in directory DIRECTORY.
-These are all file names in directory DIRECTORY which begin with FILE.  */)
-  (Lisp_Object file, Lisp_Object directory)
+These are all file names in directory DIRECTORY which begin with FILE,
+and for which PREDICATE returns non-nil. If PREDICATE is nil (the default),
+it is ignored.*/)
+  (Lisp_Object file, Lisp_Object directory, Lisp_Object predicate)
 {
   Lisp_Object handler;
   directory = Fexpand_file_name (directory, Qnil);
@@ -440,7 +442,7 @@ These are all file names in directory DIRECTORY which begin with FILE.  */)
   if (!NILP (handler))
     return call3 (handler, Qfile_name_all_completions, file, directory);
 
-  return file_name_completion (file, directory, 1, Qnil);
+  return file_name_completion (file, directory, 1, predicate);
 }
 
 static int file_name_completion_stat (int, struct dirent *, struct stat *);
diff --git a/test/automated/files.el b/test/automated/files.el
index 0522e0c..789fb01 100644
--- a/test/automated/files.el
+++ b/test/automated/files.el
@@ -166,6 +166,73 @@ form.")
       (delete-file tempfile))))
 
 
+(ert-deftest find-file-path-completion-table-duplicates ()
+  "Test completion when there are two files with the same name in
+different directories on path."
+  (let* ((root (make-temp-file "find-file-path-test" t))
+	 (dir1 (concat root "/dir1"))
+	 (dir2 (concat root "/dir2")))
+
+    (mkdir dir1)
+    (mkdir dir2)
+
+    (with-temp-file (concat dir1 "/file1.el")
+      (insert "dir1/file1.el"))
+    (with-temp-file (concat dir1 "/file2.el")
+      (insert "dir1/file2.el"))
+
+    (with-temp-file (concat dir2 "/file1.el")
+      (insert "dir2/file1.el"))
+    (with-temp-file (concat dir2 "/file3.el")
+      (insert "dir2/file3.el"))
+
+    ;; multiple completions, some with same name
+    (should (equal (find-file-path-completion-table (list dir1 dir2) nil "fi")
+		   '("file1.el<dir1>" "file1.el<dir2>" "file2.el" "file3.el")))
+
+    ;; only one completion (after user selects first of the above)
+    (should (equal (find-file-path-completion-table (list dir1 dir2) nil "file1.el<dir1>")
+		   '("file1.el<dir1>")))
+    ))
+
+(ert-deftest find-file-path-completion-table-predicate ()
+  "Test completion when there are two files with the same name in
+different directories on path, and a predicate."
+  (let* ((root (make-temp-file "find-file-path-test" t))
+	 (dir1 (concat root "/dir1"))
+	 (dir2 (concat root "/dir2"))
+         (regexp (dired-glob-regexp "*.elc"))
+	 (pred (lambda (name) (not (string-match regexp name)))))
+
+    (mkdir dir1)
+    (mkdir dir2)
+
+    (with-temp-file (concat dir1 "/file1.el")
+      (insert "dir1/file1.el"))
+    (with-temp-file (concat dir1 "/file1.elc")
+      (insert "dir1/file1.elc"))
+    (with-temp-file (concat dir1 "/file2.el")
+      (insert "dir1/file2.el"))
+    (with-temp-file (concat dir1 "/file2.elc")
+      (insert "dir1/file2.elc"))
+
+    (with-temp-file (concat dir2 "/file1.el")
+      (insert "dir2/file1.el"))
+    (with-temp-file (concat dir2 "/file1.elc")
+      (insert "dir2/file1.elc"))
+    (with-temp-file (concat dir2 "/file3.el")
+      (insert "dir2/file3.el"))
+    (with-temp-file (concat dir2 "/file3.elc")
+      (insert "dir2/file3.elc"))
+
+    ;; multiple completions, some with same name, predicate eliminates some
+    (should (equal (find-file-path-completion-table (list dir1 dir2) pred "fi")
+		   '("file1.el<dir1>" "file1.el<dir2>" "file2.el" "file3.el")))
+
+    ;; only one completion (after user selects first of the above)
+    (should (equal (find-file-path-completion-table (list dir1 dir2) pred "file1.el<dir1>")
+		   '("file1.el<dir1>")))
+    ))
 ;; Stop the above "Local Var..." confusing Emacs.
 \f
 
diff --git a/doc/lispref/files.texi b/doc/lispref/files.texi
index 735e08e..edfb045 100644
--- a/doc/lispref/files.texi
+++ b/doc/lispref/files.texi
@@ -2421,11 +2421,13 @@ the file, which in some cases may cause a security hole.
   This section describes low-level subroutines for completing a file
 name.  For higher level functions, see @ref{Reading File Names}.
 
-@defun file-name-all-completions partial-filename directory
-This function returns a list of all possible completions for a file
-whose name starts with @var{partial-filename} in directory
-@var{directory}.  The order of the completions is the order of the files
-in the directory, which is unpredictable and conveys no useful
+@defun file-name-all-completions partial-filename directory &optional predicate
+This function returns a list of all possible completions for a file in
+directory @var{directory} whose name starts with
+@var{partial-filename} and for which @var{predicate} (called with the
+filename) returns non-nil. If @var{predicate} is nil (the default), it
+is ignored. The order of the completions is the order of the files in
+the directory, which is unpredictable and conveys no useful
 information.
 
 The argument @var{partial-filename} must be a file name containing no
diff --git a/lisp/cedet/cedet-global.el b/lisp/cedet/cedet-global.el
index 3773ba0..9fb46a0 100644
--- a/lisp/cedet/cedet-global.el
+++ b/lisp/cedet/cedet-global.el
@@ -120,6 +120,60 @@ Return a fully qualified filename."
 	(error "No file found")))
     ans))
 
+(defun find-file-complete-global-table (prefix)
+  "Do completion for file names in `find-file-complete-global'"
+  ;; Returned paths are relative to default-directory
+  (cond
+   ((string-match find-file-uniquify-regexp prefix)
+    ;; User has selected one match; return it.
+    (list prefix))
+
+   (t
+    (let* ((paths ;; Matching relative paths, as returned by global.
+	    (with-current-buffer (cedet-gnu-global-call (list "--ignore-case" "-P" prefix))
+	      (split-string (buffer-substring (point-min) (point-max)) "\n" t)))
+	   (dir-names
+	    (cl-mapcar (lambda (path) (cons (file-name-directory path) (file-name-nondirectory path)))
+		       paths))
+	   )
+
+      ;; "global -P `prefix'" matches in middle of the file name, and
+      ;; in the directory portion. The calling completion function
+      ;; rejects any completions that don't start with `prefix'.
+
+      (find-file-uniquify dir-names)
+      ))
+   ))
+
+(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)))
+
+    )
+
 (defun cedet-gnu-global-show-root ()
   "Show the root of a GNU Global area under the current buffer."
   (interactive)
@@ -193,6 +247,19 @@ If a database already exists, then just update it."
       )
     ))
 
+;;; project.el integration
+
+(defun project-try-global (dir)
+  (when (cedet-gnu-global-version-check t)
+    (let ((root (locate-dominating-file dir "GTAGS")))
+      (when root
+	(list 'global root)))))
+
+(cl-defmethod project-find-file ((prj (head global)) filename)
+  (let ((default-directory (file-name-as-directory (nth 1 prj))))
+    (find-file (find-file-complete-global filename))))
+
+
 (provide 'cedet-global)
 
 ;;; cedet-global.el ends here
diff --git a/lisp/files.el b/lisp/files.el
index c309f86..ad4fb4b 100644
--- a/lisp/files.el
+++ b/lisp/files.el
@@ -1691,6 +1691,85 @@ killed."
 	;; We already ran these; don't run them again.
 	(let (kill-buffer-query-functions kill-buffer-hook)
 	  (kill-buffer obuf))))))
+
+(defconst find-file-uniquify-regexp "^\\(.*\\)<\\(.*\\)>"
+  "Regexp matching uniqufied file name.
+Match 1 is the filename, match 2 is the relative directory.")
+
+(defun find-file-uniquify-conflicts (conflicts)
+  "Subroutine of `find-file-uniquify'."
+  (let ((common-root ;; shared prefix of dirs in conflicts - may be nil
+	 (fill-common-string-prefix (car (nth 0 conflicts)) (car (nth 1 conflicts)))))
+
+    (let ((temp (cddr conflicts))
+	  dir-name)
+      (while (and common-root
+		  temp)
+	(setq dir-name (pop temp))
+	(setq common-root (fill-common-string-prefix common-root (car dir-name)))))
+
+    (when common-root
+      ;; Trim `common-root' back to last '/'
+      (let ((i (1- (length common-root))))
+	(while (and (> i 0)
+		    (not (= (aref common-root i) ?/)))
+	  (setq i (1- i)))
+	(setq common-root (substring common-root 0 (1+ i)))))
+
+    (cl-mapcar
+     (lambda (dir-name)
+       (concat (cdr dir-name)
+	       "<" (substring (car dir-name) (length common-root)) ">"))
+     conflicts)
+    ))
+
+(defun find-file-uniquify (dir-names)
+  "Return a flat list of names from DIR-NAMES with duplicate filenames extended by directories.
+DIR-NAMES is a list of (dir . name)."
+  (let (result
+	conflicts ;; list of (dir . name) where all `name' are the same.
+	)
+
+    ;; Sort dir-names so duplicates are grouped together
+    (setq dir-names (sort dir-names (lambda (a b)
+				      (string< (cdr a) (cdr b)))))
+
+    (while dir-names
+      (setq conflicts (list (pop dir-names)))
+      (while (string= (cdr (car conflicts)) (cdr (car dir-names)))
+	(push (pop dir-names) conflicts))
+
+      (if (= 1 (length conflicts))
+	  (push (cdr (car conflicts)) result)
+	(setq result (append (find-file-uniquify-conflicts conflicts) result)))
+      )
+    (nreverse result)
+    ))
+
+(defun find-file-path-completion-table (path predicate prefix)
+  "Do completion for file names in `find-file-project'."
+  (cond
+   ((string-match find-file-uniquify-regexp prefix)
+    ;; User has selected one match; return it.
+    (list prefix))
+
+   ;; FIXME: handle prefix = "Makefile<dvc"; show /Projects/org.emacs.dvc/lisp/Makefile
+
+   (t
+    (let ((dir-names nil)) ;; list of (dir . name), where `name' is a completion
+      (dolist (dir path)
+        (when (file-directory-p dir)
+	  ;; `file-name-all-completions' applies
+	  ;; `completion-ignored-extensions' and `completion-regexp-list'
+          (setq dir-names
+		(append dir-names
+			(cl-mapcar (lambda (filename) (cons dir filename))
+				   (file-name-all-completions prefix dir predicate))))
+	  ))
+
+      (find-file-uniquify dir-names)))
+   ))
+
 \f
 ;; FIXME we really need to fold the uniquify stuff in here by default,
 ;; not using advice, and add it to the doc string.
diff --git a/lisp/progmodes/project.el b/lisp/progmodes/project.el
index 186840a..e9abf17 100644
--- a/lisp/progmodes/project.el
+++ b/lisp/progmodes/project.el
@@ -76,6 +76,101 @@ should take into account the value returned by
     (project-roots project)
     (funcall project-search-path-function))))
 
+;; Conversion between recursive and flat paths.
+(defun project--directory-directories-1 (dir ignore-regexp)
+  "Return a list of all directories in DIR (non-recursively).
+Ignores directories that match IGNORE-REGEXP (a regular expression).
+DIR must not end in ?/."
+  (let ((dirs (directory-files dir t nil t))
+	(dir-dot (concat dir "/."))
+	(dir-dotdot (concat dir "/.."))
+	result)
+    (while dirs
+      (let ((dir1 (pop dirs)))
+	(when (file-directory-p dir1)
+	  (unless (or
+		   (string= dir-dot dir1)
+		   (string= dir-dotdot dir1)
+		   (and ignore-regexp
+			(string-match ignore-regexp dir1)))
+	    (push dir1 result)))))
+    result))
+
+
+(defun project--directory-directories-recurse (dir ignore-regexp)
+  "Return a list of all directories in DIR, recursively.
+Ignores directories that match IGNORE-REGEXP (a regular expression).
+DIR must not end in ?/."
+  (let ((dirs (project--directory-directories-1 dir ignore-regexp))
+	result dir)
+    (while dirs
+      (setq dir (pop dirs))
+      (push dir result)
+      (setq result (append (project--directory-directories-recurse dir ignore-regexp) result)))
+
+    result))
+
+(defun project-recursive-ignores-to-flat (recursive-path ignore-dirs)
+  "Return a flat path constructed from RECURSIVE-PATH and IGNORE-DIRS."
+  (let ((dirs recursive-path)
+	(ignore-regexp (regexp-opt ignore-dirs))
+	result dir)
+
+    (while dirs
+      (setq dir (pop dirs))
+      (push dir result)
+      (setq result (append (project--directory-directories-recurse (directory-file-name dir) ignore-regexp) result)))
+
+    result))
+
+(defun project-flat-to-recursive-ignores (flat-path)
+  "Return a cons (RECURSIVE-PATH . IGNORE-DIRS) computed from FLAT-PATH."
+  (let* ((dirs (mapcar
+                 (lambda (dir)
+                   (file-name-as-directory (expand-file-name dir)))
+                 flat-path))
+	 search-path
+	 include-dirs
+	 ignore-dirs
+	 root)
+
+    ;; FIXME: combine with above loop?
+    (cl-delete-if-not #'file-exists-p dirs)
+
+    ;; Recursively delete subdirectories under a parent that is also
+    ;; in dirs. Add remaining to search-path, add unmentioned dirs to
+    ;; ignores.
+    ;;
+    ;; Start by sorting by name, so the roots that are
+    ;; present are just before their children.
+    (setq dirs (sort dirs (lambda (a b) (string< a b))))
+
+    (while (setq root (pop dirs))
+      (setq include-dirs nil)
+      (while (string-prefix-p root (car dirs))
+	(push (pop dirs) include-dirs))
+      (push root search-path)
+      (dolist (subdir (directory-files root t))
+	(when (file-directory-p subdir)
+	  (unless (or
+		   (string= (substring subdir -1) ".")
+		   (string= (substring subdir -2) "..")
+		   (member (file-name-as-directory subdir) include-dirs))
+	    (push (file-name-as-directory subdir) ignore-dirs)))))
+
+    (cons search-path ignore-dirs)
+    ))
+
+(cl-defgeneric project-ignore-dirs (_prj)
+  "Return list of absolute directory names that should be ignored.
+The names should be matched to directories when searching in
+`project-search-path'."
+  nil)
+
+(cl-defgeneric project-flat-search-path (prj)
+  "Return a flat search path for PRJ."
+  (project-recursive-ignores-to-flat (project-search-path prj) (project-ignore-dirs prj)))
+
 (cl-defgeneric project-roots (project)
   "Return the list of directory roots related to the current project.
 It should include the current project root, as well as the roots
@@ -83,6 +178,7 @@ of any other currently open projects, if they're meant to be
 edited together.  The directory names should be absolute.")
 
 (cl-defgeneric project-ignores (_project _dir)
+  ;; FIXME: do we need both project-ignores and project-ignore-files-globs?
   "Return the list of glob patterns to ignore inside DIR.
 Patterns can match both regular files and directories.
 To root an entry, start it with `./'.  To match directories only,
@@ -97,6 +193,79 @@ an element of `project-search-path'."
     vc-directory-exclusion-list)
    grep-find-ignored-files))
 
+(cl-defgeneric project-ignore-files-globs (_prj)
+  "Return list of file glob patterns that should be ignored in all directories.
+The globs should be matched to file and directory names sans path
+when searching in `project-search-path'."
+  (require 'grep)
+  (defvar grep-find-ignored-files)
+  (append
+    vc-directory-exclusion-list ;; preloaded
+    grep-find-ignored-files))
+
+(cl-defgeneric project-ignore-files-regexp (prj)
+  "Return a regular expression equivalent to `project-ignore-files-globs'."
+  (let ((globs (project-ignore-files-globs prj)))
+    (cond
+     ((= 1 (length globs))
+      (dired-glob-regexp (car globs)))
+
+     (t
+      (concat "\\(" (mapconcat #'dired-glob-regexp globs "\\)\\|\\(") "\\)"))
+     )))
+
+(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)
+	    (not (string-match regexp filename)))))
+
+    (setq filename
+	  (completing-read
+	   "file: " ;; prompt
+	   (completion-table-dynamic (apply-partially 'find-file-path-completion-table flat-path predicate))
+	   nil
+	   t ;; require match
+	   filename
+	   ))
+
+    ;; If text properties were preserved by completing-read, we could
+    ;; store the full directory in a text property on the
+    ;; conflicts. But they are not, so 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-project (filename)
+  "Find FILENAME (default prompt) with completion in current project.
+With prefix arg, FILENAME defaults to filename at point."
+  (interactive (list (when current-prefix-arg (thing-at-point 'filename))))
+  (project-find-file (project-current) filename))
+
+
 (defgroup project-vc nil
   "Project implementation using the VC package."
   :group 'tools)
diff --git a/src/dired.c b/src/dired.c
index 9773667..5bb7c80 100644
--- a/src/dired.c
+++ b/src/dired.c
@@ -349,7 +349,7 @@ If NOSORT is non-nil, the list is not sorted--its order is unpredictable.
   handler = Ffind_file_name_handler (directory, Qdirectory_files);
   if (!NILP (handler))
     return call5 (handler, Qdirectory_files, directory,
-                  full, match, nosort);
+		  full, match, nosort);
 
   return directory_files_internal (directory, full, match, nosort, 0, Qnil);
 }
@@ -420,10 +420,12 @@ determined by the variable `completion-ignored-extensions', which see.  */)
 }
 
 DEFUN ("file-name-all-completions", Ffile_name_all_completions,
-       Sfile_name_all_completions, 2, 2, 0,
+       Sfile_name_all_completions, 2, 3, 0,
        doc: /* Return a list of all completions of file name FILE in directory DIRECTORY.
-These are all file names in directory DIRECTORY which begin with FILE.  */)
-  (Lisp_Object file, Lisp_Object directory)
+These are all file names in directory DIRECTORY which begin with FILE,
+and for which PREDICATE returns non-nil. If PREDICATE is nil (the default),
+it is ignored.*/)
+  (Lisp_Object file, Lisp_Object directory, Lisp_Object predicate)
 {
   Lisp_Object handler;
   directory = Fexpand_file_name (directory, Qnil);
@@ -440,7 +442,7 @@ These are all file names in directory DIRECTORY which begin with FILE.  */)
   if (!NILP (handler))
     return call3 (handler, Qfile_name_all_completions, file, directory);
 
-  return file_name_completion (file, directory, 1, Qnil);
+  return file_name_completion (file, directory, 1, predicate);
 }
 
 static int file_name_completion_stat (int, struct dirent *, struct stat *);
diff --git a/test/automated/files.el b/test/automated/files.el
index 0522e0c..789fb01 100644
--- a/test/automated/files.el
+++ b/test/automated/files.el
@@ -166,6 +166,73 @@ form.")
       (delete-file tempfile))))
 
 
+(ert-deftest find-file-path-completion-table-duplicates ()
+  "Test completion when there are two files with the same name in
+different directories on path."
+  (let* ((root (make-temp-file "find-file-path-test" t))
+	 (dir1 (concat root "/dir1"))
+	 (dir2 (concat root "/dir2")))
+
+    (mkdir dir1)
+    (mkdir dir2)
+
+    (with-temp-file (concat dir1 "/file1.el")
+      (insert "dir1/file1.el"))
+    (with-temp-file (concat dir1 "/file2.el")
+      (insert "dir1/file2.el"))
+
+    (with-temp-file (concat dir2 "/file1.el")
+      (insert "dir2/file1.el"))
+    (with-temp-file (concat dir2 "/file3.el")
+      (insert "dir2/file3.el"))
+
+    ;; multiple completions, some with same name
+    (should (equal (find-file-path-completion-table (list dir1 dir2) nil "fi")
+		   '("file1.el<dir1>" "file1.el<dir2>" "file2.el" "file3.el")))
+
+    ;; only one completion (after user selects first of the above)
+    (should (equal (find-file-path-completion-table (list dir1 dir2) nil "file1.el<dir1>")
+		   '("file1.el<dir1>")))
+    ))
+
+(ert-deftest find-file-path-completion-table-predicate ()
+  "Test completion when there are two files with the same name in
+different directories on path, and a predicate."
+  (let* ((root (make-temp-file "find-file-path-test" t))
+	 (dir1 (concat root "/dir1"))
+	 (dir2 (concat root "/dir2"))
+         (regexp (dired-glob-regexp "*.elc"))
+	 (pred (lambda (name) (not (string-match regexp name)))))
+
+    (mkdir dir1)
+    (mkdir dir2)
+
+    (with-temp-file (concat dir1 "/file1.el")
+      (insert "dir1/file1.el"))
+    (with-temp-file (concat dir1 "/file1.elc")
+      (insert "dir1/file1.elc"))
+    (with-temp-file (concat dir1 "/file2.el")
+      (insert "dir1/file2.el"))
+    (with-temp-file (concat dir1 "/file2.elc")
+      (insert "dir1/file2.elc"))
+
+    (with-temp-file (concat dir2 "/file1.el")
+      (insert "dir2/file1.el"))
+    (with-temp-file (concat dir2 "/file1.elc")
+      (insert "dir2/file1.elc"))
+    (with-temp-file (concat dir2 "/file3.el")
+      (insert "dir2/file3.el"))
+    (with-temp-file (concat dir2 "/file3.elc")
+      (insert "dir2/file3.elc"))
+
+    ;; multiple completions, some with same name, predicate eliminates some
+    (should (equal (find-file-path-completion-table (list dir1 dir2) pred "fi")
+		   '("file1.el<dir1>" "file1.el<dir2>" "file2.el" "file3.el")))
+
+    ;; only one completion (after user selects first of the above)
+    (should (equal (find-file-path-completion-table (list dir1 dir2) pred "file1.el<dir1>")
+		   '("file1.el<dir1>")))
+    ))
 ;; Stop the above "Local Var..." confusing Emacs.
 \f
 

^ permalink raw reply related	[flat|nested] 162+ messages in thread

end of thread, other threads:[~2016-01-21 13:46 UTC | newest]

Thread overview: 162+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2015-09-15 20:23 find-file-project Stephen Leake
2015-09-15 22:21 ` find-file-project Dmitry Gutov
2015-09-16  2:49   ` find-file-project Stephen Leake
2015-09-16  3:26     ` find-file-project Stephen Leake
2015-09-16  3:34       ` find-file-project Stephen Leake
2015-09-16  3:38       ` find-file-project Dmitry Gutov
2015-09-16 12:56         ` find-file-project Stephen Leake
2015-09-16 14:37           ` find-file-project Dmitry Gutov
2015-09-16 16:41             ` find-file-project Stephen Leake
2015-09-16 16:59               ` find-file-project Stephen Leake
2015-09-16 17:13                 ` find-file-project Dmitry Gutov
2015-09-16 17:25               ` find-file-project Dmitry Gutov
2015-09-16 21:01                 ` find-file-project Stephen Leake
2015-09-17 17:45                   ` find-file-project Dmitry Gutov
2015-09-18 16:08                     ` project.el semantics Stephen Leake
2015-09-19  0:17                       ` Dmitry Gutov
2015-11-08  1:47                       ` Dmitry Gutov
2015-11-08  7:11                         ` Stephen Leake
2015-11-08 13:07                           ` Dmitry Gutov
2015-11-08 20:11                             ` Dmitry Gutov
2015-11-09  9:10                             ` Stephen Leake
2015-11-09 13:27                               ` Dmitry Gutov
2015-11-09 18:15                                 ` Stephen Leake
2015-11-10  1:32                                   ` Dmitry Gutov
2015-11-10  2:40                                   ` Dmitry Gutov
2015-11-10 17:36                                     ` Stephen Leake
2015-11-11  0:47                                       ` Dmitry Gutov
2015-11-11 10:27                                         ` Stephen Leake
2015-11-11 13:21                                           ` Dmitry Gutov
2015-11-11 16:48                                             ` John Wiegley
2015-11-11 17:03                                               ` Dmitry Gutov
2015-11-11 17:22                                                 ` John Wiegley
2015-11-11 21:46                                                   ` Dmitry Gutov
2015-11-11 22:30                                                     ` John Wiegley
2015-11-12  2:21                                                       ` Dmitry Gutov
2015-11-12 17:26                                                         ` John Wiegley
2015-11-12 17:53                                                           ` Dmitry Gutov
2015-11-12 18:04                                                             ` Dmitry Gutov
2015-11-12 18:17                                                             ` John Wiegley
2015-11-12 18:26                                                               ` John Mastro
2015-11-12 23:37                                                                 ` Dmitry Gutov
2015-11-12 18:50                                                               ` Dmitry Gutov
2015-11-11 22:41                                                   ` Stephen Leake
2015-11-11 22:14                                             ` Stephen Leake
2015-11-11 23:26                                               ` Dmitry Gutov
2015-11-12  6:44                                                 ` Stephen Leake
2015-11-12 11:32                                                   ` Dmitry Gutov
2015-11-12 19:28                                                     ` Stephen Leake
2015-11-12 22:04                                                       ` Dmitry Gutov
2015-11-19  2:21                                                         ` Dmitry Gutov
2015-11-20 18:40                                                           ` John Wiegley
2015-11-21 10:03                                                           ` Stephen Leake
2015-11-21 10:10                                                             ` Stephen Leake
2015-11-22  5:18                                                             ` John Wiegley
2015-11-22  5:36                                                               ` Dmitry Gutov
2015-11-22  5:43                                                                 ` John Wiegley
2015-11-22  5:58                                                                   ` Dmitry Gutov
2015-11-22 16:55                                                                     ` John Wiegley
2015-11-22 17:13                                                                       ` Dmitry Gutov
2015-11-22 19:54                                                                         ` John Wiegley
2015-11-22 21:27                                                                           ` John Wiegley
2015-11-23  1:14                                                                             ` Dmitry Gutov
2015-11-23 22:04                                                                               ` Steinar Bang
2015-11-23 23:17                                                                                 ` Dmitry Gutov
2015-11-23  7:43                                                                             ` Stephen Leake
2015-11-23 12:59                                                                               ` Dmitry Gutov
2015-12-16  4:06                                                                             ` Dmitry Gutov
2015-12-16  6:52                                                                               ` John Wiegley
2015-12-28  4:20                                                                                 ` Dmitry Gutov
2015-11-22 22:04                                                                       ` Stephen Leake
2015-11-22 23:21                                                                         ` Dmitry Gutov
2015-11-23  2:06                                                               ` Richard Stallman
2015-11-22  5:32                                                             ` Dmitry Gutov
2015-11-09 22:16                                 ` John Wiegley
2015-11-10  0:58                                   ` Dmitry Gutov
2015-11-10  1:07                                     ` John Wiegley
2015-11-10  1:18                                       ` Dmitry Gutov
2015-11-10  1:40                                         ` John Wiegley
2015-11-10  3:23                                           ` Dmitry Gutov
2015-11-10  6:00                                             ` John Wiegley
2015-11-10 10:54                                               ` Dmitry Gutov
2015-11-10 14:21                                                 ` John Wiegley
2015-11-10 23:41                                                 ` Stephen Leake
2015-11-11  0:56                                                   ` Dmitry Gutov
2015-11-11  1:17                                                     ` John Wiegley
2015-11-11  1:31                                                       ` Dmitry Gutov
2015-11-11  9:55                                                         ` Stephen Leake
2015-11-11 13:30                                                           ` Dmitry Gutov
2015-11-12  8:46                                                             ` Steinar Bang
2015-11-12 19:35                                                               ` Stephen Leake
2015-11-11  9:44                                                     ` Stephen Leake
2015-11-11 13:39                                                       ` Dmitry Gutov
2015-11-10 17:38                                             ` Stephen Leake
2015-11-10 19:47                                               ` Dmitry Gutov
2015-09-16  4:41     ` find-file-project Dmitry Gutov
2015-09-16 13:04       ` find-file-project Stefan Monnier
2015-09-16 17:01         ` find-file-project Stephen Leake
2015-09-16 13:31       ` find-file-project Stephen Leake
2015-09-16 14:13         ` find-file-project Stephen Leake
2015-09-16 15:05           ` find-file-project Dmitry Gutov
2015-09-16 16:58             ` find-file-project Stephen Leake
2015-09-17 17:15               ` find-file-project Dmitry Gutov
2015-09-18 17:14                 ` project.el semantics Stephen Leake
2015-09-19  0:08                   ` Dmitry Gutov
2015-09-19 12:07                     ` Stephen Leake
2015-09-19 12:40                       ` Dmitry Gutov
2015-09-16 17:04         ` find-file-project Dmitry Gutov
2015-09-16 21:11           ` find-file-project Stephen Leake
2015-09-17 17:52             ` find-file-project Dmitry Gutov
2015-09-17  1:26           ` find-file-project Stefan Monnier
2015-09-17 18:09             ` find-file-project Dmitry Gutov
2015-09-18 17:07               ` find-file-project Stefan Monnier
2015-09-18 23:41                 ` find-file-project Dmitry Gutov
2015-09-19  4:13                   ` find-file-project Stefan Monnier
2016-01-06  1:29                 ` find-file-project Dmitry Gutov
2016-01-07  4:52                   ` find-file-project Stefan Monnier
2016-01-07 17:09                     ` find-file-project Dmitry Gutov
2016-01-07  7:12                   ` find-file-project Stephen Leake
2016-01-07 17:55                     ` find-file-project Dmitry Gutov
2016-01-07 18:26                     ` find-file-project Dmitry Gutov
2016-01-07 19:58                       ` find-file-project Stephen Leake
2016-01-07 21:12                         ` find-file-project Dmitry Gutov
2016-01-08 19:11                           ` find-file-project Stephen Leake
2016-01-08 23:49                             ` find-file-project Dmitry Gutov
2016-01-09 12:18                               ` find-file-project Stephen Leake
2016-01-09 17:11                                 ` find-file-project Dmitry Gutov
2016-01-08  1:26                   ` find-file-project John Wiegley
2016-01-08  1:38                     ` find-file-project Dmitry Gutov
2016-01-08  2:19                       ` find-file-project Richard Copley
2016-01-08 11:34                         ` find-file-project Dmitry Gutov
2016-01-08  7:39                       ` find-file-project Stefan Monnier
2016-01-19  6:15                         ` find-file-project Dmitry Gutov
2016-01-19 14:20                           ` find-file-project Stefan Monnier
2016-01-20  0:51                             ` find-file-project Dmitry Gutov
2016-01-20  1:32                               ` find-file-project Stefan Monnier
2016-01-20  1:47                                 ` find-file-project Dmitry Gutov
2016-01-20  2:25                                   ` find-file-project Stefan Monnier
2016-01-20 15:40                                     ` find-file-project Dmitry Gutov
2016-01-20 21:58                                       ` find-file-project Stefan Monnier
2016-01-20 22:12                                         ` find-file-project Dmitry Gutov
2016-01-20 22:17                                           ` find-file-project Drew Adams
2016-01-20 22:26                                             ` find-file-project Dmitry Gutov
2016-01-20 22:48                                               ` find-file-project Drew Adams
2016-01-20 22:50                                                 ` find-file-project Dmitry Gutov
2016-01-20 23:09                                                   ` find-file-project Drew Adams
2016-01-20 23:23                                                     ` find-file-project Dmitry Gutov
2016-01-20 23:46                                                       ` find-file-project Drew Adams
2016-01-20 23:51                                                         ` find-file-project Dmitry Gutov
2016-01-21  0:08                                                           ` find-file-project Drew Adams
2016-01-21  0:21                                                             ` find-file-project Dmitry Gutov
2016-01-21  3:03                                 ` find-file-project Dmitry Gutov
2016-01-21 13:46                                   ` find-file-project Stefan Monnier
2016-01-17 21:23                       ` find-file-project John Wiegley
2016-01-17 21:25                         ` find-file-project Dmitry Gutov
2016-01-17 23:07                           ` find-file-project John Wiegley
2016-01-18  1:42                             ` find-file-project Dmitry Gutov
2015-09-16  7:16   ` find-file-project Eli Zaretskii
2015-09-16 13:06     ` find-file-project Stefan Monnier
2015-09-16 13:40     ` find-file-project Stephen Leake
2015-09-16 14:32       ` find-file-project Eli Zaretskii
2015-09-16 14:57       ` find-file-project Dmitry Gutov
2015-09-16 17:03         ` find-file-project Stephen Leake

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.