Xah Lee, 2006-10
This page shows a example of writing a emacs lisp function that creates a HTML link on a url string in the buffer. If you don't know elisp, first take a look at Emacs Lisp Basics.
Suppose you work in HTML a lot, and often need to make a URL into a link.
Suppose you have this URL text in your buffer:
http://en.wikipedia.org/wiki/Emacs
and your cursor is on that line. You want to be able to, press a button, and have the text changed to
<a href="http://en.wikipedia.org/wiki/Emacs">Emacs</a>
This page shows you how to write this function.
First, our function will be of this form:
(defun wrap-url () "Wee! make you a link!" (interactive) ; 1. grab the url in the current line ; 2. parse the url into a list, and grab the last element ; this will be our linktext ; 3. insert string 「<a href="」 url 「">」 linktext 「</a>」 )
We will construct little lisp functions for each step, then put them all together.
;; this snippet grabs the current block of text delimited by space. ;; It relies on the very useful search-backward and search-forward ;; and buffer-substring (defun ff () "test. grabs the sequence of chars delimited by space." (interactive) (let (x1 x2 url) (progn (re-search-backward " ") (setq x1 (search-forward " ")) (re-search-forward " ") (setq x2 (search-backward " ")) (setq url (buffer-substring x1 x2)) (kill-region x1 x2) (insert url) ) ) )
Now, we write another test function that returns the last part in a URL separated by slash. For example, 〔http://en.wikipedia.org/wiki/Green_tea〕 will return “Green_tea”.
The key to write this function is the function “split-string”.
(defun ff () "test" (interactive) (let ((url "http://en.wikipedia.org/wiki/Green_tea") linktext) (insert (car (last (split-string url "/"))) ) ) )
Now, we have the URL string and the linktext string. We need to concatenate them into one string. The key function is “concat”.
; concatenate strings (defun ff () "test" (interactive) (insert (concat "a" "b" "c")) )
Now we know how to code all the major steps for our wrap url function. We can put them together.
(defun wrap-url () "Make thing at cursor point into a html link. Example: http://en.wikipedia.org/wiki/Emacs becomes <a href=\"http://en.wikipedia.org/wiki/Emacs\">Emacs</a>" (interactive) (let (x1 x2 url linktext) (progn (re-search-backward " ") (setq x1 (re-search-forward " ")) (re-search-forward " ") (setq x2 (re-search-backward " ")) (setq url (buffer-substring x1 x2)) (setq linktext (car (last (split-string url "/"))) ) ) (delete-region x1 x2) (insert (concat "<a href=\"" url "\">" linktext "</a>")) ) )
Now, we can test this. For example, here is a URL:
article http://en.wikipedia.org/wiki/Emacs something
Put your cursor on the URL, and type 【Alt+x wrap-url】. The URL will become a link.
Now we need to refine the code and do some clean up. For example, if the URL starts on a line by itself, the function won't work, because it is looking for spaces as the URL's delimiters. We can fix this by adding more chars in argument of the re-search-* functions.
Also, suppose, if the link is not a wikipedia link, but a generic link such as 〔http://example.com/news/3829.php〕 then it makes little sense to have the link text shown as “3829.php”. So, we modify the code so that if it is not a wikipedia link, the link text should be the full url.
Also, some links contain percent encoded characters. For example:
http://en.wikipedia.org/wiki/Europa_%28mythical%29
The “%28” and “%29” is really the left parenthesis and right parenthesis. The linktext should show them as the decoded chars as in “Europa (mythical)”.
Another thing is that, wrap-url may be a useful function that can be called by other functions. So, it would be useful, to make wrap-url take a string input and return the processed text as output. Then write a wrapper function that calls wrap-url for interactive use.
The following is the result of this clean up.
The wrap-url-string is a function that takes a string and return a string.
(defun wrap-url-string (url &optional raw) "Returns url as a HTML link. Example: http://en.wikipedia.org/wiki/Emacs becomes <a href=\"http://en.wikipedia.org/wiki/Emacs\">Emacs↗</a>. If the url is a Wikipedia link, then the link text is shortened to its title. If the optional argument raw is non-nil, no shortening will happen." (require 'gnus-util) (let ((linktext url)) (setq linktext (gnus-url-unhex-string linktext nil)) (if (and (not raw) (string-match "wikipedia.org" url)) (progn (setq linktext (concat (car (last (split-string linktext "/"))) "↗") ) (setq linktext (replace-regexp-in-string "&" "&" linktext)) (setq linktext (replace-regexp-in-string "_" " " linktext)) ) ) (concat "<a href=\"" url "\">" linktext "</a>" ) ) )
Note: in the above, the wrap-url-string takes a optional argument “raw”. This is done with “&optional”. If the argument list contains “&optional”, then all parameters after it is optional. If not given, the default value is nil.
(info "(elisp) Argument List")
Also, we needed to turn the percent encoded strings such as “%28”
“%29” to “(” and “)”. Lucky for us, such function already exist, in the
package “gnus-util” as “gnus-url-unhex-string”. (it is bundled with
emacs) So, we load the package by (require 'gnus-util).
Now, the following wrap-url function is the interactive wrapper that calls wrap-url-string.
(defun wrap-url () "Make the url at cursor point into a html link. If there is a region, use the region as url instead. This function is interface wrapper for `wrap-url-string'. See that function for detail." (interactive) (let (bds p1 p2 url) (if (and transient-mark-mode mark-active) (progn (setq p1 (region-beginning)) (setq p2 (region-end)) ) (progn (setq bds (bounds-of-thing-at-point 'url)) (setq p1 (car bds)) (setq p2 (cdr bds)) ) ) (setq url (buffer-substring-no-properties p1 p2)) (delete-region p1 p2) (goto-char p1) (insert (wrap-url-string url)) ) )
Note, in the documentation string, we used `wrap-url-string'. The backtick and single quote is there, so that when user looks up the inline doc for wrap-url, the word “wrap-url-string” will appear as a hyperlink. User can click on it to jump to its inline doc.
(See: Emacs Function's Inline Doc String Markups.)
The (if (and transient-mark-mode mark-active) …) will tell us whether there is a active region. If user has already selected a region, use that region's text as the url string.
(See: Emacs: What's Region, Active Region, transient-mark-mode?.)
The (bounds-of-thing-at-point 'url) will return a list with the beginning position of a url as first element, and ending position as second element.
What described above is the general process of writing code in lisp. In fact, it is the general process in functional programing. Start with a spec sketch. Then break it down to components. Code the components, then finally string them together into a larger function. Or, collect functions together into a coherent package.