[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
project variable patch #2
From: |
Tom Tromey |
Subject: |
project variable patch #2 |
Date: |
Sun, 09 Sep 2007 17:16:58 -0600 |
User-agent: |
Gnus/5.11 (Gnus v5.11) Emacs/22.0.990 (gnu/linux) |
Here's the next revision of patch that used to be "project.el". I
addressed (or tried to) RMS' comments about the documentation. It now
looks for .dir-settings.el, per his suggestion.
I didn't implement the C-h v thing. I will try to do that in a
followup patch.
Let me know if I missed anything else. I went back through the
various threads but they were lengthy and I may have missed something.
Tom
doc/emacs/ChangeLog:
2007-09-09 Tom Tromey <address@hidden>
* custom.texi (Project Variables): New node.
(Variables): Update.
etc/ChangeLog:
2007-09-09 Tom Tromey <address@hidden>
* NEWS: Mention project settings code.
lisp/ChangeLog:
2007-09-09 Tom Tromey <address@hidden>
* files.el (normal-mode): Call project-find-file.
(project-class-alist, project-directory-alist): New variables.
(project-get-alist): New subst.
(project-install-bindings-from-alist, project-install-bindings):
New functions.
(set-directory-project, define-project-bindings): Likewise.
(project-find-settings-file, project-filter-risky-variables):
Likewise.
(project-define-from-project-file, project-find-file): Likewise.
Index: doc/emacs/custom.texi
===================================================================
RCS file: /sources/emacs/emacs/doc/emacs/custom.texi,v
retrieving revision 1.1
diff -u -r1.1 custom.texi
--- doc/emacs/custom.texi 6 Sep 2007 04:44:54 -0000 1.1
+++ doc/emacs/custom.texi 9 Sep 2007 23:35:04 -0000
@@ -795,6 +795,7 @@
* Hooks:: Hook variables let you specify programs for parts
of Emacs to run on particular occasions.
* Locals:: Per-buffer values of variables.
+* Project Variables:: Per-project values of variables.
* File Variables:: How files can specify variable values.
@end menu
@@ -1051,6 +1052,55 @@
(default-value 'fill-column)
@end example
address@hidden Project Variables
address@hidden Per-Project Local Variables
address@hidden local variables in projects
address@hidden project local variables
+
+ Emacs provides a way to specify local variable values per-project.
+This can be done one of two ways.
+
+ The first approach is to put a special file, named
address@hidden, in the root directory of a project. When
+opening a file, Emacs searches for this file starting in the file's
+directory and then moving up the directory hierarchy. If the file is
+found, Emacs applies variable settings from the file to the new
+buffer. If the file is remote, Emacs skips this search, because it
+would be too slow.
+
+ When reading settings from a file like this, Emacs automatically
+filters out risky local variables. This makes it safe for projects to
+check in appropriate Emacs settings to their version control.
+
+ The file should hold a specially-constructed alist. This alist maps
+Emacs mode names (symbols) to sub-alists; each sub-alist maps variable
+names to values. The special mode name @samp{nil} means that the
+sub-alist should be applied to all buffers. Finally, a string key can
+be used to specify an alist which applies to a relative subdirectory
+in the project.
+
address@hidden
+((nil . ((indent-tabs-mode . t)
+ (tab-width . 8)
+ (fill-column . 80)))
+ (c-mode . ((c-file-style . "GNU")))
+ (java-mode . ((c-file-style . "GNU")))
+ ("libjava/classpath"
+ . ((nil . ((change-log-default-name . "ChangeLog.gcj")))))))
address@hidden example
+
+ This example shows some settings that would be appropriate for GCC.
+This sets @samp{indent-tabs-mode} to @samp{t} for any file in the
+source tree, and it sets the indentation style for any C or Java
+source file to @samp{GNU}. Finally, it specifies a different
address@hidden file name for any file in the project that appears
+beneath the directory @file{libjava/classpath}.
+
+ The second approach to project-local settings is to explicitly
+define a project class using @code{define-project-bindings}, and then
+to tell Emacs which directory roots correspond to that class, using
address@hidden
+
@node File Variables
@subsection Local Variables in Files
@cindex local variables in files
Index: etc/NEWS
===================================================================
RCS file: /sources/emacs/emacs/etc/NEWS,v
retrieving revision 1.1556
diff -u -r1.1556 NEWS
--- etc/NEWS 9 Sep 2007 22:31:45 -0000 1.1556
+++ etc/NEWS 9 Sep 2007 23:35:05 -0000
@@ -34,6 +34,11 @@
* Changes in Emacs 23.1
+** When opening a file, Emacs will search up the directory hierarchy
+for a file named `.dir-settings.el'. If this exists, it contains
+local variable settings that Emacs automatically applies to files in
+that directory hierarchy.
+
** split-window-preferred-function specifies whether display-buffer should
split windows vertically or horizontally.
Index: lisp/files.el
===================================================================
RCS file: /sources/emacs/emacs/lisp/files.el,v
retrieving revision 1.927
diff -u -r1.927 files.el
--- lisp/files.el 31 Aug 2007 13:29:34 -0000 1.927
+++ lisp/files.el 9 Sep 2007 23:35:13 -0000
@@ -1891,6 +1891,157 @@
(progn . ,body)
(error (message ,format (prin1-to-string err))))))
+(defvar project-class-alist '()
+ "Alist mapping project class names (symbols) to project variable alists.")
+
+(defvar project-directory-alist '()
+ "Alist mapping project directory roots to project classes.")
+
+(defsubst project-get-alist (class)
+ "Return the project variable alist for project CLASS."
+ (cdr (assq class project-class-alist)))
+
+(defun project-install-bindings-from-alist (mode-alist)
+ "Apply local variable settings from MODE-ALIST."
+ (dolist (pair mode-alist)
+ (let ((variable (car pair))
+ (value (cdr pair)))
+ (make-local-variable variable)
+ (set variable value))))
+
+(defun project-install-bindings (class root)
+ "Set variables for the current buffer for the given project class.
+CLASS is the project's class, a symbol.
+ROOT is the project's root directory, a string.
+This applies local variable settings in order from most generic
+to most specific."
+ (let* ((alist (project-get-alist class))
+ (subdir (substring (buffer-file-name) 0 (length root))))
+ ;; First use the 'nil' key to get generic variables.
+ (project-install-bindings-from-alist (cdr (assq nil alist)))
+ ;; Now apply the mode-specific variables.
+ (project-install-bindings-from-alist (cdr (assq major-mode alist)))
+ ;; Look for subdirectory matches in the class alist and apply
+ ;; based on those.
+ (dolist (elt alist)
+ (and (stringp (car elt))
+ (string= (car elt) (substring (buffer-file-name) 0
+ (length (car elt))))
+ (progn
+ ;; Again both generic and mode-specific.
+ (project-install-bindings-from-alist
+ (cdr (assq nil alist)))
+ (project-install-bindings-from-alist
+ (cdr (assq major-mode alist))))))
+ ;; Special case C and derived modes. Note that CC-based modes
+ ;; don't work with derived-mode-p. FIXME: this is arguably an
+ ;; Emacs bug. Perhaps we should be running
+ ;; hack-local-variables-hook here instead?
+ (and (boundp 'c-buffer-is-cc-mode)
+ c-buffer-is-cc-mode
+ (c-postprocess-file-styles))))
+
+(defun set-directory-project (directory class)
+ "Declare that the project rooted at DIRECTORY is an instance of CLASS.
+DIRECTORY is the name of a directory, a string.
+CLASS is the name of a project class, a symbol."
+ (setq directory (file-name-as-directory (expand-file-name directory)))
+ (unless (assq class project-class-alist)
+ (error "No such project class `%s'" (symbol-name class)))
+ (setq project-directory-alist
+ (cons (cons directory class)
+ project-directory-alist)))
+
+(defun define-project-bindings (class alist)
+ "Map the project type CLASS to an alist of variable settings.
+CLASS is the project class, a symbol.
+ALIST is an alist that maps major modes to sub-alists.
+Each sub-alist maps variable names to values.
+
+Note that this does not filter risky variables. This function
+is intended for use by trusted code only."
+ (let ((elt (assq class project-class-alist)))
+ (if elt
+ (setcdr elt alist)
+ (setq project-class-alist
+ (cons (cons class alist)
+ project-class-alist)))))
+
+;; There's a few ways we could do this. We could use VC (with a VC
+;; extension) and look for the root directory. Or we could chain
+;; settings files. For now we choose a simple approach and let the
+;; project maintainers be smart.
+(defun project-find-settings-file (file)
+ "Find the settings file for FILE.
+This searches upward in the directory tree.
+If a settings file is found, the file name is returned.
+If the file is in a registered project, a cons from
+`project-directory-alist' is returned.
+Otherwise this returns nil."
+ (let ((dir (file-name-directory file))
+ (result nil))
+ (while (and (not (string= dir "/"))
+ (not result))
+ (cond
+ ((setq result (assoc dir project-directory-alist))
+ ;; Nothing else.
+ nil)
+ ((file-exists-p (concat dir ".dir-settings.el"))
+ (setq result (concat dir ".dir-settings.el")))
+ (t
+ (setq dir (file-name-directory (directory-file-name dir))))))
+ result))
+
+(defun project-filter-risky-variables (alist)
+ "Filter risky variables from the project settings ALIST.
+This knows the expected structure of a project settings alist.
+Actually this filters unsafe variables."
+ (dolist (elt alist)
+ (let ((sub-alist (cdr elt)))
+ (if (stringp (car sub-alist))
+ ;; A string element maps to a secondary alist.
+ (setcdr sub-alist
+ (project-filter-risky-variables (cdr sub-alist)))
+ ;; Remove unsafe variables by setting their cars to nil.
+ (dolist (sub-elt sub-alist)
+ (unless (safe-local-variable-p (car sub-elt) (cdr sub-elt))
+ (setcar sub-elt nil)))
+ ;; Now remove all the deleted risky variables.
+ (setcdr elt (assq-delete-all nil sub-alist)))))
+ alist)
+
+(defun project-define-from-project-file (settings-file)
+ "Load a settings file and register a new project class and instance.
+The class name is the same as the directory in which the settings file
+was found. The settings have risky local variables filtered out."
+ (with-temp-buffer
+ (insert-file-contents settings-file)
+ (let* ((dir-name (file-name-directory settings-file))
+ (class-name (intern dir-name))
+ (alist (project-filter-risky-variables (read (current-buffer)))))
+ (define-project-bindings class-name alist)
+ (set-directory-project dir-name class-name)
+ class-name)))
+
+(defun project-find-file ()
+ "Set local variables in a buffer based on project settings."
+ (when (and (buffer-file-name) (not (file-remote-p (buffer-file-name))))
+ ;; Find the settings file.
+ (let ((settings (project-find-settings-file (buffer-file-name)))
+ (class nil)
+ (root-dir nil))
+ (cond
+ ((stringp settings)
+ (setq class (project-define-from-project-file settings))
+ (setq root-dir (file-name-directory (buffer-file-name))))
+ ((consp settings)
+ (setq root-dir (car settings))
+ (setq class (cdr settings))))
+ (when class
+ (make-local-variable 'project-class)
+ (setq project-class class)
+ (project-install-bindings class root-dir)))))
+
(defun normal-mode (&optional find-file)
"Choose the major mode for this buffer automatically.
Also sets up any specified local variables of the file.
@@ -1910,6 +2061,7 @@
(let ((enable-local-variables (or (not find-file) enable-local-variables)))
(report-errors "File mode specification error: %s"
(set-auto-mode))
+ (project-find-file)
(report-errors "File local-variables error: %s"
(hack-local-variables)))
;; Turn font lock off and on, to make sure it takes account of
- project variable patch #2,
Tom Tromey <=