* 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).