[comp.emacs] Thing commands for GNU emacs

guttman@linus.UUCP (Joshua D. Guttman) (05/31/89)

I've done up some "thing-commands" based originally on procedures defined in
the emacstool support files (sun-fns.el).  The idea, it said there, was derived
from a zmacs function on the lisp machines.  

The relevant text unit is based on the syntax of the current character.  For
instance, at the end of a line, it is the whole line.  At a word-constituent
character it is the word.  At a "_" or "-", it is the "symbol", i.e. the
ensemble of words connected by such characters.  An open or close paren (or
other grouping character) selects the s-expression.  Etc.  The object chosen
can then be marked, copied, or killed (or stuffed into the window-system
cut-buffer) as a simple unit.

I'm enclosing the two flavors that I use, one kind which simply operates on the
text, and one which I bind to mouse clicks in the X window system.  I find them
very useful, and have been using them for some months, so they should be
relatively reliable.  The mouse versions are extremely convenient, as you can
kill or copy a long symbol or sexpression in a single click.  

You will also find them very easy to extend or modify, as there's an a-list
that determines what procedure to use to find the boundaries of the relevant
thing, based on the char-syntax of the current character.  

I bind the functions to keys in my .emacs file as follows:

(cond ((eq window-system 'x)
       ;; irrelevancies removed ...
       (define-key mouse-map x-button-s-middle 'x-mouse-kill-thing)
       (define-key mouse-map x-button-s-right 'x-mouse-copy-thing)))
 
(global-set-key "\C-ck" 'kill-thing-at-point)
(global-set-key "\C-cw" 'copy-thing-at-point)

I hope you find them useful.

	Joshua Guttman

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
;;; Thing-oriented procedures for GNU emacs
;;; Written by Joshua Guttman, The MITRE Corporation
;;; Comments or suggestions to guttman@mitre.org

(provide 'thing)

(defun thing-boundaries (here)
  "Return start and end of text object at HERE using syntax table and thing-boundary-alist.  
Thing-boundary-alist is a list of pairs of the form (SYNTAX-CHAR FUNCTION)
where FUNCTION takes a single position argument and returns a cons of places
 (start end) representing boundaries of the thing at that position.  
Typically:
 Left or right Paren syntax indicates an s-expression.	
 The end of a line marks the line including a trailing newline. 
 Word syntax indicates current word. 
 Symbol syntax indicates symbol.
 If it doesn't recognize one of these it selects just the character HERE."
  (interactive "d")
  (if (save-excursion
	(goto-char here)
	(eolp))
      (thing-get-line here)
    (let* ((syntax
	    (char-syntax (char-after here)))
	   (pair
	    (assq syntax thing-boundary-alist)))
    
      (if pair
	  (funcall (car (cdr pair)) here)
	(cons here (1+ here))))))  


(defvar thing-boundary-alist
  '((?w thing-word)
    (?_ thing-symbol)
    (?\( thing-sexp-start)
    (?\$ thing-sexp-start)
    (?' thing-sexp-start)
    (?\" thing-sexp-start)
    (?\) thing-sexp-end)
    (?  thing-whitespace))
  "*List of pairs of the form (SYNTAX-CHAR FUNCTION) used by THING-BOUNDARIES.")
  
(defun thing-get-line (here)
  "Return whole of line HERE is in, with newline unless at eob."
  (save-excursion
    (goto-char here)
    (let* ((start (progn (beginning-of-line 1)
			 (point)))
	   (end (progn (forward-line 1)
		       (point))))
      (cons start end))))

(defun thing-word (here)
  "Return start and end of word at HERE."
  (save-excursion
    (goto-char here)
    (forward-word 1)
    (let ((end (point)))
      (forward-word -1)
      (cons (point) end))))

(defun thing-symbol (here)
  "Return start and end of symbol at HERE."
  (let ((end (scan-sexps here 1)))
    (cons (min here (scan-sexps end -1))
	  end)))

(defun thing-sexp-start (here)
  "Return start and end of sexp starting HERE."
  (cons here (scan-sexps here 1)))

(defun thing-sexp-end (here)
  "Return start and end of sexp ending HERE."
  (cons (scan-sexps (1+ here) -1)
	(1+ here)))

(defun thing-whitespace (here)
  "Return start to end of all but one char of whitespace HERE, unless 
there's only one char of whitespace.  Then return start to end of it."
  (save-excursion
    (let ((start (progn
		   (skip-chars-backward " \t") (1+ (point))))
	  (end (progn 
		 (skip-chars-forward " \t") (point))))
      (if (= start end)
	  (cons (1- start) end)
	(cons start end)))))





(defun mark-thing-at-point (here)
  "Set point at beginning and mark at end of text object using syntax table.
See thing-boundaries for definition of text objects"
  (interactive "d")
  (let ((bounds (thing-boundaries here)))
    (goto-char (cdr bounds))
    (set-mark-command nil)
    (goto-char (car bounds))))

(defun kill-thing-at-point (here)
  "Kill text object using syntax table.
See thing-boundaries for definition of text objects"
  (interactive "d")
  (let ((bounds (thing-boundaries here)))
    (kill-region (car bounds) (cdr bounds))))


(defun copy-thing-at-point (here)
  "Copy text object using syntax table.
See thing-boundaries for definition of text objects"
  (interactive "d")
  (let ((bounds (thing-boundaries here)))
    (copy-region-as-kill (car bounds) (cdr bounds))))

;;; Two X-related fns.  	
	    
(defun x-mouse-kill-thing (arg)
  "Kill text object at point or mouse position and insert into window system cut buffer.
Save in Emacs kill ring also."
  (interactive "d")
  (setq last-command nil)
  (x-mouse-set-point arg)
  (let* ((bounds (thing-boundaries (point)))
	 (start (car bounds))
	 (end (cdr bounds)))
    (x-store-cut-buffer (buffer-substring start end))
    (kill-region start end)))

(defun x-mouse-copy-thing (arg)
  "Copy text object at point or mouse position into window system cut buffer.
Save in Emacs kill ring also."
  (save-excursion
    (save-window-excursion
      (setq last-command nil)
      (x-mouse-set-point arg)
      (let* ((bounds (thing-boundaries (point)))
	     (start (car bounds))
	     (end (cdr bounds)))
	(x-store-cut-buffer (buffer-substring start end))
	(copy-region-as-kill start end)))))