|
;;; mouseme.el --- mouse menu with commands that operate on strings
|
|
|
|
;; Copyright (C) 1997 by Free Software Foundation, Inc.
|
|
|
|
;; Author: Howard Melman <howard@silverstream.com>
|
|
;; Keywords: mouse, menu
|
|
|
|
;; This file is part of GNU Emacs.
|
|
|
|
;; GNU Emacs is free software; you can redistribute it and/or modify
|
|
;; it under the terms of the GNU General Public License as published by
|
|
;; the Free Software Foundation; either version 2, or (at your option)
|
|
;; any later version.
|
|
|
|
;; GNU Emacs is distributed in the hope that it will be useful,
|
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
;; GNU General Public License for more details.
|
|
|
|
;; A copy of the GNU General Public License is available at
|
|
;; https://www.r-project.org/Licenses/
|
|
|
|
;;; Commentary:
|
|
|
|
;; This package provides a command `mouse-me' to be bound to a mouse
|
|
;; button. It pops up a menu of commands that operate on strings or a
|
|
;; region. The string passed to the selected command is the word or
|
|
;; symbol clicked on (with surrounding quotes or other punctuation
|
|
;; removed), or the region (if either it was just selected with the
|
|
;; mouse or if it was active with `transient-mark-mode' on). If the
|
|
;; command accepts a region, the selected region (or the region of the
|
|
;; word or symbol clicked on) will be passed to the command.
|
|
|
|
;; The idea is that for any given string in a buffer you may want to
|
|
;; do different things regardless of the mode of the buffer. URLs
|
|
;; now appear in email, news articles, comments in code, and in plain
|
|
;; text. You may want to visit that URL in a browser or you may just
|
|
;; want to copy it to the kill-ring. For an email address you might
|
|
;; want to compose mail to it, finger it, look it up in bbdb, copy it to
|
|
;; the kill ring. For a word you may want to spell check it, copy it,
|
|
;; change its case, grep for it, etc. Mouse-me provides a menu to
|
|
;; make this easy.
|
|
|
|
;; The menu popped up is generated by calling the function in the
|
|
;; variable `mouse-me-build-menu-function' which defaults to calling
|
|
;; `mouse-me-build-menu' which builds the menu from the variable
|
|
;; `mouse-me-menu-commands'. See the documentation for these
|
|
;; functions and variables for details.
|
|
|
|
;; To install, add something like the following to your ~/.emacs:
|
|
;; (require 'mouseme)
|
|
;; (global-set-key [S-mouse-2] 'mouse-me)
|
|
|
|
;;; Code:
|
|
|
|
(require 'browse-url)
|
|
(require 'thingatpt)
|
|
|
|
(eval-when-compile (require 'compile))
|
|
|
|
;;;; Variables
|
|
|
|
(defgroup mouseme nil
|
|
"Popup menu of commands that work on strings."
|
|
:prefix "mouse-me-"
|
|
:group 'hypermedia)
|
|
|
|
(defcustom mouse-me-get-string-function 'mouse-me-get-string
|
|
"Function used by `mouse-me' to get string when no region selected.
|
|
The default is `mouse-me-get-string' but this variable may commonly
|
|
be made buffer local and set to something more appropriate for
|
|
a specific mode (e.g., `word-at-point'). The function will be called
|
|
with no arguments and with point at where the mouse was clicked.
|
|
It can return either the string or to be most efficient, a list of
|
|
three elements: the string and the beginning and ending points of the
|
|
string in the buffer."
|
|
:type 'function
|
|
:options '(mouse-me-get-string)
|
|
:group 'mouseme)
|
|
|
|
(defcustom mouse-me-build-menu-function 'mouse-me-build-menu
|
|
"Function used by `mouse-me' to build the popup menu.
|
|
The default is `mouse-me-build-menu' but this variable may commonly
|
|
be made buffer local and set to something more appropriate for
|
|
a specific mode. The function will be called with one argument,
|
|
the string selected, as returned by `mouse-me-get-string-function'."
|
|
:type 'function
|
|
:options '(mouse-me-build-menu)
|
|
:group 'mouseme)
|
|
|
|
(defvar mouse-me-grep-use-extension 't
|
|
"If non-nil `mouse-me-grep' grep's in files with current file's extension.")
|
|
|
|
(defcustom mouse-me-menu-commands
|
|
'(("Copy" . kill-new)
|
|
("Kill" . kill-region)
|
|
("Capitalize" . capitalize-region)
|
|
("Lowercase" . downcase-region)
|
|
("Uppercase" . upcase-region)
|
|
("ISpell" . ispell-region)
|
|
"----"
|
|
("Browse URL" . browse-url)
|
|
("Dired" . dired)
|
|
("Execute File" . mouse-me-execute)
|
|
("Mail to" . compose-mail)
|
|
("Finger" . mouse-me-finger)
|
|
("BBDB Lookup" . mouse-me-bbdb)
|
|
"----"
|
|
("Imenu" . imenu)
|
|
("Find Tag" . find-tag)
|
|
("Grep" . mouse-me-grep)
|
|
("Find-Grep" . mouse-me-find-grep)
|
|
"----"
|
|
("Apropos" . apropos)
|
|
("Describe Function" . mouse-me-describe-function)
|
|
("Describe Variable" . mouse-me-describe-variable)
|
|
("Command Info" . mouse-me-emacs-command-info)
|
|
("Man Page" . (if (fboundp 'woman) 'woman 'man))
|
|
("Profile Function" . mouse-me-elp-instrument-function))
|
|
"Command menu used by `mouse-me-build-menu'.
|
|
A list of elements where each element is either a cons cell or a string.
|
|
If a cons cell the car is a string to be displayed in the menu and the
|
|
cdr is either a function to call passing a string to, or a list which evals
|
|
to a function to call passing a string to. If the element is a string
|
|
it makes a non-selectable element in the menu. To make a separator line
|
|
use a string consisting solely of hyphens.
|
|
|
|
The function returned from this menu will be called with one string
|
|
argument. Or if the function has the symbol property `mouse-me-type'
|
|
and if its value is the symbol `region' it will be called with the
|
|
beginning and ending points of the selected string. If the value is
|
|
the symbol `string' it will be called with one string argument."
|
|
:type '(repeat sexp)
|
|
:group 'mouseme)
|
|
|
|
(put 'kill-region 'mouse-me-type 'region)
|
|
(put 'ispell-region 'mouse-me-type 'region)
|
|
(put 'capitalize-region 'mouse-me-type 'region)
|
|
(put 'downcase-region 'mouse-me-type 'region)
|
|
(put 'upcase-region 'mouse-me-type 'region)
|
|
|
|
;;;; Commands
|
|
|
|
;;;###autoload
|
|
(defun mouse-me (event)
|
|
"Popup a menu of functions to run on selected string or region."
|
|
(interactive "e")
|
|
(mouse-me-helper event (lambda (name)
|
|
(or (x-popup-menu event (funcall mouse-me-build-menu-function name))
|
|
(error "No command to run")))))
|
|
|
|
;;;; Exposed Functions
|
|
|
|
;; Some tests:
|
|
;; <URL:http://foo.bar.com/sss/ss.html>
|
|
;; <http://foo.bar.com/sss/ss.html>
|
|
;; http://foo.bar.com/sss/ss.html
|
|
;; http://www.ditherdog.com/howard/
|
|
;; mailto:howard@silverstream.com
|
|
;; howard@silverstream.com
|
|
;; <howard@silverstream.com>
|
|
;; import com.sssw.srv.agents.AgentsRsrc;
|
|
;; public AgoHttpRequestEvent(Object o, String db, Request r)
|
|
;; <DIV><A href=3D"http://www.amazon.com/exec/obidos/ASIN/156592391X"><IMG =
|
|
;; <A HREF="http://www.suntimes.com/ebert/ebert.html">
|
|
;; d:\howard\elisp\spoon
|
|
;; \howard\elisp\spoon
|
|
;; \\absolut\howard\elisp\spoon
|
|
;; //absolut/d/Howard/Specs/servlet-2.1.pdf
|
|
;; \\absolut\d\Howard\Specs\servlet-2.1.pdf
|
|
;; gnuserv-frame.
|
|
|
|
(defun mouse-me-get-string ()
|
|
"Return a string from the buffer of text surrounding point.
|
|
Returns a list of three elements, the string and the beginning and
|
|
ending positions of the string in the buffer in that order."
|
|
(save-match-data
|
|
(save-excursion
|
|
(let ((start (point)) beg end str p)
|
|
(skip-syntax-forward "^ >()\"")
|
|
(setq end (point))
|
|
(goto-char start)
|
|
(skip-syntax-backward "^ >()\"")
|
|
(setq beg (point))
|
|
(setq str (buffer-substring-no-properties beg end))
|
|
;; remove junk from the beginning
|
|
(if (string-match "^\\([][\"'`.,?:;!@#$%^&*()_+={}|<>-]+\\)" str)
|
|
(setq str (substring str (match-end 1))
|
|
beg (+ beg (match-end 1))))
|
|
;; remove URL: from the front, it's common in email
|
|
(if (string-match "^\\(URL:\\)" str)
|
|
(setq str (substring str (match-end 1))
|
|
beg (+ beg (match-end 1))))
|
|
;; remove junk from the end
|
|
(if (string-match "\\([][\"'.,?:;!@#$%^&*()_+={}|<>-]+\\)$" str)
|
|
(setq end (- end (length (match-string 1 str))) ; must set end first
|
|
str (substring str 0 (match-beginning 1))))
|
|
(list str beg end)))))
|
|
|
|
(defun mouse-me-build-menu (name)
|
|
"Return a menu tailored for NAME for `mouse-me' from `mouse-me-menu-commands'."
|
|
(list "Mouse Me" (cons "Mouse Me"
|
|
(append (list (cons
|
|
(if (< (length name) 65)
|
|
name
|
|
"...Long String...")
|
|
'kill-new)
|
|
"---")
|
|
mouse-me-menu-commands))))
|
|
|
|
;;;; Commands for the menu
|
|
|
|
(defun mouse-me-emacs-command-info (string)
|
|
"Look in Emacs info for command named STRING."
|
|
(interactive "sCommand: ")
|
|
(let ((s (intern-soft string)))
|
|
(if (and s (commandp s))
|
|
(Info-goto-emacs-command-node s)
|
|
(error "No command named `%s'" string))))
|
|
|
|
(defun mouse-me-describe-function (string)
|
|
"Describe function named STRING."
|
|
(interactive "sFunction: ")
|
|
(let ((s (intern-soft string)))
|
|
(if (and s (fboundp s))
|
|
(describe-function s)
|
|
(error "No function named `%s'" string))))
|
|
|
|
(defun mouse-me-describe-variable (string)
|
|
"Desribe variable named STRING."
|
|
(interactive "sVariable: ")
|
|
(let ((s (intern-soft string)))
|
|
(if (and s (boundp s))
|
|
(describe-variable s)
|
|
(error "No variable named `%s'" string))))
|
|
|
|
(defun mouse-me-elp-instrument-function (string)
|
|
"Instrument Lisp function named STRING."
|
|
(interactive "sFunction: ")
|
|
(let ((s (intern-soft string)))
|
|
(if (and s (fboundp s))
|
|
(elp-instrument-function s)
|
|
(error "Must be the name of an existing Lisp function"))))
|
|
|
|
(defun mouse-me-execute (string)
|
|
"Execute STRING as a filename."
|
|
(interactive "sFile: ")
|
|
(if (fboundp 'w32-shell-execute)
|
|
(w32-shell-execute "open" (convert-standard-filename string))
|
|
(message "This function currently working only in W32.")))
|
|
|
|
|
|
(defun mouse-me-bbdb (string)
|
|
"Lookup STRING in bbdb."
|
|
(interactive "sBBDB Lookup: ")
|
|
(if (fboundp 'bbdb)
|
|
(bbdb string nil)
|
|
(error "BBDB not loaded")))
|
|
|
|
(defun mouse-me-finger (string)
|
|
"Finger a STRING mail address."
|
|
(interactive "sFinger: ")
|
|
(save-match-data
|
|
(if (string-match "\\(.*\\)@\\([-.a-zA-Z0-9]+\\)$" string)
|
|
(finger (match-string 1 string) (match-string 2 string))
|
|
(error "Not in user@host form: %s" string))))
|
|
|
|
(defun mouse-me-grep (string)
|
|
"Grep for a STRING."
|
|
(interactive "sGrep: ")
|
|
(require 'compile)
|
|
(grep-compute-defaults)
|
|
(let ((ext (mouse-me-buffer-file-extension)))
|
|
(grep (concat grep-command string
|
|
(if mouse-me-grep-use-extension
|
|
(if ext
|
|
(concat " *" ext)
|
|
" *"))))))
|
|
|
|
(defun mouse-me-find-grep (string)
|
|
"Grep for a STRING."
|
|
(interactive "sGrep: ")
|
|
(grep-compute-defaults)
|
|
(let ((reg grep-find-command)
|
|
(ext (mouse-me-buffer-file-extension))
|
|
beg end)
|
|
(if (string-match "\\(^.+-type f \\)\\(.+$\\)" reg)
|
|
(setq reg (concat (match-string 1 reg)
|
|
(if mouse-me-grep-use-extension
|
|
(concat "-name \"*" ext "\" "))
|
|
(match-string 2 reg))))
|
|
(grep-find (concat reg string))))
|
|
|
|
;;;; Internal Functions
|
|
|
|
(defun mouse-me-buffer-file-extension ()
|
|
"Return the extension of the current buffer's filename or nil.
|
|
Returned extension is a string begining with a period."
|
|
(let* ((bfn (buffer-file-name))
|
|
(filename (and bfn (file-name-sans-versions bfn)))
|
|
(index (and filename (string-match "\\.[^.]*$" filename))))
|
|
(if index
|
|
(substring filename index)
|
|
"")))
|
|
|
|
(defun mouse-me-helper (event func)
|
|
"Determine the string to use to process EVENT and call FUNC to get cmd."
|
|
(let (name sp sm mouse beg end cmd mmtype)
|
|
;; temporarily goto where the event occurred, get the name clicked
|
|
;; on and enough info to figure out what to do with it
|
|
(save-match-data
|
|
(save-excursion
|
|
(setq sp (point)) ; saved point
|
|
(setq sm (mark t)) ; saved mark
|
|
(set-buffer (window-buffer (posn-window (event-start event))))
|
|
(setq mouse (goto-char (posn-point (event-start event))))
|
|
;; if there is a region and point is inside it
|
|
;; check for sm first incase (null (mark t))
|
|
;; set name to either the thing they clicked on or region
|
|
(if (and sm
|
|
(or (and transient-mark-mode mark-active)
|
|
(eq last-command 'mouse-drag-region))
|
|
(>= mouse (setq beg (min sp sm)))
|
|
(<= mouse (setq end (max sp sm))))
|
|
(setq name (buffer-substring beg end))
|
|
(setq name (funcall mouse-me-get-string-function))
|
|
(if (listp name)
|
|
(setq beg (nth 1 name)
|
|
end (nth 2 name)
|
|
name (car name))
|
|
(goto-char mouse)
|
|
(while (not (looking-at (regexp-quote name)))
|
|
(backward-char 1))
|
|
(setq beg (point))
|
|
(setq end (search-forward name))))))
|
|
;; check if name is null, meaning they clicked on no word
|
|
(if (or (null name)
|
|
(and (stringp name) (string= name "" )))
|
|
(error "No string to pass to function"))
|
|
;; popup a menu to get a command to run
|
|
(setq cmd (funcall func))
|
|
;; run the command, eval'ing if it was a list
|
|
(if (listp cmd)
|
|
(setq cmd (eval cmd)))
|
|
(setq mmtype (get cmd 'mouse-me-type))
|
|
(cond ((eq mmtype 'region)
|
|
(funcall cmd beg end))
|
|
((eq mmtype 'string)
|
|
(funcall cmd name))
|
|
(t
|
|
(funcall cmd name)))))
|
|
|
|
(provide 'mouseme)
|
|
|
|
;;; mouseme.el ends here
|