1 ;;; mlinks.el --- Minor mode making major mode dependent links
3 ;; Author: Lennar Borgman
4 ;; Created: Tue Jan 16 2007
5 (defconst mlinks:version "0.28") ;;Version:
6 ;; Last-Updated: 2010-01-05 Tue
10 ;; Fxeatures that might be required by this library:
12 ;; `appmenu', `cl', `mail-prsvr', `mm-util', `ourcomments-util',
13 ;; `url-expand', `url-methods', `url-parse', `url-util',
16 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
20 ;; This file implements the minor mode `mlinks-mode' that create
21 ;; hyperlinks for different major modes. Such links can be visible or
22 ;; invisible. The meanings of the links are defined per mode.
26 ;; - In in html style modes the links are visible they can mean either
27 ;; open a file for editing, go to an achnor or view the link in a
30 ;; - In emacs lisp mode the links are invisible, but maybe highlighed
31 ;; when point or mouse is on them. (Having them highlighted when
32 ;; point is on them can be a quick way to check that you have
33 ;; spelled a symbol correct.) The meanings of the links in emacs
34 ;; lisp mode are go to definition.
36 ;; Common to links that open a buffer in Emacs is that you can the
37 ;; buffer opened in the same window, the other window or in a new
38 ;; frame. The same key binding is used in all major modes for this.
40 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
44 ;; FIX-ME: url-hexify-string etc
47 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
49 ;; This program is free software; you can redistribute it and/or modify
50 ;; it under the terms of the GNU General Public License as published by
51 ;; the Free Software Foundation; either version 2, or (at your option)
54 ;; This program is distributed in the hope that it will be useful,
55 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
56 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
57 ;; GNU General Public License for more details.
59 ;; You should have received a copy of the GNU General Public License
60 ;; along with this program; see the file COPYING. If not, write to the
61 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
62 ;; Boston, MA 02111-1307, USA.
64 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
68 (eval-when-compile (require 'cl))
69 (eval-when-compile (require 'appmenu nil t))
70 (eval-when-compile (require 'mumamo nil t))
71 (eval-when-compile (require 'ourcomments-util nil t))
77 (defvar mlinks-point-hilighter-overlay nil)
78 (make-variable-buffer-local 'mlinks-point-hilighter-overlay)
79 (put 'mlinks-point-hilighter-overlay 'permanent-local t)
83 "Customization group for `mlinks-mode'."
87 (defvar mlinks-link-face 'mlinks-link-face)
88 (defface mlinks-link-face
89 '((t (:inherit highlight)))
90 "Face normally active links have on them."
93 (defvar mlinks-hyperactive-link-face 'mlinks-hyperactive-link-face)
94 (defface mlinks-hyperactive-link-face
95 '((t (:inherit isearch)))
96 "Face hyper active links have on them."
99 (defvar mlinks-font-lock-face 'mlinks-font-lock-face)
100 (defface mlinks-font-lock-face
102 "Default face for MLinks' links."
107 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
108 ;;; Mode function bindings
110 ;;(customize-option mlinks-mode-functions)
111 (defcustom mlinks-mode-functions
113 ;; For message buffer etc.
115 ((goto mlinks-elisp-goto)
116 (hili mlinks-elisp-hili)
121 ((goto mlinks-elisp-goto)
122 (hili mlinks-elisp-hili)
127 (lisp-interaction-mode
128 ((goto mlinks-elisp-goto)
129 (hili mlinks-elisp-hili)
134 ((goto mlinks-elisp-goto)
135 (hili mlinks-elisp-hili)
140 ((goto mlinks-elisp-goto)
141 (hili mlinks-elisp-hili)
146 ((goto mlinks-elisp-custom-goto)
147 (hili mlinks-elisp-hili)
149 (fontify mlinks-custom-fontify)
153 ((goto mlinks-goto-plain-url)
155 (fontify mlinks-plain-urls-fontify)
160 (fontify mlinks-html-fontify)
161 (goto mlinks-html-style-goto)
166 (fontify mlinks-html-fontify)
167 (goto mlinks-html-style-goto)
172 (fontify mlinks-html-fontify)
173 (goto mlinks-html-style-goto)
178 (fontify mlinks-html-fontify)
179 (goto mlinks-html-style-goto)
183 "Defines MLinks hyperlinks for major modes.
185 ;; Each element in the list is a list with two elements
187 ;; \(MAJOR-MODE SETTINGS)
189 ;; where MAJOR-MODE is the major mode for which the settings SETTINGS should be used.
190 ;; SETTINGS is an association list which can have the following element types
192 ;; \(hili HILIGHT-FUN) ;; Mandatory
193 ;; \(goto GOTO-FUN) ;; Mandatory
194 ;; \(hion HION-BOOL) ;; Optional
195 ;; \(next NEXT-FUN) ;; Optional
196 ;; \(prev PREV-FUN) ;; Optional
199 ;; - HILIGHT-FUN is the function to hilight a link when point is
200 ;; inside the link. This is done when Emacs is idle.
201 ;; - GOTO-FUN is the function to follow the link at point.
202 ;; - HION-BOOL is t or nil depending on if hilighting should be on
204 ;; - NEXT-FUN is the function to go to the next link.
205 ;; - PREV-FUN is the function to go to the previous link."
206 ;; ;;:type '(repeat (alist :key-type symbol :value-type (alist :key-type symbol :value symbol)))
207 :type '(alist :key-type major-mode-function
210 (const :tag "Enable MLinks in this major mode" hion)
211 (const :tag "Mark All Links" mark)
212 (list :tag "Enable" (const :tag "Hilighting" hili) function)
213 (list :tag "Enable" (const :tag "Follow Link" goto) function)
214 (list :tag "Enable" (const :tag "Goto Next Link" next) function)
215 (list :tag "Enable" (const :tag "Goto Previous Link" prev) function)
220 (defun mlinks-get-mode-value (which)
221 (let* ((major major-mode)
222 (mode-rec (assoc major mlinks-mode-functions)))
226 (setq major (get major 'derived-mode-parent))
227 (setq mode-rec (assoc major mlinks-mode-functions))
228 (when mode-rec (throw 'mode-rec nil))))
230 (let* ((mode (car mode-rec))
231 (funs-alist (cadr mode-rec))
232 (funs (assoc which funs-alist)))
238 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
241 ;; (appmenu-dump-keymap mlinks-mode-map)
242 (defvar mlinks-mode-map
243 (let ((m (make-sparse-keymap "mlinks")))
244 (define-key m [(control ?c) ?\r ?\r] 'mlinks-goto)
245 (define-key m [(control ?c) ?\r ?w] 'mlinks-goto-other-window)
246 (define-key m [(control ?c) ?\r ?f] 'mlinks-goto-other-frame)
247 (define-key m [(control ?c) ?\r ?n] 'mlinks-next-saved-position)
248 (define-key m [(control ?c) ?\r ?p] 'mlinks-prev-saved-position)
249 (define-key m [(control ?c) ?\r S-tab] 'mlinks-backward-link)
250 (define-key m [(control ?c) ?\r tab] 'mlinks-forward-link)
251 (define-key m [(control ?c) ?\r ?h] 'mlinks-toggle-hilight)
252 (define-key m [(control ?c) ?\r ?c] 'mlinks-copy-link-text)
256 (define-minor-mode mlinks-mode
257 "Recognizes certain parts of a buffer as hyperlinks.
258 The hyperlinks are created in different ways for different major
259 modes with the help of the functions in the list
260 `mlinks-mode-functions'.
262 The hyperlinks can be hilighted when point is over them. Use
263 `mlinks-toggle-hilight' to toggle this feature for the current
266 All keybindings in this mode are by default done under the prefiĀ§x
271 which is supposed to be a kind of mnemonic for link (alluding to
272 the RET key commonly used in web browser to follow a link).
273 \(Unfortunately this breaks the rules in info node `Key Binding
274 Conventions'.) Below are the key bindings defined by this mode:
278 For some major modes `mlinks-backward-link' and
279 `mlinks-forward-link' will take you to the previous/next link.
280 By default the link moved to will be active, see
281 `mlinks-active-links'.
287 :keymap mlinks-mode-map
292 (mlinks-start-point-hilighter)
293 (mlinks-add-font-lock))
294 (mlinks-stop-point-hilighter)
295 (when mlinks-point-hilighter-overlay
296 (when (overlayp mlinks-point-hilighter-overlay)
297 (delete-overlay mlinks-point-hilighter-overlay))
298 (setq mlinks-point-hilighter-overlay nil))
299 (mlinks-remove-font-lock)))
300 (put 'mlinks-mode 'permanent-local t)
302 (defun mlinks-turn-on-in-buffer ()
303 (let ((hion (unless (and (boundp 'mumamo-set-major-running)
304 mumamo-set-major-running)
305 (mlinks-get-mode-value 'hion))))
306 (when hion (mlinks-mode 1))))
309 (define-globalized-minor-mode mlinks-global-mode mlinks-mode
310 mlinks-turn-on-in-buffer
311 "Turn on `mlink-mode' in all buffer where it is specified.
312 This is specified in `mlinks-mode-functions'."
315 ;; The problem with global minor modes:
316 (when (and mlinks-global-mode
317 (not (boundp 'define-global-minor-mode-bug)))
318 (mlinks-global-mode 1))
320 ;;(define-toggle mlinks-active-links t
321 (define-minor-mode mlinks-active-links
322 "Use quick movement keys on active links if non-nil.
323 When moving to an mlink with `mlinks-forward-link' or
324 `mlinks-backward-link' the link moved to will be in an active
325 state. This is marked with a new color \(the face `isearch').
326 When the new color is shown the following keys are active
328 \\{mlinks-hyperactive-point-hilighter-keymap}
329 Any command cancels this state."
336 (defun mlinks-link-text-prop-range (pos)
337 (let* ((link-here (get-text-property pos 'mlinks-link))
338 (beg (when link-here (previous-single-char-property-change (+ pos 1) 'mlinks-link)))
339 (end (when link-here (next-single-char-property-change (- pos 0) 'mlinks-link))))
343 (defun mlinks-link-range (pos)
344 (or (mlinks-link-text-prop-range pos)
345 (let ((funs-- (mlinks-get-mode-value 'hili)))
348 (run-hook-with-args-until-success 'funs--))))))
350 (defun mlinks-link-at-point ()
352 (mlinks-point-hilighter-1)
353 (when (and mlinks-point-hilighter-overlay
354 (overlay-buffer mlinks-point-hilighter-overlay))
355 (let* ((ovl mlinks-point-hilighter-overlay)
356 (beg (overlay-start ovl))
357 (end (overlay-end ovl)))
358 (buffer-substring-no-properties beg end))))
361 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
362 ;;; At point highligher
364 (defvar mlinks-point-hilighter-timer nil)
366 (defun mlinks-stop-point-hilighter ()
367 (when (timerp mlinks-point-hilighter-timer)
368 (cancel-timer mlinks-point-hilighter-timer)
369 (setq mlinks-point-hilighter-timer nil)))
371 (defun mlinks-start-point-hilighter ()
372 (mlinks-stop-point-hilighter)
373 (setq mlinks-point-hilighter-timer
374 (run-with-idle-timer 0.1 t 'mlinks-point-hilighter)))
376 (defvar mlinks-link-overlay-priority 100)
378 (defun mlinks-make-point-hilighter-overlay (bounds)
379 (unless mlinks-point-hilighter-overlay
380 (setq mlinks-point-hilighter-overlay
381 (make-overlay (car bounds) (cdr bounds)))
382 (overlay-put mlinks-point-hilighter-overlay 'priority mlinks-link-overlay-priority)
383 (overlay-put mlinks-point-hilighter-overlay 'mouse-face 'highlight)
384 (mlinks-set-normal-point-hilight)
387 (defun mlinks-point-hilighter ()
388 "Mark link at point if any.
389 This moves the hilight point overlay to point or deletes it."
390 ;; This runs in a timer, protect it.
392 (let ((inhibit-point-motion-hooks t))
393 (mlinks-point-hilighter-1))
394 (error "mlinks-point-hilighter error: %s" (error-message-string err))))
396 (defun mlinks-point-hilighter-1 ()
398 (let ((bounds-- (mlinks-link-range (point))))
400 (if mlinks-point-hilighter-overlay
401 (move-overlay mlinks-point-hilighter-overlay (car bounds--) (cdr bounds--))
402 (mlinks-make-point-hilighter-overlay bounds--))
403 (when mlinks-point-hilighter-overlay
404 (delete-overlay mlinks-point-hilighter-overlay))))))
406 (defvar mlinks-hyperactive-point-hilighter-keymap
407 (let ((m (make-sparse-keymap "mlinks")))
408 (define-key m [S-tab] 'mlinks-backward-link)
409 (define-key m [tab] 'mlinks-forward-link)
410 (define-key m "\t" 'mlinks-forward-link)
411 (define-key m [?\r] 'mlinks-goto)
412 (define-key m [?w] 'mlinks-goto-other-window)
413 (define-key m [?f] 'mlinks-goto-other-frame)
414 (define-key m [mouse-1] 'mlinks-goto)
415 (set-keymap-parent m mlinks-mode-map)
418 (defvar mlinks-point-hilighter-keymap
419 (let ((m (make-sparse-keymap "mlinks")))
420 (define-key m [mouse-1] 'mlinks-goto)
421 (set-keymap-parent m mlinks-mode-map)
424 (defun mlinks-point-hilighter-pre-command ()
426 (unless (let ((map (overlay-get mlinks-point-hilighter-overlay 'keymap)))
427 (where-is-internal this-command
430 (mlinks-set-normal-point-hilight)
431 (unless mlinks-point-hilighter-timer
432 (delete-overlay mlinks-point-hilighter-overlay)))
433 (error (message "mlinks-point-hilighter-pre-command: %s" err))))
434 (put 'mlinks-point-hilighter-pre-command 'permanent-local t)
436 (defun mlinks-set-hyperactive-point-hilight ()
437 "Make link hyper active, ie add some special key binding.
438 Used after jumping specifically to a link. The idea is that the
439 user may want to easily jump between links in this state."
440 (add-hook 'pre-command-hook 'mlinks-point-hilighter-pre-command nil t)
441 (mlinks-point-hilighter)
442 (overlay-put mlinks-point-hilighter-overlay 'face mlinks-hyperactive-link-face)
443 (overlay-put mlinks-point-hilighter-overlay 'keymap mlinks-hyperactive-point-hilighter-keymap))
445 (defun mlinks-set-normal-point-hilight ()
446 "Make link normally active as if you happened to be on it."
447 (remove-hook 'pre-command-hook 'mlinks-point-hilighter-pre-command t)
448 (mlinks-point-hilighter)
449 (overlay-put mlinks-point-hilighter-overlay 'face mlinks-link-face)
450 (overlay-put mlinks-point-hilighter-overlay 'keymap mlinks-point-hilighter-keymap))
452 (defun mlinks-set-point-hilight-after-jump-to ()
453 "Set hilight style after jump to link."
454 (if mlinks-active-links
455 (mlinks-set-hyperactive-point-hilight)
456 (mlinks-set-normal-point-hilight)))
460 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
463 (defvar mlinks-places nil)
464 (make-variable-buffer-local 'mlinks-placesn)
465 (put 'mlinks-places 'permanent-local t)
467 (defvar mlinks-places-n 0)
468 (make-variable-buffer-local 'mlinks-places-n)
469 (put 'mlinks-places-n 'permanent-local t)
471 (defun mlinks-has-links ()
472 (or (mlinks-get-mode-value 'fontify)
473 (when (and (boundp 'mumamo-multi-major-mode) mumamo-multi-major-mode)
474 ;; Fix-me: just assume multi major has it... Need a list of
475 ;; major modes. There is no way to get such a list for the
476 ;; multi major mode (since you can't know what the chunk
477 ;; functions will return. However you can get a list of
478 ;; current chunks major mode.
482 (defun mlinks-backward-link ()
483 "Go to previous `mlinks-mode' link in buffer."
485 (if (not (mlinks-has-links))
486 (message "There is no way to go to previous link for this major mode")
487 (let ((res (mlinks-prev-link)))
491 (mlinks-set-point-hilight-after-jump-to))
492 (message "No previous link found")))))
494 (defun mlinks-forward-link ()
495 "Go to next `mlinks-mode' link in buffer."
497 (if (not (mlinks-has-links))
498 (message "There is no way to go to next link for this major mode")
499 (let ((res (mlinks-next-link)))
503 (mlinks-set-point-hilight-after-jump-to))
504 (message "No next link found")))))
507 (defun mlinks-goto ()
508 "Follow `mlinks-mode' link at current point.
509 Save the current position so that they can be move to again by
510 `mlinks-prev-saved-position' and `mlinks-next-saved-position'.
512 Return non-nil if link was followed, otherewise nil."
516 (defun mlinks-goto-other-window ()
517 "Like `mlinks-goto' but opens in other window.
518 Uses `switch-to-buffer-other-window'."
520 (mlinks-goto-1 'other-window))
522 (defun mlinks-goto-other-frame ()
523 "Like `mlinks-goto' but opens in other frame.
524 Uses `switch-to-buffer-other-frame'."
526 (mlinks-goto-1 'other-frame))
528 (defun mlinks-goto-1(where)
530 (let* ((funs (mlinks-get-mode-value 'goto))
532 (mlinks-temp-buffer-where where)
533 (res (run-hook-with-args-until-success 'funs)))
536 (message "Don't know how to follow this MLink link")
538 (unless (= old (point-marker))
539 (let* ((prev (car mlinks-places)))
541 ;;(not (markerp prev))
542 (not (marker-buffer prev))
544 (setq mlinks-places (cons old mlinks-places))
545 (setq mlinks-places-n (length mlinks-places))))))))
548 (defun mlinks-prev-saved-position ()
549 "Go to previous position saved by `mlinks-goto'."
551 (unless (mlinks-goto-n (1- mlinks-places-n))
552 (message "No previous MLink position")))
554 (defun mlinks-next-saved-position ()
555 "Go to next position saved by `mlinks-goto'."
557 (unless (mlinks-goto-n (1+ mlinks-places-n))
558 (message "No next MLink position")))
560 (defun mlinks-goto-n (to)
561 (if (not mlinks-places)
562 (message "No saved MLinks positions")
564 (maxp (length mlinks-places)))
568 (message "Going to first MLinks position"))
572 (message "Going to last MLinks position"))))
573 (setq mlinks-places-n to)
574 (let ((n (- maxp to))
575 (places mlinks-places)
580 (setq places (cdr places))
582 (setq place (car places))
583 (mlinks-switch-to-buffer (marker-buffer place))
584 (goto-char place)))))
586 (defvar mlinks-temp-buffer-where nil)
587 (defun mlinks-switch-to-buffer (buffer)
588 (mlinks-switch-to-buffer-1 buffer mlinks-temp-buffer-where))
590 (defun mlinks-switch-to-buffer-1(buffer where)
593 (switch-to-buffer buffer))
594 ((eq where 'other-window)
595 (switch-to-buffer-other-window buffer))
596 ((eq where 'other-frame)
597 (switch-to-buffer-other-frame buffer))
599 (error "Invalid argument, where=%s" where))))
602 (defun mlinks-custom (var)
603 (customize-option var)
608 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
611 (defun mlinks-appmenu ()
613 ;; Fix-me: reverse the list
614 (let ((link-val (mlinks-link-at-point))
615 (map (make-sparse-keymap "mlinks"))
617 (when (mlinks-get-mode-value 'prev)
618 (define-key map [mlinks-next-link]
619 (list 'menu-item "Next Link" 'mlinks-forward-link)))
620 (when (mlinks-get-mode-value 'next)
621 (define-key map [mlinks-prev-link]
622 (list 'menu-item "Previous Link" 'mlinks-backward-link)))
624 (let* ((possible (when (member major-mode '(html-mode nxhtml-mode nxml-mode sqml-mode text-mode))
625 (mlinks-html-possible-href-actions link-val)))
626 (mailto (assoc 'mailto possible))
627 (view-web (assoc 'view-web possible))
628 (view-web-base (assoc 'view-web-base possible))
629 (edit (assoc 'edit possible))
631 (anchor (nth 2 edit))
635 (when (> (length map) num)
636 (define-key map [mlinks-href-sep] (list 'menu-item "--")))
637 (setq num (length map))
639 (define-key map [mlinks-href-view-web]
640 (list 'menu-item "Browse Link Web Url"
641 `(lambda () (interactive)
642 (browse-url ,link-val)))))
644 (define-key map [mlinks-href-view-web-based]
645 (list 'menu-item "Browse Link Web Url (base URL found)"
646 `(lambda () (interactive)
647 (browse-url (cdr ,view-web-base))))))
649 (define-key map [mlinks-href-mail]
650 (list 'menu-item (concat "&Mail to " (substring link-val 7))
651 `(lambda () (interactive)
652 (mlinks-html-mail-to ,link-val)))))
654 (when (and (file-exists-p file)
656 (assoc 'upload possible))
657 (let ((abs-file (expand-file-name file)))
658 (define-key map [mlinks-href-upload]
659 (list 'menu-item "Upload Linked File"
660 `(lambda () (interactive)
661 (html-upl-upload-file ,abs-file))))))
662 (when (and (file-exists-p file)
664 (assoc 'edit-gimp possible))
665 (let ((abs-file (expand-file-name file)))
666 (define-key map [mlinks-href-edit-gimp]
667 (list 'menu-item "Edit Linked File with GIMP"
668 `(lambda () (interactive)
669 (gimpedit-edit-file ,abs-file))))))
670 (when (and (file-exists-p file)
671 (assoc 'view-local possible))
672 (let ((url (concat "file:///" (expand-file-name file))))
674 (let ((url-anchor (concat url "#" anchor)))
675 (define-key map [mlinks-href-view-file-at]
676 (list 'menu-item (concat "Browse Linked File URL at #" anchor)
677 `(lambda () (interactive)
678 (browse-url ,url-anchor))))))
679 (define-key map [mlinks-href-view-file]
680 (list 'menu-item "&Browse Linked File URL"
681 `(lambda () (interactive)
682 (browse-url ,url))))))
683 (when (> (length map) num)
684 (define-key map [mlinks-href-sep-2] (list 'menu-item "--")))
685 (setq num (length map))
686 (unless (equal file (buffer-file-name))
687 (define-key map [mlinks-href-edit]
688 (list 'menu-item "&Open Linked File"
689 `(lambda () (interactive) (mlinks-goto))))
690 (define-key map [mlinks-href-edit-window]
691 (list 'menu-item "&Open Linked File in Other Window"
692 `(lambda () (interactive) (mlinks-goto-other-window))))
693 (define-key map [mlinks-href-edit-frame]
694 (list 'menu-item "&Open Linked File in New Frame"
695 `(lambda () (interactive) (mlinks-goto-other-frame))))
697 (when (and (file-exists-p file) anchor)
698 (define-key map [mlinks-href-edit-at]
699 (list 'menu-item (concat "Open Linked File &at #" anchor)
700 `(lambda () (interactive)
703 (when (> (length map) num)
704 (define-key map [mlinks-href-sep-1] (list 'menu-item "--")))
705 (setq num (length map))
707 (define-key map [mlinks-href-copy-link]
708 (list 'menu-item "&Copy Link Text"
709 'mlinks-copy-link-text)))))
710 (when (> (length map) 2)
713 (defun mlinks-add-appmenu ()
714 "Add entries for MLinks to AppMenu."
715 (when (featurep 'appmenu)
716 (appmenu-add 'mlinks 100 'mlinks-mode "Current MLink" 'mlinks-appmenu)))
718 (defun mlinks-copy-link-text ()
719 "Copy text of `mlinks-mode' link at point to clipboard."
721 (mlinks-point-hilighter)
722 (let ((ovl mlinks-point-hilighter-overlay))
727 (overlay-buffer ovl))
728 (<= (overlay-start ovl)
730 (>= (overlay-end ovl)
732 (let* ((beg (overlay-start ovl))
733 (end (overlay-end ovl))
734 (str (buffer-substring beg end)))
735 (copy-region-as-kill beg end)
736 (message "Copied %d chars to clipboard" (length str)))
737 (message "No link here to copy"))))
743 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
744 ;;;;;;;;;;;;;;;;;;;;;; text-mode etc ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
745 (defvar mlinks-plain-urls-regexp
746 (rx-to-string `(or (submatch (optional "mailto:")
748 ;;"[a-z0-9$%(*-=?[_][^<>\")!;:,{}]*"
749 "[a-z0-9$%(*=?[_-][^<>\")!;:,{}]*"
751 "\\(?:[a-z0-9\-]+\.\\)+[a-z0-9]\\{2,4\\}")))
752 (submatch (or (regexp "https?://")
754 (1+ (any ,url-get-url-filename-chars))
758 (defun mlinks-plain-urls-fontify (bound)
759 (mlinks-fontify bound mlinks-plain-urls-regexp 0))
761 (defun mlinks-goto-plain-url ()
762 (let* ((range (mlinks-link-range (point)))
763 (link (when range (buffer-substring-no-properties (car range) (cdr range)))))
764 ;;(mlinks-html-href-act-on link)
765 (when (= 0 (string-match mlinks-plain-urls-regexp link))
766 (let ((which (if (match-end 1) 1 2)))
769 (mlinks-html-mail-to link)
776 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
777 ;;;;;;;;;;;;;;;;;;;;;; nxhtml-mode ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
779 (defun mlinks-html-style-goto ()
780 (mlinks-html-style-mode-fun t))
782 (defvar mlinks-html-link-regexp
783 ;; This value takes care of nxhtml-strval-mode (and is therefore a little bit incorrect ...)
784 ;;"\\(?:^\\|[[:space:]]\\)\\(?:href\\|src\\)[[:space:]]*=[[:space:]]*\"\\([^<Ā«\"]*\\)\""
794 (0+ (not (any "\""))))
798 (0+ (not (any "\'"))))
801 (defun mlinks-html-style-mode-fun (goto)
807 (when (< 0 (skip-chars-forward "^\"'" (line-end-position)))
811 mlinks-html-link-regexp
812 (line-beginning-position -1))
813 (let ((which (if (match-beginning 1) 1 2)))
814 (setq start (1+ (match-beginning which)))
815 (setq end (1- (match-end which))))
816 (setq bounds (cons start end))))))
820 (let ((href-val (buffer-substring-no-properties start end)))
821 (mlinks-html-href-act-on href-val))
824 (defun mlink-check-file-to-edit (file)
825 (assert (file-name-absolute-p file))
826 (let ((file-dir (file-name-directory file)))
827 (unless (file-directory-p file-dir)
828 (if (file-directory-p (file-name-directory file))
829 (if (yes-or-no-p (format "Directory %s does not exist. Create it? " file-dir))
830 (make-directory file-dir)
832 (if (yes-or-no-p (format "Directory %s and its parent does not exist. Create them? " file-dir))
833 (make-directory file-dir t)
837 (defun mlinks-html-edit-at (file &optional anchor)
838 (let ((abs-file (if (file-name-absolute-p file)
840 (expand-file-name file))))
841 (if (or (file-directory-p abs-file)
843 (file-name-as-directory abs-file)))
844 (if (file-directory-p abs-file)
845 (when (y-or-n-p (format "Do you want to edit the directory %s? : " abs-file))
847 (message "Can't find directory %s" abs-file))
848 (when (mlink-check-file-to-edit abs-file)
849 (let ((b (find-file-noselect abs-file)))
850 (mlinks-switch-to-buffer b))
853 (anchor-regexp (concat "\\(?:id\\|name\\)[[:space:]]*=[[:space:]]*\"" anchor "\"")))
854 (goto-char (point-min))
855 (if (search-forward-regexp anchor-regexp nil t)
857 (message "Anchor \"%s\" not found" anchor)
858 (goto-char here))))))))
860 (defun mlinks-html-mail-to (addr)
863 (defun mlinks-html-href-act-on (href-val)
865 (let* ((possible (mlinks-html-possible-href-actions href-val))
866 (edit (assoc 'edit possible))
868 (anchor (nth 2 edit))
871 (mlinks-html-edit-at file anchor)
873 ((assoc 'mailto possible)
874 (when (y-or-n-p "This is a mail address. Do you want to send a message to this mail address? ")
875 (mlinks-html-mail-to href-val)))
876 ((assoc 'view-web possible)
877 (when (y-or-n-p "Can't edit this URL, it is on the web. View the URL in your web browser? ")
878 (browse-url href-val)))
879 ((assoc 'view-web-base possible)
880 (when (y-or-n-p "Can't edit, based URL is to the web. View resulting URL in your web browser? ")
881 (browse-url (cdr (assoc 'view-web-base possible)))))
883 (message "Do not know how to handle this URL"))
885 (message "No value for href attribute")))
887 (defun mlinks-html-possible-href-actions (link)
888 (let ((urlobj (url-generic-parse-url link))
891 (cond ((member (url-type urlobj) '("http" "https"))
892 (add-to-list 'possible (cons 'view-web link)))
893 ((member (url-type urlobj) '("mailto"))
894 (add-to-list 'possible (cons 'mailto link)))
896 (message "Do not know how to handle this URL"))
899 (let ((base-href (mlinks-html-find-base-href)))
901 (let ((baseobj (url-generic-parse-url base-href)))
903 (cond ((member (url-type baseobj) '("http" "https"))
904 (add-to-list 'possible (cons 'view-web-base (url-expand-file-name link base-href))))
906 (message "Do not know how to handle this URL"))
909 (let* ((full (split-string (url-filename urlobj) "#"))
911 (anchor (nth 1 full))
913 (when (equal file "")
914 (setq file (buffer-file-name)))
916 ;; We know at this point it is not a http url
917 (setq file (expand-file-name file base-href)))
918 (let ((ext (downcase (file-name-extension file))))
919 (when (member ext '("htm" "html"))
920 (add-to-list 'possible (cons 'view-local (list file anchor))))
921 (when (and (featurep 'gimpedit)
922 (member ext '("gif" "png" "jpg" "jpeg")))
923 (add-to-list 'possible (cons 'edit-gimp (list file anchor)))))
924 (when (featurep 'html-upl)
925 (add-to-list 'possible (cons 'upload (list file anchor))))
926 (add-to-list 'possible (cons 'edit (list file anchor)))))))
929 (defun mlinks-html-find-base-href ()
930 "Return base href found in the current file."
933 (goto-char (point-min))
934 (while (and (not base-href)
935 (search-forward-regexp "<!--[^!]*-->\\|<base[[:space:]]" nil t))
936 (when (equal " " (char-to-string (char-before)))
938 (when (looking-at "<base [^>]*href *= *\"\\(.*?\\)\"")
939 (setq base-href (match-string-no-properties 1))))))
942 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
943 ;;;;;;;;;;;;;;;;;;;;;; Custom-mode ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
944 (defun mlinks-elisp-custom-goto ()
945 (mlinks-elisp-mode-fun 'custom))
947 (defvar mlinks-custom-link-regexp
950 (1+ (not (any "'"))))
953 (defun mlinks-custom-fontify (bound)
954 (mlinks-fontify bound mlinks-custom-link-regexp 0))
957 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
958 ;;;;;;;;;;;;;;;;;;;;;; emacs-lisp-mode ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
959 (defun mlinks-elisp-goto ()
960 (mlinks-elisp-mode-fun 'source))
962 (defun mlinks-elisp-hili ()
963 (mlinks-elisp-mode-fun nil))
965 (defun mlinks-elisp-mode-fun (goto)
966 (let ((symbol-name (thing-at-point 'symbol)))
968 (let ((bounds-- (bounds-of-thing-at-point 'symbol))
971 (goto-char (cdr bounds--))
972 (looking-back (concat "(\\(?:require\\|featurep\\)\s+'" symbol-name)
973 (line-beginning-position)))
975 (setq ret-- bounds--)
977 (mlinks-elisp-mode-require symbol-name)))
978 (when (mlinks-elisp-mode-symbol symbol-name goto)
979 (setq ret-- bounds--)))
982 (defun mlinks-elisp-function (symbol)
983 "Go to an elisp function."
984 (interactive "aElisp function: ")
985 (mlinks-elisp-mode-symbol (symbol-name symbol) 'source))
987 (defun mlinks-elisp-mode-symbol (symbol-name-- goto--)
988 ;; Fix-me: use uninterned variables (see mail from Miles)
989 ;; Make these names a bit strange because they are boundp at the time of checking:
990 (let ((symbol-- (intern-soft symbol-name--))
992 (when (and symbol-- (boundp symbol--))
993 (add-to-list 'defs-- 'variable))
994 (when (fboundp symbol--)
995 (add-to-list 'defs-- 'function))
996 (when (facep symbol--)
997 (add-to-list 'defs-- 'face))
998 ;; Avoid some fails hits
1001 bounds-- funs-- ret--
1002 symbol-- defs-- symbol-name-- goto--))
1011 (message "Could not find definition of '%s" symbol-name--)
1014 ((eq goto-- 'source)
1015 '(nil defvar defface))
1016 ((eq goto-- 'custom)
1019 (error "Bad goto-- value: %s" goto--))))
1021 (add-to-list 'defs-places
1025 (let* ((bp (find-definition-noselect symbol-- type))
1029 (with-current-buffer b
1032 (setq bp (find-definition-noselect symbol-- type)))))
1035 ;;(lwarn '(mlinks) :error "%s" (error-message-string err))
1038 ((eq (car err) 'search-failed))
1039 ((and (eq (car err) 'error)
1040 (string= (error-message-string err)
1041 (format "Don't know where `%s' is defined" symbol--))))
1043 (message "%s: %s" (car err) (error-message-string err))))))))
1044 (if (= 1 (length defs-places))
1045 (setq def (car defs-places))
1048 (dolist (d defs-places)
1051 (unless (equal lnk (cdr d))
1054 (setq def (car defs-places))
1055 (let* ((alts (mapcar (lambda (elt)
1056 (let ((type (car elt))
1068 (stralts (mapcar (lambda (elt)
1071 (completion-ignore-case t)
1072 (stralt (completing-read "Type: " stralts nil t))
1073 (alt (assoc stralt alts)))
1074 (setq def (cdr alt))))))
1077 ((eq goto-- 'source)
1078 ;; Be sure to go to the real sources from CVS:
1079 (let* ((buf (car (cdr def)))
1080 ;; Avoid going to source
1081 ;;(file (find-source-lisp-file (with-current-buffer buf buffer-file-name)) )
1082 (file (with-current-buffer buf buffer-file-name))
1083 (orig-buf (find-file-noselect file)))
1084 (mlinks-switch-to-buffer orig-buf)
1085 (let ((p (cdr (cdr def))))
1086 ;; Fix-me: Move this test to a more general place.
1087 (if (or (< p (point-min))
1089 ;; Check for cloned indirect buffers.
1093 (dolist (indirect-buf (buffer-list))
1094 ;;(message "base-buffer=%s, orig-buf=%s, eq => %s" (buffer-base-buffer indirect-buf) orig-buf (eq (buffer-base-buffer indirect-buf) orig-buf))
1095 (when (eq (buffer-base-buffer indirect-buf) orig-buf)
1096 (with-current-buffer indirect-buf
1097 ;;(message "indirect-buf=%s" indirect-buf)
1098 (unless (or (< p (point-min))
1100 ;;(message "switching")
1101 ;;(mlinks-switch-to-buffer indirect-buf)
1102 (message "mlinks: Switching to indirect buffer because of narrowing")
1103 (throw 'view-in-buf indirect-buf)
1107 (mlinks-switch-to-buffer orig-buf))
1108 ;;(message "cb=%s" (current-buffer))
1109 (if (or (< p (point-min))
1111 (when (y-or-n-p (format "%s is invisible because of narrowing. Widen? " symbol--))
1116 ((eq goto-- 'custom)
1117 (mlinks-custom symbol--))
1119 (error "Back goto-- value again: %s" goto--)))))))))
1121 (defun mlinks-elisp-mode-require (module)
1122 (let ((where mlinks-temp-buffer-where))
1125 (find-library module))
1126 ((eq where 'other-window)
1128 (find-library module))
1129 ((eq where 'other-frame)
1130 (make-frame-command)
1131 (find-library module))
1133 (error "Invalid argument, where=%s" where)))))
1137 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1138 ;;;;;;;;;;;;; Helpers when adopting for modes ;;;;;;;;;;;;;;;;;
1140 ;;; Save this, do not delete this comment:
1142 ;; (defun mlinks-hit-test ()
1143 ;; "Just a helper function for adding support for new modes."
1145 ;; (s0 (if (match-string 0) (match-string 0) ""))
1146 ;; (s1 (if (match-string 1) (match-string 1) ""))
1147 ;; (s2 (if (match-string 2) (match-string 2) ""))
1148 ;; (s3 (if (match-string 3) (match-string 3) ""))
1150 ;; (message "match0=%s, match1=%s, match2=%s, match3=%s" s0 s1 s2 s3)))
1152 ;; (defun mlinks-handle-reg-fun-list (reg-fun-list)
1153 ;; "Just a helper function."
1161 ;; (dolist (rh reg-fun-list)
1162 ;; (message "rh=%s" rh);(sit-for 2)
1164 ;; (setq regexp (car rh))
1165 ;; (setq hitfun (cadr rh))
1166 ;; (message "regexp=%s, hitfun=%s" regexp hitfun);(sit-for 1)
1167 ;; (when (and (save-match-data
1168 ;; (setq m (re-search-backward regexp (line-beginning-position) t))
1169 ;; (> p (match-beginning 0))))
1171 ;; (setq b (match-beginning 0))
1172 ;; (setq e (match-end 0))
1174 ;; (if (not (and b e
1177 ;; (message "MLinks Mode did not find any link here")
1179 ;; (if (not (looking-at regexp))
1180 ;; (error "Internal error, regexp %s, no match looking-at" regexp)
1181 ;; (let ((last (car mlinks-places))
1182 ;; (m (make-marker)))
1183 ;; (set-marker m (line-beginning-position))
1184 ;; (when (or (not last)
1186 ;; (setq mlinks-places (cons m mlinks-places))))
1187 ;; (funcall hitfun))
1192 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1195 (defvar mlinks-link-update-pos-max nil)
1196 (make-variable-buffer-local 'mlinks-link-update-pos-max)
1197 (put 'mlinks-link-update-pos-max 'permanent-local t)
1199 (defun mlinks-remove-font-lock ()
1200 "Remove info from font-lock."
1201 (when (mlinks-want-font-locking)
1202 (mlink-font-lock nil)))
1204 (defun mlinks-add-font-lock ()
1205 "Add info to font-lock."
1206 (when (mlinks-want-font-locking)
1207 (mlink-font-lock t)))
1209 (defun mlinks-want-font-locking ()
1210 (or (mlinks-get-mode-value 'fontify)
1211 (mlinks-get-mode-value 'next-mark)))
1215 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1216 ;;; Font Lock integration
1218 (defun mlink-font-lock (on)
1219 (let* ((add-or-remove (if on 'font-lock-add-keywords 'font-lock-remove-keywords))
1220 (fontify-fun (car (mlinks-get-mode-value 'fontify)))
1221 (args (list nil `(( ,fontify-fun ( 0 mlinks-font-lock-face t ))))))
1223 ;; Note: Had a lot of trouble with this which I modelled first
1224 ;; after dlink. Using hi-lock as a model made it work with
1227 ;; Next arg, HOW, is needed to get it to work with mumamo. This
1228 ;; adds it last, like hi-lock.
1229 (when on (setq args (append args (list t))))
1230 (apply add-or-remove args)
1232 (font-lock-mode 1))))
1234 (defun mlinks-html-fontify (bound)
1235 (mlinks-fontify bound mlinks-html-link-regexp 1))
1237 (defun mlinks-fontify (bound regexp border)
1238 (let ((start (point))
1245 ;; Note: we shouldnot use save-match-data here. Instead
1246 ;; set-match-data is called below!
1247 (if (not (re-search-forward regexp bound t))
1248 (setq end-start bound)
1250 (setq end-start (- (point) 2))
1251 (let* ((which (if (match-beginning 1) 1 2))
1252 (beg (+ (match-beginning which) border))
1253 (end (- (match-end which) border)))
1254 (put-text-property beg end 'mlinks-link t)
1255 (set-match-data (list (copy-marker end) (copy-marker beg)))))
1258 (while (and (> 100 (setq wn (1+ wn)))
1259 (setq next-stop (next-single-char-property-change stop 'mlinks-link nil end-start))
1260 (/= next-stop stop))
1261 (setq stop next-stop)
1262 (if (get-text-property stop 'mlinks-link)
1265 (remove-list-of-text-properties old-beg stop '(mlinks-link 'mouse-face)))))
1268 (defun mlinks-next-link ()
1269 "Find next link, fontify as necessary."
1270 (let* ((here (point))
1272 (fontified-here (get-text-property (max (point-min) (1- prev-pos)) 'fontified))
1273 (fontified-to (next-single-char-property-change prev-pos 'fontified))
1274 (pos (next-single-char-property-change prev-pos 'mlinks-link nil
1275 (or fontified-to (point-max))))
1276 (fontified-all (and fontified-here (not fontified-to)))
1279 (while (not (or ready
1284 (unless (get-text-property pos 'mlinks-link)
1287 (setq pos (next-single-char-property-change prev-pos 'mlinks-link nil
1288 (or fontified-to (point-max)))))
1290 (setq ready (get-text-property pos 'mlinks-link))
1292 (unless ready (setq pos nil))))
1293 (unless (or fontified-all fontified-to)
1294 (if (get-text-property prev-pos 'fontified)
1296 (not (setq fontified-to
1297 (next-single-char-property-change prev-pos 'fontified))))
1298 (setq fontified-to ( or (previous-single-char-property-change prev-pos 'fontified)
1300 (setq next-fontified-to (min (+ fontified-to 5000)
1302 (mumamo-with-buffer-prepared-for-jit-lock
1304 (put-text-property fontified-to next-fontified-to 'fontified t)
1305 (font-lock-fontify-region fontified-to next-fontified-to)))
1306 (setq fontified-to (next-single-char-property-change (1- next-fontified-to)
1308 (setq fontified-all (not fontified-to))
1309 (setq pos (next-single-char-property-change prev-pos 'mlinks-link nil
1310 (or fontified-to (point-max))))))
1311 (when ready prev-pos)))
1313 (defun mlinks-prev-link ()
1314 "Find previous link, fontify as necessary."
1315 (let* ((prev-pos (point))
1316 (fontified-from (previous-single-char-property-change prev-pos 'fontified))
1317 (fontified-here (get-text-property (max (point-min) (1- prev-pos)) 'fontified))
1318 (fontified-all (and fontified-here (not fontified-from)))
1319 (pos (when fontified-here
1320 (previous-single-char-property-change prev-pos 'mlinks-link nil
1321 (or fontified-from 1))))
1323 next-fontified-from)
1324 (while (not (or ready
1327 (assert (numberp prev-pos) t)
1330 (when (and (> (1- pos) (point-min))
1331 (get-text-property (1- pos) 'mlinks-link))
1332 ;; Get out of current link
1334 (setq pos (previous-single-char-property-change prev-pos 'mlinks-link nil
1335 (or fontified-from 1))))
1338 (setq ready (and (get-text-property pos 'fontified)
1340 (not (get-text-property (1- pos) 'mlinks-link)))
1341 (get-text-property pos 'mlinks-link)))
1342 (unless ready (setq pos nil))))
1343 (setq next-fontified-from (max (- fontified-from 5000)
1345 (mumamo-with-buffer-prepared-for-jit-lock
1347 (put-text-property next-fontified-from fontified-from 'fontified t)
1348 (font-lock-fontify-region next-fontified-from fontified-from)))
1349 (setq fontified-from (previous-single-char-property-change
1350 (1+ next-fontified-from) 'fontified))
1351 (setq fontified-all (not fontified-from))
1352 (setq pos (previous-single-char-property-change prev-pos 'mlinks-link nil
1353 (or fontified-from 1)))))
1357 ;;; This is for the problem reported by some Asian users:
1359 ;;; Lisp error: (invalid-read-syntax "] in a list")
1366 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1367 ;;; mlinks.el ends here