Skip to content

Commit

Permalink
Add topspace-set-height, enhance topspace-center-position
Browse files Browse the repository at this point in the history
- Add  `topspace-set-height` to allow users and external packages to update the top space height.
- Increase flexibility of `topspace-center-position` by allowing it to also be an integer or function as follows:

    If a float, it represents the position to center buffers as a ratio of
    frame height, and can be a value from 0.0 to 1.0 where lower values center
    buffers higher up in the screen.

    If a positive or negative integer value, buffers will be centered by putting
    their center line at a distance of `topspace-center-position' lines away
    from the top of the selected window when positive, or from the bottom
    of the selected window when negative.
    The distance will be in units of lines with height `default-line-height',
    and the value should be less than the height of the window.

    If a function, the same rules above apply to the functions' return value.

- Internal refactoring and updating of docstrings
  • Loading branch information
trevorpogue committed May 10, 2022
1 parent cbd1f7e commit f15a275
Show file tree
Hide file tree
Showing 4 changed files with 468 additions and 214 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ topspace.log
\#*\#
coverage/
cask-run
test/director/
test/director/
TODO
148 changes: 115 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,17 +56,35 @@ No new keybindings are required, keep using all your previous scrolling & recent

# Customization
```elisp
(defcustom topspace-active #'topspace-default-active
"Determine when `topspace-mode' mode is active / has any effect on buffer.
This is useful in particular when `global-topspace-mode' is enabled but you want
`topspace-mode' to be inactive in certain buffers or in any specific
circumstance. When inactive, `topspace-mode' will still technically be on,
but will be effectively off and have no effect on the buffer.
Note that if `topspace-active' returns non-nil but `topspace-mode' is off,
`topspace-mode' will still be disabled.
With the default value, topspace will only be inactive in child frames.
If non-nil, then always be active. If nil, never be active.
If set to a predicate function (function that returns a boolean value),
then be active only when that function returns a non-nil value."
:type '(choice (const :tag "always" t)
(const :tag "never" nil)
(function :tag "predicate function")))
(defcustom topspace-autocenter-buffers #'topspace-default-autocenter-buffers
"Center small buffers with top space when first opened or window sizes change.
This is done by automatically calling `topspace-recenter-buffer'
and the positioning can be customized with `topspace-center-position'.
Top space will not be added if the number of text lines in the buffer is larger
than or close to the selected window's height.
Customize `topspace-center-position' to adjust the centering position.
than or close to the selected window's height, or if `window-start' is greater
than 1. Customize `topspace-center-position' to adjust the centering position.
With the default value for `topspace-autocenter-buffers',
buffers will not be centered if in a child frame or if the user
has already scrolled or used `recenter' with buffer in the selected window.
With the default value, buffers will not be centered if in a child frame
or if the user has already scrolled or used `recenter' with buffer in the
selected window.
If non-nil, then always autocenter. If nil, never autocenter.
If set to a predicate function (function that returns a boolean value),
Expand All @@ -77,28 +95,27 @@ then do auto-centering only when that function returns a non-nil value."
(function :tag "predicate function")))
(defcustom topspace-center-position 0.4
"Target position when centering buffers as a ratio of frame height.
A value from 0 to 1 where lower values center buffers higher up in the screen.
Used in `topspace-recenter-buffer' when called or when opening/resizing buffers
if `topspace-autocenter-buffers' returns non-nil."
:group 'topspace
:type 'float)
"Target position when centering buffers.
Can be set to a float, integer, or function that returns a float or integer.
(defcustom topspace-active t
"Determine when `topspace-mode' mode is active / has any effect on buffer.
This is useful in particular when `global-topspace-mode' is enabled but you want
`topspace-mode' to be inactive in certain buffers or in any specific
circumstance. When inactive, `topspace-mode' will still technically be on,
but will be effectively off and have no effect on the buffer.
Note that if `topspace-active' returns non-nil but `topspace-mode' is off,
`topspace-mode' will still be disabled.
If a float, it represents the position to center buffers as a ratio of
frame height, and can be a value from 0.0 to 1.0 where lower values center
buffers higher up in the screen.
If non-nil, then always be active. If nil, never be active.
If set to a predicate function (function that returns a boolean value),
then be active only when that function returns a non-nil value."
:type '(choice (const :tag "always" t)
(const :tag "never" nil)
(function :tag "predicate function")))
If a positive or negative integer value, buffers will be centered by putting
their center line at a distance of `topspace-center-position' lines away
from the top of the selected window when positive, or from the bottom
of the selected window when negative.
The distance will be in units of lines with height `default-line-height',
and the value should be less than the height of the window.
If a function, the same rules above apply to the functions' return value.
Used in `topspace-recenter-buffer' when called without an argument, or when
opening/resizing buffers if `topspace-autocenter-buffers' returns non-nil."
:group 'topspace
:type '(choice float integer
(function :tag "float or integer function")))
(defcustom topspace-empty-line-indicator
#'topspace-default-empty-line-indicator
Expand Down Expand Up @@ -136,42 +153,107 @@ Set this variable to nil to disable the mode line completely."
(defvar topspace-keymap (make-sparse-keymap)
"Keymap for Topspace commands.
By default this is left empty for users to set with their own
preferred bindings.")
```

# Extra functions

```elisp
;;;###autoload
(defun topspace-height ()
"Return the top space height in the selected window in number of lines.
"Return the top space height in lines for current buffer in selected window.
The top space is the empty region in the buffer above the top text line.
The return value is of type float, and is equivalent to
the top space pixel height / `default-line-height'."
the top space pixel height / `default-line-height'.
If the height does not exist yet, zero will be returned if
`topspace-autocenter-buffers' returns nil, otherwise a value that centers
the buffer will be returned according to `topspace-center-position'.
If the stored height is now invalid, it will first be corrected by
`topspace--correct-height' before being returned.
Valid top space line heights are:
- never negative,
- only positive when `window-start' equals 1,
`topspace-active' returns non-nil, and `topspace-mode' is enabled,
- not larger than `topspace--window-height' minus `topspace--context-lines'."
...
;;;###autoload
(defun topspace-set-height (&optional total-lines)
"Set and redraw the top space overlay to have a target height of TOTAL-LINES.
This sets the top space height for the current buffer in the selected window.
Int or float values are accepted for TOTAL-LINES, and the value is
considered to be in units of `default-line-height'.
If argument TOTAL-LINES is not provided, the top space height will be set to
the value returned by `topspace-height', which can be useful when redrawing a
previously stored top space height in a window after a new buffer is
displayed in it, or when first setting the height to an initial default value
according to `topspace-height'.
If TOTAL-LINES is invalid, it will be corrected by `topspace--correct-height'.
Valid top space line heights are:
- never negative,
- only positive when `window-start' equals 1,
`topspace-active' returns non-nil, and `topspace-mode' is enabled,
- not larger than `topspace--window-height' minus `topspace--context-lines'."
(interactive "p")
...
;;;###autoload
(defun topspace-recenter-buffer ()
"Add enough top space in the selected window to center small buffers.
(defun topspace-recenter-buffer (&optional position)
"Add enough top space to center small buffers according to POSITION.
POSITION defaults to `topspace-center-position'.
If POSITION is a float, it represents the position to center buffer as a ratio
of frame height, and can be a value from 0.0 to 1.0 where lower values center
the buffer higher up in the screen.
If POSITION is a positive or negative integer value, buffer will be centered
by putting its center line at a distance of `topspace-center-position' lines
away from the top of the selected window when positive, or from the bottom
of the selected window when negative.
The distance will be in units of lines with height `default-line-height',
and the value should be less than the height of the window.
Top space will not be added if the number of text lines in the buffer is larger
than or close to the selected window's height.
Customize `topspace-center-position' to adjust the centering position.
than or close to the selected window's height, or if `window-start' is greater
than 1.
Customize `topspace-center-position' to adjust the default centering position.
Customize `topspace-autocenter-buffers' to run this command automatically
after first opening buffers and after window sizes change."
(interactive)
...
;;;###autoload
(defun topspace-default-active ()
"Default function that `topspace-active' is set to.
Return nil if the selected window is in a child-frame."
...
;;;###autoload
(defun topspace-default-autocenter-buffers ()
"Return non-nil if buffer is allowed to be auto-centered.
"Default function that `topspace-autocenter-buffers' is set to.
Return nil if the selected window is in a child-frame or user has scrolled
buffer in selected window."
...
;;;###autoload
(defun topspace-default-empty-line-indicator ()
"Put the empty-line bitmap in fringe if `indicate-empty-lines' is non-nil.
"Default function that `topspace-empty-line-indicator' is set to.
Put the empty-line bitmap in fringe if `indicate-empty-lines' is non-nil.
This is done by adding a 'display property to the returned string.
The bitmap used is the one that the `empty-line' logical fringe indicator
maps to in `fringe-indicator-alist'."
...
;;;###autoload
(defun topspace-buffer-was-scrolled-p ()
"Return t if current buffer has been scrolled in the selected window before.
This is provided since it is used in `topspace-default-autocenter-buffers'."
...
```

# How it works
Expand Down
88 changes: 76 additions & 12 deletions test/topspace-test.el
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
:var (prev-height)

(before-all
(setq topspace-center-position 0.42)
(topspace--cmds (set-frame-size (selected-frame) 90 24))
(switch-to-buffer (find-file-noselect "./topspace.el" t))
(global-topspace-mode))
Expand All @@ -55,7 +56,7 @@

(it "reduces top space height before cursor can move below window-end"
(goto-char 1)
(topspace--draw 0)
(topspace-set-height 0)
(topspace--cmds
(scroll-down)
(scroll-up)
Expand Down Expand Up @@ -83,7 +84,7 @@
"topspace--after-scroll"
(it "is needed when first scrolling above the top line"
(goto-char 1)
(topspace--draw 0)
(topspace-set-height 0)
(scroll-up-line)
(scroll-down 2)
(expect (round (topspace-height)) :to-equal 1)))
Expand All @@ -94,37 +95,49 @@
(it "autocenters buffer when window size changes"
(switch-to-buffer "*scratch*")
(run-hooks 'window-configuration-change-hook)
(expect (round (* (topspace-height) 10)) :to-equal 86)
(expect (round (* (topspace-height) 10)) :to-equal 78)
(topspace--cmds (set-frame-size (selected-frame) 90 22))
(run-hooks 'window-configuration-change-hook)
(expect (round (* (topspace-height) 10)) :to-equal 78)
(expect (round (* (topspace-height) 10)) :to-equal 70)
(topspace--cmds (set-frame-size (selected-frame) 90 24)))

(it "will redraw topspace even if window height didn't change
in case topspace-autocenter-buffers changed return value"
(spy-on 'topspace--draw)
(spy-on 'topspace-set-height)
(topspace--window-configuration-change)
(expect 'topspace--draw :to-have-been-called)))
(expect 'topspace-set-height :to-have-been-called)))

(describe
"topspace-mode"
(it "can be enabled and disabled locally"
(topspace-mode -1)
(expect topspace-mode :to-equal nil)
(scroll-up-line)
(topspace--draw 1)
(topspace-set-height 1)
(expect (topspace-height) :to-equal 0)
(ignore-errors (scroll-down-line))
(topspace-mode 1)
(expect topspace-mode :to-equal t)))
(expect topspace-mode :to-equal t)
(switch-to-buffer "*scratch*")
(topspace-mode -1)
(topspace-recenter-buffer)
(expect (topspace-height) :to-equal 0)
(topspace-mode 1)))

(describe
"topspace--center-line"
(it "has an optional argument that takes the value `topspace-center-position'
by default"
(expect (topspace--center-line) :to-equal
(topspace--center-line topspace-center-position))))

(describe
"topspace--draw-increase-height"
"topspace--increase-height"
(it "increases top space height"
(goto-char 1)
(recenter)
(setq prev-height (topspace-height))
(topspace--draw-increase-height 1)
(topspace--increase-height 1)
(expect (topspace-height) :to-equal (1+ prev-height))))

(describe
Expand All @@ -150,7 +163,7 @@ in case topspace-autocenter-buffers changed return value"
(it "allows smooth-scrolling package to work with topspace"
:to-equal (smooth-scroll-lines-above-point)
(progn (goto-char 1)
(topspace--draw 0)
(topspace-set-height 0)
(goto-line smooth-scroll-margin)
(set-window-start (selected-window) (point))
(scroll-down smooth-scroll-margin)
Expand Down Expand Up @@ -222,6 +235,57 @@ returns nil"
(it "can accept an arg or no args"
(expect (topspace--current-line-plus-topspace)
:to-equal (topspace--current-line-plus-topspace
(topspace-height))))))
(topspace-height)))))

(describe
"topspace-center-position"
(it "can be a float value or function, in which case
its return value represents the position to center buffers as a ratio of
frame height, and can be a value from 0 to 1 where lower values center
buffers higher up in the screen."
(setq topspace--prev-center-position topspace-center-position)
(setq topspace-center-position 0.5)
(switch-to-buffer "*scratch*")
(topspace-recenter-buffer)
(expect (topspace-height) :to-equal
(- (* (topspace--frame-height)
(topspace--eval-choice topspace-center-position))
(window-top-line)))
(defun topspace--center-position-test () 0.5)
(setq topspace-center-position #'topspace--center-position-test)
(topspace-recenter-buffer)
(expect (topspace-height) :to-equal
(- (* (topspace--frame-height)
(topspace--eval-choice topspace-center-position))
(window-top-line)))
(setq topspace-center-position topspace--prev-center-position))

(it "can be an integer value or function, in which case:
If a positive integer value, buffers will be centered putting their center
line at a distance of `topspace-center-position' from the top of the
selected window.
If a negative integer value, buffers will be centered putting their center
line at line at a distance of `topspace-center-position' away from
the bottom of the selected window.
(ARG should be less than the height of the window.)"
(setq topspace--prev-center-position topspace-center-position)
(setq topspace-center-position 4)
(switch-to-buffer "*scratch*")
;; (expect (topspace-height) :to-equal (/ (frame-text-lines) 2))
(topspace-recenter-buffer)
(expect (topspace-height) :to-equal 4.0)
(defun topspace--center-position-test () 4)
(setq topspace-center-position #'topspace--center-position-test)
(topspace-recenter-buffer)
(expect (topspace-height) :to-equal 4.0)
(setq topspace-center-position -4)
(topspace-recenter-buffer)
(expect (topspace-height) :to-equal (- (topspace--window-height)
(topspace--context-lines)))
(setq topspace-center-position topspace--prev-center-position))
)
)

;;; test-topspace.el ends here

0 comments on commit f15a275

Please sign in to comment.