Always heed the `lexical-binding' local variable
* doc/lispref/variables.texi (File Local Variables): Document `permanently-enabled-local-variables'. * lisp/files.el (enable-local-variables): Mention the new variable. (set-auto-mode): Always call `hack-local-variables'. (hack-local-variables): Factor out the variable gathering into its own function, and respect the new variable (bug#47843). (hack-local-variables--find-variables): Factored out from `hack-local-variables'. (permanently-enabled-local-variables): New variable.
This commit is contained in:
parent
aa354dd55b
commit
5bedbe6b1d
4 changed files with 190 additions and 146 deletions
|
@ -1885,6 +1885,14 @@ any form of file-local variable. For examples of why you might want
|
||||||
to use this, @pxref{Auto Major Mode}.
|
to use this, @pxref{Auto Major Mode}.
|
||||||
@end defvar
|
@end defvar
|
||||||
|
|
||||||
|
@defvar permanently-enabled-local-variables
|
||||||
|
Some local variable settings will, by default, be heeded even if
|
||||||
|
@code{enable-local-variables} is @code{nil}. By default, this is only
|
||||||
|
the case for the @code{lexical-binding} local variable setting, but
|
||||||
|
this can be controlled by using this variable, which is a list of
|
||||||
|
symbols.
|
||||||
|
@end defvar
|
||||||
|
|
||||||
@defun hack-local-variables &optional handle-mode
|
@defun hack-local-variables &optional handle-mode
|
||||||
This function parses, and binds or evaluates as appropriate, any local
|
This function parses, and binds or evaluates as appropriate, any local
|
||||||
variables specified by the contents of the current buffer. The variable
|
variables specified by the contents of the current buffer. The variable
|
||||||
|
|
7
etc/NEWS
7
etc/NEWS
|
@ -2484,6 +2484,13 @@ This is to keep the same behavior as Eshell.
|
||||||
|
|
||||||
* Incompatible Lisp Changes in Emacs 28.1
|
* Incompatible Lisp Changes in Emacs 28.1
|
||||||
|
|
||||||
|
+++
|
||||||
|
** The 'lexical-binding' local variable is always enabled.
|
||||||
|
Previously, if 'enable-local-variables' was nil, a 'lexical-binding'
|
||||||
|
local variable would not be heeded. This has now changed, and a file
|
||||||
|
with a 'lexical-binding' cookie is always heeded. To revert to the
|
||||||
|
old behavior, set 'permanently-enabled-local-variables' to nil.
|
||||||
|
|
||||||
+++
|
+++
|
||||||
** 'completing-read-default' sets completion variables buffer-locally.
|
** 'completing-read-default' sets completion variables buffer-locally.
|
||||||
'minibuffer-completion-table' and related variables are now set buffer-locally
|
'minibuffer-completion-table' and related variables are now set buffer-locally
|
||||||
|
|
308
lisp/files.el
308
lisp/files.el
|
@ -577,7 +577,9 @@ a -*- line.
|
||||||
|
|
||||||
The command \\[normal-mode], when used interactively,
|
The command \\[normal-mode], when used interactively,
|
||||||
always obeys file local variable specifications and the -*- line,
|
always obeys file local variable specifications and the -*- line,
|
||||||
and ignores this variable."
|
and ignores this variable.
|
||||||
|
|
||||||
|
Also see the `permanently-enabled-local-variables' variable."
|
||||||
:risky t
|
:risky t
|
||||||
:type '(choice (const :tag "Query Unsafe" t)
|
:type '(choice (const :tag "Query Unsafe" t)
|
||||||
(const :tag "Safe Only" :safe)
|
(const :tag "Safe Only" :safe)
|
||||||
|
@ -3198,13 +3200,8 @@ we don't actually set it to the same mode the buffer already has."
|
||||||
(or (set-auto-mode-0 mode keep-mode-if-same)
|
(or (set-auto-mode-0 mode keep-mode-if-same)
|
||||||
;; continuing would call minor modes again, toggling them off
|
;; continuing would call minor modes again, toggling them off
|
||||||
(throw 'nop nil))))))
|
(throw 'nop nil))))))
|
||||||
;; hack-local-variables checks local-enable-local-variables etc, but
|
|
||||||
;; we might as well be explicit here for the sake of clarity.
|
|
||||||
(and (not done)
|
(and (not done)
|
||||||
enable-local-variables
|
(setq mode (hack-local-variables t (not try-locals)))
|
||||||
local-enable-local-variables
|
|
||||||
try-locals
|
|
||||||
(setq mode (hack-local-variables t))
|
|
||||||
(not (memq mode modes)) ; already tried and failed
|
(not (memq mode modes)) ; already tried and failed
|
||||||
(if (not (functionp mode))
|
(if (not (functionp mode))
|
||||||
(message "Ignoring unknown mode `%s'" mode)
|
(message "Ignoring unknown mode `%s'" mode)
|
||||||
|
@ -3503,6 +3500,10 @@ function is allowed to change the contents of this alist.
|
||||||
This hook is called only if there is at least one file-local
|
This hook is called only if there is at least one file-local
|
||||||
variable to set.")
|
variable to set.")
|
||||||
|
|
||||||
|
(defvar permanently-enabled-local-variables '(lexical-binding)
|
||||||
|
"A list of local variables that are always enabled.
|
||||||
|
This overrides any `enable-local-variables' setting.")
|
||||||
|
|
||||||
(defun hack-local-variables-confirm (all-vars unsafe-vars risky-vars dir-name)
|
(defun hack-local-variables-confirm (all-vars unsafe-vars risky-vars dir-name)
|
||||||
"Get confirmation before setting up local variable values.
|
"Get confirmation before setting up local variable values.
|
||||||
ALL-VARS is the list of all variables to be set up.
|
ALL-VARS is the list of all variables to be set up.
|
||||||
|
@ -3716,25 +3717,26 @@ DIR-NAME is the name of the associated directory. Otherwise it is nil."
|
||||||
;; TODO? Warn once per file rather than once per session?
|
;; TODO? Warn once per file rather than once per session?
|
||||||
(defvar hack-local-variables--warned-lexical nil)
|
(defvar hack-local-variables--warned-lexical nil)
|
||||||
|
|
||||||
(defun hack-local-variables (&optional handle-mode)
|
(defun hack-local-variables (&optional handle-mode inhibit-locals)
|
||||||
"Parse and put into effect this buffer's local variables spec.
|
"Parse and put into effect this buffer's local variables spec.
|
||||||
For buffers visiting files, also puts into effect directory-local
|
For buffers visiting files, also puts into effect directory-local
|
||||||
variables.
|
variables.
|
||||||
|
|
||||||
Uses `hack-local-variables-apply' to apply the variables.
|
Uses `hack-local-variables-apply' to apply the variables.
|
||||||
|
|
||||||
If HANDLE-MODE is nil, we apply all the specified local
|
See `hack-local-variables--find-variables' for the meaning of
|
||||||
variables. If HANDLE-MODE is neither nil nor t, we do the same,
|
HANDLE-MODE.
|
||||||
except that any settings of `mode' are ignored.
|
|
||||||
|
|
||||||
If HANDLE-MODE is t, all we do is check whether a \"mode:\"
|
If `enable-local-variables' or `local-enable-local-variables' is
|
||||||
is specified, and return the corresponding mode symbol, or nil.
|
nil, or INHIBIT-LOCALS is non-nil, this function disregards all
|
||||||
In this case, we try to ignore minor-modes, and return only a
|
normal local variables. If `inhibit-local-variables-regexps'
|
||||||
major-mode.
|
|
||||||
|
|
||||||
If `enable-local-variables' or `local-enable-local-variables' is nil,
|
|
||||||
this function does nothing. If `inhibit-local-variables-regexps'
|
|
||||||
applies to the file in question, the file is not scanned for
|
applies to the file in question, the file is not scanned for
|
||||||
local variables, but directory-local variables may still be applied."
|
local variables, but directory-local variables may still be
|
||||||
|
applied.
|
||||||
|
|
||||||
|
Variables present in `permanently-enabled-local-variables' will
|
||||||
|
still be evaluated, even if local variables are otherwise
|
||||||
|
inhibited."
|
||||||
;; We don't let inhibit-local-variables-p influence the value of
|
;; We don't let inhibit-local-variables-p influence the value of
|
||||||
;; enable-local-variables, because then it would affect dir-local
|
;; enable-local-variables, because then it would affect dir-local
|
||||||
;; variables. We don't want to search eg tar files for file local
|
;; variables. We don't want to search eg tar files for file local
|
||||||
|
@ -3742,9 +3744,18 @@ local variables, but directory-local variables may still be applied."
|
||||||
;; to them. The real meaning of inhibit-local-variables-p is "do
|
;; to them. The real meaning of inhibit-local-variables-p is "do
|
||||||
;; not scan this file for local variables".
|
;; not scan this file for local variables".
|
||||||
(let ((enable-local-variables
|
(let ((enable-local-variables
|
||||||
(and local-enable-local-variables enable-local-variables))
|
(and (not inhibit-locals)
|
||||||
result)
|
local-enable-local-variables enable-local-variables)))
|
||||||
(unless (eq handle-mode t)
|
(if (eq handle-mode t)
|
||||||
|
;; We're looking just for the major mode setting.
|
||||||
|
(and enable-local-variables
|
||||||
|
(not (inhibit-local-variables-p))
|
||||||
|
;; If HANDLE-MODE is t, and the prop line specifies a
|
||||||
|
;; mode, then we're done, and have no need to scan further.
|
||||||
|
(or (hack-local-variables-prop-line t)
|
||||||
|
;; Look for the mode elsewhere in the buffer.
|
||||||
|
(hack-local-variables--find-variables t)))
|
||||||
|
;; Normal handling of local variables.
|
||||||
(setq file-local-variables-alist nil)
|
(setq file-local-variables-alist nil)
|
||||||
(when (and (file-remote-p default-directory)
|
(when (and (file-remote-p default-directory)
|
||||||
(fboundp 'hack-connection-local-variables)
|
(fboundp 'hack-connection-local-variables)
|
||||||
|
@ -3755,133 +3766,138 @@ local variables, but directory-local variables may still be applied."
|
||||||
(connection-local-criteria-for-default-directory))))
|
(connection-local-criteria-for-default-directory))))
|
||||||
(with-demoted-errors "Directory-local variables error: %s"
|
(with-demoted-errors "Directory-local variables error: %s"
|
||||||
;; Note this is a no-op if enable-local-variables is nil.
|
;; Note this is a no-op if enable-local-variables is nil.
|
||||||
(hack-dir-local-variables)))
|
(hack-dir-local-variables))
|
||||||
;; This entire function is basically a no-op if enable-local-variables
|
(let ((result (append (hack-local-variables-prop-line)
|
||||||
;; is nil. All it does is set file-local-variables-alist to nil.
|
(hack-local-variables--find-variables))))
|
||||||
(when enable-local-variables
|
(if (and enable-local-variables
|
||||||
;; This part used to ignore enable-local-variables when handle-mode
|
(not (inhibit-local-variables-p)))
|
||||||
;; was t. That was inappropriate, eg consider the
|
(progn
|
||||||
;; (artificial) example of:
|
;; Set the variables.
|
||||||
;; (setq local-enable-local-variables nil)
|
(hack-local-variables-filter result nil)
|
||||||
;; Open a file foo.txt that contains "mode: sh".
|
(hack-local-variables-apply))
|
||||||
;; It correctly opens in text-mode.
|
;; Handle `lexical-binding' and other special local
|
||||||
;; M-x set-visited-file name foo.c, and it incorrectly stays in text-mode.
|
;; variables.
|
||||||
(unless (or (inhibit-local-variables-p)
|
(dolist (variable permanently-enabled-local-variables)
|
||||||
;; If HANDLE-MODE is t, and the prop line specifies a
|
(when-let ((elem (assq variable result)))
|
||||||
;; mode, then we're done, and have no need to scan further.
|
(push elem file-local-variables-alist)))
|
||||||
(and (setq result (hack-local-variables-prop-line
|
(hack-local-variables-apply))))))
|
||||||
handle-mode))
|
|
||||||
(eq handle-mode t)))
|
|
||||||
;; Look for "Local variables:" line in last page.
|
|
||||||
(save-excursion
|
|
||||||
(goto-char (point-max))
|
|
||||||
(search-backward "\n\^L" (max (- (point-max) 3000) (point-min))
|
|
||||||
'move)
|
|
||||||
(when (let ((case-fold-search t))
|
|
||||||
(search-forward "Local Variables:" nil t))
|
|
||||||
(skip-chars-forward " \t")
|
|
||||||
;; suffix is what comes after "local variables:" in its line.
|
|
||||||
;; prefix is what comes before "local variables:" in its line.
|
|
||||||
(let ((suffix
|
|
||||||
(concat
|
|
||||||
(regexp-quote (buffer-substring (point)
|
|
||||||
(line-end-position)))
|
|
||||||
"$"))
|
|
||||||
(prefix
|
|
||||||
(concat "^" (regexp-quote
|
|
||||||
(buffer-substring (line-beginning-position)
|
|
||||||
(match-beginning 0))))))
|
|
||||||
|
|
||||||
(forward-line 1)
|
(defun hack-local-variables--find-variables (&optional handle-mode)
|
||||||
(let ((startpos (point))
|
"Return all local variables in the ucrrent buffer.
|
||||||
endpos
|
If HANDLE-MODE is nil, we gather all the specified local
|
||||||
(thisbuf (current-buffer)))
|
variables. If HANDLE-MODE is neither nil nor t, we do the same,
|
||||||
(save-excursion
|
except that any settings of `mode' are ignored.
|
||||||
(unless (let ((case-fold-search t))
|
|
||||||
(re-search-forward
|
|
||||||
(concat prefix "[ \t]*End:[ \t]*" suffix)
|
|
||||||
nil t))
|
|
||||||
;; This used to be an error, but really all it means is
|
|
||||||
;; that this may simply not be a local-variables section,
|
|
||||||
;; so just ignore it.
|
|
||||||
(message "Local variables list is not properly terminated"))
|
|
||||||
(beginning-of-line)
|
|
||||||
(setq endpos (point)))
|
|
||||||
|
|
||||||
(with-temp-buffer
|
If HANDLE-MODE is t, all we do is check whether a \"mode:\"
|
||||||
(insert-buffer-substring thisbuf startpos endpos)
|
is specified, and return the corresponding mode symbol, or nil.
|
||||||
(goto-char (point-min))
|
In this case, we try to ignore minor-modes, and return only a
|
||||||
(subst-char-in-region (point) (point-max) ?\^m ?\n)
|
major-mode."
|
||||||
(while (not (eobp))
|
(let ((result nil))
|
||||||
;; Discard the prefix.
|
;; Look for "Local variables:" line in last page.
|
||||||
(if (looking-at prefix)
|
(save-excursion
|
||||||
(delete-region (point) (match-end 0))
|
(goto-char (point-max))
|
||||||
(error "Local variables entry is missing the prefix"))
|
(search-backward "\n\^L" (max (- (point-max) 3000) (point-min))
|
||||||
(end-of-line)
|
'move)
|
||||||
;; Discard the suffix.
|
(when (let ((case-fold-search t))
|
||||||
(if (looking-back suffix (line-beginning-position))
|
(search-forward "Local Variables:" nil t))
|
||||||
(delete-region (match-beginning 0) (point))
|
(skip-chars-forward " \t")
|
||||||
(error "Local variables entry is missing the suffix"))
|
;; suffix is what comes after "local variables:" in its line.
|
||||||
(forward-line 1))
|
;; prefix is what comes before "local variables:" in its line.
|
||||||
(goto-char (point-min))
|
(let ((suffix
|
||||||
|
(concat
|
||||||
|
(regexp-quote (buffer-substring (point)
|
||||||
|
(line-end-position)))
|
||||||
|
"$"))
|
||||||
|
(prefix
|
||||||
|
(concat "^" (regexp-quote
|
||||||
|
(buffer-substring (line-beginning-position)
|
||||||
|
(match-beginning 0))))))
|
||||||
|
|
||||||
(while (not (or (eobp)
|
(forward-line 1)
|
||||||
(and (eq handle-mode t) result)))
|
(let ((startpos (point))
|
||||||
;; Find the variable name;
|
endpos
|
||||||
(unless (looking-at hack-local-variable-regexp)
|
(thisbuf (current-buffer)))
|
||||||
(error "Malformed local variable line: %S"
|
(save-excursion
|
||||||
(buffer-substring-no-properties
|
(unless (let ((case-fold-search t))
|
||||||
(point) (line-end-position))))
|
(re-search-forward
|
||||||
(goto-char (match-end 1))
|
(concat prefix "[ \t]*End:[ \t]*" suffix)
|
||||||
(let* ((str (match-string 1))
|
nil t))
|
||||||
(var (intern str))
|
;; This used to be an error, but really all it means is
|
||||||
val val2)
|
;; that this may simply not be a local-variables section,
|
||||||
(and (equal (downcase (symbol-name var)) "mode")
|
;; so just ignore it.
|
||||||
(setq var 'mode))
|
(message "Local variables list is not properly terminated"))
|
||||||
;; Read the variable value.
|
(beginning-of-line)
|
||||||
(skip-chars-forward "^:")
|
(setq endpos (point)))
|
||||||
(forward-char 1)
|
|
||||||
;; As a defensive measure, we do not allow
|
(with-temp-buffer
|
||||||
;; circular data in the file-local data.
|
(insert-buffer-substring thisbuf startpos endpos)
|
||||||
(let ((read-circle nil))
|
(goto-char (point-min))
|
||||||
(setq val (read (current-buffer))))
|
(subst-char-in-region (point) (point-max) ?\^m ?\n)
|
||||||
(if (eq handle-mode t)
|
(while (not (eobp))
|
||||||
(and (eq var 'mode)
|
;; Discard the prefix.
|
||||||
;; Specifying minor-modes via mode: is
|
(if (looking-at prefix)
|
||||||
;; deprecated, but try to reject them anyway.
|
(delete-region (point) (match-end 0))
|
||||||
(not (string-match
|
(error "Local variables entry is missing the prefix"))
|
||||||
"-minor\\'"
|
(end-of-line)
|
||||||
(setq val2 (downcase (symbol-name val)))))
|
;; Discard the suffix.
|
||||||
(setq result (intern (concat val2 "-mode"))))
|
(if (looking-back suffix (line-beginning-position))
|
||||||
(cond ((eq var 'coding))
|
(delete-region (match-beginning 0) (point))
|
||||||
((eq var 'lexical-binding)
|
(error "Local variables entry is missing the suffix"))
|
||||||
(unless hack-local-variables--warned-lexical
|
(forward-line 1))
|
||||||
(setq hack-local-variables--warned-lexical t)
|
(goto-char (point-min))
|
||||||
(display-warning
|
|
||||||
'files
|
(while (not (or (eobp)
|
||||||
(format-message
|
(and (eq handle-mode t) result)))
|
||||||
"%s: `lexical-binding' at end of file unreliable"
|
;; Find the variable name;
|
||||||
(file-name-nondirectory
|
(unless (looking-at hack-local-variable-regexp)
|
||||||
;; We are called from
|
(error "Malformed local variable line: %S"
|
||||||
;; 'with-temp-buffer', so we need
|
(buffer-substring-no-properties
|
||||||
;; to use 'thisbuf's name in the
|
(point) (line-end-position))))
|
||||||
;; warning message.
|
(goto-char (match-end 1))
|
||||||
(or (buffer-file-name thisbuf) ""))))))
|
(let* ((str (match-string 1))
|
||||||
((and (eq var 'mode) handle-mode))
|
(var (intern str))
|
||||||
(t
|
val val2)
|
||||||
(ignore-errors
|
(and (equal (downcase (symbol-name var)) "mode")
|
||||||
(push (cons (if (eq var 'eval)
|
(setq var 'mode))
|
||||||
'eval
|
;; Read the variable value.
|
||||||
(indirect-variable var))
|
(skip-chars-forward "^:")
|
||||||
val)
|
(forward-char 1)
|
||||||
result))))))
|
;; As a defensive measure, we do not allow
|
||||||
(forward-line 1))))))))
|
;; circular data in the file-local data.
|
||||||
;; Now we've read all the local variables.
|
(let ((read-circle nil))
|
||||||
;; If HANDLE-MODE is t, return whether the mode was specified.
|
(setq val (read (current-buffer))))
|
||||||
(if (eq handle-mode t) result
|
(if (eq handle-mode t)
|
||||||
;; Otherwise, set the variables.
|
(and (eq var 'mode)
|
||||||
(hack-local-variables-filter result nil)
|
;; Specifying minor-modes via mode: is
|
||||||
(hack-local-variables-apply)))))
|
;; deprecated, but try to reject them anyway.
|
||||||
|
(not (string-match
|
||||||
|
"-minor\\'"
|
||||||
|
(setq val2 (downcase (symbol-name val)))))
|
||||||
|
(setq result (intern (concat val2 "-mode"))))
|
||||||
|
(cond ((eq var 'coding))
|
||||||
|
((eq var 'lexical-binding)
|
||||||
|
(unless hack-local-variables--warned-lexical
|
||||||
|
(setq hack-local-variables--warned-lexical t)
|
||||||
|
(display-warning
|
||||||
|
'files
|
||||||
|
(format-message
|
||||||
|
"%s: `lexical-binding' at end of file unreliable"
|
||||||
|
(file-name-nondirectory
|
||||||
|
;; We are called from
|
||||||
|
;; 'with-temp-buffer', so we need
|
||||||
|
;; to use 'thisbuf's name in the
|
||||||
|
;; warning message.
|
||||||
|
(or (buffer-file-name thisbuf) ""))))))
|
||||||
|
((and (eq var 'mode) handle-mode))
|
||||||
|
(t
|
||||||
|
(ignore-errors
|
||||||
|
(push (cons (if (eq var 'eval)
|
||||||
|
'eval
|
||||||
|
(indirect-variable var))
|
||||||
|
val)
|
||||||
|
result))))))
|
||||||
|
(forward-line 1)))))))
|
||||||
|
result))
|
||||||
|
|
||||||
(defun hack-local-variables-apply ()
|
(defun hack-local-variables-apply ()
|
||||||
"Apply the elements of `file-local-variables-alist'.
|
"Apply the elements of `file-local-variables-alist'.
|
||||||
|
|
|
@ -151,6 +151,19 @@ form.")
|
||||||
(dolist (subtest (cdr test))
|
(dolist (subtest (cdr test))
|
||||||
(should (file-test--do-local-variables-test str subtest)))))))
|
(should (file-test--do-local-variables-test str subtest)))))))
|
||||||
|
|
||||||
|
(ert-deftest files-tests-permanent-local-variables ()
|
||||||
|
(let ((enable-local-variables nil))
|
||||||
|
(with-temp-buffer
|
||||||
|
(insert ";;; test-test.el --- tests -*- lexical-binding: t; -*-\n\n")
|
||||||
|
(hack-local-variables)
|
||||||
|
(should (eq lexical-binding t))))
|
||||||
|
(let ((enable-local-variables nil)
|
||||||
|
(permanently-enabled-local-variables nil))
|
||||||
|
(with-temp-buffer
|
||||||
|
(insert ";;; test-test.el --- tests -*- lexical-binding: t; -*-\n\n")
|
||||||
|
(hack-local-variables)
|
||||||
|
(should (eq lexical-binding nil)))))
|
||||||
|
|
||||||
(defvar files-test-bug-18141-file
|
(defvar files-test-bug-18141-file
|
||||||
(ert-resource-file "files-bug18141.el.gz")
|
(ert-resource-file "files-bug18141.el.gz")
|
||||||
"Test file for bug#18141.")
|
"Test file for bug#18141.")
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue