[Top][All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]


From: Ivar Rummelhoff
Subject: narrow-undo.el
Date: Tue, 16 May 2006 17:59:07 +0200
User-agent: Gnus/5.11 (Gnus v5.11) Emacs/22.0.50 (windows-nt)

;;; narrow-undo.el --- Minor mode for undoing changes in narrowing

;; Copyright (C) 2006 Ivar Rummelhoff

;; Author: Ivar Rummelhoff <address@hidden>
;; Maintainer: Ivar Rummelhoff <address@hidden>
;; Created: 17 April 2006
;; Version: 1.0
;; Keywords: convenience

;; This file is an extension to (but not yet a part of) GNU Emacs.

;; This file 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.

;; This file is distributed in the hope that it will be useful, but
;; WITHOUT ANY WARRANTY; without even the implied warranty of
;; General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING.  If not, write to the
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.

;;; Commentary:

;; Narrowing (see Info node `(emacs)Narrowing') can be quite useful
;; when editing large files, except for two problems which this
;; package attempts to solve:

;; 1. While editing a narrowed buffer, it is often necessary to
;;    consult or even edit a part of the buffer which is currently
;;    unavailable.  By default the only way to do this is by calling
;;    `widen', which means that the current restriction is lost.

;; 2. Sometimes it is conventient to temporarily narrow the buffer
;;    down to a fragment of the current narrowing; but with the
;;    standard commands this again means that the current restriction
;;    is lost.

;; The global minor mode `narrow-undo-mode' solves these problems by
;; keeping a stack of previous restrictions for each buffer, so that
;; changes in narrowing can be undone by `C-x n u' (`narrow-undo').

;; Bugs/features:

;; a. The current restriction is lost when `narrow-undo' is called.
;;    In other words, there is no way to redo the changes as with
;;    ordinary `undo'.

;; b. The current restriction is only saved before calling the
;;    commands in `narrow-undo-watch-commands'.  Moreover, this is
;;    implemented in terms of remapping (see Info node
;;    `(elisp)Remapping Commands'), which means that the current
;;    restriction is only saved when these commands are called using
;;    keyboard shortcuts (or menus).

;; c. The current implementation of `narrow-undo-mode' does not work
;;    in older versions of Emacs without remapping.

;; d. The stack of previous restrictions is implemented in terms of
;;    rings of size `narrow-undo-limit' (see Info node
;;    `(elisp)Rings').  If there are more restrictions than this,
;;    then the oldest restrictions will get lost.

;; e. If `narrow-undo' is called and the stack of restrictions is
;;    empty, then `widen' is called before an error is signalled.
;;    This means that calling `narrow-undo' repeatedly will ultimately
;;    make the entire buffer accessible (even in buffers where this is
;;    undesirable, such as those used by `info' and `rmail').
;;; Code:

(require 'ring)
(eval-when-compile (require 'cl))

(defgroup narrow-undo nil
  "User options for `narrow-undo-mode'."
  :tag     "Narrow-undo"
  :group   'convenience)

(defcustom narrow-undo-mode-map
  (let ((map (make-sparse-keymap)))
    (define-key map "\C-xnu" 'narrow-undo)
  "Basic keymap for `narrow-undo-mode'.
See also `narrow-undo-watch-commands'."
  :tag   "Narrow-undo Mode Map"
  :group 'narrow-undo)

(defvar narrow-undo-map (make-sparse-keymap)
  "See `narrow-undo-define-map'.")

(defun narrow-undo-define-map ()
  "Define `narrow-undo-map'.
Uses `narrow-undo-mode-map' and `narrow-undo-watch-commands'."
  (let ((map (make-sparse-keymap)))
    (set-keymap-parent map (default-value 'narrow-undo-mode-map))
    (loop with wrap = (lambda () (interactive)
                        (call-interactively this-original-command))
          for com in narrow-undo-watch-commands
          for sym = (intern (concat "narrow-undo--" (symbol-name com)))
          (fset sym wrap)
          (put sym 'function-documentation
               (format "Same as `%s', \
but save the current restriction first.
Use `narrow-undo' to go back to previous restrictions.
These commands belong to `narrow-undo-mode'.

`%s' was generated by `narrow-undo-define-map'."
                       com sym))
          (define-key map (vector 'remap com) sym))
    (setcdr (default-value 'narrow-undo-map) (cdr map))))

(defcustom narrow-undo-watch-commands
  '(widen narrow-to-region narrow-to-defun narrow-to-page)
  "Commands that change the restriction of the current buffer.
When `narrow-undo-mode' is enabled, the current restriction is
saved before calling any of these commands (using remapping).

If this variable is changed without using `customize' and after
narrow-undo.el is loaded, then `narrow-undo-define-map' must be
called for the changes to take effect."
  :tag   "Narrow-undo Watch Commands"
  :type  '(repeat symbol)
  :group 'narrow-undo
  :require 'narrow-undo
  :set (lambda (sym val)
         (set-default sym val)

(define-minor-mode narrow-undo-mode
  "Toggle Narrow Undo mode (globally).
With arg, turn Narrow Undo mode on if and only if arg is positive.
In Narrow Undo mode changes in restrictions (narrowing) is recorded
so that they can be undone typing \
\\<narrow-undo-mode-map>\\[narrow-undo] (`narrow-undo').

This works by remapping the key bindings for each command in
`narrow-undo-watch-commands' to a command which saves the current
restriction and then calls the original command.  The restriction
is not saved if the original command is called explicitly
\(e.g. typing `M-x widen [ret]')."
  nil                                   ; init value
  nil                                   ; no mode line
  narrow-undo-map                       ; Bindings
  :tag   "Narrow-undo Mode"
  :global t
  :group 'narrow-undo)

(defcustom narrow-undo-limit 5
  "Number of restrictions to remember for each buffer.
Changing this value does not affect existing buffers with
narrow-undo information."
  :tag   "Narrow-undo Limit"
  :type  'integer
  :group 'narrow-undo)

(defvar narrow-undo-ring nil)
(make-variable-buffer-local 'narrow-undo-ring)

(defun narrow-undo-save ()
  "Save current restrictions (for `narrow-undo')."
  (unless narrow-undo-ring
    (setq narrow-undo-ring (make-ring narrow-undo-limit)))
  (let ((beg (point-min))
        (end (point-max))
        (top (if (plusp (ring-length narrow-undo-ring))
                 (ring-ref narrow-undo-ring 0))))
    (unless (and top
                 (eq beg (marker-position (car top)))
                 (eq end (marker-position (cdr top))))
      (ring-insert narrow-undo-ring
                   (cons (copy-marker beg)
                         (copy-marker end t))))))

(defconst narrow-undo-complaint "No further narrow-undo information")
(let ((reg (concat "^" narrow-undo-complaint)))
  (unless (member reg debug-ignored-errors)
    (push reg debug-ignored-errors)))

(defun narrow-undo ()
  "Undo changes in restriction (narrowing) for the current buffer.
For this to work, `narrow-undo-mode' must be enabled."
   ((or (not narrow-undo-ring)
        (zerop (ring-length narrow-undo-ring)))
    (error narrow-undo-complaint))
   (t (let ((top (ring-remove narrow-undo-ring 0)))
        (narrow-to-region (car top) (cdr top))))))
(provide 'narrow-undo)

;;; narrow-undo.el ends here

reply via email to

[Prev in Thread] Current Thread [Next in Thread]