2015-07-10 04:34:41 +03:00
|
|
|
;;; project.el --- Operations on the current project -*- lexical-binding: t; -*-
|
|
|
|
|
2020-01-01 00:19:43 +00:00
|
|
|
;; Copyright (C) 2015-2020 Free Software Foundation, Inc.
|
2020-05-18 03:44:26 +03:00
|
|
|
;; Version: 0.2.0
|
2020-05-13 11:31:21 +01:00
|
|
|
;; Package-Requires: ((emacs "26.3"))
|
|
|
|
|
|
|
|
;; This is a GNU ELPA :core package. Avoid using functionality that
|
|
|
|
;; not compatible with the version of Emacs recorded above.
|
2015-07-10 04:34:41 +03:00
|
|
|
|
|
|
|
;; This file is part of GNU Emacs.
|
|
|
|
|
|
|
|
;; GNU Emacs is free software: you can redistribute it and/or modify
|
|
|
|
;; it under the terms of the GNU General Public License as published by
|
|
|
|
;; the Free Software Foundation, either version 3 of the License, or
|
|
|
|
;; (at your option) any later version.
|
|
|
|
|
|
|
|
;; GNU Emacs is distributed in the hope that it will be useful,
|
|
|
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
;; GNU General Public License for more details.
|
|
|
|
|
|
|
|
;; You should have received a copy of the GNU General Public License
|
2017-09-13 15:52:52 -07:00
|
|
|
;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
|
2015-07-10 04:34:41 +03:00
|
|
|
|
|
|
|
;;; Commentary:
|
|
|
|
|
|
|
|
;; This file contains generic infrastructure for dealing with
|
2015-12-28 06:17:19 +02:00
|
|
|
;; projects, some utility functions, and commands using that
|
|
|
|
;; infrastructure.
|
2015-08-05 15:08:00 +03:00
|
|
|
;;
|
2015-11-03 02:11:45 +02:00
|
|
|
;; The goal is to make it easier for Lisp programs to operate on the
|
2015-08-05 15:08:00 +03:00
|
|
|
;; current project, without having to know which package handles
|
|
|
|
;; detection of that project type, parsing its config files, etc.
|
2015-12-28 06:17:19 +02:00
|
|
|
;;
|
2016-01-08 02:54:50 +03:00
|
|
|
;; NOTE: The project API is still experimental and can change in major,
|
|
|
|
;; backward-incompatible ways. Everyone is encouraged to try it, and
|
|
|
|
;; report to us any problems or use cases we hadn't anticipated, by
|
|
|
|
;; sending an email to emacs-devel, or `M-x report-emacs-bug'.
|
|
|
|
;;
|
2015-12-28 06:17:19 +02:00
|
|
|
;; Infrastructure:
|
|
|
|
;;
|
|
|
|
;; Function `project-current', to determine the current project
|
2020-05-23 04:38:27 +03:00
|
|
|
;; instance, and 4 (at the moment) generic functions that act on it.
|
2015-12-28 06:17:19 +02:00
|
|
|
;; This list is to be extended in future versions.
|
|
|
|
;;
|
|
|
|
;; Utils:
|
|
|
|
;;
|
|
|
|
;; `project-combine-directories' and `project-subtract-directories',
|
2015-12-28 19:05:50 -08:00
|
|
|
;; mainly for use in the abovementioned generics' implementations.
|
2015-12-28 06:17:19 +02:00
|
|
|
;;
|
|
|
|
;; Commands:
|
|
|
|
;;
|
2019-01-19 03:57:58 +03:00
|
|
|
;; `project-find-file', `project-find-regexp' and
|
|
|
|
;; `project-or-external-find-regexp' use the current API, and thus
|
|
|
|
;; will work in any project that has an adapter.
|
2015-12-28 06:17:19 +02:00
|
|
|
|
|
|
|
;;; TODO:
|
|
|
|
|
2016-01-07 20:14:40 +03:00
|
|
|
;; * Reliably cache the list of files in the project, probably using
|
|
|
|
;; filenotify.el (if supported) to invalidate. And avoiding caching
|
|
|
|
;; if it's not available (manual cache invalidation is not nice).
|
|
|
|
;;
|
2015-12-28 06:17:19 +02:00
|
|
|
;; * Build tool related functionality. Start with a `project-build'
|
|
|
|
;; command, which should provide completions on tasks to run, and
|
|
|
|
;; maybe allow entering some additional arguments. This might
|
|
|
|
;; be handled better with a separate API, though. Then we won't
|
|
|
|
;; force every project backend to be aware of the build tool(s) the
|
|
|
|
;; project is using.
|
|
|
|
;;
|
|
|
|
;; * Command to (re)build the tag files in all project roots. To that
|
2015-12-29 03:53:32 +02:00
|
|
|
;; end, we might need to add a way to provide file whitelist
|
|
|
|
;; wildcards for each root to limit etags to certain files (in
|
|
|
|
;; addition to the blacklist provided by ignores), and/or allow
|
|
|
|
;; specifying additional tag regexps.
|
2015-12-28 06:17:19 +02:00
|
|
|
;;
|
|
|
|
;; * UI for the user to be able to pick the current project for the
|
|
|
|
;; whole Emacs session, independent of the current directory. Or,
|
|
|
|
;; in the more advanced case, open a set of projects, and have some
|
|
|
|
;; project-related commands to use them all. E.g., have a command
|
|
|
|
;; to search for a regexp across all open projects. Provide a
|
|
|
|
;; history of projects that were opened in the past (storing it as a
|
|
|
|
;; list of directories should suffice).
|
2015-12-29 03:53:32 +02:00
|
|
|
;;
|
|
|
|
;; * Support for project-local variables: a UI to edit them, and a
|
|
|
|
;; utility function to retrieve a value. Probably useless without
|
|
|
|
;; support in various built-in commands. In the API, we might get
|
|
|
|
;; away with only adding a `project-configuration-directory' method,
|
|
|
|
;; defaulting to the project root the current file/buffer is in.
|
|
|
|
;; And prompting otherwise. How to best mix that with backends that
|
|
|
|
;; want to set/provide certain variables themselves, is up for
|
|
|
|
;; discussion.
|
2015-07-10 04:34:41 +03:00
|
|
|
|
|
|
|
;;; Code:
|
|
|
|
|
|
|
|
(require 'cl-generic)
|
|
|
|
|
2015-11-10 02:41:06 +02:00
|
|
|
(defvar project-find-functions (list #'project-try-vc)
|
2015-07-10 04:34:41 +03:00
|
|
|
"Special hook to find the project containing a given directory.
|
|
|
|
Each functions on this hook is called in turn with one
|
|
|
|
argument (the directory) and should return either nil to mean
|
|
|
|
that it is not applicable, or a project instance.")
|
|
|
|
|
|
|
|
;;;###autoload
|
2015-11-10 02:41:06 +02:00
|
|
|
(defun project-current (&optional maybe-prompt dir)
|
|
|
|
"Return the project instance in DIR or `default-directory'.
|
|
|
|
When no project found in DIR, and MAYBE-PROMPT is non-nil, ask
|
2016-04-07 02:02:13 +03:00
|
|
|
the user for a different directory to look in. If that directory
|
|
|
|
is not a part of a detectable project either, return a
|
|
|
|
`transient' project instance rooted in it."
|
2015-07-10 04:34:41 +03:00
|
|
|
(unless dir (setq dir default-directory))
|
2015-11-10 02:41:06 +02:00
|
|
|
(let ((pr (project--find-in-directory dir)))
|
|
|
|
(cond
|
|
|
|
(pr)
|
|
|
|
(maybe-prompt
|
|
|
|
(setq dir (read-directory-name "Choose the project directory: " dir nil t)
|
|
|
|
pr (project--find-in-directory dir))
|
|
|
|
(unless pr
|
2019-11-11 10:30:13 -08:00
|
|
|
(message "Using `%s' as a transient project root" dir)
|
2016-04-07 02:02:13 +03:00
|
|
|
(setq pr (cons 'transient dir)))))
|
2015-11-10 02:41:06 +02:00
|
|
|
pr))
|
|
|
|
|
|
|
|
(defun project--find-in-directory (dir)
|
2015-07-10 04:34:41 +03:00
|
|
|
(run-hook-with-args-until-success 'project-find-functions dir))
|
|
|
|
|
2020-05-23 04:38:27 +03:00
|
|
|
(cl-defgeneric project-root (project)
|
|
|
|
"Return root directory of the current project.
|
|
|
|
|
|
|
|
It usually contains the main build file, dependencies
|
|
|
|
configuration file, etc. Though neither is mandatory.
|
|
|
|
|
|
|
|
The directory name must be absolute."
|
|
|
|
(car (project-roots project)))
|
2015-11-03 02:11:45 +02:00
|
|
|
|
2020-05-23 04:38:27 +03:00
|
|
|
(cl-defgeneric project-roots (project)
|
|
|
|
"Return the list containing the current project root.
|
2015-07-31 05:37:28 +03:00
|
|
|
|
2020-05-23 04:38:27 +03:00
|
|
|
The function is obsolete, all projects have one main root anyway,
|
|
|
|
and the rest should be possible to express through
|
|
|
|
`project-external-roots'."
|
|
|
|
;; FIXME: Can we specify project's version here?
|
|
|
|
;; FIXME: Could we make this affect cl-defmethod calls too?
|
|
|
|
(declare (obsolete project-root "0.3.0"))
|
|
|
|
(list (project-root project)))
|
2015-11-03 02:11:45 +02:00
|
|
|
|
2015-12-28 06:17:19 +02:00
|
|
|
;; FIXME: Add MODE argument, like in `ede-source-paths'?
|
|
|
|
(cl-defgeneric project-external-roots (_project)
|
|
|
|
"Return the list of external roots for PROJECT.
|
2015-11-03 02:11:45 +02:00
|
|
|
|
2015-12-28 06:17:19 +02:00
|
|
|
It's the list of directories outside of the project that are
|
|
|
|
still related to it. If the project deals with source code then,
|
|
|
|
depending on the languages used, this list should include the
|
2020-05-23 04:38:27 +03:00
|
|
|
headers search path, load path, class path, and so on."
|
2015-12-28 06:17:19 +02:00
|
|
|
nil)
|
2015-07-10 04:34:41 +03:00
|
|
|
|
2015-08-02 01:01:28 +03:00
|
|
|
(cl-defgeneric project-ignores (_project _dir)
|
|
|
|
"Return the list of glob patterns to ignore inside DIR.
|
|
|
|
Patterns can match both regular files and directories.
|
2015-07-12 17:18:09 +03:00
|
|
|
To root an entry, start it with `./'. To match directories only,
|
2020-05-23 04:38:27 +03:00
|
|
|
end it with `/'. DIR must be either `project-root' or one of
|
2015-12-28 06:17:19 +02:00
|
|
|
`project-external-roots'."
|
2019-02-06 12:25:09 +03:00
|
|
|
;; TODO: Document and support regexp ignores as used by Hg.
|
|
|
|
;; TODO: Support whitelist entries.
|
2015-07-12 17:18:09 +03:00
|
|
|
(require 'grep)
|
|
|
|
(defvar grep-find-ignored-files)
|
|
|
|
(nconc
|
|
|
|
(mapcar
|
|
|
|
(lambda (dir)
|
|
|
|
(concat dir "/"))
|
|
|
|
vc-directory-exclusion-list)
|
|
|
|
grep-find-ignored-files))
|
|
|
|
|
2019-05-14 05:09:19 +03:00
|
|
|
(defun project--file-completion-table (all-files)
|
|
|
|
(lambda (string pred action)
|
|
|
|
(cond
|
|
|
|
((eq action 'metadata)
|
|
|
|
'(metadata . ((category . project-file))))
|
|
|
|
(t
|
|
|
|
(complete-with-action action all-files string pred)))))
|
2016-01-29 17:43:26 -06:00
|
|
|
|
2020-05-23 04:38:27 +03:00
|
|
|
(cl-defmethod project-root ((project (head transient)))
|
|
|
|
(cdr project))
|
2016-04-07 02:02:13 +03:00
|
|
|
|
* lisp/multifile.el: New file, extracted from etags.el
The main motivation for this change was the introduction of
project-query-replace. dired's multi-file query&replace was implemented
on top of etags.el even though it did not use TAGS in any way, so I moved
this generic multifile code into its own package, with a nicer interface,
and then used that in project.el.
* lisp/progmodes/project.el (project-files): New generic function.
(project-search, project-query-replace): New commands.
* lisp/dired-aux.el (dired-do-search, dired-do-query-replace-regexp):
Use multifile.el instead of etags.el.
* lisp/progmodes/etags.el: Remove redundant :groups.
(next-file-list): Remove var.
(tags-loop-revert-buffers): Make it an obsolete alias.
(next-file): Don't autoload (it can't do anything useful before some
other etags.el function setup the multifile operation).
(tags--all-files): New function, extracted from next-file.
(tags-next-file): Rename from next-file.
Rewrite using tags--all-files and multifile-next-file.
(next-file): Keep it as an obsolete alias.
(tags-loop-operate, tags-loop-scan): Mark as obsolete.
(tags--compat-files, tags--compat-initialize): New function.
(tags-loop-continue): Rewrite using multifile-continue. Mark as obsolete.
(tags--last-search-operate-function): New var.
(tags-search, tags-query-replace): Rewrite using multifile.el.
* lisp/emacs-lisp/generator.el (iter-end-of-sequence): Use 'define-error'.
(iter-make): New macro.
(iter-empty): New iterator.
* lisp/menu-bar.el (menu-bar-search-menu, menu-bar-replace-menu):
tags-loop-continue -> multifile-continue.
2018-09-22 11:46:35 -04:00
|
|
|
(cl-defgeneric project-files (project &optional dirs)
|
|
|
|
"Return a list of files in directories DIRS in PROJECT.
|
|
|
|
DIRS is a list of absolute directories; it should be some
|
2020-05-23 04:38:27 +03:00
|
|
|
subset of the project root and external roots.
|
2019-01-14 00:16:19 +03:00
|
|
|
|
|
|
|
The default implementation uses `find-program'. PROJECT is used
|
|
|
|
to find the list of ignores for each directory."
|
|
|
|
(cl-mapcan
|
|
|
|
(lambda (dir)
|
2019-01-19 03:46:07 +03:00
|
|
|
(project--files-in-directory dir
|
|
|
|
(project--dir-ignores project dir)))
|
2020-05-23 04:38:27 +03:00
|
|
|
(or dirs
|
|
|
|
(list (project-root project)))))
|
* lisp/multifile.el: New file, extracted from etags.el
The main motivation for this change was the introduction of
project-query-replace. dired's multi-file query&replace was implemented
on top of etags.el even though it did not use TAGS in any way, so I moved
this generic multifile code into its own package, with a nicer interface,
and then used that in project.el.
* lisp/progmodes/project.el (project-files): New generic function.
(project-search, project-query-replace): New commands.
* lisp/dired-aux.el (dired-do-search, dired-do-query-replace-regexp):
Use multifile.el instead of etags.el.
* lisp/progmodes/etags.el: Remove redundant :groups.
(next-file-list): Remove var.
(tags-loop-revert-buffers): Make it an obsolete alias.
(next-file): Don't autoload (it can't do anything useful before some
other etags.el function setup the multifile operation).
(tags--all-files): New function, extracted from next-file.
(tags-next-file): Rename from next-file.
Rewrite using tags--all-files and multifile-next-file.
(next-file): Keep it as an obsolete alias.
(tags-loop-operate, tags-loop-scan): Mark as obsolete.
(tags--compat-files, tags--compat-initialize): New function.
(tags-loop-continue): Rewrite using multifile-continue. Mark as obsolete.
(tags--last-search-operate-function): New var.
(tags-search, tags-query-replace): Rewrite using multifile.el.
* lisp/emacs-lisp/generator.el (iter-end-of-sequence): Use 'define-error'.
(iter-make): New macro.
(iter-empty): New iterator.
* lisp/menu-bar.el (menu-bar-search-menu, menu-bar-replace-menu):
tags-loop-continue -> multifile-continue.
2018-09-22 11:46:35 -04:00
|
|
|
|
2019-01-18 06:38:12 +03:00
|
|
|
(defun project--files-in-directory (dir ignores &optional files)
|
|
|
|
(require 'find-dired)
|
2019-12-29 15:22:11 +03:00
|
|
|
(require 'xref)
|
2019-01-18 06:38:12 +03:00
|
|
|
(defvar find-name-arg)
|
2020-04-29 18:58:42 +03:00
|
|
|
(let* ((default-directory dir)
|
2020-04-29 22:46:17 +03:00
|
|
|
;; Make sure ~/ etc. in local directory name is
|
|
|
|
;; expanded and not left for the shell command
|
|
|
|
;; to interpret.
|
|
|
|
(localdir (file-local-name (expand-file-name dir)))
|
2020-04-29 18:58:42 +03:00
|
|
|
(command (format "%s %s %s -type f %s -print0"
|
|
|
|
find-program
|
2020-04-29 22:46:17 +03:00
|
|
|
localdir
|
|
|
|
(xref--find-ignores-arguments ignores localdir)
|
2020-04-29 18:58:42 +03:00
|
|
|
(if files
|
|
|
|
(concat (shell-quote-argument "(")
|
|
|
|
" " find-name-arg " "
|
|
|
|
(mapconcat
|
|
|
|
#'shell-quote-argument
|
|
|
|
(split-string files)
|
|
|
|
(concat " -o " find-name-arg " "))
|
|
|
|
" "
|
|
|
|
(shell-quote-argument ")"))"")
|
|
|
|
)))
|
2019-02-07 14:22:47 +03:00
|
|
|
(project--remote-file-names
|
2019-04-15 00:39:29 +03:00
|
|
|
(sort (split-string (shell-command-to-string command) "\0" t)
|
|
|
|
#'string<))))
|
2019-02-07 14:22:47 +03:00
|
|
|
|
|
|
|
(defun project--remote-file-names (local-files)
|
|
|
|
"Return LOCAL-FILES as if they were on the system of `default-directory'."
|
|
|
|
(let ((remote-id (file-remote-p default-directory)))
|
|
|
|
(if (not remote-id)
|
|
|
|
local-files
|
|
|
|
(mapcar (lambda (file)
|
|
|
|
(concat remote-id file))
|
|
|
|
local-files))))
|
2019-01-18 06:38:12 +03:00
|
|
|
|
2015-08-10 04:04:57 +03:00
|
|
|
(defgroup project-vc nil
|
2020-05-18 03:36:43 +03:00
|
|
|
"Project implementation based on the VC package."
|
2016-01-12 20:06:49 -05:00
|
|
|
:version "25.1"
|
2015-08-10 04:04:57 +03:00
|
|
|
:group 'tools)
|
|
|
|
|
|
|
|
(defcustom project-vc-ignores nil
|
2016-01-12 20:06:49 -05:00
|
|
|
"List of patterns to include in `project-ignores'."
|
2015-08-10 04:04:57 +03:00
|
|
|
:type '(repeat string)
|
|
|
|
:safe 'listp)
|
|
|
|
|
2020-05-18 03:36:43 +03:00
|
|
|
(defcustom project-vc-merge-submodules t
|
2020-05-18 03:44:26 +03:00
|
|
|
"Non-nil to consider submodules part of the parent project.
|
|
|
|
|
|
|
|
After changing this variable (using Customize or .dir-locals.el)
|
|
|
|
you might have to restart Emacs to see the effect."
|
2020-05-18 03:36:43 +03:00
|
|
|
:type 'boolean
|
|
|
|
:package-version '(project . "0.2.0")
|
|
|
|
:safe 'booleanp)
|
|
|
|
|
2015-12-28 06:17:19 +02:00
|
|
|
;; FIXME: Using the current approach, major modes are supposed to set
|
|
|
|
;; this variable to a buffer-local value. So we don't have access to
|
|
|
|
;; the "external roots" of language A from buffers of language B, which
|
|
|
|
;; seems desirable in multi-language projects, at least for some
|
|
|
|
;; potential uses, like "jump to a file in project or external dirs".
|
|
|
|
;;
|
|
|
|
;; We could add a second argument to this function: a file extension,
|
|
|
|
;; or a language name. Some projects will know the set of languages
|
|
|
|
;; used in them; for others, like VC-based projects, we'll need
|
|
|
|
;; auto-detection. I see two options:
|
|
|
|
;;
|
|
|
|
;; - That could be implemented as a separate second hook, with a
|
|
|
|
;; list of functions that return file extensions.
|
|
|
|
;;
|
|
|
|
;; - This variable will be turned into a hook with "append" semantics,
|
|
|
|
;; and each function in it will perform auto-detection when passed
|
|
|
|
;; nil instead of an actual file extension. Then this hook will, in
|
|
|
|
;; general, be modified globally, and not from major mode functions.
|
|
|
|
;;
|
|
|
|
;; The second option seems simpler, but the first one has the
|
|
|
|
;; advantage that the user could override the list of languages used
|
|
|
|
;; in a project via a directory-local variable, thus skipping
|
|
|
|
;; languages they're not working on personally (in a big project), or
|
|
|
|
;; working around problems in language detection (the detection logic
|
|
|
|
;; might be imperfect for the project in question, or it might work
|
|
|
|
;; too slowly for the user's taste).
|
|
|
|
(defvar project-vc-external-roots-function (lambda () tags-table-list)
|
|
|
|
"Function that returns a list of external roots.
|
|
|
|
|
|
|
|
It should return a list of directory roots that contain source
|
|
|
|
files related to the current buffer.
|
|
|
|
|
|
|
|
The directory names should be absolute. Used in the VC project
|
|
|
|
backend implementation of `project-external-roots'.")
|
|
|
|
|
2015-07-10 04:34:41 +03:00
|
|
|
(defun project-try-vc (dir)
|
|
|
|
(let* ((backend (ignore-errors (vc-responsible-backend dir)))
|
2019-12-27 18:18:41 +03:00
|
|
|
(root
|
|
|
|
(pcase backend
|
|
|
|
('Git
|
|
|
|
;; Don't stop at submodule boundary.
|
|
|
|
(or (vc-file-getprop dir 'project-git-root)
|
2020-05-18 03:36:43 +03:00
|
|
|
(let ((root (vc-call-backend backend 'root dir)))
|
2020-05-15 08:10:22 +03:00
|
|
|
(vc-file-setprop
|
|
|
|
dir 'project-git-root
|
2020-05-18 03:36:43 +03:00
|
|
|
(if (and
|
2020-05-18 03:44:26 +03:00
|
|
|
;; FIXME: Invalidate the cache when the value
|
|
|
|
;; of this variable changes.
|
2020-05-18 03:36:43 +03:00
|
|
|
project-vc-merge-submodules
|
|
|
|
(project--submodule-p root))
|
|
|
|
(let* ((parent (file-name-directory
|
|
|
|
(directory-file-name root))))
|
|
|
|
(vc-call-backend backend 'root parent))
|
|
|
|
root)))))
|
2019-12-27 18:18:41 +03:00
|
|
|
('nil nil)
|
|
|
|
(_ (ignore-errors (vc-call-backend backend 'root dir))))))
|
2015-07-10 04:34:41 +03:00
|
|
|
(and root (cons 'vc root))))
|
2020-05-18 03:36:43 +03:00
|
|
|
|
|
|
|
(defun project--submodule-p (root)
|
|
|
|
;; XXX: We only support Git submodules for now.
|
|
|
|
;;
|
|
|
|
;; For submodules, at least, we expect the users to prefer them to
|
|
|
|
;; be considered part of the parent project. For those who don't,
|
|
|
|
;; there is the custom var now.
|
|
|
|
;;
|
|
|
|
;; Some users may also set up things equivalent to Git submodules
|
2020-05-18 03:44:26 +03:00
|
|
|
;; using "git worktree" (for example). However, we expect that most
|
|
|
|
;; of them would prefer to treat those as separate projects anyway.
|
2020-05-18 03:36:43 +03:00
|
|
|
(let* ((gitfile (expand-file-name ".git" root)))
|
|
|
|
(cond
|
|
|
|
((file-directory-p gitfile)
|
|
|
|
nil)
|
|
|
|
((with-temp-buffer
|
|
|
|
(insert-file-contents gitfile)
|
|
|
|
(goto-char (point-min))
|
|
|
|
;; Kind of a hack to distinguish a submodule from
|
|
|
|
;; other cases of .git files pointing elsewhere.
|
|
|
|
(looking-at "gitdir: [./]+/\\.git/modules/"))
|
|
|
|
t)
|
|
|
|
(t nil))))
|
2015-07-10 04:34:41 +03:00
|
|
|
|
2020-05-23 04:38:27 +03:00
|
|
|
(cl-defmethod project-root ((project (head vc)))
|
|
|
|
(cdr project))
|
2015-07-10 04:34:41 +03:00
|
|
|
|
2015-12-28 06:17:19 +02:00
|
|
|
(cl-defmethod project-external-roots ((project (head vc)))
|
2015-11-08 14:46:22 +02:00
|
|
|
(project-subtract-directories
|
|
|
|
(project-combine-directories
|
2015-12-28 06:17:19 +02:00
|
|
|
(mapcar
|
|
|
|
#'file-name-as-directory
|
|
|
|
(funcall project-vc-external-roots-function)))
|
2020-05-23 04:38:27 +03:00
|
|
|
(list (project-root project))))
|
2015-08-10 04:04:57 +03:00
|
|
|
|
2019-10-04 02:03:04 +03:00
|
|
|
(cl-defmethod project-files ((project (head vc)) &optional dirs)
|
|
|
|
(cl-mapcan
|
|
|
|
(lambda (dir)
|
|
|
|
(let (backend)
|
|
|
|
(if (and (file-equal-p dir (cdr project))
|
|
|
|
(setq backend (vc-responsible-backend dir))
|
|
|
|
(cond
|
|
|
|
((eq backend 'Hg))
|
|
|
|
((and (eq backend 'Git)
|
|
|
|
(or
|
|
|
|
(not project-vc-ignores)
|
|
|
|
(version<= "1.9" (vc-git--program-version)))))))
|
|
|
|
(project--vc-list-files dir backend project-vc-ignores)
|
|
|
|
(project--files-in-directory
|
|
|
|
dir
|
|
|
|
(project--dir-ignores project dir)))))
|
2020-05-23 04:38:27 +03:00
|
|
|
(or dirs
|
|
|
|
(list (project-root project)))))
|
2019-10-04 02:03:04 +03:00
|
|
|
|
2019-10-04 11:29:49 +03:00
|
|
|
(declare-function vc-git--program-version "vc-git")
|
|
|
|
(declare-function vc-git--run-command-string "vc-git")
|
|
|
|
(declare-function vc-hg-command "vc-hg")
|
|
|
|
|
2019-10-04 02:03:04 +03:00
|
|
|
(defun project--vc-list-files (dir backend extra-ignores)
|
|
|
|
(pcase backend
|
|
|
|
(`Git
|
2019-10-05 12:32:11 +03:00
|
|
|
(let ((default-directory (expand-file-name (file-name-as-directory dir)))
|
2019-12-27 18:18:41 +03:00
|
|
|
(args '("-z"))
|
|
|
|
files)
|
2019-10-04 02:03:04 +03:00
|
|
|
;; Include unregistered.
|
|
|
|
(setq args (append args '("-c" "-o" "--exclude-standard")))
|
|
|
|
(when extra-ignores
|
|
|
|
(setq args (append args
|
|
|
|
(cons "--"
|
|
|
|
(mapcar
|
|
|
|
(lambda (i)
|
|
|
|
(if (string-match "\\./" i)
|
|
|
|
(format ":!/:%s" (substring i 2))
|
|
|
|
(format ":!:%s" i)))
|
|
|
|
extra-ignores)))))
|
2019-12-27 18:18:41 +03:00
|
|
|
(setq files
|
|
|
|
(mapcar
|
|
|
|
(lambda (file) (concat default-directory file))
|
|
|
|
(split-string
|
|
|
|
(apply #'vc-git--run-command-string nil "ls-files" args)
|
|
|
|
"\0" t)))
|
|
|
|
;; Unfortunately, 'ls-files --recurse-submodules' conflicts with '-o'.
|
|
|
|
(let* ((submodules (project--git-submodules))
|
|
|
|
(sub-files
|
|
|
|
(mapcar
|
|
|
|
(lambda (module)
|
|
|
|
(when (file-directory-p module)
|
|
|
|
(project--vc-list-files
|
|
|
|
(concat default-directory module)
|
|
|
|
backend
|
|
|
|
extra-ignores)))
|
|
|
|
submodules)))
|
|
|
|
(setq files
|
|
|
|
(apply #'nconc files sub-files)))
|
2020-05-20 01:54:33 +03:00
|
|
|
;; 'git ls-files' returns duplicate entries for merge conflicts.
|
|
|
|
;; XXX: Better solutions welcome, but this seems cheap enough.
|
|
|
|
(delete-consecutive-dups files)))
|
2019-10-04 02:03:04 +03:00
|
|
|
(`Hg
|
2019-10-05 12:32:11 +03:00
|
|
|
(let ((default-directory (expand-file-name (file-name-as-directory dir)))
|
2019-10-04 15:50:16 +03:00
|
|
|
args)
|
2019-10-04 02:03:04 +03:00
|
|
|
;; Include unregistered.
|
2019-10-04 15:50:16 +03:00
|
|
|
(setq args (nconc args '("-mcardu" "--no-status" "-0")))
|
2019-10-04 02:03:04 +03:00
|
|
|
(when extra-ignores
|
|
|
|
(setq args (nconc args
|
|
|
|
(mapcan
|
|
|
|
(lambda (i)
|
|
|
|
(list "--exclude" i))
|
2019-10-04 11:29:49 +03:00
|
|
|
extra-ignores))))
|
2019-10-04 02:03:04 +03:00
|
|
|
(with-temp-buffer
|
2019-10-04 15:50:16 +03:00
|
|
|
(apply #'vc-hg-command t 0 "." "status" args)
|
|
|
|
(mapcar
|
2019-10-05 12:32:11 +03:00
|
|
|
(lambda (s) (concat default-directory s))
|
2019-10-04 15:50:16 +03:00
|
|
|
(split-string (buffer-string) "\0" t)))))))
|
2019-10-04 02:03:04 +03:00
|
|
|
|
2019-12-27 18:18:41 +03:00
|
|
|
(defun project--git-submodules ()
|
|
|
|
;; 'git submodule foreach' is much slower.
|
|
|
|
(condition-case nil
|
|
|
|
(with-temp-buffer
|
|
|
|
(insert-file-contents ".gitmodules")
|
|
|
|
(let (res)
|
|
|
|
(goto-char (point-min))
|
|
|
|
(while (re-search-forward "path *= *\\(.+\\)" nil t)
|
|
|
|
(push (match-string 1) res))
|
|
|
|
(nreverse res)))
|
|
|
|
(file-missing nil)))
|
|
|
|
|
2015-08-02 01:01:28 +03:00
|
|
|
(cl-defmethod project-ignores ((project (head vc)) dir)
|
2015-08-10 04:04:57 +03:00
|
|
|
(let* ((root (cdr project))
|
2015-08-02 01:01:28 +03:00
|
|
|
backend)
|
2015-08-10 04:04:57 +03:00
|
|
|
(append
|
2015-08-02 01:01:28 +03:00
|
|
|
(when (file-equal-p dir root)
|
|
|
|
(setq backend (vc-responsible-backend root))
|
|
|
|
(mapcar
|
|
|
|
(lambda (entry)
|
|
|
|
(if (string-match "\\`/" entry)
|
|
|
|
(replace-match "./" t t entry)
|
|
|
|
entry))
|
2015-08-10 04:04:57 +03:00
|
|
|
(vc-call-backend backend 'ignore-completion-table root)))
|
|
|
|
(project--value-in-dir 'project-vc-ignores root)
|
2019-01-19 03:46:07 +03:00
|
|
|
(mapcar
|
|
|
|
(lambda (dir)
|
|
|
|
(concat dir "/"))
|
|
|
|
vc-directory-exclusion-list))))
|
2015-07-12 17:18:09 +03:00
|
|
|
|
2015-11-03 02:11:45 +02:00
|
|
|
(defun project-combine-directories (&rest lists-of-dirs)
|
|
|
|
"Return a sorted and culled list of directory names.
|
|
|
|
Appends the elements of LISTS-OF-DIRS together, removes
|
|
|
|
non-existing directories, as well as directories a parent of
|
|
|
|
whose is already in the list."
|
2015-07-10 04:34:41 +03:00
|
|
|
(let* ((dirs (sort
|
|
|
|
(mapcar
|
|
|
|
(lambda (dir)
|
|
|
|
(file-name-as-directory (expand-file-name dir)))
|
2015-11-03 02:11:45 +02:00
|
|
|
(apply #'append lists-of-dirs))
|
2015-07-10 04:34:41 +03:00
|
|
|
#'string<))
|
|
|
|
(ref dirs))
|
|
|
|
;; Delete subdirectories from the list.
|
|
|
|
(while (cdr ref)
|
|
|
|
(if (string-prefix-p (car ref) (cadr ref))
|
|
|
|
(setcdr ref (cddr ref))
|
|
|
|
(setq ref (cdr ref))))
|
|
|
|
(cl-delete-if-not #'file-exists-p dirs)))
|
|
|
|
|
2015-11-03 02:11:45 +02:00
|
|
|
(defun project-subtract-directories (files dirs)
|
|
|
|
"Return a list of elements from FILES that are outside of DIRS.
|
|
|
|
DIRS must contain directory names."
|
2015-11-10 02:41:06 +02:00
|
|
|
;; Sidestep the issue of expanded/abbreviated file names here.
|
|
|
|
(cl-set-difference files dirs :test #'file-in-directory-p))
|
2015-11-03 02:11:45 +02:00
|
|
|
|
2015-08-10 04:04:57 +03:00
|
|
|
(defun project--value-in-dir (var dir)
|
|
|
|
(with-temp-buffer
|
|
|
|
(setq default-directory dir)
|
2016-01-08 14:32:27 +03:00
|
|
|
(let ((enable-local-variables :all))
|
|
|
|
(hack-dir-local-variables-non-file-buffer))
|
2015-08-10 04:04:57 +03:00
|
|
|
(symbol-value var)))
|
|
|
|
|
2015-11-06 05:08:51 +02:00
|
|
|
(declare-function grep-read-files "grep")
|
|
|
|
(declare-function xref--show-xrefs "xref")
|
2016-01-07 20:14:40 +03:00
|
|
|
(declare-function xref--find-ignores-arguments "xref")
|
2015-11-06 05:08:51 +02:00
|
|
|
|
|
|
|
;;;###autoload
|
|
|
|
(defun project-find-regexp (regexp)
|
2015-12-28 06:17:19 +02:00
|
|
|
"Find all matches for REGEXP in the current project's roots.
|
2015-11-06 05:08:51 +02:00
|
|
|
With \\[universal-argument] prefix, you can specify the directory
|
2017-11-17 15:39:02 +02:00
|
|
|
to search in, and the file name pattern to search for. The
|
|
|
|
pattern may use abbreviations defined in `grep-files-aliases',
|
|
|
|
e.g. entering `ch' is equivalent to `*.[ch]'. As whitespace
|
|
|
|
triggers completion when entering a pattern, including it
|
|
|
|
requires quoting, e.g. `\\[quoted-insert]<space>'."
|
2015-11-06 05:08:51 +02:00
|
|
|
(interactive (list (project--read-regexp)))
|
2019-12-29 15:22:11 +03:00
|
|
|
(require 'xref)
|
2020-03-21 13:26:19 +02:00
|
|
|
(require 'grep)
|
2015-11-10 02:41:06 +02:00
|
|
|
(let* ((pr (project-current t))
|
2019-01-18 06:38:12 +03:00
|
|
|
(files
|
|
|
|
(if (not current-prefix-arg)
|
2020-05-23 04:38:27 +03:00
|
|
|
(project-files pr)
|
2019-01-18 06:38:12 +03:00
|
|
|
(let ((dir (read-directory-name "Base directory: "
|
|
|
|
nil default-directory t)))
|
|
|
|
(project--files-in-directory dir
|
2019-05-03 01:52:05 +03:00
|
|
|
nil
|
2019-01-18 06:38:12 +03:00
|
|
|
(grep-read-files regexp))))))
|
2019-05-24 04:50:44 +03:00
|
|
|
(xref--show-xrefs
|
|
|
|
(apply-partially #'project--find-regexp-in-files regexp files)
|
|
|
|
nil)))
|
2019-01-18 06:38:12 +03:00
|
|
|
|
|
|
|
(defun project--dir-ignores (project dir)
|
2020-05-23 04:38:27 +03:00
|
|
|
(let ((root (project-root project)))
|
|
|
|
(if (not (file-in-directory-p dir root))
|
2019-01-19 03:46:07 +03:00
|
|
|
(project-ignores nil nil) ;The defaults.
|
2019-01-18 06:38:12 +03:00
|
|
|
(let ((ignores (project-ignores project root)))
|
|
|
|
(if (file-equal-p root dir)
|
|
|
|
ignores
|
|
|
|
;; FIXME: Update the "rooted" ignores to relate to DIR instead.
|
|
|
|
(cl-delete-if (lambda (str) (string-prefix-p "./" str))
|
|
|
|
ignores))))))
|
2015-11-06 05:08:51 +02:00
|
|
|
|
|
|
|
;;;###autoload
|
2015-12-28 06:17:19 +02:00
|
|
|
(defun project-or-external-find-regexp (regexp)
|
|
|
|
"Find all matches for REGEXP in the project roots or external roots.
|
2015-11-06 05:08:51 +02:00
|
|
|
With \\[universal-argument] prefix, you can specify the file name
|
|
|
|
pattern to search for."
|
|
|
|
(interactive (list (project--read-regexp)))
|
2019-12-29 15:22:11 +03:00
|
|
|
(require 'xref)
|
2015-11-10 02:41:06 +02:00
|
|
|
(let* ((pr (project-current t))
|
2019-01-18 06:38:12 +03:00
|
|
|
(files
|
2020-05-23 04:38:27 +03:00
|
|
|
(project-files pr (cons
|
|
|
|
(project-root pr)
|
2019-01-18 06:38:12 +03:00
|
|
|
(project-external-roots pr)))))
|
2019-05-24 04:50:44 +03:00
|
|
|
(xref--show-xrefs
|
|
|
|
(apply-partially #'project--find-regexp-in-files regexp files)
|
|
|
|
nil)))
|
2019-01-18 06:38:12 +03:00
|
|
|
|
|
|
|
(defun project--find-regexp-in-files (regexp files)
|
2020-03-30 23:16:03 +03:00
|
|
|
(unless files
|
|
|
|
(user-error "Empty file list"))
|
2019-12-29 15:11:53 +03:00
|
|
|
(let ((xrefs (xref-matches-in-files regexp files)))
|
2019-01-18 06:38:12 +03:00
|
|
|
(unless xrefs
|
|
|
|
(user-error "No matches for: %s" regexp))
|
2019-05-24 04:50:44 +03:00
|
|
|
xrefs))
|
2019-01-18 06:38:12 +03:00
|
|
|
|
|
|
|
(defun project--process-file-region (start end program
|
|
|
|
&optional buffer display
|
|
|
|
&rest args)
|
|
|
|
;; FIXME: This branching shouldn't be necessary, but
|
|
|
|
;; call-process-region *is* measurably faster, even for a program
|
|
|
|
;; doing some actual work (for a period of time). Even though
|
|
|
|
;; call-process-region also creates a temp file internally
|
|
|
|
;; (http://lists.gnu.org/archive/html/emacs-devel/2019-01/msg00211.html).
|
|
|
|
(if (not (file-remote-p default-directory))
|
|
|
|
(apply #'call-process-region
|
|
|
|
start end program nil buffer display args)
|
|
|
|
(let ((infile (make-temp-file "ppfr")))
|
|
|
|
(unwind-protect
|
|
|
|
(progn
|
|
|
|
(write-region start end infile nil 'silent)
|
|
|
|
(apply #'process-file program infile buffer display args))
|
|
|
|
(delete-file infile)))))
|
2015-11-06 05:08:51 +02:00
|
|
|
|
|
|
|
(defun project--read-regexp ()
|
2019-12-09 16:27:28 +02:00
|
|
|
(let ((sym (thing-at-point 'symbol)))
|
|
|
|
(read-regexp "Find regexp" (and sym (regexp-quote sym)))))
|
2015-11-06 05:08:51 +02:00
|
|
|
|
2016-01-08 14:32:27 +03:00
|
|
|
;;;###autoload
|
2019-05-20 15:24:47 -07:00
|
|
|
(defun project-find-file ()
|
2020-05-23 04:38:27 +03:00
|
|
|
"Visit a file (with completion) in the current project.
|
2016-01-29 17:43:26 -06:00
|
|
|
The completion default is the filename at point, if one is
|
|
|
|
recognized."
|
2016-01-07 20:14:40 +03:00
|
|
|
(interactive)
|
|
|
|
(let* ((pr (project-current t))
|
2020-05-23 04:38:27 +03:00
|
|
|
(dirs (list (project-root pr))))
|
2019-05-20 15:24:47 -07:00
|
|
|
(project-find-file-in (thing-at-point 'filename) dirs pr)))
|
2016-01-07 20:14:40 +03:00
|
|
|
|
2016-01-08 14:32:27 +03:00
|
|
|
;;;###autoload
|
2016-01-07 20:14:40 +03:00
|
|
|
(defun project-or-external-find-file ()
|
2020-05-23 04:38:27 +03:00
|
|
|
"Visit a file (with completion) in the current project or external roots.
|
2016-01-29 17:43:26 -06:00
|
|
|
The completion default is the filename at point, if one is
|
|
|
|
recognized."
|
2016-01-07 20:14:40 +03:00
|
|
|
(interactive)
|
|
|
|
(let* ((pr (project-current t))
|
2020-05-23 04:38:27 +03:00
|
|
|
(dirs (cons
|
|
|
|
(project-root pr)
|
2016-01-07 20:14:40 +03:00
|
|
|
(project-external-roots pr))))
|
2016-01-29 17:43:26 -06:00
|
|
|
(project-find-file-in (thing-at-point 'filename) dirs pr)))
|
2016-01-07 20:14:40 +03:00
|
|
|
|
2019-05-14 05:09:19 +03:00
|
|
|
(defcustom project-read-file-name-function #'project--read-file-cpd-relative
|
|
|
|
"Function to call to read a file name from a list.
|
|
|
|
For the arguments list, see `project--read-file-cpd-relative'."
|
2019-05-14 23:40:31 +03:00
|
|
|
:type '(choice (const :tag "Read with completion from relative names"
|
|
|
|
project--read-file-cpd-relative)
|
|
|
|
(const :tag "Read with completion from absolute names"
|
|
|
|
project--read-file-absolute)
|
|
|
|
(function :tag "Custom function" nil))
|
|
|
|
:version "27.1")
|
2019-05-14 05:09:19 +03:00
|
|
|
|
|
|
|
(defun project--read-file-cpd-relative (prompt
|
|
|
|
all-files &optional predicate
|
|
|
|
hist default)
|
2019-05-16 23:26:27 +01:00
|
|
|
"Read a file name, prompting with PROMPT.
|
|
|
|
ALL-FILES is a list of possible file name completions.
|
|
|
|
PREDICATE, HIST, and DEFAULT have the same meaning as in
|
|
|
|
`completing-read'."
|
2019-05-14 05:09:19 +03:00
|
|
|
(let* ((common-parent-directory
|
|
|
|
(let ((common-prefix (try-completion "" all-files)))
|
|
|
|
(if (> (length common-prefix) 0)
|
|
|
|
(file-name-directory common-prefix))))
|
|
|
|
(cpd-length (length common-parent-directory))
|
|
|
|
(prompt (if (zerop cpd-length)
|
|
|
|
prompt
|
|
|
|
(concat prompt (format " in %s" common-parent-directory))))
|
|
|
|
(substrings (mapcar (lambda (s) (substring s cpd-length)) all-files))
|
|
|
|
(new-collection (project--file-completion-table substrings))
|
|
|
|
(res (project--completing-read-strict prompt
|
|
|
|
new-collection
|
|
|
|
predicate
|
|
|
|
hist default)))
|
|
|
|
(concat common-parent-directory res)))
|
|
|
|
|
|
|
|
(defun project--read-file-absolute (prompt
|
|
|
|
all-files &optional predicate
|
|
|
|
hist default)
|
|
|
|
(project--completing-read-strict prompt
|
|
|
|
(project--file-completion-table all-files)
|
|
|
|
predicate
|
|
|
|
hist default))
|
|
|
|
|
2016-01-29 17:43:26 -06:00
|
|
|
(defun project-find-file-in (filename dirs project)
|
2016-01-30 07:21:31 +03:00
|
|
|
"Complete FILENAME in DIRS in PROJECT and visit the result."
|
2019-05-14 05:09:19 +03:00
|
|
|
(let* ((all-files (project-files project dirs))
|
|
|
|
(file (funcall project-read-file-name-function
|
|
|
|
"Find file" all-files nil nil
|
|
|
|
filename)))
|
2016-01-30 07:21:31 +03:00
|
|
|
(if (string= file "")
|
|
|
|
(user-error "You didn't specify the file")
|
|
|
|
(find-file file))))
|
|
|
|
|
|
|
|
(defun project--completing-read-strict (prompt
|
|
|
|
collection &optional predicate
|
2019-05-14 05:09:19 +03:00
|
|
|
hist default)
|
2016-01-30 11:55:19 +03:00
|
|
|
;; Tried both expanding the default before showing the prompt, and
|
|
|
|
;; removing it when it has no matches. Neither seems natural
|
|
|
|
;; enough. Removal is confusing; early expansion makes the prompt
|
|
|
|
;; too long.
|
2019-05-19 10:28:46 -07:00
|
|
|
(let* ((new-prompt (if (and default (not (string-equal default "")))
|
2016-01-30 07:21:31 +03:00
|
|
|
(format "%s (default %s): " prompt default)
|
|
|
|
(format "%s: " prompt)))
|
|
|
|
(res (completing-read new-prompt
|
2019-05-14 05:09:19 +03:00
|
|
|
collection predicate t
|
* lisp/multifile.el: New file, extracted from etags.el
The main motivation for this change was the introduction of
project-query-replace. dired's multi-file query&replace was implemented
on top of etags.el even though it did not use TAGS in any way, so I moved
this generic multifile code into its own package, with a nicer interface,
and then used that in project.el.
* lisp/progmodes/project.el (project-files): New generic function.
(project-search, project-query-replace): New commands.
* lisp/dired-aux.el (dired-do-search, dired-do-query-replace-regexp):
Use multifile.el instead of etags.el.
* lisp/progmodes/etags.el: Remove redundant :groups.
(next-file-list): Remove var.
(tags-loop-revert-buffers): Make it an obsolete alias.
(next-file): Don't autoload (it can't do anything useful before some
other etags.el function setup the multifile operation).
(tags--all-files): New function, extracted from next-file.
(tags-next-file): Rename from next-file.
Rewrite using tags--all-files and multifile-next-file.
(next-file): Keep it as an obsolete alias.
(tags-loop-operate, tags-loop-scan): Mark as obsolete.
(tags--compat-files, tags--compat-initialize): New function.
(tags-loop-continue): Rewrite using multifile-continue. Mark as obsolete.
(tags--last-search-operate-function): New var.
(tags-search, tags-query-replace): Rewrite using multifile.el.
* lisp/emacs-lisp/generator.el (iter-end-of-sequence): Use 'define-error'.
(iter-make): New macro.
(iter-empty): New iterator.
* lisp/menu-bar.el (menu-bar-search-menu, menu-bar-replace-menu):
tags-loop-continue -> multifile-continue.
2018-09-22 11:46:35 -04:00
|
|
|
nil ;; initial-input
|
2019-05-14 05:09:19 +03:00
|
|
|
hist default)))
|
2018-12-29 02:13:54 +02:00
|
|
|
(when (and (equal res default)
|
|
|
|
(not (test-completion res collection predicate)))
|
|
|
|
(setq res
|
|
|
|
(completing-read (format "%s: " prompt)
|
2019-05-14 05:09:19 +03:00
|
|
|
collection predicate t res hist nil)))
|
|
|
|
res))
|
2016-01-07 20:14:40 +03:00
|
|
|
|
2019-02-07 12:20:09 +03:00
|
|
|
(declare-function fileloop-continue "fileloop" ())
|
* lisp/multifile.el: New file, extracted from etags.el
The main motivation for this change was the introduction of
project-query-replace. dired's multi-file query&replace was implemented
on top of etags.el even though it did not use TAGS in any way, so I moved
this generic multifile code into its own package, with a nicer interface,
and then used that in project.el.
* lisp/progmodes/project.el (project-files): New generic function.
(project-search, project-query-replace): New commands.
* lisp/dired-aux.el (dired-do-search, dired-do-query-replace-regexp):
Use multifile.el instead of etags.el.
* lisp/progmodes/etags.el: Remove redundant :groups.
(next-file-list): Remove var.
(tags-loop-revert-buffers): Make it an obsolete alias.
(next-file): Don't autoload (it can't do anything useful before some
other etags.el function setup the multifile operation).
(tags--all-files): New function, extracted from next-file.
(tags-next-file): Rename from next-file.
Rewrite using tags--all-files and multifile-next-file.
(next-file): Keep it as an obsolete alias.
(tags-loop-operate, tags-loop-scan): Mark as obsolete.
(tags--compat-files, tags--compat-initialize): New function.
(tags-loop-continue): Rewrite using multifile-continue. Mark as obsolete.
(tags--last-search-operate-function): New var.
(tags-search, tags-query-replace): Rewrite using multifile.el.
* lisp/emacs-lisp/generator.el (iter-end-of-sequence): Use 'define-error'.
(iter-make): New macro.
(iter-empty): New iterator.
* lisp/menu-bar.el (menu-bar-search-menu, menu-bar-replace-menu):
tags-loop-continue -> multifile-continue.
2018-09-22 11:46:35 -04:00
|
|
|
|
|
|
|
;;;###autoload
|
|
|
|
(defun project-search (regexp)
|
|
|
|
"Search for REGEXP in all the files of the project.
|
|
|
|
Stops when a match is found.
|
2020-03-21 13:26:19 +02:00
|
|
|
To continue searching for the next match, use the
|
|
|
|
command \\[fileloop-continue]."
|
* lisp/multifile.el: New file, extracted from etags.el
The main motivation for this change was the introduction of
project-query-replace. dired's multi-file query&replace was implemented
on top of etags.el even though it did not use TAGS in any way, so I moved
this generic multifile code into its own package, with a nicer interface,
and then used that in project.el.
* lisp/progmodes/project.el (project-files): New generic function.
(project-search, project-query-replace): New commands.
* lisp/dired-aux.el (dired-do-search, dired-do-query-replace-regexp):
Use multifile.el instead of etags.el.
* lisp/progmodes/etags.el: Remove redundant :groups.
(next-file-list): Remove var.
(tags-loop-revert-buffers): Make it an obsolete alias.
(next-file): Don't autoload (it can't do anything useful before some
other etags.el function setup the multifile operation).
(tags--all-files): New function, extracted from next-file.
(tags-next-file): Rename from next-file.
Rewrite using tags--all-files and multifile-next-file.
(next-file): Keep it as an obsolete alias.
(tags-loop-operate, tags-loop-scan): Mark as obsolete.
(tags--compat-files, tags--compat-initialize): New function.
(tags-loop-continue): Rewrite using multifile-continue. Mark as obsolete.
(tags--last-search-operate-function): New var.
(tags-search, tags-query-replace): Rewrite using multifile.el.
* lisp/emacs-lisp/generator.el (iter-end-of-sequence): Use 'define-error'.
(iter-make): New macro.
(iter-empty): New iterator.
* lisp/menu-bar.el (menu-bar-search-menu, menu-bar-replace-menu):
tags-loop-continue -> multifile-continue.
2018-09-22 11:46:35 -04:00
|
|
|
(interactive "sSearch (regexp): ")
|
2019-02-07 12:20:09 +03:00
|
|
|
(fileloop-initialize-search
|
* lisp/multifile.el: New file, extracted from etags.el
The main motivation for this change was the introduction of
project-query-replace. dired's multi-file query&replace was implemented
on top of etags.el even though it did not use TAGS in any way, so I moved
this generic multifile code into its own package, with a nicer interface,
and then used that in project.el.
* lisp/progmodes/project.el (project-files): New generic function.
(project-search, project-query-replace): New commands.
* lisp/dired-aux.el (dired-do-search, dired-do-query-replace-regexp):
Use multifile.el instead of etags.el.
* lisp/progmodes/etags.el: Remove redundant :groups.
(next-file-list): Remove var.
(tags-loop-revert-buffers): Make it an obsolete alias.
(next-file): Don't autoload (it can't do anything useful before some
other etags.el function setup the multifile operation).
(tags--all-files): New function, extracted from next-file.
(tags-next-file): Rename from next-file.
Rewrite using tags--all-files and multifile-next-file.
(next-file): Keep it as an obsolete alias.
(tags-loop-operate, tags-loop-scan): Mark as obsolete.
(tags--compat-files, tags--compat-initialize): New function.
(tags-loop-continue): Rewrite using multifile-continue. Mark as obsolete.
(tags--last-search-operate-function): New var.
(tags-search, tags-query-replace): Rewrite using multifile.el.
* lisp/emacs-lisp/generator.el (iter-end-of-sequence): Use 'define-error'.
(iter-make): New macro.
(iter-empty): New iterator.
* lisp/menu-bar.el (menu-bar-search-menu, menu-bar-replace-menu):
tags-loop-continue -> multifile-continue.
2018-09-22 11:46:35 -04:00
|
|
|
regexp (project-files (project-current t)) 'default)
|
2019-02-07 12:20:09 +03:00
|
|
|
(fileloop-continue))
|
* lisp/multifile.el: New file, extracted from etags.el
The main motivation for this change was the introduction of
project-query-replace. dired's multi-file query&replace was implemented
on top of etags.el even though it did not use TAGS in any way, so I moved
this generic multifile code into its own package, with a nicer interface,
and then used that in project.el.
* lisp/progmodes/project.el (project-files): New generic function.
(project-search, project-query-replace): New commands.
* lisp/dired-aux.el (dired-do-search, dired-do-query-replace-regexp):
Use multifile.el instead of etags.el.
* lisp/progmodes/etags.el: Remove redundant :groups.
(next-file-list): Remove var.
(tags-loop-revert-buffers): Make it an obsolete alias.
(next-file): Don't autoload (it can't do anything useful before some
other etags.el function setup the multifile operation).
(tags--all-files): New function, extracted from next-file.
(tags-next-file): Rename from next-file.
Rewrite using tags--all-files and multifile-next-file.
(next-file): Keep it as an obsolete alias.
(tags-loop-operate, tags-loop-scan): Mark as obsolete.
(tags--compat-files, tags--compat-initialize): New function.
(tags-loop-continue): Rewrite using multifile-continue. Mark as obsolete.
(tags--last-search-operate-function): New var.
(tags-search, tags-query-replace): Rewrite using multifile.el.
* lisp/emacs-lisp/generator.el (iter-end-of-sequence): Use 'define-error'.
(iter-make): New macro.
(iter-empty): New iterator.
* lisp/menu-bar.el (menu-bar-search-menu, menu-bar-replace-menu):
tags-loop-continue -> multifile-continue.
2018-09-22 11:46:35 -04:00
|
|
|
|
|
|
|
;;;###autoload
|
2019-02-14 04:08:44 +03:00
|
|
|
(defun project-query-replace-regexp (from to)
|
2020-03-21 13:26:19 +02:00
|
|
|
"Query-replace REGEXP in all the files of the project.
|
|
|
|
Stops when a match is found and prompts for whether to replace it.
|
|
|
|
If you exit the query-replace, you can later continue the query-replace
|
|
|
|
loop using the command \\[fileloop-continue]."
|
* lisp/multifile.el: New file, extracted from etags.el
The main motivation for this change was the introduction of
project-query-replace. dired's multi-file query&replace was implemented
on top of etags.el even though it did not use TAGS in any way, so I moved
this generic multifile code into its own package, with a nicer interface,
and then used that in project.el.
* lisp/progmodes/project.el (project-files): New generic function.
(project-search, project-query-replace): New commands.
* lisp/dired-aux.el (dired-do-search, dired-do-query-replace-regexp):
Use multifile.el instead of etags.el.
* lisp/progmodes/etags.el: Remove redundant :groups.
(next-file-list): Remove var.
(tags-loop-revert-buffers): Make it an obsolete alias.
(next-file): Don't autoload (it can't do anything useful before some
other etags.el function setup the multifile operation).
(tags--all-files): New function, extracted from next-file.
(tags-next-file): Rename from next-file.
Rewrite using tags--all-files and multifile-next-file.
(next-file): Keep it as an obsolete alias.
(tags-loop-operate, tags-loop-scan): Mark as obsolete.
(tags--compat-files, tags--compat-initialize): New function.
(tags-loop-continue): Rewrite using multifile-continue. Mark as obsolete.
(tags--last-search-operate-function): New var.
(tags-search, tags-query-replace): Rewrite using multifile.el.
* lisp/emacs-lisp/generator.el (iter-end-of-sequence): Use 'define-error'.
(iter-make): New macro.
(iter-empty): New iterator.
* lisp/menu-bar.el (menu-bar-search-menu, menu-bar-replace-menu):
tags-loop-continue -> multifile-continue.
2018-09-22 11:46:35 -04:00
|
|
|
(interactive
|
|
|
|
(pcase-let ((`(,from ,to)
|
|
|
|
(query-replace-read-args "Query replace (regexp)" t t)))
|
|
|
|
(list from to)))
|
2019-02-07 12:20:09 +03:00
|
|
|
(fileloop-initialize-replace
|
* lisp/multifile.el: New file, extracted from etags.el
The main motivation for this change was the introduction of
project-query-replace. dired's multi-file query&replace was implemented
on top of etags.el even though it did not use TAGS in any way, so I moved
this generic multifile code into its own package, with a nicer interface,
and then used that in project.el.
* lisp/progmodes/project.el (project-files): New generic function.
(project-search, project-query-replace): New commands.
* lisp/dired-aux.el (dired-do-search, dired-do-query-replace-regexp):
Use multifile.el instead of etags.el.
* lisp/progmodes/etags.el: Remove redundant :groups.
(next-file-list): Remove var.
(tags-loop-revert-buffers): Make it an obsolete alias.
(next-file): Don't autoload (it can't do anything useful before some
other etags.el function setup the multifile operation).
(tags--all-files): New function, extracted from next-file.
(tags-next-file): Rename from next-file.
Rewrite using tags--all-files and multifile-next-file.
(next-file): Keep it as an obsolete alias.
(tags-loop-operate, tags-loop-scan): Mark as obsolete.
(tags--compat-files, tags--compat-initialize): New function.
(tags-loop-continue): Rewrite using multifile-continue. Mark as obsolete.
(tags--last-search-operate-function): New var.
(tags-search, tags-query-replace): Rewrite using multifile.el.
* lisp/emacs-lisp/generator.el (iter-end-of-sequence): Use 'define-error'.
(iter-make): New macro.
(iter-empty): New iterator.
* lisp/menu-bar.el (menu-bar-search-menu, menu-bar-replace-menu):
tags-loop-continue -> multifile-continue.
2018-09-22 11:46:35 -04:00
|
|
|
from to (project-files (project-current t)) 'default)
|
2019-02-07 12:20:09 +03:00
|
|
|
(fileloop-continue))
|
* lisp/multifile.el: New file, extracted from etags.el
The main motivation for this change was the introduction of
project-query-replace. dired's multi-file query&replace was implemented
on top of etags.el even though it did not use TAGS in any way, so I moved
this generic multifile code into its own package, with a nicer interface,
and then used that in project.el.
* lisp/progmodes/project.el (project-files): New generic function.
(project-search, project-query-replace): New commands.
* lisp/dired-aux.el (dired-do-search, dired-do-query-replace-regexp):
Use multifile.el instead of etags.el.
* lisp/progmodes/etags.el: Remove redundant :groups.
(next-file-list): Remove var.
(tags-loop-revert-buffers): Make it an obsolete alias.
(next-file): Don't autoload (it can't do anything useful before some
other etags.el function setup the multifile operation).
(tags--all-files): New function, extracted from next-file.
(tags-next-file): Rename from next-file.
Rewrite using tags--all-files and multifile-next-file.
(next-file): Keep it as an obsolete alias.
(tags-loop-operate, tags-loop-scan): Mark as obsolete.
(tags--compat-files, tags--compat-initialize): New function.
(tags-loop-continue): Rewrite using multifile-continue. Mark as obsolete.
(tags--last-search-operate-function): New var.
(tags-search, tags-query-replace): Rewrite using multifile.el.
* lisp/emacs-lisp/generator.el (iter-end-of-sequence): Use 'define-error'.
(iter-make): New macro.
(iter-empty): New iterator.
* lisp/menu-bar.el (menu-bar-search-menu, menu-bar-replace-menu):
tags-loop-continue -> multifile-continue.
2018-09-22 11:46:35 -04:00
|
|
|
|
2020-05-19 19:30:14 +02:00
|
|
|
;;;###autoload
|
|
|
|
(defun project-compile ()
|
|
|
|
"Run `compile' in the project root."
|
|
|
|
(interactive)
|
|
|
|
(let* ((pr (project-current t))
|
2020-05-23 04:38:27 +03:00
|
|
|
(default-directory (project-root pr)))
|
2020-05-19 19:30:14 +02:00
|
|
|
(call-interactively 'compile)))
|
|
|
|
|
2015-07-10 04:34:41 +03:00
|
|
|
(provide 'project)
|
|
|
|
;;; project.el ends here
|