all messages for Emacs-related lists mirrored at yhetil.org
 help / color / mirror / code / Atom feed
From: Tom Tromey <tromey@redhat.com>
To: rms@gnu.org
Cc: joakim@verona.se, emacs-devel@gnu.org
Subject: Re: status of dir-vars or dir-locals inclusion in emacs?
Date: Sat, 01 Sep 2007 16:40:22 -0600	[thread overview]
Message-ID: <m3fy1yko15.fsf@fleche.redhat.com> (raw)
In-Reply-To: <E1IFvAI-0003B2-OX@fencepost.gnu.org> (Richard Stallman's message of "Tue\, 31 Jul 2007 13\:05\:46 -0400")

>>>>> "rms" == Richard Stallman <rms@gnu.org> writes:

[project.el]
rms> It looks ready to install, as far as I can see, aside from some
rms> minor points.  Do you disagree?

I do agree.  I've made the changes you suggested, and I've finally
updated the documentation and NEWS.  Patch appended.  Let me know what
you think; I can revise (perhaps slowly :-) if needed.

>     ;; - per-project and per-mode new file skeletons
rms> I don't think that is very important.

FWIW I left this comment in.  When we set up Eclipse for use with GNU
Classpath and for Mauve, having new-file skeletons was quite helpful
-- it meant that contributors could make a new file and have the right
thing happen, without them necessarily having to research what that
was.

Tom

man/ChangeLog:
2007-09-01  Tom Tromey  <tromey@redhat.com>

	* custom.texi (Project Variables): New node.
	(Variables): Updated.

lisp/ChangeLog:
2007-09-01  Tom Tromey  <tromey@redhat.com>

	* project.el: New file.
	* files.el (normal-mode): Call project-find-file.
	* loadup.el: Load project.

etc/ChangeLog:
2007-09-01  Tom Tromey  <tromey@redhat.com>

	* NEWS: Mention project.el.

Index: man/custom.texi
===================================================================
RCS file: /sources/emacs/emacs/man/custom.texi,v
retrieving revision 1.129
diff -u -r1.129 custom.texi
--- man/custom.texi	31 Mar 2007 09:06:48 -0000	1.129
+++ man/custom.texi	1 Sep 2007 22:55:22 -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,57 @@
 (default-value 'fill-column)
 @end example
 
+@node Project Variables
+@subsection Per-Project Local Variables
+@cindex local variables in projects
+@cindex 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 in the root directory of
+a project.  When opening a local file, Emacs will search for this file
+and, if the file exists, Emacs will apply local variable settings from
+the file to the new buffer.  (This search is skipped for remote
+files.)
+
+  Emacs looks for files named @file{emacs-settings.el},
+@file{.emacs-settings.el}, or @file{.emacs-settings}, in that order.
+
+  When reading settings from a file like this, Emacs will
+automatically filter 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.
+
+@example
+((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")))))))
+@end example
+
+  This example, although incomplete, defines some settings for GNU
+GCC.  This will set @samp{indent-tabs-mode} to @samp{t} for any file
+in the source tree.  It will set the indentation style for any C or
+Java source file to @samp{GNU}.  Finally, it specifies a different
+@file{ChangeLog} 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
+@code{set-directory-project}.
+
 @node File Variables
 @subsection Local Variables in Files
 @cindex local variables in files
Index: lisp/loadup.el
===================================================================
RCS file: /sources/emacs/emacs/lisp/loadup.el,v
retrieving revision 1.153
diff -u -r1.153 loadup.el
--- lisp/loadup.el	29 Aug 2007 05:28:07 -0000	1.153
+++ lisp/loadup.el	1 Sep 2007 22:55:25 -0000
@@ -68,6 +68,7 @@
 (load "bindings")
 (setq load-source-file-function 'load-with-code-conversion)
 (load "files")
+(load "project")
 
 (load "cus-face")
 (load "faces")  ; after here, `defface' may be used.
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	1 Sep 2007 22:55:26 -0000
@@ -1910,6 +1910,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
Index: lisp/project.el
===================================================================
RCS file: lisp/project.el
diff -N lisp/project.el
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ lisp/project.el 1 Sep 2007 23:00:16 -0000
@@ -0,0 +1,257 @@
+;;; project.el --- per-project settings
+
+;; Copyright (C) 2007 Free Software Foundation, Inc.
+
+;; Author: Tom Tromey <tromey@redhat.com>
+;; Created: 27 Apr 2007
+;; Version: 0.3
+;; Keywords: tools
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs 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 3, or (at your option)
+;; any later version.
+
+;; GNU Emacs 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., 51 Franklin Street, Fifth Floor,
+;; Boston, MA 02110-1301, USA.
+
+;;; Commentary:
+
+;; This package makes it easy to set variables on a per-mode basis for
+;; an entire project.  This is much simpler than editing the local
+;; variables section of every file in the project.  You can check
+;; settings in to the project's version control and Emacs will
+;; automatically find and use them.
+
+;; You can manually define a project class using
+;; `define-project-bindings'.  Then you can associate a directory with
+;; a given class using `set-directory-project'.  This indirection lets
+;; you easily share settings between multiple instances of a project,
+;; for instance if you have multiple branches checked out.
+
+;; The alist argument to `define-project-bindings' is an alist with a
+;; special structure, defined below.
+
+;; When Emacs visits a file, project.el will search up the directory
+;; hierarchy.  If it finds a directory that was registered with
+;; `set-directory-project', it will use the corresponding project
+;; class.
+
+;; Otherwise, if a directory contains a file `emacs-settings.el',
+;; `.emacs-settings.el', or `.emacs-settings', then Emacs uses the
+;; contents of that file as an alist (of the type
+;; `define-project-bindings' takes).  In this case, Emacs first scrubs
+;; the alist of risky variable settings.
+
+;; Emacs uses the alist referred to above to set local variables.  The
+;; car of an entry in the alist is one of three things: the major mode
+;; (a symbol); `nil', which lists variables applied to every file in
+;; the project; or a string, which is a subdirectory of the project.
+
+;; If the car is a mode name or `nil', the cdr is an alist mapping
+;; variable names to variable values.
+
+;; If the car is a string, the cdr is another alist of the form above.
+
+;; Here's an example for GCC:
+
+;; (define-project-bindings 'gcc
+;;   '((nil . ((indent-tabs-mode . t)
+;;             (tab-width . 8)
+;;             (fill-column . 80)))
+;;     (c-mode . ((style . "GNU")))
+;;     (java-mode . ((style . "GNU")))
+;;     ("libjava/classpath"
+;;      . ((nil . ((change-log-default-name . "ChangeLog.gcj")))))))
+
+;; Note that in general you should only set project-related variables
+;; in a settings.el file that is checked in.  Things like key bindings
+;; (or electric keys for C mode) should probably not be set by the
+;; project.
+
+;;; ToDo:
+
+;; - should cache the mod time of settings file and then reload it
+;;   or at least offer user a way to invalidate cache
+;; - should let the user augment the project settings with personal ones
+;; - per-project and per-mode new file skeletons
+;; - maybe an easy way to integrate with customize?
+;; - make it easy to save per-project user settings like build
+;;   commands, gdb commands, etc
+;; - let user close a project and close all associated buffers; then
+;;   reopen the project and have the buffers open?
+;; - any role for bugzilla URLs, patch address and formatting?
+;; - got a request to emulate Eclipse's S-C-r binding; maybe integrate
+;;   with iswitch-fc or filename cache
+
+;;; Code:
+
+(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))))
+
+;;;###autoload
+(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)))
+
+;;;###autoload
+(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 "emacs-settings.el"))
+	(setq result (concat dir "emacs-settings.el")))
+       ((file-exists-p (concat dir ".emacs-settings.el"))
+	(setq result (concat dir ".emacs-settings.el")))
+       ((file-exists-p (concat dir ".emacs-settings"))
+	(setq result (concat dir ".emacs-settings")))
+       (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.
+	;; FIXME: or look only at risky-local-variable-p?
+	(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)))
+
+;;;###autoload
+(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)))))
+
+;;; project.el ends here
Index: etc/NEWS
===================================================================
RCS file: /sources/emacs/emacs/etc/NEWS,v
retrieving revision 1.1550
diff -u -r1.1550 NEWS
--- etc/NEWS	31 Aug 2007 08:11:26 -0000	1.1550
+++ etc/NEWS	1 Sep 2007 22:55:28 -0000
@@ -71,6 +71,9 @@
 ** The new command close-display-connection can be used to close a connection
 to a remote display, e.g. because the display is about to become unreachable.
 
+** project.el is now part of Emacs.  This provides for per-project
+settings of local variables.
+
 ** The command shell prompts for the default directory, when it is
 called with a prefix, and the default directory is a remote file name.
 This is because some file name handler (like ange-ftp) are not able to

  parent reply	other threads:[~2007-09-01 22:40 UTC|newest]

Thread overview: 28+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2007-07-03 13:48 status of dir-vars or dir-locals inclusion in emacs? joakim
2007-07-04  0:25 ` Tom Tromey
2007-07-10 22:01   ` Richard Stallman
2007-07-29 22:59     ` Tom Tromey
2007-07-30 14:45       ` Stefan Monnier
2007-07-30 15:08         ` Tom Tromey
2007-08-03 17:29           ` Vagn Johansen
2007-08-03 22:02             ` Richard Stallman
2007-08-04  7:02               ` David Kastrup
2007-08-05  3:05                 ` Richard Stallman
2007-07-31 17:05       ` Richard Stallman
2007-07-31 18:45         ` Stefan Monnier
2007-09-01 22:40         ` Tom Tromey [this message]
2007-09-02 11:56           ` Vagn Johansen
2007-09-03  3:04             ` Richard Stallman
2007-09-03 18:25           ` Richard Stallman
2007-09-04 21:53             ` Dan Nicolaescu
2007-09-05  3:16               ` Stefan Monnier
2007-09-05 17:32                 ` Dan Nicolaescu
2007-09-05 18:18                   ` Stefan Monnier
2007-09-05 18:04                     ` Tom Tromey
2007-09-05 18:28                   ` David Kastrup
2007-09-05  6:16               ` Richard Stallman
2007-09-05 14:06                 ` Stefan Monnier
2007-09-05 14:36                   ` Davis Herring
2007-09-03 18:26           ` Richard Stallman
2007-07-04 17:04 ` Vagn Johansen
2007-07-05  1:29 ` Richard Stallman

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=m3fy1yko15.fsf@fleche.redhat.com \
    --to=tromey@redhat.com \
    --cc=emacs-devel@gnu.org \
    --cc=joakim@verona.se \
    --cc=rms@gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this external index

	https://git.savannah.gnu.org/cgit/emacs.git
	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.