karb94 / neoscroll.nvim

Smooth scrolling neovim plugin written in lua
MIT License
1.54k stars 37 forks source link

[Feature request] Scrolling cursor alone, without viewport #106

Open Rumi152 opened 5 months ago

Rumi152 commented 5 months ago

Could we get option for scrolling only cursor, without viewport, unless the cursor goes out of bounds. It will be really useful if you create custom scrolling with chaining multiple scrolls with hooks. I am doing equivalent of zz that looks nice, which is combination of scrolling only cursor (not possible), scrolling viewport, and scrolling both

Rumi152 commented 5 months ago

For anyone interested here is my config for auto zz after and , without weird jumping.

local function post_hook(info)
    local neoscroll = require("neoscroll")

    if info == nil then
        return
    end

    if info.move_view ~= nil then
        vim.defer_fn(function()
            neoscroll.scroll(info.move_view.lines, {
                move_cursor = false,
                duration = info.move_view.duration,
            })
        end, 10)
    end

    if info.move_cursor ~= nil then
        vim.defer_fn(function()
            vim.api.nvim_win_set_cursor(0, {
                math.max(1 - vim.fn.line("."), math.min(vim.fn.line("$"), vim.fn.line(".") + info.move_cursor.lines)),
                vim.api.nvim_win_get_cursor(0)[2],
            })
        end, 10 + info.move_cursor.duration)
    end
end

local function scroll_and_center(scroll, half_win_duration)
    local window_height = vim.api.nvim_win_get_height(0)
    local topmost_line = vim.fn.line("w0")
    local current_middle_line = topmost_line + math.ceil(window_height / 2)
    local current_line = vim.fn.line(".")

    local off_target = math.max(-current_line + 1, math.min(scroll, vim.fn.line("$") - current_line))
    local off_center = current_line - current_middle_line

    local both_scroll = 0
    local view_scroll = 0
    local cursor_scroll = 0
    -- both scrolls work together in one direction
    if off_center * off_target >= 0 then
        both_scroll = off_target
        view_scroll = off_center
        cursor_scroll = 0
    else
        both_scroll = off_target + off_center -- off_center is opposite to off target here, so actually its substracting
        view_scroll = 0
        cursor_scroll = -off_center
    end

    local both_scroll_duration = math.floor(half_win_duration * (math.abs(both_scroll) / (window_height / 2)) + 0.5)
    local view_scroll_duration = math.floor(half_win_duration * (math.abs(view_scroll) / (window_height / 2)) + 0.5)
    local cursor_scroll_duration = math.floor(half_win_duration * (math.abs(cursor_scroll) / (window_height / 2)) + 0.5)

    if both_scroll == 0 then
        post_hook({
            move_view = {
                duration = view_scroll_duration,
                lines = view_scroll,
            },
            move_cursor = {
                duration = cursor_scroll_duration,
                lines = cursor_scroll,
            },
        })
    else
        require("neoscroll").scroll(both_scroll, {
            move_cursor = true,
            duration = both_scroll_duration,
            info = {
                move_view = {
                    duration = view_scroll_duration,
                    lines = view_scroll,
                },
                move_cursor = {
                    duration = cursor_scroll_duration,
                    lines = cursor_scroll,
                },
            },
        })
    end
end

return {
    "karb94/neoscroll.nvim",
    config = function()
        local neoscroll = require("neoscroll")
        neoscroll.setup({
            mappings = {},
            -- pre_hook = function(info) end,
            post_hook = post_hook,
        })
        local keymap = {
            ["<C-u>"] = function()
                scroll_and_center(-vim.wo.scroll, 100)
            end,
            ["<C-d>"] = function()
                scroll_and_center(vim.wo.scroll, 100)
            end,
            ["<C-y>"] = function()
                neoscroll.scroll(-0.1, { move_cursor = false, duration = 80 })
            end,
            ["<C-e>"] = function()
                neoscroll.scroll(0.1, { move_cursor = false, duration = 80 })
            end,
            ["zz"] = function()
                neoscroll.zz({ half_win_duration = 100 })
            end,
            -- ["G"] = function()
            --  neoscroll.G({ half_win_duration = 100 })
            -- end,
            -- ["gg"] = function()
            --  neoscroll.gg({ half_win_duration = 100 })
        }
        local modes = { "n", "v", "x" }
        for key, func in pairs(keymap) do
            vim.keymap.set(modes, key, func)
        end
    end,
}
karb94 commented 5 months ago

Can you explain in more detail what kind of animation are you looking to achieve? I copied your config but I can't really tell what is going on.

Rumi152 commented 5 months ago

When the view is at the bottom of a file the cursor keeps scrolling until it hits eof (At least when this option is set in config). I would love to have this possibility whenever i want. My config makes it so C-d is basically C-D + zz, but doign these two commands one after another makes view go down first and then back up after (If cursor is at the top half of screen). I wanted to do it, so view scrolls down just enough to be centered around line that is vim.wo.scroll lines down.

Behavior for C-d (analogous to C-u)

If cursor is on to half, for example first line of window: cursor scrolls half a page, but the view doesnt need to scroll at all. This is problematic because plugin doesn't allow for smooth scrolling cursor, without moving view. Let me know if I need to explain more, english isn't my first language and my lua skills are not great either so it's kind of messy

at-karan-sharma commented 5 months ago

Thanks for sharing your config @Rumi152 -- I was just trying to customize C-u and C-d so that it keeps the cursor in the middle of the screen.

To piggyback on this request, it would be great if this case was natively supported as an option on the plugin itself. My default remap for \<C-d> used to be "\<C-d>zz" (based on ThePrimeagen's dotfiles). Feels less disorienting than the default vim behaviour personally.

In any case, big thanks for neoscroll @karb94!

karb94 commented 4 months ago

This is just a thought but what about using the pre_hook functionality to move the cursor to the centre before starting the scrolling animation? You could even hide the cursor so that the transition is seamless.

Rumi152 commented 4 months ago

I mean yeah, it kind of works right now. Just the option of smoothly moving the cursor instead of hiding it and showing again is not possible. Even without that the plugin is great and I love using similiar solution, it just doesn't seamlessly integrate with defaults (Where both the view and cursor scroll together, not the one where cursor is hidden during animation).

Lcchy commented 1 week ago

I wanted to achieve the same effect and also couldn't manage with neoscroll, so I wrote my own animation and it works well. I thought I would share it here in case it helps, as an inspiration.

https://gist.github.com/Lcchy/a438997093438785db4796ebcb97b873

ps: it uses coop.nvim for async execution