Skip to content

Latest commit

 

History

History
325 lines (260 loc) · 12.2 KB

emacs-elisp.org

File metadata and controls

325 lines (260 loc) · 12.2 KB

Emacs Lisp Programming for Emacs

Sure, everything in my setup files is in Emacs Lisp, but this helps me write more of that… like making snazzy symbols and colorizing the variables.

Introduction

Since Emacs is great environment for writing Emacs Lisp, I never thought to customize that experience.

However, after watching John Wiegley’s talk, I decided to craft my experience a bit (especially since I do quite a bit of Emacs Lisp nowadays).

See these features for general programming applicable to Emacs Lisp. Check out the built-in Info documentation:

C-h i
Including Elisp Introduction and Full Reference
C-h f
Function description
C-h v
Variable description with current and previous setting
C-h S
Search the Info based on function or variable name

Emacs Lisp Mode

Hook in some packages into the built-in Emacs Lisp Mode. The only real snazzy symbol that I like is replacing the lambda with λ:

(use-package lisp-mode
  :init
  (defconst lisp--prettify-symbols-alist
    '(("lambda"  . )                  ; Shrink this
      ("."       . ?•)))                ; Enlarge this

  :bind (("C-c e i" . ielm))

  :config
  (add-hook 'emacs-lisp-mode-hook 'global-prettify-symbols-mode)
  (add-hook 'emacs-lisp-mode-hook 'turn-on-eldoc-mode)
  (add-hook 'emacs-lisp-mode-hook 'activate-aggressive-indent)

  ;; Bind some prefixes to a couple of mode maps:
  (bind-keys :map emacs-lisp-mode-map
             :prefix-map lisp-find-map
             :prefix "C-h e"
             ("e" . view-echo-area-messages)
             ("f" . find-function)
             ("k" . find-function-on-key)
             ("l" . find-library)
             ("v" . find-variable)
             ("V" . apropos-value))

  (dolist (m (list emacs-lisp-mode-map lisp-interaction-mode-map))
    (bind-keys :map m
               :prefix-map lisp-evaluation-map
               :prefix "C-c e"
               ("b" . eval-buffer)
               ("r" . eval-region)
               ("c" . eval-and-comment-output) ;; Defined below
               ("o" . eval-and-comment-output)
               ("d" . toggle-debug-on-error)
               ("f" . emacs-lisp-byte-compile-and-load))))

Emacs obviously does Emacs Lisp well, including most keybindings you can imagine (did you know that you can be in the Info mode, and evaluate any Lisp expression in the documentation with a C-x C-e).

IDO Everywhere

To help with some of the find- functions, we need IDO on everything:

