emacs-devel
[Top][All Lists]
Advanced

[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




reply via email to

[Prev in Thread] Current Thread [Next in Thread]