comparison lisp/quickurl.el @ 25442:b12e672a1edd

Initial revision
author Richard M. Stallman <rms@gnu.org>
date Sun, 29 Aug 1999 20:48:15 +0000
parents
children de4c44c1c073
comparison
equal deleted inserted replaced
25441:d30cff97fb20 25442:b12e672a1edd
1 ;;; quickurl.el --- Insert an URL based on text at point in buffer.
2
3 ;; Copyright (C) 1999 Free Software Foundation, Inc.
4
5 ;; Author: Dave Pearson <davep@hagbard.demon.co.uk>
6 ;; Maintainer: Dave Pearson <davep@hagbard.demon.co.uk>
7 ;; Created: 1999-05-28
8 ;; Keywords: hypermedia
9
10 ;; This file is part of GNU emacs.
11
12 ;; GNU Emacs is free software; you can redistribute it and/or modify
13 ;; it under the terms of the GNU General Public License as published by
14 ;; the Free Software Foundation; either version 2, or (at your option)
15 ;; any later version.
16
17 ;; GNU Emacs is distributed in the hope that it will be useful,
18 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ;; GNU General Public License for more details.
21
22 ;; You should have received a copy of the GNU General Public License
23 ;; along with GNU Emacs; see the file COPYING. If not, write to the
24 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
25 ;; Boston, MA 02111-1307, USA.
26
27 ;;; Commentary:
28 ;;
29 ;; This package provides a simple method of inserting an URL based on the
30 ;; text at point in the current buffer. This is part of an on-going
31 ;; effort to increase the information I provide people while reducing the
32 ;; ammount of typing I need to do (see also handyurl.el which is available
33 ;; from <URL:http://www.hagbard.demon.co.uk/archives/handyurl.el>), no-doubt
34 ;; there are undiscovered Emacs packages out there that do all of this and
35 ;; do it better, feel free to point me to them, in the mean time I'm having
36 ;; fun playing with Emacs Lisp.
37 ;;
38 ;; The URLs are stored in an external file as a list of either cons cells,
39 ;; or lists. A cons cell entry looks like this:
40 ;;
41 ;; (<Lookup> . <URL>)
42 ;;
43 ;; where <Lookup> is a string that acts as the keyword lookup and <URL> is
44 ;; the URL associated with it. An example might be:
45 ;;
46 ;; ("GNU" . "http://www.gnu.org/")
47 ;;
48 ;; A list entry looks like:
49 ;;
50 ;; (<Lookup> <URL> <Comment>)
51 ;;
52 ;; where <Lookup> and <URL> are the same as with the cons cell and <Comment>
53 ;; is any text you like that describes the URL. This description will be
54 ;; used when presenting a list of URLS using `quickurl-list'. An example
55 ;; might be:
56 ;;
57 ;; ("FSF" "http://www.fsf.org/" "The Free Software Foundation")
58 ;;
59 ;; Given the above, your quickurl file might look like:
60 ;;
61 ;; (("GNU" . "http://www.gnu.org/")
62 ;; ("FSF" "http://www.fsf.org/" "The Free Software Foundation")
63 ;; ("emacs" . "http://www.emacs.org/")
64 ;; ("hagbard" "http://www.hagbard.demon.co.uk" "Hagbard's World"))
65 ;;
66 ;; In case you're wondering about the mixture of cons cells and lists,
67 ;; quickurl started life using just the cons cells, there were no comments.
68 ;; URL comments are a later addition and so there is a mixture to keep
69 ;; backward compatibility with existing URL lists.
70 ;;
71 ;; The name and location of the file is up to you, the default name used by
72 ;; the `quickurl' is stored in `quickurl-url-file'.
73 ;;
74 ;; quickurl is always available from:
75 ;;
76 ;; <URL:http://www.hagbard.demon.co.uk/archives/quickurl.el>
77 ;; <URL:http://www.acemake.com/hagbard/archives/quickurl.el>
78
79 ;;; TODO:
80 ;;
81 ;; o The quickurl-browse-url* functions pretty much duplicate their non
82 ;; browsing friends. It would feel better if a more generic solution could
83 ;; be found.
84 ;;
85 ;; o Merge quickurl.el and handyurl.el to provide a fuller URL management
86 ;; facility that would allow for the pulling, inserting and browsing of
87 ;; URLs.
88
89 ;;; Code:
90
91 ;; Things we need:
92
93 (eval-when-compile
94 (require 'cl))
95 (require 'thingatpt)
96 (require 'pp)
97 (require 'browse-url)
98
99 ;; Attempt to handle older/other emacs.
100 (eval-and-compile
101 ;; If customize isn't available just use defvar instead.
102 (unless (fboundp 'defgroup)
103 (defmacro defgroup (&rest rest) nil)
104 (defmacro defcustom (symbol init docstring &rest rest)
105 `(defvar ,symbol ,init ,docstring))))
106
107 ;; `caddr' is a function in cl and so might not always be available
108 ;; (remembering the general rule that says cl functions should not be used,
109 ;; only cl macros). So, to make use of `caddr' without forcing the load of
110 ;; cl-seq we'll define out own.
111
112 (eval-when (load eval)
113 (unless (fboundp 'caddr)
114 (defun caddr (l)
115 "Return the `car' of the `cddr' of L."
116 (car (cddr l)))))
117
118 ;; Create a version constant.
119
120 ;; Customize options.
121
122 (defgroup quickurl nil
123 "Insert an URL based on text at point in buffer."
124 :group 'abbrev
125 :prefix "quickurl-")
126
127 (defcustom quickurl-url-file "~/.quickurls"
128 "*File that contains the URL list."
129 :type 'file
130 :group 'quickurl)
131
132 (defcustom quickurl-format-function (lambda (url) (format "<URL:%s>" url))
133 "*Function to format the URL before insertion into the current buffer."
134 :type 'function
135 :group 'quickurl)
136
137 (defcustom quickurl-sort-function (lambda (list)
138 (sort list
139 (lambda (x y)
140 (string<
141 (downcase (quickurl-url-description x))
142 (downcase (quickurl-url-description y))))))
143 "*Function to sort the URL list."
144 :type 'function
145 :group 'quickurl)
146
147 (defcustom quickurl-grab-lookup-function #'current-word
148 "*Function to grab the thing to lookup."
149 :type 'function
150 :group 'quickurl)
151
152 (defcustom quickurl-assoc-function #'assoc-ignore-case
153 "*Function to use for alist lookup into `quickurl-urls'."
154 :type 'function
155 :group 'quickurl)
156
157 (defcustom quickurl-prefix ";; -*- lisp -*-\n\n"
158 "*Text to write to `quickurl-url-file' before writing the URL list."
159 :type 'string
160 :group 'quickurl)
161
162 (defcustom quickurl-postfix ""
163 "*Text to write to `quickurl-url-file' after writing the URL list.
164
165 See the constant `quickurl-reread-hook-postfix' for some example text that
166 could be used here."
167 :type 'string
168 :group 'quickurl)
169
170 (defcustom quickurl-list-mode-hook nil
171 "*Hooks for `quickurl-list-mode'."
172 :type 'hook
173 :group 'quickurl)
174
175 ;; Constants.
176
177 ;;;###autoload
178 (defconst quickurl-reread-hook-postfix
179 "
180 ;; Local Variables:
181 ;; eval: (progn (require 'quickurl) (add-hook 'local-write-file-hooks (lambda () (quickurl-read) nil)))
182 ;; End:
183 "
184 "Example `quickurl-postfix' text that adds a local variable to the
185 `quickurl-url-file' so that if you edit it by hand it will ensure that
186 `quickurl-urls' is updated with the new URL list.
187
188 To make use of this do something like:
189
190 (setq quickurl-postfix quickurl-reread-hook-postfix)
191
192 in your ~/.emacs (after loading/requiring quickurl).")
193
194 ;; Non-customize variables.
195
196 (defvar quickurl-urls nil
197 "URL alist for use with `quickurl' and `quickurl-ask'.")
198
199 (defvar quickurl-list-mode-map nil
200 "Local keymap for a `quickurl-list-mode' buffer.")
201
202 (defvar quickurl-list-buffer-name "*quickurl-list*"
203 "Name for the URL listinig buffer.")
204
205 (defvar quickurl-list-last-buffer nil
206 "`current-buffer' when `quickurl-list' was called.")
207
208 ;; Functions for working with an URL entry.
209
210 (defun quickurl-url-commented-p (url)
211 "Does the URL have a comment?"
212 (listp (cdr url)))
213
214 (defun quickurl-make-url (keyword url &optional comment)
215 "Create an URL from KEYWORD, URL and (optionaly) COMMENT."
216 (if (and comment (not (zerop (length comment))))
217 (list keyword url comment)
218 (cons keyword url)))
219
220 (defun quickurl-url-keyword (url)
221 "Return the keyword for the URL.
222
223 Note that this function is a setfable place."
224 (car url))
225
226 (defsetf quickurl-url-keyword (url) (store)
227 `(setf (car ,url) ,store))
228
229 (defun quickurl-url-url (url)
230 "Return the actual URL of the URL.
231
232 Note that this function is a setfable place."
233 (if (quickurl-url-commented-p url)
234 (cadr url)
235 (cdr url)))
236
237 (defsetf quickurl-url-url (url) (store)
238 `
239 (if (quickurl-url-commented-p ,url)
240 (setf (cadr ,url) ,store)
241 (setf (cdr ,url) ,store)))
242
243 (defun quickurl-url-comment (url)
244 "Get the comment from an URL.
245
246 If the URL has no comment an empty string is returned. Also note that this
247 function is a setfable place."
248 (if (quickurl-url-commented-p url)
249 (caddr url)
250 ""))
251
252 (defsetf quickurl-url-comment (url) (store)
253 `
254 (if (quickurl-url-commented-p ,url)
255 (if (zerop (length ,store))
256 (setf (cdr ,url) (cadr ,url))
257 (setf (caddr ,url) ,store))
258 (unless (zerop (length ,store))
259 (setf (cdr ,url) (list (cdr ,url) ,store)))))
260
261 (defun quickurl-url-description (url)
262 "Return a description for the URL.
263
264 If the URL has a comment then this is returned, otherwise the keyword is
265 returned."
266 (let ((desc (quickurl-url-comment url)))
267 (if (zerop (length desc))
268 (quickurl-url-keyword url)
269 desc)))
270
271 ;; Main code:
272
273 (defun* quickurl-read (&optional (buffer (current-buffer)))
274 "`read' the URL list from BUFFER into `quickurl-urls'.
275
276 Note that this function moves point to `point-min' before doing the `read'
277 It also restores point after the `read'."
278 (save-excursion
279 (setf (point) (point-min))
280 (setq quickurl-urls (funcall quickurl-sort-function (read buffer)))))
281
282 (defun quickurl-load-urls ()
283 "Load the contents of `quickurl-url-file' into `quickurl-urls'."
284 (when (file-exists-p quickurl-url-file)
285 (with-temp-buffer
286 (insert-file-contents quickurl-url-file)
287 (quickurl-read))))
288
289 (defun quickurl-save-urls ()
290 "Save the contents of `quickurl-urls' to `quickurl-url-file'."
291 (with-temp-buffer
292 (let ((standard-output (current-buffer)))
293 (princ quickurl-prefix)
294 (pp quickurl-urls)
295 (princ quickurl-postfix)
296 (write-region (point-min) (point-max) quickurl-url-file nil 0))))
297
298 (defun quickurl-find-url (lookup)
299 "Return URL associated with key LOOKUP.
300
301 The lookup is done by looking in the alist `quickurl-urls' and the `cons'
302 for the URL is returned. The actual method used to look into the alist
303 depends on the setting of the variable `quickurl-assoc-function'."
304 (funcall quickurl-assoc-function lookup quickurl-urls))
305
306 (defun quickurl-insert (url &optional silent)
307 "Insert URL, formatted using `quickurl-format-function'.
308
309 Also display a `message' saying what the URL was unless SILENT is non-nil."
310 (insert (funcall quickurl-format-function (quickurl-url-url url)))
311 (unless silent
312 (message "Found %s" (quickurl-url-url url))))
313
314 ;;;###autoload
315 (defun* quickurl (&optional (lookup (funcall quickurl-grab-lookup-function)))
316 "Insert an URL based on LOOKUP.
317
318 If not supplied LOOKUP is taken to be the word at point in the
319 current buffer, this default action can be modifed via
320 `quickurl-grab-lookup-function'."
321 (interactive)
322 (when lookup
323 (quickurl-load-urls)
324 (let ((url (quickurl-find-url lookup)))
325 (if (null url)
326 (error "No URL associated with \"%s\"" lookup)
327 (when (looking-at "\\w")
328 (skip-syntax-forward "\\w"))
329 (insert " ")
330 (quickurl-insert url)))))
331
332 ;;;###autoload
333 (defun quickurl-ask (lookup)
334 "Insert an URL, with `completing-read' prompt, based on LOOKUP."
335 (interactive
336 (list
337 (progn
338 (quickurl-load-urls)
339 (completing-read "Lookup: " quickurl-urls nil t))))
340 (let ((url (quickurl-find-url lookup)))
341 (when url
342 (quickurl-insert url))))
343
344 (defun quickurl-grab-url ()
345 "Attempt to grab a word/url pair from point in the current buffer.
346
347 Point should be somewhere on the URL and the word is taken to be the thing
348 that is returned from calling `quickurl-grab-lookup-function' once a
349 `backward-word' has been issued at the start of the URL.
350
351 It is assumed that the URL is either \"unguarded\" or is wrapped inside an
352 <URL:...> wrapper."
353 (let ((url (thing-at-point 'url)))
354 (when url
355 (save-excursion
356 (beginning-of-thing 'url)
357 ;; `beginning-of-thing' doesn't take you to the start of a marked-up
358 ;; URL, only to the start of the URL within the "markup". So, we
359 ;; need to do a little more work to get to where we want to be.
360 (when (thing-at-point-looking-at thing-at-point-markedup-url-regexp)
361 (search-backward "<URL:"))
362 (backward-word 1)
363 (let ((word (funcall quickurl-grab-lookup-function)))
364 (when word
365 (quickurl-make-url
366 ;; `thing-at-point' returns the word with properties. I don't
367 ;; want the properties. I couldn't find a method of stripping
368 ;; them from a "string" so this will have to do. If you know of
369 ;; a better method of doing this I'd love to know.
370 (with-temp-buffer
371 (insert word)
372 (buffer-substring-no-properties (point-min) (point-max)))
373 url)))))))
374
375 ;;;###autoload
376 (defun quickurl-add-url (word url comment)
377 "Allow the user to interactively add a new URL associated with WORD.
378
379 See `quickurl-grab-url' for details on how the default word/url combination
380 is decided."
381 (interactive (let ((word-url (quickurl-grab-url)))
382 (list (read-string "Word: " (quickurl-url-keyword word-url))
383 (read-string "URL: " (quickurl-url-url word-url))
384 (read-string "Comment: " (quickurl-url-comment word-url)))))
385 (if (zerop (length word))
386 (error "You must specify a WORD for lookup")
387 (quickurl-load-urls)
388 (let* ((current-url (quickurl-find-url word))
389 (add-it (if current-url
390 (if (interactive-p)
391 (y-or-n-p (format "\"%s\" exists, replace URL? " word))
392 t)
393 t)))
394 (when add-it
395 (if current-url
396 (progn
397 (setf (quickurl-url-url current-url) url)
398 (setf (quickurl-url-comment current-url) comment))
399 (push (quickurl-make-url word url comment) quickurl-urls))
400 (setq quickurl-urls (funcall quickurl-sort-function quickurl-urls))
401 (quickurl-save-urls)
402 (when (get-buffer quickurl-list-buffer-name)
403 (quickurl-list-populate-buffer))
404 (when (interactive-p)
405 (message "Added %s" url))))))
406
407 ;;;###autoload
408 (defun* quickurl-browse-url (&optional (lookup (funcall quickurl-grab-lookup-function)))
409 "Browse the URL associated with LOOKUP.
410
411 If not supplied LOOKUP is taken to be the word at point in the
412 current buffer, this default action can be modifed via
413 `quickurl-grab-lookup-function'."
414 (interactive)
415 (when lookup
416 (quickurl-load-urls)
417 (let ((url (quickurl-find-url lookup)))
418 (if url
419 (browse-url (quickurl-url-url url))
420 (error "No URL associated with \"%s\"" lookup)))))
421
422 ;;;###autoload
423 (defun quickurl-browse-url-ask (lookup)
424 "Browse the URL, with `completing-read' prompt, associated with LOOKUP."
425 (interactive (list
426 (progn
427 (quickurl-load-urls)
428 (completing-read "Browse: " quickurl-urls nil t))))
429 (let ((url (quickurl-find-url lookup)))
430 (when url
431 (browse-url (quickurl-url-url url)))))
432
433 ;;;###autoload
434 (defun quickurl-edit-urls ()
435 "Pull `quickurl-url-file' into a buffer for hand editing."
436 (interactive)
437 (find-file quickurl-url-file))
438
439 ;; quickurl-list mode.
440
441 (unless quickurl-list-mode-map
442 (let ((map (make-sparse-keymap)))
443 (suppress-keymap map t)
444 (define-key map "a" #'quickurl-list-add-url)
445 (define-key map [(control m)] #'quickurl-list-insert-url)
446 (define-key map "u" #'quickurl-list-insert-naked-url)
447 (define-key map " " #'quickurl-list-insert-with-lookup)
448 (define-key map "l" #'quickurl-list-insert-lookup)
449 (define-key map "d" #'quickurl-list-insert-with-desc)
450 (define-key map [(control g)] #'quickurl-list-quit)
451 (define-key map "q" #'quickurl-list-quit)
452 (define-key map [mouse-2] #'quickurl-list-mouse-select)
453 (define-key map "?" #'describe-mode)
454 (setq quickurl-list-mode-map map)))
455
456 (put 'quickurl-list-mode 'mode-class 'special)
457
458 ;;;###autoload
459 (defun quickurl-list-mode ()
460 "A mode for browsing the quickurl URL list.
461
462 The key bindings for `quickurl-list-mode' are:
463
464 \\{quickurl-list-mode-map}"
465 (interactive)
466 (kill-all-local-variables)
467 (use-local-map quickurl-list-mode-map)
468 (setq major-mode 'quickurl-list-mode
469 mode-name "quickurl list")
470 (run-hooks 'quickurl-list-mode-hook)
471 (setq buffer-read-only t
472 truncate-lines t))
473
474 ;;;###autoload
475 (defun quickurl-list ()
476 "Display `quickurl-list' as a formatted list using `quickurl-list-mode'."
477 (interactive)
478 (quickurl-load-urls)
479 (unless (string= (buffer-name) quickurl-list-buffer-name)
480 (setq quickurl-list-last-buffer (current-buffer)))
481 (pop-to-buffer quickurl-list-buffer-name)
482 (quickurl-list-populate-buffer)
483 (quickurl-list-mode))
484
485 (defun quickurl-list-populate-buffer ()
486 "Populate the `quickurl-list' buffer."
487 (with-current-buffer (get-buffer quickurl-list-buffer-name)
488 (let ((buffer-read-only nil)
489 (fmt (format "%%-%ds %%s\n"
490 (apply #'max (or (loop for url in quickurl-urls
491 collect (length (quickurl-url-description url)))
492 (list 20))))))
493 (setf (buffer-string) "")
494 (loop for url in quickurl-urls
495 do (let ((start (point)))
496 (insert (format fmt (quickurl-url-description url)
497 (quickurl-url-url url)))
498 (put-text-property start (1- (point))
499 'mouse-face 'highlight)))
500 (setf (point) (point-min)))))
501
502 (defun quickurl-list-add-url (word url comment)
503 "Wrapper for `quickurl-add-url' that doesn't guess the parameters."
504 (interactive "sWord: \nsURL: \nsComment: ")
505 (quickurl-add-url word url comment))
506
507 (defun quickurl-list-quit ()
508 "Kill the buffer named `quickurl-list-buffer-name'."
509 (interactive)
510 (kill-buffer quickurl-list-buffer-name)
511 (switch-to-buffer quickurl-list-last-buffer)
512 (delete-other-windows))
513
514 (defun quickurl-list-mouse-select (event)
515 "Select the URL under the mouse click."
516 (interactive "e")
517 (setf (point) (posn-point (event-end event)))
518 (quickurl-list-insert-url))
519
520 (defun quickurl-list-focused-line ()
521 "Work out the line containing point."
522 (save-excursion
523 (beginning-of-line)
524 (let ((point (point)))
525 (setf (point) (point-min))
526 (loop while (< (point) point) sum 1 do (next-line 1)))))
527
528 (defun quickurl-list-insert (type)
529 "Insert the URL under cursor into `quickurl-list-last-buffer'.
530 TYPE dictates what will be inserted, options are:
531 `url' - Insert the URL as <URL:url>
532 `naked-url' - Insert the URL with no formatting
533 `with-lookup' - Insert \"lookup <URL:url>\"
534 `with-desc' - Insert \"description <URL:url>\"
535 `lookup' - Insert the lookup for that URL"
536 (let ((url (nth (quickurl-list-focused-line) quickurl-urls)))
537 (if url
538 (with-current-buffer quickurl-list-last-buffer
539 (insert
540 (case type
541 ('url (format "<URL:%s>" (quickurl-url-url url)))
542 ('naked-url (quickurl-url-url url))
543 ('with-lookup (format "%s <URL:%s>"
544 (quickurl-url-keyword url)
545 (quickurl-url-url url)))
546 ('with-desc (format "%S <URL:%s>"
547 (quickurl-url-description url)
548 (quickurl-url-url url)))
549 ('lookup (quickurl-url-keyword url)))))
550 (error "No URL details on that line"))
551 url))
552
553 (defmacro quickurl-list-make-inserter (type)
554 "Macro to make a key-response function for use in `quickurl-list-mode-map'."
555 `(defun ,(intern (format "quickurl-list-insert-%S" type)) ()
556 ,(format "Insert the result of calling `quickurl-list-insert' with `%s'." type)
557 (interactive)
558 (when (quickurl-list-insert ',type)
559 (quickurl-list-quit))))
560
561 (quickurl-list-make-inserter url)
562 (quickurl-list-make-inserter naked-url)
563 (quickurl-list-make-inserter with-lookup)
564 (quickurl-list-make-inserter with-desc)
565 (quickurl-list-make-inserter lookup)
566
567 (provide 'quickurl)
568
569 ;;; quickurl.el ends here