├── README.md └── smooth-scrolling.el /README.md: -------------------------------------------------------------------------------- 1 | # `smooth-scrolling.el` 2 | 3 | **THIS PACKAGE IS LARGELY UNMAINTAINED. It is recommended to consider 4 | other options such the native `M-x pixel-scroll-mode`, available since 5 | Emacs 26, or alternatives such as [ultra-scroll](https://github.com/jdtsmith/ultra-scroll).** 6 | 7 | If you feel there is still value in keeping this package alive, or 8 | are willing to take over maintainership, please comment in 9 | . 10 | 11 | ## About 12 | 13 | This package offers a minor mode which make emacs scroll smoothly. It 14 | keeps the point away from the top and bottom of the current buffer's 15 | window in order to keep lines of context around the point visible as 16 | much as possible, whilst minimising the frequency of sudden scroll 17 | jumps which are visually confusing. 18 | 19 | This is a nice alternative to all the native `scroll-*` custom 20 | variables, which unfortunately cannot provide this functionality 21 | perfectly. For example, when using the built-in variables, clicking 22 | with the mouse in the margin will immediately scroll the window to 23 | maintain the margin, so the text that you clicked on will no longer be 24 | under the mouse. This can be disorienting. In contrast, this mode 25 | will not do any scrolling until you actually move up or down a line. 26 | 27 | Also, the built-in margin code does not interact well with small 28 | windows. If the margin is more than half the window height, you get 29 | some weird behavior, because the point is always hitting both the top 30 | and bottom margins. This package auto-adjusts the margin in each 31 | buffer to never exceed half the window height, so the top and bottom 32 | margins never overlap. 33 | 34 | See also emacswiki's 35 | [SmoothScrolling page](http://www.emacswiki.org/cgi-bin/wiki/SmoothScrolling) 36 | for more information, although at the time of writing, its content 37 | probably did more to confuse than enlighten. 38 | 39 | ## Installation 40 | 41 | You have various options, including the following: 42 | 43 | * Install the [package](https://melpa.org/#/smooth-scrolling) 44 | from [MELPA](https://melpa.org/#/getting-started) 45 | (see this [friendly quickstart guide](http://ergoemacs.org/emacs/emacs_package_system.html)) 46 | * Install via [`el-get`](https://github.com/dimitri/el-get/blob/master/README.md) 47 | * Simply download this repository and place the elisp file 48 | somewhere on your [`load-path`](https://www.emacswiki.org/emacs/LoadPath). 49 | 50 | ## Usage 51 | 52 | To interactively toggle the mode on / off: 53 | 54 | M-x smooth-scrolling-mode 55 | 56 | To make the mode permanent, put this in your .emacs: 57 | 58 | (require 'smooth-scrolling) 59 | (smooth-scrolling-mode 1) 60 | 61 | ## Difference with `smooth-scroll.el` 62 | 63 | This package should not be confused with the similarly-named 64 | [`smooth-scroll.el`](https://www.emacswiki.org/emacs/smooth-scroll.el), 65 | which has similar goals but takes a different approach, requiring 66 | navigation keys to be bound to dedicated 67 | `scroll-{up,down,left,right}-1` functions. 68 | 69 | ## Notes 70 | 71 | This only affects the behaviour of the `next-line` and `previous-line` 72 | functions, usually bound to the cursor keys and `C-n`/`C-p`, and 73 | repeated isearches (`isearch-repeat`). Other methods of moving the 74 | point will behave as normal according to the standard custom 75 | variables. 76 | 77 | Prefix arguments to `next-line` and `previous-line` are honored. The 78 | minimum number of lines are scrolled in order to keep the point 79 | outside the margin. 80 | 81 | There is one case where moving the point in this fashion may cause a 82 | jump: if the point is placed inside one of the margins by another 83 | method (e.g. left mouse click, or `M-x goto-line`) and then moved in 84 | the normal way, the advice code will scroll the minimum number of 85 | lines in order to keep the point outside the margin. This jump may 86 | cause some slight confusion at first, but hopefully it is justified by 87 | the benefit of automatically ensuring `smooth-scroll-margin` lines of 88 | context are visible around the point as often as possible. 89 | 90 | ## TODO 91 | 92 | - Maybe add option to avoid scroll jumps when point is within margin. 93 | - Minimize the number of autoloads in the file. Currently 94 | everything is marked as such. 95 | 96 | ## Authors 97 | 98 | Originally written by Adam Spiers, it was made into a proper ELPA 99 | package by Jeremy Bondeson, and later converted into a minor mode by 100 | Ryan C. Thompson. 101 | 102 | Thanks also to Mark Hulme-Jones and consolers on #emacs for helping 103 | debug issues with line-wrapping in the original implementation. 104 | -------------------------------------------------------------------------------- /smooth-scrolling.el: -------------------------------------------------------------------------------- 1 | ;;; smooth-scrolling.el --- Make emacs scroll smoothly 2 | ;; 3 | ;; Copyright (c) 2007-2016 Adam Spiers 4 | ;; 5 | ;; Filename: smooth-scrolling.el 6 | ;; Description: Make emacs scroll smoothly 7 | ;; Author: Adam Spiers 8 | ;; Jeremy Bondeson 9 | ;; Ryan C. Thompson 10 | ;; Maintainer: Adam Spiers 11 | ;; Homepage: http://github.com/aspiers/smooth-scrolling/ 12 | ;; Version: 2.0.0 13 | ;; Keywords: convenience 14 | ;; GitHub: http://github.com/aspiers/smooth-scrolling/ 15 | 16 | ;; This file is not part of GNU Emacs 17 | 18 | ;;; License: 19 | ;; 20 | ;; This program is free software; you can redistribute it and/or 21 | ;; modify it under the terms of the GNU General Public License 22 | ;; as published by the Free Software Foundation; either version 2 23 | ;; of the License, or (at your option) any later version. 24 | ;; 25 | ;; This program is distributed in the hope that it will be useful, 26 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 27 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 28 | ;; GNU General Public License for more details. 29 | ;; 30 | ;; You should have received a copy of the GNU General Public License 31 | ;; along with this program; if not, write to the Free Software 32 | ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 33 | 34 | ;;; Commentary: 35 | 36 | ;; To interactively toggle the mode on / off: 37 | ;; 38 | ;; M-x smooth-scrolling-mode 39 | ;; 40 | ;; To make the mode permanent, put this in your .emacs: 41 | ;; 42 | ;; (require 'smooth-scrolling) 43 | ;; (smooth-scrolling-mode 1) 44 | ;; 45 | ;; This package offers a global minor mode which make emacs scroll 46 | ;; smoothly. It keeps the point away from the top and bottom of the 47 | ;; current buffer's window in order to keep lines of context around 48 | ;; the point visible as much as possible, whilst minimising the 49 | ;; frequency of sudden scroll jumps which are visually confusing. 50 | ;; 51 | ;; This is a nice alternative to all the native `scroll-*` custom 52 | ;; variables, which unfortunately cannot provide this functionality 53 | ;; perfectly. For example, when using the built-in variables, clicking 54 | ;; with the mouse in the margin will immediately scroll the window to 55 | ;; maintain the margin, so the text that you clicked on will no longer be 56 | ;; under the mouse. This can be disorienting. In contrast, this mode 57 | ;; will not do any scrolling until you actually move up or down a line. 58 | ;; 59 | ;; Also, the built-in margin code does not interact well with small 60 | ;; windows. If the margin is more than half the window height, you get 61 | ;; some weird behavior, because the point is always hitting both the top 62 | ;; and bottom margins. This package auto-adjusts the margin in each 63 | ;; buffer to never exceed half the window height, so the top and bottom 64 | ;; margins never overlap. 65 | 66 | ;; See the README.md for more details. 67 | 68 | ;;; Change Log: 69 | ;; 27 Feb 2016 -- v2.0.0 70 | ;; * Converted to global minor mode "smooth-scrolling-mode". This 71 | ;; means that simply loading the file no longer enables smooth 72 | ;; scrolling; you must also enable the mode. 73 | ;; * Internal code restructuring that should improve some edge 74 | ;; cases, but otherwise have no user-visible effects. 75 | ;; 19 Dec 2013 -- v1.0.4 76 | ;; * Disabled scrolling while a keyboard macro is executing in 77 | ;; order to prevent a premature termination of the macro by 78 | ;; the mode throwing an error such as "End of Buffer" 79 | ;; 02 Jun 2013 -- v1.0.3 80 | ;; * Fixed Issue #3 where bounds checking was not being performed 81 | ;; prior to calls to 'count-lines' and 'count-screen-lines' 82 | ;; functions. 83 | ;; 14 Apr 2013 -- v1.0.2 84 | ;; * Adam Spiers GitHub account now houses the canonical 85 | ;; repository. 86 | ;; 06 Dec 2011 -- v1.0.1 87 | ;; * Altered structure to conform to package.el standards. 88 | ;; * Restructured code to group settings changes 89 | ;; * Set "redisplay-dont-pause" to true. 90 | ;; ?? ??? 2007 -- v1.0.0 91 | ;; * Original version from Adam Spiers 92 | 93 | ;;; Code: 94 | 95 | ;;;_ + internal variables 96 | (defvar smooth-scroll-orig-scroll-margin nil) 97 | 98 | ;;;_ + defcustoms 99 | 100 | (defgroup smooth-scrolling nil 101 | "Make emacs scroll smoothly" 102 | :group 'convenience) 103 | 104 | ;;;###autoload 105 | (define-minor-mode smooth-scrolling-mode 106 | "Make emacs scroll smoothly" 107 | :init-value nil 108 | :global t 109 | :group 'smooth-scrolling 110 | (if smooth-scrolling-mode 111 | (setq smooth-scroll-orig-scroll-margin scroll-margin 112 | scroll-margin 0) 113 | (setq scroll-margin smooth-scroll-orig-scroll-margin 114 | smooth-scroll-orig-scroll-margin nil))) 115 | 116 | ;;;###autoload 117 | (defcustom smooth-scroll-margin 10 118 | "Number of lines of visible margin at the top and bottom of a window. 119 | If the point is within these margins, then scrolling will occur 120 | smoothly for `previous-line' at the top of the window, and for 121 | `next-line' at the bottom. 122 | 123 | This is very similar in its goal to `scroll-margin'. However, it 124 | is implemented by activating `smooth-scroll-down' and 125 | `smooth-scroll-up' advise via `defadvice' for `previous-line' and 126 | `next-line' respectively. As a result it avoids problems 127 | afflicting `scroll-margin', such as a sudden jump and unexpected 128 | highlighting of a region when the mouse is clicked in the margin. 129 | 130 | Scrolling only occurs when the point is closer to the window 131 | boundary it is heading for (top or bottom) than the middle of the 132 | window. This is to intelligently handle the case where the 133 | margins cover the whole buffer (e.g. `smooth-scroll-margin' set 134 | to 5 and `window-height' returning 10 or less). 135 | 136 | See also `smooth-scroll-strict-margins'." 137 | :type 'integer 138 | :group 'smooth-scrolling) 139 | 140 | ;;;###autoload 141 | (defcustom smooth-scroll-strict-margins t 142 | "If true, the advice code supporting `smooth-scroll-margin' 143 | will use `count-screen-lines' to determine the number of 144 | *visible* lines between the point and the window top/bottom, 145 | rather than `count-lines' which obtains the number of actual 146 | newlines. This is because there might be extra newlines hidden 147 | by a mode such as folding-mode, outline-mode, org-mode etc., or 148 | fewer due to very long lines being displayed wrapped when 149 | `truncate-lines' is nil. 150 | 151 | However, using `count-screen-lines' can supposedly cause 152 | performance issues in buffers with extremely long lines. Setting 153 | `cache-long-line-scans' may be able to address this; 154 | alternatively you can set this variable to nil so that the advice 155 | code uses `count-lines', and put up with the fact that sometimes 156 | the point will be allowed to stray into the margin." 157 | :type 'boolean 158 | :group 'smooth-scrolling) 159 | 160 | ;;;_ + helper functions 161 | (defmacro smooth-scroll-ignore-scroll-errors (&rest body) 162 | "Like `progn', but ignores beginning/end of line errors. 163 | 164 | If BODY encounters such an error, further evaluation is stopped 165 | and this form returns nil. Any other error is raised as normal." 166 | (declare (indent 0)) 167 | `(condition-case err 168 | (progn ,@body) 169 | (end-of-buffer nil) 170 | (beginning-of-buffer nil) 171 | (error (signal (car err) (cdr err))))) 172 | 173 | (defun smooth-scroll-line-beginning-position () 174 | "Return position at beginning of (logical/visual) line. 175 | 176 | If `smooth-scroll-strict-margins' is non-nil, this looks to the 177 | beginning of the visual line. Otherwise it uses the beginning of 178 | the logical line." 179 | (save-excursion 180 | ;; Cannot use `line-beginning-position' here because there is no 181 | ;; visual-line equivalent. 182 | (funcall (if smooth-scroll-strict-margins 183 | #'beginning-of-visual-line 184 | #'beginning-of-line)) 185 | (point))) 186 | 187 | (defun smooth-scroll-count-lines (start end) 188 | "Return number of (logical/visual) lines between START and END. 189 | 190 | If `smooth-scroll-strict-margins' is non-nil, this counts visual 191 | lines. Otherwise it counts logical lines. 192 | 193 | If END is less than START, this returns zero, so it is important 194 | to pass them in order." 195 | (if (< end start) 196 | 0 197 | (funcall (if smooth-scroll-strict-margins 198 | #'count-screen-lines 199 | #'count-lines) 200 | start end))) 201 | 202 | (defun smooth-scroll-lines-above-point () 203 | "Return the number of lines in window above point. 204 | 205 | This does not include the line that point is on." 206 | (smooth-scroll-count-lines (window-start) 207 | (smooth-scroll-line-beginning-position))) 208 | 209 | (defun smooth-scroll-lines-below-point () 210 | "Return the number of lines in window above point. 211 | 212 | This does not include the line that point is on." 213 | ;; We don't rely on `window-end' because if we are scrolled near the 214 | ;; end of the buffer, it will only give the number of lines 215 | ;; remaining in the file, not the number of lines to the bottom of 216 | ;; the window. 217 | (- (window-height) 2 (smooth-scroll-lines-above-point))) 218 | 219 | (defun smooth-scroll-window-allowed-margin () 220 | "Return the maximum allowed margin above or below point. 221 | 222 | This only matters for windows whose height is 223 | `smooth-scroll-margin' * 2 lines or less." 224 | ;; We subtract 1 for the modeline, which is counted in 225 | ;; `window-height', and one more for the line that point is on. Then 226 | ;; we divide by 2, rouding down. 227 | (/ (- (window-height) 2) 2)) 228 | 229 | (defsubst window-is-at-bob-p () 230 | "Returns non-nil if `(window-start)' is 1 (or less)." 231 | (<= (window-start) 1)) 232 | 233 | ;;;_ + main function 234 | (defun do-smooth-scroll () 235 | "Ensure that point is not to close to window edges. 236 | 237 | This function scrolls the window until there are at least 238 | `smooth-scroll-margin' lines between the point and both the top 239 | and bottom of the window. If this is not possible because the 240 | window is too small, th window is scrolled such that the point is 241 | roughly centered within the window." 242 | (interactive) 243 | (when smooth-scrolling-mode 244 | (let* ((desired-margin 245 | ;; For short windows, we reduce `smooth-scroll-margin' to 246 | ;; half the window height minus 1. 247 | (min (smooth-scroll-window-allowed-margin) 248 | smooth-scroll-margin)) 249 | (upper-margin (smooth-scroll-lines-above-point)) 250 | (lower-margin (smooth-scroll-lines-below-point))) 251 | (smooth-scroll-ignore-scroll-errors 252 | (cond 253 | ((< upper-margin desired-margin) 254 | (save-excursion 255 | (dotimes (i (- desired-margin upper-margin)) 256 | (scroll-down 1)))) 257 | ((< lower-margin desired-margin) 258 | (save-excursion 259 | (dotimes (i (- desired-margin lower-margin)) 260 | (scroll-up 1))))))))) 261 | 262 | ;;;_ + advice setup 263 | 264 | ;;;###autoload 265 | (defmacro enable-smooth-scroll-for-function (func) 266 | "Define advice on FUNC to do smooth scrolling. 267 | 268 | This adds after advice with name `smooth-scroll' to FUNC. 269 | 270 | Note that the advice will not have an effect unless 271 | `smooth-scrolling-mode' is enabled." 272 | `(defadvice ,func (after smooth-scroll activate) 273 | "Do smooth scrolling after command finishes. 274 | 275 | This advice only has an effect when `smooth-scrolling-mode' is 276 | enabled. See `smooth-scrolling-mode' for details. To remove this 277 | advice, use `disable-smooth-scroll-for-function'." 278 | (do-smooth-scroll))) 279 | 280 | (defmacro enable-smooth-scroll-for-function-conditionally (func cond) 281 | "Define advice on FUNC to do smooth scrolling conditionally. 282 | 283 | This adds after advice with name `smooth-scroll' to FUNC. The 284 | advice runs smooth scrolling if expression COND evaluates to 285 | true. COND is included within the advice and therefore has access 286 | to all of FUNC's arguments. 287 | 288 | Note that the advice will not have an effect unless 289 | `smooth-scrolling-mode' is enabled." 290 | (declare (indent 1)) 291 | `(defadvice ,func (after smooth-scroll activate) 292 | ,(format "Do smooth scrolling conditionally after command finishes. 293 | 294 | Smooth sccrolling will only be performed if the following 295 | expression evaluates to true after the function has run: 296 | 297 | %s 298 | This advice only has an effect when `smooth-scrolling-mode' is 299 | enabled. See `smooth-scrolling-mode' for details. To remove this 300 | advice, use `disable-smooth-scroll-for-function'." 301 | (pp-to-string cond)) 302 | (when ,cond 303 | (do-smooth-scroll)))) 304 | 305 | (defmacro disable-smooth-scroll-for-function (func) 306 | "Delete smooth-scroll advice for FUNC." 307 | ;; This doesn't actually need to be a macro, but it is one for 308 | ;; consistency with the enabling macro. Errors are ignored in case 309 | ;; the advice has already been removed. 310 | `(ignore-errors 311 | (ad-remove-advice ',func 'after 'smooth-scroll) 312 | (ad-activate ',func))) 313 | 314 | (progn 315 | (enable-smooth-scroll-for-function previous-line) 316 | (enable-smooth-scroll-for-function next-line) 317 | (enable-smooth-scroll-for-function dired-previous-line) 318 | (enable-smooth-scroll-for-function dired-next-line) 319 | (enable-smooth-scroll-for-function isearch-repeat) 320 | (enable-smooth-scroll-for-function-conditionally scroll-up-command 321 | (not (window-is-at-bob-p))) 322 | (enable-smooth-scroll-for-function-conditionally scroll-down-command 323 | (not (window-is-at-bob-p)))) 324 | 325 | ;;;_ + provide 326 | (provide 'smooth-scrolling) 327 | ;;; smooth-scrolling.el ends here 328 | --------------------------------------------------------------------------------