diff options
| author | Michael Olson | 2008-05-19 22:36:09 +0000 |
|---|---|---|
| committer | Michael Olson | 2008-05-19 22:36:09 +0000 |
| commit | 1b21ee065d77baee6da0c46a207bfb44d7cc0a95 (patch) | |
| tree | ac173ee06682f6fe2d09403c2be5d246b9d26558 /lisp | |
| parent | ad97b375e8189e1826d562898ea78e4f3bb94bda (diff) | |
| download | emacs-1b21ee065d77baee6da0c46a207bfb44d7cc0a95.tar.gz emacs-1b21ee065d77baee6da0c46a207bfb44d7cc0a95.zip | |
Implement Project-local variables.
Diffstat (limited to 'lisp')
| -rw-r--r-- | lisp/files.el | 260 |
1 files changed, 216 insertions, 44 deletions
diff --git a/lisp/files.el b/lisp/files.el index b2f3002d3dd..3afbb2a1d18 100644 --- a/lisp/files.el +++ b/lisp/files.el | |||
| @@ -1973,6 +1973,8 @@ in that case, this function acts as if `enable-local-variables' were t." | |||
| 1973 | (let ((enable-local-variables (or (not find-file) enable-local-variables))) | 1973 | (let ((enable-local-variables (or (not find-file) enable-local-variables))) |
| 1974 | (report-errors "File mode specification error: %s" | 1974 | (report-errors "File mode specification error: %s" |
| 1975 | (set-auto-mode)) | 1975 | (set-auto-mode)) |
| 1976 | (report-errors "Project local-variables error: %s" | ||
| 1977 | (hack-project-variables)) | ||
| 1976 | (report-errors "File local-variables error: %s" | 1978 | (report-errors "File local-variables error: %s" |
| 1977 | (hack-local-variables))) | 1979 | (hack-local-variables))) |
| 1978 | ;; Turn font lock off and on, to make sure it takes account of | 1980 | ;; Turn font lock off and on, to make sure it takes account of |
| @@ -2623,11 +2625,13 @@ asking you for confirmation." | |||
| 2623 | 2625 | ||
| 2624 | (put 'c-set-style 'safe-local-eval-function t) | 2626 | (put 'c-set-style 'safe-local-eval-function t) |
| 2625 | 2627 | ||
| 2626 | (defun hack-local-variables-confirm (all-vars unsafe-vars risky-vars) | 2628 | (defun hack-local-variables-confirm (all-vars unsafe-vars risky-vars project) |
| 2627 | "Get confirmation before setting up local variable values. | 2629 | "Get confirmation before setting up local variable values. |
| 2628 | ALL-VARS is the list of all variables to be set up. | 2630 | ALL-VARS is the list of all variables to be set up. |
| 2629 | UNSAFE-VARS is the list of those that aren't marked as safe or risky. | 2631 | UNSAFE-VARS is the list of those that aren't marked as safe or risky. |
| 2630 | RISKY-VARS is the list of those that are marked as risky." | 2632 | RISKY-VARS is the list of those that are marked as risky. |
| 2633 | PROJECT is a directory name if these settings come from directory-local | ||
| 2634 | settings; nil otherwise." | ||
| 2631 | (if noninteractive | 2635 | (if noninteractive |
| 2632 | nil | 2636 | nil |
| 2633 | (let ((name (if buffer-file-name | 2637 | (let ((name (if buffer-file-name |
| @@ -2641,15 +2645,16 @@ RISKY-VARS is the list of those that are marked as risky." | |||
| 2641 | (set (make-local-variable 'cursor-type) nil) | 2645 | (set (make-local-variable 'cursor-type) nil) |
| 2642 | (erase-buffer) | 2646 | (erase-buffer) |
| 2643 | (if unsafe-vars | 2647 | (if unsafe-vars |
| 2644 | (insert "The local variables list in " name | 2648 | (insert "The local variables list in " (or project name) |
| 2645 | "\ncontains values that may not be safe (*)" | 2649 | "\ncontains values that may not be safe (*)" |
| 2646 | (if risky-vars | 2650 | (if risky-vars |
| 2647 | ", and variables that are risky (**)." | 2651 | ", and variables that are risky (**)." |
| 2648 | ".")) | 2652 | ".")) |
| 2649 | (if risky-vars | 2653 | (if risky-vars |
| 2650 | (insert "The local variables list in " name | 2654 | (insert "The local variables list in " (or project name) |
| 2651 | "\ncontains variables that are risky (**).") | 2655 | "\ncontains variables that are risky (**).") |
| 2652 | (insert "A local variables list is specified in " name "."))) | 2656 | (insert "A local variables list is specified in " |
| 2657 | (or project name) "."))) | ||
| 2653 | (insert "\n\nDo you want to apply it? You can type | 2658 | (insert "\n\nDo you want to apply it? You can type |
| 2654 | y -- to apply the local variables list. | 2659 | y -- to apply the local variables list. |
| 2655 | n -- to ignore the local variables list.") | 2660 | n -- to ignore the local variables list.") |
| @@ -2771,6 +2776,50 @@ and VAL is the specified value." | |||
| 2771 | mode-specified | 2776 | mode-specified |
| 2772 | result)))) | 2777 | result)))) |
| 2773 | 2778 | ||
| 2779 | (defun hack-local-variables-apply (result project) | ||
| 2780 | "Apply an alist of local variable settings. | ||
| 2781 | RESULT is the alist. | ||
| 2782 | Will query the user when necessary." | ||
| 2783 | (dolist (ignored ignored-local-variables) | ||
| 2784 | (setq result (assq-delete-all ignored result))) | ||
| 2785 | (if (null enable-local-eval) | ||
| 2786 | (setq result (assq-delete-all 'eval result))) | ||
| 2787 | (when result | ||
| 2788 | (setq result (nreverse result)) | ||
| 2789 | ;; Find those variables that we may want to save to | ||
| 2790 | ;; `safe-local-variable-values'. | ||
| 2791 | (let (risky-vars unsafe-vars) | ||
| 2792 | (dolist (elt result) | ||
| 2793 | (let ((var (car elt)) | ||
| 2794 | (val (cdr elt))) | ||
| 2795 | ;; Don't query about the fake variables. | ||
| 2796 | (or (memq var '(mode unibyte coding)) | ||
| 2797 | (and (eq var 'eval) | ||
| 2798 | (or (eq enable-local-eval t) | ||
| 2799 | (hack-one-local-variable-eval-safep | ||
| 2800 | (eval (quote val))))) | ||
| 2801 | (safe-local-variable-p var val) | ||
| 2802 | (and (risky-local-variable-p var val) | ||
| 2803 | (push elt risky-vars)) | ||
| 2804 | (push elt unsafe-vars)))) | ||
| 2805 | (if (eq enable-local-variables :safe) | ||
| 2806 | ;; If caller wants only the safe variables, | ||
| 2807 | ;; install only them. | ||
| 2808 | (dolist (elt result) | ||
| 2809 | (unless (or (member elt unsafe-vars) | ||
| 2810 | (member elt risky-vars)) | ||
| 2811 | (hack-one-local-variable (car elt) (cdr elt)))) | ||
| 2812 | ;; Query, except in the case where all are known safe | ||
| 2813 | ;; if the user wants no query in that case. | ||
| 2814 | (if (or (and (eq enable-local-variables t) | ||
| 2815 | (null unsafe-vars) | ||
| 2816 | (null risky-vars)) | ||
| 2817 | (eq enable-local-variables :all) | ||
| 2818 | (hack-local-variables-confirm | ||
| 2819 | result unsafe-vars risky-vars project)) | ||
| 2820 | (dolist (elt result) | ||
| 2821 | (hack-one-local-variable (car elt) (cdr elt)))))))) | ||
| 2822 | |||
| 2774 | (defun hack-local-variables (&optional mode-only) | 2823 | (defun hack-local-variables (&optional mode-only) |
| 2775 | "Parse and put into effect this buffer's local variables spec. | 2824 | "Parse and put into effect this buffer's local variables spec. |
| 2776 | If MODE-ONLY is non-nil, all we do is check whether the major mode | 2825 | If MODE-ONLY is non-nil, all we do is check whether the major mode |
| @@ -2862,45 +2911,7 @@ is specified, returning t if it is specified." | |||
| 2862 | ;; variables (if MODE-ONLY is nil.) | 2911 | ;; variables (if MODE-ONLY is nil.) |
| 2863 | (if mode-only | 2912 | (if mode-only |
| 2864 | result | 2913 | result |
| 2865 | (dolist (ignored ignored-local-variables) | 2914 | (hack-local-variables-apply result nil) |
| 2866 | (setq result (assq-delete-all ignored result))) | ||
| 2867 | (if (null enable-local-eval) | ||
| 2868 | (setq result (assq-delete-all 'eval result))) | ||
| 2869 | (when result | ||
| 2870 | (setq result (nreverse result)) | ||
| 2871 | ;; Find those variables that we may want to save to | ||
| 2872 | ;; `safe-local-variable-values'. | ||
| 2873 | (let (risky-vars unsafe-vars) | ||
| 2874 | (dolist (elt result) | ||
| 2875 | (let ((var (car elt)) | ||
| 2876 | (val (cdr elt))) | ||
| 2877 | ;; Don't query about the fake variables. | ||
| 2878 | (or (memq var '(mode unibyte coding)) | ||
| 2879 | (and (eq var 'eval) | ||
| 2880 | (or (eq enable-local-eval t) | ||
| 2881 | (hack-one-local-variable-eval-safep | ||
| 2882 | (eval (quote val))))) | ||
| 2883 | (safe-local-variable-p var val) | ||
| 2884 | (and (risky-local-variable-p var val) | ||
| 2885 | (push elt risky-vars)) | ||
| 2886 | (push elt unsafe-vars)))) | ||
| 2887 | (if (eq enable-local-variables :safe) | ||
| 2888 | ;; If caller wants only the safe variables, | ||
| 2889 | ;; install only them. | ||
| 2890 | (dolist (elt result) | ||
| 2891 | (unless (or (member elt unsafe-vars) | ||
| 2892 | (member elt risky-vars)) | ||
| 2893 | (hack-one-local-variable (car elt) (cdr elt)))) | ||
| 2894 | ;; Query, except in the case where all are known safe | ||
| 2895 | ;; if the user wants no quuery in that case. | ||
| 2896 | (if (or (and (eq enable-local-variables t) | ||
| 2897 | (null unsafe-vars) | ||
| 2898 | (null risky-vars)) | ||
| 2899 | (eq enable-local-variables :all) | ||
| 2900 | (hack-local-variables-confirm | ||
| 2901 | result unsafe-vars risky-vars)) | ||
| 2902 | (dolist (elt result) | ||
| 2903 | (hack-one-local-variable (car elt) (cdr elt))))))) | ||
| 2904 | (run-hooks 'hack-local-variables-hook))))) | 2915 | (run-hooks 'hack-local-variables-hook))))) |
| 2905 | 2916 | ||
| 2906 | (defun safe-local-variable-p (sym val) | 2917 | (defun safe-local-variable-p (sym val) |
| @@ -3004,6 +3015,167 @@ already the major mode." | |||
| 3004 | (if (stringp val) | 3015 | (if (stringp val) |
| 3005 | (set-text-properties 0 (length val) nil val)) | 3016 | (set-text-properties 0 (length val) nil val)) |
| 3006 | (set (make-local-variable var) val)))) | 3017 | (set (make-local-variable var) val)))) |
| 3018 | |||
| 3019 | ;;; Handling directory local variables, aka project settings. | ||
| 3020 | |||
| 3021 | (defvar project-class-alist '() | ||
| 3022 | "Alist mapping project class names (symbols) to project variable lists.") | ||
| 3023 | |||
| 3024 | (defvar project-directory-alist '() | ||
| 3025 | "Alist mapping project directory roots to project classes.") | ||
| 3026 | |||
| 3027 | (defsubst project-get-alist (class) | ||
| 3028 | "Return the project variable list for project CLASS." | ||
| 3029 | (cdr (assq class project-class-alist))) | ||
| 3030 | |||
| 3031 | (defun project-collect-bindings-from-alist (mode-alist settings) | ||
| 3032 | "Collect local variable settings from MODE-ALIST. | ||
| 3033 | SETTINGS is the initial list of bindings. | ||
| 3034 | Returns the new list." | ||
| 3035 | (dolist (pair mode-alist settings) | ||
| 3036 | (let* ((variable (car pair)) | ||
| 3037 | (value (cdr pair)) | ||
| 3038 | (slot (assq variable settings))) | ||
| 3039 | (if slot | ||
| 3040 | (setcdr slot value) | ||
| 3041 | ;; Need a new cons in case we setcdr later. | ||
| 3042 | (push (cons variable value) settings))))) | ||
| 3043 | |||
| 3044 | (defun project-collect-binding-list (binding-list root settings) | ||
| 3045 | "Collect entries from BINDING-LIST into SETTINGS. | ||
| 3046 | ROOT is the root directory of the project. | ||
| 3047 | Return the new settings list." | ||
| 3048 | (let* ((file-name (buffer-file-name)) | ||
| 3049 | (sub-file-name (if file-name | ||
| 3050 | (substring file-name (length root))))) | ||
| 3051 | (dolist (entry binding-list settings) | ||
| 3052 | (let ((key (car entry))) | ||
| 3053 | (cond | ||
| 3054 | ((stringp key) | ||
| 3055 | ;; Don't include this in the previous condition, because we | ||
| 3056 | ;; want to filter all strings before the next condition. | ||
| 3057 | (when (and sub-file-name | ||
| 3058 | (>= (length sub-file-name) (length key)) | ||
| 3059 | (string= key (substring sub-file-name 0 (length key)))) | ||
| 3060 | (setq settings (project-collect-binding-list (cdr entry) | ||
| 3061 | root settings)))) | ||
| 3062 | ((or (not key) | ||
| 3063 | (derived-mode-p key)) | ||
| 3064 | (setq settings (project-collect-bindings-from-alist (cdr entry) | ||
| 3065 | settings)))))))) | ||
| 3066 | |||
| 3067 | (defun set-directory-project (directory class) | ||
| 3068 | "Declare that the project rooted at DIRECTORY is an instance of CLASS. | ||
| 3069 | DIRECTORY is the name of a directory, a string. | ||
| 3070 | CLASS is the name of a project class, a symbol. | ||
| 3071 | |||
| 3072 | When a file beneath DIRECTORY is visited, the mode-specific | ||
| 3073 | settings from CLASS will be applied to the buffer. The settings | ||
| 3074 | for a class are defined using `define-project-bindings'." | ||
| 3075 | (setq directory (file-name-as-directory (expand-file-name directory))) | ||
| 3076 | (unless (assq class project-class-alist) | ||
| 3077 | (error "No such project class `%s'" (symbol-name class))) | ||
| 3078 | (push (cons directory class) project-directory-alist)) | ||
| 3079 | |||
| 3080 | (defun define-project-bindings (class list) | ||
| 3081 | "Map the project type CLASS to a list of variable settings. | ||
| 3082 | CLASS is the project class, a symbol. | ||
| 3083 | LIST is a list that declares variable settings for the class. | ||
| 3084 | An element in LIST is either of the form: | ||
| 3085 | (MAJOR-MODE . ALIST) | ||
| 3086 | or | ||
| 3087 | (DIRECTORY . LIST) | ||
| 3088 | |||
| 3089 | In the first form, MAJOR-MODE is a symbol, and ALIST is an alist | ||
| 3090 | whose elements are of the form (VARIABLE . VALUE). | ||
| 3091 | |||
| 3092 | In the second form, DIRECTORY is a directory name (a string), and | ||
| 3093 | LIST is a list of the form accepted by the function. | ||
| 3094 | |||
| 3095 | When a file is visited, the file's class is found. A directory | ||
| 3096 | may be assigned a class using `set-directory-project'. Then | ||
| 3097 | variables are set in the file's buffer according to the class' | ||
| 3098 | LIST. The list is processed in order. | ||
| 3099 | |||
| 3100 | * If the element is of the form (MAJOR-MODE . ALIST), and the | ||
| 3101 | buffer's major mode is derived from MAJOR-MODE (as determined | ||
| 3102 | by `derived-mode-p'), then all the settings in ALIST are | ||
| 3103 | applied. A MAJOR-MODE of nil may be used to match any buffer. | ||
| 3104 | `make-local-variable' is called for each variable before it is | ||
| 3105 | set. | ||
| 3106 | |||
| 3107 | * If the element is of the form (DIRECTORY . LIST), and DIRECTORY | ||
| 3108 | is an initial substring of the file's directory, then LIST is | ||
| 3109 | applied by recursively following these rules." | ||
| 3110 | (let ((elt (assq class project-class-alist))) | ||
| 3111 | (if elt | ||
| 3112 | (setcdr elt list) | ||
| 3113 | (push (cons class list) project-class-alist)))) | ||
| 3114 | |||
| 3115 | (defun project-find-settings-file (file) | ||
| 3116 | "Find the settings file for FILE. | ||
| 3117 | This searches upward in the directory tree. | ||
| 3118 | If a settings file is found, the file name is returned. | ||
| 3119 | If the file is in a registered project, a cons from | ||
| 3120 | `project-directory-alist' is returned. | ||
| 3121 | Otherwise this returns nil." | ||
| 3122 | (let ((dir (file-name-directory file)) | ||
| 3123 | (result nil)) | ||
| 3124 | (while (and (not (string= dir "/")) | ||
| 3125 | (not result)) | ||
| 3126 | (cond | ||
| 3127 | ((setq result (assoc dir project-directory-alist)) | ||
| 3128 | ;; Nothing else. | ||
| 3129 | nil) | ||
| 3130 | ((file-exists-p (concat dir ".dir-settings.el")) | ||
| 3131 | (setq result (concat dir ".dir-settings.el"))) | ||
| 3132 | (t | ||
| 3133 | (setq dir (file-name-directory (directory-file-name dir)))))) | ||
| 3134 | result)) | ||
| 3135 | |||
| 3136 | (defun project-define-from-project-file (settings-file) | ||
| 3137 | "Load a settings file and register a new project class and instance. | ||
| 3138 | SETTINGS-FILE is the name of the file holding the settings to apply. | ||
| 3139 | The new class name is the same as the directory in which SETTINGS-FILE | ||
| 3140 | is found. Returns the new class name." | ||
| 3141 | (with-temp-buffer | ||
| 3142 | ;; We should probably store the modtime of SETTINGS-FILE and then | ||
| 3143 | ;; reload it whenever it changes. | ||
| 3144 | (insert-file-contents settings-file) | ||
| 3145 | (let* ((dir-name (file-name-directory settings-file)) | ||
| 3146 | (class-name (intern dir-name)) | ||
| 3147 | (list (read (current-buffer)))) | ||
| 3148 | (define-project-bindings class-name list) | ||
| 3149 | (set-directory-project dir-name class-name) | ||
| 3150 | class-name))) | ||
| 3151 | |||
| 3152 | (defun hack-project-variables () | ||
| 3153 | "Set local variables in a buffer based on project settings." | ||
| 3154 | (when (and (buffer-file-name) (not (file-remote-p (buffer-file-name)))) | ||
| 3155 | ;; Find the settings file. | ||
| 3156 | (let ((settings (project-find-settings-file (buffer-file-name))) | ||
| 3157 | (class nil) | ||
| 3158 | (root-dir nil)) | ||
| 3159 | (cond | ||
| 3160 | ((stringp settings) | ||
| 3161 | (setq root-dir (file-name-directory (buffer-file-name))) | ||
| 3162 | (setq class (project-define-from-project-file settings))) | ||
| 3163 | ((consp settings) | ||
| 3164 | (setq root-dir (car settings)) | ||
| 3165 | (setq class (cdr settings)))) | ||
| 3166 | (when class | ||
| 3167 | (let ((bindings | ||
| 3168 | (project-collect-binding-list (project-get-alist class) | ||
| 3169 | root-dir nil))) | ||
| 3170 | (when bindings | ||
| 3171 | (hack-local-variables-apply bindings root-dir) | ||
| 3172 | ;; Special case C and derived modes. Note that CC-based | ||
| 3173 | ;; modes don't work with derived-mode-p. In general I | ||
| 3174 | ;; think modes could use an auxiliary method which is | ||
| 3175 | ;; called after local variables are hacked. | ||
| 3176 | (and (boundp 'c-buffer-is-cc-mode) | ||
| 3177 | c-buffer-is-cc-mode | ||
| 3178 | (c-postprocess-file-styles)))))))) | ||
| 3007 | 3179 | ||
| 3008 | 3180 | ||
| 3009 | (defcustom change-major-mode-with-file-name t | 3181 | (defcustom change-major-mode-with-file-name t |