The command \\[normal-mode], when used interactively,
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
:type '(choice (const :tag "Query Unsafe" t)
(const :tag "Safe Only" :safe)
(or (set-auto-mode-0 mode keep-mode-if-same)
;; continuing would call minor modes again, toggling them off
(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)
- enable-local-variables
- local-enable-local-variables
- try-locals
- (setq mode (hack-local-variables t))
+ (setq mode (hack-local-variables t (not try-locals)))
(not (memq mode modes)) ; already tried and failed
(if (not (functionp mode))
(message "Ignoring unknown mode `%s'" mode)
This hook is called only if there is at least one file-local
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)
"Get confirmation before setting up local variable values.
ALL-VARS is the list of all variables to be set up.
;; TODO? Warn once per file rather than once per session?
(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.
For buffers visiting files, also puts into effect directory-local
variables.
-Uses `hack-local-variables-apply' to apply the variables.
-If HANDLE-MODE is nil, we apply all the specified local
-variables. If HANDLE-MODE is neither nil nor t, we do the same,
-except that any settings of `mode' are ignored.
+Uses `hack-local-variables-apply' to apply the variables.
-If HANDLE-MODE is t, all we do is check whether a \"mode:\"
-is specified, and return the corresponding mode symbol, or nil.
-In this case, we try to ignore minor-modes, and return only a
-major-mode.
+See `hack-local-variables--find-variables' for the meaning of
+HANDLE-MODE.
-If `enable-local-variables' or `local-enable-local-variables' is nil,
-this function does nothing. If `inhibit-local-variables-regexps'
+If `enable-local-variables' or `local-enable-local-variables' is
+nil, or INHIBIT-LOCALS is non-nil, this function disregards all
+normal local variables. If `inhibit-local-variables-regexps'
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
;; enable-local-variables, because then it would affect dir-local
;; variables. We don't want to search eg tar files for file local
;; to them. The real meaning of inhibit-local-variables-p is "do
;; not scan this file for local variables".
(let ((enable-local-variables
- (and local-enable-local-variables enable-local-variables))
- result)
- (unless (eq handle-mode t)
+ (and (not inhibit-locals)
+ local-enable-local-variables enable-local-variables)))
+ (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)
(when (and (file-remote-p default-directory)
(fboundp 'hack-connection-local-variables)
(connection-local-criteria-for-default-directory))))
(with-demoted-errors "Directory-local variables error: %s"
;; Note this is a no-op if enable-local-variables is nil.
- (hack-dir-local-variables)))
- ;; This entire function is basically a no-op if enable-local-variables
- ;; is nil. All it does is set file-local-variables-alist to nil.
- (when enable-local-variables
- ;; This part used to ignore enable-local-variables when handle-mode
- ;; was t. That was inappropriate, eg consider the
- ;; (artificial) example of:
- ;; (setq local-enable-local-variables nil)
- ;; Open a file foo.txt that contains "mode: sh".
- ;; It correctly opens in text-mode.
- ;; M-x set-visited-file name foo.c, and it incorrectly stays in text-mode.
- (unless (or (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.
- (and (setq result (hack-local-variables-prop-line
- 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)
- (let ((startpos (point))
- endpos
- (thisbuf (current-buffer)))
- (save-excursion
- (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
- (insert-buffer-substring thisbuf startpos endpos)
- (goto-char (point-min))
- (subst-char-in-region (point) (point-max) ?\^m ?\n)
- (while (not (eobp))
- ;; Discard the prefix.
- (if (looking-at prefix)
- (delete-region (point) (match-end 0))
- (error "Local variables entry is missing the prefix"))
- (end-of-line)
- ;; Discard the suffix.
- (if (looking-back suffix (line-beginning-position))
- (delete-region (match-beginning 0) (point))
- (error "Local variables entry is missing the suffix"))
- (forward-line 1))
- (goto-char (point-min))
-
- (while (not (or (eobp)
- (and (eq handle-mode t) result)))
- ;; Find the variable name;
- (unless (looking-at hack-local-variable-regexp)
- (error "Malformed local variable line: %S"
- (buffer-substring-no-properties
- (point) (line-end-position))))
- (goto-char (match-end 1))
- (let* ((str (match-string 1))
- (var (intern str))
- val val2)
- (and (equal (downcase (symbol-name var)) "mode")
- (setq var 'mode))
- ;; Read the variable value.
- (skip-chars-forward "^:")
- (forward-char 1)
- ;; As a defensive measure, we do not allow
- ;; circular data in the file-local data.
- (let ((read-circle nil))
- (setq val (read (current-buffer))))
- (if (eq handle-mode t)
- (and (eq var 'mode)
- ;; Specifying minor-modes via mode: is
- ;; 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))))))))
- ;; Now we've read all the local variables.
- ;; If HANDLE-MODE is t, return whether the mode was specified.
- (if (eq handle-mode t) result
- ;; Otherwise, set the variables.
- (hack-local-variables-filter result nil)
- (hack-local-variables-apply)))))
+ (hack-dir-local-variables))
+ (let ((result (append (hack-local-variables-prop-line)
+ (hack-local-variables--find-variables))))
+ (if (and enable-local-variables
+ (not (inhibit-local-variables-p)))
+ (progn
+ ;; Set the variables.
+ (hack-local-variables-filter result nil)
+ (hack-local-variables-apply))
+ ;; Handle `lexical-binding' and other special local
+ ;; variables.
+ (dolist (variable permanently-enabled-local-variables)
+ (when-let ((elem (assq variable result)))
+ (push elem file-local-variables-alist)))
+ (hack-local-variables-apply))))))
+
+(defun hack-local-variables--find-variables (&optional handle-mode)
+ "Return all local variables in the ucrrent buffer.
+If HANDLE-MODE is nil, we gather all the specified local
+variables. If HANDLE-MODE is neither nil nor t, we do the same,
+except that any settings of `mode' are ignored.
+
+If HANDLE-MODE is t, all we do is check whether a \"mode:\"
+is specified, and return the corresponding mode symbol, or nil.
+In this case, we try to ignore minor-modes, and return only a
+major-mode."
+ (let ((result nil))
+ ;; 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)
+ (let ((startpos (point))
+ endpos
+ (thisbuf (current-buffer)))
+ (save-excursion
+ (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
+ (insert-buffer-substring thisbuf startpos endpos)
+ (goto-char (point-min))
+ (subst-char-in-region (point) (point-max) ?\^m ?\n)
+ (while (not (eobp))
+ ;; Discard the prefix.
+ (if (looking-at prefix)
+ (delete-region (point) (match-end 0))
+ (error "Local variables entry is missing the prefix"))
+ (end-of-line)
+ ;; Discard the suffix.
+ (if (looking-back suffix (line-beginning-position))
+ (delete-region (match-beginning 0) (point))
+ (error "Local variables entry is missing the suffix"))
+ (forward-line 1))
+ (goto-char (point-min))
+
+ (while (not (or (eobp)
+ (and (eq handle-mode t) result)))
+ ;; Find the variable name;
+ (unless (looking-at hack-local-variable-regexp)
+ (error "Malformed local variable line: %S"
+ (buffer-substring-no-properties
+ (point) (line-end-position))))
+ (goto-char (match-end 1))
+ (let* ((str (match-string 1))
+ (var (intern str))
+ val val2)
+ (and (equal (downcase (symbol-name var)) "mode")
+ (setq var 'mode))
+ ;; Read the variable value.
+ (skip-chars-forward "^:")
+ (forward-char 1)
+ ;; As a defensive measure, we do not allow
+ ;; circular data in the file-local data.
+ (let ((read-circle nil))
+ (setq val (read (current-buffer))))
+ (if (eq handle-mode t)
+ (and (eq var 'mode)
+ ;; Specifying minor-modes via mode: is
+ ;; 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 ()
"Apply the elements of `file-local-variables-alist'.