Emacs Lisp Examples: Symbol Properties, Set Frame, Font, Fold, Shell Call
This page shows some short but advanced examples of elisp. For more basic elisp examples, goto: Basic Elisp Examples.
This examples shows a simple use of
(defadvice kill-ring-save (before slick-copy activate compile) "When called interactively with no active region, copy the current line." (interactive (if mark-active (list (region-beginning) (region-end)) (progn (message "Current line is copied.") (list (line-beginning-position) (line-beginning-position 2)) ) ) ))
(defadvice kill-region (before slick-copy activate compile) "When called interactively with no active region, cut the current line." (interactive (if mark-active (list (region-beginning) (region-end)) (progn (list (line-beginning-position) (line-beginning-position 2)) ) ) ))
Symbol Properties, Emacs Frame, Font
The following example lets you press a key to change the font in the current window. When pressed again, it cycles among 2 fonts, one monospaced and one variable width.
(defun cycle-font () "Change font in current frame. When called repeatedly, cycle thru a predefined set of fonts. Warning: expected to work for Windows only. May not work in other OS." (interactive) (if (not (eq last-command this-command)) (progn (set-frame-parameter nil 'font "Courier New-10") (put this-command 'state "2")) (cond ((string= (get this-command 'state) "1") (set-frame-parameter nil 'font "Courier New-10") (put this-command 'state "2")) ((string= (get this-command 'state) "2" ) (set-frame-parameter nil 'font "Lucida Sans Unicode-10") (put this-command 'state "1")) ) ) )
Although the code is only 15 lines, but this example shows several advanced elisp use and emacs system. It shows you: ① how to get and set properties to lisp symbols. ② how to set font in a “frame”. ③ How to test if a command is called repeatedly.
First, the code checks if it is being called repeatedly. If being called for the first time, then just set font to monospace. But if it is being called repeatedly, it will cycle between 2 fonts: one monospace (same width) and the other variable width.
;; check if the command is being called repeatedly (if (not (eq last-command this-command)) … )
In the above, the “this-command” and “last-command” are built-in variables. They store the command currently called or last called.
(set-frame-parameter nil 'font "Courier New-10") ;; set a font in current frame
In the above,
set-frame-parameter is used to set a font in a frame. What we call a “window” is called a “frame” by emacs (and what we call a “pane” in a split window is called a “window” by emacs.).
(get this-command 'state) ; get the value of a lisp symbol's property (put this-command 'state "2") ; set the value of a lisp symbol's property
In the above, it gets and sets lisp symbol's property. A lisp “symbol” is similar to a variable/function name in common languages. It stores a value. However, a lisp symbol can also hold other special values, called “properties”. You can almost think of it as a “object” of object oriented programing.
In the line
(put this-command 'state "2"), the “this-command” evaluates to the symbol
cycle-font, so it becomes this:
(put 'cycle-font 'state "2"), and that means set the value “2” to the “state” property of the symbol
We are using a property to store the current state of the cycle point. The code checks the value of the “state” property to know what state it is in.
Symbols can have any number of properties, and each property can have any name, and each property's value can be almost any lisp datatype.
Lisp's symbol property is heavily used in elisp. For example, syntax coloring info are stored as property of strings, font faces use symbol property to store their definition such as color, size, font family.
PS: for a much better cycle-font command, see: How to Quickly Switch Fonts in Emacs.
The following example shows a basic way to define the Fold (aka “reduce”) function that's commonly found in functional programing languages. For a example documentation of Fold in Mathematica, see: Mathematica: Fold
(defun fold (f x list) "Recursively applies (F i j) to LIST starting with X. For example, (fold F X '(1 2 3)) computes (F (F (F X 1) 2) 3)." (let ((li list) (x2 x)) (while li (setq x2 (funcall f x2 (pop li))) ) x2 ) )
Here's a example of usage:
(defun sum2 (x y) (+ x y)) ; add 2 numbers (fold 'sum2 1 '(2 3)) ; returns 6
In the following, we use fold to define replace-string-pairs, which does string replacement by a given list of find-replace pairs.
(defun replace-string-pairs (str pairs) "replace the string STR repeatedy by the list PAIRS. Example: (replace-string-pairs \"abcd\" '( (\"a\" \"1\") (\"b\" \"2\") (\"c\" \"3\")) ) ⇒ 123d" (fold (lambda (x y) "" (replace-regexp-in-string (nth 0 y) (nth 1 y) x) ) str pairs ) )
Note: this is just a toy example, not with practical considerations.
Emacs lisp compiler does not optimize linear recursion (i.e. Tail Recursion). So the above is comparatively slow and resource hogging, also no error handling.
In the Common Lisp package
(require 'cl), there's the
reduce function that is should be more practical.
For a practical elisp function that does multiple replace pairs, see: Elisp: Multi-Pair String Replacement Function.
Get Image Width Height
moved to Elisp: HTML image-link Command
Open File in OS
Parsing Nested Text
The following example is the solution to this challenge:
Define a function f, such that
(f "simple sexp") returns the argument as a lisp's list.
(f "(a (b) c)") returns
(a (b) c).
This problem is a simple example of a parser. The program reads a string that represents a tree, and turn this string into a actual tree structure.
The following code is given by Jorgen Schäfer, 2006. If i recall correctly, it's written in less than 10 minutes.
(defun parse-string (s) (with-temp-buffer (insert s) (goto-char (point-min)) (parse))) (defun parse () (cond ((looking-at "\\s-*(\\s-*") (goto-char (match-end 0)) (let ((exprs nil) (this nil)) (while (and (not (looking-at "\\s-*)\\s-*")) (setq this (parse))) (setq exprs (cons this exprs))) (goto-char (match-end 0)) (apply 'list (reverse exprs)))) ((looking-at "\\s-*\\([a-z]+\\)\\s-*") (goto-char (match-end 0)) (intern (match-string 1))) (else (error "Syntax error")))) ; sample usage (parse-string "(a (b (h h)) (c d))")
Thanks to Hauke Rehfeld for a improvment of “fold”.