(defvar ido-enable-replace-completing-read t
  "If t, use ido-completing-read instead of completing-read if possible.

    Set it to nil using let in around-advice for functions where the
    original completing-read is required.  For example, if a function
    foo absolutely must use the original completing-read, define some
    advice like this:

    (defadvice foo (around original-completing-read-only activate)
      (let (ido-enable-replace-completing-read) ad-do-it))")

;; Replace completing-read wherever possible, unless directed otherwise
(defadvice completing-read
    (around use-ido-when-possible activate)
  (if (or (not ido-enable-replace-completing-read) ; Manual override disable ido
          (and (boundp 'ido-cur-list)
               ido-cur-list)) ; Avoid infinite loop from ido calling this
      ad-do-it
    (let ((allcomp (all-completions "" collection predicate)))
      (if allcomp
          (setq ad-return-value
                (ido-completing-read prompt
                                     allcomp
                                     nil require-match initial-input hist def))
        ad-do-it))))

Hrm… perhaps this code should be in my primary Emacs configuration file.

Nicer Paren Matching

The reverse mode of the default parenthesis matching doesn’t match as well, so this code just makes it bold and more obvious:

(use-package paren
  :init
  (set-face-background 'show-paren-match (face-background 'default))
  (set-face-foreground 'show-paren-match "#afa")
  (set-face-attribute  'show-paren-match nil :weight 'black)
  (set-face-background 'show-paren-mismatch (face-background 'default))
  (set-face-foreground 'show-paren-mismatch "#c66")
  (set-face-attribute  'show-paren-mismatch nil :weight 'black))

While we are at it, let’s dim the parens with paren-face. May want to customize the face to be even darker.

(use-package paren-face
  :ensure t
  :init
  (global-paren-face-mode))

While we are at it, let’s make sure that we get an error if we ever attempt to save a file with mismatched parenthesis:

(add-hook 'after-save-hook 'check-parens nil t)

Code Templating

The Speed of Thought concept for lots of little templates may be helpful, assuming one can learn all the acronyms, but with the overlaps and conflicts, I’ve decided to stick to Yasnippet templates and Company mode (for auto-completing).

Code Evaluation

I’m intrigued with the Inferior Emacs Lisp Mode (IELM), so let’s add the eldoc feature to it:

(use-package ielm
  :init
  (add-hook 'ielm-mode-hook 'turn-on-eldoc-mode))

Instead of displaying the results in a separate buffer (like the above code does), The EROS project displays the results temporarily in the buffer in an overlay. No need to do anything special:

(use-package eros
  :ensure t
  :init
  (add-hook 'emacs-lisp-mode-hook (lambda () (eros-mode 1))))

Insert Comment of Eval

While writing and documenting Emacs Lisp code, it would be helpful to insert the results of evaluation of an s-expression directly into the code as a comment:

(defun current-sexp ()
  "Returns the _current expression_ based on the position of the
  point within or on the edges of an s-expression."
  (cond
   ((looking-at "(") (sexp-at-point))
   ((looking-back ")" 1) (elisp--preceding-sexp))
   (t (save-excursion
        (search-backward "(")
        (sexp-at-point)))))

(defun eval-current-sexp ()
  "Evaluates the expression at point. Unlike `eval-last-sexp',
the point doesn't need to be at the end of the expression, but
can be at the beginning (on the parenthesis) or even somewher
inside."
  (interactive)
  (eval-expression (current-sexp)))

(defun eval-and-comment-output ()
  "Add the output of the `current-sexp' as a comment at the end
of the line. Calling this multiple times replaces the comment
with the new evaluation value."
  (interactive)
  (let* ((marker " ; -> ")
         (expression (current-sexp))
         (results (eval expression)))
    (save-excursion
      (beginning-of-line)
      (if (search-forward marker (line-end-position) t)
          (delete-region (point) (line-end-position))
        (end-of-line)
        (insert marker))
      (condition-case nil
          (princ (pp-to-string results) (current-buffer))
        (error (message "Invalid expression"))))))

Paredit

One of the cooler features of Emacs is the ParEdit mode which keeps all parenthesis balanced in Lisp-oriented languages. See this cheatsheet.

(use-package paredit
  :ensure t
  :diminish "﹙﹚"
  :init
  (dolist (m (list 'emacs-lisp-mode-hook 'lisp-interaction-mode-hook 'eval-expression-minibuffer-setup-hook 'ielm-mode-hook))
    (add-hook m 'enable-paredit-mode)))

As they say, “If you think paredit is not for you then you need to become the kind of person that paredit is for.”

Code Navigation

The lispy project takes the code navigation of Paredit, with the keyboard movement ideas from Vi. Essentially, if you are on a parenthesis character (where typing a letter wouldn’t make sense), then it binds that to a movement command.

(use-package lispy
  :ensure t
  :defer t
  :bind (:map lispy-mode-map
         ("C-1" . nil)
         ("C-2" . nil)
         ("C-3" . nil)
         ("C-4" . nil))
  :init
  (dolist (hook '(emacs-lisp-mode-hook
                  lisp-interaction-mode-hook
                  lisp-mode-hook
                  clojure-mode-hook))
    (add-hook hook (lambda () (lispy-mode 1)))))

Some bindings, like C-1 is wrong, so I remove those. I don’t know if I will ever get to know all the keybindings here:

d
Toggle between both sides of the s-expression
f
Move from paren to paren regardless of indentation (inside)
h
Move up to the start of the containing s-expression (parent)
j / k
Move from start of one sibling s-expression to the next
b
moves back in history for all above commands

Actually, I think I would prefer to turn on each of the keybindings, since it has so many conflicting ones.

Refactoring

Checked out Redshank, but quite disappointed. I really need the ability to pull things out into let expressions, functions and variables (and inline them back again), and for this feature, Wilfred HughesEMR system works quite well.

(use-package emr
  :config
  (add-hook 'emacs-lisp-mode-hook 'emr-initialize)
  (bind-key "R" #'emr-show-refactor-menu lisp-evaluation-map))

Debugging

Debugging is built into Emacs. Simply prepend a C-u before you evaluate a function, and when it is run, it will drop you into the debugger.

Remember the following key-bindings once started:

SPC
To stop at the next stop point
b
Set a breakpoint and q to execute until that breakpoint
q
quit the debugger (other commands, hit ? to see what is available)

Unfamiliar? Check out this introduction (or see the Info).

Suggesting

Intrigued to play with Wilfred Hughes’ project, suggest:

(use-package suggest
  :bind ((:prefix-map lisp-evaluation-map
          :prefix "C-c e"
          ("s" . suggest))))

To use, simply: M-x suggest

Technical Artifacts

Make sure that we can simply require this library.

(provide 'init-elisp)

Before you can build this on a new system, make sure that you put the cursor over any of these properties, and hit: C-c C-c