|
|
|
@ -0,0 +1,435 @@ |
|
|
|
;;; centered-cursor-mode.el --- cursor stays vertically centered |
|
|
|
|
|
|
|
;; Copyright (C) 2007 André Riemann |
|
|
|
|
|
|
|
;; Author: André Riemann <andre.riemann@web.de> |
|
|
|
;; Maintainer: André Riemann <andre.riemann@web.de> |
|
|
|
;; Created: 2007-09-14 |
|
|
|
;; Keywords: convenience |
|
|
|
;; Package-Version: 20150302.831 |
|
|
|
|
|
|
|
;; URL: http://www.emacswiki.org/cgi-bin/wiki/centered-cursor-mode.el |
|
|
|
;; Compatibility: tested with GNU Emacs 23.0, 24 |
|
|
|
;; Version: 0.5.4 |
|
|
|
;; Last-Updated: 2015-10-01 |
|
|
|
|
|
|
|
;; 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 |
|
|
|
;; 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 GNU Emacs; see the file COPYING. If not, write to the Free |
|
|
|
;; Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, |
|
|
|
;; MA 02110-1301, USA. |
|
|
|
|
|
|
|
;;; Commentary: |
|
|
|
|
|
|
|
;; Makes the cursor stay vertically in a defined position (usually |
|
|
|
;; centered). The vertical position can be altered, see key definition |
|
|
|
;; below. |
|
|
|
|
|
|
|
;; To load put that in .emacs: |
|
|
|
;; (require 'centered-cursor-mode) |
|
|
|
;; To activate do: |
|
|
|
;; M-x centered-cursor-mode |
|
|
|
;; for buffer local or |
|
|
|
;; M-x global-centered-cursor-mode |
|
|
|
;; for global minor mode. |
|
|
|
;; Also possible: put that in .emacs |
|
|
|
;; (and |
|
|
|
;; (require 'centered-cursor-mode) |
|
|
|
;; (global-centered-cursor-mode +1)) |
|
|
|
;; to always have centered-cursor-mode on in all buffers. |
|
|
|
|
|
|
|
;;; TODO: |
|
|
|
;; - the code is a mess |
|
|
|
;; - ccm-vpos-inverted doesn't work with ccm-vpos == 0, because first |
|
|
|
;; position from top is 0 and from bottom -1 |
|
|
|
;; - interactive first start isn't animated when calling global-... |
|
|
|
;; because it starts the modes for each buffer and interactive-p fails |
|
|
|
;; for that |
|
|
|
;; - more bugs? |
|
|
|
|
|
|
|
;;; Change Log: |
|
|
|
;; 2015-10-01 Hinrik Örn Sigurðsson <hinrik.sig@gmail.com> |
|
|
|
;; * Avoided calling count-lines when unnecessary, which |
|
|
|
;; fixes slow scrolling in large files |
|
|
|
;; 2015-03-01 andre-r |
|
|
|
;; * fixed bug where Emacs without X support (emacs-nox) didn't find mouse-wheel-mode |
|
|
|
;; 2009-08-31 andre-r |
|
|
|
;; * replaced window-body-height with window-text-height |
|
|
|
;; (partially visible lines are not counted in window-text-height) |
|
|
|
;; * bug fixed in ccm-vpos-recenter |
|
|
|
;; (some parentheses where wrong after the last update) |
|
|
|
;; 2009-02-23 andre-r |
|
|
|
;; * some simplifications |
|
|
|
;; 2009-02-22 andre-r |
|
|
|
;; * some tips from Drew Adams: |
|
|
|
;; - new local variable coding:utf-8 |
|
|
|
;; - made recenter-sequence a defvar |
|
|
|
;; - added groups scrolling and convenience |
|
|
|
;; - replaced mouse-4 and mouse-5 with |
|
|
|
;; mouse-wheel-up-event and mouse-wheel-down-event |
|
|
|
;; - added scroll-bar-toolkit-scroll to ccm-ignored-commands |
|
|
|
;; - made ccm-ignored-commands customisable |
|
|
|
;; * removed a bug where it didn't work with more than one window |
|
|
|
;; displaying the same buffer |
|
|
|
;; * added function for page up and down scrolling |
|
|
|
;; (standard ones didn't work well with this mode) |
|
|
|
;; * made the animation delay customisable |
|
|
|
;; * made the initial vertical position customisable |
|
|
|
;; * made the behaviour at the end of the file customisable |
|
|
|
;; 2008-02-02 andre-r |
|
|
|
;; * fixed bug that led to wrong-type-argument |
|
|
|
;; when opening a new buffer |
|
|
|
;; * some other minor stuff |
|
|
|
;; 2007-09-24 andre-r |
|
|
|
;; * added global minor mode |
|
|
|
;; 2007-09-21 andre-r |
|
|
|
;; * not recentering at end of buffer |
|
|
|
;; * defvar animate-first-start-p |
|
|
|
;; 2007-09-14 andre-r |
|
|
|
;; * inital release |
|
|
|
|
|
|
|
;; This file is *NOT* part of GNU Emacs. |
|
|
|
|
|
|
|
;;; Code: |
|
|
|
|
|
|
|
|
|
|
|
(require 'mouse-wheel-mode nil 'noerror) |
|
|
|
|
|
|
|
(defgroup centered-cursor nil |
|
|
|
"Makes the cursor stay vertically in a defined position (usually centered). |
|
|
|
Instead the cursor the text moves around the cursor." |
|
|
|
:group 'scrolling |
|
|
|
:group 'convenience |
|
|
|
:link '(emacs-library-link :tag "Source Lisp File" "centered-cursor-mode.el") |
|
|
|
:link '(url-link "http://www.emacswiki.org/cgi-bin/wiki/centered-cursor-mode.el")) |
|
|
|
|
|
|
|
(defcustom ccm-step-size 2 |
|
|
|
"Step size when animated recentering." |
|
|
|
:group 'centered-cursor |
|
|
|
:tag "Animation step size" |
|
|
|
:type 'integer) |
|
|
|
|
|
|
|
(defcustom ccm-step-delay 0.02 |
|
|
|
"Delay between animation steps. |
|
|
|
If you want a different animation speed." |
|
|
|
:group 'centered-cursor |
|
|
|
:tag "Animation step delay" |
|
|
|
:type 'number) |
|
|
|
|
|
|
|
(defcustom ccm-ignored-commands '(mouse-drag-region |
|
|
|
mouse-set-point |
|
|
|
widget-button-click |
|
|
|
scroll-bar-toolkit-scroll) |
|
|
|
"After these commands recentering is ignored. |
|
|
|
This is to prevent unintentional jumping (especially when mouse |
|
|
|
clicking). Following commands (except the ignored ones) will |
|
|
|
cause an animated recentering to give a feedback and not just |
|
|
|
jumping to the center." |
|
|
|
:group 'centered-cursor |
|
|
|
:tag "Ignored commands" |
|
|
|
:type '(repeat (symbol :tag "Command"))) |
|
|
|
|
|
|
|
(defcustom ccm-vpos-init '(round (window-text-height) 2) |
|
|
|
"This is the screen line position where the cursor initially stays." |
|
|
|
:group 'centered-cursor |
|
|
|
:tag "Vertical cursor position" |
|
|
|
:type '(choice (const :tag "Center" (round (window-text-height) 2)) |
|
|
|
(const :tag "Golden ratio" (round (* 21 (window-text-height)) 34)) |
|
|
|
(integer :tag "Lines from top" :value 10))) |
|
|
|
(make-variable-buffer-local 'ccm-vpos-init) |
|
|
|
|
|
|
|
(defcustom ccm-vpos-inverted 1 |
|
|
|
"Inverted vertical cursor position. |
|
|
|
Defines if the initial vertical position `ccm-vpos-init' is |
|
|
|
measured from the bottom instead from the top." |
|
|
|
:group 'centered-cursor |
|
|
|
:tag "Inverted cursor position" |
|
|
|
:type '(choice (const :tag "Inverted" -1) |
|
|
|
(const :tag "Not inverted" 1))) |
|
|
|
(make-variable-buffer-local 'ccm-vpos-inverted) |
|
|
|
|
|
|
|
(defcustom ccm-recenter-at-end-of-file nil |
|
|
|
"Recenter at the end of the file. |
|
|
|
If non-nil the end of the file is recentered. If nil the end of |
|
|
|
the file stays at the end of the window." |
|
|
|
:group 'centered-cursor |
|
|
|
:tag "Recenter at EOF" |
|
|
|
:type '(choice (const :tag "Don't recenter at the end of the file" nil) |
|
|
|
(const :tag "Recenter at the end of the file" t))) |
|
|
|
(make-variable-buffer-local 'ccm-recenter-end-of-file) |
|
|
|
|
|
|
|
(defvar ccm-vpos nil |
|
|
|
"This is the screen line position where the cursor stays.") |
|
|
|
(make-variable-buffer-local 'ccm-vpos) |
|
|
|
|
|
|
|
(defvar animate-first-start-p nil |
|
|
|
"Whether or not to animate at first start. It is set to nil, if |
|
|
|
centered-cursor-mode is called non-interactively.") |
|
|
|
(make-variable-buffer-local 'animate-first-start-p) |
|
|
|
|
|
|
|
(defvar recenter-sequence nil |
|
|
|
"Before animated recentering a list is generated first with positions |
|
|
|
to successively recenter to") |
|
|
|
(make-variable-buffer-local 'recenter-sequence) |
|
|
|
|
|
|
|
(defvar ccm-map |
|
|
|
(let ((ccm-map (make-sparse-keymap))) |
|
|
|
(define-key ccm-map [(control meta -)] 'ccm-vpos-up) |
|
|
|
(define-key ccm-map [(control meta +)] 'ccm-vpos-down) |
|
|
|
(define-key ccm-map [(control meta =)] 'ccm-vpos-down) |
|
|
|
(define-key ccm-map [(control meta ?0)] 'ccm-vpos-recenter) |
|
|
|
(when (and (boundp 'mouse-wheel-mode) mouse-wheel-mode) |
|
|
|
(mapc (lambda (key) |
|
|
|
(define-key ccm-map key 'ccm-mwheel-scroll)) |
|
|
|
(list (vector mouse-wheel-up-event) |
|
|
|
(vector mouse-wheel-down-event) |
|
|
|
(vector (list 'control mouse-wheel-up-event)) |
|
|
|
(vector (list 'control mouse-wheel-down-event)) |
|
|
|
(vector (list 'shift mouse-wheel-up-event)) |
|
|
|
(vector (list 'shift mouse-wheel-down-event))))) |
|
|
|
(define-key ccm-map [(meta v)] 'ccm-scroll-down) |
|
|
|
(define-key ccm-map [(control v)] 'ccm-scroll-up) |
|
|
|
(define-key ccm-map [prior] 'ccm-scroll-down) |
|
|
|
(define-key ccm-map [next] 'ccm-scroll-up) |
|
|
|
ccm-map) |
|
|
|
"Keymap used in centered-cursor-mode.") |
|
|
|
|
|
|
|
|
|
|
|
(defun ccm-mwheel-scroll (event) |
|
|
|
"Very similar to `mwheel-scroll', but does not use `scroll-down' |
|
|
|
and `scroll-up' but `previous-line' and `next-line', that is, the |
|
|
|
cursor is moved and thus the text in the window is scrolled |
|
|
|
due to `recenter'. |
|
|
|
|
|
|
|
The customizable variable `mouse-wheel-scroll-amount' is used to |
|
|
|
determine how much to scroll, where nil instead of a number means |
|
|
|
the same as in mwheel-scroll, scroll by a near full screen. |
|
|
|
|
|
|
|
This command exists, because mwheel-scroll caused strange |
|
|
|
behaviour with automatic recentering." |
|
|
|
;; (interactive (list last-input-event)) |
|
|
|
(interactive "e") |
|
|
|
(let* ((mods (delq 'click (delq 'double (delq 'triple (event-modifiers event))))) |
|
|
|
(amt (assoc mods mouse-wheel-scroll-amount))) |
|
|
|
;;(message "%S" mods) |
|
|
|
(if amt |
|
|
|
(setq amt (or (cdr amt) |
|
|
|
(- (window-text-height) |
|
|
|
next-screen-context-lines))) |
|
|
|
(let ((list-elt mouse-wheel-scroll-amount)) |
|
|
|
(while (consp (setq amt (pop list-elt)))))) |
|
|
|
(if mouse-wheel-follow-mouse |
|
|
|
(select-window (posn-window (event-start event)))) |
|
|
|
(let ((button (mwheel-event-button event))) |
|
|
|
(cond |
|
|
|
((eq button mouse-wheel-down-event) |
|
|
|
(forward-line (- amt))) |
|
|
|
;;(princ amt)) |
|
|
|
((eq button mouse-wheel-up-event) |
|
|
|
(forward-line amt)) |
|
|
|
;;(princ amt)) |
|
|
|
(t (error "Bad binding in ccm-mwheel-scroll")))))) |
|
|
|
|
|
|
|
(defun ccm-scroll-down (&optional arg) |
|
|
|
"Replaces `scroll-down' because with scroll-down |
|
|
|
`centered-cursor-mode' sometimes doesn't reach the top of the |
|
|
|
buffer. This version actually moves the cursor with |
|
|
|
`previous-line'. Since with centered-cursor-mode the cursor is in |
|
|
|
a fixed position the movement appears as page up." |
|
|
|
(interactive "P") |
|
|
|
(let ((amt (or arg (- (window-text-height) |
|
|
|
next-screen-context-lines)))) |
|
|
|
(forward-line (- amt)))) |
|
|
|
|
|
|
|
(defun ccm-scroll-up (&optional arg) |
|
|
|
"Replaces `scroll-up' to be consistent with `ccm-scroll-down'. |
|
|
|
This version actually moves the cursor with `previous-line'. |
|
|
|
Since with centered-cursor-mode the cursor is in a fixed position |
|
|
|
the movement appears as page up." |
|
|
|
(interactive "P") |
|
|
|
(let ((amt (or arg (- (window-text-height) |
|
|
|
next-screen-context-lines)))) |
|
|
|
(forward-line amt))) |
|
|
|
|
|
|
|
|
|
|
|
(defun ccm-vpos-down (arg) |
|
|
|
"Adjust the value of the screen line (where the cursor stays) by arg. |
|
|
|
Negative values for arg are possible. Just the variable ccm-vpos |
|
|
|
is set." |
|
|
|
(interactive "p") |
|
|
|
(or arg (setq arg 1)) |
|
|
|
(let ((new-pos (if (< ccm-vpos 0) |
|
|
|
(- ccm-vpos arg) |
|
|
|
(+ ccm-vpos arg))) |
|
|
|
;; see pos-visible-in-window-p |
|
|
|
(vpos-max (if (< ccm-vpos 0) |
|
|
|
-1 |
|
|
|
(- (window-text-height) 1))) |
|
|
|
(vpos-min (if (< ccm-vpos 0) |
|
|
|
(- (window-text-height)) |
|
|
|
0))) |
|
|
|
(setq ccm-vpos |
|
|
|
(cond |
|
|
|
((< new-pos vpos-min) |
|
|
|
vpos-min) |
|
|
|
((> new-pos vpos-max) |
|
|
|
vpos-max) |
|
|
|
(t |
|
|
|
new-pos))))) |
|
|
|
|
|
|
|
(defun ccm-vpos-up (arg) |
|
|
|
"See `ccm-vpos-down'." |
|
|
|
(interactive "p") |
|
|
|
(or arg (setq arg 1)) |
|
|
|
(ccm-vpos-down (- arg))) |
|
|
|
|
|
|
|
(defun ccm-vpos-recenter () |
|
|
|
"Set the value of the screen line (where the cursor stays) in |
|
|
|
the center. Just the variable ccm-vpos is set." |
|
|
|
(interactive) |
|
|
|
(if (equal (current-buffer) |
|
|
|
(window-buffer (selected-window))) |
|
|
|
(setq ccm-vpos (* (eval ccm-vpos-init) |
|
|
|
ccm-vpos-inverted)))) |
|
|
|
|
|
|
|
(defun ccm-position-cursor () |
|
|
|
"Do the actual recentering at the position `ccm-vpos'." |
|
|
|
(unless (member this-command ccm-ignored-commands) |
|
|
|
(unless ccm-vpos |
|
|
|
(ccm-vpos-recenter)) |
|
|
|
(unless (minibufferp (current-buffer)) |
|
|
|
(if (equal (current-buffer) |
|
|
|
(window-buffer (selected-window))) |
|
|
|
(let* ((current-line |
|
|
|
(if (< ccm-vpos 0) |
|
|
|
;; one-based, from bottom, negative |
|
|
|
(- (count-lines (point) |
|
|
|
;; window-end is sometimes < 0 |
|
|
|
;; when opening a help buffer |
|
|
|
(if (> (window-end) 0) |
|
|
|
(window-end) |
|
|
|
1))) |
|
|
|
;; zero-based, from top, positive |
|
|
|
(+ (count-lines (window-start) (point)) |
|
|
|
;; count-lines returns different value in column 0 |
|
|
|
(if (= (current-column) 0) 0 -1)))) |
|
|
|
(diff (- ccm-vpos current-line)) |
|
|
|
(step-size ccm-step-size) |
|
|
|
(step-delay ccm-step-delay) |
|
|
|
(vpos-inverted ccm-vpos-inverted) |
|
|
|
(recenter-at-end-of-file ccm-recenter-at-end-of-file)) |
|
|
|
|
|
|
|
(let* ((bottom-vpos (if (< ccm-vpos 0) |
|
|
|
(- ccm-vpos) |
|
|
|
(- (window-text-height) ccm-vpos))) |
|
|
|
(correction (save-excursion |
|
|
|
(if (or (= (point) (point-max)) |
|
|
|
(progn |
|
|
|
(goto-char (point-max)) |
|
|
|
(zerop (current-column)))) |
|
|
|
1 0))) |
|
|
|
(window-is-at-bottom (= (window-end) (point-max))) |
|
|
|
;; lines from point to end of buffer |
|
|
|
(bottom-lines (if window-is-at-bottom |
|
|
|
(+ (count-lines (point) (point-max)) |
|
|
|
correction)))) |
|
|
|
|
|
|
|
;; only animate if the point was moved rather far away |
|
|
|
;; before by a mouseclick (see ccm-ignored-commands) |
|
|
|
;; or if minor mode is just entered interactively |
|
|
|
(if (not (and (> (abs diff) 4) |
|
|
|
(or (member last-command ccm-ignored-commands) |
|
|
|
animate-first-start-p))) |
|
|
|
|
|
|
|
(recenter (if (and window-is-at-bottom |
|
|
|
(< bottom-lines bottom-vpos) |
|
|
|
(not recenter-at-end-of-file)) |
|
|
|
;; if near the bottom, recenter in the |
|
|
|
;; negative screen line that equals the |
|
|
|
;; bottom buffer line, i.e. if we are in |
|
|
|
;; the second last line (-2) of the |
|
|
|
;; buffer, the cursor will be recentered |
|
|
|
;; in -2 |
|
|
|
(- bottom-lines) |
|
|
|
ccm-vpos)) |
|
|
|
|
|
|
|
(setq animate-first-start-p nil) |
|
|
|
;; first build a list with positions to successively recenter to |
|
|
|
(setq recenter-sequence |
|
|
|
;; reverse: because we build the list not FROM -> TO but |
|
|
|
;; TO -> FROM because if step size in number-sequence is |
|
|
|
;; bigger than one, TO might not included, that means the |
|
|
|
;; ccm-vpos would not be reached |
|
|
|
;; cdr: don't recenter the current-line |
|
|
|
(if (and window-is-at-bottom |
|
|
|
(< bottom-lines bottom-vpos) |
|
|
|
(not recenter-at-end-of-file)) |
|
|
|
;; this one is for animation near the bottom |
|
|
|
(cdr (reverse (number-sequence |
|
|
|
(- bottom-lines) |
|
|
|
(if (< ccm-vpos 0) |
|
|
|
current-line |
|
|
|
(- (- (window-text-height) current-line))) |
|
|
|
(* (/ diff (abs diff)) (- step-size))))) |
|
|
|
(cdr (reverse (number-sequence |
|
|
|
ccm-vpos |
|
|
|
current-line |
|
|
|
(* (/ diff (abs diff)) (- step-size))))))) |
|
|
|
;; (message "%d %d %d (%d): %S" current-line ccm-vpos bottom-lines diff recenter-sequence) |
|
|
|
(while recenter-sequence |
|
|
|
;; actual animation |
|
|
|
(recenter (pop recenter-sequence)) |
|
|
|
(if (car recenter-sequence) (sit-for step-delay t)))))))))) |
|
|
|
|
|
|
|
(defun ccm-first-start (animate) |
|
|
|
"Called from centered-cursor-mode. Animate at first start, if |
|
|
|
centered-cursor-mode is called interactively." |
|
|
|
(let ((animate-first-start-p animate)) |
|
|
|
(ccm-vpos-recenter) |
|
|
|
(ccm-position-cursor))) |
|
|
|
|
|
|
|
;;(defalias 'ccm 'centered-cursor-mode) |
|
|
|
;;;###autoload |
|
|
|
(define-minor-mode centered-cursor-mode |
|
|
|
"Makes the cursor stay vertically in a defined |
|
|
|
position (usually centered)." |
|
|
|
:init-value nil |
|
|
|
;; :lighter nil |
|
|
|
:lighter " ¢" |
|
|
|
:keymap ccm-map |
|
|
|
(cond |
|
|
|
(centered-cursor-mode |
|
|
|
(ccm-first-start (interactive-p)) |
|
|
|
(add-hook 'post-command-hook 'ccm-position-cursor t t) |
|
|
|
(add-hook 'window-configuration-change-hook 'ccm-vpos-recenter t t)) |
|
|
|
(t |
|
|
|
(remove-hook 'post-command-hook 'ccm-position-cursor t) |
|
|
|
(remove-hook 'window-configuration-change-hook 'ccm-vpos-recenter t)))) |
|
|
|
|
|
|
|
|
|
|
|
(define-global-minor-mode global-centered-cursor-mode centered-cursor-mode |
|
|
|
centered-cursor-mode) |
|
|
|
|
|
|
|
(provide 'centered-cursor-mode) |
|
|
|
|
|
|
|
;;; Help: |
|
|
|
;; (info "(elisp)Defining Minor Modes") |
|
|
|
;; (info "(elisp)Screen Lines") |
|
|
|
;; (info "(elisp)Hooks") |
|
|
|
;; (info "(elisp)Customization") |
|
|
|
;; (find-function 'mwheel-scroll) |
|
|
|
|
|
|
|
;; Local Variables: |
|
|
|
;; coding: utf-8 |
|
|
|
;; End: |
|
|
|
|
|
|
|
;;; centered-cursor-mode.el ends here |