2

I'm on day 2 of my switch from 15 years of Vim to Evil Emacs -- so I'm just starting learning how to customize emacs and how to read elisp.

Question 1

I'm trying to use helm-projectile-find-file with elscreen-find-file That is, I want search for a file in my project and open up in a new window/tab.

I tried creating my own version of elscreen-find-file like this

(defun elscreen-find-file (filename)
  "Edit file FILENAME.
Switch to a screen visiting file FILENAME,
creating one if none already exists."
  (interactive "FFind file in new screen: ")
  (elscreen-find-and-goto-by-buffer (helm-projectile-find-file filename) 'create))

But it's not activating the helm-projectile-find-file functionality. I'm curious to learn the best way to accomplish this task. Thanks

Question 2:

I think by answering just #2 I can extrapolate a solution for #1. I'd like to helm-projectile-find-file and split the result in a new vertical window

Follow-up

I think it might have been a simpler solution to just open up a blank tab, switch to it, and then run helm-projectile

I did something similar trying to replicate Vim's vsplit:

  (defun vsplit ()
    (interactive)
    (progn
      (split-window-right)
      (other-window 1)
      (helm-projectile)))
1
  • I guess helm-projectile-find-file doesn't return a buffer, not like find-file-noselect in the original elscreen-find-file, while elscreen-find-and-goto-by-buffe needs buffer as its 1st argument, so your version didn't work. About question 2, it seems there is already an action ("Find file other window C-c o") for that, maybe you want give it a try. BTW, I don't use elscreen, so I might misunderstand you.
    – xuchunyang
    Commented Jan 7, 2016 at 13:29

1 Answer 1

8
+50

Okay, let me share a bit of the process of arriving at the solution to Question #1 (which should also resolve #2).

I'm going to assume you know Emacs key notation and at least a little about Elisp.

TL;DR

It's hard. :)

Research

First, let's think a bit. helm-projectile-find-file is definitely calling find-file or some similar function at some point. You want to substitute elscreen-find-file for whatever helm-projectile-find-file is using by default. With this in mind, it's time to determine what helm-projectile-find-file actually does.

C-h f helm-projectile-find-file RET

You should be met with the following:

helm-projectile-find-file is an interactive compiled Lisp function in helm-projectile.el.

(helm-projectile-find-file &optional ARG)

Use projectile with Helm for finding files in project

With a prefix ARG invalidates the cache first.

Navigate over to the helm-projectile.el and press RET

You'll be met with the following message:

Unable to find location in file

This is bad news. This means that the function you're looking for was generated using some form of metaprogramming, and the literal definition of the function is not available.

In this instance, I'd recommend reading the whole file. Yes, I understand you're new to Elisp, and most of it will not make sense (esp. since Helm source is particularly difficult Elisp). But, thankfully, there are provisions to help you understand:

  1. Docstrings
  2. Function and variable names

After plugging through the whole file and reading these, you should have a decent idea of what should be doing what. You might even have determined that the variable helm-projectile-file-actions is what's controlling the actions that helm-projectile-find-file takes after you select a file.

The definition of helm-projectile-file-actions is a bit involved, but you can skip right to the important stuff (i.e. the actual value it holds) by doing C-h v helm-projectile-file-actions RET:

Value:

(("Find File" . helm-find-file-or-marked)
 ("Find file in Dired" . helm-point-file-in-dired)
 ("View file" . view-file)
 ("Checksum File" . helm-ff-checksum)
 ...
 ("Add files to Dired buffer `C-c a'" . helm-projectile-dired-files-add-action))

(Ellipses mine)

This is a list of conses. The first element of each pair is clearly a string. After a few C-h fs you can determine that the second element of each pair is a function. You can probably also guess that the first element of the list is the one that is executed when you press RET during a helm session.

You can check your guess with M-x debug-on-entry RET helm-find-file-or-marked RET. Now attempt to open a file with helm-projectile-find-file, and you'll be greeted by the Emacs debugger (a topic for another time, I'm afraid), signaling that that was indeed the function that was run. q to quit the debugger. To clear the instrumentation, repeat the process with cancel-debug-on-entry instead of debug-on-entry.

Alright. C-h f helm-find-file-or-marked RET and skip straight to the source. You'll be greeted by a long and complicated function. However, you don't necessarily need to read the entire function, you just need to know what you're looking for: something to do with find-file.

32 lines down the function you'll see this:

(mapc 'find-file-noselect (cdr marked))
(find-file (car marked))

This code is run when you select multiple helm candidates at once, using C-SPC.

44 lines down the function you'll also see this:

(find-file-at-point
      <confusing garbage here>)

This code is run when you are acting on only one candidate.

Both find-file and find-file-at-point are essentially synonymous in this usage. They both represent the point where helm hands the file path off to Emacs builtins to actually open the file.

Execution

Copy the entire helm-find-file-or-marked definition into your init file, rename it my/helm-elscreen-find-file-or-marked and replace both find-file and file-file-at-point with elscreen-find-file. (find-file-noselect as well, depending on what you're trying to achieve)

Now, you've produced a helm action function that opens the given candidate with elscreen-find-file. The next step is to climb back up the levels of abstraction, and make a helm command that uses this function.

You're going to need to go back to helm-projectile.el. Let me just extract the relevant bits here:

(defvar helm-source-projectile-files-list
  (helm-build-in-buffer-source "Projectile files"
    :data (lambda ()
             (condition-case nil
                (projectile-current-project-files)
              (error nil)))
    :fuzzy-match helm-projectile-fuzzy-match
    :coerce 'helm-projectile-coerce-file
    :keymap helm-projectile-find-file-map
    :help-message 'helm-ff-help-message
    :mode-line helm-read-file-name-mode-line-string
    :action helm-projectile-file-actions
    )
  "Helm source definition for Projectile files.")

(defvar helm-source-projectile-files-and-dired-list
  '(helm-source-projectile-dired-files-list
    helm-source-projectile-files-list))

(helm-projectile-command "find-file" helm-source-projectile-files-and-dired-list "Find file: ")

Now what are you going to need to do?

  1. Create a new source my/helm-source-projectile-elscreen-files-list, using our new action
  2. Create a new helm-projectile command with that source

1.

(defvar my/helm-source-projectile-elscreen-files-list
  (helm-build-in-buffer-source "Projectile files"
    :data (lambda ()
             (condition-case nil
                (projectile-current-project-files)
              (error nil)))
    :fuzzy-match helm-projectile-fuzzy-match
    :coerce 'helm-projectile-coerce-file
    :keymap helm-projectile-find-file-map
    :help-message 'helm-ff-help-message
    :mode-line helm-read-file-name-mode-line-string
    :action (cons my/helm-elscreen-find-file-or-marked 
                  helm-projectile-file-actions))
  "Helm source definition for Projectile files.")

2.

(helm-projectile-command "elscreen-find-file" 'my/helm-source-projectile-elscreen-files-list "Find file in projects (elscreen): " t)

I've created a gist will the finished code.

Disclaimer

There are more efficient ways to do this. Namely, instead of copying the definition of helm-find-file-or-marked, you could instead override the functions programmatically using cl-letf, like so:

(defun my/helm-elscreen-find-file-or-marked (candidate)
  (cl-letf (((symbol-function #'find-file) #'elscreen-find-file)
            ((symbol-function #'find-file-noselect) #'elscreen-find-file)
            ((symbol-function #'find-file-at-point) #'elscreen-find-file))
    (helm-find-file-or-marked)))

However, this is reasonablely advanced ELisp.

This thing is long, and probably contains errors. But I'm hoping there will be at least a few useful tidbits that will help you understand how I produced my answer, and how you can go about answering some of your questions in the future.

Keep at it and don't give up! :)

0

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.