11

I'm trying to ignore some file patterns while performing helm-projectile-grep and some with helm-projectile-find-file. I've found that it could be achieved using .dir-locals.el here.
Following this chunk of documentation I've made my .dir-locals.el looks like:

((nil
  (grep-find-ignored-files . '("*.log"))
  (projectile-globally-ignored-files . '("*.log"))))

When helm-projectile-grep is called it yields an error in helm-projectile:

Error running timer `helm-projectile-grep-or-ack': (wrong-type-argument stringp quote)

Since both emacs manual and projectile docs are sketchy about .dir-locals.el and it's usage I have few questions?

  • How .dir-locals.el works? Does it merge global and dir-local value of an variable? If I have a variable a which is '("a" "b") and set it in dir-local to '("c"), what is its value that Emacs use when I perform an operation in certain scope of .dir-locals.el?
  • What kind of structure is expected in .dir-locals.el for grep-find-ignored-files and projectile-globally-ignored-files?

On Projectile itself:

  • Why Projectile helm-projectile-grep and helm-projectile-find-file don't ignore file patterns which are already ignored in .gitignore? Is it turned off somewhere in my config?

Solution

According to accepted answer, if you want to ignore e.g all the .md files from Projectile operations in your project using .dir-locals.el you should do something like this:

((nil
  (eval . (set (make-local-variable 'projectile-globally-ignored-files)
               (append projectile-globally-ignored-files
                       (f-entries (projectile-project-root)
                                  (lambda (f) (string-match "\\.md$" f)) t))))))
foki
  • 886
  • 8
  • 22
  • 1. I can't reproduce your error with the same setup. You need to `toggle-debug-on-error` to see where it's coming from. 2. `projectile` does ignore files in `.gitignore`, at least I am certain about `helm-projectile-find-files`. It's impossible to say why you see a different behavior without dissecting your `init.el`. – Yuri Steinschreiber Jul 28 '16 at 20:14
  • @YuriSteinschreiber Sorry, I've forgot to mention that one should call `helm-projectile-grep` after visiting some file in project to see exactly the same error as I've seen. Question is updated now. – foki Jul 28 '16 at 21:47
  • @YuriSteinschreiber You didn't even got `wrong-type-argument stringp quote` on visiting file when you use `.dir-locals.el` from above? Debugger says that projectile try to `expand-file-name(quote "/my/project/root/")`. It would be very strange to me if your Projectile doesn't try to do the same. – foki Jul 28 '16 at 21:57
  • Now after visiting a file I get the same behavior, and it is coming from the fact that the value is interpreted literally, so it's a list with a `quote` symbol as the first element. Which is not a string, and that's the error. Looks weird and doesn't match either `Projectile` docs or my understanding. I have deleted my partial answer while I am trying to understand the reason (and maybe correct myself if I was wrong somewhere.) – Yuri Steinschreiber Jul 28 '16 at 22:19
  • Yes, that's what confusing me - it looks like variable values are interpreted literally. Thank you for your effort anyway. – foki Jul 28 '16 at 22:30
  • Ok, figured it out. Took a bit more effort than I intended to expend, but couldn't stop already :) The values are left unevaluated, the emacs manual is a bit scattered about it, so needed to dig the sources. If you leave the quote out it works, see my answer. – Yuri Steinschreiber Jul 28 '16 at 22:47
  • Re: your problem with `.gitignore`. Funny thing - I've just stumbled over the same problem, and it turned out that `.gitignore` is itself ignored by Projectile unless the project is indeed under `git`. Check if your project has a `git` repository, if it doesn't - init it and get the first commit. Then see if Projectile takes it into account. (I will update my answer soon.) – Yuri Steinschreiber Jul 29 '16 at 19:43

1 Answers1

11
  1. .dir-locals.el does not care about the meaning of the variables that it sets, nor does it assume their values are lists (they do not have to be.) So it does not perform any merge or other processing. The only thing that's been taken into account is precedence which is described in the documentation you have linked to.

  2. The values are interpreted as-is, emacs uses read function to get them, so they are unevaluated. That's what causes the error - do not use a quote, use straight values.

  3. To get the effect of merging, you need to do it yourself, and since the values are unevaluated you need to use a special variable eval, like this:

((nil
   (eval . (setq a-variable-you-need-to-merge (add-to-list whatever-you-need))))

Update: as pointed out in the comments, using setq in eval will change the global value of the variable (unless of course the variable was declared local to begin with.) To make the change local, make-local-variable can be used, so for example changing projectile-globally-ignored-files locally can be done like this:

((nil
   (eval . 
         (set 
          (make-local-variable 'projectile-globally-ignored-files)
          (push "SOME-VALUE" projectile-globally-ignored-files)))))

It is a bit sloppy, and can certainly be done shorter, but I just wanted to make a point of using make-local-variable.

(I would definitely make a helper out of this kind of functions and put it in init.el and just called from .dir-locals.el)

  1. As I said, .dir-locals.el does not assume anything about the variables, so the requirements are the same as if they were set in any other way. And as far as I can tell from looking at projectile source, projectile-globally-ignored-files does not expect blobs (asterisks etc.) but rather single file names, so you will have to either list them all or write an expression that calculates them and use eval with it.

  2. You should have gotten a warning from Emacs about some local variables not being safe (because of *.) Make sure you haven't ignored the variable list, otherwise your .dir-locals.el will have no effect.

Update regarding .gitignore:

Seems that Projectile takes .gitignore file into account only if the project is indeed under git, that is, a git repository exists.

  • Doesn't this `setq` should looks like `(setq a-variable-you-need-to-merge (add-to-list a-variable-you-need-to-merge whatever-you-need))` ? Does this mean that Projectile's docs is wrong, do we need to open an GH issue on this? – foki Jul 29 '16 at 09:32
  • 1. Yes, you can `setq` like this. You can actually put an arbitrary expression there, so you can create a helper function and just call it from there. 2. Yes, I think it's a documentation bug, – Yuri Steinschreiber Jul 29 '16 at 17:25
  • There is serious problem with this approach. When you evaluate `(setq some-var ...)` you change its global scope value, not `dir-local` as it should. In this particular use case value that one append to e.g `grep-find-ignored-files` in one projects `.dir-locals.el` will be used in all projects. – foki Jul 29 '16 at 20:41
  • You're right. I think `make-local-variable` instead of `setq` should do the trick. Will check it too as soon as I have a chance. I was preoccupied with forcing the evaluation, and didn't give much thought to what exactly evaluate. – Yuri Steinschreiber Jul 29 '16 at 21:11
  • 1
    Yes, it works with `make-local-variable`. I've updated the answer. – Yuri Steinschreiber Jul 29 '16 at 21:35
  • How did you proved that it won't change the global value? I think that since `push` is destructive operation i.e modify original list which is in this case global value of `projectile-globally-ignored-files` this updated approach will do the same as old one. Fix would be to use something like: `(append projectile-globally-ignored-files '("SOME-VALUE"))` instead of `push`. – foki Jul 30 '16 at 10:15
  • 1
    Sure, I opened a project file, checked that the `projectile-globally-ignored-files` got the value set in `.dir-local.el` (and emacs correctly said it was buffer local,) and then checked it in another buffer outside of the project - no change. The point is `make-local-variable` is evaluated before `push`, so it's a local variable that's affected. So `set` in this particular case is not necessary, I said it could be done shorter - it's just an example. – Yuri Steinschreiber Jul 30 '16 at 17:16
  • Hm weird ... It it not the case for me. When visit the project file then `C-h v projectile-globally-ignored-files` and it says that value is OK but `Local in buffer global value is the same.` Then in other project buffer again `C-h v ...` and it says that value is the same and nothing about is it buffer local or not. It works well only when I use it with `set` and non-destructive operation to extend list. – foki Jul 30 '16 at 19:43
  • 1
    Weird indeed. I noticed I was using a destructive function myself, but was in a hurry to check it and since `make-local-variable` was first to be evaluated decided to give it a go. Testing was a bit of a headache by itself, I needed to make sure all buffers are killed before testing changes, ended up killing and restarting emacs to make certain. As soon as I saw it worked I went ahead and posted it. (I am not fluent enough in elisp, and working in a few Lisps simultaneously doesn't make it easier. Sorry if it caused a few more attempts on your side.) – Yuri Steinschreiber Jul 30 '16 at 21:13