New to elisp and need help.
I have a simple function that pages down docview when the frame is split into two windows:
(defun noteview-pagedown ()
(interactive)
(with-selected-window (other-window-for-scrolling)
(doc-view-scroll-up-or-next-page nil)))
(defconst noteview-mode-keys
'(("C-M-v" . noteview-pagedown)))
; lifted from android-mode.el
(defvar noteview-mode-map (make-sparse-keymap))
(add-hook 'noteview-mode-hook
(lambda ()
(dolist (spec noteview-mode-keys)
(define-key
noteview-mode-map
(read-kbd-macro (concat noteview-mode-key-prefix " " (car spec)))
(cdr spec)))))
Left window has a text doc with this function defined. Right window has a pdf with docview open.
When I press C-M-v it runs correctly the first time then incorrectly the second time. It seems to do a page-down then a page-up alternately so I never progress to the next page.
When I run this function interactively with M-x noteview-pagedown it works.
Any hints on why this won't work from a keyboard macro would be appreciated.
You've defined a
noteview-mode-map
, but no
noteview-mode
. I'm unconvinced that this dangling map can ever do anything, unless you've defined the minor-mode else where.
This shouldn't be a minor-mode in any case, you appear to want to make C-M-v scroll other window, even when it's showing a doc-view buffer?
(defun my-scroll-up (arg)
(interactive "P")
(with-selected-window (other-window-for-scrolling)
(if (eq 'doc-view-mode major-mode)
(doc-view-scroll-up-or-next-page arg)
(scroll-up arg))))
(define-key (current-global-map) [remap scroll-up] 'my-scroll-up)
I know nothing about doc-view, but I just did C-h f doc-view-scroll-up-or-next-page, and it seems to say that in order for it to move to the next page you must have non-nil doc-view-continuous. Is that the case? Here's the doc:
Scroll page up ARG lines if possible, else goto next page.
When `doc-view-continuous' is non-nil, scrolling upward
at the bottom edge of the page moves to the next page.
Otherwise, goto next page only on typing SPC (ARG is nil).
HTH.
Related
I change my cursor based on state in evil. However I notice that when I type in the minibuffer I'm in normal mode.
I'm trying to make code that change the state from whatever it's in to insert state while I type in the minibuffer and switch back when I exit the minibuffer. Note, I use ivy for minibuffer completion (though I don't think it makes a difference).
(setq evil-insert-state-cursor '((bar . 3) "chartreuse3"))
(defun void-ivy-insert-state (orig-func &rest args)
"Wrapper around ivy, so it goes into insert state."
;; minibuffer is different so I have to manually change the state
(let ((saved-evil-state evil-state))
(evil-insert-state)
(setq cursor-type (elt evil-insert-state-cursor 0))
(set-cursor-color (elt evil-insert-state-cursor 1))
(apply orig-func args)
(evil-change-state saved-evil-state)))
(advice-add 'ivy-read :around #'void-ivy-insert-state)
I expect the cursor to be the right color and the write shape while typing something into the minibuffer. And to return to the appropriate shape of the state I was in before entering the minibuffer.
What actually happens is that the cursor is the right color, but it's the wrong shape. And if I exit the minibuffer with C-g insert state persists. It never returns to the original state. I think C-g aborts the execution of my advice.
I achieved the desired behavior with this code.
(add-hook 'minibuffer-setup-hook (lambda () (evil-insert-state)))
(add-hook 'minibuffer-exit-hook (lambda () (evil-normal-state)))
(define-key evil-insert-state-map [escape] (lambda () (interactive)
(evil-normal-state)
(minibuffer-keyboard-quit)))
The only annoying side effect is that whenever ESC is pressed No recursive edit is in progress is printed to the output.
The version below is the one I use in my code. Instead of returning to normal state, it uses a variable to return to the previous evil-state before the minibuffer was opened. Also it only uses minibuffer-keyboard-quit if the minibuffer is active.
(defvar evil-state-before-minibuffer-setup 'normal)
(add-hook 'minibuffer-setup-hook
(lambda () (setq evil-state-before-minibuffer-setup evil-state)
(evil-insert-state)))
(add-hook 'minibuffer-exit-hook (lambda ()
(evil-change-state evil-state-before-minibuffer-setup)))
(define-key evil-insert-state-map [escape]
(lambda () (interactive) (evil-normal-state)
(when (eq (active-minibuffer-window))
(selected-window))
(minibuffer-keyboard-quit))))
I make sure I only quit when the minibuffer is the selected window. Otherwise, the minibuffer would quit if we were to, say, edit a buffer while viewing the contents of the minibuffer.
is there a way to quickly browse through lots of files in Emacs (24.3)? More specifically:
Let's assume an Emacs frame is split into two windows. Suppose focus is in the left window that has an open 'dired' buffer with lots of text files (or code). I would like to go up and down the list of files (e.g. with cursor keys), while at the same time the current file is shown in the right window. Even better the file is only viewed and closed once I move in the dired buffer to the next file. This would be very useful especially together with some 'omit' mode.
Can this be done in 'dired'? I also coudn't find this functionality in dired-x or in sunrise-commander. Is it possible?
The best candidates I tried already (and why they not solve the problem):
'v' which shows the current file, but also moves the attention
'C-o' which shows the current file, but after moving up or down, I have to press C-o again, also it generates lots of buffers
Thanks a lot for your help!
A simple and generic (while not optimum) solution could be via the C-x () mechanism.
First open the two panes in Emacs, with - say - top one being dired.
Press o to open the first file in the 2nd pane.
Then you can start the repetition mechanism:
do C-x ( to start recording a macro
do C-x k and return to close the buffer
do o again to go back to dired
do down key to go to next file
do o to open next file in bottom pane
do C-x ) to end the macro
From that point (being in bottom pane, dired in top pane), doing a mere
C-x e (and then only e if there is no other operation in between)
will automatically
close bottom pane file, go to top pane, down to next file, open it in bottom pane
There is maybe a more specific way to do that, but knowing the macro mechanism is anyway very helpful in Emacs.
Here's how I do this with view-mode:
(add-hook 'view-mode-hook
(lambda()
(define-key view-mode-map (kbd "n") 'dired-view-next)
(define-key view-mode-map (kbd "p") 'dired-view-prev)))
(defun dired-view-next ()
"Move to next dired line and view ."
(interactive)
(quit-window)
(dired-next-line 1)
(dired-view-file))
(defun dired-view-prev ()
"Move to next dired line and view ."
(interactive)
(quit-window)
(dired-next-line -1)
(dired-view-file))
UPD:
This one has two panes:
(defun dired-view-next-pane ()
(interactive)
(other-window 1)
(if view-mode
(kill-buffer))
(other-window -1)
(dired-next-line 1)
(view-file-other-window
(dired-get-file-for-visit))
(other-window -1))
Thanks a lot for all those answers. Summarizing I created the following solution (extending the answer of "abo-abo"):
;; little modification to dired-mode that let's you browse through lots of files
(add-hook 'dired-mode-hook
(lambda()
(define-key dired-mode-map (kbd "C-o") 'dired-view-current) ; was dired-display-file
(define-key dired-mode-map (kbd "n") 'dired-view-next) ; was dired-next-line
(define-key dired-mode-map (kbd "p") 'dired-view-previous))) ; was dired-previous-line
(defun dired-view-next ()
"Move down one line and view the current file in another window."
(interactive)
(dired-next-line)
(dired-view-current))
(defun dired-view-previous ()
"Move up one line and view the current file in another window."
(interactive)
(dired-previous-line)
(dired-view-current))
(defun dired-view-current ()
"View the current file in another window (possibly newly created)."
(interactive)
(if (not (window-parent))
(split-window)) ; create a new window if necessary
(let ((file (dired-get-file-for-visit))
(dbuffer (current-buffer)))
(other-window 1) ; switch to the other window
(unless (equal dbuffer (current-buffer)) ; don't kill the dired buffer
(if (or view-mode (equal major-mode 'dired-mode)) ; only if in view- or dired-mode
(kill-buffer))) ; ... kill it
(let ((filebuffer (get-file-buffer file)))
(if filebuffer ; does a buffer already look at the file
(switch-to-buffer filebuffer) ; simply switch
(view-file file)) ; ... view it
(other-window -1)))) ; give the attention back to the dired buffer
Three keys are changed:
C-o to view the current item in another window (possibly create one).
n to view the next item in another window.
p to view the previous item in another window.
This can be used in a dired buffer. Note that only dired-mode buffers and view-mode buffers get killed while moving up and down. If a file is shown that another buffer is already visiting (not in view-mode), that buffer is shown as well, but not killed when moving to the next. Another subtlety is the case when the passively shown buffer is the dired buffer used for going through the list (this can easily happen, when going inside a folder with RET). To handle this case, we first check whether we are trying to kill the initial dired buffer.
Load Icicles.
Define this command:
(defun my-find-file ()
"Like `icicle-find-file', but alt action views file temporarily.
Alternate action keys such as `C-S-down' visit the candidate file in
`view-mode' and kill the buffer of the last such viewed candidate."
(interactive)
(let ((icicle-candidate-alt-action-fn
(lambda (file)
(when (and my-last-viewed
(get-file-buffer my-last-viewed))
(kill-buffer (get-file-buffer my-last-viewed)))
(setq my-last-viewed (abbreviate-file-name file))
(view-file file)
(select-frame-set-input-focus
(window-frame (active-minibuffer-window))))))
(icicle-find-file-of-content)))
(defvar my-last-viewed nil
"Last file viewed by alternate action of `my-find-file'.")
Then you can:
Use M-x my-find-file (or bind it to a key - e.g., C-x C-f).
Optionally type part of a file name, to limit the matching names.
Optionally use down or up to cycle among file names.
Use C-S-down to visit the next file in order.
Repeat #4 to see other files in order.
Repeat #2 or #3 to see other sets of files.
End with RET to choose a file to visit or C-g to cancel.
Each file buffer you visited with C-S-down was killed when you
viewed the next one. You can also mix in C-down or C-RET to
also visit files whose buffers you do not want to kill
automatically. (Change view-file to find-file if you don't
want to visit in view-mode, which is read-only.)
[By default, the alternate action for icicle-find-file is
icicle-alt-act-fn-for-type, which prompts you for a file-
appropriate action to use on the particular candidate chosen for
the action. Command my-find-file just substitutes a different
alternate action function (for all candidates you choose).]
See also this thread from help-gnu-emacs#gnu.org. It is pretty much the same question as yours, I think. My replies there were pretty much the same as my reply here, but there are also replies from others that might help you as well.
Try
M-x speedbar
That might appeal to you
Another view-mode solution on top of ag-mode lists. I couldn't find a question for ag-mode, maybe this helps someone generalize a ffap-preview for any mode.
(defun directory-ag-results ()
(save-excursion
(goto-char (point-min))
(search-forward "\"")
(setq a (point))
(search-forward "\"")
(setq b (- (point) 1))
(buffer-substring-no-properties a b)))
(defun search-item-path ()
(let ((dir (directory-ag-results))
(file-parts (split-string (substring-no-properties (thing-at-point 'filename)) ":")))
(concat dir (nth 0 file-parts))))
(defun search-item-line ()
(let ((file-parts (split-string (substring-no-properties (thing-at-point 'filename)) ":")))
(- (string-to-number (nth 1 file-parts)) 1)))
(defun view-current ()
"Quickly view the current file in another window."
(if (not (window-parent))
(split-window)) ; create a new window if necessary
(let ((file (search-item-path))
(line (search-item-line))
(dbuffer (current-buffer)))
(other-window 1) ; switch to the other window
(unless (equal dbuffer (current-buffer)) ; don't kill the dired buffer
(if (or view-mode (equal major-mode 'dired-mode)) ; only if in view- or dired-mode
(kill-buffer))) ; ... kill it
(let ((filebuffer (get-file-buffer file)))
(if filebuffer ; does a buffer already look at the file
(switch-to-buffer filebuffer) ; simply switch
(progn
(view-file file) ; ... view it
(goto-char (point-min))
(next-line line)))
(other-window -1))))
(defun next-view-current ()
(interactive)
(next-line)
(view-current))
(defun previous-view-current ()
(interactive)
(previous-line)
(view-current))
(define-key ag-mode-map (kbd "M-p") 'previous-view-current)
(define-key ag-mode-map (kbd "M-n") 'next-view-current)
This is the one thing I think Sublime does better than Emacs. Blasphemy, I know! I like the "q to exit" feel of view-mode, rather than timer-based solutions, and like scrolling around a previewed file. This snippet navigates to the line number found in the search results, optimizing for browsing speed.
Note about the code: I tried polyfilling vc-root-dir from Emacs 25, but it doesn't really make sense for ag-mode since ag-mode's buffer is outside the repo you're searching in. I ended up pulling the root dir from the top of the "ag search" buffer.
Early stages. Improvements welcome.
Demo
Edit: It works for ag-mode, not dired. Demo gif.
Credits: abo-abo, user2979331
In the interest of keeping StackOverflow up to date, the package peep-dired does everything posted in the other answers, and I’m sure there are other packages as well. You don’t have to maintain copypasted or home-rolled lisp for this job.
I would like that after splitting the window (C-x 3 or C-x 2) to be able to automatically get to cursor in the new opened buffer (the other than the current). How can I achieve this behavior ?
You can switch between buffers with C-x o. As to do that automatically I don't think there is an existing command for that.
You can do it like this:
(global-set-key "\C-x2" (lambda () (interactive)(split-window-vertically) (other-window 1)))
(global-set-key "\C-x3" (lambda () (interactive)(split-window-horizontally) (other-window 1)))
In Emacs 24.3.1 it works if you change the argument 1 for 0.
!!!DO NOT USE THIS ANSWER!!! -- as pointed out in the comments, advising split-window can lead to undesired side-effects.
I recommend Bozhidar Batsov's answer instead.
Put the following in your .emacs file:
(defadvice split-window (after move-point-to-new-window activate)
"Moves the point to the newly created window after splitting."
(other-window 1))
As well as splitting the frame manually with C-x 2 or C-x 3, buffers are also automatically "popped-up" some times. These are also not selected/active by default.
This can be fixed by changing the function used to split a window. It's set to split-window-sensibly by default, but you can set it to your own function that calls split-window-sensibly and then selects the buffer.
Unfortunately, though, this has the side-effect of selecting the *Completions* buffer when you hit TAB in the minibuffer. So, it's worth checking to see if the minibuffer is active and not switching in this case. I'd bet there are other such undesirable scenarios as well. I'll try to update this post as and when I find them.
;; after splitting a frame automatically, switch to the new window (unless we
;; were in the minibuffer)
(setq split-window-preferred-function 'my/split-window-func)
(defun my/split-window-func (&optional window)
(let ((new-window (split-window-sensibly window)))
(if (not (active-minibuffer-window))
(select-window new-window))))
(Works with Emacs 24.5.1.)
My thought of when you would want to follow the window after a split-window was when it had the same buffer like in the following code:
(defun split-window--select-window (orig-func &rest args)
"Switch to the other window after a `split-window'"
(let ((cur-window (selected-window))
(new-window (apply orig-func args)))
(when (equal (window-buffer cur-window) (window-buffer new-window))
(select-window new-window))
new-window))
(advice-add 'split-window :around #'split-window--select-window)
Simple
C-x o will help you switch to the "other" buffer.
I don't know if there's something wrong with my settings but when I press M-f (forward a word)
it doesn't matter where I am, it never place the cursor in the next word (just between words). This doesn't happen with M-b which place my cursor in the beginning of the previous word.
Is this a normal behavior? How do I place my cursor at the beginning of the following word?
The macro solution described is a great way to get this behavior in a session, but it's a little inconvenient if that's the default behavior you want, since you have to define it every time you start emacs. If you want M-f to work like this all the time, you can define an elisp function and bind it to the key. Put this in your .emacs file:
(defun next-word (p)
"Move point to the beginning of the next word, past any spaces"
(interactive "d")
(forward-word)
(forward-word)
(backward-word))
(global-set-key "\M-f" 'next-word)
Ok, just so we're clear, Im going to assume you are talking about the commands forward-word and backward-word these are bound by default to Alt+f and Alt+b
eg string: "Hello dolly I am here"
If your cursor is on the "H" of "Hello", and you do forward-word the cursor will move to the space between "Hello" and "dolly", but it sounds like you want the cursor to be on the letter "d" of "dolly" instead of in-front of it.
So, do forward-word twice, then backward-word once.
That will put your cursor on the "d" of "dolly".
This can be automated with a macro.
;; = comments, do not type them
Ctrl+x ( ;;start macro
Alt+f Alt+f Alt+b
Ctrl+x ) ;;end macro
Then to run last defined macro do this:
Ctrl+x e
EDIT: as pascal mentioned in a comment, this can also just be done with
Alt+f Ctrl+f
You could put that into a macro as well, either way the result is the same.
That is correct behavior. According to the Emacs manual, "[f]orward motion stops right after the last letter of the word, while backward motion stops right before the first letter."
Why is it this way? Perhaps to be consistent with kill-word (M-d).
Moving forward twice and backwards once is fine unless you are at the beginning of a line with spaces in the front. Then going forward twice and back once will move you to the next word not the first word. The code below will mimic vi's "w" command perfectly. I've written this quite fast so this code can be cleaned up further.
(defun forward-word-to-beginning (&optional n)
"Move point forward n words and place cursor at the beginning."
(interactive "p")
(let (myword)
(setq myword
(if (and transient-mark-mode mark-active)
(buffer-substring-no-properties (region-beginning) (region-end))
(thing-at-point 'symbol)))
(if (not (eq myword nil))
(forward-word n))
(forward-word n)
(backward-word n)))
(global-set-key (kbd "M-C-f") 'forward-word-to-beginning)
Try something like following:
;; replace common word-operations on same-syntax-operations
(require 'thingatpt)
(global-set-key "\M-f" 'forward-same-syntax)
(global-set-key "\M-b" (lambda()
(interactive)
(forward-same-syntax -1)))
(defun kill-syntax (&optional arg)
"Kill ARG sets of syntax characters after point."
(interactive "p")
(let ((opoint (point)))
(forward-same-syntax arg)
(kill-region opoint (point))))
(global-set-key "\M-d" 'kill-syntax)
(global-set-key [(meta backspace)] (lambda()
(interactive)
(kill-syntax -1)))
You can achieve this behavior by using the forward-to-word and backward-to-word found in misc.el. I have these bound to Meta-F/Meta-B (i.e. with Shift pressed). These are equivalent to Meta-f/Meta-b for forward-word/backward-word
My .emacs has the following bindings
(global-set-key (kbd "M-F") #'forward-to-word)
(global-set-key (kbd "M-B") #'backward-to-word)
To put it simply, I'm trying to get scrolling in emacs like in vim and most other editors; when I'm for example, two lines from the bottom/top, and I press down/up (Ctrl-p,n, ↑,↓) it goes only one line up or down, not half the screen.
See some of the suggestions on the Emacs Wiki:
Emacs Wiki: Smooth Scrolling
(setq scroll-step 1
scroll-conservatively 10000)
If you want to position the screen exactly, you can use Ctrl-L.
By default it positions the current line in the middle of the screen.
ESC 0 Ctrl-L positions the current line at the top.
I'm a bit late to the party, but if you don't mind installing a package then smooth-scrolling (github, also available in MELPA) may be what you're looking for - it certainly works for me.
Once you've installed it you can pop the following in your init.el:
(require 'smooth-scrolling)
(smooth-scrolling-mode 1)
(setq smooth-scroll-margin 5)
The last line is optional; it starts scrolling near the screen edge rather than at it, so you've always got a little context around the point. Adjust to taste.
My solution is not to change Emac's default scrolling, but rather to create a key sequence command from a macro. This way you have a convenient way to scroll one line at a time when you want. Not ideal, but super easy. It just happens that M-(↓) and M-(↑) are available, so that's what I used.
This is how I did it. First, you need to record a macro for one line scrolls, up and down.
Begin macro
C-x (
Scroll down one
C-u 1 C-v
Stop macro
C-x )
For scroll up one, use
C-u 1 M-v
Next you need to name the macro.
M-x name-last-kbd-macro
Give it a name when prompted like:
down-one-line
Then just use the following to bind a key sequence to that command name:
M-x global-set-key
And upon prompting, use something like:
M-(down arrow)
Then it will ask you which command you want to bind, and you should give it the name you invented earlier, e.g., down-one-line.
Here is where I got this information. You can also find instructions below and elsewhere about adding your macro to the .emacs file.
Here for macro definition explanation
Here for how to control scrolling
I've been using these in my .emacs file since 2000.
(global-set-key (quote [M-down]) (quote View-scroll-line-forward))
(global-set-key (quote [M-up]) (quote View-scroll-line-backward))
This way, I can keep the Emacs default behavior as well as scroll one line at a time, depending on what I'm doing.
This worked till at least GNU Emacs 22. I recently upgraded to Emacs 24 and discovered that View-scroll-line-forward and View-scroll-line-backward are no longer available. After some hunting, I discovered that scroll-up-line and scroll-down-line work. So if you're using Emacs 24, you can use this.
(global-set-key (quote [M-down]) (quote scroll-up-line))
(global-set-key (quote [M-up]) (quote scroll-down-line))
I mostly skipped Emacs 23, so if that is the version you're using, you can experiment with both the above.
Note: scroll-up-line actually scrolls one line down, because the buffer is being moved one line up.
I rebind my arrow keys to perform scrolling operations.
(global-set-key [up] (lambda () (interactive) (scroll-down 1)))
(global-set-key [down] (lambda () (interactive) (scroll-up 1)))
(global-set-key [left] (lambda () (interactive) (scroll-right tab-width t)))
(global-set-key [right] (lambda () (interactive) (scroll-left tab-width t)))
Simples do this:
(global-set-key [M-up] (lambda () (interactive) (scroll-up 1)))
(global-set-key [M-down] (lambda () (interactive) (scroll-down 1)))
then meta cursor up moves up and meta cursor down moves down.
QED. Not sure what all the above people were smoking!
I have the following in my .emacs file to enable a nice ctrl-up, ctrl-down scrolling behavior. I also use this for the mousewheel.
(defun scroll-down-in-place (n)
(interactive "p")
(previous-line n)
(scroll-down n))
(defun scroll-up-in-place (n)
(interactive "p")
(next-line n)
(scroll-up n))
(global-set-key [mouse-4] 'scroll-down-in-place)
(global-set-key [mouse-5] 'scroll-up-in-place)
(global-set-key [C-up] 'scroll-down-in-place)
(global-set-key [C-down] 'scroll-up-in-place)
If you are looking for a quick way to create a scroll-like effect, enter in C-n and C-l sequentially which moves the cursor down and then centers it.
To have the "vim" scrolling put this to your .emacs file:
(defun next-line-and-recenter () (interactive) (next-line) (recenter))
(defun previous-line-and-recenter () (interactive) (previous-line) (recenter))
(global-set-key (kbd "C-n") 'next-line-and-recenter)
(global-set-key (kbd "C-p") 'previous-line-and-recenter)
Since it can be annoying to use the M-up, M-down because it interferes with the org-mode which overloads these commands. To avoid this issue I personally use those commands which combine M-page-up M-page-down". Here I defined the scroll up and down to 1 line.
;;;scroll by `number-of-lines' without the cursor attached to the screen
(global-set-key [M-prior] (lambda () (interactive) (let ((number-of-lines 1))
(scroll-down number-of-lines)
(forward-line (- number-of-lines)))))
(global-set-key [M-next] (lambda () (interactive) (let ((number-of-lines 1))
(scroll-up number-of-lines)
(forward-line number-of-lines))))
;;;scroll by `number-of-lines' with the cursor attached to the screen
(global-set-key [S-M-prior] (lambda () (interactive) (let ((number-of-lines 1))
(scroll-down number-of-lines))))
(global-set-key [S-M-next] (lambda () (interactive) (let ((number-of-lines 1))
(scroll-up number-of-lines))))
M-x customize-variable scroll-conservatively
Set it to 1.
You don't really want to do this, though.
If you don't mind using the mouse and have a scroll wheel, you can customize the variable mouse-wheel-scroll-amount by either:
C-h v mouse-wheel-scroll-amount (click on customize, change value to "Specific # of lines" 1, ApplyAndSave.)
or add to .emacs the line:
'(mouse-wheel-scroll-amount '(1 ((shift) . 1) ((meta)) ((control) . text-scale)))
There are lots of possibilities listed at
https://www.emacswiki.org/emacs/Scrolling
If you start emacs in .xsession, in my case setting scroll-conservatively to 100+ will not work, nor scroll-step 1. But if u start emacs after X, it works.
After playing a bit with the available configuration (emacs 26.3), I got to the following set of values:
(setq scroll-step 1
scroll-preserve-screen-position t
scroll-margin 10
scroll-conservatively 10
maximum-scroll-margin 0.0
scroll-up-aggressively 0.0
scroll-down-aggressively 0.0)
I believe the values for scroll-margin and scroll-conservatively do not matter much because the maximum-scroll-margin clamps them down. They just need to be equal (maybe?).
Scroll happens line by line, even on the end of the file (worst case for me). The only missing feature was that with this the margin on top and bottom are lost.
Its a compromise and, for me, smooth scrolling is worth it.