unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
* project variable patch #2
@ 2007-09-09 23:16 Tom Tromey
  2007-09-10  1:13 ` Richard Stallman
                   ` (2 more replies)
  0 siblings, 3 replies; 4+ messages in thread
From: Tom Tromey @ 2007-09-09 23:16 UTC (permalink / raw)
  To: Emacs Hackers

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  <tromey@redhat.com>

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

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

	* NEWS: Mention project settings code.

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

	* 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
 
+@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, named
+@file{.dir-settings.el}, 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.
+
+@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 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
+@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: 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 @@
 \f
 * 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

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: project variable patch #2
  2007-09-09 23:16 project variable patch #2 Tom Tromey
@ 2007-09-10  1:13 ` Richard Stallman
  2007-09-10  2:52 ` Stefan Monnier
  2007-09-10 18:07 ` Vagn Johansen
  2 siblings, 0 replies; 4+ messages in thread
From: Richard Stallman @ 2007-09-10  1:13 UTC (permalink / raw)
  To: tromey; +Cc: emacs-devel

    +  Emacs provides a way to specify local variable values per-project.

The term "project" is misleading for this.  A project is an activity,
not a collection of code.  Your project for this week could be to
add a new feature to Emacs, but the directory would not be for
your project, it would be for Emacs.

Let's call them "local variable values for a whole directory".

This means changing the node name, section title, and text.

Changing the function names is also called for.

    +  This example shows some settings that would be appropriate for GCC.

"appropriate for the GCC source code".
("Appropriate for GCC" is ambiguous.)

      Finally, a string key can
    +be used to specify an alist which applies to a relative subdirectory
    +in the project.
    +

Please avoid the passive voice.

    +  When reading settings from a file like this, Emacs automatically

...from this file...

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: project variable patch #2
  2007-09-09 23:16 project variable patch #2 Tom Tromey
  2007-09-10  1:13 ` Richard Stallman
@ 2007-09-10  2:52 ` Stefan Monnier
  2007-09-10 18:07 ` Vagn Johansen
  2 siblings, 0 replies; 4+ messages in thread
From: Stefan Monnier @ 2007-09-10  2:52 UTC (permalink / raw)
  To: tromey; +Cc: Emacs Hackers

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

Since the variables specified inside a file are called "file variables" or
"file-local variables", why not call them "directory variables" or
"directory-local variables"?

This way the name expresses the behavior more directly rather than the
intended/expected use case.

Actually, base on this, we could call the file ".dir-variables.el" (just
like the "File Variables:" section in files).  But I guess "dir-settings.el"
is just as good (after all, it may set more than just variables).

> +      (make-local-variable variable)
> +      (set variable value))))

I recommend (set (make-local-variable variable) value), so as to make the
link between the two statements more immediately obvious.

By the way, how about using major-mode derivation, so that if you add
settings for text-mode, they'll apply to all derivatives of text-mode
(e.g. latex-mode, outline-mode, message-mode, ...).

> +    ;; 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))))

I don't understand what this is referring to.  Could it be fixed by changes
in CC-modes?

> +  (setq project-directory-alist
> +	(cons (cons directory class)
> +	      project-directory-alist)))

Aka (push (cons directory class) project-directory-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))

Why not use the new function locate-dominating-file ?

> +(defun project-find-file ()
> +  "Set local variables in a buffer based on project settings."

The name suggests it's going to do a sort of `find-file', which seems wrong
considering the docstring.  How 'bout hack-directory-local-variables?


        Stefan

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: project variable patch #2
  2007-09-09 23:16 project variable patch #2 Tom Tromey
  2007-09-10  1:13 ` Richard Stallman
  2007-09-10  2:52 ` Stefan Monnier
@ 2007-09-10 18:07 ` Vagn Johansen
  2 siblings, 0 replies; 4+ messages in thread
From: Vagn Johansen @ 2007-09-10 18:07 UTC (permalink / raw)
  To: emacs-devel

Tom Tromey <tromey@redhat.com> writes:

> Here's the next revision of patch that used to be "project.el".  

> +(defun project-find-settings-file (file)

> +    (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))

This can cause infinite loops on windows because the dir variable will
end up as c:/ and the setq dir at the bottom will set it to c:/ again.

> +(defun project-filter-risky-variables (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)
> +

This can silently ignore settings. Take a look at
hack-local-variables-confirm in files.el to see how this is handled
for Local Variables.

-- 
Vagn Johansen

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2007-09-10 18:07 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2007-09-09 23:16 project variable patch #2 Tom Tromey
2007-09-10  1:13 ` Richard Stallman
2007-09-10  2:52 ` Stefan Monnier
2007-09-10 18:07 ` Vagn Johansen

Code repositories for project(s) associated with this public inbox

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

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).