commit 36d059f02a7036edfb1100bef840ea9f319ffbe2 parent 1f8f58a49d176b793119bc57818adfe9e55a1bac Author: Jake Bauer <jbauer@paritybit.ca> Date: Tue, 4 Apr 2023 14:46:13 -0400 Add neoscroll.nvim plugin Diffstat:
16 files changed, 1527 insertions(+), 0 deletions(-)
diff --git a/.config/nvim/init.vim b/.config/nvim/init.vim @@ -4,10 +4,12 @@ " vim-gutentags — (auto-generate tags) " vim-monochrome — (custom colorscheme) " vim-fastline — (custom status bar) +" neoscroll.nvim — (smooth scrolling) let mapleader = "," " Plugin Settings +lua require('neoscroll').setup() let g:buftabline_show = 1 let g:gutentags_project_root = ['Makefile'] let g:gutentags_add_default_project_roots = 1 diff --git a/.config/nvim/pack/bundle/start/neoscroll.nvim/LICENSE b/.config/nvim/pack/bundle/start/neoscroll.nvim/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Carles Rafols i Belles + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. diff --git a/.config/nvim/pack/bundle/start/neoscroll.nvim/README.md b/.config/nvim/pack/bundle/start/neoscroll.nvim/README.md @@ -0,0 +1,157 @@ +# Neoscroll: a smooth scrolling neovim plugin written in lua + +## Demo +https://user-images.githubusercontent.com/41967813/121818668-7b36c800-cc80-11eb-8c3a-45a4767b8f05.mp4 + + +## Features +* Smooth scrolling for window movement commands (mappings optional): `<C-u>`, `<C-d>`, `<C-b>`, `<C-f>`, `<C-y>`, `<C-e>`, `zt`, `zz`, `zb`. +* Takes into account folds. +* A single scrolling function that accepts either the number of lines or the percentage of the window to scroll. +* Cursor is hidden while scrolling (optional) for a more pleasing scrolling experience. +* Customizable scrolling behaviour. +* You can use predefined easing functions for the scrolling animation. +* Performance mode that turns off syntax highlighting while scrolling for slower machines or files with heavy regex syntax highlighting. +* Cancel scroll by scrolling in the opposite direction. +* Simulated "stop on key release" when holding down a key to scroll. + +## Installation +You will need neovim 0.5 for this plugin to work. Install it using your favorite plugin manager: + +- With [Packer](https://github.com/wbthomason/packer.nvim): `use 'karb94/neoscroll.nvim'` + +- With [vim-plug](https://github.com/junegunn/vim-plug): `Plug 'karb94/neoscroll.nvim'` + + +## Quickstart +Add the `setup()` function to your init file. + +For `init.lua`: +```Lua +require('neoscroll').setup() +``` +For `init.vim`: +```Vim +lua require('neoscroll').setup() +``` + + +## Options +Setup function with all the options and their default values: +```Lua +require('neoscroll').setup({ + -- All these keys will be mapped to their corresponding default scrolling animation + mappings = {'<C-u>', '<C-d>', '<C-b>', '<C-f>', + '<C-y>', '<C-e>', 'zt', 'zz', 'zb'}, + hide_cursor = true, -- Hide cursor while scrolling + stop_eof = true, -- Stop at <EOF> when scrolling downwards + respect_scrolloff = false, -- Stop scrolling when the cursor reaches the scrolloff margin of the file + cursor_scrolls_alone = true, -- The cursor will keep on scrolling even if the window cannot scroll further + easing_function = nil, -- Default easing function + pre_hook = nil, -- Function to run before the scrolling animation starts + post_hook = nil, -- Function to run after the scrolling animation ends + performance_mode = false, -- Disable "Performance Mode" on all buffers. +}) +``` + + +## Custom mappings +You can create your own scrolling mappings using the following lua functions: +* `scroll(lines, move_cursor, time[, easing])` +* `zt(half_win_time[, easing])` +* `zz(half_win_time[, easing])` +* `zb(half_win_time[, easing])` + +Read the documentation for more details on how to use each function. + +You can use the following syntactic sugar in your init.lua to define lua function mappings in normal, visual +and select modes: +```Lua +require('neoscroll').setup({ + -- Set any options as needed +}) + +local t = {} +-- Syntax: t[keys] = {function, {function arguments}} +t['<C-u>'] = {'scroll', {'-vim.wo.scroll', 'true', '250'}} +t['<C-d>'] = {'scroll', { 'vim.wo.scroll', 'true', '250'}} +t['<C-b>'] = {'scroll', {'-vim.api.nvim_win_get_height(0)', 'true', '450'}} +t['<C-f>'] = {'scroll', { 'vim.api.nvim_win_get_height(0)', 'true', '450'}} +t['<C-y>'] = {'scroll', {'-0.10', 'false', '100'}} +t['<C-e>'] = {'scroll', { '0.10', 'false', '100'}} +t['zt'] = {'zt', {'250'}} +t['zz'] = {'zz', {'250'}} +t['zb'] = {'zb', {'250'}} + +require('neoscroll.config').set_mappings(t) +``` + + +## Easing functions +By default the scrolling animation has a constant speed, i.e. the time between each line scroll is constant. +If you want to smooth the start and/or end of the scrolling animation you can pass the name of one of the +easing functions that Neoscroll provides to the `scroll()` function. You can use any of the following easing +functions: `quadratic`, `cubic`, `quartic`, `quintic`, `circular`, `sine`. Neoscroll will then adjust the time +between each line scroll using the selected easing function. This dynamic time adjustment can make animations +more pleasing to the eye. + +To learn more about easing functions here are some useful links: +* [Microsoft documentation](https://docs.microsoft.com/en-us/dotnet/desktop/wpf/graphics-multimedia/easing-functions?view=netframeworkdesktop-4.8) +* [easings.net](https://easings.net/) +* [febucci.com](https://www.febucci.com/2018/08/easing-functions/) + +### Examples +Using the same syntactic sugar introduced in _Custom mappings_ we can write the following config: +```Lua +require('neoscroll').setup({ + easing_function = "quadratic" -- Default easing function + -- Set any other options as needed +}) + +local t = {} +-- Syntax: t[keys] = {function, {function arguments}} +-- Use the "sine" easing function +t['<C-u>'] = {'scroll', {'-vim.wo.scroll', 'true', '350', [['sine']]}} +t['<C-d>'] = {'scroll', { 'vim.wo.scroll', 'true', '350', [['sine']]}} +-- Use the "circular" easing function +t['<C-b>'] = {'scroll', {'-vim.api.nvim_win_get_height(0)', 'true', '500', [['circular']]}} +t['<C-f>'] = {'scroll', { 'vim.api.nvim_win_get_height(0)', 'true', '500', [['circular']]}} +-- Pass "nil" to disable the easing animation (constant scrolling speed) +t['<C-y>'] = {'scroll', {'-0.10', 'false', '100', nil}} +t['<C-e>'] = {'scroll', { '0.10', 'false', '100', nil}} +-- When no easing function is provided the default easing function (in this case "quadratic") will be used +t['zt'] = {'zt', {'300'}} +t['zz'] = {'zz', {'300'}} +t['zb'] = {'zb', {'300'}} + +require('neoscroll.config').set_mappings(t) +``` + +## `pre_hook` and `post_hook` functions +Set `pre_hook` and `post_hook` functions to run custom code before and/or after the scrolling animation. +The function will be called with the `info` parameter which can be optionally passed to `scroll()` (or any of the provided wrappers). This can be used to conditionally run different hooks for different types of scrolling +animations. + +For example, if you want to hide the `cursorline` only for `<C-d>`/`<C-u>` scrolling animations +you can do something like this: +```lua +require('neoscroll').setup({ + pre_hook = function(info) if info == "cursorline" then vim.wo.cursorline = false end end, + post_hook = function(info) if info == "cursorline" then vim.wo.cursorline = true end end +}) +local t = {} +t['<C-u>'] = { 'scroll', { '-vim.wo.scroll', 'true', '350', 'sine', [['cursorline']] } } +t['<C-d>'] = { 'scroll', { 'vim.wo.scroll', 'true', '350', 'sine', [['cursorline']] } } +require('neoscroll.config').set_mappings(t) +``` +Keep in mind that the `info` variable is not restricted to a string. It can also be a table with multiple +key-pair values. + + +## Known issues +* `<C-u>`, `<C-d>`, `<C-b>`, `<C-f>` mess up macros ([issue](https://github.com/karb94/neoscroll.nvim/issues/9)). + + +## Acknowledgements +This plugin was inspired by [vim-smoothie](https://github.com/psliwka/vim-smoothie) and [neo-smooth-scroll.nvim](https://github.com/cossonleo/neo-smooth-scroll.nvim). +Big thank you to their authors! diff --git a/.config/nvim/pack/bundle/start/neoscroll.nvim/doc/neoscroll.txt b/.config/nvim/pack/bundle/start/neoscroll.nvim/doc/neoscroll.txt @@ -0,0 +1,180 @@ +*neoscroll.txt* Smooth scrolling + +Author: Carles Rafols i Belles + + +=============================================================================== +INTRODUCTION *neoscroll-introduction* + +The default mappings substitute most of the |scrolling| commands (see +|neoscroll-default-mappings| for details) to provide smooth window scrolling. +You can create your own scrolling commands using |neoscroll.scroll()|. You +can customize the scrolling behaviour and the scrolling stop rules by changing +the |neoscroll-options| as needed. + +=============================================================================== +QUICKSTART *neoscroll-quickstart* + +Add the `setup()` function in your init file: +> + " In init.lua: + require('neoscroll').setup() + " In init.vim: + lua require('neoscroll').setup() + +=============================================================================== +FUNCTIONS *neoscroll-functions* + +neoscroll.setup({opts}) *neoscroll-setup()* + Overwrites the default options with the options in the table `opts`, + defines the default mappings and creates the necessary commands. + + Example with all the default options: > + require('neoscroll').setup({ + mappings = {'<C-u>', '<C-d>', '<C-b>', + '<C-f>', '<C-y>', '<C-e>', 'zt', 'zz', 'zb'}, + hide_cursor = true, + stop_eof = true, + respect_scrolloff = false, + cursor_scrolls_alone = true + }) +< + +neoscroll.scroll(lines, move_cursor, time[, easing, info]) *neoscroll.scroll()* + Scroll a number of lines or a fraction of the current window optionally + using an easing function to smooth the start and the end of the scrolling + animation. + `lines`: + - If integer: number of lines to scroll. + - If float: number of "windows" to scroll (e.g. 0.1 = 10% of the + current window's height. + - A positive number scrolls downwards (mnemonic: increase line + number). A negative number scrolls upwards (mnemonic: decrease + line number). + `move_cursor`: scroll and move the cursor simultaneously. When `true` + scrolling downwards will stop when the cursor line is the last + line of the file unless |stop_eof| is set to `true`. + `time`: duration of the scrolling animation in milliseconds. + `easing`: name of the easing function to use for the scrolling + animation. If no name is passed the easing function defined in + |easing_function| will be used. Supported easing function names: + `quadratic`, `cubic`, `quartic`, `quintic`, `circular`, `sine`. + `info`: optional parameter that will be passed to the |pre_hook| and + |post_hook| functions. Useful to differenciate different scrolling + animations or to pass additional information to the hook functions. + + Examples: > + " Scroll 10 lines upwards mimicking <C-d> + :lua require('neoscroll').scroll(-10, true, 300) + " Scroll 'w:scroll' lines downwards (mimicking <C-u> + :lua require('neoscroll').scroll(vim.wo.scroll, true, 300) + " Scroll 10% of the window height downwards + :lua require('neoscroll').scroll(0.1, true, 200) + " Scroll only the screen 10% of the window height downwards + :lua require('neoscroll').scroll(0.1, false, 200) + " Scroll 20 lines upwards using the "sine" easing function. + :lua require('neoscroll').scroll(-20, true, 400, "sine") +< + +neoscroll.{zt,zz,zb}(half_win_time[, easing, info]) *neoscroll-zt-zz-zb* + `scroll()` wrapper functions to mimic |zt|, |zz| and |zb| scrolling + commands. The `half_win_time` argument is the time it would take to + scroll half of the window. The `time` argument passed to the `scroll()` + function is calculated as: > + half_screen_time * (lines_to_scroll/(window_lines/2)) +< where `lines_to_scroll` is the number of lines to be scrolled and + `window_lines` is the number of lines of the window. This ensures that + when there are very few lines to scroll the duration of the scrolling + animation is proportionately shorter. + +neoscroll.config.set_mappings(custom_mappings) + Map all the keymaps in the `custom_mappings` table in normal, visual and + select mode. `custom_mappings` must have the following form: > + t[keys] = {function, {function arguments}} +< If `custom_mappings` is not passed then the default mappings are loaded + only for the keys specified in the |mappings| option. + +=============================================================================== +COMMANDS *neoscroll-commands* + +|NeoscrollEnablePM| / |NeoscrollDisablePM| + Enable/disable "Performance Mode" which disables Tree-sitter and regular + syntax highlighting while scrolling. Particularly useful for large files, + slow machines or heavy regex syntax highlighting. + These commands are the same as |NeoscrollEnableBufferPM| and + |NeoscrollDisableBufferPM|. + +|NeoscrollEnableBufferPM| / |NeoscrollDisableBufferPM| + Enable/disable "Performance Mode" for the current buffer. + +|NeoscrollEnableGlobalPM| / |NeoscrollDisablGlobalePM| + Enable/disable "Performance Mode" for all buffers. + +=============================================================================== +OPTIONS *neoscroll-options* + +|mappings| (default: `{'<C-u>', '<C-d>', '<C-b>', '<C-f>',` + `'<C-y>', '<C-e>', 'zt', 'zz', 'zb'}`) + All the keys defined in this option will be mapped to their corresponding + default scrolling animation. To no map any key pass an empty table (`{}`). + +|hide_cursor| (default: `true`) + If 'termguicolors' is set hide the cursor while scrolling. + +|stop_eof| (default: `true`) + When `move_cursor` is `true` scrolling downwards will stop when the bottom + line of the window is the last line of the file. + +|use_local_scrolloff| (default: `false`) + Due to the Neovim #13964 bug if the local 'scrolloff' option is not + explicitly set it doesn't inherit the global value of the option, instead + it stores a garbage (random) value. Therefore Neoscroll cannot make + assumptions about what scope to use so it will use the global scope by + default unless this option is set to `true`. + +|respect_scrolloff| (default: `false`) + The cursor stops at the scrolloff margin. Try combining this option with + either |stop_eof| or |cursor_scrolls_alone| (or both). + +|cursor_scrolls_alone| (default: `true`) + The cursor will keep on scrolling even if the window cannot scroll + further. + +|easing_function| (default: `nil`) + Name of the easing function to use by default in all scrolling animamtions. + `scroll()` that don't provide the optional `easing` argument will use this + easing function. If set to `nil` (the default) no easing function will be + used in the scrolling animation (constant scrolling speed). + +|pre_hook| (default: `nil`) + Function to run before the scrolling animation starts. The function will + be called with the `info` parameter which can be optionally passed to + `scroll()` (or any of the provided wrappers). This can be used to + conditionally run different hooks for different types of scrolling + animations. + +|post_hook| (default: `nil`) + Equivalent to |pre_hook| but the function will run after the scrolling + animation ends. + +|performance_mode| (default: `false`) + Option to enable "Performance Mode" on all buffers. + +=============================================================================== +DEFAULT MAPPINGS *neoscroll-default-mappings* + +The following mappings are valid for normal, visual and select mode. + + Key Function ~ + <C-u> scroll(-vim.wo.scroll, true, 350) + <C-d> scroll( vim.wo.scroll, true, 350) + <C-b> scroll(-vim.api.nvim_win_get_height(0), true, 550) + <C-f> scroll( vim.api.nvim_win_get_height(0), true, 550) + <C-y> scroll(-0.10, false, 100) + <C-e> scroll( 0.10, false, 100) + zt zt(200) + zz zz(200) + zb zb(200) + +=============================================================================== +vim:tw=78:ts=4:et:ft=help:norl: diff --git a/.config/nvim/pack/bundle/start/neoscroll.nvim/lua/neoscroll/config.lua b/.config/nvim/pack/bundle/start/neoscroll.nvim/lua/neoscroll/config.lua @@ -0,0 +1,87 @@ +local config = {} + +config.options = { + mappings = { "<C-u>", "<C-d>", "<C-b>", "<C-f>", "<C-y>", "<C-e>", "zt", "zz", "zb" }, + hide_cursor = true, + stop_eof = true, + respect_scrolloff = false, + cursor_scrolls_alone = true, + performance_mode = false, +} + +function config.set_options(custom_opts) + config.options = vim.tbl_deep_extend("force", config.options, custom_opts or {}) +end + +config.easing_functions = { + quadratic = function(x) + return 1 - math.pow(1 - x, 1 / 2) + end, + cubic = function(x) + return 1 - math.pow(1 - x, 1 / 3) + end, + quartic = function(x) + return 1 - math.pow(1 - x, 1 / 4) + end, + quintic = function(x) + return 1 - math.pow(1 - x, 1 / 5) + end, + circular = function(x) + return 1 - math.pow(1 - x * x, 1 / 2) + end, + sine = function(x) + return 2 * math.asin(x) / math.pi + end, +} + +local function generate_default_mappings(custom_mappings) + custom_mappings = custom_mappings and custom_mappings or {} + local defaults = {} + defaults["<C-u>"] = { "scroll", { "-vim.wo.scroll", "true", "250" } } + defaults["<C-d>"] = { "scroll", { "vim.wo.scroll", "true", "250" } } + defaults["<C-b>"] = { "scroll", { "-vim.api.nvim_win_get_height(0)", "true", "450" } } + defaults["<C-f>"] = { "scroll", { "vim.api.nvim_win_get_height(0)", "true", "450" } } + defaults["<C-y>"] = { "scroll", { "-0.10", "false", "100" } } + defaults["<C-e>"] = { "scroll", { "0.10", "false", "100" } } + defaults["zt"] = { "zt", { "250" } } + defaults["zz"] = { "zz", { "250" } } + defaults["zb"] = { "zb", { "250" } } + defaults["G"] = { "G", { "100" } } + defaults["gg"] = { "gg", { "100" } } + + local t = {} + local keys = config.options.mappings + for i = 1, #keys do + if defaults[keys[i]] ~= nil then + t[keys[i]] = defaults[keys[i]] + end + end + return t +end + +-- Helper function for mapping keys +local function map_key(key, func, args) + local args_str = table.concat(args, ", ") + local prefix = [[lua require('neoscroll').]] + local lua_cmd = prefix .. func .. "(" .. args_str .. ")" + local cmd = "<cmd>" .. lua_cmd .. "<CR>" + local opts = { silent = true, noremap = true } + vim.api.nvim_set_keymap("n", key, cmd, opts) + vim.api.nvim_set_keymap("x", key, cmd, opts) +end + +-- Set mappings +function config.set_mappings(custom_mappings) + if custom_mappings ~= nil then + for key, val in pairs(custom_mappings) do + map_key(key, val[1], val[2]) + end + else + local default_mappings = generate_default_mappings() + for key, val in pairs(default_mappings) do + map_key(key, val[1], val[2]) + end + end +end + +return config diff --git a/.config/nvim/pack/bundle/start/neoscroll.nvim/lua/neoscroll/init.lua b/.config/nvim/pack/bundle/start/neoscroll.nvim/lua/neoscroll/init.lua @@ -0,0 +1,395 @@ +local config = require("neoscroll.config") +local utils = require("neoscroll.utils") +local opts +local so_scope + +local scroll_timer = vim.loop.new_timer() +local target_line = 0 +local relative_line = 0 +local cursor_win_line +local scrolling = false +local continuous_scroll = false +-- Highlight group to hide the cursor +vim.api.nvim_exec( + [[ +augroup custom_highlight +autocmd! +autocmd ColorScheme * highlight NeoscrollHiddenCursor gui=reverse blend=100 +augroup END +]], + true +) +vim.cmd("highlight NeoscrollHiddenCursor gui=reverse blend=100") + +-- excecute commands to scroll screen [and cursor] up/down one line +-- `execute` is necessary to allow the use of special characters like <C-y> +-- The bang (!) `normal!` in normal ignores mappings +local function scroll_up(data, scroll_window, scroll_cursor, n_repeat) + local n = n_repeat == nil and 1 or n_repeat + local cursor_scroll_input = scroll_cursor and string.rep("gk", n) or "" + local window_scroll_input = scroll_window and [[\<C-y>]] or "" + local scroll_input + -- if scrolloff or window edge are going to move the cursor for you then only + -- scroll the window + if + ( + ( + data.last_line_visible + and data.win_lines_below_cursor == data.lines_below_cursor + and data.lines_below_cursor <= utils.get_scrolloff() + ) or data.win_lines_below_cursor == utils.get_scrolloff() + ) and scroll_window + then + scroll_input = window_scroll_input + else + scroll_input = window_scroll_input .. cursor_scroll_input + end + return [[exec "normal! ]] .. scroll_input .. [["]] +end + +local function scroll_down(data, scroll_window, scroll_cursor, n_repeat) + local n = n_repeat == nil and 1 or n_repeat + local cursor_scroll_input = scroll_cursor and string.rep("gj", n) or "" + local window_scroll_input = scroll_window and [[\<C-e>]] or "" + local scroll_input + -- if scrolloff or window edge are going to move the cursor for you then only + -- scroll the window + if + ( + (data.first_line_visible and data.win_lines_above_cursor <= utils.get_scrolloff()) + or data.win_lines_above_cursor <= utils.get_scrolloff() + ) and scroll_window + then + scroll_input = window_scroll_input + else + scroll_input = window_scroll_input .. cursor_scroll_input + end + return [[exec "normal! ]] .. scroll_input .. [["]] +end + +-- Window rules for when to stop scrolling +local function window_reached_limit(data, move_cursor, direction) + if data.last_line_visible and direction > 0 then + if move_cursor then + if opts.stop_eof and data.lines_below_cursor == data.win_lines_below_cursor then + return true + elseif opts.respect_scrolloff and data.lines_below_cursor <= utils.get_scrolloff() then + return true + else + return data.lines_below_cursor == 0 + end + else + return data.lines_below_cursor == 0 and data.win_lines_above_cursor == 0 + end + elseif data.first_line_visible and direction < 0 then + return true + else + return false + end +end + +-- Cursor rules for when to stop scrolling +local function cursor_reached_limit(data) + if data.first_line_visible then + if opts.respect_scrolloff and data.win_lines_above_cursor <= utils.get_scrolloff() then + return true + end + return data.win_lines_above_cursor == 0 + elseif data.last_line_visible then + if opts.respect_scrolloff and data.lines_below_cursor <= utils.get_scrolloff() then + return true + end + return data.lines_below_cursor == 0 + end +end + +-- Check if the window and the cursor can be scrolled further +local function who_scrolls(data, move_cursor, direction) + local scroll_window, scroll_cursor + local half_window = math.floor(data.window_height / 2) + scroll_window = not window_reached_limit(data, move_cursor, direction) + if not move_cursor then + scroll_cursor = false + elseif scroll_window then + if utils.get_scrolloff() < half_window then + scroll_cursor = true + else + scroll_cursor = false + end + elseif opts.cursor_scrolls_alone then + scroll_cursor = not cursor_reached_limit(data) + else + scroll_cursor = false + end + return scroll_window, scroll_cursor +end + +-- Scroll one line in the given direction +local function scroll_one_line(lines_to_scroll, scroll_window, scroll_cursor, data) + local winline_before = vim.fn.winline() + local curpos_line_before = vim.api.nvim_win_get_cursor(0)[1] + local scroll + local scrolled_lines + if lines_to_scroll > 0 then + scrolled_lines = 1 + scroll = scroll_down + else + scrolled_lines = -1 + scroll = scroll_up + end + vim.cmd(scroll(data, scroll_window, scroll_cursor)) + -- Correct for wrapped lines + local lines_behind = vim.fn.winline() - cursor_win_line + if lines_to_scroll > 0 then + lines_behind = -lines_behind + end + if scroll_cursor and scroll_window and lines_behind > 0 then + vim.cmd(scroll(data, false, scroll_cursor, lines_behind)) + end + if curpos_line_before == vim.api.nvim_win_get_cursor(0)[1] then + -- if curpos_line didn't change, we can use it to get scrolled_lines + -- This is more accurate when some lines are wrapped + scrolled_lines = winline_before - vim.fn.winline() + end + relative_line = relative_line + scrolled_lines +end + +-- Scrolling constructor +local function before_scrolling(lines, move_cursor, info) + if opts.pre_hook ~= nil then + opts.pre_hook(info) + end + -- Start scrolling + scrolling = true + -- Hide cursor line + if opts.hide_cursor and move_cursor then + utils.hide_cursor() + end + -- Performance mode + local performance_mode = vim.b.neoscroll_performance_mode or vim.g.neoscroll_performance_mode + if performance_mode and move_cursor then + if vim.g.loaded_nvim_treesitter then + vim.cmd("TSBufDisable highlight") + end + vim.bo.syntax = "OFF" + end + -- Assign number of lines to scroll + target_line = lines +end + +-- Scrolling destructor +local function stop_scrolling(move_cursor, info) + if opts.hide_cursor == true and move_cursor then + utils.unhide_cursor() + end + --Performance mode + local performance_mode = vim.b.neoscroll_performance_mode or vim.g.neoscroll_performance_mode + if performance_mode and move_cursor then + vim.bo.syntax = "ON" + if vim.g.loaded_nvim_treesitter then + vim.cmd("TSBufEnable highlight") + end + end + if opts.post_hook ~= nil then + opts.post_hook(info) + end + + relative_line = 0 + target_line = 0 + scroll_timer:stop() + scrolling = false + continuous_scroll = false +end + +local function compute_time_step(lines_to_scroll, lines, time, easing_function) + -- lines_to_scroll should always be positive + -- If there's less than one line to scroll time_step doesn't matter + if lines_to_scroll < 1 then + return 1000 + end + local lines_range = math.abs(lines) + local ef = config.easing_functions[easing_function] + local time_step + -- If not yet in range return average time-step + if not ef then + time_step = math.floor(time / (lines_range - 1) + 0.5) + elseif lines_to_scroll >= lines_range then + time_step = math.floor(time * ef(1 / lines_range) + 0.5) + else + local x1 = (lines_range - lines_to_scroll) / lines_range + local x2 = (lines_range - lines_to_scroll + 1) / lines_range + time_step = math.floor(time * (ef(x2) - ef(x1)) + 0.5) + end + if time_step == 0 then + time_step = 1 + end + return time_step +end + +local neoscroll = {} + +-- Scrolling function +-- lines: number of lines to scroll or fraction of window to scroll +-- move_cursor: scroll the window and the cursor simultaneously +-- easing_function: name of the easing function to use for the scrolling animation +function neoscroll.scroll(lines, move_cursor, time, easing_function, info) + -- If lines is a fraction of the window transform it to lines + if utils.is_float(lines) then + lines = utils.get_lines_from_win_fraction(lines) + end + if lines == 0 then + return + end + -- If still scrolling just modify the amount of lines to scroll + -- If the scroll is in the opposite direction and + -- lines_to_scroll is longer than lines stop smoothly + if scrolling then + local lines_to_scroll = relative_line - target_line + local opposite_direction = lines_to_scroll * lines > 0 + local long_scroll = math.abs(lines_to_scroll) - math.abs(lines) > 0 + if opposite_direction and long_scroll then + target_line = relative_line - lines + elseif continuous_scroll then + target_line = relative_line + 2 * lines + elseif math.abs(lines_to_scroll) > math.abs(5 * lines) then + continuous_scroll = true + relative_line = target_line - 2 * lines + else + target_line = target_line + lines + end + + return + end + -- Check if the window and the cursor are allowed to scroll in that direction + local data = utils.get_data() + local half_window = math.floor(data.window_height / 2) + if utils.get_scrolloff() >= half_window then + cursor_win_line = half_window + elseif data.win_lines_above_cursor <= utils.get_scrolloff() then + cursor_win_line = utils.get_scrolloff() + 1 + elseif data.win_lines_below_cursor <= utils.get_scrolloff() then + cursor_win_line = data.window_height - utils.get_scrolloff() + else + cursor_win_line = data.cursor_win_line + end + local scroll_window, scroll_cursor = who_scrolls(data, move_cursor, lines) + -- If neither the window nor the cursor are allowed to scroll finish early + if not scroll_window and not scroll_cursor then + return + end + -- Preparation before scrolling starts + before_scrolling(lines, move_cursor, info) + -- If easing function is not specified default to easing_function + local ef = easing_function and easing_function or opts.easing_function + + local lines_to_scroll = math.abs(relative_line - target_line) + scroll_one_line(lines, scroll_window, scroll_cursor, data) + if lines_to_scroll == 1 then + stop_scrolling(move_cursor, info) + end + local time_step = compute_time_step(lines_to_scroll, lines, time, ef) + local next_time_step = compute_time_step(lines_to_scroll - 1, lines, time, ef) + local next_next_time_step = compute_time_step(lines_to_scroll - 2, lines, time, ef) + -- Scroll the first line + + -- Callback function triggered by scroll_timer + local function scroll_callback() + lines_to_scroll = target_line - relative_line + data = utils.get_data() + scroll_window, scroll_cursor = who_scrolls(data, move_cursor, lines_to_scroll) + if not scroll_window and not scroll_cursor then + stop_scrolling(move_cursor, info) + return + end + + if math.abs(lines_to_scroll) > 2 and ef then + local next_lines_to_scroll = math.abs(lines_to_scroll) - 2 + next_time_step = compute_time_step(next_lines_to_scroll, lines, time, ef) + -- sets the repeat of the next cycle + scroll_timer:set_repeat(next_time_step) + end + if math.abs(lines_to_scroll) == 0 then + stop_scrolling(move_cursor, info) + return + end + scroll_one_line(lines_to_scroll, scroll_window, scroll_cursor, data) + if math.abs(lines_to_scroll) == 1 then + stop_scrolling(move_cursor, info) + return + end + end + + -- Start timer to scroll the rest of the lines + scroll_timer:start(time_step, next_time_step, vim.schedule_wrap(scroll_callback)) + scroll_timer:set_repeat(next_next_time_step) +end + +-- Wrapper for zt +function neoscroll.zt(half_screen_time, easing, info) + local window_height = vim.api.nvim_win_get_height(0) + local win_lines_above_cursor = vim.fn.winline() - 1 + -- Temporary fix for garbage values in local scrolloff when not set + local lines = win_lines_above_cursor - utils.get_scrolloff() + if lines == 0 then + return + end + local corrected_time = math.floor(half_screen_time * (math.abs(lines) / (window_height / 2)) + 0.5) + neoscroll.scroll(lines, false, corrected_time, easing, info) +end +-- Wrapper for zz +function neoscroll.zz(half_screen_time, easing, info) + local window_height = vim.api.nvim_win_get_height(0) + local lines = vim.fn.winline() - math.ceil(window_height / 2) + if lines == 0 then + return + end + local corrected_time = math.floor(half_screen_time * (math.abs(lines) / (window_height / 2)) + 0.5) + neoscroll.scroll(lines, false, corrected_time, easing, info) +end +-- Wrapper for zb +function neoscroll.zb(half_screen_time, easing, info) + local window_height = vim.api.nvim_win_get_height(0) + local lines_below_cursor = window_height - vim.fn.winline() + -- Temporary fix for garbage values in local scrolloff when not set + local lines = -lines_below_cursor + utils.get_scrolloff() + if lines == 0 then + return + end + local corrected_time = math.floor(half_screen_time * (math.abs(lines) / (window_height / 2)) + 0.5) + neoscroll.scroll(lines, false, corrected_time, easing, info) +end + +function neoscroll.G(half_screen_time, easing, info) + local lines = utils.get_lines_below(vim.fn.line("w$")) + local window_height = vim.api.nvim_win_get_height(0) + local cursor_win_line = vim.fn.winline() + local win_lines_below_cursor = window_height - cursor_win_line + local corrected_time = math.floor(half_screen_time * (math.abs(lines) / (window_height / 2)) + 0.5) + neoscroll.scroll(lines, true, corrected_time, easing, { G = true }) +end + +function neoscroll.gg(half_screen_time, easing, info) + local lines = utils.get_lines_above(vim.fn.line("w0")) + local window_height = vim.api.nvim_win_get_height(0) + local cursor_win_line = vim.fn.winline() + lines = -lines - cursor_win_line + local corrected_time = math.floor(half_screen_time * (math.abs(lines) / (window_height / 2)) + 0.5) + neoscroll.scroll(lines, true, corrected_time, easing, info) +end + +function neoscroll.setup(custom_opts) + config.set_options(custom_opts) + opts = require("neoscroll.config").options + require("neoscroll.config").set_mappings() + vim.cmd("command! NeoscrollEnablePM let b:neoscroll_performance_mode = v:true") + vim.cmd("command! NeoscrollDisablePM let b:neoscroll_performance_mode = v:false") + vim.cmd("command! NeoscrollEnableBufferPM let b:neoscroll_performance_mode = v:true") + vim.cmd("command! NeoscrollDisableBufferPM let b:neoscroll_performance_mode = v:false") + vim.cmd("command! NeoscrollEnableGlobalPM let g:neoscroll_performance_mode = v:true") + vim.cmd("command! NeoscrollDisablGlobalePM let g:neoscroll_performance_mode = v:false") + if opts.performance_mode then + vim.g.neoscroll_performance_mode = true + end +end + +return neoscroll diff --git a/.config/nvim/pack/bundle/start/neoscroll.nvim/lua/neoscroll/utils.lua b/.config/nvim/pack/bundle/start/neoscroll.nvim/lua/neoscroll/utils.lua @@ -0,0 +1,97 @@ +local utils = {} + +-- Helper function to check if a number is a float +function utils.is_float(n) + return math.floor(math.abs(n)) ~= math.abs(n) +end + +function utils.get_lines_above(line) + local lines_above = 0 + local first_folded_line = vim.fn.foldclosed(line) + if first_folded_line ~= -1 then + line = first_folded_line + end + while line > 1 do + lines_above = lines_above + 1 + line = line - 1 + first_folded_line = vim.fn.foldclosed(line) + if first_folded_line ~= -1 then + line = first_folded_line + end + end + return lines_above +end + +function utils.get_lines_below(line) + local last_line = vim.fn.line("$") + local lines_below = 0 + local last_folded_line = vim.fn.foldclosedend(line) + if last_folded_line ~= -1 then + line = last_folded_line + end + while line < last_line do + lines_below = lines_below + 1 + line = line + 1 + last_folded_line = vim.fn.foldclosedend(line) + if last_folded_line ~= -1 then + line = last_folded_line + end + end + return lines_below +end + +-- Collect all the necessary window, buffer and cursor data +-- vim.fn.line("w0") -> if there's a fold returns first line of fold +-- vim.fn.line("w$") -> if there's a fold returns last line of fold +function utils.get_data() + local data = {} + data.win_top_line = vim.fn.line("w0") + data.win_bottom_line = vim.fn.line("w$") + data.last_line = vim.fn.line("$") + data.first_line_visible = data.win_top_line == 1 + data.last_line_visible = data.win_bottom_line == data.last_line + data.window_height = vim.api.nvim_win_get_height(0) + data.cursor_win_line = vim.fn.winline() + data.win_lines_below_cursor = data.window_height - data.cursor_win_line + data.win_lines_above_cursor = data.cursor_win_line - 1 + if data.last_line_visible then + data.lines_below_cursor = utils.get_lines_below(vim.fn.line(".")) + end + return data +end + +-- Hide/unhide cursor during scrolling for a better visual effect +function utils.hide_cursor() + if vim.o.termguicolors and vim.o.guicursor ~= "" then + utils.guicursor = vim.o.guicursor + vim.o.guicursor = "a:NeoscrollHiddenCursor" + end +end +function utils.unhide_cursor() + if vim.o.guicursor == "a:NeoscrollHiddenCursor" then + vim.o.guicursor = utils.guicursor + end +end + +-- Transforms fraction of window to number of lines +function utils.get_lines_from_win_fraction(fraction) + local height_fraction = fraction * vim.api.nvim_win_get_height(0) + local lines + if height_fraction < 0 then + lines = -math.floor(math.abs(height_fraction) + 0.5) + else + lines = math.floor(height_fraction + 0.5) + end + return lines +end + +function utils.get_scrolloff() + local window_scrolloff = vim.wo.scrolloff + if window_scrolloff == -1 then + return vim.go.scrolloff + else + return window_scrolloff + end +end + +return utils diff --git a/.config/nvim/pack/bundle/start/neoscroll.nvim/lua/tests/cursor_scrolls_alone_spec.lua b/.config/nvim/pack/bundle/start/neoscroll.nvim/lua/tests/cursor_scrolls_alone_spec.lua @@ -0,0 +1,42 @@ +describe("When EOF is reached", function() + local neoscroll, cursor_start, cursor_finish, window_start, window_finish + local time = 100 + local time_tol = 5 + local lines = 7 + neoscroll = require("neoscroll") + vim.api.nvim_command('help help | only') + + before_each(function() + vim.api.nvim_command('normal ggGM') + time = 100 + time_tol = 5 + lines = 7 + cursor_start = vim.fn.line(".") + window_start = vim.fn.line("w0") + end) + + it("should scroll cursor when cursor_scrolls_alone==true", function() + neoscroll.setup({stop_eof = true, cursor_scrolls_alone = true}) + -- Scroll forwards + -- print('window line:', vim.fn.winline()) + neoscroll.scroll(lines, true, time) + vim.wait(time + time_tol) + -- print('window line:', vim.fn.winline()) + cursor_finish = vim.fn.line(".") + window_finish = vim.fn.line("w0") + assert.equals(cursor_start + lines, cursor_finish) + assert.equals(window_start, window_finish) + end) + + it("should not scroll cursor when cursor_scrolls_alone==false", function() + neoscroll.setup({stop_eof = true, cursor_scrolls_alone = false}) + -- Scroll forwards + neoscroll.scroll(lines, true, time) + vim.wait(time + time_tol) + cursor_finish = vim.fn.line(".") + window_finish = vim.fn.line("w0") + assert.equals(cursor_start, cursor_finish) + assert.equals(window_start, window_finish) + end) + +end) diff --git a/.config/nvim/pack/bundle/start/neoscroll.nvim/lua/tests/minimal_init.vim b/.config/nvim/pack/bundle/start/neoscroll.nvim/lua/tests/minimal_init.vim @@ -0,0 +1,2 @@ +set rtp^=../.. +set rtp^=. diff --git a/.config/nvim/pack/bundle/start/neoscroll.nvim/lua/tests/respect_scrolloff_spec.lua b/.config/nvim/pack/bundle/start/neoscroll.nvim/lua/tests/respect_scrolloff_spec.lua @@ -0,0 +1,223 @@ +describe("When BOF is reached", function() + local neoscroll, cursor_start, cursor_finish, window_start, window_finish + local time = 100 + local time_tol = 5 + local opts = {stop_eof = true, cursor_scrolls_alone = true} + neoscroll = require("neoscroll") + vim.api.nvim_command('help help | only') + + before_each(function() + vim.api.nvim_command('normal ggM') + cursor_start = vim.fn.line(".") + window_start = vim.fn.line("w0") + end) + + it("should scroll cursor till top when respect_scrolloff==false", function() + local scrolloff = 0 + vim.go.scrolloff = scrolloff + opts.respect_scrolloff = false + neoscroll.setup(opts) + -- Scroll forwards + neoscroll.scroll(-(cursor_start-1), true, time) + vim.wait(time + time_tol) + cursor_finish = vim.fn.line(".") + window_finish = vim.fn.line("w0") + assert.equals(scrolloff + 1, cursor_finish) + assert.equals(window_start, window_finish) + end) + + it("should scroll cursor till top when respect_scrolloff==true and go.scrolloff==0", function() + local scrolloff = 0 + vim.go.scrolloff = scrolloff + opts.respect_scrolloff = true + neoscroll.setup(opts) + -- Scroll forwards + neoscroll.scroll(-(cursor_start-1), true, time) + vim.wait(time + time_tol) + cursor_finish = vim.fn.line(".") + window_finish = vim.fn.line("w0") + assert.equals(scrolloff + 1, cursor_finish) + assert.equals(window_start, window_finish) + end) + + it("should not scroll cursor till top when respect_scrolloff==true and go.scrolloff==5", function() + local scrolloff = 5 + vim.go.scrolloff = scrolloff + opts.respect_scrolloff = true + neoscroll.setup(opts) + -- scroll forwards + neoscroll.scroll(-(cursor_start-1), true, time) + vim.wait(time + time_tol) + cursor_finish = vim.fn.line(".") + window_finish = vim.fn.line("w0") + assert.equals(scrolloff + 1, cursor_finish) + assert.equals(window_start, window_finish) + end) + + it("should not scroll cursor till top when respect_scrolloff==true and wo.scrolloff==5", function() + local scrolloff = 5 + vim.go.scrolloff = 0 + vim.wo.scrolloff = scrolloff + opts.respect_scrolloff = true + neoscroll.setup(opts) + -- scroll forwards + neoscroll.scroll(-(cursor_start-1), true, time) + vim.wait(time + time_tol) + cursor_finish = vim.fn.line(".") + window_finish = vim.fn.line("w0") + assert.equals(scrolloff + 1, cursor_finish) + assert.equals(window_start, window_finish) + end) + +end) + +describe("When EOF is reached", function() + local neoscroll, cursor_start, cursor_finish, window_start, window_finish + local time = 100 + local time_tol = 5 + local opts = {stop_eof = true, cursor_scrolls_alone = true} + local last_line = vim.fn.line("$") + local lines + vim.wo.scrolloff = -1 + neoscroll = require("neoscroll") + + before_each(function() + vim.api.nvim_command('normal ggGM') + -- vim.api.nvim_command([[exec "normal! \<c-e>"]]) + cursor_start = vim.fn.line(".") + window_start = vim.fn.line("w0") + lines = last_line - cursor_start + 1 -- +1 to make sure it stops and doesn't go further + end) + + it("should scroll cursor till bottom when respect_scrolloff==false", function() + local scrolloff = 0 + vim.go.scrolloff = scrolloff + opts.respect_scrolloff = false + neoscroll.setup(opts) + -- Scroll forwards + neoscroll.scroll(lines, true, time) + vim.wait(time + time_tol) + cursor_finish = vim.fn.line(".") + window_finish = vim.fn.line("w0") + assert.equals(last_line, cursor_finish) + assert.equals(window_start, window_finish) + end) + + it("should scroll cursor till bottom when respect_scrolloff==true and go.scrolloff==0", function() + local scrolloff = 0 + vim.go.scrolloff = scrolloff + opts.respect_scrolloff = true + neoscroll.setup(opts) + -- Scroll forwards + neoscroll.scroll(lines, true, time) + vim.wait(time + time_tol) + cursor_finish = vim.fn.line(".") + window_finish = vim.fn.line("w0") + assert.equals(last_line, cursor_finish) + assert.equals(window_start, window_finish) + end) + + it("should not scroll cursor till top when respect_scrolloff==true and go.scrolloff==5", function() + local scrolloff = 5 + vim.go.scrolloff = scrolloff + opts.respect_scrolloff = true + neoscroll.setup(opts) + -- Scroll forwards + neoscroll.scroll(lines, true, time) + vim.wait(time + time_tol) + cursor_finish = vim.fn.line(".") + window_finish = vim.fn.line("w0") + assert.equals(last_line - scrolloff, cursor_finish) + assert.equals(window_start, window_finish) + end) + + it("should not scroll cursor till top when respect_scrolloff==true and wo.scrolloff==5", function() + local scrolloff = 5 + vim.go.scrolloff = 0 + vim.wo.scrolloff = scrolloff + opts.respect_scrolloff = true + neoscroll.setup(opts) + -- Scroll forwards + neoscroll.scroll(lines, true, time) + vim.wait(time + time_tol) + cursor_finish = vim.fn.line(".") + window_finish = vim.fn.line("w0") + assert.equals(last_line - scrolloff, cursor_finish) + assert.equals(window_start, window_finish) + end) +end) + +describe("When beyond EOF", function() + local neoscroll, cursor_start, cursor_finish, window_start, window_finish + local time = 100 + local time_tol = 5 + local opts = {stop_eof = true, cursor_scrolls_alone = true} + local lines = vim.fn.winheight(0) + vim.wo.scrolloff = -1 + neoscroll = require("neoscroll") + local last_line = vim.fn.line("$") + + before_each(function() + vim.api.nvim_command('normal ggGM') + vim.api.nvim_command([[exec "normal! \<c-e>"]]) + cursor_start = vim.fn.line(".") + window_start = vim.fn.line("w0") + end) + + it("should scroll cursor till bottom when respect_scrolloff==false", function() + local scrolloff = 0 + vim.go.scrolloff = scrolloff + opts.respect_scrolloff = false + neoscroll.setup(opts) + -- Scroll forwards + neoscroll.scroll(lines, true, time) + vim.wait(time + time_tol) + cursor_finish = vim.fn.line(".") + window_finish = vim.fn.line("w0") + assert.equals(last_line, cursor_finish) + assert.equals(window_start + (cursor_finish - cursor_start) , window_finish) + end) + + it("should scroll cursor till top when respect_scrolloff==true and go.scrolloff==0", function() + local scrolloff = 0 + vim.go.scrolloff = scrolloff + opts.respect_scrolloff = true + neoscroll.setup(opts) + -- Scroll forwards + neoscroll.scroll(lines, true, time) + vim.wait(time + time_tol) + cursor_finish = vim.fn.line(".") + window_finish = vim.fn.line("w0") + assert.equals(last_line, cursor_finish) + assert.equals(window_start + (cursor_finish - cursor_start) , window_finish) + end) + + it("should not scroll cursor till top when respect_scrolloff==true and go.scrolloff==5", function() + local scrolloff = 5 + vim.go.scrolloff = scrolloff + opts.respect_scrolloff = true + neoscroll.setup(opts) + -- Scroll forwards + neoscroll.scroll(lines, true, time) + vim.wait(time + time_tol) + cursor_finish = vim.fn.line(".") + window_finish = vim.fn.line("w0") + assert.equals(last_line - scrolloff, cursor_finish) + assert.equals(window_start + (cursor_finish - cursor_start) , window_finish) + end) + + it("should not scroll cursor till top when respect_scrolloff==true and wo.scrolloff==5", function() + local scrolloff = 5 + vim.go.scrolloff = 0 + vim.wo.scrolloff = scrolloff + opts.respect_scrolloff = true + neoscroll.setup(opts) + -- Scroll forwards + neoscroll.scroll(lines, true, time) + vim.wait(time + time_tol) + cursor_finish = vim.fn.line(".") + window_finish = vim.fn.line("w0") + assert.equals(last_line - scrolloff, cursor_finish) + assert.equals(window_start + (cursor_finish - cursor_start) , window_finish) + end) +end) diff --git a/.config/nvim/pack/bundle/start/neoscroll.nvim/lua/tests/scroll_bottom_spec.lua b/.config/nvim/pack/bundle/start/neoscroll.nvim/lua/tests/scroll_bottom_spec.lua @@ -0,0 +1,82 @@ +local function scroll_win_cursor(scrolloff) + local neoscroll = require("neoscroll") + local time = 100 + local lines = 7 + local time_tol = 5 + local cursor_start = vim.fn.line(".") + local window_start = vim.fn.line("w0") + local cursor_finish, window_finish + + -- Scroll backwards + neoscroll.scroll(-lines, true, time) + vim.wait(time + time_tol) + cursor_finish = vim.fn.line(".") + window_finish = vim.fn.line("w0") + assert.equals(window_start - lines, window_finish) + if not scrolloff then + assert.equals(cursor_start - lines, cursor_finish) + end + + -- Scroll forwards + neoscroll.scroll(lines, true, time) + vim.wait(time + time_tol) + cursor_finish = vim.fn.line(".") + window_finish = vim.fn.line("w0") + assert.equals(window_start, window_finish) + if not scrolloff then + assert.equals(cursor_start, cursor_finish) + end + +end + +local motion_opts = { + stop_eof = false, + respect_scrolloff = true, + cursor_scrolls_alone = false, +} + +describe("Scrolls from bottom without scrolloff", function() + local neoscroll = require("neoscroll") + vim.api.nvim_command('help help | only') + + before_each(function() + vim.api.nvim_command('normal ggG') + end) + + for opt1, val1 in pairs(motion_opts) do + local custom_opts = {[opt1] = val1} + for opt2, val2 in pairs(motion_opts) do + custom_opts[opt2] = val2 + for opt3, val3 in pairs(motion_opts) do + custom_opts[opt3] = val3 + it(vim.inspect(custom_opts), function() + neoscroll.setup(custom_opts) + scroll_win_cursor(vim.go.scrolloff ~= 0) + end) + end + end + end +end) + +describe("Scrolls from bottom with scrolloff", function() + local neoscroll = require("neoscroll") + vim.go.scrolloff = 3 + + before_each(function() + vim.api.nvim_command('normal ggG') + end) + + for opt1, val1 in pairs(motion_opts) do + local custom_opts = {[opt1] = val1} + for opt2, val2 in pairs(motion_opts) do + custom_opts[opt2] = val2 + for opt3, val3 in pairs(motion_opts) do + custom_opts[opt3] = val3 + it(vim.inspect(custom_opts), function() + neoscroll.setup(custom_opts) + scroll_win_cursor(vim.go.scrolloff ~= 0) + end) + end + end + end +end) diff --git a/.config/nvim/pack/bundle/start/neoscroll.nvim/lua/tests/scroll_spec.lua b/.config/nvim/pack/bundle/start/neoscroll.nvim/lua/tests/scroll_spec.lua @@ -0,0 +1,58 @@ +local function scroll_win_cursor() + local neoscroll = require("neoscroll") + local time = 100 + local time_tol = 5 + local lines = 7 + local cursor_start = vim.fn.line(".") + local window_start = vim.fn.line("w0") + local cursor_finish, window_finish + + -- Scroll forwards + neoscroll.scroll(lines, true, time) + vim.wait(time + time_tol) + cursor_finish = vim.fn.line(".") + window_finish = vim.fn.line("w0") + assert.equals(cursor_start + lines, cursor_finish) + assert.equals(window_start + lines, window_finish) + + -- Scroll backwards + neoscroll.scroll(-lines, true, time) + vim.wait(time + time_tol) + cursor_finish = vim.fn.line(".") + window_finish = vim.fn.line("w0") + assert.equals(cursor_start, cursor_finish) + assert.equals(window_start, window_finish) +end + +local motion_opts = { + stop_eof = false, + respect_scrolloff = true, + cursor_scrolls_alone = false, +} + + +describe("Scrolls properly with", function() + local neoscroll = require("neoscroll") + vim.go.scrolloff = 3 + vim.api.nvim_command('help help | only') + + before_each(function() + vim.api.nvim_command('normal ggM') + end) + + for opt1, val1 in pairs(motion_opts) do + local custom_opts = {[opt1] = val1} + for opt2, val2 in pairs(motion_opts) do + custom_opts[opt2] = val2 + for opt3, val3 in pairs(motion_opts) do + custom_opts[opt3] = val3 + it(vim.inspect(custom_opts), function() + neoscroll.setup(custom_opts) + scroll_win_cursor() + end) + end + end + end + + +end) diff --git a/.config/nvim/pack/bundle/start/neoscroll.nvim/lua/tests/scroll_top_spec.lua b/.config/nvim/pack/bundle/start/neoscroll.nvim/lua/tests/scroll_top_spec.lua @@ -0,0 +1,81 @@ +local function scroll_win_cursor(scrolloff) + local neoscroll = require("neoscroll") + local time = 100 + local lines = 7 + local time_tol = 5 + local cursor_start = vim.fn.line(".") + local window_start = vim.fn.line("w0") + local cursor_finish, window_finish + + -- Scroll forwards + neoscroll.scroll(lines, true, time) + vim.wait(time + time_tol) + cursor_finish = vim.fn.line(".") + window_finish = vim.fn.line("w0") + assert.equals(window_start + lines, window_finish) + if not scrolloff then + assert.equals(cursor_start + lines, cursor_finish) + end + + -- Scroll backwards + neoscroll.scroll(-lines, true, time) + vim.wait(time + time_tol) + cursor_finish = vim.fn.line(".") + window_finish = vim.fn.line("w0") + assert.equals(window_start, window_finish) +if not scrolloff then + assert.equals(cursor_start, cursor_finish) +end +end + +local motion_opts = { + stop_eof = false, + respect_scrolloff = true, + cursor_scrolls_alone = false, +} + +describe("Scrolls from top without scrolloff", function() + local neoscroll = require("neoscroll") + vim.api.nvim_command('help help | only') + + before_each(function() + vim.api.nvim_command('normal gg') + end) + + for opt1, val1 in pairs(motion_opts) do + local custom_opts = {[opt1] = val1} + for opt2, val2 in pairs(motion_opts) do + custom_opts[opt2] = val2 + for opt3, val3 in pairs(motion_opts) do + custom_opts[opt3] = val3 + it(vim.inspect(custom_opts), function() + neoscroll.setup(custom_opts) + scroll_win_cursor(vim.go.scrolloff ~= 0) + end) + end + end + end +end) + +describe("Scrolls from top with scrolloff", function() + local neoscroll = require("neoscroll") + vim.go.scrolloff = 3 + + before_each(function() + vim.api.nvim_command('normal gg') + end) + + for opt1, val1 in pairs(motion_opts) do + local custom_opts = {[opt1] = val1} + for opt2, val2 in pairs(motion_opts) do + custom_opts[opt2] = val2 + for opt3, val3 in pairs(motion_opts) do + custom_opts[opt3] = val3 + it(vim.inspect(custom_opts), function() + neoscroll.setup(custom_opts) + scroll_win_cursor(vim.go.scrolloff ~= 0) + end) + end + end + end +end) diff --git a/.config/nvim/pack/bundle/start/neoscroll.nvim/lua/tests/scroll_window_spec.lua b/.config/nvim/pack/bundle/start/neoscroll.nvim/lua/tests/scroll_window_spec.lua @@ -0,0 +1,58 @@ +local function scroll_win_cursor() + local neoscroll = require("neoscroll") + local time = 100 + local time_tol = 5 + local lines = 7 + local cursor_start = vim.fn.line(".") + local window_start = vim.fn.line("w0") + local cursor_finish, window_finish + + -- Scroll forwards + neoscroll.scroll(lines, false, time) + vim.wait(time + time_tol) + cursor_finish = vim.fn.line(".") + window_finish = vim.fn.line("w0") + assert.equals(cursor_start, cursor_finish) + assert.equals(window_start + lines, window_finish) + + -- Scroll backwards + neoscroll.scroll(-lines, false, time) + vim.wait(time + time_tol) + cursor_finish = vim.fn.line(".") + window_finish = vim.fn.line("w0") + assert.equals(cursor_start, cursor_finish) + assert.equals(window_start, window_finish) +end + +local motion_opts = { + stop_eof = false, + respect_scrolloff = true, + cursor_scrolls_alone = false, +} + + +describe("Scrolls properly with", function() + local neoscroll = require("neoscroll") + vim.go.scrolloff = 3 + vim.api.nvim_command('help help | only') + + before_each(function() + vim.api.nvim_command('normal ggM') + end) + + for opt1, val1 in pairs(motion_opts) do + local custom_opts = {[opt1] = val1} + for opt2, val2 in pairs(motion_opts) do + custom_opts[opt2] = val2 + for opt3, val3 in pairs(motion_opts) do + custom_opts[opt3] = val3 + it(vim.inspect(custom_opts), function() + neoscroll.setup(custom_opts) + scroll_win_cursor() + end) + end + end + end + + +end) diff --git a/.config/nvim/pack/bundle/start/neoscroll.nvim/lua/tests/stop_eof_spec.lua b/.config/nvim/pack/bundle/start/neoscroll.nvim/lua/tests/stop_eof_spec.lua @@ -0,0 +1,39 @@ +describe("When EOF is reached", function() + local neoscroll, cursor_start, cursor_finish, window_start, window_finish + local time = 100 + local time_tol = 5 + local lines = 7 + neoscroll = require("neoscroll") + vim.api.nvim_command('help help | only') + + before_each(function() + vim.api.nvim_command('normal ggGM') + cursor_start = vim.fn.line(".") + window_start = vim.fn.line("w0") + end) + + it("should not scroll window further when stop_eof==true", function() + neoscroll.setup({stop_eof = true}) + -- Scroll forwards + -- print('window line:', vim.fn.winline()) + neoscroll.scroll(lines, true, time) + vim.wait(time + time_tol) + -- print('window line:', vim.fn.winline()) + cursor_finish = vim.fn.line(".") + window_finish = vim.fn.line("w0") + assert.equals(cursor_start + lines, cursor_finish) + assert.equals(window_start, window_finish) + end) + + it("should not scroll window further when stop_eof==false", function() + neoscroll.setup({stop_eof = false}) + -- Scroll forwards + neoscroll.scroll(lines, true, time) + vim.wait(time + time_tol) + cursor_finish = vim.fn.line(".") + window_finish = vim.fn.line("w0") + assert.equals(cursor_start + lines, cursor_finish) + assert.equals(window_start + lines, window_finish) + end) + +end) diff --git a/.config/nvim/pack/bundle/start/neoscroll.nvim/scripts/run_tests.sh b/.config/nvim/pack/bundle/start/neoscroll.nvim/scripts/run_tests.sh @@ -0,0 +1,3 @@ +#! /bin/sh + +nvim --headless -c "PlenaryBustedDirectory ../lua/tests {minimal_init = '../lua/tests/minimal_init.vim'}"