From: Tom Tromey <tromey@redhat.com>
To: joakim@verona.se
Cc: emacs-devel <emacs-devel@gnu.org>
Subject: Re: status of dir-vars or dir-locals inclusion in emacs?
Date: Tue, 03 Jul 2007 18:25:11 -0600 [thread overview]
Message-ID: <m3ir912exk.fsf@fleche.redhat.com> (raw)
In-Reply-To: <m3odit4myo.fsf@kurono.home> (joakim@verona.se's message of "Tue\, 03 Jul 2007 15\:48\:47 +0200")
>>>>> "Joakim" == Joakim Verona <joakim@verona.se> writes:
Joakim> There was discussion some time ago about including either
Joakim> dir-locals.el or dir-vars.el.
Joakim> - What is the status of this? Which one will be included?
One thing I don't like about dirvars is that, AFAICS, it only allows a
single setting of a variable for a project -- there's no way to have
different settings depending on the major mode.
Here's some code I've been playing with that does the same kind of
thing, in a slightly different way. You can define a project class
and have it define settings and then define different directories as
instances of a defined class, or you can have a file in the top-level
directory of a project.
I do like how dirvars by default doesn't search upward on remote
directories. I wish I'd thought of that :-).
Tom
;;; project.el --- per-project settings
;; Copyright (C) 2007 Tom Tromey <tromey@redhat.com>
;; Author: Tom Tromey <tromey@redhat.com>
;; Created: 27 Apr 2007
;; Version: 0.1
;; Keywords: tools
;; This file is not (yet) part of GNU Emacs.
;; However, it is distributed under the same license.
;; 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 2, 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. Settings can be
;; checked in to the project's version control and they will
;; automatically be found and used by Emacs.
;; FIXME: elpa instructions
;; To set up:
;; (add-hook 'find-file-hooks #'project-find-file)
;; A project class can be defined manually using
;; `project-define-class'. Then a directory can be associated with a
;; given class using `project-define-instance'. 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 `project-define-class' is an alist with a
;; special structure, defined below.
;; When a file is loaded, project.el will search up the directory
;; hierarchy. If it finds a directory that was registered with
;; `project-define-instance', it will use the corresponding project
;; class.
;; Otherwise, if a directory contains a file `settings.el' or
;; `.settings.el', then the contents of that file are used as an alist
;; (of the type `project-define-class' takes). In this case, the
;; alist is first scrubbed of risky variable settings.
;; The alist referred to above is used 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:
;; (project-define-class 'gcc
;; '((nil . ((indent-tabs-mode . t)
;; (tab-width . 8)
;; (fill-column . 80)))
;; (c-mode . ((style . "GNU")
;; (new-file-skeleton . (blah))))
;; (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:
;; - need a way to add to auto-mode-alist per-project
;; e.g., semantic wants GCC's .def files to be c-mode
;; - 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
;;; 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-set-variables-from-alist (mode-alist)
"Apply local variable settings from MODE-ALIST."
(mapc (lambda (pair)
(let ((variable (car pair))
(value (cdr pair)))
(make-local-variable variable)
(set variable value)))
mode-alist))
(defun project-set-variables (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-set-variables-from-alist (cdr (assq nil alist)))
;; Now apply the mode-specific variables.
(project-set-variables-from-alist (cdr (assq major-mode alist)))
;; Look for subdirectory matches in the class alist and apply
;; based on those.
(mapc (lambda (elt)
(and (stringp (car elt))
(string= (car elt) (substring (buffer-file-name) 0
(length (car elt))))
(progn
;; Again both generic and mode-specific.
(project-set-variables-from-alist
(cdr (assq nil alist)))
(project-set-variables-from-alist
(cdr (assq major-mode alist))))))
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 project-define-instance (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 project-define-class (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 "settings.el"))
(setq result (concat dir "settings.el")))
((file-exists-p (concat dir ".settings.el"))
(setq result (concat dir ".settings.el")))
(t
(setq dir (file-name-directory (directory-file-name dir))))))
result))
;; Taken from Emacs 22.
(defun project-safe-local-variable-p (sym val)
"Non-nil if SYM is safe as a file-local variable with value VAL.
It is safe if any of these conditions are met:
* There is a matching entry (SYM . VAL) in the
`safe-local-variable-values' user option.
* The `safe-local-variable' property of SYM is a function that
evaluates to a non-nil value with VAL as an argument."
(or (member (cons sym val) safe-local-variable-values)
(let ((safep (get sym 'safe-local-variable)))
(and (functionp safep) (funcall safep val)))))
(unless (fboundp 'safe-local-variable-p)
(fset 'safe-local-variable-p 'project-safe-local-variable-p))
(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."
(mapc (lambda (elt)
(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?
(mapc (lambda (sub-elt)
(unless (safe-local-variable-p (car sub-elt)
(cdr sub-elt))
(setcar sub-elt nil)))
sub-alist)
;; Now remove all the deleted risky variables.
(setcdr elt (assq-delete-all nil sub-alist)))))
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)))))
(project-define-class class-name alist)
(project-define-instance dir-name class-name)
class-name)))
;; Put this on find-file-hooks.
;;;###autoload
(defun project-find-file ()
"Set local variables in a buffer based on project settings."
(when (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-set-variables class root-dir)))))
;;; project.el ends here
next prev parent reply other threads:[~2007-07-04 0:25 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 [this message]
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
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=m3ir912exk.fsf@fleche.redhat.com \
--to=tromey@redhat.com \
--cc=emacs-devel@gnu.org \
--cc=joakim@verona.se \
/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.