diff options
Diffstat (limited to 'lisp/smooth-scrolling.el')
| -rw-r--r-- | lisp/smooth-scrolling.el | 327 | 
1 files changed, 327 insertions, 0 deletions
diff --git a/lisp/smooth-scrolling.el b/lisp/smooth-scrolling.el new file mode 100644 index 0000000..98df30e --- /dev/null +++ b/lisp/smooth-scrolling.el @@ -0,0 +1,327 @@ +;;; smooth-scrolling.el --- Make emacs scroll smoothly +;; +;; Copyright (c) 2007-2016 Adam Spiers +;; +;; Filename: smooth-scrolling.el +;; Description: Make emacs scroll smoothly +;; Author: Adam Spiers <emacs-ss@adamspiers.org> +;;         Jeremy Bondeson <jbondeson@gmail.com> +;;         Ryan C. Thompson <rct+github@thompsonclan.org> +;; Maintainer: Adam Spiers <emacs-ss@adamspiers.org> +;; Homepage: http://github.com/aspiers/smooth-scrolling/ +;; Version: 2.0.0 +;; Keywords: convenience +;; GitHub: http://github.com/aspiers/smooth-scrolling/ + +;; This file is not part of GNU Emacs + +;;; License: +;; +;; 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 2 +;; of the License, 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. +;; +;; You should have received a copy of the GNU General Public License +;; along with this program; if not, write to the Free Software +;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA. + +;;; Commentary: + +;; To interactively toggle the mode on / off: +;; +;;     M-x smooth-scrolling-mode +;; +;; To make the mode permanent, put this in your .emacs: +;; +;;     (require 'smooth-scrolling) +;;     (smooth-scrolling-mode 1) +;; +;; This package offers a global minor mode which make emacs scroll +;; smoothly.  It keeps the point away from the top and bottom of the +;; current buffer's window in order to keep lines of context around +;; the point visible as much as possible, whilst minimising the +;; frequency of sudden scroll jumps which are visually confusing. +;; +;; This is a nice alternative to all the native `scroll-*` custom +;; variables, which unfortunately cannot provide this functionality +;; perfectly.  For example, when using the built-in variables, clicking +;; with the mouse in the margin will immediately scroll the window to +;; maintain the margin, so the text that you clicked on will no longer be +;; under the mouse.  This can be disorienting.  In contrast, this mode +;; will not do any scrolling until you actually move up or down a line. +;; +;; Also, the built-in margin code does not interact well with small +;; windows.  If the margin is more than half the window height, you get +;; some weird behavior, because the point is always hitting both the top +;; and bottom margins.  This package auto-adjusts the margin in each +;; buffer to never exceed half the window height, so the top and bottom +;; margins never overlap. + +;; See the README.md for more details. + +;;; Change Log: +;; 27 Feb 2016 -- v2.0.0 +;;      * Converted to global minor mode "smooth-scrolling-mode". This +;;        means that simply loading the file no longer enables smooth +;;        scrolling; you must also enable the mode. +;;      * Internal code restructuring that should improve some edge +;;        cases, but otherwise have no user-visible effects. +;; 19 Dec 2013 -- v1.0.4 +;;      * Disabled scrolling while a keyboard macro is executing in +;;        order to prevent a premature termination of the macro by +;;        the mode throwing an error such as "End of Buffer" +;; 02 Jun 2013 -- v1.0.3 +;;      * Fixed Issue #3 where bounds checking was not being performed +;;        prior to calls to 'count-lines' and 'count-screen-lines' +;;        functions. +;; 14 Apr 2013 -- v1.0.2 +;;      * Adam Spiers GitHub account now houses the canonical +;;        repository. +;; 06 Dec 2011 -- v1.0.1 +;;	* Altered structure to conform to package.el standards. +;;	* Restructured code to group settings changes +;;	* Set "redisplay-dont-pause" to true. +;; ?? ??? 2007 -- v1.0.0 +;;      * Original version from Adam Spiers + +;;; Code: + +;;;_ + internal variables +(defvar smooth-scroll-orig-scroll-margin nil) + +;;;_ + defcustoms + +(defgroup smooth-scrolling nil +  "Make emacs scroll smoothly" +  :group 'convenience) + +;;;###autoload +(define-minor-mode smooth-scrolling-mode +  "Make emacs scroll smoothly" +  :init-value nil +  :global t +  :group 'smooth-scrolling +  (if smooth-scrolling-mode +      (setq smooth-scroll-orig-scroll-margin scroll-margin +            scroll-margin 0) +    (setq scroll-margin smooth-scroll-orig-scroll-margin +          smooth-scroll-orig-scroll-margin nil))) + +;;;###autoload +(defcustom smooth-scroll-margin 10 +  "Number of lines of visible margin at the top and bottom of a window. +If the point is within these margins, then scrolling will occur +smoothly for `previous-line' at the top of the window, and for +`next-line' at the bottom. + +This is very similar in its goal to `scroll-margin'.  However, it +is implemented by activating `smooth-scroll-down' and +`smooth-scroll-up' advise via `defadvice' for `previous-line' and +`next-line' respectively.  As a result it avoids problems +afflicting `scroll-margin', such as a sudden jump and unexpected +highlighting of a region when the mouse is clicked in the margin. + +Scrolling only occurs when the point is closer to the window +boundary it is heading for (top or bottom) than the middle of the +window.  This is to intelligently handle the case where the +margins cover the whole buffer (e.g. `smooth-scroll-margin' set +to 5 and `window-height' returning 10 or less). + +See also `smooth-scroll-strict-margins'." +  :type  'integer +  :group 'smooth-scrolling) + +;;;###autoload +(defcustom smooth-scroll-strict-margins t +  "If true, the advice code supporting `smooth-scroll-margin' +will use `count-screen-lines' to determine the number of +*visible* lines between the point and the window top/bottom, +rather than `count-lines' which obtains the number of actual +newlines.  This is because there might be extra newlines hidden +by a mode such as folding-mode, outline-mode, org-mode etc., or +fewer due to very long lines being displayed wrapped when +`truncate-lines' is nil. + +However, using `count-screen-lines' can supposedly cause +performance issues in buffers with extremely long lines.  Setting +`cache-long-line-scans' may be able to address this; +alternatively you can set this variable to nil so that the advice +code uses `count-lines', and put up with the fact that sometimes +the point will be allowed to stray into the margin." +  :type  'boolean +  :group 'smooth-scrolling) + +;;;_ + helper functions +(defmacro smooth-scroll-ignore-scroll-errors (&rest body) +  "Like `progn', but ignores beginning/end of line errors. + +If BODY encounters such an error, further evaluation is stopped +and this form returns nil. Any other error is raised as normal." +  (declare (indent 0)) +  `(condition-case err +       (progn ,@body) +     (end-of-buffer nil) +     (beginning-of-buffer nil) +     (error (signal (car err) (cdr err))))) + +(defun smooth-scroll-line-beginning-position () +  "Return position at beginning of (logical/visual) line. + +If `smooth-scroll-strict-margins' is non-nil, this looks to the +beginning of the visual line. Otherwise it uses the beginning of +the logical line." +  (save-excursion +    ;; Cannot use `line-beginning-position' here because there is no +    ;; visual-line equivalent. +    (funcall (if smooth-scroll-strict-margins +                 #'beginning-of-visual-line +               #'beginning-of-line)) +    (point))) + +(defun smooth-scroll-count-lines (start end) +  "Return number of (logical/visual) lines between START and END. + +If `smooth-scroll-strict-margins' is non-nil, this counts visual +lines. Otherwise it counts logical lines. + +If END is less than START, this returns zero, so it is important +to pass them in order." +  (if (< end start) +      0 +    (funcall (if smooth-scroll-strict-margins +                 #'count-screen-lines +               #'count-lines) +             start end))) + +(defun smooth-scroll-lines-above-point () +  "Return the number of lines in window above point. + +This does not include the line that point is on." +  (smooth-scroll-count-lines (window-start) +                             (smooth-scroll-line-beginning-position))) + +(defun smooth-scroll-lines-below-point () +  "Return the number of lines in window above point. + +This does not include the line that point is on." +  ;; We don't rely on `window-end' because if we are scrolled near the +  ;; end of the buffer, it will only give the number of lines +  ;; remaining in the file, not the number of lines to the bottom of +  ;; the window. +  (- (window-height) 2 (smooth-scroll-lines-above-point))) + +(defun smooth-scroll-window-allowed-margin () +  "Return the maximum allowed margin above or below point. + +This only matters for windows whose height is +`smooth-scroll-margin' * 2 lines or less." +  ;; We subtract 1 for the modeline, which is counted in +  ;; `window-height', and one more for the line that point is on. Then +  ;; we divide by 2, rouding down. +  (/ (- (window-height) 2) 2)) + +(defsubst window-is-at-bob-p () +  "Returns non-nil if `(window-start)' is 1 (or less)." +  (<= (window-start) 1)) + +;;;_ + main function +(defun do-smooth-scroll () +  "Ensure that point is not to close to window edges. + +This function scrolls the window until there are at least +`smooth-scroll-margin' lines between the point and both the top +and bottom of the window. If this is not possible because the +window is too small, th window is scrolled such that the point is +roughly centered within the window." +  (interactive) +  (when smooth-scrolling-mode +    (let* ((desired-margin +            ;; For short windows, we reduce `smooth-scroll-margin' to +            ;; half the window height minus 1. +            (min (smooth-scroll-window-allowed-margin) +                 smooth-scroll-margin)) +           (upper-margin (smooth-scroll-lines-above-point)) +           (lower-margin (smooth-scroll-lines-below-point))) +      (smooth-scroll-ignore-scroll-errors +        (cond +         ((< upper-margin desired-margin) +          (save-excursion +            (dotimes (i (- desired-margin upper-margin)) +              (scroll-down 1)))) +         ((< lower-margin desired-margin) +          (save-excursion +            (dotimes (i (- desired-margin lower-margin)) +              (scroll-up 1))))))))) + +;;;_ + advice setup + +;;;###autoload +(defmacro enable-smooth-scroll-for-function (func) +  "Define advice on FUNC to do smooth scrolling. + +This adds after advice with name `smooth-scroll' to FUNC. + +Note that the advice will not have an effect unless +`smooth-scrolling-mode' is enabled." +  `(defadvice ,func (after smooth-scroll activate) +     "Do smooth scrolling after command finishes. + +This advice only has an effect when `smooth-scrolling-mode' is +enabled. See `smooth-scrolling-mode' for details. To remove this +advice, use `disable-smooth-scroll-for-function'." +     (do-smooth-scroll))) + +(defmacro enable-smooth-scroll-for-function-conditionally (func cond) +  "Define advice on FUNC to do smooth scrolling conditionally. + +This adds after advice with name `smooth-scroll' to FUNC. The +advice runs smooth scrolling if expression COND evaluates to +true. COND is included within the advice and therefore has access +to all of FUNC's arguments. + +Note that the advice will not have an effect unless +`smooth-scrolling-mode' is enabled." +  (declare (indent 1)) +  `(defadvice ,func (after smooth-scroll activate) +     ,(format "Do smooth scrolling conditionally after command finishes. + +Smooth sccrolling will only be performed if the following +expression evaluates to true after the function has run: + +%s +This advice only has an effect when `smooth-scrolling-mode' is +enabled. See `smooth-scrolling-mode' for details. To remove this +advice, use `disable-smooth-scroll-for-function'." +              (pp-to-string cond)) +     (when ,cond +       (do-smooth-scroll)))) + +(defmacro disable-smooth-scroll-for-function (func) +  "Delete smooth-scroll advice for FUNC." +  ;; This doesn't actually need to be a macro, but it is one for +  ;; consistency with the enabling macro.  Errors are ignored in case +  ;; the advice has already been removed. +  `(ignore-errors +     (ad-remove-advice ',func 'after 'smooth-scroll) +     (ad-activate ',func))) + +(progn +  (enable-smooth-scroll-for-function previous-line) +  (enable-smooth-scroll-for-function next-line) +  (enable-smooth-scroll-for-function dired-previous-line) +  (enable-smooth-scroll-for-function dired-next-line) +  (enable-smooth-scroll-for-function isearch-repeat) +  (enable-smooth-scroll-for-function-conditionally scroll-up-command +    (not (window-is-at-bob-p))) +  (enable-smooth-scroll-for-function-conditionally scroll-down-command +    (not (window-is-at-bob-p)))) + +;;;_ + provide +(provide 'smooth-scrolling) +;;; smooth-scrolling.el ends here  | 
