28

I work on many different C projects with unique indentation styles. How do I get Emacs to do per-project indentation settings without polluting the upstream project trees with .dir-locals.el files? I want to match the projects using their paths on disk.

1
  • 2
    How about using EditorConfig instead of .dir-locals.el. May be coworkers would like this idea?
    – welldan97
    Sep 24, 2014 at 2:17

5 Answers 5

21

.dir-locals.el is my preferred method when it's possible, especially as it applies to all Emacs users working on that project.

For projects that are hostile to Emacs users or otherwise don't want .dir-locals.el files, or if you want to have personal preferences (presumably not indentation) that shouldn't apply to other people, if you use a distributed version control system, an option is to always work on personal branches that have your .dir-locals.el. I don't know of a way to use a DCVS that makes this painless, however.

Another option is to not check in the .dir-locals.el files. For example, with git, add it to .git/info/exclude. That's painful when you have many checkouts of a project, of course.

What I've done where .dir-locals.el wasn't an option is to add a hook that looks up buffer-file-name against known values.

(defvar guessed-project nil)
(make-variable-buffer-local 'guessed-project)
(defun guess-project-from-file-name ()
  (save-match-data
    (setq guessed-project
          (cond
           ((string-match "/linux-kernel/" buffer-file-name)
            'linux-kernel)
           ((string-match "/gnu/" buffer-file-name)
            'gnu)
           ; etc.
          ))))
(defun c-my-project-hook ()
  (guess-project-from-file-name)
  (case guessed-project
    ((gnu) (c-sty-style 'gnu))
    ((linux-kernel) (c-sty-style 'linux))
  ))
(add-hook 'c-mode-hook 'c-my-project-hook)

(Warning: code typed directly into my browser as I don't have access to my real code right now.)

4
  • 2
    Adding .dir-locals.el to .git/info/exclude seems like a good option.
    – shosti
    Sep 23, 2014 at 21:51
  • What's wrong with adding it to .gitignore in the root of a project?
    – Squidly
    Oct 27, 2014 at 12:52
  • @Nothing, but some projects don't want editor-specific files there. Oct 27, 2014 at 14:13
  • For git users, you can add .dir-locals.el to your global gitignore at ~/.config/git/ignore.
    – Jeremy
    Jun 16, 2015 at 17:56
7

You can't convince upstream projects like llvm and linux to check in a .dir-locals.el.

An elaborate solution to the problem:

(defmacro define-new-c-style (name derived-from style-alist match-path)
  `(progn
     (c-add-style ,name
                  '(,derived-from ,@style-alist))
     (add-hook 'c-mode-hook
               (lambda ()
                 (let ((filename (buffer-file-name)))
                   (when (and filename
                              (string-match (expand-file-name ,match-path) filename))
                     (c-set-style ,name)))))))

Used as follows:

(define-new-c-style "llvm" "gnu" ((fill-column . 80)
                                  (c++-indent-level . 2)
                                  (c-offsets-alist . ((innamespace 0))))
  "~/src/llvm")

(define-new-c-style "emacs" "gnu" nil "~/src/emacs")

I have a similar macro for other language modes as well.

6

distributing .dir-locals.el in different projects is hard to maintain.

My way is pretty simple, put all the code in one ~/.custom.el.

Basically, the code will be run in prog-mode-hook (or your-whatever-major-mode-hook) and do the following things:

  • analyze whether the full path of file does contain the specific project name
  • if true then do the setup for that project (including tweaking the indent).

I've been successfully using this method for one year.

Here is the code (call my-setup-develop-environment in prog-mode-hook):

(defun my-project-name-contains-substring (REGEX)
  (let ((dir (if (buffer-file-name)
                 (file-name-directory (buffer-file-name))
               "")))
    (string-match-p REGEX dir)))

(defun my-office-code-style ()
  (interactive)
  (message "Office code style!")
  ;; web development
  (setq coffee-tab-width 4)
  (setq javascript-indent-level 4)
  (setq js-indent-level 4)
  (setq js2-basic-offset 4)
  (setq web-mode-indent-style 4)
  (setq web-mode-markup-indent-offset 4)
  (setq web-mode-css-indent-offset 4)
  (setq web-mode-code-indent-offset 4)
  )

(defun my-personal-code-style ()
  (interactive)
  (message "My personal code style!")
  (setq coffee-tab-width 4)
  (setq javascript-indent-level 2)
  (setq js-indent-level 2)
  (setq js2-basic-offset 2)
  (setq web-mode-indent-style 2)
  (setq web-mode-markup-indent-offset 2)
  (setq web-mode-css-indent-offset 2)
  (setq web-mode-code-indent-offset 2)
  )

(defun my-setup-develop-environment ()
  (interactive)
  (cond
   ;; company's project for ttag
   ((my-project-name-contains-substring "commerical-proj1")
    (my-office-code-style))
   ((my-project-name-contains-substring "hobby-proj1")
    (my-personal-code-style))
  )
)
(add-hook 'prog-mode-hook 'my-setup-develop-environment)
2

The normal way to do it is using .dir-locals. This is why they were introduced in the first place. Why not? To my knowledge, most of project-related tools tend to keep their configs in the root project directory: git, svn, Eclipse and many others…

Just don't forget to add the file to .gitignore or similar.

2

I use guess-style.el.

It does pretty well at guessing a file's preferred indentation style.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.