Last Updated: 2020-02-20 Thu 13:48

Tool Time Session 2: Emacs Customization with Lisp

Table of Contents

1 Metadata

Session Synopsis: Most of the power in Emacs derives from a built-in Lisp interpreter. Nearly every aspect of Emacs can be customized through writing bits of Lisp code and this session will cover how. As a by-product, participants will gain some basics of Lisp, a language still relevant 50+ years into its life.

2 What's About to Happen?

  • We'll talk about the Emacs text editor
  • Focus on some of the customization aspects that are useful to get in your pocket early and build on over time
  • Try to surmount the difficult of getting acquainted with a very old but still relevant piece of software

emacs-learning-curve.jpg

2.1 Thank Yous

  • Joe Finnegan: for making the recordings possible and enshrining all my mistakes permanently the in the clogged tubes of the Internet
  • Computer Science Dept: for supporting and advertising the series
  • Institute of Mathematics and its Applications: for lending us Keller 3-180 to do this session
  • Students Past and Present: for showing interest in these tools, pestering me to show them how they work, and showing up today

3 Keystrokes, Commands, Lisp Code

  • In previous session, determined that things happen in Emacs by running commands of various kinds
  • Some commands have a Key Binding associated with them
  • Others are interactive allowing M-x command to be used
  • Ultimately all of them are some sort of Lisp function that can be executed by asking Emacs to call the func
  • Keystroke M-: calls up a prompt for raw Lisp code
  • Below table shows 3 ways to run some common commands
Keys Interactive Command Emacs Lisp Function Evaluation
C-f M-x forward-char M-: (forward-char)
C-g M-x keyboard-quit M-: (keyboard-quit)
C-x C-f M-x find-file M-: (find-file "~/.emacs")
C-x C-s M-x save-buffer M-: (save-buffer)
C-x C-v M-x find-alternate-file M-: (find-alternate-file "~/.bashrc")
C-x b M-x switch-to-buffer M-: (switch-to-buffer ".bashrc")
C-x C-b M-x buffer-menu M-: (buffer-menu)

4 Nature of Customizations

  • Interacting with Emacs means running commands
  • Changing the appearance of Emacs will also mean running commands
  • Unlike interactive editing, often want Customizations run once at startup
  • Makes sense to Save customizations in a file
  • Basic customizations (key bindings, etc.) are usually put in the file ~/.emacs
  • Many old-school Unix programs follow this pattern: custom settings stored in a file in the users home directory, usually starting with a dot to make it a Unix "hidden" file

    Program Settings File
      In Home Dir
    Emacs .emacs
    Bash Shell .bashrc
    Vim .vimrc
    Top .toprc
  • The "rc" associated with many files is short for "run commands" as in "run these commands on startup"
  • For Emacs, the 'run commands' are Lisp code to run at startup

5 Basic and Recommended Customizations

  • Default functions to allow buffer/file selection are limited, don't provide robust auto-completion
  • ido-mode enables better auto-completion
  • Every time one starts Emacs, could type

    M-x ido-mode
    
    
  • OR one could add the Lisp code to .emacs

    (ido-mode 1)    ; turns on ido-mode
    
    
  • Try this and restart Emacs: ido-mode will be active at startup
  • A number of other global mode customizations are worth exploring
;; simple .emacs file with some basic mode customizations
(ido-mode 1)                            ;; better auto completion for files/buffers
(blink-cursor-mode 0)                   ;; disable blinking cursor to prevent mental trauma
(show-paren-mode 1)                     ;; shows matching parens or failure to match
(global-auto-revert-mode 1)             ;; reload files that change on disk
(column-number-mode 1)                  ;; show column numbers in mode line
(global-hl-line-mode 1)                 ;; change the color of the line the cursor is on

(setq-default truncate-lines t)         ;; default to NOT wrapping long lines in display
(tool-bar-mode 0)                       ;; hide the tool bar
(menu-bar-mode 0)                       ;; hide the "File Edit..." menu
(scroll-bar-mode -1)                    ;; hide the scroll bars

