summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSean Whitton <spwhitton@spwhitton.name>2020-07-25 20:10:53 -0700
committerSean Whitton <spwhitton@spwhitton.name>2020-07-25 20:10:53 -0700
commitc2c422ba2c1851d86a99038fde2e93805e217394 (patch)
treef76c7cc101d8dc6b3f1533b5f9c0b4762e5e2f29
parentf3de5bd709b1909a9dd5956bc38d3ca4ba9295a4 (diff)
parent9f01ce6327af886f26399924a9aadf16cdd4fd9f (diff)
downloademacs-c2c422ba2c1851d86a99038fde2e93805e217394.tar.gz
Merge remote-tracking branch 'origin/master' into athena/unstable
-rw-r--r--doc/emacs/custom.texi4
-rw-r--r--doc/emacs/emacs.texi6
-rw-r--r--doc/emacs/maintaining.texi161
-rw-r--r--etc/NEWS20
-rw-r--r--lisp/emacs-lisp/byte-opt.el10
-rw-r--r--lisp/emacs-lisp/eldoc.el61
-rw-r--r--lisp/gnus/mm-decode.el10
-rw-r--r--lisp/ielm.el4
-rw-r--r--lisp/man.el4
-rw-r--r--lisp/progmodes/bug-reference.el66
-rw-r--r--lisp/progmodes/project.el5
-rw-r--r--lisp/vc/vc-git.el2
-rw-r--r--src/emacs-module.c108
-rw-r--r--test/data/emacs-module/mod-test.c32
-rw-r--r--test/src/emacs-module-tests.el29
15 files changed, 396 insertions, 126 deletions
diff --git a/doc/emacs/custom.texi b/doc/emacs/custom.texi
index 00c8ee4f98b..acd7fb13ae1 100644
--- a/doc/emacs/custom.texi
+++ b/doc/emacs/custom.texi
@@ -1630,6 +1630,10 @@ characters are actually defined by this map.
@item
@vindex mode-specific-map
@code{mode-specific-map} is for characters that follow @kbd{C-c}.
+@item
+@vindex project-prefix-map
+@code{project-prefix-map} is for characters that follow @kbd{C-x p},
+used for project-related commands (@pxref{Projects}).
@end itemize
@node Local Keymaps
diff --git a/doc/emacs/emacs.texi b/doc/emacs/emacs.texi
index 6b82aeb8234..5b6b7b7e93e 100644
--- a/doc/emacs/emacs.texi
+++ b/doc/emacs/emacs.texi
@@ -857,6 +857,12 @@ Customizing VC
* CVS Options:: Options for CVS.
@end ifnottex
+Projects
+
+* Project File Commands:: Commands for handling project files.
+* Project Buffer Commands:: Commands for handling project buffers.
+* Switching Projects:: Switching between projects.
+
Change Logs
* Change Log Commands:: Commands for editing change log files.
diff --git a/doc/emacs/maintaining.texi b/doc/emacs/maintaining.texi
index cc7415e7ad5..43ec2d4e9f2 100644
--- a/doc/emacs/maintaining.texi
+++ b/doc/emacs/maintaining.texi
@@ -1657,12 +1657,43 @@ the project back-end. For example, the VC back-end doesn't consider
``ignored'' files (@pxref{VC Ignore}) to be part of the project.
@menu
-* Project File Commands:: Commands for handling project files.
-* Switching Projects:: Switching between projects.
+* Project File Commands:: Commands for handling project files.
+* Project Buffer Commands:: Commands for handling project buffers.
+* Switching Projects:: Switching between projects.
@end menu
@node Project File Commands
-@subsection Project File Commands
+@subsection Project Commands That Operate on Files
+
+@table @kbd
+@item C-x p f
+Visit a file that belongs to the current project
+(@code{project-find-file}).
+@item C-x p g
+Find matches for a regexp in all files that belong to the current
+project (@code{project-find-regexp}).
+@item M-x project-search
+Interactively search for regexp matches in all files that belong to
+the current project.
+@item C-x p r
+Perform query-replace for a regexp in all files that belong to the
+current project (@code{project-query-replace-regexp}).
+@item C-x p d
+Run Dired in the current project's root directory
+(@code{project-dired}).
+@item C-x p v
+Run @code{vc-dir} in the current project's root directory
+(@code{project-vc-dir}).
+@item C-x p s
+Start an inferior shell in the current project's root directory
+(@code{project-shell}).
+@item C-x p e
+Start Eshell in the current project's root directory
+(@code{project-eshell}).
+@item C-x p c
+Run compilation in the current project's root directory
+(@code{project-compile}).
+@end table
Emacs provides commands for handling project files conveniently.
This subsection describes these commands.
@@ -1676,25 +1707,26 @@ doesn't seem to belong to a recognizable project, these commands
prompt you for the project directory.
@findex project-find-file
- The command @code{project-find-file} is a convenient way of visiting
-files (@pxref{Visiting}) that belong to the current project. Unlike
-@kbd{C-x C-f}, this command doesn't require to type the full file name
-of the file to visit, you can type only the file's base name (i.e.,
-omit the leading directories). In addition, the completion candidates
-considered by the command include only the files belonging to the
-current project, and nothing else. If there's a file name at point,
-this command offers that file as the default to visit.
+ The command @kbd{C-x p f} (@code{project-find-file}) is a convenient
+way of visiting files (@pxref{Visiting}) that belong to the current
+project. Unlike @kbd{C-x C-f}, this command doesn't require to type
+the full file name of the file to visit, you can type only the file's
+base name (i.e., omit the leading directories). In addition, the
+completion candidates considered by the command include only the files
+belonging to the current project, and nothing else. If there's a file
+name at point, this command offers that file as the default to visit.
@findex project-find-regexp
- The command @code{project-find-regexp} is similar to @code{rgrep}
-(@pxref{Grep Searching}), but it searches only the files that belong
-to the current project. The command prompts for the regular
-expression to search, and pops up an Xref mode buffer with the search
-results, where you can select a match using the Xref mode commands
-(@pxref{Xref Commands}). When invoked with a prefix argument, this
-command additionally prompts for the base directory from which to
-start the search; this allows, for example, to limit the search only
-to project files under a certain subdirectory of the project root.
+ The command @kbd{C-x p g} (@code{project-find-regexp}) is similar to
+@code{rgrep} (@pxref{Grep Searching}), but it searches only the files
+that belong to the current project. The command prompts for the
+regular expression to search, and pops up an Xref mode buffer with the
+search results, where you can select a match using the Xref mode
+commands (@pxref{Xref Commands}). When invoked with a prefix
+argument, this command additionally prompts for the base directory
+from which to start the search; this allows, for example, to limit the
+search only to project files under a certain subdirectory of the
+project root.
@findex project-search
@kbd{M-x project-search} is an interactive variant of
@@ -1706,7 +1738,7 @@ matched file. To find the rest of the matches, type @w{@kbd{M-x
fileloop-continue @key{RET}}}.
@findex project-query-replace-regexp
- @kbd{M-x project-query-replace-regexp} is similar to
+ @kbd{C-x p r} (@code{project-query-replace-regexp}) is similar to
@code{project-search}, but it prompts you for whether to replace each
match it finds, like @code{query-replace} does (@pxref{Query
Replace}), and continues to the next match after you respond. If your
@@ -1714,40 +1746,85 @@ response causes Emacs to exit the query-replace loop, you can later
continue with @w{@kbd{M-x fileloop-continue @key{RET}}}.
@findex project-dired
- The command @code{project-dired} opens a Dired buffer
-(@pxref{Dired}) listing the files in the current project's root
+ The command @kbd{C-x p d} (@code{project-dired}) opens a Dired
+buffer (@pxref{Dired}) listing the files in the current project's root
directory.
@findex project-vc-dir
- The command @code{project-vc-dir} opens a VC Directory buffer
-(@pxref{VC Directory Mode}) listing the version control statuses of
-the files in a directory tree under the current project's
-root directory.
+ The command @kbd{C-x p v} (@code{project-vc-dir}) opens a VC
+Directory buffer (@pxref{VC Directory Mode}) listing the version
+control statuses of the files in a directory tree under the current
+project's root directory.
@findex project-shell
- The command @code{project-shell} starts a shell session
-(@pxref{Shell}) in a new buffer with the current project's root as the
-working directory.
+ The command @kbd{C-x p s} (@code{project-shell}) starts a shell
+session (@pxref{Shell}) in a new buffer with the current project's
+root as the working directory.
@findex project-eshell
- The command @code{project-eshell} starts an Eshell session in a new
-buffer with the current project's root as the working directory.
-@xref{Top,Eshell,Eshell, eshell, Eshell: The Emacs Shell}.
+ The command @kbd{C-x p e} (@code{project-eshell}) starts an Eshell
+session in a new buffer with the current project's root as the working
+directory. @xref{Top,Eshell,Eshell, eshell, Eshell: The Emacs Shell}.
+
+@findex project-compile
+ The command @kbd{C-x p c} (@code{project-compile}) runs compilation
+(@pxref{Compilation}) in the current project's root directory.
+
+@node Project Buffer Commands
+@subsection Project Commands That Operate on Buffers
+
+@table @kbd
+@item C-x p b
+Switch to another buffer belonging to the current project
+(@code{project-switch-to-buffer}).
+@item C-x p k
+Kill all live buffers that belong to the current project
+(@code{project-kill-buffers}).
+@end table
+
+@findex project-switch-to-buffer
+ Working on a project could potentially involve having many buffers
+visiting files that belong to the project, and also buffers that
+belong to the project, but don't visit any files (like the
+@file{*compilation*} buffer created by @code{project-compile}). The
+command @kbd{C-x p b} (@code{project-switch-to-buffer}) helps you
+switch between buffers that belong to the current project by prompting
+for a buffer to switch and considering only the current project's
+buffers as candidates for completion.
+
+@findex project-kill-buffers
+@vindex project-kill-buffers-ignores
+ When you finish working on the project, you may wish to kill all the
+buffers that belong to the project, to keep your Emacs session
+smaller. The command @kbd{C-x p k} (@code{project-kill-buffers})
+accomplishes that: it kills all the buffers that belong to the current
+project, except if @code{project-kill-buffers-ignores} tells
+otherwise.
@node Switching Projects
@subsection Switching Projects
+@table @kbd
+@item C-x p p
+Run an Emacs command for another project (@code{project-switch-project}).
+@end table
+
+@findex project-switch-project
+@vindex project-switch-commands
Commands that operate on project files (@pxref{Project File
Commands}) will conveniently prompt you for a project directory when
-no project is current. When you are inside a project but you want to
-operate on a different project, the command
-@code{project-switch-project} can be used.
-
- This command prompts you to choose a directory among known project
-roots, and then displays the menu of available commands to operate on
-the chosen project. The variable @code{project-switch-commands}
-controls which commands are available in the menu, and by which keys
-they are invoked.
+no project is current. When you are inside some project, but you want
+to operate on a different project, use the @kbd{C-x p p} command
+(@code{project-switch-project}). This command prompts you to choose a
+directory among known project roots, and then displays the menu of
+available commands to operate on the project you choose. The variable
+@code{project-switch-commands} controls which commands are available
+in the menu, and which key invokes each command.
+
+@vindex project-list-file
+ The variable @code{project-list-file} names the file in which Emacs
+records the list of known projects. It defaults to the file
+@file{projects} in @code{user-emacs-directory} (@pxref{Find Init}).
@node Change Log
@section Change Logs
diff --git a/etc/NEWS b/etc/NEWS
index 7c6c9fe2620..650b95867fb 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -502,21 +502,31 @@ information, see the related entry about 'shr-browse-url' above.
*** New user option 'project-vc-merge-submodules'.
-*** Previously used project directories are now suggested by
-all commands that prompt for a project directory.
+*** Project commands now have their own history.
+Previously used project directories are now suggested by all commands
+that prompt for a project directory.
+
++++
+*** New prefix keymap 'project-prefix-map'.
+Key sequences that invoke project-related commands start with the
+prefix 'C-x p'. Type "C-x p C-h" to show the full list.
+++
*** New commands 'project-dired', 'project-vc-dir', 'project-shell',
'project-eshell'. These commands run Dired/VC-Dir and Shell/Eshell in
a project's root directory, respectively.
-*** New command 'project-compile', which runs compilation.
++++
+*** New command 'project-compile'.
+This command runs compilation in the current project's root
+directory.
+++
*** New command 'project-switch-project'.
This command lets you "switch" to another project and run a project
command chosen from a dispatch menu.
++++
*** New user option 'project-list-file'.
** json.el
@@ -566,8 +576,8 @@ appropriate values for those two variables. There are three guessing
mechanisms so far: based on version control information of the current
buffer's file, based on newsgroup/mail-folder name and several news
and mail message headers in Gnus buffers, and based on IRC channel and
-server in rcirc buffers. All mechanisms are extensible with custom
-rules, see the variables 'bug-reference-setup-from-vc-alist',
+network in rcirc and ERC buffers. All mechanisms are extensible with
+custom rules, see the variables 'bug-reference-setup-from-vc-alist',
'bug-reference-setup-from-mail-alist', and
'bug-reference-setup-from-irc-alist'.
diff --git a/lisp/emacs-lisp/byte-opt.el b/lisp/emacs-lisp/byte-opt.el
index 194ceee176f..6f801be5457 100644
--- a/lisp/emacs-lisp/byte-opt.el
+++ b/lisp/emacs-lisp/byte-opt.el
@@ -709,6 +709,9 @@
(integer (if integer-is-first arg1 arg2))
(other (if integer-is-first arg2 arg1)))
(list (if (eq integer 1) '1+ '1-) other)))
+ ;; (+ x y z) -> (+ (+ x y) z)
+ ((= (length args) 3)
+ `(+ ,(byte-optimize-plus `(+ ,(car args) ,(cadr args))) ,@(cddr args)))
;; not further optimized
((equal args (cdr form)) form)
(t (cons '+ args)))))
@@ -737,6 +740,9 @@
((and (null (cdr args))
(numberp (car args)))
(- (car args)))
+ ;; (- x y z) -> (- (- x y) z)
+ ((= (length args) 3)
+ `(- ,(byte-optimize-minus `(- ,(car args) ,(cadr args))) ,@(cddr args)))
;; not further optimized
((equal args (cdr form)) form)
(t (cons '- args))))))
@@ -764,6 +770,10 @@
((null args) 1)
;; (* n) -> n, where n is a number
((and (null (cdr args)) (numberp (car args))) (car args))
+ ;; (* x y z) -> (* (* x y) z)
+ ((= (length args) 3)
+ `(* ,(byte-optimize-multiply `(* ,(car args) ,(cadr args)))
+ ,@(cddr args)))
;; not further optimized
((equal args (cdr form)) form)
(t (cons '* args)))))
diff --git a/lisp/emacs-lisp/eldoc.el b/lisp/emacs-lisp/eldoc.el
index 6ed5bff9f44..fcb104e5477 100644
--- a/lisp/emacs-lisp/eldoc.el
+++ b/lisp/emacs-lisp/eldoc.el
@@ -5,7 +5,7 @@
;; Author: Noah Friedman <friedman@splode.com>
;; Keywords: extensions
;; Created: 1995-10-06
-;; Version: 1.5.0
+;; Version: 1.6.0
;; Package-Requires: ((emacs "26.3"))
;; This is a GNU ELPA :core package. Avoid functionality that is not
@@ -340,16 +340,32 @@ Also store it in `eldoc-last-message' and return that value."
;; for us, but do note that the last-message will be gone.
(setq eldoc-last-message nil))))
-;; Decide whether now is a good time to display a message.
-(defun eldoc-display-message-p ()
- "Return non-nil when it is appropriate to display an ElDoc message."
- (and (eldoc-display-message-no-interference-p)
- ;; If this-command is non-nil while running via an idle
- ;; timer, we're still in the middle of executing a command,
- ;; e.g. a query-replace where it would be annoying to
- ;; overwrite the echo area.
- (not this-command)
- (eldoc--message-command-p last-command)))
+(defvar-local eldoc--last-request-state nil
+ "Tuple containing information about last ElDoc request.")
+(defun eldoc--request-state ()
+ "Compute information to store in `eldoc--last-request-state'."
+ (list (current-buffer) (buffer-modified-tick) (point)))
+
+(defun eldoc--request-docs-p (request-state)
+ "Return non-nil when it is appropriate to request docs.
+REQUEST-STATE is a candidate for `eldoc--last-request-state'"
+ (and
+ ;; FIXME: The original idea behind this function is to protect the
+ ;; Echo area from ElDoc interference, but since that is only one of
+ ;; the possible outlets of ElDoc, this must soon be reworked.
+ (eldoc-display-message-no-interference-p)
+ (not (and eldoc--doc-buffer
+ (get-buffer-window eldoc--doc-buffer)
+ (equal request-state
+ (with-current-buffer
+ eldoc--doc-buffer
+ eldoc--last-request-state))))
+ ;; If this-command is non-nil while running via an idle
+ ;; timer, we're still in the middle of executing a command,
+ ;; e.g. a query-replace where it would be annoying to
+ ;; overwrite the echo area.
+ (not this-command)
+ (eldoc--message-command-p last-command)))
;; Check various conditions about the current environment that might make
@@ -400,7 +416,8 @@ so that the global value (i.e. the default value of the hook) is
taken into account if the major mode specific function does not
return any documentation.")
-(defvar eldoc--doc-buffer nil "Buffer holding latest eldoc-produced docs.")
+(defvar eldoc--doc-buffer nil "Buffer displaying latest ElDoc-produced docs.")
+
(defun eldoc-doc-buffer (&optional interactive)
"Get latest *eldoc* help buffer. Interactively, display it."
(interactive (list t))
@@ -410,6 +427,7 @@ return any documentation.")
(setq eldoc--doc-buffer (get-buffer-create "*eldoc*")))
(when interactive (display-buffer eldoc--doc-buffer))))
+
(defun eldoc--handle-docs (docs)
"Display multiple DOCS in echo area.
DOCS is a list of (STRING PLIST...). It is already sorted.
@@ -429,9 +447,12 @@ Honor most of `eldoc-echo-area-use-multiline-p'."
(integer val)
(t 1)))
(things-reported-on)
+ (request eldoc--last-request-state)
single-doc single-doc-sym)
;; Then, compose the contents of the `*eldoc*' buffer.
(with-current-buffer (eldoc-doc-buffer)
+ ;; Set doc-buffer's `eldoc--last-request-state', too
+ (setq eldoc--last-request-state request)
(let ((inhibit-read-only t))
(erase-buffer) (setq buffer-read-only t)
(local-set-key "q" 'quit-window)
@@ -741,14 +762,16 @@ should endeavour to display the docstrings eventually produced."
(defun eldoc-print-current-symbol-info (&optional interactive)
"Document thing at point."
(interactive '(t))
- (cond (interactive
- (eldoc--invoke-strategy))
- (t
- (if (not (eldoc-display-message-p))
- ;; Erase the last message if we won't display a new one.
- (when eldoc-last-message
- (eldoc--message nil))
+ (let ((token (eldoc--request-state)))
+ (cond (interactive
+ (eldoc--invoke-strategy))
+ ((not (eldoc--request-docs-p token))
+ ;; Erase the last message if we won't display a new one.
+ (when eldoc-last-message
+ (eldoc--message nil)))
+ (t
(let ((non-essential t))
+ (setq eldoc--last-request-state token)
;; Only keep looking for the info as long as the user hasn't
;; requested our attention. This also locally disables
;; inhibit-quit.
diff --git a/lisp/gnus/mm-decode.el b/lisp/gnus/mm-decode.el
index 96695aabfde..587c4e01b92 100644
--- a/lisp/gnus/mm-decode.el
+++ b/lisp/gnus/mm-decode.el
@@ -1680,8 +1680,14 @@ If RECURSIVE, search recursively."
(t (y-or-n-p
(format "Decrypt (S/MIME) part? "))))
(mm-view-pkcs7 parts from))
- (goto-char (point-min))
- (insert "Content-type: text/plain\n\n")
+ ;; Normally there will be a Content-type header here, but
+ ;; some mailers don't add that to the encrypted part, which
+ ;; makes the subsequent re-dissection fail here.
+ (save-restriction
+ (mail-narrow-to-head)
+ (unless (mail-fetch-field "content-type")
+ (goto-char (point-max))
+ (insert "Content-type: text/plain\n\n")))
(setq parts (mm-dissect-buffer t)))))
((equal subtype "signed")
(unless (and (setq protocol
diff --git a/lisp/ielm.el b/lisp/ielm.el
index 47c5158ce41..b3654b91d37 100644
--- a/lisp/ielm.el
+++ b/lisp/ielm.el
@@ -537,7 +537,9 @@ Customized bindings may be defined in `ielm-map', which currently contains:
'(comint-replace-by-expanded-history
ielm-complete-filename elisp-completion-at-point))
(add-hook 'eldoc-documentation-functions
- #'elisp-eldoc-documentation-function nil t)
+ #'elisp-eldoc-var-docstring nil t)
+ (add-hook 'eldoc-documentation-functions
+ #'elisp-eldoc-funcall nil t)
(set (make-local-variable 'ielm-prompt-internal) ielm-prompt)
(set (make-local-variable 'comint-prompt-read-only) ielm-prompt-read-only)
(setq comint-get-old-input 'ielm-get-old-input)
diff --git a/lisp/man.el b/lisp/man.el
index 8a36f3ac25d..3121334c6f9 100644
--- a/lisp/man.el
+++ b/lisp/man.el
@@ -1396,7 +1396,7 @@ synchronously, PROCESS is the name of the buffer where the manpage
command is run. Second argument STRING is the entire string of output."
(save-excursion
(let ((Man-buffer (process-buffer process)))
- (if (null (buffer-name Man-buffer)) ;; deleted buffer
+ (if (not (buffer-live-p Man-buffer)) ;; deleted buffer
(set-process-buffer process nil)
(with-current-buffer Man-buffer
@@ -1430,7 +1430,7 @@ manpage command."
(delete-buff nil)
message)
- (if (null (buffer-name Man-buffer)) ;; deleted buffer
+ (if (not (buffer-live-p Man-buffer)) ;; deleted buffer
(or (stringp process)
(set-process-buffer process nil))
diff --git a/lisp/progmodes/bug-reference.el b/lisp/progmodes/bug-reference.el
index b88ea0af82c..c52331f84fa 100644
--- a/lisp/progmodes/bug-reference.el
+++ b/lisp/progmodes/bug-reference.el
@@ -353,38 +353,45 @@ and set it if applicable."
This takes action if `bug-reference-mode' is enabled in IRC
channels using one of Emacs' IRC clients (rcirc and ERC).
-Currently, only rcirc is supported.
+Currently, rcirc and ERC are supported.
Each element has the form
- (CHANNEL-REGEXP SERVER-REGEXP BUG-REGEXP URL-FORMAT)
+ (CHANNEL-REGEXP NETWORK-REGEXP BUG-REGEXP URL-FORMAT)
-CHANNEL-REGEXP is a regexp matched against the current mail IRC
-channel name. SERVER-REGEXP is matched against the IRC server
-name. If any of those matches, BUG-REGEXP is set as
+CHANNEL-REGEXP is a regexp matched against the current IRC
+channel name (e.g. #emacs). NETWORK-REGEXP is matched against
+the IRC network name (e.g. freenode). Both entries are optional.
+If all given entries match, BUG-REGEXP is set as
`bug-reference-bug-regexp' and URL-FORMAT is set as
`bug-reference-url-format'.")
-(defun bug-reference--maybe-setup-from-irc (channel server)
- "Set up according to IRC CHANNEL or SERVER.
-CHANNEL is an IRC channel name and SERVER is that channel's
-server name.
+(defun bug-reference--maybe-setup-from-irc (channel network)
+ "Set up according to IRC CHANNEL or NETWORK.
+CHANNEL is an IRC channel name (or generally a target, i.e., it
+could also be a user name) and NETWORK is that channel's network
+name.
-If any CHANNEL-REGEXP or SERVER-REGEXP of
-`bug-reference-setup-from-irc-alist' matches CHANNEL or SERVER,
-the corresponding BUG-REGEXP and URL-FORMAT are set."
+If any `bug-reference-setup-from-irc-alist' entry's
+CHANNEL-REGEXP and NETWORK-REGEXP match CHANNEL and NETWORK, the
+corresponding BUG-REGEXP and URL-FORMAT are set."
(catch 'setup-done
(dolist (config bug-reference-setup-from-irc-alist)
- (when (or
- (and channel
- (car config)
- (string-match-p (car config) channel))
- (and server
- (nth 1 config)
- (string-match-p (car config) server)))
- (setq-local bug-reference-bug-regexp (nth 2 config))
- (setq-local bug-reference-url-format (nth 3 config))
- (throw 'setup-done t)))))
+ (let ((channel-rx (car config))
+ (network-rx (nth 1 config)))
+ (when (and
+ ;; One of both has to be given.
+ (or channel-rx network-rx)
+ ;; The args have to be set.
+ channel network)
+ (when (and
+ (or (null channel-rx)
+ (string-match-p channel-rx channel))
+ (or (null network-rx)
+ (string-match-p network-rx network)))
+ (setq-local bug-reference-bug-regexp (nth 2 config))
+ (setq-local bug-reference-url-format (nth 3 config))
+ (throw 'setup-done t)))))))
(defvar rcirc-target)
(defvar rcirc-server-buffer)
@@ -402,6 +409,18 @@ and set it if applicable."
(with-current-buffer rcirc-server-buffer
rcirc-server)))))
+(declare-function erc-format-target "erc")
+(declare-function erc-network-name "erc-networks")
+
+(defun bug-reference-try-setup-from-erc ()
+ "Try setting up `bug-reference-mode' based on ERC channel and server.
+Test each configuration in `bug-reference-setup-from-irc-alist'
+and set it if applicable."
+ (when (derived-mode-p 'erc-mode)
+ (bug-reference--maybe-setup-from-irc
+ (erc-format-target)
+ (erc-network-name))))
+
(defun bug-reference--run-auto-setup ()
(when (or bug-reference-mode
bug-reference-prog-mode)
@@ -414,7 +433,8 @@ and set it if applicable."
(catch 'setup
(dolist (f (list #'bug-reference-try-setup-from-vc
#'bug-reference-try-setup-from-gnus
- #'bug-reference-try-setup-from-rcirc))
+ #'bug-reference-try-setup-from-rcirc
+ #'bug-reference-try-setup-from-erc))
(when (funcall f)
(throw 'setup t))))))))
diff --git a/lisp/progmodes/project.el b/lisp/progmodes/project.el
index a0930553bd7..5cfc6a20986 100644
--- a/lisp/progmodes/project.el
+++ b/lisp/progmodes/project.el
@@ -197,7 +197,7 @@ of the project instance object."
pr (project--find-in-directory directory))))
(when maybe-prompt
(if pr
- (project--add-to-project-list-front pr)
+ (project-remember-project pr)
(project--remove-from-project-list directory)
(setq pr (cons 'transient directory))))
pr))
@@ -987,7 +987,8 @@ With some possible metadata (to be decided).")
(pp project--list (current-buffer))
(write-region nil nil filename nil 'silent))))
-(defun project--add-to-project-list-front (pr)
+;;;###autoload
+(defun project-remember-project (pr)
"Add project PR to the front of the project list.
Save the result in `project-list-file' if the list of projects has changed."
(project--ensure-read-project-list)
diff --git a/lisp/vc/vc-git.el b/lisp/vc/vc-git.el
index b5cb842aeee..7f6e1db1ed7 100644
--- a/lisp/vc/vc-git.el
+++ b/lisp/vc/vc-git.el
@@ -1237,7 +1237,7 @@ log entries."
(set (make-local-variable 'log-view-message-re)
(if (not (memq vc-log-view-type '(long log-search with-diff)))
(cadr vc-git-root-log-format)
- "^commit *\\([0-9a-z]+\\)"))
+ "^commit +\\([0-9a-z]+\\)"))
;; Allow expanding short log entries.
(when (memq vc-log-view-type '(short log-outgoing log-incoming mergebase))
(setq truncate-lines t)
diff --git a/src/emacs-module.c b/src/emacs-module.c
index 3d1827c7dad..e4e7da088d7 100644
--- a/src/emacs-module.c
+++ b/src/emacs-module.c
@@ -78,6 +78,7 @@ To add a new module function, proceed as follows:
#include "emacs-module.h"
#include <stdarg.h>
+#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
@@ -154,11 +155,11 @@ struct emacs_value_frame
/* A structure that holds an initial frame (so that the first local
values require no dynamic allocation) and keeps track of the
current frame. */
-static struct emacs_value_storage
+struct emacs_value_storage
{
struct emacs_value_frame initial;
struct emacs_value_frame *current;
-} global_storage;
+};
/* Private runtime and environment members. */
@@ -371,10 +372,57 @@ module_get_environment (struct emacs_runtime *runtime)
}
/* To make global refs (GC-protected global values) keep a hash that
- maps global Lisp objects to reference counts. */
+ maps global Lisp objects to 'struct module_global_reference'
+ objects. We store the 'emacs_value' in the hash table so that it
+ is automatically garbage-collected (Bug#42482). */
static Lisp_Object Vmodule_refs_hash;
+/* Pseudovector type for global references. The pseudovector tag is
+ PVEC_OTHER since these values are never printed and don't need to
+ be special-cased for garbage collection. */
+
+struct module_global_reference {
+ /* Pseudovector header, must come first. */
+ union vectorlike_header header;
+
+ /* Holds the emacs_value for the object. The Lisp_Object stored
+ therein must be the same as the hash key. */
+ struct emacs_value_tag value;
+
+ /* Reference count, always positive. */
+ ptrdiff_t refcount;
+};
+
+static struct module_global_reference *
+XMODULE_GLOBAL_REFERENCE (Lisp_Object o)
+{
+ eassert (PSEUDOVECTORP (o, PVEC_OTHER));
+ return XUNTAG (o, Lisp_Vectorlike, struct module_global_reference);
+}
+
+/* Returns whether V is a global reference. Only used to check module
+ assertions. If V is not a global reference, increment *N by the
+ number of global references (for debugging output). */
+
+static bool
+module_global_reference_p (emacs_value v, ptrdiff_t *n)
+{
+ struct Lisp_Hash_Table *h = XHASH_TABLE (Vmodule_refs_hash);
+ /* Note that we can't use `hash_lookup' because V might be a local
+ reference that's identical to some global reference. */
+ for (ptrdiff_t i = 0; i < HASH_TABLE_SIZE (h); ++i)
+ {
+ if (!EQ (HASH_KEY (h, i), Qunbound)
+ && &XMODULE_GLOBAL_REFERENCE (HASH_VALUE (h, i))->value == v)
+ return true;
+ }
+ /* Only used for debugging, so we don't care about overflow, just
+ make sure the operation is defined. */
+ INT_ADD_WRAPV (*n, h->count, n);
+ return false;
+}
+
static emacs_value
module_make_global_ref (emacs_env *env, emacs_value value)
{
@@ -383,21 +431,30 @@ module_make_global_ref (emacs_env *env, emacs_value value)
Lisp_Object new_obj = value_to_lisp (value), hashcode;
ptrdiff_t i = hash_lookup (h, new_obj, &hashcode);
+ /* Note: This approach requires the garbage collector to never move
+ objects. */
+
if (i >= 0)
{
Lisp_Object value = HASH_VALUE (h, i);
- EMACS_INT refcount = XFIXNAT (value) + 1;
- if (MOST_POSITIVE_FIXNUM < refcount)
+ struct module_global_reference *ref = XMODULE_GLOBAL_REFERENCE (value);
+ bool overflow = INT_ADD_WRAPV (ref->refcount, 1, &ref->refcount);
+ if (overflow)
overflow_error ();
- value = make_fixed_natnum (refcount);
- set_hash_value_slot (h, i, value);
+ return &ref->value;
}
else
{
- hash_put (h, new_obj, make_fixed_natnum (1), hashcode);
+ struct module_global_reference *ref
+ = ALLOCATE_PLAIN_PSEUDOVECTOR (struct module_global_reference,
+ PVEC_OTHER);
+ ref->value.v = new_obj;
+ ref->refcount = 1;
+ Lisp_Object value;
+ XSETPSEUDOVECTOR (value, ref, PVEC_OTHER);
+ hash_put (h, new_obj, value, hashcode);
+ return &ref->value;
}
-
- return allocate_emacs_value (env, &global_storage, new_obj);
}
static void
@@ -411,25 +468,21 @@ module_free_global_ref (emacs_env *env, emacs_value global_value)
Lisp_Object obj = value_to_lisp (global_value);
ptrdiff_t i = hash_lookup (h, obj, NULL);
- if (i >= 0)
+ if (module_assertions)
{
- EMACS_INT refcount = XFIXNAT (HASH_VALUE (h, i)) - 1;
- if (refcount > 0)
- set_hash_value_slot (h, i, make_fixed_natnum (refcount));
- else
- {
- eassert (refcount == 0);
- hash_remove_from_table (h, obj);
- }
+ ptrdiff_t n = 0;
+ if (! module_global_reference_p (global_value, &n))
+ module_abort ("Global value was not found in list of %"pD"d globals",
+ n);
}
- if (module_assertions)
+ if (i >= 0)
{
- ptrdiff_t count = 0;
- if (value_storage_contains_p (&global_storage, global_value, &count))
- return;
- module_abort ("Global value was not found in list of %"pD"d globals",
- count);
+ Lisp_Object value = HASH_VALUE (h, i);
+ struct module_global_reference *ref = XMODULE_GLOBAL_REFERENCE (value);
+ eassert (0 < ref->refcount);
+ if (--ref->refcount == 0)
+ hash_remove_from_table (h, obj);
}
}
@@ -1250,7 +1303,7 @@ value_to_lisp (emacs_value v)
++num_environments;
}
/* Also check global values. */
- if (value_storage_contains_p (&global_storage, v, &num_values))
+ if (module_global_reference_p (v, &num_values))
goto ok;
module_abort (("Emacs value not found in %"pD"d values "
"of %"pD"d environments"),
@@ -1467,10 +1520,7 @@ module_handle_nonlocal_exit (emacs_env *env, enum nonlocal_exit type,
void
init_module_assertions (bool enable)
{
- /* If enabling module assertions, use a hidden environment for
- storing the globals. This environment is never freed. */
module_assertions = enable;
- initialize_storage (&global_storage);
}
/* Return whether STORAGE contains VALUE. Used to check module
diff --git a/test/data/emacs-module/mod-test.c b/test/data/emacs-module/mod-test.c
index 1e64bcd65f1..ed289d7a863 100644
--- a/test/data/emacs-module/mod-test.c
+++ b/test/data/emacs-module/mod-test.c
@@ -201,7 +201,19 @@ Fmod_test_globref_free (emacs_env *env, ptrdiff_t nargs, emacs_value args[],
return env->intern (env, "ok");
}
+/* Treat a local reference as global and free it. Module assertions
+ should detect this case even if a global reference representing the
+ same object also exists. */
+static emacs_value
+Fmod_test_globref_invalid_free (emacs_env *env, ptrdiff_t nargs,
+ emacs_value *args, void *data)
+{
+ emacs_value local = env->make_integer (env, 9876);
+ env->make_global_ref (env, local);
+ env->free_global_ref (env, local); /* Not allowed. */
+ return env->intern (env, "nil");
+}
/* Return a copy of the argument string where every 'a' is replaced
with 'b'. */
@@ -306,6 +318,22 @@ Fmod_test_invalid_load (emacs_env *env, ptrdiff_t nargs, emacs_value *args,
return invalid_stored_value;
}
+/* The next function works in conjunction with the two previous ones.
+ It stows away a copy of the object created by
+ `Fmod_test_invalid_store' in a global reference. Module assertions
+ should still detect the invalid load of the local reference. */
+
+static emacs_value global_copy_of_invalid_stored_value;
+
+static emacs_value
+Fmod_test_invalid_store_copy (emacs_env *env, ptrdiff_t nargs,
+ emacs_value *args, void *data)
+{
+ emacs_value local = Fmod_test_invalid_store (env, 0, NULL, NULL);
+ return global_copy_of_invalid_stored_value
+ = env->make_global_ref (env, local);
+}
+
/* An invalid finalizer: Finalizers are run during garbage collection,
where Lisp code can't be executed. -module-assertions tests for
this case. */
@@ -678,12 +706,16 @@ emacs_module_init (struct emacs_runtime *ert)
1, 1, NULL, NULL);
DEFUN ("mod-test-globref-make", Fmod_test_globref_make, 0, 0, NULL, NULL);
DEFUN ("mod-test-globref-free", Fmod_test_globref_free, 4, 4, NULL, NULL);
+ DEFUN ("mod-test-globref-invalid-free", Fmod_test_globref_invalid_free, 0, 0,
+ NULL, NULL);
DEFUN ("mod-test-string-a-to-b", Fmod_test_string_a_to_b, 1, 1, NULL, NULL);
DEFUN ("mod-test-userptr-make", Fmod_test_userptr_make, 1, 1, NULL, NULL);
DEFUN ("mod-test-userptr-get", Fmod_test_userptr_get, 1, 1, NULL, NULL);
DEFUN ("mod-test-vector-fill", Fmod_test_vector_fill, 2, 2, NULL, NULL);
DEFUN ("mod-test-vector-eq", Fmod_test_vector_eq, 2, 2, NULL, NULL);
DEFUN ("mod-test-invalid-store", Fmod_test_invalid_store, 0, 0, NULL, NULL);
+ DEFUN ("mod-test-invalid-store-copy", Fmod_test_invalid_store_copy, 0, 0,
+ NULL, NULL);
DEFUN ("mod-test-invalid-load", Fmod_test_invalid_load, 0, 0, NULL, NULL);
DEFUN ("mod-test-invalid-finalizer", Fmod_test_invalid_finalizer, 0, 0,
NULL, NULL);
diff --git a/test/src/emacs-module-tests.el b/test/src/emacs-module-tests.el
index 411b4505da0..8465fd02e1e 100644
--- a/test/src/emacs-module-tests.el
+++ b/test/src/emacs-module-tests.el
@@ -272,6 +272,24 @@ must evaluate to a regular expression string."
(mod-test-invalid-store)
(mod-test-invalid-load)))
+(ert-deftest module--test-assertions--load-non-live-object-with-global-copy ()
+ "Check that -module-assertions verify that non-live objects aren't accessed.
+This differs from `module--test-assertions-load-non-live-object'
+in that it stows away a global reference. The module assertions
+should nevertheless detect the invalid load."
+ (skip-unless (or (file-executable-p mod-test-emacs)
+ (and (eq system-type 'windows-nt)
+ (file-executable-p (concat mod-test-emacs ".exe")))))
+ ;; This doesn't yet cause undefined behavior.
+ (should (eq (mod-test-invalid-store-copy) 123))
+ (module--test-assertion (rx "Emacs value not found in "
+ (+ digit) " values of "
+ (+ digit) " environments\n")
+ ;; Storing and reloading a local value causes undefined behavior,
+ ;; which should be detected by the module assertions.
+ (mod-test-invalid-store-copy)
+ (mod-test-invalid-load)))
+
(ert-deftest module--test-assertions--call-emacs-from-gc ()
"Check that -module-assertions prevents calling Emacs functions
during garbage collection."
@@ -283,6 +301,17 @@ during garbage collection."
(mod-test-invalid-finalizer)
(garbage-collect)))
+(ert-deftest module--test-assertions--globref-invalid-free ()
+ "Check that -module-assertions detects invalid freeing of a
+local reference."
+ (skip-unless (or (file-executable-p mod-test-emacs)
+ (and (eq system-type 'windows-nt)
+ (file-executable-p (concat mod-test-emacs ".exe")))))
+ (module--test-assertion
+ (rx "Global value was not found in list of " (+ digit) " globals")
+ (mod-test-globref-invalid-free)
+ (garbage-collect)))
+
(ert-deftest module/describe-function-1 ()
"Check that Bug#30163 is fixed."
(with-temp-buffer