I'm trying to define a function which will keep my fixed width body in Emacs centered in the buffer rather than aligned to the left side. In particular I want this to be buffer-local, and I'm trying to do this by locally setting left- and right-margin-width.
This works fine by itself, but I also want a hook to adjust the margins when the window size changes, and this is causing problems for me. Here's the code (adapted from https://stackoverflow.com/a/23731757/3822233):
(defun center-body ()
(let* ((max-text-width 70)
(margin (max 0 (/ (- (window-width) max-text-width) 2))))
(setq-local left-margin-width margin)
(setq-local right-margin-width margin)
(set-window-buffer nil (current-buffer))))
(defun uncenter-body ()
(setq-local left-margin-width 0)
(setq-local right-margin-width 0)
(set-window-buffer nil (current-buffer)))
(defun body-center-mode ()
(interactive)
(if (= left-margin-width 0)
(progn
(center-body))
(add-hook 'window-configuration-change-hook 'center-body nil 1))
(uncenter-body)
(remove-hook 'window-configuration-change-hook 'center-body 1)))
When the add-hook and remove-hook lines are removed, everthing's ok. But as soon as I call the add-hook I get a nesting exceeds max-lisp-eval-depth error.
I don't really understand lisp so I'm having trouble debugging this.
Most likely set-window-buffer will end up running window-configuration-change-hook.
So I'd recommend you only call set-window-buffer when left-margin-width or right-margin-width actually changes.
But in any case you'll get into trouble as soon as your buffer gets displayed in 2 or more windows of different width.
So the better answer is to not use left-margin-width and right-margin-width but instead to use set-window-margins.
Not only this will not call window-configuration-change-hook and hence won't suffer from this infinite recursion problem, but on top of that, it will happily adjust the margins differently for every window that displays the buffer, depending on those windows's widths.
Related
Emacs (and all other text editors) by default show blank space below the bottom lines of a buffer. I want emacs to be able to also scroll above/show blank space like this above the top lines in a buffer as well so that the top lines can be viewed in the center of the screen for small files.
With guidance from Thomas's answer here I have created a minor mode for this which is now available on MELPA:
TopSpace - Recenter line 1 with scrollable upper margin/padding
I've started a small minor-mode to accomplish this. However, as first versions go, it's probably buggy, doesn't handle all edge cases (such as e.g., narrowing), and isn't particularly efficient.
Thus, I'd be happy to accept improvements: feel free to directly edit this answer if you can make this code better or to extend it.
(defvar vertical-center-num-buffers 0
"The number of buffers in which `vertical-center-mode' is activated.")
(define-minor-mode vertical-center-mode
"This minor mode displays the contents of a buffer vertically
centered with respect to the window height. This, of course, only
makes sense for buffers whose content is shorter than the window
height."
nil
" vc"
nil
;; is the mode being turned on or off?
(if vertical-center-mode
;; on
(progn
;; keep track of the number of lines in the buffer
(setq-local vertical-center-num-lines (count-lines (point-min) (point-max)))
;; use an overlay to display empty lines at the beginning of the buffer
(setq-local vertical-center-overlay (make-overlay (point-min) (point-max)))
;; initial call to the function that centers the buffer contents
(vertical-center--lines-changed 0)
;; react to changes to the buffer or the window
(add-hook 'kill-buffer-hook 'vertical-center--kill-buffer)
(add-hook 'window-size-change-functions 'vertical-center--window-size-changed)
(when (= vertical-center-num-buffers 0)
(add-hook 'before-change-functions 'vertical-center--before-change)
(add-hook 'after-change-functions 'vertical-center--after-change))
;; this is just to play nice and remove the above hook
;; functions when they're no longer needed. Let's keep our
;; fingers crossed that we'll always stay in sync.
(setq vertical-center-num-buffers (1+ vertical-center-num-buffers)))
;; off
;; delete/unset data structures when the mode is turned off
(delete-overlay vertical-center-overlay)
(makunbound 'vertical-center-num-lines)
(makunbound 'vertical-center-overlay)
(setq vertical-center-num-buffers (1- vertical-center-num-buffers))
;; remove hook functions when they're no longer needed
(when (= vertical-center-num-buffers 0)
(remove-hook 'kill-buffer-hook 'vertical-center--kill-buffer)
(remove-hook 'window-size-change-functions 'vertical-center--window-size-changed)
(remove-hook 'before-change-functions 'vertical-center--before-change)
(remove-hook 'after-change-functions 'vertical-center--after-change))))
;; handle killing of buffers
(defun vertical-center--kill-buffer ()
(when vertical-center-mode
(setq vertical-center-num-buffers (1- vertical-center-num-buffers))))
;; react to changes in the window height
(defun vertical-center--window-size-changed (arg)
(vertical-center--lines-changed 0))
;; handle deletions of buffer text
(defun vertical-center--before-change (beginning end)
(when (boundp 'vertical-center-num-lines)
(let ((num-lines 0))
(while (< beginning end)
(when (= (char-after beginning) ?\n)
(setq num-lines (1- num-lines)))
(setq beginning (1+ beginning)))
(when (< num-lines 0)
(vertical-center--lines-changed num-lines)))))
;; handle insertions into the buffer
(defun vertical-center--after-change (beginning end previous-length)
(when (boundp 'vertical-center-num-lines)
(let ((num-lines 0))
(while (< beginning end)
(when (= (char-after beginning) ?\n)
(setq num-lines (1+ num-lines)))
(setq beginning (1+ beginning)))
(when (> num-lines 0)
(vertical-center--lines-changed num-lines)))))
;; update the display when either the buffer content or the window
;; height has changed
(defun vertical-center--lines-changed (num-lines)
(setq vertical-center-num-lines (+ vertical-center-num-lines num-lines))
(let ((top-margin (/ (- (window-height) vertical-center-num-lines) 2)))
;; set the top margin
(overlay-put vertical-center-overlay 'before-string
(when (> top-margin 0)
(make-string top-margin ?\n)))))
Save the above code in a file named "vertical-center.el" in a directory of your choice, and then add the following lines to your .emacs file:
(setq load-path (append load-path "<directory>"))
(autoload 'vertical-center-mode "vertical-center")
Here, <directory> should be the path to the directory in which you saved the "vertical-center.el" file.
After restarting Emacs, you can now activate or deactivate the mode by typing M-x vertical-center-mode.
I am a historian trying to configure Emacs to efficiently write academic papers. With that aim in mind, I decided to add margins to the Emacs buffer. The end result looks like this, and was achieved with this code:
;; Margins function
(defun my-set-margins ()
"Set margins in current buffer."
(setq left-margin-width 26)
(setq right-margin-width 26))
;; Add margins by default to a mode
(add-hook 'org-mode-hook 'my-set-margins)
The problem is that when I split the screen vertically, the margins make the text unreadable. So, I came to the conclusion that it would be necessary to either (1) have Emacs remove the margins automatically during vertical split screen and put them back when in single screen; or (2) create a keyboard shortcut to toggle the margins. Any elips ideas on how to do either? Thanks in advance!
The documentation states that setting the left-margin-width or right-margin-width does not immediately affect the window. These variables are checked when a new buffer is displayed in the window. Thus, you can make changes take effect by calling set-window-buffer. https://www.gnu.org/software/emacs/manual/html_node/elisp/Display-Margins.html
The following function checks to see whether either margin is greater than 0, and toggles either on or off based thereon. I bound the interactive function to the global-map with the f5 key.
(defun my-toggle-margins ()
"Set margins in current buffer."
(interactive)
(if (or (> left-margin-width 0) (> right-margin-width 0))
(progn
(setq left-margin-width 0)
(setq right-margin-width 0)
(set-window-buffer (selected-window) (current-buffer)))
(setq left-margin-width 26)
(setq right-margin-width 26)
(set-window-buffer (selected-window) (current-buffer))))
(global-set-key [f5] 'my-toggle-margins)
I'm looking for some assistance, please, to programmatically scroll a particular window to the very beginning without incurring an error beginning of buffer (which necessarily halts the function). The current (point) is within the visible window, but near the bottom. The number of lines to scroll-down may vary, so I cannot say for sure exactly how many is needed -- but the very beginning of the buffer is what I'm looking for. I don't think (save-excursion (goto-char (point-min))) will do what I want.
scroll-down does not have an argument for a specific window, so if the solution to this question could also include an example to perform the action with a specific window (without switching to it), that would be greatly appreciated. In other words, I'd like to be in a different window while this function is going on in the target window.
(when
(not (pos-visible-in-window-p (point-min)
(get-buffer-window calendar-buffer (selected-frame))))
(scroll-down 20))
You can wrap scroll-down in a condition-case to regain control when an error is signaled:
(condition-case nil
(scroll-down 20)
(error nil))
I wasn't exactly sure if you wanted your last code snippet was part of the function you were referring to, but you can wrap it all in a save-selected-window:
(let ((cal (get-buffer-window calendar-buffer (selected-frame))))
(when cal
(save-selected-window
(select-window cal)
(condition-case nil
(scroll-down 20)
(error nil)))))
EDIT: jpkotta suggested ignore-errors in the comment, which I agree looks cleaner:
(ignore-errors
(scroll-down 20))
(let ((win (get-buffer-window calendar-buffer (selected-frame))))
(when (not (pos-visible-in-window-p (point-min) win))
(set-window-start win (point-min))))
`set-window-start` is a built-in function in `C source code'.
(set-window-start WINDOW POS &optional NOFORCE)
Make display in WINDOW start at position POS in WINDOW's buffer.
WINDOW must be a live window and defaults to the selected one. Return
POS. Optional third arg NOFORCE non-nil inhibits next redisplay from
overriding motion of point in order to display at this exact start.
See also http://www.gnu.org/software/emacs/manual/html_node/elisp/Window-Start-and-End.html
See also http://www.gnu.org/software/emacs/manual/html_node/elisp/Selecting-Windows.html
Emacs erc keep recentering. It is very hard to keep up with the conversations in a small buffer because of it.
I've tried the followings but none seems to work...
(erc-scrolltobottom-mode)
(require 'erc-goodies)
(setq erc-input-line-position -1)
(erc-add-scroll-to-bottom)
(add-hook 'erc-mode-hook 'erc-add-scroll-to-bottom)
(add-hook 'erc-insert-post-hook 'erc-scroll-to-bottom)
Is there a way to keep erc from recentering?
The issue is that Emacs (and not erc) tries to recenter the screen every time, the cursor moves out of visible portion. See the docstring of scroll-conservatively(C-hvscroll-conservativelyRET)
Scroll up to this many lines, to bring point back on screen. If point
moves off-screen, redisplay will scroll by up to
`scroll-conservatively' lines in order to bring point just barely onto
the screen again. If that cannot be done, then redisplay recenters
point as usual.
If the value is greater than 100, redisplay will never recenter point,
but will always scroll just enough text to bring point into view, even
if you move far away.
A value of zero means always recenter point if it moves off screen.
So setting scroll-conservatively in erc-mode-hook might do the trick
(add-to-list 'erc-mode-hook (lambda ()
(set (make-local-variable 'scroll-conservatively) 100)))
Actually there are a lot of ways to achieve what you want. Have a look at this section of the Emacs manual
SOLUTION # 1: Comment out line 101 of the Github source code in the following link -- i.e., comment out the line of code that looks like this: (recenter (or erc-input-line-position -1))
https://github.com/emacsmirror/erc/blob/master/erc-goodies.el
(defun erc-scroll-to-bottom (window display-start)
"Recenter WINDOW so that `point' is on the last line.
This is added to `window-scroll-functions' by `erc-add-scroll-to-bottom'.
You can control which line is recentered to by customizing the
variable `erc-input-line-position'.
DISPLAY-START is ignored."
(if (window-live-p window)
;; Temporarily bind resize-mini-windows to nil so that users who have it
;; set to a non-nil value will not suffer from premature minibuffer
;; shrinkage due to the below recenter call. I have no idea why this
;; works, but it solves the problem, and has no negative side effects.
;; (Fran Litterio, 2003/01/07)
(let ((resize-mini-windows nil))
(erc-with-selected-window window
(save-restriction
(widen)
(when (and erc-insert-marker
;; we're editing a line. Scroll.
(> (point) erc-insert-marker))
(save-excursion
(goto-char (point-max))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; LINE 101 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; (recenter (or erc-input-line-position -1))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(sit-for 0))))))))
SOLUTION # 2: In your .emacs file, you can create a new function and a defalias like this:
(require 'erc-goodies)
(erc-scrolltobottom-mode)
(setq erc-input-line-position -1)
(erc-add-scroll-to-bottom)
(add-hook 'erc-mode-hook 'erc-add-scroll-to-bottom)
(add-hook 'erc-insert-post-hook 'erc-scroll-to-bottom)
(defun guimobob-erc-scroll-to-bottom (window display-start)
"Recenter WINDOW so that `point' is on the last line.
This is added to `window-scroll-functions' by `erc-add-scroll-to-bottom'.
You can control which line is recentered to by customizing the
variable `erc-input-line-position'.
DISPLAY-START is ignored."
(if (window-live-p window)
;; Temporarily bind resize-mini-windows to nil so that users who have it
;; set to a non-nil value will not suffer from premature minibuffer
;; shrinkage due to the below recenter call. I have no idea why this
;; works, but it solves the problem, and has no negative side effects.
;; (Fran Litterio, 2003/01/07)
(let ((resize-mini-windows nil))
(erc-with-selected-window window
(save-restriction
(widen)
(when (and erc-insert-marker
;; we're editing a line. Scroll.
(> (point) erc-insert-marker))
(save-excursion
(goto-char (point-max))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; (recenter (or erc-input-line-position -1))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(sit-for 0))))))))
(defalias 'erc-scroll-to-bottom 'guimobob-erc-scroll-to-bottom)
In Emacs 21.x I don't know if via a specific customization of split-window or due to a different default behaviour by Emacs, invoking the split-window-below besides splitting the window, it switched the buffer in the non-focused window to the next buffer.
Currently (Emacs 24.x), the split-window and siblings split-window-below and split-window-right don't seem to allow such a customization. Is this true?
If so, how to tweak Emacs to have this behaviour? Redefining split-window or split-window-below and split-window-right to have an extra step of switching to the next on the non-focused window. This could be done with advices:
(defun split-window-and-next-buffer (new-window)
(let ((old-window (selected-window)))
(select-window new-window)
(next-buffer)
(select-window old-window)
new-window))
(defadvice split-window-right (after split-window-right-and-next-buffer
activate protect compile)
(split-window-and-next-buffer ad-return-value))
(defadvice split-window-below (after split-window-bellow-and-next-buffer
activate protect compile)
(split-window-and-next-buffer ad-return-value))
With the corrections indicated by lawlist which are already available above the advices already work and I get the intended behaviour, but it isn't customizable to have the old behaviour.
In response to the question, the original poster might want try changing the spelling of the word below within the code posted.
This function adds three lines of code (at the end) to the current version of Emacs Trunk split-window-below and renames the function to lawlist-split-window-below with a defalias. One closing parentheses was moved to the end of the function to permit using two of the let bindings defined farther up in the function. If the user wants focus in the new-window (after exiting the function) instead, then just remove the last line of code (select-window old-window).
(defun lawlist-split-window-below (&optional size)
"Split the selected window into two windows, one above the other.
The selected window is above. The newly split-off window is
below, and displays the 'next-buffer'. Return the new window.
If optional argument SIZE is omitted or nil, both windows get the
same height, or close to it. If SIZE is positive, the upper
\(selected) window gets SIZE lines. If SIZE is negative, the
lower (new) window gets -SIZE lines.
If the variable `split-window-keep-point' is non-nil, both
windows get the same value of point as the selected window.
Otherwise, the window starts are chosen so as to minimize the
amount of redisplay; this is convenient on slow terminals."
(interactive "P")
(let ((old-window (selected-window))
(old-point (window-point))
(size (and size (prefix-numeric-value size)))
moved-by-window-height moved new-window bottom)
(when (and size (< size 0) (< (- size) window-min-height))
;; `split-window' would not signal an error here.
(error "Size of new window too small"))
(setq new-window (split-window nil size))
(unless split-window-keep-point
(with-current-buffer (window-buffer)
;; Use `save-excursion' around vertical movements below
;; (Bug#10971). Note: When the selected window's buffer has a
;; header line, up to two lines of the buffer may not show up
;; in the resulting configuration.
(save-excursion
(goto-char (window-start))
(setq moved (vertical-motion (window-height)))
(set-window-start new-window (point))
(when (> (point) (window-point new-window))
(set-window-point new-window (point)))
(when (= moved (window-height))
(setq moved-by-window-height t)
(vertical-motion -1))
(setq bottom (point)))
(and moved-by-window-height
(<= bottom (point))
(set-window-point old-window (1- bottom)))
(and moved-by-window-height
(<= (window-start new-window) old-point)
(set-window-point new-window old-point)
(select-window new-window)))
;; Always copy quit-restore parameter in interactive use.
(let ((quit-restore (window-parameter old-window 'quit-restore)))
(when quit-restore
(set-window-parameter new-window 'quit-restore quit-restore)))
new-window)
(select-window new-window)
(next-buffer)
(select-window old-window)))
(defalias 'split-window-below 'lawlist-split-window-below)