;;; dir-locals.el --- Local variables for a directory tree
;; Copyright (C) 2005, 2006 Free Software Foundation, Inc.
;; Author: Dave Love <fx@gnu.org>
;; Keywords: files
;; $Revision: 1.7 $
;; URL: http://www.loveshack.ukfsn.org/emacs
;; This file 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 2, or (at your option)
;; any later version.
;; This file 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
;; along with GNU Emacs; see the file COPYING. If not, write to
;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.
;;; Commentary:
;; It can be useful to specify local variables directory-wide, e.g. to
;; define CC mode styles consistently. This library implements such a
;; scheme, controlled by the global minor mode `dir-locals-mode'.
;; Place a file named `.emacs-locals' (or the value of
;; `dir-locals-file-name') in the directory root. This should specify
;; local variables in the usual way. The values it sets are inherited
;; when a file in the directory tree is found. Local variables
;; specified in the found file override the directory-wide ones.
;; However, `eval' pseudo-variables specified in the file are
;; evaluated (assuming `enable-local-eval' is true) _before_ any
;; directory-wide processing, and they are evaluated in a scratch
;; buffer, so that they are only useful for side effects on local
;; variables. `mode' pseudo-variables which specify minor modes
;; toggle those modes for files within the directory. If
;; .emacs-locals specifies a major mode, it doesn't propagate, but any
;; local variables and minor modes its hook sets will; thus it should
;; normally not specify a major mode. The `coding' pseudo-variable
;; will not propagate from .emacs-locals.
;; For example, with dir-locals mode on, placing this in .emacs-locals
;; at the top-level of the Linux source tree would set the C
;; indentation style appropriately for files within the tree:
;;
;; Local variables:
;; c-file-style: "linux"
;; End:
;;
;; (and ignore the stupid remarks in Documentation/CodingStyle).
;; Another possible use is, say, setting change-log parameters in
;; different trees for which the Emacs 22 development source broke use
;; of change-log-mode-hook.
;; NB: This doesn't work with some versions of the Emacs 22 codebase
;; which changed the way hack-local-variables-hook is run, but the
;; change has been reverted.
;; Another, less clean, implementation of this sort of thing was
;; posted to gnu-emacs-sources as dirvals.el by Benjamin Rutt
;; <rutt.4@osu.edu>, June 2006, based on work by Matt Armstrong
;; <matt@lickey.com>. It uses a different format for the equivalent
;; of .emacs-locals.
;;; Code:
(defgroup dir-locals ()
"Directory-wide file-local variables"
:link '(emacs-commentary-link "dir-locals")
:group 'files)
(defcustom dir-locals-file-name ".emacs-locals"
"File name used by Dir-Locals mode to specify local variables.
This should specify local variables in the normal way. When Dir-Locals
minor mode is active, these will be inherited by files found in a
directory tree containing such a file at its root.
This may also be a function of no arguments which returns the name to
use, allowing arbitrary per-directory customization of the
per-directory customization file on the basis of `default-directory'."
:group 'dir-locals
:type '(choice file function))
;; Adapted from dirvals.el.
(defcustom dir-locals-chase-remote nil
"Non-nil means search upwards for `dir-locals-file-name' in remote filesystem."
:group 'dir-locals
:type 'boolean)
(define-minor-mode dir-locals-mode
"Toggle use of directory-wide file-local variables.
See `dir-locals-file-name'."
:global t
(if dir-locals-mode
(add-hook 'hack-local-variables-hook 'dir-locals-hack-local-variables)
(remove-hook 'hack-local-variables-hook
'dir-locals-hack-local-variables)))
;; Following find-change-log. Fixme: Should be abstracted from there.
(defun dir-locals-tree-find (file)
"Find FILE in the current directory or one of its parents.
If one is found, return its fully-qualified name, otherwise return
nil.
FILE may be a string or a nullary function returning one on the basis
of `default-directory'."
(unless (and (not dir-locals-chase-remote)
(fboundp 'file-remote-p) ; not in Emacs 21
(file-remote-p default-directory))
(let* ((dir-name
;; Chase links in the source file and start searching in
;; the dir where it resides.
(or (if buffer-file-name
(file-name-directory (file-chase-links buffer-file-name)))
default-directory))
(file (if (functionp file)
(funcall file)
file))
(file1 (if (file-directory-p dir-name)
(expand-file-name file dir-name))))
;; Chase links before visiting the file. This makes it easier
;; to use a file for several related directories.
(setq file1 (expand-file-name (file-chase-links file1)))
;; Move up in the dir hierarchy till we find a suitable file.
(while (and (not (file-exists-p file1))
(setq dir-name (file-name-directory
(directory-file-name
(file-name-directory file1))))
;; Give up if we are already at the root dir.
(not (string= (file-name-directory file1) dir-name)))
;; Move up to the parent dir and try again.
(setq file1 (expand-file-name (file-name-nondirectory file) dir-name)))
(if (file-exists-p file1)
file1))))
(defun dir-locals-hack-local-variables ()
"Set local variables from directory-wide values.
Inherit the local variables set in `dir-locals-file-name' if that is
found by `dir-locals-tree-find'. Ignore everything ignored by
`hack-local-variables'."
(let* ((file (dir-locals-tree-find dir-locals-file-name))
(hack-local-variables-hook nil)
(buffer-file
(if buffer-file-name
(expand-file-name (file-chase-links buffer-file-name))))
;; Fixme: Probably condition-case this and ensure any error
;; messages indicate the directory file.
(vars (when (and file
;; Don't do it twice, so as to avoid
;; repeating possible interactive queries.
(not (equal file buffer-file)))
(with-temp-buffer
;; Make queries from `hack-local-variables' clearer.
(rename-buffer (file-name-nondirectory file) t)
(insert-file-contents file)
(let* ((locals (buffer-local-variables))
(_ (hack-local-variables))
(new-locals (buffer-local-variables)))
;; Derive the list of new pairs.
(dolist (l locals)
(setq new-locals (delete l new-locals)))
;; And some internals which get updated.
(dolist (l '(buffer-display-time buffer-display-count))
(setq new-locals (assq-delete-all l new-locals)))
new-locals)))))
(dolist (v vars)
(let ((sym (car v)))
(unless (local-variable-p sym) ; file-locals take precedence
(if (and (string-match "-mode\\'" (symbol-name sym))
(fboundp sym))
(funcall sym)
(set (make-local-variable sym) (cdr v))))))))
(provide 'dir-locals)
;;; dir-locals.el ends here