PageRenderTime 29ms CodeModel.GetById 25ms app.highlight 2ms RepoModel.GetById 1ms app.codeStats 0ms

Emacs Lisp | 201 lines | 101 code | 14 blank | 86 comment | 5 complexity | cff6aad284bdd5c50ab54c728d342e98 MD5 | raw file
Possible License(s): GPL-2.0
  1;; smooth-scrolling.el
  2;; $Id: smooth-scrolling.el,v 1.10 2009-12-19 01:45:28 adam Exp $
  3;; Adam Spiers <>
  5;; Make emacs scroll smoothly, keeping the point away from the top and
  6;; bottom of the current buffer's window in order to keep lines of
  7;; context around the point visible as much as possible, whilst
  8;; avoiding sudden scroll jumps which are visually confusing.
 10;; This is a nice alternative to all the native scroll-* custom
 11;; variables, which unfortunately cannot provide this functionality
 12;; perfectly.  `scroll-margin' comes closest, but has some bugs
 13;; (e.g. with handling of mouse clicks).  See
 17;; for the gory details.
 19;;;_* Installation
 21;; Put somewhere on your `load-path' and include
 23;;   (require 'smooth-scrolling)
 25;; in your .emacs initialization file.
 27;;;_* Notes
 29;; This only affects the behaviour of the `next-line' and
 30;; `previous-line' functions, usually bound to the cursor keys and
 31;; C-n/C-p, and repeated isearches (`isearch-repeat').  Other methods
 32;; of moving the point will behave as normal according to the standard
 33;; custom variables.
 35;; Prefix arguments to `next-line' and `previous-line' are honoured.
 36;; The minimum number of lines are scrolled in order to keep the
 37;; point outside the margin.
 39;; There is one case where moving the point in this fashion may cause
 40;; a jump: if the point is placed inside one of the margins by another
 41;; method (e.g. left mouse click, or M-x goto-line) and then moved in
 42;; the normal way, the advice code will scroll the minimum number of
 43;; lines in order to keep the point outside the margin.  This jump may
 44;; cause some slight confusion at first, but hopefully it is justified
 45;; by the benefit of automatically ensuring `smooth-scroll-margin'
 46;; lines of context are visible around the point as often as possible.
 48;;;_* TODO
 50;; - Maybe add option to avoid scroll jumps when point is within
 51;;   margin.
 53;;;_* Acknowledgements
 55;; Thanks to Mark Hulme-Jones and consolers on #emacs for helping
 56;; debug issues with line-wrapping.
 58;;;_* License
 60;; Released under the GNU General Public License v2 or later, with
 61;; all rights assigned to the Free Software Foundation.
 64;;;_* Code follows
 65;;;_ + disable `scroll-margin'
 66(setq scroll-margin 0)
 67;;;_ + defcustoms
 68(defcustom smooth-scroll-margin 10
 69  "Number of lines of visible margin at the top and bottom of a window.
 70If the point is within these margins, then scrolling will occur
 71smoothly for `previous-line' at the top of the window, and for
 72`next-line' at the bottom.
 74This is very similar in its goal to `scroll-margin'.  However, it
 75is implemented by activating `smooth-scroll-down' and
 76`smooth-scroll-up' advise via `defadvice' for `previous-line' and
 77`next-line' respectively.  As a result it avoids problems
 78afflicting `scroll-margin', such as a sudden jump and unexpected
 79highlighting of a region when the mouse is clicked in the margin.
 81Scrolling only occurs when the point is closer to the window
 82boundary it is heading for (top or bottom) than the middle of the
 83window.  This is to intelligently handle the case where the
 84margins cover the whole buffer (e.g. `smooth-scroll-margin' set
 85to 5 and `window-height' returning 10 or less).
 87See also `smooth-scroll-strict-margins'."
 88  :type  'integer
 89  :group 'windows)
 91(defcustom smooth-scroll-strict-margins t
 92  "If true, the advice code supporting `smooth-scroll-margin'
 93will use `count-screen-lines' to determine the number of
 94*visible* lines between the point and the window top/bottom,
 95rather than `count-lines' which obtains the number of actual
 96newlines.  This is because there might be extra newlines hidden
 97by a mode such as folding-mode, outline-mode, org-mode etc., or
 98fewer due to very long lines being displayed wrapped when
 99`truncate-lines' is nil.
101However, using `count-screen-lines' can supposedly cause
102performance issues in buffers with extremely long lines.  Setting
103`cache-long-line-scans' may be able to address this;
104alternatively you can set this variable to nil so that the advice
105code uses `count-lines', and put up with the fact that sometimes
106the point will be allowed to stray into the margin."
107  :type  'boolean
108  :group 'windows)
109;;;_ + helper functions
110(defun smooth-scroll-lines-from-window-top ()
111  "Work out, using the function indicated by
112`smooth-scroll-strict-margins', what the current screen line is,
113relative to the top of the window.  Counting starts with 1 referring
114to the top line in the window."
115  (interactive)
116  (cond ((= (window-start) (point))
117         ;; In this case, count-screen-lines would return 0, so we override.
118         1)
119        (smooth-scroll-strict-margins
120         (count-screen-lines (window-start) (point) 'count-final-newline))
121        (t
122         (count-lines (window-start) (point)))))
124(defun smooth-scroll-lines-from-window-bottom ()
125  "Work out, using the function indicated by
126`smooth-scroll-strict-margins', how many screen lines there are
127between the point and the bottom of the window.  Counting starts
128with 1 referring to the bottom line in the window."
129  (interactive)
130  (if smooth-scroll-strict-margins
131      (count-screen-lines (point) (window-end))
132    (count-lines (point) (window-end))))
133;;;_ + after advice
135(defun smooth-scroll-down ()
136  "Scroll down smoothly if cursor is within `smooth-scroll-margin'
137lines of the top of the window."
138  (and
139   ;; Only scroll down if there is buffer above the start of the window.
140   (> (line-number-at-pos (window-start)) 1)
141   (let ((lines-from-window-top
142          (smooth-scroll-lines-from-window-top)))
143     (and
144      ;; Only scroll down if we're within the top margin
145      (<= lines-from-window-top smooth-scroll-margin)
146      ;; Only scroll down if we're in the top half of the window
147      (<= lines-from-window-top
148          ;; N.B. `window-height' includes modeline, so if it returned 21,
149          ;; that would mean exactly 10 lines in the top half and 10 in
150          ;; the bottom.  22 (or any even number) means there's one in the
151          ;; middle.  In both cases the following expression will
152          ;; yield 10:
153          (/ (1- (window-height)) 2))
154      (save-excursion
155        (scroll-down
156              (1+ (- smooth-scroll-margin lines-from-window-top))))))))
158(defun smooth-scroll-up ()
159  "Scroll up smoothly if cursor is within `smooth-scroll-margin'
160lines of the bottom of the window."
161  (and
162   ;; Only scroll up if there is buffer below the end of the window.
163   (< (window-end) (buffer-end 1))
164   (let ((lines-from-window-bottom
165          (smooth-scroll-lines-from-window-bottom)))
166     (and
167      ;; Only scroll up if we're within the bottom margin
168      (<= lines-from-window-bottom smooth-scroll-margin)
169      ;; Only scroll up if we're in the bottom half of the window.
170      (<= lines-from-window-bottom
171          ;; See above notes on `window-height'.
172          (/ (1- (window-height)) 2))
173      (save-excursion
174        (scroll-up
175         (1+ (- smooth-scroll-margin lines-from-window-bottom))))))))
177(defadvice previous-line (after smooth-scroll-down
178                            (&optional arg try-vscroll)
179                            activate)
180  (smooth-scroll-down))
181(defadvice next-line (after smooth-scroll-up
182                            (&optional arg try-vscroll)
183                            activate)
184  (smooth-scroll-up))
186(defadvice isearch-repeat (after isearch-smooth-scroll
187                                 (direction)
188                                 activate)
189  (if (eq direction 'forward)
190      (smooth-scroll-up)
191    (smooth-scroll-down)))
193;;;_ + provide
194(provide 'smooth-scrolling)
196;;;_* Local emacs variables
198;;Local variables:
199;;allout-layout: (0 : -1 0)
200;;mode: allout