From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!not-for-mail From: Tom Tromey Newsgroups: gmane.emacs.devel Subject: Re: status of dir-vars or dir-locals inclusion in emacs? Date: Tue, 03 Jul 2007 18:25:11 -0600 Message-ID: References: Reply-To: tromey@redhat.com NNTP-Posting-Host: lo.gmane.org Mime-Version: 1.0 Content-Type: text/plain; charset=us-ascii X-Trace: sea.gmane.org 1183509966 19816 80.91.229.12 (4 Jul 2007 00:46:06 GMT) X-Complaints-To: usenet@sea.gmane.org NNTP-Posting-Date: Wed, 4 Jul 2007 00:46:06 +0000 (UTC) Cc: emacs-devel To: joakim@verona.se Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Wed Jul 04 02:46:03 2007 connect(): Connection refused Return-path: Envelope-to: ged-emacs-devel@m.gmane.org Original-Received: from lists.gnu.org ([199.232.76.165]) by lo.gmane.org with esmtp (Exim 4.50) id 1I5t0M-0001r6-JA for ged-emacs-devel@m.gmane.org; Wed, 04 Jul 2007 02:46:03 +0200 Original-Received: from localhost ([127.0.0.1] helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1I5t0M-0005GC-0W for ged-emacs-devel@m.gmane.org; Tue, 03 Jul 2007 20:46:02 -0400 Original-Received: from mailman by lists.gnu.org with tmda-scanned (Exim 4.43) id 1I5szv-0004by-4M for emacs-devel@gnu.org; Tue, 03 Jul 2007 20:45:35 -0400 Original-Received: from exim by lists.gnu.org with spam-scanned (Exim 4.43) id 1I5szu-0004b3-8U for emacs-devel@gnu.org; Tue, 03 Jul 2007 20:45:34 -0400 Original-Received: from [199.232.76.173] (helo=monty-python.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1I5szu-0004at-4T for emacs-devel@gnu.org; Tue, 03 Jul 2007 20:45:34 -0400 Original-Received: from mx1.redhat.com ([66.187.233.31]) by monty-python.gnu.org with esmtp (Exim 4.60) (envelope-from ) id 1I5szt-0004Rv-J2 for emacs-devel@gnu.org; Tue, 03 Jul 2007 20:45:33 -0400 Original-Received: from int-mx1.corp.redhat.com (int-mx1.corp.redhat.com [172.16.52.254]) by mx1.redhat.com (8.13.1/8.13.1) with ESMTP id l640jREk013825; Tue, 3 Jul 2007 20:45:27 -0400 Original-Received: from pobox.corp.redhat.com (pobox.corp.redhat.com [10.11.255.20]) by int-mx1.corp.redhat.com (8.13.1/8.13.1) with ESMTP id l640jQWT024282; Tue, 3 Jul 2007 20:45:27 -0400 Original-Received: from opsy.redhat.com (ton.toronto.redhat.com [172.16.14.15]) by pobox.corp.redhat.com (8.13.1/8.13.1) with ESMTP id l640jM2v023079; Tue, 3 Jul 2007 20:45:23 -0400 Original-Received: by opsy.redhat.com (Postfix, from userid 500) id F18C9C88042; Tue, 3 Jul 2007 18:25:11 -0600 (MDT) X-Attribution: Tom In-Reply-To: (joakim@verona.se's message of "Tue\, 03 Jul 2007 15\:48\:47 +0200") User-Agent: Gnus/5.11 (Gnus v5.11) Emacs/22.0.990 (gnu/linux) X-detected-kernel: Linux 2.6, seldom 2.4 (older, 4) X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Original-Sender: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Xref: news.gmane.org gmane.emacs.devel:74259 Archived-At: >>>>> "Joakim" == Joakim Verona 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 ;; Author: Tom Tromey ;; 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