1 ;;; snap.el --- save/load snapshot of application to/from text
3 ;; Copyright (c) 2003, 2004, 2005, 2006, 2007
4 ;; by HIRAOKA Kazuyuki <khi@users.sourceforge.jp>
5 ;; $Id: snap.el,v 1.40 2007/05/16 14:44:28 hira Exp $
7 ;; This program is free software; you can redistribute it and/or modify
8 ;; it under the terms of the GNU General Public License as published by
9 ;; the Free Software Foundation; either version 1, or (at your option)
12 ;; This program is distributed in the hope that it will be useful,
13 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 ;; GNU General Public License for more details.
17 ;; The GNU General Public License is available by anonymouse ftp from
18 ;; prep.ai.mit.edu in pub/gnu/COPYING. Alternately, you can write to
19 ;; the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139,
26 ;; (1) M-x snap-record on application, e.g. Wanderlust.
27 ;; (2) Yank (C-y) on any buffer, e.g. *scratch* or ~/memo.txt.
28 ;; (3) M-x snap-play on yanked text ==> snapshot (1) is restored.
30 ;; Supported applications:
39 ;; - howm-search ( C-c , g )
42 ;; - Navi2ch (Article buffer)
43 ;; - occur (experimental, using fake cgi-extension)
48 ;; - Wanderlust (Summary buffer)
49 ;; - snap:/// (only message it's version)
51 ;; For unsupported buffers,
52 ;; file name and current position are recorded.
54 ;; Caution for byte-compilation:
56 ;; Byte-compiling this file is not recommended.
57 ;; Some fucntions will be dropped silently if required features are not
58 ;; available at compile time.
59 ;; For example, snap-*:w3m are ignored if the feature w3m is not available.
60 ;; You may want to recompile this file after you set up these features.
64 ;; Format of snapshot string is "snap://MAJOR-MODE/SPELL".
65 ;; Format and meaning of SPELL depend on MAJOR-MODE.
67 ;; snap://wl-summary-mode/+ME/hira/<20031101192305.AFA8C43EDC@hoge.fuga.piyo>
68 ;; is a snapshot string of wl-summary-mode for the spell
69 ;; +ME/hira/<20031101192305.AFA8C43EDC@hoge.fuga.piyo>,
71 ;; message-id <20031101192305.AFA8C43EDC@hoge.fuga.piyo>
72 ;; in the folder +ME/hira.
74 ;; Please define snap-record:MAJOR-MODE and snap-play:MAJOR-MODE
75 ;; if you want to support your favorite application.
76 ;; (snap-record:MAJOR-MODE) returns SPELL string for current snapshot.
77 ;; (snap-play:MAJOR-MODE SPELL) restores snapshot from SPELL string.
79 ;; Abbreviation (experimental):
81 ;; You can add abbreviation rules of snap strings
82 ;; to the variable `snap-abbrev'. See its docstring for details.
84 ;; Fake cgi-extension (experimental):
86 ;; When `snap-record-cgi' is not empty, you can use a
87 ;; fake cgi like "snap://MAJOR-MODE/SPELL??g=110&s=str&q=word&x=",
88 ;; which calls snap-play::g, snap-play::s, snap-play::q and
91 ;; At this experimental stage, format of url is not
92 ;; strict like RFC and not *escaped*. (and I have no idea for doing it
93 ;; :-) An example of the problem is
94 ;; "snap://occur-mode/dired-mode/~/??q=drwx??g=2", but it still works
95 ;; because of longest-match tricks. See `snap-cgi-decode'
97 ;; Supported cgi-functions:
99 ;; s=str search string
103 ;; For some cases, mode-specific commands may be desired.
104 ;; See `snap-play-cgi' and `snap-play:help-mode:' for example.
106 ;; Repair (experimental):
108 ;; When you fail snap-play, you can try M-x snap-repair
109 ;; to repair snapshot text.
110 ;; This can happen, e.g. when you move mails to other folders.
112 ;; You have to write your own 'my-snap-search-mail' function
113 ;; which receives message-id and returns its file name.
114 ;; My version requires namazu and howm.
115 ;; - namazu: full text search engine <http://www.namazu.org/index.html.en>
116 ;; - howm: note-taking tool <http://howm.sourceforge.jp/>
117 ;; (defvar my-namazu-mail-dir (expand-file-name "~/PATH/NMZ/Mail"))
118 ;; (defun my-snap-search-mail (message-id)
119 ;; (let* ((query (format "+message-id:%s" message-id))
120 ;; (args `("-l" "-n" "1" ,query ,my-namazu-mail-dir)))
121 ;; (car (howm-view-call-process "namazu" args))))
123 ;; Replace environment variables in file name.
125 ;; If you like "snap:///${HOME}/hoge" and "snap:///${FOODIR}/bar"
126 ;; instead of "snap:///~/hoge" and "snap:///usr/local/foo/bar", try this.
127 ;; I'm not sure whether there is considerable demand for this feature.
129 ;; (defvar snap-abbreviate-environment-variables '("FOODIR" "HOME"))
130 ;; (defadvice snap-abbreviate-file-name (around env-var (raw-path) activate)
132 ;; (let ((path (expand-file-name raw-path))
133 ;; (rules (mapcar (lambda (var)
134 ;; (let ((val (getenv var)))
136 ;; (cons (concat "^" (regexp-quote val))
137 ;; (format "${%s}" var)))))
138 ;; snap-abbreviate-environment-variables)))
140 ;; (when (and r (string-match (car r) path))
141 ;; (setq ad-return-value
142 ;; (replace-regexp-in-string (car r) (cdr r) path))))
143 ;; (reverse rules))))
145 ;; With bookmark and ffap (experimental):
147 ;; ;; Put this code into your .emacs to enable bookmark+snap feature.
148 ;; (eval-after-load "bookmark"
149 ;; (ad-enable-advice 'bookmark-buffer-file-name 'around 'with-snap)
150 ;; (ad-enable-advice 'bookmark-jump-noselect 'around 'with-snap))
152 ;; ;; Put this code into your .emacs to enable ffap+snap feature.
153 ;; (setq ffap-url-regexp snap-ffap-url-regexp)
154 ;; (setq ffap-url-fetcher snap-ffap-url-fetcher)
158 ;; [2007-08-01] Made most things work for (S)XEmacs (Steve Youngs)
159 ;; [2007-05-16] use snap-define-op instead of require in defun.
160 ;; [2007-05-16] PCVS and Thumbs are supported. (thx > Ma)
161 ;; [2007-05-16] experimental features with bookmark and ffap (thx > Ma)
162 ;; [2007-02-24] byte-compilation is now OK.
163 ;; (thx > Taiki SUGAWARA <buzz.taiki at gmail.com>)
164 ;; [2006-06-15] snap-record:dired-mode also supports environment variables
166 ;; [2006-06-07] snap-play:dired-mode also supports environment variables
168 ;; [2006-05-28] snap-try-decode for atode.el
169 ;; [2006-04-23] replace environment variables in snap-record: (thx > taku)
170 ;; [2006-04-11] environment variables in file path are expanded (thx > taku)
171 ;; [2006-03-25] fix: Obsolete constant name in bibtex. (thx > 20)
172 ;; [2006-03-21] add document on byte-compilation. (thx > 20)
173 ;; [2006-01-31] fix: Drive letter problem in windows. (thx > Touhi)
174 ;; [2005-09-28] cgi for Man-mode. (thx > Ma)
175 ;; [2005-09-27] mode-specific cgi command. (thx > Ma)
176 ;; [2005-07-03] Gnus is supported. (thx > Wi)
177 ;; Variable snap-mode-functions for extension.
178 ;; [2005-05-24] snap-record-string never cause error again.
179 ;; This is necessary for my another tool, atode.el.
180 ;; http://howm.sourceforge.jp/a/atode.el
181 ;; [2005-05-19] BBDB, BibTeX, Shell ,occur, howm-search are supported.
182 ;; fix: `snap-play' and extend fake cgi and `snap-expand-alist'.
183 ;; And set `snap-record-default-format'. (thx > Ma)
184 ;; [2005-03-03] snap-record-string doesn't cause error any more.
185 ;; [2004-11-16] fix: second -> cadr (thx > Toorisugari)
186 ;; [2004-09-11] Emacs-wiki, Navi2ch, w3m, Dired are supported. (thx > Ma)
187 ;; [2004-04-21] fix: Error when action-lock is not available (thx > Nanashi)
188 ;; [2004-04-18] Goto occurrence when it is unique match.
189 ;; [2004-04-10] Help, Bookmark, Man, Info are supported. (thx > Ma)
190 ;; [2004-02-25] action-lock
191 ;; [2004-02-23] fix: Error on CVS latest Wanderlust (thx > hirose31)
192 ;; [2004-01-16] Jump to specified position
193 ;; [2003-11-09] fix: All modes said 'not supported'.
194 ;; [2003-11-08] First upload
195 ;; [2003-11-05] First version
198 ;; - thing-at-point fails to recognize "snap:///file#1: snap:///"
206 (defvar snap-version "$Id: snap.el,v 1.40 2007/05/16 14:44:28 hira Exp $")
207 (defvar snap-prt "snap://")
208 (defvar snap-format (concat snap-prt "%s/%s"))
209 (defvar snap-regexp (concat (regexp-quote snap-prt) "\\([^/\r\n]*\\)/\\(.*\\)"))
210 (defvar snap-mode-pos 1)
211 (defvar snap-spell-pos 2)
212 (defvar snap-root-dir (expand-file-name "/")) ;; "c:/" etc. for windows
213 (defvar snap-record-string-no-error t
214 "For private use by other packages.
215 It indicates that old bug on `snap-record-string' is already fixed.")
216 (defvar snap-spell-format "%s??%s"
217 "Note: You can change this default to \"%s?%s\" like a cgi. But you
218 will face to ploblem; how to deal with
219 \"snap://w3m-mode/http://www.google.com?q=1?q=2\".")
220 (defvar snap-cgi-format "%s=%s")
221 (defvar snap-spell-regexp "\\(.*\\)[?][?]\\([a-z][=].*\\)"
222 "Note: Longest match of first part is important for the case:
223 \"snap://occur-mode/dired-mode/~/??q=drwx??g=2\"")
224 (defvar snap-nocgi-pos 1)
225 (defvar snap-cgi-pos 2)
226 (defvar snap-cgi-separator "&")
227 (defvar snap-record-cgi nil
228 "List of recorded cgi types in `snap-record'")
230 ;;; (setq snap-record-cgi '("g" "s" "q"))
232 (defvar snap-abbrev nil
233 "List of rules on abbreviation for snap string.
234 Each rule is a list of three strings: ABBREV, MODE, and SPELL-HEAD.
235 snap://ABBREV/xxx is expanded as snap://MODE/SPELL-HEADxxx.
238 ;; snap://l/file ==> snap://dired-mode/usr/local/meadow/1.15/lisp/file
239 ;; snap://s/dir ==> snap://shell-mode/~/#dir
241 '((\"l\" \"dired-mode\" \"usr/local/meadow/1.15/lisp/\")
242 (\"s\" \"shell-mode\" \"~/\#\")))
245 (defvar snap-mode-functions nil
246 "List of functions which give the mode string of current buffer
247 instead of the variable `major-mode'.
248 Each function must return nil if it cannot determine the mode, so that
249 decision is passed to the next function.
251 This variable is prepared for applications which does not use
252 their own major-mode, e.g. message buffers in Wanderlust.")
254 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
257 (defun snap-record ()
258 "Convert snapshot of application to string, and put it to kill-ring."
260 (let ((snap (snap-record-string)))
262 (error "This buffer is not supported."))
264 (message "%s" snap)))
267 "Restore snapshot of application from string at point."
269 (let ((snap (thing-at-point 'snap)))
270 ;; avoid (snap-play-string nil)
271 (and snap (snap-play-string snap))))
273 (defun snap-repair ()
275 (let ((snap (thing-at-point 'snap))
276 (beg (match-beginning 0))
278 (let ((repaired (snap-repair-string snap)))
280 (delete-region beg end)
282 (message "Repaired."))))
284 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
287 (defun snap-record-string ()
288 (let ((long (snap-record-string-exact)))
289 (and long (snap-shrink-string long))))
290 (defun snap-play-string (snap)
291 (snap-play-string-exact (snap-expand-string snap)))
293 (defun snap-shrink-string (snap)
294 "String SNAP is shrinked according to rules in `snap-abbrev'.
295 When several rules are applicable, the shortest result is returned."
296 (let ((candidates (mapcar (lambda (rule)
297 (snap-shrink-string-by-rule snap rule))
300 (car (sort candidates (lambda (x y) (< (length x) (length y)))))
303 (defun snap-shrink-string-by-rule (snap rule)
304 (apply (lambda (abbrev mode spell-head)
305 (apply (lambda (o-mode o-spell)
306 (let ((reg (concat "^" (regexp-quote spell-head))))
307 (if (and (string= mode o-mode)
308 (string-match reg o-spell))
309 (snap-encode abbrev (substring o-spell (match-end 0)))
314 (defun snap-expand-string (snap)
315 (apply (lambda (a-mode a-spell)
316 (let ((rule (assoc a-mode snap-abbrev)))
318 (apply (lambda (abbrev mode spell-head)
319 (snap-encode mode (concat spell-head a-spell)))
324 (defun snap-record-string-exact ()
325 "Convert snapshot of application to string.
326 Nil is returned for unsupported buffer."
327 (let* ((mode (snap-get-mode snap-mode-functions))
328 (recorder (or (snap-op 'record mode t)
329 (progn (setq mode "") (snap-op 'record mode))))
330 (spell (funcall recorder)))
332 (snap-encode mode spell
333 (delq nil (mapcar #'snap-record-cgi snap-record-cgi))))))
335 (defun snap-get-mode (functions)
338 (or (funcall (car functions))
339 (snap-get-mode (cdr functions)))))
341 (defun snap-play-string-exact (snap)
342 "Restore snapshot of application from string. "
343 (let* ((x (snap-decode snap snap-record-cgi))
347 (player (snap-op 'play mode)))
348 (funcall player spell)
350 (apply (lambda (op val) (snap-play-cgi op val mode))
354 (defun snap-play-cgi (op val &optional mode)
355 "Find fake cgi command for operation OP and call it with the argument VAL.
356 If MODE is given, snap-play:MODE:OP or snap-play:MODE: are used preferably
357 rather than general snap-play::OP.
359 (snap-play:MODE:OP VAL)
360 (snap-play:MODE: OP VAL)
363 (let ((player-mo (and mode (snap-op 'play (concat mode ":" op) t)))
365 (let ((f (snap-op 'play (concat mode ":"))))
367 ;; elisp is not scheme. sigh...
368 `(lambda (val) (funcall (function ,f)
371 (player-o (snap-op 'play (concat ":" op))))
372 (funcall (or player-mo player-m player-o) val)))
374 (defun snap-play-cgi-on (buffer op val)
375 (snap-do-on buffer (lambda () (snap-play-cgi op val))))
377 (defun snap-do-on (buffer proc)
378 (save-selected-window
379 (select-window (get-buffer-window buffer t))
382 (defun snap-record-cgi (op)
383 (let ((s (funcall (snap-op 'record (concat ":" op)))))
385 (snap-cgi-encode op s)
388 (defun snap-spell-decode (spell)
389 ;; suppose: spell has no-property
391 ;; (snap-spell-decode "body#tag1?g=1&q=2??g=op1&q=?q=&x=#tag2&x")
392 ;; => ("body#tag1?g=1&q=2" ("g" "op1") ("q" "?q") ("x" "#tag2&x"))
393 (if (string-match snap-spell-regexp spell)
394 (cons (match-string snap-nocgi-pos spell)
395 (snap-cgi-decode (match-string snap-cgi-pos spell)))
398 (defun snap-cgi-decode (cgi)
399 ;; (snap-cgi-decode "a=1&b=c&d&e=&f")
400 ;; => '(("a" "1") ("b" "c&d") ("e" "&f"))
401 (let* ((f-regexp (snap-cgi-encode "\\([a-z]\\)" "\\(.*\\)"))
402 (s-regexp (concat "^\\(.*\\)" snap-cgi-separator f-regexp))
403 ;; using longest-match of the first part.
406 (while (string-match s-regexp rest)
407 (setq olist (cons (list (match-string 2 rest) (match-string 3 rest)) olist))
408 (setq rest (match-string 1 rest)))
409 (if (string-match f-regexp rest)
410 (setq olist (cons (list (match-string 1 rest) (match-string 2 rest)) olist))
411 (message "unknown error"))
414 (defun snap-repair-string (snap)
415 (let* ((x (snap-decode snap))
418 (let ((repairer (snap-op 'repair mode)))
419 (snap-encode mode (funcall repairer spell)))))
421 (defun snap-encode (mode spell &optional cgi-list)
424 (format snap-spell-format
426 (mapconcat #'identity cgi-list
427 snap-cgi-separator))))
428 (format snap-format mode spell))
430 (defun snap-spell-encode (spell cgi)
431 (format snap-spell-format spell cgi))
433 (defun snap-cgi-encode (op str)
434 (format snap-cgi-format op str))
436 (defun snap-decode (snap &optional cgi-p)
437 (or (snap-try-decode snap cgi-p)
438 (error "Wrong snapshot format: %s" snap)))
440 (defun snap-try-decode (snap &optional cgi-p)
441 (and (string-match snap-regexp snap)
442 (let ((mode (match-string-no-properties snap-mode-pos snap))
443 (spell (match-string-no-properties snap-spell-pos snap)))
445 (cons mode (snap-spell-decode spell))
446 (list mode spell)))))
448 (defun snap-op (op mode &optional no-err)
449 (let ((f (intern-soft (format "snap-%s:%s" op mode))))
450 (cond ((functionp f) f)
452 (t (error "%s is not supported." mode)))))
454 ;;; for thing-at-point
455 (defun forward-snap (arg)
458 (re-search-forward snap-regexp nil 'move arg)
460 (skip-chars-forward "^ \t\r\n")
462 (if (re-search-backward snap-regexp nil 'move)
463 (skip-chars-backward "^ \t\r\n"))
464 (setq arg (1+ arg))))))
466 ;;; You need your own 'my-snap-search-mail'
467 ;;; which receives message-id and returns its file name.
469 (defalias 'my-snap-search-mail 'ignore))
471 (defun snap-search-mail (message-id)
472 (message "Searching...")
473 (or (my-snap-search-mail message-id)
474 (error "Not found: %s" message-id)))
476 (defun snap-line-number ()
477 (let ((raw (count-lines (point-min) (point))))
478 ;; see (describe-function 'count-lines)
484 (let ((snap-abbrev '(("l" "dired-mode" "usr/meadow/1.15/lisp/")
485 ("s" "shell-mode" "~/#")))
486 (qa '(("snap://l/file" "snap://dired-mode/usr/meadow/1.15/lisp/file")
487 ("snap://s/dir" "snap://shell-mode/~/#dir"))))
489 (apply (lambda (short long)
490 (if (and (string= short (snap-shrink-string long))
491 (string= (snap-expand-string short) long))
493 (error "incorrect snap-abbrev: %s %s" short long)))
497 (defun snap-find-file (path)
498 (find-file (expand-file-name (substitute-in-file-name path)
501 (put 'snap-with-features 'lisp-indent-function 1)
502 (put 'snap-define-op 'lisp-indent-function 2)
503 ;; SXEmacs doesn't have font-lock-add-keywords --SY.
504 (unless (featurep 'sxemacs)
505 (font-lock-add-keywords
507 '(("(\\(snap-with-features\\)\\>" 1 font-lock-keyword-face)
508 ("(\\(snap-define-op\\)\\>[
510 (1 font-lock-keyword-face)
511 (2 font-lock-function-name-face)))))
513 (defmacro snap-with-features (snap-features &rest body)
514 "Check existence of all SNAP-FEATURES and evaluate BODY if ok.
515 This macro also attaches requirements of SNAP-FEATURES
516 inside `eval-when-compile'.
518 In this environment, a macro `snap-define-op' is available.
519 This macro is similar to `defun', but requires SNAP-FEATURES."
521 (mapcar (lambda (feature)
522 (locate-library (symbol-name feature)))
524 ;; `(eval-when-compile
525 ;; (message "Ignore some features which require %s." ',snap-features))
528 ,@(mapcar (lambda (feature)
529 `(require ',feature))
533 (name arg &rest body)
534 (append (list 'defun name arg)
535 (mapcar (lambda (feature)
536 (list 'require (list 'quote feature)))
541 ;; dummy definition for completion
542 (defalias 'snap-define-op 'ignore)
544 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
551 ;;; snap:///~/elisp/snap.el#177:(defun snap-record: ()
553 (defvar snap-record-default-format "%s#%s:%s")
554 ;see also `snap-record:occur-mode'
555 (defun snap-record: ()
556 (let ((raw-path (buffer-file-name)))
559 (let* ((line (snap-line-number))
560 (text (save-excursion
562 (looking-at "^[ \t]*\\(.*\\)")
563 (match-string-no-properties 1)))
564 (path (snap-abbreviate-file-name raw-path)))
565 (format snap-record-default-format path line text)))))
567 (defun snap-abbreviate-file-name (raw-path)
568 (let ((relative-path ;; not snap:////etc but snap:///etc
569 (file-relative-name raw-path snap-root-dir))
570 (abbrev-path ;; not snap:///home/foo but snap:///~foo
571 (abbreviate-file-name raw-path)))
573 (if (< (length relative-path) (length abbrev-path))
577 (defun snap-play: (spell)
579 ((or (null spell) (string= spell ""))
580 (message "snap-version %s" snap-version))
581 ((string-match "\\([^#\r\n]+\\)\\(#\\([0-9]+\\):\\(.*\\)\\)?" spell)
582 (let ((path (match-string-no-properties 1 spell))
583 (positionp (match-string-no-properties 2 spell))
584 (line (match-string-no-properties 3 spell))
585 (text (match-string-no-properties 4 spell)))
586 (snap-find-file path)
588 (snap-play-search: (concat "^[ \t]*" (regexp-quote text) "$")
589 (string-to-number line)))))
591 (error "not supported: %s" spell))))
593 (defun snap-play-search: (regexp line-number)
594 (goto-line line-number)
595 (cond ((looking-at regexp) t)
596 ((snap-occur-p regexp) (snap-occur regexp line-number))
597 (t (message "No match."))))
599 (defun snap-occur-p (regexp)
601 (goto-char (point-min))
602 (re-search-forward regexp nil t)))
604 (defun snap-occur (regexp line-number)
606 (switch-to-buffer "*Occur*") ;; why needed??
607 (let ((hits (snap-looking-at-number)))
610 (snap-occur-goto-occurence)
611 (snap-occur-goto-line line-number))))
613 (defun snap-occur-goto-occurence ()
614 (message "Line number is obsolete.")
615 (occur-mode-goto-occurrence)
619 (defun snap-occur-goto-line (line-number)
620 (while (let* ((n (snap-looking-at-number))
621 (stop (and n (>= n line-number))))
623 (= (forward-line) 0)))
626 (if (not (snap-looking-at-number))
629 (defun snap-looking-at-number ()
630 (and (looking-at "[ \t]*\\([0-9]+\\)")
631 (string-to-number (match-string-no-properties 1))))
634 ;;; snap://wl-summary-mode/+ME/hira/<20031101192305.AFA8C43EDC@hoge.fuga.piyo>
636 (snap-with-features (wl)
637 (snap-define-op snap-record:wl-summary-mode ()
638 (let ((n (wl-summary-message-number)))
640 (let* ((folder wl-summary-buffer-elmo-folder)
641 (fld-name (elmo-folder-name-internal folder))
642 (id (elmo-message-field folder n 'message-id)))
643 (snap-encode:wl-summary-mode fld-name id)))))
645 (snap-define-op snap-play:wl-summary-mode (spell)
646 (let ((prefix-arg 4))
647 (wl prefix-arg)) ;; skip folder checking
648 (let* ((state (snap-decode:wl-summary-mode spell))
649 (fld-name (car state))
651 (summary-buf (wl-summary-get-buffer-create fld-name)))
652 (wl-summary-goto-folder-subr fld-name
653 (wl-summary-get-sync-range
654 (wl-folder-get-elmo-folder fld-name))
656 (wl-summary-jump-to-msg-by-message-id id)
657 (wl-summary-redisplay)))
659 (snap-define-op snap-repair:wl-summary-mode (spell)
660 (let* ((state (snap-decode:wl-summary-mode spell))
662 (found-file (snap-search-mail id))
663 (folder (snap:wl-file-folder found-file)))
665 (error "No folder for %s" found-file))
666 (snap-encode:wl-summary-mode folder id)))
668 (defun snap-encode:wl-summary-mode (folder-name message-id)
669 (concat folder-name "/" message-id))
671 (defun snap-decode:wl-summary-mode (spell)
672 (and (string-match "\\(.*\\)/\\([^/]*\\)" spell)
673 (let ((fld-name (match-string-no-properties 1 spell))
674 (id (match-string-no-properties 2 spell)))
675 (list fld-name id))))
677 (defun snap:wl-file-folder (file)
678 (setq file (file-truename file))
679 (let ((buf (current-buffer)))
681 (goto-char (point-min))
686 (let* ((name (wl-folder-get-entity-from-buffer))
687 (folder (wl-folder-search-entity-by-name
689 wl-folder-entity 'folder))
690 (ef (and folder (wl-folder-get-elmo-folder folder)))
692 (eq (elmo-folder-type-internal ef) 'localdir)
693 (elmo-localdir-folder-directory-internal ef))))
695 (string-match (format "^%s"
697 (file-truename dir)))
702 (switch-to-buffer buf))))
706 ;;; snap://help-mode/f/find-file
707 ;;; snap://help-mode/m/lambda
708 ;;; snap://help-mode/v/buffer-file-name
710 ;; Recording the `m' (macro) variant of the spell is only available
711 ;; on (S)XEmacs because I didn't know how to do it for GNU/Emacs.
712 ;; snap-play should work fine for all emacsen though. --SY.
714 (when (featurep 'xemacs)
717 (snap-with-features (help)
718 (snap-define-op snap-record:help-mode ()
719 (if (featurep 'xemacs)
721 (let ((name (save-excursion
722 (goto-char (point-min))
723 (re-search-forward "^`\\(.*\\)'" (eolp) t)
724 (intern-soft (match-string 1)))))
726 (if (eq 'macro (car-safe (symbol-function name)))
728 (format "f/%s" name))
729 (format "v/%s" name))))
730 (let ((function (car help-xref-stack-item))
731 (variable (car (cdr help-xref-stack-item))))
733 ((equal function 'describe-function) (format "f/%s" variable))
734 ((equal function 'describe-variable) (format "v/%s" variable))
735 (help-xref-stack-item help-xref-stack-item)
738 (snap-define-op snap-play:help-mode (spell)
739 (if (string-match "\\([^/\n \t]+\\)/\\(.+\\)" spell)
740 (let ((function (match-string 1 spell))
741 (variable (match-string 2 spell)))
743 ((or (string-match "^f.*" function)
744 (string-match "^m.*" function)
745 (string-match "describe-function" function))
746 (describe-function (intern variable)))
747 ((or (string-match "^v.*" function)
748 (string-match "describe-variable" function))
749 (describe-variable (intern variable)))
751 (message "Not support this method %s" spell))))
752 (message "I can't all %s" spell)))
755 (defun snap-play:help-mode: (op val)
756 (snap-play-cgi-on "*Help*" op val))
760 ;;; snap://bookmark-bmenu-mode/kuzu
762 (snap-with-features (bookmark)
763 (snap-define-op snap-record:bookmark-bmenu-mode ()
764 (bookmark-bmenu-bookmark))
766 (snap-define-op snap-play:bookmark-bmenu-mode (spell)
769 (bookmark-bmenu-list)
770 (switch-to-buffer "*Bookmark List*"))
771 (bookmark-jump spell)))
775 ;;; snap://Man-mode/printf/3
777 ;;; (S)XEmacs records to these spells, but can play both formats. --SY.
778 ;;; snap://Manual-mode/ls
779 ;;; snap://Manual-mode/printf/3
781 (snap-with-features (man)
782 (defvar snap-man-spacer "/")
784 (defvar *snap-man-mode-cgi* nil "for internal use")
785 (defvar *snap-man-mode-buffer* nil "for internal use")
787 (if (featurep 'xemacs)
790 (snap-define-op snap-record:Manual-mode ()
791 (let ((buf (buffer-name)))
793 ((string-match "^Man: \\(.*\\)(\\([1-8]+\\))$" buf)
794 (concat (match-string 1 buf) snap-man-spacer (match-string 2 buf)))
795 ((string-match "^Man: \\(.*$\\)" buf)
796 (concat (match-string 1 buf)))
798 (error "not support buffer-name of man-mode: %s" buf)))))
800 (snap-define-op snap-play:Manual-mode (spell)
801 (let* ((strs (split-string spell (regexp-quote snap-man-spacer)))
803 (str-sec (mapconcat 'concat (cdr strs) snap-man-spacer))
804 (topic (if (equal str-sec "")
806 (concat str-com "(" str-sec ")"))))
807 ;; `snap-play:Man-mode:' needs this information.
808 (setq *snap-man-mode-buffer* (snap-man-mode-buffer topic))
809 (manual-entry topic)))
811 (snap-define-op snap-play:Man-mode (spell)
812 (let* ((strs (split-string spell (regexp-quote snap-man-spacer)))
814 (str-sec (mapconcat 'concat (cdr strs) snap-man-spacer))
815 (topic (if (equal str-sec "")
817 (concat str-com "(" str-sec ")"))))
818 ;; `snap-play:Man-mode:' needs this information.
819 (setq *snap-man-mode-buffer* (snap-man-mode-buffer topic))
820 (manual-entry topic))))
822 (snap-define-op snap-record:Man-mode ()
823 (let ((buf (buffer-name)))
825 ((string-match "^[*]Man[ \t]+\\([^ \t]+\\)[ \t]+\\([^ \t]+\\)[*]" buf)
826 (concat (match-string 2 buf) snap-man-spacer (match-string 1 buf)))
827 ((string-match "^[*]Man[ \t]+\\([^ \t]+\\)[*]" buf)
828 (concat (match-string 1 buf)))
830 (error "not support buffer-name of man-mode: %s" buf)))))
832 (snap-define-op snap-play:Man-mode (spell)
833 (let* ((strs (split-string spell (regexp-quote snap-man-spacer)))
835 (str-sec (mapconcat 'concat (cdr strs) snap-man-spacer))
836 (topic (if (equal str-sec "")
838 (concat str-com "(" str-sec ")"))))
839 ;; `snap-play:Man-mode:' needs this information.
840 (setq *snap-man-mode-buffer* (snap-man-mode-buffer topic))
844 ;; For fake cgi, we need to adjourn operations
845 ;; because man process is run asynchronously.
847 (defun snap-play:Man-mode: (op val)
848 (if *snap-man-mode-buffer*
849 ;; When corresponding man-buffer already exists, `man' just notifies it.
850 ;; Man-mode-hook is not run in this case.
851 ;; So, we will do our job immediately.
852 (snap-play-cgi-on *snap-man-mode-buffer* op val)
853 (snap-man-mode-adjourn-cgi op val)))
855 (defadvice Man-bgproc-sentinel (around snap-cgi (process msg) activate)
857 (snap-do-on (snap-man-mode-process-buffer process)
858 #'snap-man-mode-play-cgi))
860 (defun snap-man-mode-adjourn-cgi (op val)
861 (add-to-list '*snap-man-mode-cgi* (cons op val)))
863 (defun snap-man-mode-play-cgi (&optional buf)
865 (mapcar (lambda (pair)
866 (snap-play-cgi (car pair) (cdr pair)))
868 (setq *snap-man-mode-cgi* nil)))
870 ;; copied from man.el
872 (defun snap-man-mode-buffer (topic)
873 ;; from `Man-getpage-in-background'
874 (let* ((man-args topic)
875 (bufname (concat "*Man " man-args "*"))
876 (buffer (get-buffer bufname)))
879 (defun snap-man-mode-process-buffer (process)
880 ;; from `Man-bgproc-sentinel'
881 (if (stringp process)
883 (process-buffer process)))
885 ;; ;; I used `Man-mode-hook' instead of defadvice at first.
886 ;; ;; But, "q" operation failed because Man-mode-hook is run
887 ;; ;; inside save-excursion in `Man-bgproc-sentinel'.
888 ;; (add-hook 'Man-mode-hook #'snap-man-mode-play-cgi)
892 ;;; snap://Info-mode/cvs#Tracking sources
894 (snap-with-features (info)
895 (defvar snap-info-spacer "#")
897 (snap-define-op snap-record:Info-mode ()
898 (let ((str-file (if Info-current-file
899 (file-name-nondirectory Info-current-file)
901 (str-node (or Info-current-node "")))
902 (concat str-file snap-info-spacer str-node)))
904 (snap-define-op snap-play:Info-mode (spell)
905 (let* ((strs (split-string spell (regexp-quote snap-info-spacer)))
906 (str-file (or (car strs) "dir"))
907 (str-node (mapconcat 'concat (cdr strs) snap-info-spacer)))
908 (Info-goto-node (concat "(" str-file ")" str-node))))
912 ;;; snap://emacs-wiki-mode/WelcomePage#title
914 (snap-with-features (emacs-wiki)
915 (snap-define-op snap-record:emacs-wiki-mode ()
916 (let ((raw-path (buffer-file-name)))
919 (format "%s" (file-name-nondirectory raw-path)))))
921 (snap-define-op snap-play:emacs-wiki-mode (spell)
922 (emacs-wiki-visit-link spell))
926 ;;; snap://navi2ch-article-mode/pc5.2ch.net/test/read.cgi/tech/1068351911/100-200
927 ;;; snap://navi2ch-article-mode/http://pc5.2ch.net/test/read.cgi/tech/1068351911/150
929 (snap-with-features (navi2ch)
930 (defvar snap-navi2ch-set-offline t)
932 (snap-define-op snap-record:navi2ch-article-mode ()
934 (let* ((n (navi2ch-article-get-current-number))
935 (s (navi2ch-article-to-url navi2ch-article-current-board
936 navi2ch-article-current-article
938 (when (string-match "^http://" s)
939 (setq s (substring s (match-end 0))))
942 (snap-define-op snap-play:navi2ch-article-mode (spell)
943 (when snap-navi2ch-set-offline
944 (setq navi2ch-offline t))
945 (navi2ch-goto-url (if (string-match "^http://" spell)
947 (concat "http://" spell))))
951 ;;; snap://w3m-mode/http://www
953 ;; (snap-with-features (w3m)
954 ;; (snap-define-op snap-record:w3m-mode ()
957 ;; (snap-define-op snap-play:w3m-mode (spell)
962 ;;; snap://dired-mode/~/
964 (snap-with-features (dired)
965 (snap-define-op snap-record:dired-mode ()
966 (snap-abbreviate-file-name dired-directory))
968 (snap-define-op snap-play:dired-mode (spell)
969 (snap-find-file spell))
973 ;;; snap://bbdb-mode/name
975 (snap-with-features (bbdb)
976 (snap-define-op snap-play:bbdb-mode (spell)
979 (snap-define-op snap-record:bbdb-mode ()
980 (let ((bbdb-record (bbdb-current-record)))
981 (car (bbdb-record-net bbdb-record))))
983 (defun snap-play:bbdb-mode: (op val)
989 ;;; snap://bibtex-mode/file#bibtex-key
991 (snap-with-features (bibtex)
992 (defvar snap-bibtex-spacer "#")
993 (snap-define-op snap-play:bibtex-mode (spell)
994 (if (string-match "^\\(.*\\)#\\(.*\\)$" spell)
995 (let ((k (match-string 2 spell)))
996 (find-file (match-string 1 spell))
998 (not (snap-bibtex-search k))
999 (message "No such bibtex-key \"%s\"" k)))
1001 (defun snap-bibtex-search (k)
1002 (let ((regexp (concat "^@.*" k)))
1003 (goto-char (point-max))
1004 (while (and (re-search-backward regexp nil t)
1005 (not (string= k (snap-bibtex-key)))))
1006 (string= k (snap-bibtex-key))))
1007 (defun snap-bibtex-key ()
1008 (save-excursion ;c.f. `bibtex-clean-entry'
1009 (let ((case-fold-search t)
1010 (eob (bibtex-end-of-entry))
1011 (head (cond ((boundp 'bibtex-entry-head) ; new
1013 ((boundp 'bibtex-reference-head) ; old
1014 bibtex-reference-head)
1016 (error "Neither bibtex-entry-head nor bibtex-reference-head is defined.")))))
1017 (bibtex-beginning-of-entry)
1018 (if (re-search-forward head eob t)
1019 (buffer-substring-no-properties
1020 (match-beginning bibtex-key-in-head)
1021 (match-end bibtex-key-in-head))))))
1022 (snap-define-op snap-record:bibtex-mode ()
1023 (let ((f (buffer-file-name))
1024 (k (snap-bibtex-key)))
1026 (concat f snap-bibtex-spacer k)
1031 ;;; snap://shell-mode/~/#pwd
1033 ;;; ToDo directory with # is not allowed!
1035 (snap-with-features (shell)
1036 (defvar snap-shell-spacer "#")
1037 (defvar snap-shell-buffer-name "*shell*snap*")
1039 (snap-define-op snap-record:shell-mode ()
1040 "record now directory and a command now inputed"
1041 (let ((pm (process-mark (get-buffer-process (current-buffer))))
1043 ;; c.f. comint-kill-input
1044 (concat default-directory
1045 (if (> p (marker-position pm))
1046 (concat snap-shell-spacer (buffer-substring-no-properties pm p))))))
1047 (snap-define-op snap-play:shell-mode (spell)
1048 "1. start shell-mode for snap 2. insert a command (without
1050 (string-match "\\([^#\r\n]+\\)#?\\(.*\\)" spell)
1051 (let ((default-directory (match-string-no-properties 1 spell))
1052 (c (or (match-string-no-properties 2 spell) ""))
1054 (if (not (comint-check-proc "*shell*"))
1057 (set-buffer "*shell*")
1058 (setq no (rename-buffer "*shell*" t))
1060 (setq nn (rename-buffer snap-shell-buffer-name t))
1062 (rename-buffer "*shell*" t)
1069 ;;; snap://occur-mode/dired-mode/~/??q=drwx??g=2
1070 ;;; by using "snap://MAJOR-MODE/SPELL??q=word"
1072 (snap-with-features ()
1073 (defvar snap-occur-cgi-string "q")
1074 (snap-define-op snap-record:occur-mode ()
1075 (let* ((b occur-buffer)
1076 (s (car occur-command-arguments))
1077 (snap-record-cgi nil)
1078 (snap-record-default-format "%s")
1079 (x (snap-decode (save-excursion (set-buffer b) (snap-record-string))))
1082 (snap (snap-encode mode (snap-spell-encode spell (snap-cgi-encode snap-occur-cgi-string s)))))
1083 (if (string-match (concat "^" snap-prt) snap)
1084 (substring snap (match-end 0))
1087 (snap-define-op snap-play:occur-mode (spell)
1088 (save-window-excursion
1089 (snap-play-string (concat snap-prt spell)))
1090 (if (get-buffer "*Occur*")
1091 (switch-to-buffer "*Occur*")
1092 (message "maybe failed to match")))
1096 ;;; snap://howm-view-summary-mode/word
1097 ;;; snap://howm-view-contents-mode/word
1098 ; checked on howm-test-050518
1100 (snap-with-features (howm)
1101 (snap-define-op snap-record:howm-view-summary-mode ()
1103 (snap-define-op snap-record:howm-view-contents-mode ()
1105 (snap-define-op snap-play:howm-view-summary-mode (spell)
1106 ;; completion-p is always nil in my case.
1107 (message "howm searching %s ..." spell)
1108 ;; message is needed because howm-search needs long time.
1109 (howm-search spell nil))
1110 (snap-define-op snap-play:howm-view-contents-mode (spell)
1111 (message "howm searching %s ..." spell)
1112 (howm-search spell nil))
1116 ;;; snap://gnus-summary-mode/group/article-number:<20031101.ACDC@hoge.fuga.piyo>
1118 (snap-with-features (gnus gnus-sum)
1119 (snap-define-op snap-record:gnus-summary-mode ()
1120 (snap-encode:gnus-summary-mode
1122 (gnus-summary-article-number)
1123 (mail-header-message-id (gnus-summary-article-header))))
1125 (snap-define-op snap-play:gnus-summary-mode (spell)
1126 (unless (and (fboundp 'gnus-alive-p) (gnus-alive-p)) (gnus))
1127 (require 'gnus-score)
1128 (let* ((state (snap-decode:gnus-summary-mode spell))
1130 (article (cadr state))
1131 (id (car (cddr state)))
1133 ;; cf. gnus-group-quick-select-group
1135 gnus-score-find-score-files-function
1136 gnus-home-score-file
1137 gnus-apply-kill-hook
1138 gnus-summary-expunge-below)
1140 (if (string-match "\\([^+]+\\).*:.+" group)
1141 (match-string 1 group)
1142 (symbol-name (car gnus-select-method))))
1143 ;; disable getting new message
1144 (eval `(let ((,(intern (concat backend "-get-new-mail")) nil))
1145 (gnus-group-read-group 0 t group)))
1147 (gnus-summary-goto-article article nil t)
1148 (string= id (mail-header-message-id (gnus-summary-article-header))))
1149 (gnus-summary-goto-article id nil t))))
1151 (defun snap-encode:gnus-summary-mode (group article id)
1152 (format "%s/%s:%s" group article id))
1154 (defun snap-decode:gnus-summary-mode (spell)
1155 (when (string-match "\\(.*\\)/\\([0-9]+\\):\\([^/]*\\)" spell)
1156 (list (match-string-no-properties 1 spell)
1157 (match-string-no-properties 2 spell)
1158 (match-string-no-properties 3 spell))))
1162 ;;; snap://cvs-mode/~/hoge/
1164 (snap-with-features (pcl-cvs)
1165 (snap-define-op snap-play:cvs-mode (spell)
1166 (cvs-examine spell t))
1167 (snap-define-op snap-record:cvs-mode ()
1168 (abbreviate-file-name default-directory))
1172 ;;; snap://thumbs-mode/~/hoge/
1173 ;;; snap://thumbs-view-image-mode/~/tmp.jpg
1175 (snap-with-features (thumbs)
1176 (snap-define-op snap-record:thumbs-mode ()
1177 ;; only for `thumbs-show-all-from-dir' not `thumbs-dired-show-marked'.
1178 (abbreviate-file-name thumbs-current-dir))
1179 (snap-define-op snap-play:thumbs-mode (spell)
1180 (thumbs-show-all-from-dir spell nil t))
1181 (snap-define-op snap-record:thumbs-view-image-mode ()
1182 (abbreviate-file-name thumbs-current-image-filename))
1183 (snap-define-op snap-play:thumbs-view-image-mode (spell)
1184 (if (file-exists-p spell)
1185 (thumbs-find-image spell)
1186 (message "No such file:%s" spell)))
1189 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1193 ;;;|goto-line |snap:///file??g=110
1194 ;;;|search&move str |snap:///file??s=str
1195 ;;;|occur str |snap:///file??q=str
1196 ;;;|dired-x (file) |snap:///??x=file
1197 ;;;|dired-x (buffer)|snap:///??x=
1198 ;;;|open & dired-x |snap:///file??x=
1199 ;;;|open &dired-x |snap:///dir??x=file
1200 ;;;|find & dired-x |snap:///dir??s=str&x=
1201 ;;;|move & dired-x |snap:///dir??g=10&x=
1203 ;;; ToDo: find and compilation
1205 (defun snap-play-dired-x (file)
1207 (let ((dir (or (file-name-directory file) default-directory))
1208 (filename (file-name-nondirectory file))
1209 (font-lock-global-modes nil))
1212 (goto-char (point-min))
1213 (search-forward-regexp (concat "[ ]" (regexp-quote filename) "$") nil)
1214 (call-interactively 'dired-do-shell-command)
1217 (defun snap-play::x (spell)
1218 "snap-record cgi extension for execute"
1219 (if (or (null spell) (string= "" spell))
1222 (snap-play-dired-x buffer-file-name))
1223 ((eq major-mode 'dired-mode)
1224 (call-interactively 'dired-do-shell-command))
1228 ((or (file-exists-p spell) (eq major-mode 'dired-mode))
1229 (snap-play-dired-x spell))
1231 (snap-play-dired-x buffer-file-name))
1233 (message "error")))))
1234 (defun snap-record::g ()
1235 "snap-record cgi extension for goto-line"
1236 (number-to-string (snap-line-number)))
1237 (defun snap-play::g (spell)
1238 "snap-record cgi extension for goto-line"
1239 (goto-line (string-to-number spell)))
1240 (defun snap-record:: ()
1241 "snap-record cgi extension for default tag"
1242 (number-to-string (snap-line-number)))
1243 (defun snap-play:: (spell)
1244 "snap-record cgi extension for default tag"
1245 (goto-line (string-to-number spell)))
1246 (defun snap-record::s ()
1247 "snap-record cgi extension for search return the string of
1248 kill-ring. (not work. help) "
1250 ;; ((eq last-command 'kill-ring-save)
1251 ;; (remove-text-properties (current-kill 0))
1256 (looking-at "^[ \t]*\\(.*\\)")
1257 (match-string-no-properties 1)))))
1258 (defun snap-play::s (spell)
1259 "snap-play cgi extension for search around point"
1260 (or (search-forward spell nil t)
1261 (progn (goto-char (point-max))
1262 (search-backward spell nil t))
1263 (message "Failed search")))
1264 (defun snap-record::q ()
1265 "snap-record cgi extension for search
1267 return 1. the string of kill-ring. (not yet)
1269 2. the word at cursor."
1271 ;; ((eq last-command 'kill-ring-save)
1272 ;; (remove-text-properties (current-kill 0))
1274 ((provide 'thingatpt)
1275 (or (thing-at-point 'word) (thing-at-point 'symbol)))
1279 (defun snap-play::q (spell)
1280 "snap-play cgi extension for occur"
1283 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1284 ;;; with other tools
1286 ;;; with action-lock.el
1287 ;;; (in howm: <http://howm.sourceforge.jp/>)
1289 (snap-with-features (action-lock)
1290 (defun snap-action-lock (regexp arg-pos &optional hilit-pos)
1291 (action-lock-general #'(lambda (f u)
1292 (call-interactively 'snap-play))
1293 regexp arg-pos hilit-pos t))
1296 (eval-after-load "action-lock"
1297 '(let ((snap-action-lock-rules (list (snap-action-lock snap-regexp 0))))
1298 (setq action-lock-default-rules
1299 (append snap-action-lock-rules action-lock-default-rules))))
1303 (snap-with-features (bookmark)
1304 (defadvice bookmark-buffer-file-name
1305 (around with-snap first () disable)
1306 "Extend it's function for snap protocol"
1308 (unless ad-return-value
1309 (setq ad-return-value (snap-record-string))))
1310 (defadvice bookmark-jump-noselect (around with-snap first (str) disable)
1311 "Extend it's function for snap protocol with the help of
1312 `snap-bookmark-jump-noselect'.
1314 Suppose `bookmark-jump-noselect' has (str) as inputs and
1315 returns (BUFFER . POINT)
1317 (bookmark-maybe-load-default-file)
1318 (let* ((str (ad-get-arg 0))
1319 (url (bookmark-get-filename str)))
1321 ((string-match snap-regexp url)
1322 (setq ad-return-value (snap-bookmark-jump-noselect str)))
1324 (defun snap-bookmark-jump-noselect (str)
1325 (let* ((url (bookmark-get-filename str))
1326 (snap-p (string-match snap-regexp url))
1327 ;; (file (if snap-p url (expand-file-name url)))
1328 (forward-str (bookmark-get-front-context-string str))
1329 (behind-str (bookmark-get-rear-context-string str))
1330 ;; (place (bookmark-get-position str))
1331 ;; (info-node (bookmark-get-info-node str))
1336 (save-window-excursion
1337 (snap-play-string url)
1338 (when (and forward-str
1339 (search-forward forward-str (point-max) t))
1340 (goto-char (match-beginning 0)))
1341 (when (and behind-str
1342 (search-backward behind-str (point-min) t))
1343 (goto-char (match-end 0)))
1344 (setq bookmark-current-bookmark str)
1345 (cons (current-buffer) (point))))
1351 (snap-with-features (ffap)
1352 (defvar snap-ffap-url-regexp
1355 "news\\(post\\)?:\\|mailto:\\|file:" ; no host ok
1357 "\\(ftp\\|https?\\|telnet\\|gopher\\|www\\|wais\\|snap\\)://" ; needs host
1358 "\\)." ; require one more character
1360 (defvar snap-ffap-url-fetcher 'snap-ffap-browse-url)
1361 (defun snap-ffap-browse-url (url &rest args)
1362 "Deal with a snap protocol in addition to the function `browse-url'"
1363 (if (string-match snap-regexp url)
1364 (snap-play-string url)
1365 (browse-url url args)))
1368 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1373 ;;; snap.el ends here.