8000 mini.nvim/lua/mini/animate.lua at main · cathyprime/mini.nvim · GitHub
[go: up one dir, main page]

Skip to content

Latest commit

 

History

History
2146 lines (1880 loc) · 81.8 KB

animate.lua

File metadata and controls

2146 lines (1880 loc) · 81.8 KB
--- *mini.animate* Animate common Neovim actions
--- *MiniAnimate*
---
--- MIT License Copyright (c) 2022 Evgeni Chasnovski
---
--- ==============================================================================
---
--- Features:
--- - Works out of the box with a single `require('mini.animate').setup()`.
--- No extra mappings or commands needed.
---
--- - Animate cursor movement inside same buffer by showing customizable path.
--- See |MiniAnimate.config.cursor| for more details.
---
--- - Animate scrolling with a series of subscrolls ("smooth scrolling").
--- See |MiniAnimate.config.scroll| for more details.
---
--- - Animate window resize by gradually changing sizes of all windows.
--- See |MiniAnimate.config.resize| for more details.
---
--- - Animate window open/close with visually updating floating window.
--- See |MiniAnimate.config.open| and |MiniAnimate.config.close| for more details.
---
--- - Timings for all actions can be customized independently.
--- See |MiniAnimate-timing| for more details.
---
--- - Action animations can be enabled/disabled independently.
---
--- - All animations are asynchronous/non-blocking and trigger a targeted event
--- which can be used to perform actions after animation is done.
---
--- - |MiniAnimate.animate()| function which can be used to perform own animations.
---
--- Notes:
--- - Cursor movement is animated inside same window and buffer, not as cursor
--- moves across the screen.
---
--- - Scroll and resize animations are done with "side effects": they actually
--- change the state of what is animated (window view and sizes
--- respectively). This has a downside of possibly needing extra work to
--- account for asynchronous nature of animation (like adjusting certain
--- mappings, etc.). See |MiniAnimate.config.scroll| and
--- |MiniAnimate.config.resize| for more details.
---
--- - Although all animations work in all supported versions of Neovim, scroll
--- and resize animations have best experience with Neovim>=0.9. This is due
--- to updated implementation of |WinScrolled| event.
---
--- # Setup ~
---
--- This module needs a setup with `require('mini.animate').setup({})` (replace
--- `{}` with your `config` table). It will create global Lua table `MiniAnimate`
--- which you can use for scripting or manually (with `:lua MiniAnimate.*`).
---
--- See |MiniAnimate.config| for available config settings.
---
--- You can override runtime config settings (like `config.modifiers`) locally
--- to buffer inside `vim.b.minianimate_config` which should have same structure
--- as `MiniAnimate.config`. See |mini.nvim-buffer-local-config| for more details.
---
--- # Comparisons ~
---
--- - Neovide:
--- - Neovide is a standalone GUI which has more control over its animations.
--- While 'mini.animate' works inside terminal emulator (with all its
--- limitations, like lack of pixel-size control over animations).
--- - Neovide animates cursor movement across screen, while 'mini.animate' -
--- as it moves across same buffer.
--- - Neovide has fixed number of animation effects per action, while
--- 'mini.animate' is fully customizable.
--- - 'mini.animate' implements animations for window open/close, while
--- Neovide does not.
--- - 'edluffy/specs.nvim':
--- - 'mini.animate' approaches cursor movement visualization via
--- customizable path function (uses extmarks), while 'specs.nvim' can
--- customize within its own visual effects (shading and floating
--- window resizing).
--- - 'karb94/neoscroll.nvim':
--- - Scroll animation is triggered only inside dedicated mappings.
--- 'mini.animate' animates scroll resulting from any window view change.
--- - 'anuvyklack/windows.nvim':
--- - Resize animation is done only within custom commands and mappings,
--- while 'mini.animate' animates any resize out of the box (works
--- similarly to 'windows.nvim' in Neovim>=0.9 with appropriate
--- 'winheight' / 'winwidth' and 'winminheight' / 'winminwidth').
---
--- # Highlight groups ~
---
--- * `MiniAnimateCursor` - highlight of cursor during its animated movement.
--- * `MiniAnimateNormalFloat` - highlight of floating window for `open` and
--- `close` animations.
---
--- To change any highlight group, modify it directly with |:highlight|.
---
--- # Disabling ~
---
--- To disable, set `vim.g.minianimate_disable` (globally) or
--- `vim.b.minianimate_disable` (for a buffer) to `true`. Considering high
--- number of different scenarios and customization intentions, writing exact
--- rules for disabling module's functionality is left to user. See
--- |mini.nvim-disabling-recipes| for common recipes.
---@diagnostic disable:undefined-field
-- Module definition ==========================================================
local MiniAnimate = {}
local H = {}
--- Module setup
---
---@param config table|nil Module config table. See |MiniAnimate.config|.
---
---@usage >lua
--- require('mini.animate').setup() -- use default config
--- -- OR
--- require('mini.animate').setup({}) -- replace {} with your config table
--- <
MiniAnimate.setup = function(config)
-- Export module
_G.MiniAnimate = MiniAnimate
-- Setup config
config = H.setup_config(config)
-- Apply config
H.apply_config(config)
-- Define behavior
H.create_autocommands()
H.track_scroll_state()
-- Create default highlighting
H.create_default_hl()
end
--- Module config
---
--- Default values:
---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section)
---@text # General ~
--- *MiniAnimate-timing*
--- - Every animation is a non-blockingly scheduled series of specific actions.
--- They are executed in a sequence of timed steps controlled by `timing` option.
--- It is a callable which, given next and total step numbers, returns wait time
--- (in ms). See |MiniAnimate.gen_timing| for builtin timing functions.
--- See |MiniAnimate.animate()| for more details about animation process.
---
--- - Every animation can be enabled/disabled independently by setting `enable`
--- option to `true`/`false`.
---
--- *MiniAnimate-done-event*
--- - Every animation triggers custom |User| event when it is finished. It is
--- named `MiniAnimateDoneXxx` with `Xxx` replaced by capitalized supported
--- animation action name (like `MiniAnimateDoneCursor`). Use it to schedule
--- some action after certain animation is completed. Alternatively, you can
--- use |MiniAnimate.execute_after()| (usually preferred in mappings).
---
--- - Each animation has its main step generator which defines how particular
--- animation is done. They all are callables which take some input data and
--- return an array of step data. Length of that array determines number of
--- animation steps. Outputs `nil` and empty table result in no animation.
---
--- *MiniAnimate.config.cursor*
--- # Cursor ~
---
--- This animation is triggered for each movement of cursor inside same window
--- and buffer. Its visualization step consists from placing single extmark (see
--- |extmarks|) at certain position. This extmark contains single space and is
--- highlighted with `MiniAnimateCursor` highlight group.
---
--- Exact places of extmark and their number is controlled by `path` option. It
--- is a callable which takes `destination` argument (2d integer point in
--- `(line, col)` coordinates) and returns array of relative to `(0, 0)` places
--- for extmark to be placed. Example:
--- - Input `(2, -3)` means cursor jumped 2 lines forward and 3 columns backward.
--- - Output `{ {0, 0 }, { 0, -1 }, { 0, -2 }, { 0, -3 }, { 1, -3 } }` means
--- that path is first visualized along the initial line and then along final
--- column.
---
--- See |MiniAnimate.gen_path| for builtin path generators.
---
--- Notes:
--- - Input `destination` value is computed ignoring folds. This is by design
--- as it helps better visualize distance between two cursor positions.
--- - Outputs of path generator resulting in a place where extmark can't be
--- placed are silently omitted during animation: this step won't show any
--- visualization.
---
--- Configuration example: >lua
---
--- local animate = require('mini.animate')
--- animate.setup({
--- cursor = {
--- -- Animate for 200 milliseconds with linear easing
--- timing = animate.gen_timing.linear({ duration = 200, unit = 'total' }),
---
--- -- Animate with shortest line for any cursor move
--- path = animate.gen_path.line({
--- predicate = function() return true end,
--- }),
--- }
--- })
--- <
--- After animation is done, `MiniAnimateDoneCursor` event is triggered.
---
--- *MiniAnimate.config.scroll*
--- # Scroll ~
---
--- This animation is triggered for each vertical scroll of current window.
--- Its visualization step consists from performing a small subscroll which all
--- in total will result into needed total scroll.
---
--- Exact subscroll values and their number is controlled by `subscroll` option.
--- It is a callable which takes `total_scroll` argument (single non-negative
--- integer) and returns array of non-negative integers each representing the
--- amount of lines needed to be scrolled inside corresponding step. All
--- subscroll values should sum to input `total_scroll`.
--- Example:
--- - Input `5` means that total scroll consists from 5 lines (either up or down,
--- which doesn't matter).
--- - Output of `{ 1, 1, 1, 1, 1 }` means that there are 5 equal subscrolls.
---
--- See |MiniAnimate.gen_subscroll| for builtin subscroll generators.
---
--- Notes:
--- - Input value of `total_scroll` is computed taking folds into account.
--- - As scroll animation is essentially a precisely scheduled non-blocking
--- subscrolls, this has two important interconnected consequences:
--- - If another scroll is attempted during the animation, it is done based
--- on the **currently visible** window view. Example: if user presses
--- |CTRL-D| and then |CTRL-U| when animation is half done, window will not
--- display the previous view half of 'scroll' above it. This especially
--- affects mouse wheel scrolling, as each its turn results in a new scroll
--- for number of lines defined by 'mousescroll'. Tweak it to your liking.
--- - It breaks the use of several relative scrolling commands in the same
--- command. Use |MiniAnimate.execute_after()| to schedule action after
--- reaching target window view.
--- Example: a useful `nnoremap n nzvzz` mapping (consecutive application
--- of |n|, |zv|, and |zz|) should be expressed in the following way: >lua
---
--- '<Cmd>lua vim.cmd("normal! n"); ' ..
--- 'MiniAnimate.execute_after("scroll", "normal! zvzz")<CR>'
--- <
--- - This animation works best with Neovim>=0.9 (after certain updates to
--- |WinScrolled| event).
---
--- Configuration example: >lua
---
--- local animate = require('mini.animate')
--- animate.setup({
--- scroll = {
--- -- Animate for 200 milliseconds with linear easing
--- timing = animate.gen_timing.linear({ duration = 200, unit = 'total' }),
---
--- -- Animate equally but with at most 120 steps instead of default 60
--- subscroll = animate.gen_subscroll.equal({ max_output_steps = 120 }),
--- }
--- })
--- <
--- After animation is done, `MiniAnimateDoneScroll` event is triggered.
---
--- *MiniAnimate.config.resize*
--- # Resize ~
---
--- This animation is triggered for window resize while having same layout of
--- same windows. For example, it won't trigger when window is opened/closed or
--- after something like |CTRL-W_K|. Its visualization step consists from setting
--- certain sizes to all visible windows (last step being for "true" final sizes).
---
--- Exact window step sizes and their number is controlled by `subresize` option.
--- It is a callable which takes `sizes_from` and `sizes_to` arguments (both
--- tables with window id as keys and dimension table as values) and returns
--- array of same shaped data.
--- Example:
--- - Input: >lua
---
--- -- First
--- { [1000] = {width = 7, height = 5}, [1001] = {width = 7, height = 10} }
--- -- Second
--- { [1000] = {width = 9, height = 5}, [1001] = {width = 5, height = 10} }
--- -- Means window 1000 increased its width by 2 in expense of window 1001
--- <
--- - The following output demonstrates equal resizing: >lua
---
--- {
--- { [1000] = {width = 8, height = 5}, [1001] = {width = 6, height = 10} },
--- { [1000] = {width = 9, height = 5}, [1001] = {width = 5, height = 10} },
--- }
--- <
--- See |MiniAnimate.gen_subresize| for builtin subresize generators.
---
--- Notes:
---
--- - As resize animation is essentially a precisely scheduled non-blocking
--- subresizes, this has two important interconnected consequences:
--- - If another resize is attempted during the animation, it is done based
--- on the **currently visible** window sizes. This might affect relative
--- resizing.
--- - It breaks the use of several relative resizing commands in the same
--- command. Use |MiniAnimate.execute_after()| to schedule action after
--- reaching target window sizes.
--- - This animation works best with Neovim>=0.9 (after certain updates to
--- |WinScrolled| event). For example, resize resulting from effect of
--- 'winheight' / 'winwidth' will work properly.
---
--- Configuration example: >lua
---
--- local is_many_wins = function(sizes_from, sizes_to)
--- return vim.tbl_count(sizes_from) >= 3
--- end
--- local animate = require('mini.animate')
--- animate.setup({
--- resize = {
--- -- Animate for 200 milliseconds with linear easing
--- timing = animate.gen_timing.linear({ duration = 200, unit = 'total' }),
---
--- -- Animate only if there are at least 3 windows
--- subresize = animate.gen_subscroll.equal({ predicate = is_many_wins }),
--- }
--- })
--- <
--- After animation is done, `MiniAnimateDoneResize` event is triggered.
---
--- *MiniAnimate.config.open* *MiniAnimate.config.close*
--- # Window open/close ~
---
--- These animations are similarly triggered for regular (non-floating) window
--- open/close. Their visualization step consists from drawing empty floating
--- window with customizable config and transparency.
---
--- Exact window visualization characteristics are controlled by `winconfig`
--- and `winblend` options.
---
--- The `winconfig` option is a callable which takes window id (|window-ID|) as
--- input and returns an array of floating window configs (as in `config`
--- argument of |nvim_open_win()|). Its length determines number of animation steps.
--- Example:
--- - The following output results into two animation steps with second being
--- upper left quarter of a first: >lua
---
--- {
--- {
--- row = 0, col = 0,
--- width = 10, height = 10,
--- relative = 'editor', anchor = 'NW', focusable = false,
--- zindex = 1, style = 'minimal',
--- },
--- {
--- row = 0, col = 0,
--- width = 5, height = 5,
--- relative = 'editor', anchor = 'NW', focusable = false,
--- zindex = 1, style = 'minimal',
--- },
--- }
--- <
--- The `winblend` option is similar to `timing` option: it is a callable
--- which, given current and total step numbers, returns value of floating
--- window's 'winblend' option. Note, that it is called for current step (so
--- starts from 0), as opposed to `timing` which is called before step.
--- Example:
--- - Function `function(s, n) return 80 + 20 * s / n end` results in linear
--- transition from `winblend` value of 80 to 100.
---
--- See |MiniAnimate.gen_winconfig| for builtin window config generators.
--- See |MiniAnimate.gen_winblend| for builtin window transparency generators.
---
--- Configuration example: >lua
---
--- local animate = require('mini.animate')
--- animate.setup({
--- open = {
--- -- Animate for 400 milliseconds with linear easing
--- timing = animate.gen_timing.linear({ duration = 400, unit = 'total' }),
---
--- -- Animate with wiping from nearest edge instead of default static one
--- winconfig = animate.gen_winconfig.wipe({ direction = 'from_edge' }),
---
--- -- Make bigger windows more transparent
--- winblend = animate.gen_winblend.linear({ from = 80, to = 100 }),
--- },
---
--- close = {
--- -- Animate for 400 milliseconds with linear easing
--- timing = animate.gen_timing.linear({ duration = 400, unit = 'total' }),
---
--- -- Animate with wiping to nearest edge instead of default static one
--- winconfig = animate.gen_winconfig.wipe({ direction = 'to_edge' }),
---
--- -- Make bigger windows more transparent
--- winblend = animate.gen_winblend.linear({ from = 100, to = 80 }),
--- },
--- })
--- <
--- After animation is done, `MiniAnimateDoneOpen` or `MiniAnimateDoneClose`
--- event is triggered for `open` and `close` animation respectively.
MiniAnimate.config = {
-- Cursor path
cursor = {
-- Whether to enable this animation
enable = true,
-- Timing of animation (how steps will progress in time)
--minidoc_replace_start timing = --<function: implements linear total 250ms animation duration>,
timing = function(_, n) return 250 / n end,
--minidoc_replace_end
-- Path generator for visualized cursor movement
--minidoc_replace_start path = --<function: implements shortest line path no longer than 1000>,
path = function(destination)
return H.path_line(destination, { predicate = H.default_path_predicate, max_output_steps = 1000 })
end,
--minidoc_replace_end
},
-- Vertical scroll
scroll = {
-- Whether to enable this animation
enable = true,
-- Timing of animation (how steps will progress in time)
--minidoc_replace_start timing = --<function: implements linear total 250ms animation duration>,
timing = function(_, n) return 250 / n end,
--minidoc_replace_end
-- Subscroll generator based on total scroll
--minidoc_replace_start subscroll = --<function: implements equal scroll with at most 60 steps>,
subscroll = function(total_scroll)
return H.subscroll_equal(total_scroll, { predicate = H.default_subscroll_predicate, max_output_steps = 60 })
end,
--minidoc_replace_end
},
-- Window resize
resize = {
-- Whether to enable this animation
enable = true,
-- Timing of animation (how steps will progress in time)
--minidoc_replace_start timing = --<function: implements linear total 250ms animation duration>,
timing = function(_, n) return 250 / n end,
--minidoc_replace_end
-- Subresize generator for all steps of resize animations
--minidoc_replace_start subresize = --<function: implements equal linear steps>,
subresize = function(sizes_from, sizes_to)
return H.subresize_equal(sizes_from, sizes_to, { predicate = H.default_subresize_predicate })
end,
--minidoc_replace_end
},
-- Window open
open = {
-- Whether to enable this animation
enable = true,
-- Timing of animation (how steps will progress in time)
--minidoc_replace_start timing = --<function: implements linear total 250ms animation duration>,
timing = function(_, n) return 250 / n end,
--minidoc_replace_end
-- Floating window config generator visualizing specific window
--minidoc_replace_start winconfig = --<function: implements static window for 25 steps>,
winconfig = function(win_id)
return H.winconfig_static(win_id, { predicate = H.default_winconfig_predicate, n_steps = 25 })
end,
--minidoc_replace_end
-- 'winblend' (window transparency) generator for floating window
--minidoc_replace_start winblend = --<function: implements equal linear steps from 80 to 100>,
winblend = function(s, n) return 80 + 20 * (s / n) end,
--minidoc_replace_end
},
-- Window close
close = {
-- Whether to enable this animation
enable = true,
-- Timing of animation (how steps will progress in time)
--minidoc_replace_start timing = --<function: implements linear total 250ms animation duration>,
timing = function(_, n) return 250 / n end,
--minidoc_replace_end
-- Floating window config generator visualizing specific window
--minidoc_replace_start winconfig = --<function: implements static window for 25 steps>,
winconfig = function(win_id)
return H.winconfig_static(win_id, { predicate = H.default_winconfig_predicate, n_steps = 25 })
end,
--minidoc_replace_end
-- 'winblend' (window transparency) generator for floating window
--minidoc_replace_start winblend = --<function: implements equal linear steps from 80 to 100>,
winblend = function(s, n) return 80 + 20 * (s / n) end,
--minidoc_replace_end
},
}
--minidoc_afterlines_end
-- Module functionality =======================================================
--- Check animation activity
---
---@param animation_type string One of supported animation types
--- (entries of |MiniAnimate.config|, like `'cursor'`, etc.).
---
---@return boolean Whether the animation is currently active.
MiniAnimate.is_active = function(animation_type)
local res = H.cache[animation_type .. '_is_active']
if res == nil then H.error('Wrong `animation_type` for `is_active()`.') end
return res
end
--- Execute action after some animation is done
---
--- Execute action immediately if animation is not active (checked with
--- |MiniAnimate.is_active()|). Else, schedule its execution until after
--- animation is done (on corresponding "done event", see
--- |MiniAnimate-done-event|).
---
--- Mostly meant to be used inside mappings.
---
--- Example ~
---
--- A useful `nnoremap n nzvzz` mapping (consecutive application of |n|, |zv|, and |zz|)
--- should be expressed in the following way: >lua
---
--- '<Cmd>lua vim.cmd("normal! n"); ' ..
--- 'MiniAnimate.execute_after("scroll", "normal! zvzz")<CR>'
--- <
---@param animation_type string One of supported animation types
--- (as in |MiniAnimate.is_active()|).
---@param action string|function Action to be executed. If string, executed as
--- command (via |vim.cmd()|).
MiniAnimate.execute_after = function(animation_type, action)
local event_name = H.animation_done_events[animation_type]
if event_name == nil then H.error('Wrong `animation_type` for `execute_after`.') end
local callable = action
if type(callable) == 'string' then callable = function() vim.cmd(action) end end
if not vim.is_callable(callable) then
H.error('Argument `action` of `execute_after()` should be string or callable.')
end
-- Schedule conditional action execution to allow animation to actually take
-- effect. This helps creating more universal mappings, because some commands
-- (like `n`) not always result into scrolling.
vim.schedule(function()
if MiniAnimate.is_active(animation_type) then
vim.api.nvim_create_autocmd('User', { pattern = event_name, callback = callable })
else
callable()
end
end)
end
-- Action (step 0) - wait (step 1) - action (step 1) - ...
-- `step_action` should return `false` or `nil` (equivalent to not returning anything explicitly) in order to stop animation.
--- Animate action
---
--- This is equivalent to asynchronous execution of the following algorithm:
--- - Call `step_action(0)` immediately after calling this function. Stop if
--- action returned `false` or `nil`.
--- - Wait `step_timing(1)` milliseconds.
--- - Call `step_action(1)`. Stop if it returned `false` or `nil`.
--- - Wait `step_timing(2)` milliseconds.
--- - Call `step_action(2)`. Stop if it returned `false` or `nil`.
--- - ...
---
--- Notes:
--- - Animation is also stopped on action error or if maximum number of steps
--- is reached.
--- - Asynchronous execution is done with |uv.new_timer()|. It only allows
--- integer parts as repeat value. This has several implications:
--- - Outputs of `step_timing()` are accumulated in order to preserve total
--- execution time.
--- - Any wait time less than 1 ms means that action will be executed
--- immediately.
---
---@param step_action function|table Callable which takes `step` (integer 0, 1, 2,
--- etc. indicating current step) and executes some action. Its return value
--- defines when animation should stop: values `false` and `nil` (equivalent
--- to no explicit return) stop animation timer; any other continues it.
---@param step_timing function|table Callable which takes `step` (integer 1, 2, etc.
--- indicating next step) and returns how many milliseconds to wait before
--- executing this step action.
---@param opts table|nil Options. Possible fields:
--- - <max_steps> - Maximum value of allowed step to execute. Default: 10000000.
MiniAnimate.animate = function(step_action, step_timing, opts)
opts = vim.tbl_deep_extend('force', { max_steps = 10000000 }, opts or {})
local step, max_steps = 0, opts.max_steps
local timer, wait_time = vim.loop.new_timer(), 0
local draw_step
draw_step = vim.schedule_wrap(function()
local ok, should_continue = pcall(step_action, step)
if not (ok and should_continue and step < max_steps) then
timer:stop()
return
end
step = step + 1
wait_time = wait_time + step_timing(step)
-- Repeat value of `timer` seems to be rounded down to milliseconds. This
-- means that values less than 1 will lead to timer stop repeating. Instead
-- call next step function directly.
if wait_time < 1 then
timer:set_repeat(0)
-- Use `return` to make this proper "tail call"
return draw_step()
else
timer:set_repeat(wait_time)
wait_time = wait_time - timer:get_repeat()
timer:again()
end
end)
-- Start non-repeating timer without callback execution
timer:start(10000000, 0, draw_step)
-- Draw step zero (at origin) immediately
draw_step()
end
--- Generate animation timing
---
--- Each field corresponds to one family of progression which can be customized
--- further by supplying appropriate arguments.
---
--- This is a table with function elements. Call to actually get timing function.
---
--- Example: >lua
---
--- local animate = require('mini.animate')
--- animate.setup({
--- cursor = {
--- timing = animate.gen_timing.linear({ duration = 100, unit = 'total' })
--- },
--- })
--- <
---@seealso |MiniIndentscope.gen_animation| for similar concept in 'mini.indentscope'.
MiniAnimate.gen_timing = {}
---@alias __animate_timing_opts table|nil Options that control progression. Possible keys:
--- - <easing> `(string)` - a subtype of progression. One of "in"
--- (accelerating from zero speed), "out" (decelerating to zero speed),
--- "in-out" (default; accelerating halfway, decelerating after).
--- - <duration> `(number)` - duration (in ms) of a unit. Default: 20.
--- - <unit> `(string)` - which unit's duration `opts.duration` controls. One
--- of "step" (default; ensures average duration of step to be `opts.duration`)
--- or "total" (ensures fixed total duration regardless of scope's range).
---@alias __animate_timing_return function Timing function (see |MiniAnimate-timing|).
--- Generate timing with no animation
---
--- Show final result immediately. Usually better to use `enable` field in `config`
--- if you want to disable animation.
MiniAnimate.gen_timing.none = function()
return function() return 0 end
end
--- Generate timing with linear progression
---
---@param opts __animate_timing_opts
---
---@return __animate_timing_return
MiniAnimate.gen_timing.linear = function(opts) return H.timing_arithmetic(0, H.normalize_timing_opts(opts)) end
--- Generate timing with quadratic progression
---
---@param opts __animate_timing_opts
---
---@return __animate_timing_return
MiniAnimate.gen_timing.quadratic = function(opts) return H.timing_arithmetic(1, H.normalize_timing_opts(opts)) end
--- Generate timing with cubic progression
---
---@param opts __animate_timing_opts
---
---@return __animate_timing_return
MiniAnimate.gen_timing.cubic = function(opts) return H.timing_arithmetic(2, H.normalize_timing_opts(opts)) end
--- Generate timing with quartic progression
---
---@param opts __animate_timing_opts
---
---@return __animate_timing_return
MiniAnimate.gen_timing.quartic = function(opts) return H.timing_arithmetic(3, H.normalize_timing_opts(opts)) end
--- Generate timing with exponential progression
---
---@param opts __animate_timing_opts
---
---@return __animate_timing_return
MiniAnimate.gen_timing.exponential = function(opts) return H.timing_geometrical(H.normalize_timing_opts(opts)) end
--- Generate cursor animation path
---
--- For more information see |MiniAnimate.config.cursor|.
---
--- This is a table with function elements. Call to actually get generator.
---
--- Example: >lua
---
--- local animate = require('mini.animate')
--- animate.setup({
--- cursor = {
--- -- Animate with line-column angle instead of shortest line
--- path = animate.gen_path.angle(),
--- }
--- })
--- <
MiniAnimate.gen_path = {}
---@alias __animate_path_opts_common table|nil Options that control generator. Possible keys:
--- - <predicate> `(function)` - a callable which takes `destination` as input and
--- returns boolean value indicating whether animation should be done.
--- Default: `false` if `destination` is within one line of origin (reduces
--- flickering), `true` otherwise.
---@alias __animate_path_return function Path function (see |MiniAnimate.config.cursor|).
--- Generate path as shortest line
---
---@param opts __animate_path_opts_common
--- - <max_output_steps> `(number)` - maximum number of steps in output.
--- Default: 1000.
---
---@return __animate_path_return
MiniAnimate.gen_path.line = function(opts)
opts = vim.tbl_deep_extend('force', { predicate = H.default_path_predicate, max_output_steps = 1000 }, opts or {})
return function(destination) return H.path_line(destination, opts) end
end
--- Generate path as line/column angle
---
---@param opts __animate_path_opts_common
--- - <max_output_steps> `(number)` - maximum number of steps per side in output.
--- Default: 1000.
--- - <first_direction> `(string)` - one of `"horizontal"` (default; animates
--- across initial line first) or `"vertical"` (animates across initial
--- column first).
---
---@return __animate_path_return
MiniAnimate.gen_path.angle = function(opts)
local default_opts = { predicate = H.default_path_predicate, max_output_steps = 1000, first_direction = 'horizontal' }
opts = vim.tbl_deep_extend('force', default_opts, opts or {})
local append_horizontal = function(res, dest_col, const_line)
if dest_col == 0 then return end
local n_steps = math.min(math.abs(dest_col), opts.max_output_steps)
local coef = dest_col / n_steps
for i = 0, n_steps - 1 do
table.insert(res, { const_line, H.round(coef * i) })
end
end
local append_vertical = function(res, dest_line, const_col)
if dest_line == 0 then return end
local n_steps = math.min(math.abs(dest_line), opts.max_output_steps)
local coef = dest_line / n_steps
for i = 0, n_steps - 1 do
table.insert(res, { H.round(coef * i), const_col })
end
end
return function(destination)
-- Don't animate in case of false predicate
if not opts.predicate(destination) then return {} end
-- Travel along horizontal/vertical lines
local res = {}
if opts.first_direction == 'horizontal' then
append_horizontal(res, destination[2], 0)
append_vertical(res, destination[1], destination[2])
else
append_vertical(res, destination[1], 0)
append_horizontal(res, destination[2], destination[1])
end
return res
end
end
--- Generate path as closing walls at final position
---
---@param opts __animate_path_opts_common
--- - <width> `(number)` - initial width of left and right walls. Default: 10.
---
---@return __animate_path_return
MiniAnimate.gen_path.walls = function(opts)
opts = opts or {}
local predicate = opts.predicate or H.default_path_predicate
local width = opts.width or 10
return function(destination)
-- Don't animate in case of false predicate
if not predicate(destination) then return {} end
-- Don't animate in case of no movement
if destination[1] == 0 and destination[2] == 0 then return {} end
local dest_line, dest_col = destination[1], destination[2]
local res = {}
for i = width, 1, -1 do
table.insert(res, { dest_line, dest_col + i })
table.insert(res, { dest_line, dest_col - i })
end
return res
end
end
--- Generate path as diminishing spiral at final position
---
---@param opts __animate_path_opts_common
--- - <width> `(number)` - initial width of spiral. Default: 2.
---
---@return __animate_path_return
MiniAnimate.gen_path.spiral = function(opts)
opts = opts or {}
local predicate = opts.predicate or H.default_path_predicate
local width = opts.width or 2
local add_layer = function(res, w, destination)
local dest_line, dest_col = destination[1], destination[2]
--stylua: ignore start
for j = -w, w-1 do table.insert(res, { dest_line - w, dest_col + j }) end
for i = -w, w-1 do table.insert(res, { dest_line + i, dest_col + w }) end
for j = -w, w-1 do table.insert(res, { dest_line + w, dest_col - j }) end
for i = -w, w-1 do table.insert(res, { dest_line - i, dest_col - w }) end
--stylua: ignore end
end
return function(destination)
-- Don't animate in case of false predicate
if not predicate(destination) then return {} end
-- Don't animate in case of no movement
if destination[1] == 0 and destination[2] == 0 then return {} end
local res = {}
for w = width, 1, -1 do
add_layer(res, w, destination)
end
return res
end
end
--- Generate scroll animation subscroll
---
--- For more information see |MiniAnimate.config.scroll|.
---
--- This is a table with function elements. Call to actually get generator.
---
--- Example: >lua
---
--- local animate = require('mini.animate')
--- animate.setup({
--- scroll = {
--- -- Animate equally but with 120 maximum steps instead of default 60
--- subscroll = animate.gen_subscroll.equal({ max_output_steps = 120 }),
--- }
--- })
--- <
MiniAnimate.gen_subscroll = {}
--- Generate subscroll with equal steps
---
---@param opts table|nil Options that control generator. Possible keys:
--- - <predicate> `(function)` - a callable which takes `total_scroll` as
--- input and returns boolean value indicating whether animation should be
--- done. Default: `false` if `total_scroll` is 1 or less (reduces
--- unnecessary waiting), `true` otherwise.
--- - <max_output_steps> `(number)` - maximum number of subscroll steps in output.
--- Adjust this to reduce computations in expense of reduced smoothness.
--- Default: 60.
---
---@return function Subscroll function (see |MiniAnimate.config.scroll|).
MiniAnimate.gen_subscroll.equal = function(opts)
opts = vim.tbl_deep_extend('force', { predicate = H.default_subscroll_predicate, max_output_steps = 60 }, opts or {})
return function(total_scroll) return H.subscroll_equal(total_scroll, opts) end
end
--- Generate resize animation subresize
---
--- For more information see |MiniAnimate.config.resize|.
---
--- This is a table with function elements. Call to actually get generator.
---
--- Example: >lua
---
--- local is_many_wins = function(sizes_from, sizes_to)
--- return vim.tbl_count(sizes_from) >= 3
--- end
--- local animate = require('mini.animate')
--- animate.setup({
--- resize = {
--- -- Animate only if there are at least 3 windows
--- subresize = animate.gen_subresize.equal({ predicate = is_many_wins }),
--- }
--- })
--- <
MiniAnimate.gen_subresize = {}
--- Generate subresize with equal steps
---
---@param opts table|nil Options that control generator. Possible keys:
--- - <predicate> `(function)` - a callable which takes `sizes_from` and
--- `sizes_to` as input and returns boolean value indicating whether
--- animation should be done. Default: always `true`.
---
---@return function Subresize function (see |MiniAnimate.config.resize|).
MiniAnimate.gen_subresize.equal = function(opts)
opts = vim.tbl_deep_extend('force', { predicate = H.default_subresize_predicate }, opts or {})
return function(sizes_from, sizes_to) return H.subresize_equal(sizes_from, sizes_to, opts) end
end
--- Generate open/close animation winconfig
---
--- For more information see |MiniAnimate.config.open| or |MiniAnimate.config.close|.
---
--- This is a table with function elements. Call to actually get generator.
---
--- Example: >lua
---
--- local is_not_single_window = function(win_id)
--- local tabpage_id = vim.api.nvim_win_get_tabpage(win_id)
--- return #vim.api.nvim_tabpage_list_wins(tabpage_id) > 1
--- end
--- local animate = require('mini.animate')
--- animate.setup({
--- open = {
--- -- Animate with wiping from nearest edge instead of default static one
--- -- and only if it is not a single window in tabpage
--- winconfig = animate.gen_winconfig.wipe({
--- predicate = is_not_single_window,
--- direction = 'from_edge',
--- }),
--- },
--- close = {
--- -- Animate with wiping to nearest edge instead of default static one
--- -- and only if it is not a single window in tabpage
--- winconfig = animate.gen_winconfig.wipe({
--- predicate = is_not_single_window,
--- direction = 'to_edge',
--- }),
--- },
--- })
--- <
MiniAnimate.gen_winconfig = {}
---@alias __animate_winconfig_opts_common table|nil Options that control generator. Possible keys:
--- - <predicate> `(function)` - a callable which takes `win_id` as input and
--- returns boolean value indicating whether animation should be done.
--- Default: always `true`.
---@alias __animate_winconfig_return function Winconfig function (see |MiniAnimate.config.open|
--- or |MiniAnimate.config.close|).
--- Generate winconfig for static floating window
---
--- This will result into floating window statically covering whole target
--- window.
---
---@param opts __animate_winconfig_opts_common
--- - <n_steps> `(number)` - number of output steps, all with same config.
--- Useful to tweak smoothness of transparency animation (done inside
--- `winblend` config option). Default: 25.
---
---@return __animate_winconfig_return
MiniAnimate.gen_winconfig.static = function(opts)
opts = vim.tbl_deep_extend('force', { predicate = H.default_winconfig_predicate, n_steps = 25 }, opts or {})
return function(win_id) return H.winconfig_static(win_id, opts) end
end
--- Generate winconfig for center-focused animated floating window
---
--- This will result into floating window growing from or shrinking to the
--- target window center.
---
---@param opts __animate_winconfig_opts_common
--- - <direction> `(string)` - one of `"to_center"` (default; window will
--- shrink from full coverage to center) or `"from_center"` (window will
--- grow from center to full coverage).
---
---@return __animate_winconfig_return
MiniAnimate.gen_winconfig.center = function(opts)
opts = opts or {}
local predicate = opts.predicate or H.default_winconfig_predicate
local direction = opts.direction or 'to_center'
return function(win_id)
-- Don't animate in case of false predicate
if not predicate(win_id) then return {} end
local pos = vim.fn.win_screenpos(win_id)
local row, col = pos[1] - 1, pos[2] - 1
local height, width = vim.api.nvim_win_get_height(win_id), vim.api.nvim_win_get_width(win_id)
0