Xah Lee, 2005-11, …, 2011-01-15
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.
(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)) ) ) ))
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 cycle-font.
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.
2010-04-09 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: Source documents.wolfram.com
(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 ) )
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: Emacs Lisp: Multi-Pair String Replacement Function.
The following shows how to call a shell command and process its output.
(defun get-image-dimensions (img-file-path) "Returns a image file's width and height as a list. This function requires ImageMagick's “identity” shell command." (let (cmd-name sh-output width height) (setq cmd-name "identify") (setq sh-output (shell-command-to-string (concat cmd-name " " img-file-path)) ) ; sample output from “identify”: ; some.png PNG 520x429+0+0 DirectClass 8-bit 9.1k 0.0u 0:01 (string-match "^[^ ]+ [^ ]+ \\([0-9]+\\)x\\([0-9]+\\)" sh-output) (setq width (match-string 1 sh-output)) (setq height (match-string 2 sh-output)) (list (string-to-number width) (string-to-number height)) ))
(defun tag-image () "Replace a image file name with a HTML img tag. Example, if cursor is on the word “emacs_logo.png”, then it will became “<img src=\"emacs_logo.png\" alt=\"emacs logo\" width=\"123\" height=\"456\">”. This function requires the “identify” command from ImageMagick.com." (interactive) (let (img-file-path bounds img-dim width height altText myResult) (setq img-file-path (thing-at-point 'filename)) (setq bounds (bounds-of-thing-at-point 'filename)) (setq altText img-file-path) (setq altText (replace-regexp-in-string "\\.[A-Za-z]\\{3,4\\}$" "" altText t t)) (setq altText (replace-regexp-in-string "_" " " altText t t)) (setq img-dim (get-image-dimensions2 img-file-path)) (setq width (number-to-string (car img-dim))) (setq height (number-to-string (car (last img-dim)))) (setq myResult (concat "<img src=\"" img-file-path "\"" " " "alt=\"" altText "\"" " " "width=\"" width "\" " "height=\"" height "\">")) (save-excursion (delete-region (car bounds) (cdr bounds)) (insert myResult)) ))
For detailed, see: Emacs Lisp: Writing a image-linkify Function.
Often you want to open the current dir in the operating system's desktop. Here's the code:
(defun open-in-desktop () "Open the current file in desktop. Works in Microsoft Windows, Mac OS X, Linux." (interactive) (cond ((string-equal system-type "windows-nt") (w32-shell-execute "explore" (replace-regexp-in-string "/" "\\" default-directory t t))) ((string-equal system-type "darwin") (shell-command "open .")) ((string-equal system-type "gnu/linux") (shell-command "xdg-open .")) ) )
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.
Example: (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”.
blog comments powered by Disqus