(setq inhibit-startup-screen t)         ;; don't show the "splash" screen
(setq initial-scratch-message           ;; put this text in the scratch buffer
      ";; Scratch that!\n")

6 Dynamically Executing Code

  • Emacs has a built-in Lisp interpreter
  • After making changes to .emacs, Can restart…
  • OR can just execute the code/file
  • In an Emacs Lisp buffer (.emacs or *scratch*), several keys/commands for executing Lisp

    Keys Effect
    C-M-x Execute Lisp code under cursor
    C-x C-e Execute Lisp expression left of the cursor
    M-x eval-buffer Evaluate all Lisp code in the buffer
    M-x load-file Load and execute a Lisp file from disk
  • Note that Emacs provides code execution for other interpreters as well: send a python function/buffer to the Python interpreter, send a Clojure function definition to a running session, etc.
  • Worth exploring for those interactive environments

7 Customizing Key Bindings

  • Emacs allows keys to be bound to run arbitrary commands, highly configurable
  • Two common interactive functions for this

    Keys Effect
    M-x global-set-key Bind a key binding for all of Emacs
    M-x local-set-key Bind a key for only the current buffer
  • Using these will prompt for a key sequence then an interactive Lisp function (command) to run
  • Will look later at Standard vs Interactive Lisp functions
  • lorem ipus abaasldf jasdkf jasdf jkasdf jasdf jkasdf jj asdf jkkasdf jjkkasdf jjasdf jjkkasdf jasdf asdf jasdkf asdf jaksdjf kasdf asdf asdf kk asdfj asdf jasdf
  • Some of my favorite global key bindings

      (global-set-key "\M-o"  'other-window)        ; change quickly between windows, default C-x o
      (global-set-key "\C-cc" (quote compile))      ; compile in buffers directory, usually via 'make'
      (global-set-key "\C-cl" 'auto-fill-mode)      ; toggle whether emacs wraps/newlines text or not
      (global-set-key "\C-cp" 'replace-string)      ; query for find/replace and replace all
      (global-set-key "\M-j"  'join-line)           ; join current line with line above
      (global-set-key "\C-xg" 'magit-status)        ; emacs interface to git called 'Magit'
      (global-set-key "\C-ck" 'browse-kill-ring)    ; examine the "cut" text newest to oldest
      (global-set-key "\C-cv" 'find-file-at-point)  ; treat text at point as a file name and open it
      (global-set-key "\C-cd" 'set-comment-column)  ; set how for righ-hand comments appear, use C-; to introduce comments
    
  • Note use of quotes above: this is an important part of Lisp, allows symbols to be used, will discuss some later, but here the symbols are for function names to run when keys are pressed
  • Emacs has several ways to express keystrokes that global-set-key understands

      (global-set-key "\C-cc"       'compile) ; standard, old-school
      (global-set-key "c"         'compile) ; includes non-printing ctrl-C char which may not show up in web browsers
      (global-set-key (kbd "C-c c") 'compile) ; kbd converts to internal syntax
    

8 Aside: What Command did I run?

  • Sometimes know an interactive version of a command

    M-x ido-mode
    
    
  • Don't know the right way to call the Lisp function

    (ido-mode ???)
    
    
  • Commonly the case for setting key bindings

    M-x global-set-key ...
    (global-set-key ???)
    
    

    Some key bindings are difficult to describe

  • Trick: run the interactive command then use

    Keys Effect
    C-x Esc Esc Rerun last command, shows lisp code for it
  • Will show last Lisp code evaluated in the minibuffer
  • Very useful for figuring out key bindings

9 Mode Keybindings and Mode Hooks

  • Major Modes like c-mode, python-mode, shell-mode, etc. will bind keys specially for the tasks they address
  • General Conventions for Key Bindings:

    Keys Notes
    key Self-insertion, movement, modes customize, users usually don't
    C-key / M-key Used for movement, editing, modes customize, users do less often
    C-x key are global-ish, buffer/window/file control, modes/users don't often customize
    C-c key are specific to major modes, most likely to customize by users and major modes
  • Documentation for major modes (via C-h m) lists their most significant keybindings
  • Users often want tweaks for key bindings, mode variables, etc. just for that mode
    • Kauffman likes to hide some files in directories but show them when pressing 'h' (dired-mode)
    • Kauffman prefers 2 spaces of indentation in C code, use // for comments in C code (c-mode)
  • Emacs provides Mode Hooks, a place to hang code that will be run whenever a major mode starts
  • Some sample short mode hooks
(require 'dired-x)			   ; require additional functionality for dired 
(add-hook 'dired-mode-hook                 ; when running dired-mode to edit directories...
  (lambda ()                               ; run the following 0-arg function which...
     (setq dired-omit-files                ; sets a regexp for which files to hide
           "^\\.?#\\|^\\.$\\|^\\.\\.$\\|^\\.[a-zA-Z]")
     (dired-omit-mode 1)                   ; initially hide uninteresting files
     (local-set-key "h" 'dired-omit-mode)  ; toggle hiding 'uninteresting' files
    ...                                    ; and some other stuff
))

(add-hook 'c-mode-common-hook              ; when starting a c-mode or derived...
 (lambda ()                                ; run this 0-arg function which...
   (setq comment-start "// ")              ; use // for comment syntax
   (setq comment-end "")                   ; no ending required
   (setq c-basic-offset 2)))               ; 2 spaces for indentation
  • Keep in mind mode hooks run when the mode initially starts; changes to mode hooks mean that you must reload a file which will restart the mode and re-run the hooks
  • Keep in mind add-hook adds hooks:

      (add-hook 'dired-mode-hook
        (lambda () (local-set-key "h" 'help)))             ; second function to run
      
      (add-hook 'dired-mode-hook
        (lambda () (local-set-key "h" 'dired-omit-mode)))  ; first function to run
    
  • CS folks can probably guess why the order of the two functions above is as indicated (What is the "Lis" in "Lisp"?)

    Hook variables store a singly linked list which adds things (cons) onto the front. The most recently added function in the hook is run first followed by earlier functions.

  • Hooks are stored in variables and an be reset via

      (setq dired-mode-hook '())  ; sets list of hooks to empty
    

10 Package System

  • Emacs has an extensive set of packages that can be installed to augment its built-in features
  • This like typical package managers in Linux like APT, RPM, and Pacman
    • "Emacs is a great operating system, lacking only a decent text editor."
  • Note: Default GNU server that lists packages is somewhat limited, strongly suggest following code which will query another widely used MELPA package server:

      (add-to-list 'package-archives '("melpa" . "http://melpa.milkbox.net/packages/") t)
      (package-initialize)
    
  • Package manager basics are as follows

    Keys Effect
    M-x list-packages Opens a buffer listing packages available/installed
    n,p Next / previous lines
    C-s Standard interactive search forward
    Return Show more package info
    i Mark server package for installation
    d Mark installed package for deletion
    x Execute install/deletions
  • Demo installation of the nyan-mode which is pure fun
  • Of course, after installing packages, one has to know how to use them, e.g. add (nyan-mode) to .emacs
  • Installation of packages is in the .emacs.d/ directory where Emacs stores lots of other stuff

11 Changing Colors and Fonts

  • Emacs comes with a few scant built-in color themes
  • Can explore these via M-x load-theme, use TAB to get completion lists
  • For builtin themes, following code in .emacs will change colors at startup

      (load-theme (quote wombat) t)
    
  • A number of other color themes like solarized and abyss exist in the package manager
  • BUT getting these to work in your init file could prove troublesome
  • Color themes are relatively new to Emacs and are still experiencing some growing pains (e.g. "Face" system preceded it, ad hoc color-theme package preceded it, only recently centering)
  • For the daring, can define your own themes by copying and extending existing ones: see lila-theme.el
(deftheme lila
  "Created 2018-04-27.")

(custom-theme-set-variables
 'lila
 '(ansi-color-names-vector ["black" "red3" "green3" "yellow3" "blue2" "magenta3" "cyan3" "white"]))

(custom-theme-set-faces
 'lila
 '(default ((t (:inherit nil :stipple nil :background "#000000" :foreground "#FFFFFF" :inverse-video nil :box nil :strike-through nil :overline nil :underline nil :slant normal :weight normal :height 90 :width normal :foundry "PfEd" :family "DejaVu Sans Mono"))))
 '(cursor ((((background light)) (:background "black")) (((background dark)) (:background "white"))))
 '(fixed-pitch ((t (:family "Monospace"))))
 '(variable-pitch ((((type w32)) (:foundry "outline" :family "Arial")) (t (:family "Sans Serif"))))
 '(escape-glyph ((((background dark)) (:foreground "cyan")) (((type pc)) (:foreground "magenta")) (t (:foreground "brown"))))
...
  • Might want to say a few words about Faces at this time if time allows

12 The Customization System

  • Emacs has a built-in customization system
  • Try M-x customize and browse arround

    Keys Effect
    M-x customize Opens customization subsystem buffer
    M-x customize-group Select a specific group to customize (e.g. lisp, c, java)
    M-x customize-variable Customize a variable defined in a package to be something in particular
    Enter Select something
    C-c C-c Save for Current session to try out the settings
    C-x C-s Save the setting into .emacs permanently
  • Saved customizations are in a special region of .emacs

13 Emacs Lisp

  • Lisp is old, has a long association with AI, BUT is just a good programming language if you can stomach it: "These were your father's parentheses…"
  • General "syntax" is

    (operator arg1 arg2 arg3)
    
    
  • Operator may be a normal function name like insert or a special form like if or defun
  • Takes some getting used to but once you do, it feels REALLY good
  • MIT and UMN used to teach Programming 1 in Scheme, a dialect of Lisp
  • Every proficient programmer should have some Lisp in their tool belt

14 Demo of Defining some Handy Functions

  • A simple interactive function (command) to get us started

      (defun hello-world ()                             ; define a func, 0 args
          "Say hello world in the messages/mini-buffer" ; documentation string
          (interactive)                                 ; make func interactive (via M-x hello-world)
          (message "hello world"))                      ; put a messag ein the mini buffer
    
  • Tend to bounce between writing code, checking what functions key bindings run, consulting function documentation in Emacs, reading manual, Googling for useful functions
  • Problem: Start a shell in the directory associated with a buffer
    • Otherwise run *shell* is not running, start it running
    • Change to the current directory in it
    • Split windows (vertically) so that the shell appears in an adjacent window
(defun shell-jump-to-current-directory ()
  "Open *shell* and change to directory of the given
buffer. Start's shell if needed. Handles dired-mode specially to
look for the 'closest' subdirectory when multiple directories are
open."
  (interactive)
  (let* ((dir (if (equal major-mode 'dired-mode)          ; using dired?
                  (dired-find-directory-at-point)         ; dired mode: find closest directory
                  default-directory))                     ; other buffer: use default dir for buffer
         (dir (replace-regexp-in-string " " "\\\\ " dir)) ; escape spaces
	 (cmd (concat "cd " dir)))                        ; add on the 'cd' 
    (when (one-window-p)                                  ; in there is only one window
      (split-window-right))                               ; split to 2 windows, left/right
    (if (get-buffer "*shell*")                            ; check if shell is already active
      (progn                                              ; yes (true case)
        (other-window 1)                                  ; switch to other window
        (switch-to-buffer "*shell*"))                     ; change to the *shell* buffer
      (progn                                              ; no (false case)
        (shell)                                           ; start the shell
        (sleep-for 0 10)))                                ; 10 millisec delay to let the shell get started before sending input (hack)
    (end-of-buffer)                                       ; move point to the end of the shell buffer
    (insert cmd)                                          ; insert the 'cd' command
    (comint-send-input)))                                 ; signal the shell that a command has been sent

(global-set-key "\C-cj" 'shell-jump-to-current-directory)

Author: Chris Kauffman (kauffman@umn.edu)
Date: 2020-02-20 Thu 13:48