2016-01-26 Emacs lisp, projectile, and eshell

Table of Contents

Elisp

Emacs lisp is the language that the Emacs VM is written in. You can think of Emacs as simply a lisp interpreter that happens to have an editor implemented on top of it. Emacs lisp has great support for dealing with things like strings, buffers, and files (as you would expect from an editor). Emacs lisp is also a LISP-21.

To follow along, open an elisp repl with M-x ielm!

Helpful packages and modes

  • eldoc-mode
  • edebug
  • describe-function (bound to C-h f)
  • describe-variable (bound to C-h v)
  • M-x eval-expression (bound to M-: usually)
  • elisp-slime-nav (enables M-. in elisp buffers)

Basic Introduction to Lisp and Emacs Lisp

If you haven't worked with lisp before, it uses a lot of parentheses (). Lisp is known for a number of traits:

  • Lists
  • S-expressions
  • Lambda / functional underpinnings

When I say "lists", what I mean is that expressions look like:

'(1 2 3)
'("a" (1 2) 'thing)

In the land of lisp, the operator for a method is the first element of the list

(+ 1 2) ;; => 3
;; ' and (list ...) are the same things
'("a" "b" "c")     ;; => ("a" "b" "c")
(list "a" "b" "c") ;; => ("a" "b" "c")
(quote ("a" "b" "c"))
(append '(1 2) '(4 5)) ;; => (1 2 4 5)

Variables can be defined using defvar and set by using setq (set quoted)

(defvar my-fancy-var '(1 2)
  "This is my fancy variable")
(setq my-fancy-var (cons 0 my-fancy-var))
my-fancy-var ;; => (0 1 2)

Functions can be defined using defun

(defun add-one (x)
  "Adds 1 to a number provided as `x'."
  (projectile-find-file )
  (+ 1 x))

(defun jump-to-thingy (x))

(add-one 3) ;; => 4

Or with multiple / optional arguments

(defun print-things (&rest things)
  (message "args: %s" things)
  (c-mode)
  (mapcar (lambda (thing) (message "thing: %s" thing)) things))

(print-things "a" 1 '('b "hi"))

Notice the lambda there, Emacs supports anonymous functions this way.

Local variables

You can define local variables using the let form, though I vastly prefer the let* form because it behaves closer to Clojure's 'let' form, allowing you to reference variables defined in the same form.

(defun make-ngrams (string ngram-size)
  (let* ((string-length (string-bytes string)) ;; length of the string
         (ngram-size (min ngram-size string-length))
         (i 0) ;; a counter
         (new-chunks '())) ;; empty list we'll use as state
    ;; while we haven't reached the end of the string yet
    (while (<= (+ i ngram-size) string-length)
      (let ((s (substring string i (+ i ngram-size))))
        (push s new-chunks) ;; add the string to the list
        (setq i (+ i 1)))) ;; increment i
    new-chunks))

(make-ngrams "supercalafragilisticexpeallodocious" 1)
(make-ngrams "supercalafragilisticexpeallodocious" 2)
(make-ngrams "supercalafragilisticexpeallodocious" 3)
(make-ngrams "supercalafragilisticexpeallodocious" 120)

What do you think would have happened if I used let instead of let*?

(interactive), prompting magic

Our add-one function is great and all, but what about making a function that shows up in M-x? To do that, a function needs to be marked as interactive.

;; Doesn't show up with M-x!
(defun say-hello ()
  (message "Hello %s" (user-full-name)))

(defun say-hello2 ()
  (interactive)
  (message "Hello %s" (user-full-name)))

Interactive is for more than this though, it can also be used for prompting input as placeholders for function arguments

(defun say-hello-to-me (username favorite-number)
  (interactive "sWhat's your name?
nWhat's your favorite number? ")
  (message "Why, hello there %s! Your favorite number times 7 is %d"
           username
           (* 7 favorite-number)))

;; You can still invoke it as a regular function
(say-hello-to-me "Sally" 42)

There are a bunch of other interactive parameters too, check out the help for it (C-h f interactive) or the Emacs manual entry for it.

Interacting with buffers

Emacs lisp has many, many, function to deal with text, let's make a function that will number lines!

(defun my-number-lines (&optional offset)
  (interactive)
  (goto-char (point-min))
  (let ((i (or offset 1)))
    (beginning-of-line)
    (insert-string "0. ")
    (while (not (= (forward-line 1) 1))
      (beginning-of-line)
      (insert-string (format "%d. " i))
      (setq i (+ i 1)))))

But wait! you notice at the end of running that, the cursor has jumped around, what if we don't want that?

(defun my-number-lines (&optional offset)
  (interactive)
  (save-excursion
    (goto-char (point-min))
    (let ((i (+ 1 (or offset 0))))
      (beginning-of-line)
      (insert-string (format "%d. " (- i 1)))
      (while (not (= (forward-line 1) 1))
        (beginning-of-line)
        (insert-string (format "%d. " i))
        (setq i (+ i 1))))))

Then it can be bound to a key:

(global-set-key (kbd "C-x N") #'my-number-lines)

Projectile

Projectile is a project interactive library for Emacs. This means you can do all sorts of neat things like going to a file in a project, searching the whole project, jump between a test and implementation, switch between project buffers, etc.

To install it, make sure you have MELPA set up and then do:

(package-install 'projectile)

Then you can enable it in your config globally with:

(projectile-global-mode)

All of projectile's bindings start with the projectile prefix, which defaults to C-c p.

Here are some of the most important keybindings:

Keybinding Action
C-c p f Find a file in the project
C-c p p Switch project
C-c p g Run grep on all files in the project
C-c p b Switch between buffers for the project
C-c p r Run query-replace for all files in the project (for refactoring)
C-c p k Kill all open buffers for the project

If there are files you don't want to show up in projectile, you can create a .projectile file and prefix things to remove with -2.

There is a lot more to projectile, but these are the basics!

Demo

Eshell

Eshell is a shell written entirely in Emacs, why on earth would you want that? Because it's awesome!

It allows you to evaluate list directly in the shell, which can be really nice.

Invoke it with M-x eshell. If you invoke it a second time it will jump back to the same buffer. To create an additional shell, use the prefix argument C-u M-x eshell.

You can redirect the output of processes directly to buffers by using a special syntax:

cat log.txt | grep "foo" >> #<buffer *scratch*>

Additionally, if you set eshell-buffer-shorthand to t you can use a shorthand for the buffers:

cat log.txt | grep "bar" >> #'*scratch*

Demo

Additional Eshell Resources

Footnotes:

1

This means that variables and functions share a different namespace, so you can have a function called "foo" and also a variable named "foo"

2

You can also invert this and use + to select only the specified files/folders

Author: Lee Hinman

Created: 2016-01-27 Wed 13:13

Validate