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