;;; extraregional.el ;;; ===================================================== ;;; Unlinks the selection of text with the mouse from the ;;; manipulation of the point, mark and region. ;;; ===================================================== ;;; ;;; Copyright (C) 1995 John Maraist maraist@ira.uka.de ;;; Pre-release version 0.1.0 ;;; This program 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 1, or (at your option) ;;; any later version. ;;; ;;; This program 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 can be obtained from the ;;; author of this program at the email address above; from the Free ;;; Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA; ;;; or from most emacs implementations from the main "Help" or "Info" ;;; screens. ;; LCD Archive Entry: ;; To be completed for version 1.0. ;;; Installation: ;;; ;;; It is probably best to load this package at startup: ;;; ;;; (require 'extraregional) ;;; ;;; It is also possible to use autoloads: for every function ;;; mentioned below that you wish to link to keys (none of this ;;; package is well-suited to M-x style use), add an autoload. For ;;; example, ;;; ;;; (autoload 'extraregional-mouse-select "extraregional" nil t) ;;; ;;; but this strategy is not recommended. ;;; Usage: ;;; ;;; This package can be run either as a global minor mode or as a ;;; set of global declarations; for the time being we recommend the ;;; former approach. One can also make the mode buffer-local, but ;;; this seems silly, and so we will not make it so easy as to tell ;;; you how to do this. All of the sample integration code will be ;;; geared towards the minor mode approach; for a global setup, use ;;; ;;; (global-set-key ...) ;;; ;;; rather than ;;; ;;; (define-key extraregional-mode-map ...) ;;; ;;; Sample functions for customization are included at the end of this ;;; file. ;;; ;;; ;;; The selection routine must be called from a mouse button, and can ;;; be installed by (say) ;;; ;;; (define-key extraregional-mode-map ;;; 'button3 'extraregional-mouse-select) ;;; ;;; There are seven selection manipulation functions, decribed below. ;;; They vary according to (1) whether the original selection is ;;; removed, (2) whether a copy of the selection is put onto the kill ;;; ring and (3) whether a copy of the selection is inserted at the ;;; current point. The eighth combination of these three options is ;;; achieved by leaving the mouse and keyboard untouched. ;;; ;;; +-----------------------------------+-----------+--------+-----------+ ;;; | Function name | Remove | Add to | Insert at | ;;; | | original? | kill | current | ;;; | | | ring? | point? | ;;; +-----------------------------------+-----------+--------+-----------+ ;;; | extraregional-copy | N | Y | N | ;;; +-----------------------------------+-----------+--------+-----------+ ;;; | extraregional-delete | Y | N | N | ;;; +-----------------------------------+-----------+--------+-----------+ ;;; | extraregional-kill | Y | Y | N | ;;; +-----------------------------------+-----------+--------+-----------+ ;;; | extraregional-duplicate-at-point | N | N | Y | ;;; +-----------------------------------+-----------+--------+-----------+ ;;; | extraregional-copy-and-to-point | N | Y | Y | ;;; +-----------------------------------+-----------+--------+-----------+ ;;; | extraregional-delete-and-to-point | Y | N | Y | ;;; +-----------------------------------+-----------+--------+-----------+ ;;; | extraregional-kill-and-to-point | Y | Y | Y | ;;; +-----------------------------------+-----------+--------+-----------+ ;;; ;;; All of these functions are best used from key bindings. ;;; ;;; ;;; Other mode operations. The function purge-extraregional-mode ;;; removes all functions and variables related to extraregional-mode ;;; from memory. When extraregional-mode starts, it runs all ;;; functions in the extraregional-mode-hook. ;;; User options: ;;; ;;; The following user options are currently recognized. All may be ;;; set by the user before or after loading this package, but if done ;;; before, then defconst should be used rather than setq. ;;; ;;;;; kill-ring-save-on-extraregional-select ;;;;; ;;;;; If non-null, then every mouse selection is automatically copied ;;;;; to the kill ring. Note that this option makes the ;;;;; extraregional-copy redundant, but at this version the function is ;;;;; not disabled or otherwise affected by the option. ;;; ;;;;; extraregional-selection-bkgnd-color ;;;;; ;;;;; The background color used to highlight the selection. ;;; ;;; Known bugs, problems and needs. ;;; ;;; ;;; The resolution of deleted source buffers for the extraregional ;;; selection could be rethought. ;;; ;;; Some more user options should be designed and implemented: a ;;; master on/off switch for region mimicry and deactivation upon ;;; changes come to mind. ;;; ;;; The shift-click selection command doesn't work and I don't know ;;; why. ;;; ;;; Version history: ;;; ;;;;; -- 0.1.1 -- 11 September 1995 -- ;;;;; Implemented the extraregional-vanish-on-new-kill and ;;;;; extraregional-manipulators-resort-to-region user options. The ;;;;; package seems usable at this point. ;;; ;;;;; -- 0.1.0a -- 6 September 1995 -- ;;;;; (1) Made extraregional selection irrelevant to the buffer ;;;;; modification flag and undo lists. (2) Created a minor mode which ;;;;; can be used to avoid global bindings. ;;; ;;;;; -- 0.1.0 -- 4 September 1995 -- ;;;;; First working draft. Released at the local site only. ;;; (provide 'extraregional) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Definition of user options. ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defvar kill-ring-save-on-extraregional-select nil "*Indicates whether extraregional selection should be copied to the kill ring. The default is nil. If non-null, then every mouse selection is automatically copied to the kill ring. Note that this option makes the extraregional-copy redundant, but at this version the function is not disabled or otherwise affected by the option.") (defvar extraregional-selection-bkgnd-color "Yellow" "*The background color used to highlight extraregional selections. The default is yellow.") ; To implement: ; ; (defvar extraregional-mimic-region ; t ; "*User option: ") (defvar extraregional-vanish-on-change t "*User option: remove the extraregional selection upon any change. NOT CURRENTLY IMPLEMENTED! It is the default in Xemacs now that when (say) new text is added, the region is deactivated. This option, when set to a non-null value, mimics this behavior for the extraregional selection. If the value of before-change-function is non-null at the time of extraregional selection, the old function will be restored and called after the selection extent is removed.") (defvar extraregional-vanish-on-same-buffer-change t "*User option: remove the extraregional selection upon local changes. NOT CURRENTLY IMPLEMENTED! This option is like extraregional-vanish-on-change, but only changes to the same buffer as where the extraregional text lies will cause deselection. If both extraregional-vanish-on-same-buffer-change and extraregional-vanish-on-change are selected, then extraregional-vanish-on-change will take priority. This option also manipulates the before-change-function, and also restores it to its original state.") (defvar extraregional-vanish-on-new-kill t "*User option: remove the extraregional selection on addition to the kill ring. This option allows extraregional and normal selections to more easily co-exist in the kill ring. When this option is set to a non-null value, the addition of a new element to the kill ring will cause extraregional deselection.") (defvar extraregional-manipulators-resort-to-region t "*User option: should extraregional manipulators resort to the region? When this option is set to a non-null value, the extraregional manipulation functions will draw from the kill ring and/or current region when no extraregional selection is present.") ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Global variables and the selection typeface (background color). ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defvar buffer-mouse-selection-text "") (defvar buffer-mouse-selection-extent nil) (make-face 'buffer-mouse-selection-face) (set-face-background 'buffer-mouse-selection-face extraregional-selection-bkgnd-color) ;;;;;;;;;;;;;;;;;;;;;; ;;; The minor mode ;;; ;;;;;;;;;;;;;;;;;;;;;; (defvar extraregional-mode nil "Implies extraregional minor mode. By default, this is a global setting. See the function definition of extraregional-mode for more information.") (or (assq 'extraregional-mode minor-mode-alist) (setq minor-mode-alist (cons '(extraregional-mode " ExtrReg") minor-mode-alist))) (defun extraregional-mode (&optional arg) "Activates extraregional minor mode. This mode unlinks the selection of text with the mouse from the manipulation of the point, mark and region. There is currently one selection function, extraregional-mouse-select. This selection routine must be called from a mouse button, and can be installed by (say) (define-key 'extraregional-mode-map 'button3 'extraregional-mouse-select) There are seven selection manipulation functions, decribed below. They vary according to (1) whether the original selection is removed, (2) whether a copy of the selection is put onto the kill ring and (3) whether a copy of the selection is inserted at the current point. The eighth combination of these three options is achieved by leaving the mouse and keyboard untouched. +-----------------------------------+-----------+--------+-----------+ | Function name | Remove | Add to | Insert at | | | original? | kill | current | | | | ring? | point? | +-----------------------------------+-----------+--------+-----------+ | extraregional-copy | N | Y | N | +-----------------------------------+-----------+--------+-----------+ | extraregional-delete | Y | N | N | +-----------------------------------+-----------+--------+-----------+ | extraregional-kill | Y | Y | N | +-----------------------------------+-----------+--------+-----------+ | extraregional-duplicate-at-point | N | N | Y | +-----------------------------------+-----------+--------+-----------+ | extraregional-copy-and-to-point | N | Y | Y | +-----------------------------------+-----------+--------+-----------+ | extraregional-delete-and-to-point | Y | N | Y | +-----------------------------------+-----------+--------+-----------+ | extraregional-kill-and-to-point | Y | Y | Y | +-----------------------------------+-----------+--------+-----------+ All of these functions are best used from key bindings in the mode keymap. The current key binding in the minor mode map are: \\{extraregional-mode-map} It is also possible, of course, to use global key bindings rather than the minor mode. The function purge-extraregional-mode will remove the minor mode definitions (but not the rest of this package's code from the emacs system, and may be useful to save memory if you're desperate for space. User options: The following user options are currently recognized. All may be set by the user before or after loading this package, but if done before, then defconst should be used rather than setq. More information about each user option may be found on the help page for the respective variable names. * kill-ring-save-on-extraregional-select * extraregional-selection-bkgnd-color * extraregional-vanish-on-change * extraregional-vanish-on-same-buffer-change * extraregional-vanish-on-new-kill * extraregional-manipulators-resort-to-region" (interactive) (setq extraregional-mode (if (null arg) (not extraregional-mode) (> (prefix-numeric-value arg) 0))) (run-hooks 'extraregional-mode-hook)) (defvar extraregional-mode-map (let ((m (make-sparse-keymap))) (define-key m 'button3 'extraregional-mouse-select) m) "Keymap for extraregional minor mode") (or (assq 'extraregional-mode minor-mode-map-alist) (setq minor-mode-map-alist (cons (cons 'extraregional-mode extraregional-mode-map) minor-mode-map-alist))) (defun purge-extraregional-mode () (interactive) (if (yes-or-no-p "Really purge extraregional minor mode? ") (progn (fmakunbound 'extraregional-mode) (makunbound 'extraregional-mode) (makunbound 'extraregional-mode-map) (makunbound 'extraregional-mode-hooks) (setq minor-mode-alist (unassq 'extraregional-mode minor-mode-alist)) (setq minor-mode-map-alist (unassq 'extraregional-mode minor-mode-map-alist)) (message "Extraregional minor mode purged.")) (message "Cancelled."))) (defun unassq (id xs) (if (null xs) nil (if (equal id (car (car xs))) (unassq id (cdr xs)) (cons (car xs) (unassq id (cdr xs)))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Text selection and mouse tracking. ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defun extraregional-mouse-select (click) (interactive "e") (extraregional-deselect) (if (null click) (error "Where's the mouse?!?")) (let* ((old-screen (selected-screen)) (old-window (selected-window)) (old-buffer (current-buffer)) (old-point (point)) ;;(old-mark (mark)) (click-buffer-mod-flag) (click-window (event-window click)) (click-beginning) (click-end)) (if (null click-window) (mouse-track click) (progn (select-window click-window) (setq click-buffer-mod-flag (buffer-modified-p)) (set-mark nil) (mouse-track click) (if (mark) (progn (setq click-beginning (region-beginning)) (setq click-end (region-end))) (progn (setq click-beginning (point)) (setq click-end (+ click-beginning 1)))) (setq buffer-mouse-selection-text (buffer-substring click-beginning click-end)) (delete-region click-beginning click-end) (insert buffer-mouse-selection-text) (setq buffer-mouse-selection-extent (make-extent click-beginning click-end)) (set-extent-property buffer-mouse-selection-extent 'face 'buffer-mouse-selection-face) (set-buffer-modified-p click-buffer-mod-flag) (setq buffer-undo-list (cdr (cdr (cdr (cdr buffer-undo-list))))) (select-screen old-screen) (select-window old-window) (set-window-buffer old-window old-buffer) (goto-char old-point) (set-mark nil) (if kill-ring-save-on-extraregional-select (progn (kill-new buffer-mouse-selection-text) (message "Extraregional text selected and copied to kill ring.")) (message "Extraregional text selected.")))))) ;;; ;;; WARNING: this function works only some of the time. ;;; ;;; A system function mouse-track-anchor fails. There is no ;;; documentation for this function. ;;; ;;; Until Something Is Done about this problem, this function will be ;;; undefined immediately after this defun. ;;; (defun extraregional-mouse-select-extend (click) (interactive "e") (if (null click) (error "Where's the mouse?!?")) (let* ((click-window (event-window click)) (click-buffer (if (null click-window) nil (window-buffer click-window))) (previous-click-buffer (if (extent-property buffer-mouse-selection-extent 'detached) nil (extent-buffer buffer-mouse-selection-extent)))) (if (and previous-click-buffer (equal click-buffer previous-click-buffer)) (let* ((old-screen (selected-screen)) (old-window (selected-window)) (old-buffer (current-buffer)) (old-point (point)) ;;(old-mark (mark)) (click-beginning (extent-start-position buffer-mouse-selection-extent)) ;; The above will always be defined since this block of ;; the last if-conditional will be executed only if ;; previous-click-buffer is true, which happens only if ;; buffer-mouse-selection-extent is not detached. (click-end)) ;; We do not need to make the ;; ;; (if (not (null click-window)) ...) ;; ;; test here; we've taken care of that indirectly with ;; the check that click-buffer is equal to a non-null ;; previous-click-buffer. (select-window click-window) (set-mark nil) (goto-char click-beginning) ;; (set-extent-property buffer-mouse-selection-extent 'face nil) (mouse-track-adjust click) (if (mark) (progn (setq click-beginning (region-beginning)) (setq click-end (region-end))) (progn (setq click-beginning (point)) (setq click-end (+ click-beginning 1)))) (setq buffer-mouse-selection-text (buffer-substring click-beginning click-end)) (delete-region click-beginning click-end) (insert buffer-mouse-selection-text) (setq buffer-mouse-selection-extent (make-extent click-beginning click-end)) (set-extent-property buffer-mouse-selection-extent 'face 'buffer-mouse-selection-face) (select-screen old-screen) (select-window old-window) (set-window-buffer old-window old-buffer) (goto-char old-point) (set-mark nil) (if kill-ring-save-on-extraregional-select (progn (kill-new buffer-mouse-selection-text) (message "Extraregional text selected and copied to kill ring.")) (message "Extraregional text selected."))) (extraregional-mouse-select click)))) (fmakunbound 'extraregional-mouse-select-extend) (defun extraregional-deselect () (if (not (null buffer-mouse-selection-extent)) (progn (delete-extent buffer-mouse-selection-extent) ))) ;;;;;;;;;;;;; ;;; Hooks ;;; ;;;;;;;;;;;;; (defun extraregional-kill-hook (&rest args) (if (and extraregional-vanish-on-new-kill buffer-mouse-selection-extent) (extraregional-deselect))) (or (memq 'extraregional-kill-hook kill-hooks) (add-hook 'kill-hooks 'extraregional-kill-hook)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Manipulating selected text ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defun extraregional-copy () "Command: copy the separate mouse selection into the kill ring. It may be a good idea to maintain separate kill rings, one for the usual region kills, and one for kills with the separate mouse package. Perhaps in the next version. If the extraregional-manipulators-resort-to-region is activated and the region is active, then the function kill-ring-save is called. Note that since making an extraregional selection deactivates the region, the presence of an active region and an active extraregional selection at the same time means that the region, and not the extraregion, was more recently selected." (interactive) (if (and extraregional-manipulators-resort-to-region (mark)) (kill-ring-save (region-beginning) (region-end)) (if (not (extent-property buffer-mouse-selection-extent 'destroyed)) (progn (kill-new buffer-mouse-selection-text) (if (not (buffer-name (extent-buffer buffer-mouse-selection-extent))) (message "Warning: buffer housing selection has been deleted."))) (error "No selection.")))) (defun extraregional-delete () "Command: delete the separate mouse selection from its buffer. This command does not alter the kill ring. If the extraregional-manipulators-resort-to-region is activated and the region is active, then the function delete-region is called. Note that since making an extraregional selection deactivates the region, the presence of an active region and an active extraregional selection at the same time means that the region, and not the extraregion, was more recently selected." (interactive) (if (and extraregional-manipulators-resort-to-region (mark)) (delete-region (region-beginning) (region-end)) (if (not (extent-property buffer-mouse-selection-extent 'destroyed)) (let ((from-buffer (extent-buffer buffer-mouse-selection-extent))) (if (not (buffer-name from-buffer)) (error "Buffer housing selection has been deleted.")) (save-excursion (set-buffer from-buffer) (delete-region (extent-start-position buffer-mouse-selection-extent) (extent-end-position buffer-mouse-selection-extent)))) (error "No selection.")))) (defun extraregional-kill () "Command: cut the separate mouse selection from its buffer to the kill ring. If the extraregional-manipulators-resort-to-region is activated and the region is active, then the function kill-region is called. Note that since making an extraregional selection deactivates the region, the presence of an active region and an active extraregional selection at the same time means that the region, and not the extraregion, was more recently selected." (interactive) (if (and extraregional-manipulators-resort-to-region (mark)) (kill-region (region-beginning) (region-end)) (if (not (extent-property buffer-mouse-selection-extent 'destroyed)) (let ((from-buffer (extent-buffer buffer-mouse-selection-extent))) (if (not (buffer-name from-buffer)) (error "Buffer housing selection has been deleted.")) (save-excursion (set-buffer from-buffer) (kill-region (extent-start-position buffer-mouse-selection-extent) (extent-end-position buffer-mouse-selection-extent)) (extraregional-deselect))) (error "No selection.")))) (defun extraregional-duplicate-at-point () "Command: insert the mouse selection at point, leaving selection buffer intact. This command does not affect the kill ring. If the extraregional-manipulators-resort-to-region is activated and no extraregional selection is current, then the function yank is called." (interactive) (if (or (extent-property buffer-mouse-selection-extent 'destroyed) (not (buffer-name (extent-buffer buffer-mouse-selection-extent)))) (yank) (insert buffer-mouse-selection-text))) (defun extraregional-copy-and-to-point () "Command: copy the separate mouse selection into the kill ring and at point. If the extraregional-manipulators-resort-to-region is activated and no extraregional selection has been made, then the function yank is called. Note that yank does not completely parallel this command's functionality: the command extraregional-copy-and-to-point adds to the kill-ring and inserts text at point, while yank inserts text at point from the kill ring." (interactive) (if (not (extent-property buffer-mouse-selection-extent 'destroyed)) (progn (kill-new buffer-mouse-selection-text) (insert buffer-mouse-selection-text) (if (not (buffer-name (extent-buffer buffer-mouse-selection-extent))) (message "Warning: buffer housing selection has been deleted."))) (if extraregional-manipulators-resort-to-region (yank) (error "No selection.")))) (defun extraregional-delete-and-to-point () "Command: delete the separate mouse selection from its buffer. This command does not affect the kill ring. One aspect of this command's behavior is a bit odd: if the buffer where an extraregional selection was made is subsequently deleted and either: (1) extraregional-manipulators-resort-to-region is either set to nil or has the 'destroyed property, or (2) the region is not active, then the text from the last extraregional selection (which is currently stored separately) is still inserted into the buffer. We should rethink this behavior. If the extraregional-manipulators-resort-to-region is activated, no extraregional selection has been made, and the region is active, then the function delete-region is called. Note that delete-region does not completely parallel this command's functionality for extraregional selection, as no text is inserted at the point." (interactive) (let ((from-buffer (extent-buffer buffer-mouse-selection-extent))) (if (and (not (extent-property buffer-mouse-selection-extent 'destroyed)) (buffer-name from-buffer)) (progn (insert buffer-mouse-selection-text) (save-excursion (set-buffer from-buffer) (delete-region (extent-start-position buffer-mouse-selection-extent) (extent-end-position buffer-mouse-selection-extent)))) (if (and extraregional-manipulators-resort-to-region (mark)) (delete-region (region-beginning) (region-end)) (if buffer-mouse-selection-text (insert buffer-mouse-selection-text) (error "No selection.")))))) (defun extraregional-kill-and-to-point () "Command: cut the separate mouse selection from its buffer to the kill ring. One aspect of this command's behavior is a bit odd: if the buffer where extraregion selection was made is subsequently deleted, and either (1) extraregional-manipulators-resort-to-region is set to nil, or (2) the region is not active, then the text from the last extraregional selection (which is currently stored separately) is still inserted into the buffer. We should rethink this behavior. If the extraregional-manipulators-resort-to-region is activated, no extraregional selection has been made, and the region is active, then the function kill-region is called. Note that kill-region does not completely parallel this command's functionality for extraregional selection, as no text is inserted at the point." (interactive) (let ((from-buffer (extent-buffer buffer-mouse-selection-extent))) (if (and (not (extent-property buffer-mouse-selection-extent 'destroyed)) (buffer-name from-buffer)) (progn (insert buffer-mouse-selection-text) (save-excursion (set-buffer from-buffer) (kill-region (extent-start-position buffer-mouse-selection-extent) (extent-end-position buffer-mouse-selection-extent)))) (if (and extraregional-manipulators-resort-to-region (mark)) (kill-region (region-beginning) (region-end)) (if buffer-mouse-selection-text (insert buffer-mouse-selection-text) (error "No selection.")))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Sample customization functions ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defun standard-sun-x-extraregional-hook-fn () "Sample extraregional-mode customization routine for xemacs from X-windows. This routine, if used, should be added to the extraregional-mode-hook *before* this file is loaded: (and (member 'standard-sun-x-extraregional-hook-fn extraregional-mode-hook) (add-hook 'extraregional-mode-hook 'standard-sun-x-extraregional-hook-fn)) If added to the hook after loading, the effects will not take place until extraregional-mode is deactivated and re-activated." (define-key extraregional-mode-map 'button3 'extraregional-mouse-select) (define-key extraregional-mode-map 'f16 'extraregional-copy) (define-key extraregional-mode-map 'f18 'extraregional-copy-and-to-point) (define-key extraregional-mode-map 'f20 'extraregional-kill) (define-key extraregional-mode-map '(meta w) 'extraregional-copy) (define-key extraregional-mode-map '(control y) 'extraregional-copy-and-to-point) ) ;;; end of package extraregional.el