retrovim 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.conda/retrovim.yaml +259 -0
- package/.conda/terax.icns +0 -0
- package/.conda/terax.ico +0 -0
- package/.conda/terax.png +0 -0
- package/.github/workflows/release.yml +95 -0
- package/.gitmodules +3 -0
- package/.npm/kanata_touchcursor.js +28 -0
- package/.npm/nvim.js +26 -0
- package/.npm/yazi.js +26 -0
- package/.npm/zsh.js +17 -0
- package/.pip/kanata_touchcursor/__init__.py +28 -0
- package/.pip/nvim/__init__.py +31 -0
- package/.pip/yazi/__init__.py +31 -0
- package/.pip/zsh/__init__.py +22 -0
- package/CHANGELOG.md +26 -0
- package/LICENSE +674 -0
- package/README.md +336 -0
- package/kanata/touchcursor.kbd +164 -0
- package/nvim/init.lua +813 -0
- package/nvim/plugins/mini.nvim/.github/DISCUSSION_TEMPLATE/q-a.yml +72 -0
- package/nvim/plugins/mini.nvim/.github/ISSUE_TEMPLATE/bug-report.yml +136 -0
- package/nvim/plugins/mini.nvim/.github/ISSUE_TEMPLATE/config.yml +5 -0
- package/nvim/plugins/mini.nvim/.github/ISSUE_TEMPLATE/feature-request.yml +75 -0
- package/nvim/plugins/mini.nvim/.github/PULL_REQUEST_TEMPLATE.md +2 -0
- package/nvim/plugins/mini.nvim/.github/workflows/quality-control.yml +150 -0
- package/nvim/plugins/mini.nvim/.pre-commit-config.yaml +20 -0
- package/nvim/plugins/mini.nvim/.stylua.toml +7 -0
- package/nvim/plugins/mini.nvim/.styluaignore +1 -0
- package/nvim/plugins/mini.nvim/CHANGELOG.md +1894 -0
- package/nvim/plugins/mini.nvim/CODE_OF_CONDUCT.md +132 -0
- package/nvim/plugins/mini.nvim/CONTRIBUTING.md +331 -0
- package/nvim/plugins/mini.nvim/LICENSE +21 -0
- package/nvim/plugins/mini.nvim/MAINTAINING.md +330 -0
- package/nvim/plugins/mini.nvim/Makefile +51 -0
- package/nvim/plugins/mini.nvim/README.md +261 -0
- package/nvim/plugins/mini.nvim/TESTING.md +1011 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/README.md +35 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/benchmark.sh +56 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_dashboard-alpha.lua +21 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_dashboard-original.lua +17 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_dashboard-starter.lua +18 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_empty.lua +4 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_starter-default.lua +7 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_startify-alpha.lua +10 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_startify-original.lua +7 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_startify-starter.lua +20 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/install.sh +10 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/make_summary.py +68 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/startup-summary.md +10 -0
- package/nvim/plugins/mini.nvim/colors/miniautumn.lua +55 -0
- package/nvim/plugins/mini.nvim/colors/minicyan.lua +95 -0
- package/nvim/plugins/mini.nvim/colors/minischeme.lua +95 -0
- package/nvim/plugins/mini.nvim/colors/minispring.lua +53 -0
- package/nvim/plugins/mini.nvim/colors/minisummer.lua +49 -0
- package/nvim/plugins/mini.nvim/colors/miniwinter.lua +51 -0
- package/nvim/plugins/mini.nvim/colors/randomhue.lua +16 -0
- package/nvim/plugins/mini.nvim/doc/mini-ai.txt +889 -0
- package/nvim/plugins/mini.nvim/doc/mini-align.txt +923 -0
- package/nvim/plugins/mini.nvim/doc/mini-animate.txt +937 -0
- package/nvim/plugins/mini.nvim/doc/mini-base16.txt +285 -0
- package/nvim/plugins/mini.nvim/doc/mini-basics.txt +359 -0
- package/nvim/plugins/mini.nvim/doc/mini-bracketed.txt +622 -0
- package/nvim/plugins/mini.nvim/doc/mini-bufremove.txt +123 -0
- package/nvim/plugins/mini.nvim/doc/mini-clue.txt +760 -0
- package/nvim/plugins/mini.nvim/doc/mini-cmdline.txt +392 -0
- package/nvim/plugins/mini.nvim/doc/mini-colors.txt +880 -0
- package/nvim/plugins/mini.nvim/doc/mini-comment.txt +230 -0
- package/nvim/plugins/mini.nvim/doc/mini-completion.txt +477 -0
- package/nvim/plugins/mini.nvim/doc/mini-cursorword.txt +105 -0
- package/nvim/plugins/mini.nvim/doc/mini-deps.txt +590 -0
- package/nvim/plugins/mini.nvim/doc/mini-diff.txt +661 -0
- package/nvim/plugins/mini.nvim/doc/mini-doc.txt +434 -0
- package/nvim/plugins/mini.nvim/doc/mini-extra.txt +856 -0
- package/nvim/plugins/mini.nvim/doc/mini-files.txt +1018 -0
- package/nvim/plugins/mini.nvim/doc/mini-fuzzy.txt +160 -0
- package/nvim/plugins/mini.nvim/doc/mini-git.txt +425 -0
- package/nvim/plugins/mini.nvim/doc/mini-hipatterns.txt +489 -0
- package/nvim/plugins/mini.nvim/doc/mini-hues.txt +420 -0
- package/nvim/plugins/mini.nvim/doc/mini-icons.txt +508 -0
- package/nvim/plugins/mini.nvim/doc/mini-indentscope.txt +485 -0
- package/nvim/plugins/mini.nvim/doc/mini-input.txt +932 -0
- package/nvim/plugins/mini.nvim/doc/mini-jump.txt +202 -0
- package/nvim/plugins/mini.nvim/doc/mini-jump2d.txt +525 -0
- package/nvim/plugins/mini.nvim/doc/mini-keymap.txt +449 -0
- package/nvim/plugins/mini.nvim/doc/mini-map.txt +717 -0
- package/nvim/plugins/mini.nvim/doc/mini-misc.txt +431 -0
- package/nvim/plugins/mini.nvim/doc/mini-move.txt +181 -0
- package/nvim/plugins/mini.nvim/doc/mini-notify.txt +434 -0
- package/nvim/plugins/mini.nvim/doc/mini-nvim.txt +431 -0
- package/nvim/plugins/mini.nvim/doc/mini-operators.txt +472 -0
- package/nvim/plugins/mini.nvim/doc/mini-pairs.txt +346 -0
- package/nvim/plugins/mini.nvim/doc/mini-pick.txt +1535 -0
- package/nvim/plugins/mini.nvim/doc/mini-sessions.txt +239 -0
- package/nvim/plugins/mini.nvim/doc/mini-snippets.txt +1291 -0
- package/nvim/plugins/mini.nvim/doc/mini-splitjoin.txt +536 -0
- package/nvim/plugins/mini.nvim/doc/mini-starter.txt +618 -0
- package/nvim/plugins/mini.nvim/doc/mini-statusline.txt +361 -0
- package/nvim/plugins/mini.nvim/doc/mini-surround.txt +863 -0
- package/nvim/plugins/mini.nvim/doc/mini-tabline.txt +151 -0
- package/nvim/plugins/mini.nvim/doc/mini-test.txt +966 -0
- package/nvim/plugins/mini.nvim/doc/mini-trailspace.txt +91 -0
- package/nvim/plugins/mini.nvim/doc/mini-visits.txt +937 -0
- package/nvim/plugins/mini.nvim/logo.png +0 -0
- package/nvim/plugins/mini.nvim/lua/mini/ai.lua +2244 -0
- package/nvim/plugins/mini.nvim/lua/mini/align.lua +2076 -0
- package/nvim/plugins/mini.nvim/lua/mini/animate.lua +2127 -0
- package/nvim/plugins/mini.nvim/lua/mini/base16.lua +1752 -0
- package/nvim/plugins/mini.nvim/lua/mini/basics.lua +772 -0
- package/nvim/plugins/mini.nvim/lua/mini/bracketed.lua +2013 -0
- package/nvim/plugins/mini.nvim/lua/mini/bufremove.lua +283 -0
- package/nvim/plugins/mini.nvim/lua/mini/clue.lua +2096 -0
- package/nvim/plugins/mini.nvim/lua/mini/cmdline.lua +1182 -0
- package/nvim/plugins/mini.nvim/lua/mini/colors.lua +2483 -0
- package/nvim/plugins/mini.nvim/lua/mini/comment.lua +586 -0
- package/nvim/plugins/mini.nvim/lua/mini/completion.lua +2094 -0
- package/nvim/plugins/mini.nvim/lua/mini/cursorword.lua +311 -0
- package/nvim/plugins/mini.nvim/lua/mini/deps.lua +1660 -0
- package/nvim/plugins/mini.nvim/lua/mini/diff.lua +1938 -0
- package/nvim/plugins/mini.nvim/lua/mini/doc.lua +1371 -0
- package/nvim/plugins/mini.nvim/lua/mini/extra.lua +2290 -0
- package/nvim/plugins/mini.nvim/lua/mini/files.lua +3145 -0
- package/nvim/plugins/mini.nvim/lua/mini/fuzzy.lua +356 -0
- package/nvim/plugins/mini.nvim/lua/mini/git.lua +1737 -0
- package/nvim/plugins/mini.nvim/lua/mini/hipatterns.lua +1042 -0
- package/nvim/plugins/mini.nvim/lua/mini/hues.lua +2088 -0
- package/nvim/plugins/mini.nvim/lua/mini/icons.lua +2254 -0
- package/nvim/plugins/mini.nvim/lua/mini/indentscope.lua +1158 -0
- package/nvim/plugins/mini.nvim/lua/mini/init.lua +427 -0
- package/nvim/plugins/mini.nvim/lua/mini/input.lua +2183 -0
- package/nvim/plugins/mini.nvim/lua/mini/jump.lua +609 -0
- package/nvim/plugins/mini.nvim/lua/mini/jump2d.lua +1244 -0
- package/nvim/plugins/mini.nvim/lua/mini/keymap.lua +880 -0
- package/nvim/plugins/mini.nvim/lua/mini/map.lua +1709 -0
- package/nvim/plugins/mini.nvim/lua/mini/misc.lua +969 -0
- package/nvim/plugins/mini.nvim/lua/mini/move.lua +509 -0
- package/nvim/plugins/mini.nvim/lua/mini/notify.lua +938 -0
- package/nvim/plugins/mini.nvim/lua/mini/operators.lua +1349 -0
- package/nvim/plugins/mini.nvim/lua/mini/pairs.lua +657 -0
- package/nvim/plugins/mini.nvim/lua/mini/pick.lua +3876 -0
- package/nvim/plugins/mini.nvim/lua/mini/sessions.lua +689 -0
- package/nvim/plugins/mini.nvim/lua/mini/snippets.lua +2828 -0
- package/nvim/plugins/mini.nvim/lua/mini/splitjoin.lua +1132 -0
- package/nvim/plugins/mini.nvim/lua/mini/starter.lua +1599 -0
- package/nvim/plugins/mini.nvim/lua/mini/statusline.lua +686 -0
- package/nvim/plugins/mini.nvim/lua/mini/surround.lua +2388 -0
- package/nvim/plugins/mini.nvim/lua/mini/tabline.lua +564 -0
- package/nvim/plugins/mini.nvim/lua/mini/test.lua +2516 -0
- package/nvim/plugins/mini.nvim/lua/mini/trailspace.lua +215 -0
- package/nvim/plugins/mini.nvim/lua/mini/visits.lua +1577 -0
- package/nvim/plugins/mini.nvim/readmes/mini-ai.md +190 -0
- package/nvim/plugins/mini.nvim/readmes/mini-align.md +203 -0
- package/nvim/plugins/mini.nvim/readmes/mini-animate.md +214 -0
- package/nvim/plugins/mini.nvim/readmes/mini-base16.md +198 -0
- package/nvim/plugins/mini.nvim/readmes/mini-basics.md +184 -0
- package/nvim/plugins/mini.nvim/readmes/mini-bracketed.md +193 -0
- package/nvim/plugins/mini.nvim/readmes/mini-bufremove.md +139 -0
- package/nvim/plugins/mini.nvim/readmes/mini-clue.md +260 -0
- package/nvim/plugins/mini.nvim/readmes/mini-cmdline.md +183 -0
- package/nvim/plugins/mini.nvim/readmes/mini-colors.md +169 -0
- package/nvim/plugins/mini.nvim/readmes/mini-comment.md +183 -0
- package/nvim/plugins/mini.nvim/readmes/mini-completion.md +217 -0
- package/nvim/plugins/mini.nvim/readmes/mini-cursorword.md +137 -0
- package/nvim/plugins/mini.nvim/readmes/mini-deps.md +271 -0
- package/nvim/plugins/mini.nvim/readmes/mini-diff.md +289 -0
- package/nvim/plugins/mini.nvim/readmes/mini-doc.md +198 -0
- package/nvim/plugins/mini.nvim/readmes/mini-extra.md +158 -0
- package/nvim/plugins/mini.nvim/readmes/mini-files.md +247 -0
- package/nvim/plugins/mini.nvim/readmes/mini-fuzzy.md +134 -0
- package/nvim/plugins/mini.nvim/readmes/mini-git.md +168 -0
- package/nvim/plugins/mini.nvim/readmes/mini-hipatterns.md +176 -0
- package/nvim/plugins/mini.nvim/readmes/mini-hues.md +285 -0
- package/nvim/plugins/mini.nvim/readmes/mini-icons.md +183 -0
- package/nvim/plugins/mini.nvim/readmes/mini-indentscope.md +191 -0
- package/nvim/plugins/mini.nvim/readmes/mini-input.md +163 -0
- package/nvim/plugins/mini.nvim/readmes/mini-jump.md +161 -0
- package/nvim/plugins/mini.nvim/readmes/mini-jump2d.md +197 -0
- package/nvim/plugins/mini.nvim/readmes/mini-keymap.md +205 -0
- package/nvim/plugins/mini.nvim/readmes/mini-map.md +191 -0
- package/nvim/plugins/mini.nvim/readmes/mini-misc.md +145 -0
- package/nvim/plugins/mini.nvim/readmes/mini-move.md +163 -0
- package/nvim/plugins/mini.nvim/readmes/mini-notify.md +175 -0
- package/nvim/plugins/mini.nvim/readmes/mini-operators.md +193 -0
- package/nvim/plugins/mini.nvim/readmes/mini-pairs.md +158 -0
- package/nvim/plugins/mini.nvim/readmes/mini-pick.md +340 -0
- package/nvim/plugins/mini.nvim/readmes/mini-sessions.md +164 -0
- package/nvim/plugins/mini.nvim/readmes/mini-snippets.md +373 -0
- package/nvim/plugins/mini.nvim/readmes/mini-splitjoin.md +194 -0
- package/nvim/plugins/mini.nvim/readmes/mini-starter.md +176 -0
- package/nvim/plugins/mini.nvim/readmes/mini-statusline.md +155 -0
- package/nvim/plugins/mini.nvim/readmes/mini-surround.md +190 -0
- package/nvim/plugins/mini.nvim/readmes/mini-tabline.md +156 -0
- package/nvim/plugins/mini.nvim/readmes/mini-test.md +175 -0
- package/nvim/plugins/mini.nvim/readmes/mini-trailspace.md +137 -0
- package/nvim/plugins/mini.nvim/readmes/mini-visits.md +243 -0
- package/nvim/plugins/mini.nvim/scripts/dual_log.sh +13 -0
- package/nvim/plugins/mini.nvim/scripts/dual_push.sh +15 -0
- package/nvim/plugins/mini.nvim/scripts/dual_release.sh +102 -0
- package/nvim/plugins/mini.nvim/scripts/dual_sync.sh +115 -0
- package/nvim/plugins/mini.nvim/scripts/init-deps-example.lua +59 -0
- package/nvim/plugins/mini.nvim/scripts/lint-filename-length.sh +16 -0
- package/nvim/plugins/mini.nvim/scripts/lintcommit-ci.sh +18 -0
- package/nvim/plugins/mini.nvim/scripts/lintcommit.lua +393 -0
- package/nvim/plugins/mini.nvim/scripts/minidoc.lua +57 -0
- package/nvim/plugins/mini.nvim/scripts/minimal_init.lua +28 -0
- package/nvim/plugins/mini.nvim/scripts/minitest.lua +4 -0
- package/package.json +16 -0
- package/pyproject.toml +43 -0
- package/yazi/init.lua +12 -0
- package/yazi/keymap.toml +114 -0
- package/yazi/lesskey +5 -0
- package/yazi/plugins/bookmarks.yazi/main.lua +86 -0
- package/yazi/plugins/cd-git-root.yazi/main.lua +31 -0
- package/yazi/plugins/smart-enter.yazi/main.lua +11 -0
- package/yazi/theme.toml +26 -0
- package/yazi/yazi.toml +15 -0
- package/zsh/.zshrc +145 -0
- package/zsh/starship.toml +20 -0
- package/zsh/zsh-patina.toml +2 -0
|
@@ -0,0 +1,2127 @@
|
|
|
1
|
+
--- *mini.animate* Animate common Neovim actions
|
|
2
|
+
---
|
|
3
|
+
--- MIT License Copyright (c) 2022 Evgeni Chasnovski
|
|
4
|
+
|
|
5
|
+
--- Features:
|
|
6
|
+
--- - Works out of the box with a single `require('mini.animate').setup()`.
|
|
7
|
+
--- No extra mappings or commands needed.
|
|
8
|
+
---
|
|
9
|
+
--- - Animate cursor movement inside same buffer by showing customizable path.
|
|
10
|
+
--- See |MiniAnimate.config.cursor| for more details.
|
|
11
|
+
---
|
|
12
|
+
--- - Animate scrolling with a series of subscrolls ("smooth scrolling").
|
|
13
|
+
--- See |MiniAnimate.config.scroll| for more details.
|
|
14
|
+
---
|
|
15
|
+
--- - Animate window resize by gradually changing sizes of all windows.
|
|
16
|
+
--- See |MiniAnimate.config.resize| for more details.
|
|
17
|
+
---
|
|
18
|
+
--- - Animate window open/close with visually updating floating window.
|
|
19
|
+
--- See |MiniAnimate.config.open| and |MiniAnimate.config.close| for more details.
|
|
20
|
+
---
|
|
21
|
+
--- - Timings for all actions can be customized independently.
|
|
22
|
+
--- See |MiniAnimate-timing| for more details.
|
|
23
|
+
---
|
|
24
|
+
--- - Action animations can be enabled/disabled independently.
|
|
25
|
+
---
|
|
26
|
+
--- - All animations are asynchronous/non-blocking and trigger a targeted event
|
|
27
|
+
--- which can be used to perform actions after animation is done.
|
|
28
|
+
---
|
|
29
|
+
--- - |MiniAnimate.animate()| function which can be used to perform own animations.
|
|
30
|
+
---
|
|
31
|
+
--- Notes:
|
|
32
|
+
--- - Cursor movement is animated inside same window and buffer, not as cursor
|
|
33
|
+
--- moves across the screen.
|
|
34
|
+
---
|
|
35
|
+
--- - Scroll and resize animations are done with "side effects": they actually
|
|
36
|
+
--- change the state of what is animated (window view and sizes
|
|
37
|
+
--- respectively). This has a downside of possibly needing extra work to
|
|
38
|
+
--- account for asynchronous nature of animation (like adjusting certain
|
|
39
|
+
--- mappings, etc.). See |MiniAnimate.config.scroll| and
|
|
40
|
+
--- |MiniAnimate.config.resize| for more details.
|
|
41
|
+
---
|
|
42
|
+
--- # Setup ~
|
|
43
|
+
---
|
|
44
|
+
--- This module needs a setup with `require('mini.animate').setup({})` (replace
|
|
45
|
+
--- `{}` with your `config` table). It will create global Lua table `MiniAnimate`
|
|
46
|
+
--- which you can use for scripting or manually (with `:lua MiniAnimate.*`).
|
|
47
|
+
---
|
|
48
|
+
--- See |MiniAnimate.config| for available config settings.
|
|
49
|
+
---
|
|
50
|
+
--- You can override runtime config settings (like `config.modifiers`) locally
|
|
51
|
+
--- to buffer inside `vim.b.minianimate_config` which should have same structure
|
|
52
|
+
--- as `MiniAnimate.config`. See |mini.nvim-buffer-local-config| for more details.
|
|
53
|
+
---
|
|
54
|
+
--- # Comparisons ~
|
|
55
|
+
---
|
|
56
|
+
--- - [Neovide](https://neovide.dev/):
|
|
57
|
+
--- - Neovide is a standalone GUI which has more control over its animations.
|
|
58
|
+
--- While |mini.animate| works inside terminal emulator (with all its
|
|
59
|
+
--- limitations, like lack of pixel-size control over animations).
|
|
60
|
+
--- - Neovide animates cursor movement across screen, while |mini.animate| -
|
|
61
|
+
--- as it moves across same buffer.
|
|
62
|
+
--- - Neovide has fixed number of animation effects per action, while
|
|
63
|
+
--- |mini.animate| is fully customizable.
|
|
64
|
+
--- - |mini.animate| implements animations for window open/close, while
|
|
65
|
+
--- Neovide does not.
|
|
66
|
+
--- - [edluffy/specs.nvim](https://github.com/edluffy/specs.nvim):
|
|
67
|
+
--- - |mini.animate| approaches cursor movement visualization via
|
|
68
|
+
--- customizable path function (uses extmarks), while `specs.nvim` can
|
|
69
|
+
--- customize within its own visual effects (shading and floating
|
|
70
|
+
--- window resizing).
|
|
71
|
+
--- - [karb94/neoscroll.nvim](https://github.com/karb94/neoscroll.nvim):
|
|
72
|
+
--- - Scroll animation is triggered only inside dedicated mappings.
|
|
73
|
+
--- |mini.animate| animates scroll resulting from any window view change.
|
|
74
|
+
--- - [anuvyklack/windows.nvim](https://github.com/anuvyklack/windows.nvim):
|
|
75
|
+
--- - Resize animation is done only within custom commands and mappings,
|
|
76
|
+
--- while |mini.animate| animates any resize with appropriate values of
|
|
77
|
+
--- |'winheight'| / |'winwidth'| and |'winminheight'| / |'winminwidth'|).
|
|
78
|
+
---
|
|
79
|
+
--- # Highlight groups ~
|
|
80
|
+
--- *MiniAnimate-hl-groups*
|
|
81
|
+
---
|
|
82
|
+
--- - `MiniAnimateCursor` - highlight of cursor during its animated movement.
|
|
83
|
+
--- - `MiniAnimateNormalFloat` - highlight of floating window for `open` and
|
|
84
|
+
--- `close` animations.
|
|
85
|
+
---
|
|
86
|
+
--- To change any highlight group, set it directly with |nvim_set_hl()|.
|
|
87
|
+
---
|
|
88
|
+
--- # Disabling ~
|
|
89
|
+
---
|
|
90
|
+
--- To disable, set `vim.g.minianimate_disable` (globally) or
|
|
91
|
+
--- `vim.b.minianimate_disable` (for a buffer) to `true`. Considering high
|
|
92
|
+
--- number of different scenarios and customization intentions, writing exact
|
|
93
|
+
--- rules for disabling module's functionality is left to user. See
|
|
94
|
+
--- |mini.nvim-disabling-recipes| for common recipes.
|
|
95
|
+
---@tag MiniAnimate
|
|
96
|
+
|
|
97
|
+
---@diagnostic disable:undefined-field
|
|
98
|
+
|
|
99
|
+
-- Module definition ==========================================================
|
|
100
|
+
local MiniAnimate = {}
|
|
101
|
+
local H = {}
|
|
102
|
+
|
|
103
|
+
--- Module setup
|
|
104
|
+
---
|
|
105
|
+
---@param config table|nil Module config table. See |MiniAnimate.config|.
|
|
106
|
+
---
|
|
107
|
+
---@usage >lua
|
|
108
|
+
--- require('mini.animate').setup() -- use default config
|
|
109
|
+
--- -- OR
|
|
110
|
+
--- require('mini.animate').setup({}) -- replace {} with your config table
|
|
111
|
+
--- <
|
|
112
|
+
MiniAnimate.setup = function(config)
|
|
113
|
+
-- TODO: Remove after Neovim=0.9 support is dropped
|
|
114
|
+
if vim.fn.has('nvim-0.10') == 0 then
|
|
115
|
+
vim.notify(
|
|
116
|
+
'(mini.animate) Neovim<0.10 is soft deprecated (module works but is not supported).'
|
|
117
|
+
.. " It will be deprecated after the next 'mini.nvim' release (module might not work)."
|
|
118
|
+
.. ' Please update your Neovim version.'
|
|
119
|
+
)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
-- Export module
|
|
123
|
+
_G.MiniAnimate = MiniAnimate
|
|
124
|
+
|
|
125
|
+
-- Setup config
|
|
126
|
+
config = H.setup_config(config)
|
|
127
|
+
|
|
128
|
+
-- Apply config
|
|
129
|
+
H.apply_config(config)
|
|
130
|
+
|
|
131
|
+
-- Define behavior
|
|
132
|
+
H.create_autocommands()
|
|
133
|
+
H.track_scroll_state()
|
|
134
|
+
|
|
135
|
+
-- Create default highlighting
|
|
136
|
+
H.create_default_hl()
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
--- Defaults ~
|
|
140
|
+
---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section)
|
|
141
|
+
---@text # General ~
|
|
142
|
+
---
|
|
143
|
+
--- - *MiniAnimate-timing* Every animation is a non-blockingly scheduled series of
|
|
144
|
+
--- specific actions. They are executed in a sequence of timed steps controlled
|
|
145
|
+
--- by `timing` option. It is a callable which, given next and total step numbers,
|
|
146
|
+
--- returns wait time (in ms).
|
|
147
|
+
--- See |MiniAnimate.gen_timing| for builtin timing functions.
|
|
148
|
+
--- See |MiniAnimate.animate()| for more details about animation process.
|
|
149
|
+
---
|
|
150
|
+
--- - Every animation can be enabled/disabled independently by setting `enable`
|
|
151
|
+
--- option to `true`/`false`.
|
|
152
|
+
---
|
|
153
|
+
--- - *MiniAnimate-done-event* Every animation triggers custom |User| event when it
|
|
154
|
+
--- is finished. It is named `MiniAnimateDoneXxx` with `Xxx` replaced by capitalized
|
|
155
|
+
--- supported animation action name (like `MiniAnimateDoneCursor`). Use it to
|
|
156
|
+
--- schedule some action after certain animation is completed. Alternatively,
|
|
157
|
+
--- you can use |MiniAnimate.execute_after()| (usually preferred in mappings).
|
|
158
|
+
---
|
|
159
|
+
--- - Each animation has its main step generator which defines how particular
|
|
160
|
+
--- animation is done. They all are callables which take some input data and
|
|
161
|
+
--- return an array of step data. Length of that array determines number of
|
|
162
|
+
--- animation steps. Outputs `nil` and empty table result in no animation.
|
|
163
|
+
---
|
|
164
|
+
--- # Cursor ~
|
|
165
|
+
--- *MiniAnimate.config.cursor*
|
|
166
|
+
---
|
|
167
|
+
--- This animation is triggered for each movement of cursor inside same window
|
|
168
|
+
--- and buffer. Its visualization step consists from placing single extmark (see
|
|
169
|
+
--- |extmarks|) at certain position. This extmark contains single space and is
|
|
170
|
+
--- highlighted with `MiniAnimateCursor` highlight group.
|
|
171
|
+
---
|
|
172
|
+
--- Exact places of extmark and their number is controlled by `path` option. It
|
|
173
|
+
--- is a callable which takes `destination` argument (2d integer point in
|
|
174
|
+
--- `(line, col)` coordinates) and returns array of relative to `(0, 0)` places
|
|
175
|
+
--- for extmark to be placed. Example:
|
|
176
|
+
--- - Input `(2, -3)` means cursor jumped 2 lines forward and 3 columns backward.
|
|
177
|
+
--- - Output `{ {0, 0 }, { 0, -1 }, { 0, -2 }, { 0, -3 }, { 1, -3 } }` means
|
|
178
|
+
--- that path is first visualized along the initial line and then along final
|
|
179
|
+
--- column.
|
|
180
|
+
---
|
|
181
|
+
--- See |MiniAnimate.gen_path| for builtin path generators.
|
|
182
|
+
---
|
|
183
|
+
--- Notes:
|
|
184
|
+
--- - Input `destination` value is computed ignoring folds. This is by design
|
|
185
|
+
--- as it helps better visualize distance between two cursor positions.
|
|
186
|
+
--- - Outputs of path generator resulting in a place where extmark can't be
|
|
187
|
+
--- placed are silently omitted during animation: this step won't show any
|
|
188
|
+
--- visualization.
|
|
189
|
+
---
|
|
190
|
+
--- Configuration example: >lua
|
|
191
|
+
---
|
|
192
|
+
--- local animate = require('mini.animate')
|
|
193
|
+
--- animate.setup({
|
|
194
|
+
--- cursor = {
|
|
195
|
+
--- -- Animate for 200 milliseconds with linear easing
|
|
196
|
+
--- timing = animate.gen_timing.linear({ duration = 200, unit = 'total' }),
|
|
197
|
+
---
|
|
198
|
+
--- -- Animate with shortest line for any cursor move
|
|
199
|
+
--- path = animate.gen_path.line({
|
|
200
|
+
--- predicate = function() return true end,
|
|
201
|
+
--- }),
|
|
202
|
+
--- }
|
|
203
|
+
--- })
|
|
204
|
+
--- <
|
|
205
|
+
--- After animation is done, `MiniAnimateDoneCursor` event is triggered.
|
|
206
|
+
---
|
|
207
|
+
--- # Scroll ~
|
|
208
|
+
--- *MiniAnimate.config.scroll*
|
|
209
|
+
---
|
|
210
|
+
--- This animation is triggered for each vertical scroll of current window.
|
|
211
|
+
--- Its visualization step consists from performing a small subscroll which all
|
|
212
|
+
--- in total will result into needed total scroll.
|
|
213
|
+
---
|
|
214
|
+
--- Exact subscroll values and their number is controlled by `subscroll` option.
|
|
215
|
+
--- It is a callable which takes `total_scroll` argument (single non-negative
|
|
216
|
+
--- integer) and returns array of non-negative integers each representing the
|
|
217
|
+
--- amount of lines needed to be scrolled inside corresponding step. All
|
|
218
|
+
--- subscroll values should sum to input `total_scroll`.
|
|
219
|
+
--- Example:
|
|
220
|
+
--- - Input `5` means that total scroll consists from 5 lines (either up or down,
|
|
221
|
+
--- which doesn't matter).
|
|
222
|
+
--- - Output of `{ 1, 1, 1, 1, 1 }` means that there are 5 equal subscrolls.
|
|
223
|
+
---
|
|
224
|
+
--- See |MiniAnimate.gen_subscroll| for builtin subscroll generators.
|
|
225
|
+
---
|
|
226
|
+
--- Notes:
|
|
227
|
+
--- - Input value of `total_scroll` is computed taking folds into account.
|
|
228
|
+
--- - As scroll animation is essentially a precisely scheduled non-blocking
|
|
229
|
+
--- subscrolls, this has two important interconnected consequences:
|
|
230
|
+
--- - If another scroll is attempted during the animation, it is done based
|
|
231
|
+
--- on the CURRENTLY VISIBLE window view. Example: if user presses |CTRL-D|
|
|
232
|
+
--- and then |CTRL-U| when animation is half done, window will not display
|
|
233
|
+
--- the previous view half of |'scroll'| above it. This especially affects
|
|
234
|
+
--- mouse wheel scrolling, as each its turn results in a new scroll for
|
|
235
|
+
--- number of lines defined by |'mousescroll'|. Tweak it to your liking.
|
|
236
|
+
--- - It breaks the use of several relative scrolling commands in the same
|
|
237
|
+
--- command. Use |MiniAnimate.execute_after()| to schedule action after
|
|
238
|
+
--- reaching target window view.
|
|
239
|
+
--- Example: a useful `nnoremap n nzvzz` mapping (consecutive application
|
|
240
|
+
--- of |n|, |zv|, and |zz|) should be expressed in the following way: >lua
|
|
241
|
+
---
|
|
242
|
+
--- '<Cmd>lua vim.cmd("normal! n"); ' ..
|
|
243
|
+
--- 'MiniAnimate.execute_after("scroll", "normal! zvzz")<CR>'
|
|
244
|
+
--- <
|
|
245
|
+
--- - Default timing might conflict with scrolling via holding a key (like `j` or `k`
|
|
246
|
+
--- with |'wrap'| enabled) due to high key repeat rate: next scroll is done before
|
|
247
|
+
--- first step of current one finishes. Resolve this by not scrolling like that
|
|
248
|
+
--- or by ensuring maximum value of step duration to be lower than between
|
|
249
|
+
--- repeated keys: set timing like `function(_, n) return math.min(250/n, 10) end`
|
|
250
|
+
--- or use timing with constant step duration.
|
|
251
|
+
---
|
|
252
|
+
--- Configuration example: >lua
|
|
253
|
+
---
|
|
254
|
+
--- local animate = require('mini.animate')
|
|
255
|
+
--- animate.setup({
|
|
256
|
+
--- scroll = {
|
|
257
|
+
--- -- Animate for 200 milliseconds with linear easing
|
|
258
|
+
--- timing = animate.gen_timing.linear({ duration = 200, unit = 'total' }),
|
|
259
|
+
---
|
|
260
|
+
--- -- Animate equally but with at most 120 steps instead of default 60
|
|
261
|
+
--- subscroll = animate.gen_subscroll.equal({ max_output_steps = 120 }),
|
|
262
|
+
--- }
|
|
263
|
+
--- })
|
|
264
|
+
--- <
|
|
265
|
+
--- After animation is done, `MiniAnimateDoneScroll` event is triggered.
|
|
266
|
+
---
|
|
267
|
+
--- # Resize ~
|
|
268
|
+
--- *MiniAnimate.config.resize*
|
|
269
|
+
---
|
|
270
|
+
--- This animation is triggered for window resize while having same layout of
|
|
271
|
+
--- same windows. For example, it won't trigger when window is opened/closed or
|
|
272
|
+
--- after something like |CTRL-W_K|. Its visualization step consists from setting
|
|
273
|
+
--- certain sizes to all visible windows (last step being for "true" final sizes).
|
|
274
|
+
---
|
|
275
|
+
--- Exact window step sizes and their number is controlled by `subresize` option.
|
|
276
|
+
--- It is a callable which takes `sizes_from` and `sizes_to` arguments (both
|
|
277
|
+
--- tables with window id as keys and dimension table as values) and returns
|
|
278
|
+
--- array of same shaped data.
|
|
279
|
+
--- Example:
|
|
280
|
+
--- - Input: >lua
|
|
281
|
+
---
|
|
282
|
+
--- -- First
|
|
283
|
+
--- { [1000] = {width = 7, height = 5}, [1001] = {width = 7, height = 10} }
|
|
284
|
+
--- -- Second
|
|
285
|
+
--- { [1000] = {width = 9, height = 5}, [1001] = {width = 5, height = 10} }
|
|
286
|
+
--- -- Means window 1000 increased its width by 2 in expense of window 1001
|
|
287
|
+
--- <
|
|
288
|
+
--- - The following output demonstrates equal resizing: >lua
|
|
289
|
+
---
|
|
290
|
+
--- {
|
|
291
|
+
--- { [1000] = {width = 8, height = 5}, [1001] = {width = 6, height = 10} },
|
|
292
|
+
--- { [1000] = {width = 9, height = 5}, [1001] = {width = 5, height = 10} },
|
|
293
|
+
--- }
|
|
294
|
+
--- <
|
|
295
|
+
--- See |MiniAnimate.gen_subresize| for builtin subresize generators.
|
|
296
|
+
---
|
|
297
|
+
--- Notes:
|
|
298
|
+
---
|
|
299
|
+
--- - As resize animation is essentially a precisely scheduled non-blocking
|
|
300
|
+
--- subresizes, this has two important interconnected consequences:
|
|
301
|
+
--- - If another resize is attempted during the animation, it is done based
|
|
302
|
+
--- on the CURRENTLY VISIBLE window sizes. This might affect relative
|
|
303
|
+
--- resizing.
|
|
304
|
+
--- - It breaks the use of several relative resizing commands in the same
|
|
305
|
+
--- command. Use |MiniAnimate.execute_after()| to schedule action after
|
|
306
|
+
--- reaching target window sizes.
|
|
307
|
+
---
|
|
308
|
+
--- Configuration example: >lua
|
|
309
|
+
---
|
|
310
|
+
--- local is_many_wins = function(sizes_from, sizes_to)
|
|
311
|
+
--- return vim.tbl_count(sizes_from) >= 3
|
|
312
|
+
--- end
|
|
313
|
+
--- local animate = require('mini.animate')
|
|
314
|
+
--- animate.setup({
|
|
315
|
+
--- resize = {
|
|
316
|
+
--- -- Animate for 200 milliseconds with linear easing
|
|
317
|
+
--- timing = animate.gen_timing.linear({ duration = 200, unit = 'total' }),
|
|
318
|
+
---
|
|
319
|
+
--- -- Animate only if there are at least 3 windows
|
|
320
|
+
--- subresize = animate.gen_subscroll.equal({ predicate = is_many_wins }),
|
|
321
|
+
--- }
|
|
322
|
+
--- })
|
|
323
|
+
--- <
|
|
324
|
+
--- After animation is done, `MiniAnimateDoneResize` event is triggered.
|
|
325
|
+
---
|
|
326
|
+
--- # Window open/close ~
|
|
327
|
+
--- *MiniAnimate.config.open*
|
|
328
|
+
--- *MiniAnimate.config.close*
|
|
329
|
+
---
|
|
330
|
+
--- These animations are similarly triggered for regular (non-floating) window
|
|
331
|
+
--- open/close. Their visualization step consists from drawing empty floating
|
|
332
|
+
--- window with customizable config and transparency.
|
|
333
|
+
---
|
|
334
|
+
--- Exact window visualization characteristics are controlled by `winconfig`
|
|
335
|
+
--- and `winblend` options.
|
|
336
|
+
---
|
|
337
|
+
--- The `winconfig` option is a callable which takes window id (|window-ID|) as
|
|
338
|
+
--- input and returns an array of floating window configs (as in `config`
|
|
339
|
+
--- argument of |nvim_open_win()|). Its length determines number of animation steps.
|
|
340
|
+
--- Example:
|
|
341
|
+
--- - The following output results into two animation steps with second being
|
|
342
|
+
--- upper left quarter of a first: >lua
|
|
343
|
+
---
|
|
344
|
+
--- {
|
|
345
|
+
--- {
|
|
346
|
+
--- row = 0, col = 0,
|
|
347
|
+
--- width = 10, height = 10,
|
|
348
|
+
--- relative = 'editor', anchor = 'NW', focusable = false,
|
|
349
|
+
--- zindex = 1, border = 'none', style = 'minimal',
|
|
350
|
+
--- },
|
|
351
|
+
--- {
|
|
352
|
+
--- row = 0, col = 0,
|
|
353
|
+
--- width = 5, height = 5,
|
|
354
|
+
--- relative = 'editor', anchor = 'NW', focusable = false,
|
|
355
|
+
--- zindex = 1, border = 'none', style = 'minimal',
|
|
356
|
+
--- },
|
|
357
|
+
--- }
|
|
358
|
+
--- <
|
|
359
|
+
--- The `winblend` option is similar to `timing` option: it is a callable
|
|
360
|
+
--- which, given current and total step numbers, returns value of floating
|
|
361
|
+
--- window's |'winblend'| option. Note, that it is called for current step (so
|
|
362
|
+
--- starts from 0), as opposed to `timing` which is called before step.
|
|
363
|
+
--- Example:
|
|
364
|
+
--- - Function `function(s, n) return 80 + 20 * s / n end` results in linear
|
|
365
|
+
--- transition from `winblend` value of 80 to 100.
|
|
366
|
+
---
|
|
367
|
+
--- See |MiniAnimate.gen_winconfig| for builtin window config generators.
|
|
368
|
+
--- See |MiniAnimate.gen_winblend| for builtin window transparency generators.
|
|
369
|
+
---
|
|
370
|
+
--- Configuration example: >lua
|
|
371
|
+
---
|
|
372
|
+
--- local animate = require('mini.animate')
|
|
373
|
+
--- animate.setup({
|
|
374
|
+
--- open = {
|
|
375
|
+
--- -- Animate for 400 milliseconds with linear easing
|
|
376
|
+
--- timing = animate.gen_timing.linear({ duration = 400, unit = 'total' }),
|
|
377
|
+
---
|
|
378
|
+
--- -- Animate with wiping from nearest edge instead of default static one
|
|
379
|
+
--- winconfig = animate.gen_winconfig.wipe({ direction = 'from_edge' }),
|
|
380
|
+
---
|
|
381
|
+
--- -- Make bigger windows more transparent
|
|
382
|
+
--- winblend = animate.gen_winblend.linear({ from = 80, to = 100 }),
|
|
383
|
+
--- },
|
|
384
|
+
---
|
|
385
|
+
--- close = {
|
|
386
|
+
--- -- Animate for 400 milliseconds with linear easing
|
|
387
|
+
--- timing = animate.gen_timing.linear({ duration = 400, unit = 'total' }),
|
|
388
|
+
---
|
|
389
|
+
--- -- Animate with wiping to nearest edge instead of default static one
|
|
390
|
+
--- winconfig = animate.gen_winconfig.wipe({ direction = 'to_edge' }),
|
|
391
|
+
---
|
|
392
|
+
--- -- Make bigger windows more transparent
|
|
393
|
+
--- winblend = animate.gen_winblend.linear({ from = 100, to = 80 }),
|
|
394
|
+
--- },
|
|
395
|
+
--- })
|
|
396
|
+
--- <
|
|
397
|
+
--- After animation is done, `MiniAnimateDoneOpen` or `MiniAnimateDoneClose`
|
|
398
|
+
--- event is triggered for `open` and `close` animation respectively.
|
|
399
|
+
MiniAnimate.config = {
|
|
400
|
+
-- Cursor path
|
|
401
|
+
cursor = {
|
|
402
|
+
-- Whether to enable this animation
|
|
403
|
+
enable = true,
|
|
404
|
+
|
|
405
|
+
-- Timing of animation (how steps will progress in time)
|
|
406
|
+
--minidoc_replace_start timing = --<function: linear animation, total 250ms>,
|
|
407
|
+
timing = function(_, n) return 250 / n end,
|
|
408
|
+
--minidoc_replace_end
|
|
409
|
+
|
|
410
|
+
-- Path generator for visualized cursor movement
|
|
411
|
+
--minidoc_replace_start path = --<function: implements shortest line path no longer than 1000>,
|
|
412
|
+
path = function(destination)
|
|
413
|
+
return H.path_line(destination, { predicate = H.default_path_predicate, max_output_steps = 1000 })
|
|
414
|
+
end,
|
|
415
|
+
--minidoc_replace_end
|
|
416
|
+
},
|
|
417
|
+
|
|
418
|
+
-- Vertical scroll
|
|
419
|
+
scroll = {
|
|
420
|
+
-- Whether to enable this animation
|
|
421
|
+
enable = true,
|
|
422
|
+
|
|
423
|
+
-- Timing of animation (how steps will progress in time)
|
|
424
|
+
--minidoc_replace_start timing = --<function: linear animation, total 250ms>,
|
|
425
|
+
timing = function(_, n) return 250 / n end,
|
|
426
|
+
--minidoc_replace_end
|
|
427
|
+
|
|
428
|
+
-- Subscroll generator based on total scroll
|
|
429
|
+
--minidoc_replace_start subscroll = --<function: implements equal scroll with at most 60 steps>,
|
|
430
|
+
subscroll = function(total_scroll)
|
|
431
|
+
return H.subscroll_equal(total_scroll, { predicate = H.default_subscroll_predicate, max_output_steps = 60 })
|
|
432
|
+
end,
|
|
433
|
+
--minidoc_replace_end
|
|
434
|
+
},
|
|
435
|
+
|
|
436
|
+
-- Window resize
|
|
437
|
+
resize = {
|
|
438
|
+
-- Whether to enable this animation
|
|
439
|
+
enable = true,
|
|
440
|
+
|
|
441
|
+
-- Timing of animation (how steps will progress in time)
|
|
442
|
+
--minidoc_replace_start timing = --<function: linear animation, total 250ms>,
|
|
443
|
+
timing = function(_, n) return 250 / n end,
|
|
444
|
+
--minidoc_replace_end
|
|
445
|
+
|
|
446
|
+
-- Subresize generator for all steps of resize animations
|
|
447
|
+
--minidoc_replace_start subresize = --<function: implements equal linear steps>,
|
|
448
|
+
subresize = function(sizes_from, sizes_to)
|
|
449
|
+
return H.subresize_equal(sizes_from, sizes_to, { predicate = H.default_subresize_predicate })
|
|
450
|
+
end,
|
|
451
|
+
--minidoc_replace_end
|
|
452
|
+
},
|
|
453
|
+
|
|
454
|
+
-- Window open
|
|
455
|
+
open = {
|
|
456
|
+
-- Whether to enable this animation
|
|
457
|
+
enable = true,
|
|
458
|
+
|
|
459
|
+
-- Timing of animation (how steps will progress in time)
|
|
460
|
+
--minidoc_replace_start timing = --<function: linear animation, total 250ms>,
|
|
461
|
+
timing = function(_, n) return 250 / n end,
|
|
462
|
+
--minidoc_replace_end
|
|
463
|
+
|
|
464
|
+
-- Floating window config generator visualizing specific window
|
|
465
|
+
--minidoc_replace_start winconfig = --<function: implements static window for 25 steps>,
|
|
466
|
+
winconfig = function(win_id)
|
|
467
|
+
return H.winconfig_static(win_id, { predicate = H.default_winconfig_predicate, n_steps = 25 })
|
|
468
|
+
end,
|
|
469
|
+
--minidoc_replace_end
|
|
470
|
+
|
|
471
|
+
-- 'winblend' (window transparency) generator for floating window
|
|
472
|
+
--minidoc_replace_start winblend = --<function: implements equal linear steps from 80 to 100>,
|
|
473
|
+
winblend = function(s, n) return 80 + 20 * (s / n) end,
|
|
474
|
+
--minidoc_replace_end
|
|
475
|
+
},
|
|
476
|
+
|
|
477
|
+
-- Window close
|
|
478
|
+
close = {
|
|
479
|
+
-- Whether to enable this animation
|
|
480
|
+
enable = true,
|
|
481
|
+
|
|
482
|
+
-- Timing of animation (how steps will progress in time)
|
|
483
|
+
--minidoc_replace_start timing = --<function: linear animation, total 250ms>,
|
|
484
|
+
timing = function(_, n) return 250 / n end,
|
|
485
|
+
--minidoc_replace_end
|
|
486
|
+
|
|
487
|
+
-- Floating window config generator visualizing specific window
|
|
488
|
+
--minidoc_replace_start winconfig = --<function: implements static window for 25 steps>,
|
|
489
|
+
winconfig = function(win_id)
|
|
490
|
+
return H.winconfig_static(win_id, { predicate = H.default_winconfig_predicate, n_steps = 25 })
|
|
491
|
+
end,
|
|
492
|
+
--minidoc_replace_end
|
|
493
|
+
|
|
494
|
+
-- 'winblend' (window transparency) generator for floating window
|
|
495
|
+
--minidoc_replace_start winblend = --<function: implements equal linear steps from 80 to 100>,
|
|
496
|
+
winblend = function(s, n) return 80 + 20 * (s / n) end,
|
|
497
|
+
--minidoc_replace_end
|
|
498
|
+
},
|
|
499
|
+
}
|
|
500
|
+
--minidoc_afterlines_end
|
|
501
|
+
|
|
502
|
+
-- Module functionality =======================================================
|
|
503
|
+
--- Check animation activity
|
|
504
|
+
---
|
|
505
|
+
---@param animation_type string One of supported animation types
|
|
506
|
+
--- (entries of |MiniAnimate.config|, like `'cursor'`, etc.).
|
|
507
|
+
---
|
|
508
|
+
---@return boolean Whether the animation is currently active.
|
|
509
|
+
MiniAnimate.is_active = function(animation_type)
|
|
510
|
+
local res = H.cache[animation_type .. '_is_active']
|
|
511
|
+
if res == nil then H.error('Wrong `animation_type` for `is_active()`.') end
|
|
512
|
+
return res
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
--- Execute action after some animation is done
|
|
516
|
+
---
|
|
517
|
+
--- Execute action immediately if animation is not active (checked with
|
|
518
|
+
--- |MiniAnimate.is_active()|). Else, schedule its execution until after
|
|
519
|
+
--- animation is done (on corresponding "done event", see
|
|
520
|
+
--- |MiniAnimate-done-event|).
|
|
521
|
+
---
|
|
522
|
+
--- Mostly meant to be used inside mappings.
|
|
523
|
+
---
|
|
524
|
+
--- Example:
|
|
525
|
+
---
|
|
526
|
+
--- A useful `nnoremap n nzvzz` mapping (consecutive application of |n|, |zv|, and |zz|)
|
|
527
|
+
--- should be expressed in the following way: >lua
|
|
528
|
+
---
|
|
529
|
+
--- '<Cmd>lua vim.cmd("normal! n"); ' ..
|
|
530
|
+
--- 'MiniAnimate.execute_after("scroll", "normal! zvzz")<CR>'
|
|
531
|
+
--- <
|
|
532
|
+
---@param animation_type string One of supported animation types
|
|
533
|
+
--- (as in |MiniAnimate.is_active()|).
|
|
534
|
+
---@param action string|function Action to be executed. If string, executed as
|
|
535
|
+
--- command (via |vim.cmd()|).
|
|
536
|
+
MiniAnimate.execute_after = function(animation_type, action)
|
|
537
|
+
local event_name = H.animation_done_events[animation_type]
|
|
538
|
+
if event_name == nil then H.error('Wrong `animation_type` for `execute_after`.') end
|
|
539
|
+
|
|
540
|
+
local callable = action
|
|
541
|
+
if type(callable) == 'string' then callable = function() vim.cmd(action) end end
|
|
542
|
+
if not vim.is_callable(callable) then
|
|
543
|
+
H.error('Argument `action` of `execute_after()` should be string or callable.')
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
-- Schedule conditional action execution to allow animation to actually take
|
|
547
|
+
-- effect. This helps creating more universal mappings, because some commands
|
|
548
|
+
-- (like `n`) not always result into scrolling.
|
|
549
|
+
vim.schedule(function()
|
|
550
|
+
if MiniAnimate.is_active(animation_type) then
|
|
551
|
+
vim.api.nvim_create_autocmd('User', { pattern = event_name, once = true, callback = callable })
|
|
552
|
+
else
|
|
553
|
+
callable()
|
|
554
|
+
end
|
|
555
|
+
end)
|
|
556
|
+
end
|
|
557
|
+
|
|
558
|
+
-- Action (step 0) - wait (step 1) - action (step 1) - ...
|
|
559
|
+
-- `step_action` should return `false` or `nil` (equivalent to not returning anything explicitly) in order to stop animation.
|
|
560
|
+
--- Animate action
|
|
561
|
+
---
|
|
562
|
+
--- This is equivalent to asynchronous execution of the following algorithm:
|
|
563
|
+
--- - Call `step_action(0)` immediately after calling this function. Stop if
|
|
564
|
+
--- action returned `false` or `nil`.
|
|
565
|
+
--- - Wait `step_timing(1)` milliseconds.
|
|
566
|
+
--- - Call `step_action(1)`. Stop if it returned `false` or `nil`.
|
|
567
|
+
--- - Wait `step_timing(2)` milliseconds.
|
|
568
|
+
--- - Call `step_action(2)`. Stop if it returned `false` or `nil`.
|
|
569
|
+
--- - ...
|
|
570
|
+
---
|
|
571
|
+
--- Notes:
|
|
572
|
+
--- - Animation is also stopped on action error or if maximum number of steps
|
|
573
|
+
--- is reached.
|
|
574
|
+
--- - Asynchronous execution is done with |uv.new_timer()|. It only allows
|
|
575
|
+
--- integer parts as repeat value. This has several implications:
|
|
576
|
+
--- - Outputs of `step_timing()` are accumulated in order to preserve total
|
|
577
|
+
--- execution time.
|
|
578
|
+
--- - Any wait time less than 1 ms means that action will be executed
|
|
579
|
+
--- immediately.
|
|
580
|
+
---
|
|
581
|
+
---@param step_action function|table Callable which takes `step` (integer 0, 1, 2,
|
|
582
|
+
--- etc. indicating current step) and executes some action. Its return value
|
|
583
|
+
--- defines when animation should stop: values `false` and `nil` (equivalent
|
|
584
|
+
--- to no explicit return) stop animation timer; any other continues it.
|
|
585
|
+
---@param step_timing function|table Callable which takes `step` (integer 1, 2, etc.
|
|
586
|
+
--- indicating next step) and returns how many milliseconds to wait before
|
|
587
|
+
--- executing this step action.
|
|
588
|
+
---@param opts table|nil Options. Possible fields:
|
|
589
|
+
--- - <max_steps> - Maximum value of allowed step to execute. Default: 10000000.
|
|
590
|
+
MiniAnimate.animate = function(step_action, step_timing, opts)
|
|
591
|
+
opts = vim.tbl_deep_extend('force', { max_steps = 10000000 }, opts or {})
|
|
592
|
+
|
|
593
|
+
local step, max_steps = 0, opts.max_steps
|
|
594
|
+
local timer, wait_time = vim.loop.new_timer(), 0
|
|
595
|
+
|
|
596
|
+
local draw_step
|
|
597
|
+
draw_step = vim.schedule_wrap(function()
|
|
598
|
+
local ok, should_continue = pcall(step_action, step)
|
|
599
|
+
if not (ok and should_continue and step < max_steps) then
|
|
600
|
+
timer:stop()
|
|
601
|
+
return
|
|
602
|
+
end
|
|
603
|
+
|
|
604
|
+
step = step + 1
|
|
605
|
+
wait_time = wait_time + step_timing(step)
|
|
606
|
+
|
|
607
|
+
-- Repeat value of `timer` seems to be rounded down to milliseconds. This
|
|
608
|
+
-- means that values less than 1 will lead to timer stop repeating. Instead
|
|
609
|
+
-- call next step function directly.
|
|
610
|
+
if wait_time < 1 then
|
|
611
|
+
timer:set_repeat(0)
|
|
612
|
+
-- Use `return` to make this proper "tail call"
|
|
613
|
+
return draw_step()
|
|
614
|
+
else
|
|
615
|
+
timer:set_repeat(wait_time)
|
|
616
|
+
wait_time = wait_time - timer:get_repeat()
|
|
617
|
+
timer:again()
|
|
618
|
+
end
|
|
619
|
+
end)
|
|
620
|
+
|
|
621
|
+
-- Start non-repeating timer without callback execution
|
|
622
|
+
timer:start(10000000, 0, draw_step)
|
|
623
|
+
|
|
624
|
+
-- Draw step zero (at origin) immediately
|
|
625
|
+
draw_step()
|
|
626
|
+
end
|
|
627
|
+
|
|
628
|
+
--- Generate animation timing
|
|
629
|
+
---
|
|
630
|
+
--- Each field corresponds to one family of progression which can be customized
|
|
631
|
+
--- further by supplying appropriate arguments.
|
|
632
|
+
---
|
|
633
|
+
--- This is a table with function elements. Call to actually get timing function.
|
|
634
|
+
---
|
|
635
|
+
--- Example: >lua
|
|
636
|
+
---
|
|
637
|
+
--- local animate = require('mini.animate')
|
|
638
|
+
--- animate.setup({
|
|
639
|
+
--- cursor = {
|
|
640
|
+
--- timing = animate.gen_timing.linear({ duration = 100, unit = 'total' })
|
|
641
|
+
--- },
|
|
642
|
+
--- })
|
|
643
|
+
--- <
|
|
644
|
+
---@seealso |MiniIndentscope.gen_animation| for similar concept in |mini.indentscope|.
|
|
645
|
+
MiniAnimate.gen_timing = {}
|
|
646
|
+
|
|
647
|
+
---@alias __animate_timing_opts table|nil Options that control progression. Possible keys:
|
|
648
|
+
--- - <easing> `(string)` - a subtype of progression. One of "in"
|
|
649
|
+
--- (accelerating from zero speed), "out" (decelerating to zero speed),
|
|
650
|
+
--- "in-out" (default; accelerating halfway, decelerating after).
|
|
651
|
+
--- - <duration> `(number)` - duration (in ms) of a unit. Default: 20.
|
|
652
|
+
--- - <unit> `(string)` - which unit's duration `opts.duration` controls. One
|
|
653
|
+
--- of "step" (default; ensures average duration of step to be `opts.duration`)
|
|
654
|
+
--- or "total" (ensures fixed total duration regardless of scope's range).
|
|
655
|
+
---@alias __animate_timing_return function Timing function (see |MiniAnimate-timing|).
|
|
656
|
+
|
|
657
|
+
--- Generate timing with no animation
|
|
658
|
+
---
|
|
659
|
+
--- Show final result immediately. Usually better to use `enable` field in `config`
|
|
660
|
+
--- if you want to disable animation.
|
|
661
|
+
MiniAnimate.gen_timing.none = function()
|
|
662
|
+
return function() return 0 end
|
|
663
|
+
end
|
|
664
|
+
|
|
665
|
+
--- Generate timing with linear progression
|
|
666
|
+
---
|
|
667
|
+
---@param opts __animate_timing_opts
|
|
668
|
+
---
|
|
669
|
+
---@return __animate_timing_return
|
|
670
|
+
MiniAnimate.gen_timing.linear = function(opts) return H.timing_arithmetic(0, H.normalize_timing_opts(opts)) end
|
|
671
|
+
|
|
672
|
+
--- Generate timing with quadratic progression
|
|
673
|
+
---
|
|
674
|
+
---@param opts __animate_timing_opts
|
|
675
|
+
---
|
|
676
|
+
---@return __animate_timing_return
|
|
677
|
+
MiniAnimate.gen_timing.quadratic = function(opts) return H.timing_arithmetic(1, H.normalize_timing_opts(opts)) end
|
|
678
|
+
|
|
679
|
+
--- Generate timing with cubic progression
|
|
680
|
+
---
|
|
681
|
+
---@param opts __animate_timing_opts
|
|
682
|
+
---
|
|
683
|
+
---@return __animate_timing_return
|
|
684
|
+
MiniAnimate.gen_timing.cubic = function(opts) return H.timing_arithmetic(2, H.normalize_timing_opts(opts)) end
|
|
685
|
+
|
|
686
|
+
--- Generate timing with quartic progression
|
|
687
|
+
---
|
|
688
|
+
---@param opts __animate_timing_opts
|
|
689
|
+
---
|
|
690
|
+
---@return __animate_timing_return
|
|
691
|
+
MiniAnimate.gen_timing.quartic = function(opts) return H.timing_arithmetic(3, H.normalize_timing_opts(opts)) end
|
|
692
|
+
|
|
693
|
+
--- Generate timing with exponential progression
|
|
694
|
+
---
|
|
695
|
+
---@param opts __animate_timing_opts
|
|
696
|
+
---
|
|
697
|
+
---@return __animate_timing_return
|
|
698
|
+
MiniAnimate.gen_timing.exponential = function(opts) return H.timing_geometrical(H.normalize_timing_opts(opts)) end
|
|
699
|
+
|
|
700
|
+
--- Generate cursor animation path
|
|
701
|
+
---
|
|
702
|
+
--- For more information see |MiniAnimate.config.cursor|.
|
|
703
|
+
---
|
|
704
|
+
--- This is a table with function elements. Call to actually get generator.
|
|
705
|
+
---
|
|
706
|
+
--- Example: >lua
|
|
707
|
+
---
|
|
708
|
+
--- local animate = require('mini.animate')
|
|
709
|
+
--- animate.setup({
|
|
710
|
+
--- cursor = {
|
|
711
|
+
--- -- Animate with line-column angle instead of shortest line
|
|
712
|
+
--- path = animate.gen_path.angle(),
|
|
713
|
+
--- }
|
|
714
|
+
--- })
|
|
715
|
+
--- <
|
|
716
|
+
MiniAnimate.gen_path = {}
|
|
717
|
+
|
|
718
|
+
---@alias __animate_path_opts_common table|nil Options that control generator. Possible keys:
|
|
719
|
+
--- - <predicate> `(function)` - a callable which takes `destination` as input and
|
|
720
|
+
--- returns boolean value indicating whether animation should be done.
|
|
721
|
+
--- Default: `false` if `destination` is within one line of origin (reduces
|
|
722
|
+
--- flickering), `true` otherwise.
|
|
723
|
+
---@alias __animate_path_return function Path function (see |MiniAnimate.config.cursor|).
|
|
724
|
+
|
|
725
|
+
--- Generate path as shortest line
|
|
726
|
+
---
|
|
727
|
+
---@param opts __animate_path_opts_common
|
|
728
|
+
--- - <max_output_steps> `(number)` - maximum number of steps in output.
|
|
729
|
+
--- Default: 1000.
|
|
730
|
+
---
|
|
731
|
+
---@return __animate_path_return
|
|
732
|
+
MiniAnimate.gen_path.line = function(opts)
|
|
733
|
+
opts = vim.tbl_deep_extend('force', { predicate = H.default_path_predicate, max_output_steps = 1000 }, opts or {})
|
|
734
|
+
|
|
735
|
+
return function(destination) return H.path_line(destination, opts) end
|
|
736
|
+
end
|
|
737
|
+
|
|
738
|
+
--- Generate path as line/column angle
|
|
739
|
+
---
|
|
740
|
+
---@param opts __animate_path_opts_common
|
|
741
|
+
--- - <max_output_steps> `(number)` - maximum number of steps per side in output.
|
|
742
|
+
--- Default: 1000.
|
|
743
|
+
--- - <first_direction> `(string)` - one of `"horizontal"` (default; animates
|
|
744
|
+
--- across initial line first) or `"vertical"` (animates across initial
|
|
745
|
+
--- column first).
|
|
746
|
+
---
|
|
747
|
+
---@return __animate_path_return
|
|
748
|
+
MiniAnimate.gen_path.angle = function(opts)
|
|
749
|
+
local default_opts = { predicate = H.default_path_predicate, max_output_steps = 1000, first_direction = 'horizontal' }
|
|
750
|
+
opts = vim.tbl_deep_extend('force', default_opts, opts or {})
|
|
751
|
+
|
|
752
|
+
local append_horizontal = function(res, dest_col, const_line)
|
|
753
|
+
if dest_col == 0 then return end
|
|
754
|
+
local n_steps = math.min(math.abs(dest_col), opts.max_output_steps)
|
|
755
|
+
local coef = dest_col / n_steps
|
|
756
|
+
for i = 0, n_steps - 1 do
|
|
757
|
+
table.insert(res, { const_line, H.round(coef * i) })
|
|
758
|
+
end
|
|
759
|
+
end
|
|
760
|
+
|
|
761
|
+
local append_vertical = function(res, dest_line, const_col)
|
|
762
|
+
if dest_line == 0 then return end
|
|
763
|
+
local n_steps = math.min(math.abs(dest_line), opts.max_output_steps)
|
|
764
|
+
local coef = dest_line / n_steps
|
|
765
|
+
for i = 0, n_steps - 1 do
|
|
766
|
+
table.insert(res, { H.round(coef * i), const_col })
|
|
767
|
+
end
|
|
768
|
+
end
|
|
769
|
+
|
|
770
|
+
return function(destination)
|
|
771
|
+
-- Don't animate in case of false predicate
|
|
772
|
+
if not opts.predicate(destination) then return {} end
|
|
773
|
+
|
|
774
|
+
-- Travel along horizontal/vertical lines
|
|
775
|
+
local res = {}
|
|
776
|
+
if opts.first_direction == 'horizontal' then
|
|
777
|
+
append_horizontal(res, destination[2], 0)
|
|
778
|
+
append_vertical(res, destination[1], destination[2])
|
|
779
|
+
else
|
|
780
|
+
append_vertical(res, destination[1], 0)
|
|
781
|
+
append_horizontal(res, destination[2], destination[1])
|
|
782
|
+
end
|
|
783
|
+
|
|
784
|
+
return res
|
|
785
|
+
end
|
|
786
|
+
end
|
|
787
|
+
|
|
788
|
+
--- Generate path as closing walls at final position
|
|
789
|
+
---
|
|
790
|
+
---@param opts __animate_path_opts_common
|
|
791
|
+
--- - <width> `(number)` - initial width of left and right walls. Default: 10.
|
|
792
|
+
---
|
|
793
|
+
---@return __animate_path_return
|
|
794
|
+
MiniAnimate.gen_path.walls = function(opts)
|
|
795
|
+
opts = opts or {}
|
|
796
|
+
local predicate = opts.predicate or H.default_path_predicate
|
|
797
|
+
local width = opts.width or 10
|
|
798
|
+
|
|
799
|
+
return function(destination)
|
|
800
|
+
-- Don't animate in case of false predicate
|
|
801
|
+
if not predicate(destination) then return {} end
|
|
802
|
+
|
|
803
|
+
-- Don't animate in case of no movement
|
|
804
|
+
if destination[1] == 0 and destination[2] == 0 then return {} end
|
|
805
|
+
|
|
806
|
+
local dest_line, dest_col = destination[1], destination[2]
|
|
807
|
+
local res = {}
|
|
808
|
+
for i = width, 1, -1 do
|
|
809
|
+
table.insert(res, { dest_line, dest_col + i })
|
|
810
|
+
table.insert(res, { dest_line, dest_col - i })
|
|
811
|
+
end
|
|
812
|
+
return res
|
|
813
|
+
end
|
|
814
|
+
end
|
|
815
|
+
|
|
816
|
+
--- Generate path as diminishing spiral at final position
|
|
817
|
+
---
|
|
818
|
+
---@param opts __animate_path_opts_common
|
|
819
|
+
--- - <width> `(number)` - initial width of spiral. Default: 2.
|
|
820
|
+
---
|
|
821
|
+
---@return __animate_path_return
|
|
822
|
+
MiniAnimate.gen_path.spiral = function(opts)
|
|
823
|
+
opts = opts or {}
|
|
824
|
+
local predicate = opts.predicate or H.default_path_predicate
|
|
825
|
+
local width = opts.width or 2
|
|
826
|
+
|
|
827
|
+
local add_layer = function(res, w, destination)
|
|
828
|
+
local dest_line, dest_col = destination[1], destination[2]
|
|
829
|
+
--stylua: ignore start
|
|
830
|
+
for j = -w, w-1 do table.insert(res, { dest_line - w, dest_col + j }) end
|
|
831
|
+
for i = -w, w-1 do table.insert(res, { dest_line + i, dest_col + w }) end
|
|
832
|
+
for j = -w, w-1 do table.insert(res, { dest_line + w, dest_col - j }) end
|
|
833
|
+
for i = -w, w-1 do table.insert(res, { dest_line - i, dest_col - w }) end
|
|
834
|
+
--stylua: ignore end
|
|
835
|
+
end
|
|
836
|
+
|
|
837
|
+
return function(destination)
|
|
838
|
+
-- Don't animate in case of false predicate
|
|
839
|
+
if not predicate(destination) then return {} end
|
|
840
|
+
|
|
841
|
+
-- Don't animate in case of no movement
|
|
842
|
+
if destination[1] == 0 and destination[2] == 0 then return {} end
|
|
843
|
+
|
|
844
|
+
local res = {}
|
|
845
|
+
for w = width, 1, -1 do
|
|
846
|
+
add_layer(res, w, destination)
|
|
847
|
+
end
|
|
848
|
+
return res
|
|
849
|
+
end
|
|
850
|
+
end
|
|
851
|
+
|
|
852
|
+
--- Generate scroll animation subscroll
|
|
853
|
+
---
|
|
854
|
+
--- For more information see |MiniAnimate.config.scroll|.
|
|
855
|
+
---
|
|
856
|
+
--- This is a table with function elements. Call to actually get generator.
|
|
857
|
+
---
|
|
858
|
+
--- Example: >lua
|
|
859
|
+
---
|
|
860
|
+
--- local animate = require('mini.animate')
|
|
861
|
+
--- animate.setup({
|
|
862
|
+
--- scroll = {
|
|
863
|
+
--- -- Animate equally but with 120 maximum steps instead of default 60
|
|
864
|
+
--- subscroll = animate.gen_subscroll.equal({ max_output_steps = 120 }),
|
|
865
|
+
--- }
|
|
866
|
+
--- })
|
|
867
|
+
--- <
|
|
868
|
+
MiniAnimate.gen_subscroll = {}
|
|
869
|
+
|
|
870
|
+
--- Generate subscroll with equal steps
|
|
871
|
+
---
|
|
872
|
+
---@param opts table|nil Options that control generator. Possible keys:
|
|
873
|
+
--- - <predicate> `(function)` - a callable which takes `total_scroll` as
|
|
874
|
+
--- input and returns boolean value indicating whether animation should be
|
|
875
|
+
--- done. Default: `false` if `total_scroll` is 1 or less (reduces
|
|
876
|
+
--- unnecessary waiting), `true` otherwise.
|
|
877
|
+
--- - <max_output_steps> `(number)` - maximum number of subscroll steps in output.
|
|
878
|
+
--- Adjust this to reduce computations in expense of reduced smoothness.
|
|
879
|
+
--- Default: 60.
|
|
880
|
+
---
|
|
881
|
+
---@return function Subscroll function (see |MiniAnimate.config.scroll|).
|
|
882
|
+
MiniAnimate.gen_subscroll.equal = function(opts)
|
|
883
|
+
opts = vim.tbl_deep_extend('force', { predicate = H.default_subscroll_predicate, max_output_steps = 60 }, opts or {})
|
|
884
|
+
|
|
885
|
+
return function(total_scroll) return H.subscroll_equal(total_scroll, opts) end
|
|
886
|
+
end
|
|
887
|
+
|
|
888
|
+
--- Generate resize animation subresize
|
|
889
|
+
---
|
|
890
|
+
--- For more information see |MiniAnimate.config.resize|.
|
|
891
|
+
---
|
|
892
|
+
--- This is a table with function elements. Call to actually get generator.
|
|
893
|
+
---
|
|
894
|
+
--- Example: >lua
|
|
895
|
+
---
|
|
896
|
+
--- local is_many_wins = function(sizes_from, sizes_to)
|
|
897
|
+
--- return vim.tbl_count(sizes_from) >= 3
|
|
898
|
+
--- end
|
|
899
|
+
--- local animate = require('mini.animate')
|
|
900
|
+
--- animate.setup({
|
|
901
|
+
--- resize = {
|
|
902
|
+
--- -- Animate only if there are at least 3 windows
|
|
903
|
+
--- subresize = animate.gen_subresize.equal({ predicate = is_many_wins }),
|
|
904
|
+
--- }
|
|
905
|
+
--- })
|
|
906
|
+
--- <
|
|
907
|
+
MiniAnimate.gen_subresize = {}
|
|
908
|
+
|
|
909
|
+
--- Generate subresize with equal steps
|
|
910
|
+
---
|
|
911
|
+
---@param opts table|nil Options that control generator. Possible keys:
|
|
912
|
+
--- - <predicate> `(function)` - a callable which takes `sizes_from` and
|
|
913
|
+
--- `sizes_to` as input and returns boolean value indicating whether
|
|
914
|
+
--- animation should be done. Default: always `true`.
|
|
915
|
+
---
|
|
916
|
+
---@return function Subresize function (see |MiniAnimate.config.resize|).
|
|
917
|
+
MiniAnimate.gen_subresize.equal = function(opts)
|
|
918
|
+
opts = vim.tbl_deep_extend('force', { predicate = H.default_subresize_predicate }, opts or {})
|
|
919
|
+
|
|
920
|
+
return function(sizes_from, sizes_to) return H.subresize_equal(sizes_from, sizes_to, opts) end
|
|
921
|
+
end
|
|
922
|
+
|
|
923
|
+
--- Generate open/close animation winconfig
|
|
924
|
+
---
|
|
925
|
+
--- For more information see |MiniAnimate.config.open| or |MiniAnimate.config.close|.
|
|
926
|
+
---
|
|
927
|
+
--- This is a table with function elements. Call to actually get generator.
|
|
928
|
+
---
|
|
929
|
+
--- Example: >lua
|
|
930
|
+
---
|
|
931
|
+
--- local is_not_single_window = function(win_id)
|
|
932
|
+
--- local tabpage_id = vim.api.nvim_win_get_tabpage(win_id)
|
|
933
|
+
--- return #vim.api.nvim_tabpage_list_wins(tabpage_id) > 1
|
|
934
|
+
--- end
|
|
935
|
+
--- local animate = require('mini.animate')
|
|
936
|
+
--- animate.setup({
|
|
937
|
+
--- open = {
|
|
938
|
+
--- -- Animate with wiping from nearest edge instead of default static one
|
|
939
|
+
--- -- and only if it is not a single window in tabpage
|
|
940
|
+
--- winconfig = animate.gen_winconfig.wipe({
|
|
941
|
+
--- predicate = is_not_single_window,
|
|
942
|
+
--- direction = 'from_edge',
|
|
943
|
+
--- }),
|
|
944
|
+
--- },
|
|
945
|
+
--- close = {
|
|
946
|
+
--- -- Animate with wiping to nearest edge instead of default static one
|
|
947
|
+
--- -- and only if it is not a single window in tabpage
|
|
948
|
+
--- winconfig = animate.gen_winconfig.wipe({
|
|
949
|
+
--- predicate = is_not_single_window,
|
|
950
|
+
--- direction = 'to_edge',
|
|
951
|
+
--- }),
|
|
952
|
+
--- },
|
|
953
|
+
--- })
|
|
954
|
+
--- <
|
|
955
|
+
MiniAnimate.gen_winconfig = {}
|
|
956
|
+
|
|
957
|
+
---@alias __animate_winconfig_opts_common table|nil Options that control generator. Possible keys:
|
|
958
|
+
--- - <predicate> `(function)` - a callable which takes `win_id` as input and
|
|
959
|
+
--- returns boolean value indicating whether animation should be done.
|
|
960
|
+
--- Default: always `true`.
|
|
961
|
+
---@alias __animate_winconfig_return function Winconfig function (see |MiniAnimate.config.open|
|
|
962
|
+
--- or |MiniAnimate.config.close|).
|
|
963
|
+
|
|
964
|
+
--- Generate winconfig for static floating window
|
|
965
|
+
---
|
|
966
|
+
--- This will result into floating window statically covering whole target
|
|
967
|
+
--- window.
|
|
968
|
+
---
|
|
969
|
+
---@param opts __animate_winconfig_opts_common
|
|
970
|
+
--- - <n_steps> `(number)` - number of output steps, all with same config.
|
|
971
|
+
--- Useful to tweak smoothness of transparency animation (done inside
|
|
972
|
+
--- `winblend` config option). Default: 25.
|
|
973
|
+
---
|
|
974
|
+
---@return __animate_winconfig_return
|
|
975
|
+
MiniAnimate.gen_winconfig.static = function(opts)
|
|
976
|
+
opts = vim.tbl_deep_extend('force', { predicate = H.default_winconfig_predicate, n_steps = 25 }, opts or {})
|
|
977
|
+
|
|
978
|
+
return function(win_id) return H.winconfig_static(win_id, opts) end
|
|
979
|
+
end
|
|
980
|
+
|
|
981
|
+
--- Generate winconfig for center-focused animated floating window
|
|
982
|
+
---
|
|
983
|
+
--- This will result into floating window growing from or shrinking to the
|
|
984
|
+
--- target window center.
|
|
985
|
+
---
|
|
986
|
+
---@param opts __animate_winconfig_opts_common
|
|
987
|
+
--- - <direction> `(string)` - one of `"to_center"` (default; window will
|
|
988
|
+
--- shrink from full coverage to center) or `"from_center"` (window will
|
|
989
|
+
--- grow from center to full coverage).
|
|
990
|
+
---
|
|
991
|
+
---@return __animate_winconfig_return
|
|
992
|
+
MiniAnimate.gen_winconfig.center = function(opts)
|
|
993
|
+
opts = opts or {}
|
|
994
|
+
local predicate = opts.predicate or H.default_winconfig_predicate
|
|
995
|
+
local direction = opts.direction or 'to_center'
|
|
996
|
+
|
|
997
|
+
return function(win_id)
|
|
998
|
+
-- Don't animate in case of false predicate
|
|
999
|
+
if not predicate(win_id) then return {} end
|
|
1000
|
+
|
|
1001
|
+
local pos = vim.fn.win_screenpos(win_id)
|
|
1002
|
+
local row, col = pos[1] - 1, pos[2] - 1
|
|
1003
|
+
local height, width = vim.api.nvim_win_get_height(win_id), vim.api.nvim_win_get_width(win_id)
|
|
1004
|
+
|
|
1005
|
+
local n_steps = math.max(height, width)
|
|
1006
|
+
local res = {}
|
|
1007
|
+
-- Progression should be between fully covering target window and minimal
|
|
1008
|
+
-- dimensions in target window center.
|
|
1009
|
+
for i = 1, n_steps do
|
|
1010
|
+
local coef = (i - 1) / n_steps
|
|
1011
|
+
|
|
1012
|
+
-- Reverse output if progression is from center
|
|
1013
|
+
local res_ind = direction == 'to_center' and i or (n_steps - i + 1)
|
|
1014
|
+
|
|
1015
|
+
--stylua: ignore
|
|
1016
|
+
res[res_ind] = {
|
|
1017
|
+
relative = 'editor',
|
|
1018
|
+
anchor = 'NW',
|
|
1019
|
+
row = H.round(row + 0.5 * coef * height),
|
|
1020
|
+
col = H.round(col + 0.5 * coef * width),
|
|
1021
|
+
width = math.ceil((1 - coef) * width),
|
|
1022
|
+
height = math.ceil((1 - coef) * height),
|
|
1023
|
+
focusable = false,
|
|
1024
|
+
zindex = 1,
|
|
1025
|
+
border = 'none',
|
|
1026
|
+
style = 'minimal',
|
|
1027
|
+
}
|
|
1028
|
+
end
|
|
1029
|
+
|
|
1030
|
+
return res
|
|
1031
|
+
end
|
|
1032
|
+
end
|
|
1033
|
+
|
|
1034
|
+
--- Generate winconfig for wiping animated floating window
|
|
1035
|
+
---
|
|
1036
|
+
--- This will result into floating window growing from or shrinking to the
|
|
1037
|
+
--- nearest edge. This also takes into account the split type of target window:
|
|
1038
|
+
--- vertically split window will progress towards vertical edge; horizontally -
|
|
1039
|
+
--- towards horizontal.
|
|
1040
|
+
---
|
|
1041
|
+
---@param opts __animate_winconfig_opts_common
|
|
1042
|
+
--- - <direction> `(string)` - one of `"to_edge"` (default; window will
|
|
1043
|
+
--- shrink from full coverage to nearest edge) or `"from_edge"` (window
|
|
1044
|
+
--- will grow from edge to full coverage).
|
|
1045
|
+
---
|
|
1046
|
+
---@return __animate_winconfig_return
|
|
1047
|
+
MiniAnimate.gen_winconfig.wipe = function(opts)
|
|
1048
|
+
opts = opts or {}
|
|
1049
|
+
local predicate = opts.predicate or H.default_winconfig_predicate
|
|
1050
|
+
local direction = opts.direction or 'to_edge'
|
|
1051
|
+
|
|
1052
|
+
return function(win_id)
|
|
1053
|
+
-- Don't animate in case of false predicate
|
|
1054
|
+
if not predicate(win_id) then return {} end
|
|
1055
|
+
|
|
1056
|
+
-- Get window data
|
|
1057
|
+
local win_pos = vim.fn.win_screenpos(win_id)
|
|
1058
|
+
local top_row, left_col = win_pos[1], win_pos[2]
|
|
1059
|
+
local win_height, win_width = vim.api.nvim_win_get_height(win_id), vim.api.nvim_win_get_width(win_id)
|
|
1060
|
+
|
|
1061
|
+
-- Compute progression data
|
|
1062
|
+
local cur_row, cur_col = top_row, left_col
|
|
1063
|
+
local cur_width, cur_height = win_width, win_height
|
|
1064
|
+
|
|
1065
|
+
local increment_row, increment_col, increment_height, increment_width
|
|
1066
|
+
local n_steps
|
|
1067
|
+
|
|
1068
|
+
local win_container = H.get_window_parent_container(win_id)
|
|
1069
|
+
--stylua: ignore
|
|
1070
|
+
if win_container == 'col' then
|
|
1071
|
+
-- Determine closest top/bottom screen edge and progress to it
|
|
1072
|
+
local bottom_row = top_row + win_height - 1
|
|
1073
|
+
local is_top_edge_closer = top_row < (vim.o.lines - bottom_row + 1)
|
|
1074
|
+
|
|
1075
|
+
increment_row, increment_col = (is_top_edge_closer and 0 or 1), 0
|
|
1076
|
+
increment_width, increment_height = 0, -1
|
|
1077
|
+
n_steps = win_height
|
|
1078
|
+
else
|
|
1079
|
+
-- Determine closest left/right screen edge and progress to it
|
|
1080
|
+
local right_col = left_col + win_width - 1
|
|
1081
|
+
local is_left_edge_closer = left_col < (vim.o.columns - right_col + 1)
|
|
1082
|
+
|
|
1083
|
+
increment_row, increment_col = 0, (is_left_edge_closer and 0 or 1)
|
|
1084
|
+
increment_width, increment_height = -1, 0
|
|
1085
|
+
n_steps = win_width
|
|
1086
|
+
end
|
|
1087
|
+
|
|
1088
|
+
-- Make step configs
|
|
1089
|
+
local res = {}
|
|
1090
|
+
for i = 1, n_steps do
|
|
1091
|
+
-- Reverse output if progression is from edge
|
|
1092
|
+
local res_ind = direction == 'to_edge' and i or (n_steps - i + 1)
|
|
1093
|
+
res[res_ind] = {
|
|
1094
|
+
relative = 'editor',
|
|
1095
|
+
anchor = 'NW',
|
|
1096
|
+
row = cur_row - 1,
|
|
1097
|
+
col = cur_col - 1,
|
|
1098
|
+
width = cur_width,
|
|
1099
|
+
height = cur_height,
|
|
1100
|
+
focusable = false,
|
|
1101
|
+
zindex = 1,
|
|
1102
|
+
border = 'none',
|
|
1103
|
+
style = 'minimal',
|
|
1104
|
+
}
|
|
1105
|
+
cur_row = cur_row + increment_row
|
|
1106
|
+
cur_col = cur_col + increment_col
|
|
1107
|
+
cur_height = cur_height + increment_height
|
|
1108
|
+
cur_width = cur_width + increment_width
|
|
1109
|
+
end
|
|
1110
|
+
return res
|
|
1111
|
+
end
|
|
1112
|
+
end
|
|
1113
|
+
|
|
1114
|
+
--- Generate open/close animation `winblend` progression
|
|
1115
|
+
---
|
|
1116
|
+
--- For more information see |MiniAnimate.config.open| or |MiniAnimate.config.close|.
|
|
1117
|
+
---
|
|
1118
|
+
--- This is a table with function elements. Call to actually get transparency
|
|
1119
|
+
--- function.
|
|
1120
|
+
---
|
|
1121
|
+
--- Example: >lua
|
|
1122
|
+
---
|
|
1123
|
+
--- local animate = require('mini.animate')
|
|
1124
|
+
--- animate.setup({
|
|
1125
|
+
--- open = {
|
|
1126
|
+
--- -- Change transparency from 60 to 80 instead of default 80 to 100
|
|
1127
|
+
--- winblend = animate.gen_winblend.linear({ from = 60, to = 80 }),
|
|
1128
|
+
--- },
|
|
1129
|
+
--- close = {
|
|
1130
|
+
--- -- Change transparency from 60 to 80 instead of default 80 to 100
|
|
1131
|
+
--- winblend = animate.gen_winblend.linear({ from = 60, to = 80 }),
|
|
1132
|
+
--- },
|
|
1133
|
+
--- })
|
|
1134
|
+
--- <
|
|
1135
|
+
MiniAnimate.gen_winblend = {}
|
|
1136
|
+
|
|
1137
|
+
--- Generate linear `winblend` progression
|
|
1138
|
+
---
|
|
1139
|
+
---@param opts table|nil Options that control generator. Possible keys:
|
|
1140
|
+
--- - <from> `(number)` - initial value of |'winblend'|.
|
|
1141
|
+
--- - <to> `(number)` - final value of |'winblend'|.
|
|
1142
|
+
---
|
|
1143
|
+
---@return function Winblend function (see |MiniAnimate.config.open|
|
|
1144
|
+
--- or |MiniAnimate.config.close|).
|
|
1145
|
+
MiniAnimate.gen_winblend.linear = function(opts)
|
|
1146
|
+
opts = opts or {}
|
|
1147
|
+
local from = opts.from or 80
|
|
1148
|
+
local to = opts.to or 100
|
|
1149
|
+
local diff = to - from
|
|
1150
|
+
|
|
1151
|
+
return function(s, n) return from + (s / n) * diff end
|
|
1152
|
+
end
|
|
1153
|
+
|
|
1154
|
+
-- Helper data ================================================================
|
|
1155
|
+
-- Module default config
|
|
1156
|
+
H.default_config = vim.deepcopy(MiniAnimate.config)
|
|
1157
|
+
|
|
1158
|
+
-- Cache for various operations
|
|
1159
|
+
H.cache = {
|
|
1160
|
+
-- Cursor move animation data
|
|
1161
|
+
cursor_event_id = 0,
|
|
1162
|
+
cursor_is_active = false,
|
|
1163
|
+
cursor_state = { buf_id = nil, pos = {} },
|
|
1164
|
+
|
|
1165
|
+
-- Scroll animation data
|
|
1166
|
+
scroll_event_id = 0,
|
|
1167
|
+
scroll_is_active = false,
|
|
1168
|
+
scroll_state = { buf_id = nil, win_id = nil, view = {}, cursor = {} },
|
|
1169
|
+
|
|
1170
|
+
-- Resize animation data
|
|
1171
|
+
resize_event_id = 0,
|
|
1172
|
+
resize_is_active = false,
|
|
1173
|
+
resize_state = { layout = {}, sizes = {}, views = {} },
|
|
1174
|
+
|
|
1175
|
+
-- Window open animation data
|
|
1176
|
+
open_event_id = 0,
|
|
1177
|
+
open_is_active = false,
|
|
1178
|
+
open_active_windows = {},
|
|
1179
|
+
|
|
1180
|
+
-- Window close animation data
|
|
1181
|
+
close_event_id = 0,
|
|
1182
|
+
close_is_active = false,
|
|
1183
|
+
close_active_windows = {},
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
-- Namespaces for module operations
|
|
1187
|
+
H.ns_id = {
|
|
1188
|
+
-- Extmarks used to show cursor path
|
|
1189
|
+
cursor = vim.api.nvim_create_namespace('MiniAnimateCursor'),
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
-- Identifier of empty buffer used inside open/close animations
|
|
1193
|
+
H.empty_buf_id = nil
|
|
1194
|
+
|
|
1195
|
+
-- Names of `User` events triggered after certain type of animation is done
|
|
1196
|
+
H.animation_done_events = {
|
|
1197
|
+
cursor = 'MiniAnimateDoneCursor',
|
|
1198
|
+
scroll = 'MiniAnimateDoneScroll',
|
|
1199
|
+
resize = 'MiniAnimateDoneResize',
|
|
1200
|
+
open = 'MiniAnimateDoneOpen',
|
|
1201
|
+
close = 'MiniAnimateDoneClose',
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
-- Helper functionality =======================================================
|
|
1205
|
+
-- Settings -------------------------------------------------------------------
|
|
1206
|
+
H.setup_config = function(config)
|
|
1207
|
+
H.check_type('config', config, 'table', true)
|
|
1208
|
+
config = vim.tbl_deep_extend('force', vim.deepcopy(H.default_config), config or {})
|
|
1209
|
+
|
|
1210
|
+
H.check_type('cursor', config.cursor, 'table')
|
|
1211
|
+
H.check_type('cursor.enable', config.cursor.enable, 'boolean')
|
|
1212
|
+
H.check_type('cursor.timing', config.cursor.timing, 'callable')
|
|
1213
|
+
H.check_type('cursor.path', config.cursor.path, 'callable')
|
|
1214
|
+
|
|
1215
|
+
H.check_type('scroll', config.scroll, 'table')
|
|
1216
|
+
H.check_type('scroll.enable', config.scroll.enable, 'boolean')
|
|
1217
|
+
H.check_type('scroll.timing', config.scroll.timing, 'callable')
|
|
1218
|
+
H.check_type('scroll.subscroll', config.scroll.subscroll, 'callable')
|
|
1219
|
+
|
|
1220
|
+
H.check_type('resize', config.resize, 'table')
|
|
1221
|
+
H.check_type('resize.enable', config.resize.enable, 'boolean')
|
|
1222
|
+
H.check_type('resize.timing', config.resize.timing, 'callable')
|
|
1223
|
+
H.check_type('resize.subresize', config.resize.subresize, 'callable')
|
|
1224
|
+
|
|
1225
|
+
H.check_type('open', config.open, 'table')
|
|
1226
|
+
H.check_type('open.enable', config.open.enable, 'boolean')
|
|
1227
|
+
H.check_type('open.timing', config.open.timing, 'callable')
|
|
1228
|
+
H.check_type('open.winconfig', config.open.winconfig, 'callable')
|
|
1229
|
+
H.check_type('open.winblend', config.open.winblend, 'callable')
|
|
1230
|
+
|
|
1231
|
+
H.check_type('close', config.close, 'table')
|
|
1232
|
+
H.check_type('close.enable', config.close.enable, 'boolean')
|
|
1233
|
+
H.check_type('close.timing', config.close.timing, 'callable')
|
|
1234
|
+
H.check_type('close.winconfig', config.close.winconfig, 'callable')
|
|
1235
|
+
H.check_type('close.winblend', config.close.winblend, 'callable')
|
|
1236
|
+
|
|
1237
|
+
return config
|
|
1238
|
+
end
|
|
1239
|
+
|
|
1240
|
+
H.apply_config = function(config) MiniAnimate.config = config end
|
|
1241
|
+
|
|
1242
|
+
H.create_autocommands = function()
|
|
1243
|
+
local gr = vim.api.nvim_create_augroup('MiniAnimate', {})
|
|
1244
|
+
|
|
1245
|
+
local au = function(event, pattern, callback, desc)
|
|
1246
|
+
vim.api.nvim_create_autocmd(event, { group = gr, pattern = pattern, callback = callback, desc = desc })
|
|
1247
|
+
end
|
|
1248
|
+
|
|
1249
|
+
au('CursorMoved', '*', H.auto_cursor, 'Animate cursor')
|
|
1250
|
+
|
|
1251
|
+
au('WinScrolled', '*', function()
|
|
1252
|
+
-- On Neovim>=0.13 `WinScrolled` is also triggered when window scrolls
|
|
1253
|
+
-- during 'incsearch' and when cancelling. Ignore these state changes to
|
|
1254
|
+
-- not have extra scroll as a result of it.
|
|
1255
|
+
H.ignore_incsearch_scroll()
|
|
1256
|
+
|
|
1257
|
+
-- Inside `WinScrolled` first animate resize before scroll to avoid flicker
|
|
1258
|
+
H.auto_resize()
|
|
1259
|
+
H.auto_scroll()
|
|
1260
|
+
end, 'Animate resize and animate scroll')
|
|
1261
|
+
-- Track scroll state on buffer and window enter to animate its first scroll.
|
|
1262
|
+
-- Use `vim.schedule_wrap()` to allow other immediate commands to change view
|
|
1263
|
+
-- (like builtin cursor center on buffer change) to avoid unnecessary
|
|
1264
|
+
-- animated scroll.
|
|
1265
|
+
au({ 'BufEnter', 'WinEnter' }, '*', vim.schedule_wrap(H.track_scroll_state), 'Track scroll state')
|
|
1266
|
+
-- Track immediately scroll state after leaving terminal mode. Otherwise it
|
|
1267
|
+
-- will lead to scroll animation starting at latest non-Terminal mode view.
|
|
1268
|
+
au('TermLeave', '*', H.track_scroll_state, 'Track scroll state')
|
|
1269
|
+
-- Track scroll state (partially) on every cursor move to keep cursor
|
|
1270
|
+
-- position up to date. This enables visually better cursor positioning
|
|
1271
|
+
-- during scroll animation (convex progression from start cursor position to
|
|
1272
|
+
-- end). Use `vim.schedule()` to make it affect state only after scroll is
|
|
1273
|
+
-- done and cursor is already in correct final position.
|
|
1274
|
+
au('CursorMoved', '*', vim.schedule_wrap(H.track_scroll_state_partial), 'Track partial scroll state')
|
|
1275
|
+
au('CmdlineLeave', '*', function() H.ignore_incsearch_scroll() end, 'Ignore incsearch scroll')
|
|
1276
|
+
|
|
1277
|
+
-- Use `vim.schedule_wrap()` animation to get a window data used for
|
|
1278
|
+
-- displaying (and not one after just opening). Useful for 'nvim-tree'.
|
|
1279
|
+
au('WinNew', '*', vim.schedule_wrap(function() H.auto_openclose('open') end), 'Animate window open')
|
|
1280
|
+
|
|
1281
|
+
au('WinClosed', '*', function() H.auto_openclose('close') end, 'Animate window close')
|
|
1282
|
+
|
|
1283
|
+
au('ColorScheme', '*', H.create_default_hl, 'Ensure colors')
|
|
1284
|
+
end
|
|
1285
|
+
|
|
1286
|
+
H.create_default_hl = function()
|
|
1287
|
+
vim.api.nvim_set_hl(0, 'MiniAnimateCursor', { default = true, reverse = true, nocombine = true })
|
|
1288
|
+
vim.api.nvim_set_hl(0, 'MiniAnimateNormalFloat', { default = true, link = 'NormalFloat' })
|
|
1289
|
+
end
|
|
1290
|
+
|
|
1291
|
+
H.is_disabled = function() return vim.g.minianimate_disable == true or vim.b.minianimate_disable == true end
|
|
1292
|
+
|
|
1293
|
+
H.get_config = function(config)
|
|
1294
|
+
return vim.tbl_deep_extend('force', MiniAnimate.config, vim.b.minianimate_config or {}, config or {})
|
|
1295
|
+
end
|
|
1296
|
+
|
|
1297
|
+
-- Autocommands ---------------------------------------------------------------
|
|
1298
|
+
H.auto_cursor = function()
|
|
1299
|
+
-- Don't animate if disabled
|
|
1300
|
+
local cursor_config = H.get_config().cursor
|
|
1301
|
+
if not cursor_config.enable or H.is_disabled() then
|
|
1302
|
+
-- Reset state to not use an outdated one if enabled again
|
|
1303
|
+
H.cache.cursor_state = { buf_id = nil, pos = {} }
|
|
1304
|
+
return
|
|
1305
|
+
end
|
|
1306
|
+
|
|
1307
|
+
-- Don't animate if inside scroll animation
|
|
1308
|
+
if H.cache.scroll_is_active then return end
|
|
1309
|
+
|
|
1310
|
+
-- Update necessary information. NOTE: update state only on `CursorMoved` and
|
|
1311
|
+
-- not inside every animation step (like in scroll animation) for performance
|
|
1312
|
+
-- reasons: cursor movement is much more common action than scrolling.
|
|
1313
|
+
local prev_state, new_state = H.cache.cursor_state, H.get_cursor_state()
|
|
1314
|
+
H.cache.cursor_state = new_state
|
|
1315
|
+
H.cache.cursor_event_id = H.cache.cursor_event_id + 1
|
|
1316
|
+
|
|
1317
|
+
-- Don't animate if changed buffer
|
|
1318
|
+
if new_state.buf_id ~= prev_state.buf_id then return end
|
|
1319
|
+
|
|
1320
|
+
-- Make animation step data and possibly animate
|
|
1321
|
+
local animate_step = H.make_cursor_step(prev_state, new_state, cursor_config)
|
|
1322
|
+
if not animate_step then return end
|
|
1323
|
+
|
|
1324
|
+
H.start_cursor()
|
|
1325
|
+
MiniAnimate.animate(animate_step.step_action, animate_step.step_timing)
|
|
1326
|
+
end
|
|
1327
|
+
|
|
1328
|
+
H.auto_resize = function()
|
|
1329
|
+
-- Don't animate if disabled
|
|
1330
|
+
local resize_config = H.get_config().resize
|
|
1331
|
+
if not resize_config.enable or H.is_disabled() then
|
|
1332
|
+
-- Reset state to not use an outdated one if enabled again
|
|
1333
|
+
H.cache.resize_state = { layout = {}, sizes = {}, views = {} }
|
|
1334
|
+
return
|
|
1335
|
+
end
|
|
1336
|
+
|
|
1337
|
+
-- Don't animate if inside scroll animation. This reduces computations and
|
|
1338
|
+
-- occasional flickering.
|
|
1339
|
+
if H.cache.scroll_is_active then return end
|
|
1340
|
+
|
|
1341
|
+
-- Update state. This also ensures that window views are up to date.
|
|
1342
|
+
local prev_state, new_state = H.cache.resize_state, H.get_resize_state()
|
|
1343
|
+
H.cache.resize_state = new_state
|
|
1344
|
+
|
|
1345
|
+
-- Don't animate if there is nothing to animate (should be same layout but
|
|
1346
|
+
-- different sizes). This also stops triggering animation on window scrolls.
|
|
1347
|
+
local same_state = H.is_equal_resize_state(prev_state, new_state)
|
|
1348
|
+
if not (same_state.layout and not same_state.sizes) then return end
|
|
1349
|
+
|
|
1350
|
+
-- Register new event only in case there is something to animate
|
|
1351
|
+
H.cache.resize_event_id = H.cache.resize_event_id + 1
|
|
1352
|
+
|
|
1353
|
+
-- Make animation step data and possibly animate
|
|
1354
|
+
local animate_step = H.make_resize_step(prev_state, new_state, resize_config)
|
|
1355
|
+
if not animate_step then return end
|
|
1356
|
+
|
|
1357
|
+
H.start_resize(prev_state)
|
|
1358
|
+
MiniAnimate.animate(animate_step.step_action, animate_step.step_timing)
|
|
1359
|
+
end
|
|
1360
|
+
|
|
1361
|
+
H.auto_scroll = function()
|
|
1362
|
+
-- Don't animate if disabled
|
|
1363
|
+
local scroll_config = H.get_config().scroll
|
|
1364
|
+
if not scroll_config.enable or H.is_disabled() then
|
|
1365
|
+
-- Reset state to not use an outdated one if enabled again
|
|
1366
|
+
H.cache.scroll_state = { buf_id = nil, win_id = nil, view = {}, cursor = {} }
|
|
1367
|
+
return
|
|
1368
|
+
end
|
|
1369
|
+
|
|
1370
|
+
-- Get states
|
|
1371
|
+
local prev_state, new_state = H.cache.scroll_state, H.get_scroll_state()
|
|
1372
|
+
|
|
1373
|
+
-- Don't animate if nothing to animate. Mostly used to distinguish
|
|
1374
|
+
-- `WinScrolled` resulting from module animation from the other ones.
|
|
1375
|
+
local is_same_bufwin = new_state.buf_id == prev_state.buf_id and new_state.win_id == prev_state.win_id
|
|
1376
|
+
local is_same_topline = new_state.view.topline == prev_state.view.topline
|
|
1377
|
+
if is_same_topline and is_same_bufwin then return end
|
|
1378
|
+
|
|
1379
|
+
-- Update necessary information
|
|
1380
|
+
H.cache.scroll_state = new_state
|
|
1381
|
+
H.cache.scroll_event_id = H.cache.scroll_event_id + 1
|
|
1382
|
+
|
|
1383
|
+
-- Don't animate if changed buffer or window
|
|
1384
|
+
if not is_same_bufwin then return end
|
|
1385
|
+
|
|
1386
|
+
-- Don't animate if inside resize animation. This reduces computations and
|
|
1387
|
+
-- occasional flickering.
|
|
1388
|
+
if H.cache.resize_is_active then return end
|
|
1389
|
+
|
|
1390
|
+
-- Make animation step data and possibly animate
|
|
1391
|
+
local animate_step = H.make_scroll_step(prev_state, new_state, scroll_config)
|
|
1392
|
+
if not animate_step then return end
|
|
1393
|
+
|
|
1394
|
+
H.start_scroll(prev_state)
|
|
1395
|
+
MiniAnimate.animate(animate_step.step_action, animate_step.step_timing)
|
|
1396
|
+
end
|
|
1397
|
+
|
|
1398
|
+
H.track_scroll_state = function() H.cache.scroll_state = H.get_scroll_state() end
|
|
1399
|
+
|
|
1400
|
+
H.track_scroll_state_partial = function()
|
|
1401
|
+
-- This not only improves computation load, but seems to be crucial for
|
|
1402
|
+
-- a proper state tracking
|
|
1403
|
+
if H.cache.scroll_is_active then return end
|
|
1404
|
+
|
|
1405
|
+
H.cache.scroll_state.cursor = { line = vim.fn.line('.'), virtcol = vim.fn.virtcol('.') }
|
|
1406
|
+
end
|
|
1407
|
+
|
|
1408
|
+
H.ignore_incsearch_scroll = function()
|
|
1409
|
+
local cmd_type = vim.fn.getcmdtype()
|
|
1410
|
+
local is_insearch = vim.o.incsearch and (cmd_type == '/' or cmd_type == '?')
|
|
1411
|
+
if not (is_insearch or H.cache.scroll_state.aborted_incsearch) then return end
|
|
1412
|
+
|
|
1413
|
+
-- Update scroll state so that there is no scroll animation after confirming
|
|
1414
|
+
-- incremental search. Otherwise it leads to unnecessary animation from
|
|
1415
|
+
-- initial scroll state to the one **already shown**.
|
|
1416
|
+
H.track_scroll_state()
|
|
1417
|
+
H.cache.scroll_state.aborted_incsearch = vim.v.event.abort
|
|
1418
|
+
end
|
|
1419
|
+
|
|
1420
|
+
H.auto_openclose = function(action_type)
|
|
1421
|
+
action_type = action_type or 'open'
|
|
1422
|
+
|
|
1423
|
+
-- Don't animate if disabled
|
|
1424
|
+
local config = H.get_config()[action_type]
|
|
1425
|
+
if not config.enable or H.is_disabled() then return end
|
|
1426
|
+
|
|
1427
|
+
-- Get window id to act upon
|
|
1428
|
+
local win_id
|
|
1429
|
+
if action_type == 'close' then win_id = tonumber(vim.fn.expand('<amatch>')) end
|
|
1430
|
+
if action_type == 'open' then win_id = math.max(unpack(vim.api.nvim_list_wins())) end
|
|
1431
|
+
|
|
1432
|
+
-- Don't animate if created window is not right (valid and not floating)
|
|
1433
|
+
if win_id == nil or not vim.api.nvim_win_is_valid(win_id) then return end
|
|
1434
|
+
if vim.api.nvim_win_get_config(win_id).relative ~= '' then return end
|
|
1435
|
+
|
|
1436
|
+
-- Register new event only in case there is something to animate
|
|
1437
|
+
local event_id_name = action_type .. '_event_id'
|
|
1438
|
+
H.cache[event_id_name] = H.cache[event_id_name] + 1
|
|
1439
|
+
|
|
1440
|
+
-- Make animation step data and possibly animate
|
|
1441
|
+
local animate_step = H.make_openclose_step(action_type, win_id, config)
|
|
1442
|
+
if not animate_step then return end
|
|
1443
|
+
|
|
1444
|
+
H.start_openclose(action_type)
|
|
1445
|
+
MiniAnimate.animate(animate_step.step_action, animate_step.step_timing)
|
|
1446
|
+
end
|
|
1447
|
+
|
|
1448
|
+
-- General animation ----------------------------------------------------------
|
|
1449
|
+
H.trigger_done_event = function(animation_type) vim.cmd('doautocmd User ' .. H.animation_done_events[animation_type]) end
|
|
1450
|
+
|
|
1451
|
+
-- Cursor ---------------------------------------------------------------------
|
|
1452
|
+
H.make_cursor_step = function(state_from, state_to, opts)
|
|
1453
|
+
local pos_from, pos_to = state_from.pos, state_to.pos
|
|
1454
|
+
local destination = { pos_to[1] - pos_from[1], pos_to[2] - pos_from[2] }
|
|
1455
|
+
local path = opts.path(destination)
|
|
1456
|
+
if path == nil or #path == 0 then return end
|
|
1457
|
+
|
|
1458
|
+
local n_steps = #path
|
|
1459
|
+
local timing = opts.timing
|
|
1460
|
+
|
|
1461
|
+
-- Using explicit buffer id allows correct animation stop after buffer switch
|
|
1462
|
+
local event_id, buf_id = H.cache.cursor_event_id, state_from.buf_id
|
|
1463
|
+
|
|
1464
|
+
return {
|
|
1465
|
+
step_action = function(step)
|
|
1466
|
+
-- Undraw previous mark. Doing it before early return allows to clear
|
|
1467
|
+
-- last animation mark.
|
|
1468
|
+
H.undraw_cursor_mark(buf_id)
|
|
1469
|
+
|
|
1470
|
+
-- Stop animation if another cursor movement is active. Don't use
|
|
1471
|
+
-- `stop_cursor()` because it will also stop parallel animation.
|
|
1472
|
+
if H.cache.cursor_event_id ~= event_id then return false end
|
|
1473
|
+
|
|
1474
|
+
-- Don't draw outside of set number of steps or not inside current buffer
|
|
1475
|
+
if n_steps <= step or vim.api.nvim_get_current_buf() ~= buf_id then return H.stop_cursor() end
|
|
1476
|
+
|
|
1477
|
+
-- Draw cursor mark (starting from initial zero step)
|
|
1478
|
+
local pos = path[step + 1]
|
|
1479
|
+
H.draw_cursor_mark(pos_from[1] + pos[1], pos_from[2] + pos[2], buf_id)
|
|
1480
|
+
return true
|
|
1481
|
+
end,
|
|
1482
|
+
step_timing = function(step) return timing(step, n_steps) end,
|
|
1483
|
+
}
|
|
1484
|
+
end
|
|
1485
|
+
|
|
1486
|
+
H.get_cursor_state = function()
|
|
1487
|
+
-- Use virtual column to respect position outside of line width and tabs
|
|
1488
|
+
return { buf_id = vim.api.nvim_get_current_buf(), pos = { vim.fn.line('.'), vim.fn.virtcol('.') } }
|
|
1489
|
+
end
|
|
1490
|
+
|
|
1491
|
+
H.draw_cursor_mark = function(line, virt_col, buf_id)
|
|
1492
|
+
-- Use only absolute coordinates. Allows to not draw outside of buffer.
|
|
1493
|
+
if line <= 0 or virt_col <= 0 then return end
|
|
1494
|
+
|
|
1495
|
+
-- Compute window column at which to place mark. Don't use explicit `col`
|
|
1496
|
+
-- argument because it won't allow placing mark outside of text line.
|
|
1497
|
+
local win_col = virt_col - vim.fn.winsaveview().leftcol
|
|
1498
|
+
if win_col < 1 then return end
|
|
1499
|
+
|
|
1500
|
+
-- Set extmark
|
|
1501
|
+
local extmark_opts = {
|
|
1502
|
+
id = 1,
|
|
1503
|
+
hl_mode = 'combine',
|
|
1504
|
+
priority = 1000,
|
|
1505
|
+
right_gravity = false,
|
|
1506
|
+
virt_text = { { ' ', 'MiniAnimateCursor' } },
|
|
1507
|
+
virt_text_win_col = win_col - 1,
|
|
1508
|
+
virt_text_pos = 'overlay',
|
|
1509
|
+
}
|
|
1510
|
+
pcall(vim.api.nvim_buf_set_extmark, buf_id, H.ns_id.cursor, line - 1, 0, extmark_opts)
|
|
1511
|
+
end
|
|
1512
|
+
|
|
1513
|
+
H.undraw_cursor_mark = function(buf_id) pcall(vim.api.nvim_buf_del_extmark, buf_id, H.ns_id.cursor, 1) end
|
|
1514
|
+
|
|
1515
|
+
H.start_cursor = function()
|
|
1516
|
+
H.cache.cursor_is_active = true
|
|
1517
|
+
return true
|
|
1518
|
+
end
|
|
1519
|
+
|
|
1520
|
+
H.stop_cursor = function()
|
|
1521
|
+
H.cache.cursor_is_active = false
|
|
1522
|
+
H.trigger_done_event('cursor')
|
|
1523
|
+
return false
|
|
1524
|
+
end
|
|
1525
|
+
|
|
1526
|
+
-- Scroll ---------------------------------------------------------------------
|
|
1527
|
+
H.make_scroll_step = function(state_from, state_to, opts)
|
|
1528
|
+
-- Do not animate in Select mode because it resets it
|
|
1529
|
+
local is_select_mode = ({ s = true, S = true, ['\19'] = true })[vim.fn.mode()]
|
|
1530
|
+
if is_select_mode then return end
|
|
1531
|
+
|
|
1532
|
+
-- Compute how subscrolling is done
|
|
1533
|
+
local from_line, to_line = state_from.view.topline, state_to.view.topline
|
|
1534
|
+
local total_scroll = H.get_n_visible_lines(from_line, to_line) - 1
|
|
1535
|
+
local step_scrolls = opts.subscroll(total_scroll)
|
|
1536
|
+
|
|
1537
|
+
-- Don't animate if no subscroll steps is returned
|
|
1538
|
+
if step_scrolls == nil or #step_scrolls == 0 then return end
|
|
1539
|
+
|
|
1540
|
+
-- Compute scrolling key ('\25' and '\5' are escaped '<C-Y>' and '<C-E>')
|
|
1541
|
+
local scroll_key = from_line < to_line and '\5' or '\25'
|
|
1542
|
+
|
|
1543
|
+
-- Cache frequently accessed data
|
|
1544
|
+
local from_cur_line, to_cur_line = state_from.cursor.line, state_to.cursor.line
|
|
1545
|
+
local from_cur_virtcol, to_cur_virtcol = state_from.cursor.virtcol, state_to.cursor.virtcol
|
|
1546
|
+
|
|
1547
|
+
local event_id, buf_id, win_id = H.cache.scroll_event_id, state_from.buf_id, state_from.win_id
|
|
1548
|
+
local n_steps, timing = #step_scrolls, opts.timing
|
|
1549
|
+
|
|
1550
|
+
return {
|
|
1551
|
+
step_action = function(step)
|
|
1552
|
+
-- Stop animation if another scroll is active. Don't use `stop_scroll()`
|
|
1553
|
+
-- because it will stop parallel animation.
|
|
1554
|
+
if H.cache.scroll_event_id ~= event_id then return false end
|
|
1555
|
+
|
|
1556
|
+
-- Stop animation if jumped to different buffer or window. Don't restore
|
|
1557
|
+
-- window view as it can only operate on current window.
|
|
1558
|
+
local is_same_win_buf = vim.api.nvim_get_current_buf() == buf_id and vim.api.nvim_get_current_win() == win_id
|
|
1559
|
+
if not is_same_win_buf then return H.stop_scroll() end
|
|
1560
|
+
|
|
1561
|
+
-- Compute intermediate cursor position. This relies on `virtualedit=all`
|
|
1562
|
+
-- to be able to place cursor anywhere on screen (has better animation;
|
|
1563
|
+
-- at least for default equally spread subscrolls).
|
|
1564
|
+
local coef = step / n_steps
|
|
1565
|
+
local cursor_line = H.convex_point(from_cur_line, to_cur_line, coef)
|
|
1566
|
+
local cursor_virtcol = H.convex_point(from_cur_virtcol, to_cur_virtcol, coef)
|
|
1567
|
+
local cursor_data = { line = cursor_line, virtcol = cursor_virtcol }
|
|
1568
|
+
|
|
1569
|
+
-- Perform scroll. Possibly stop on error.
|
|
1570
|
+
local ok, _ = pcall(H.scroll_action, scroll_key, step_scrolls[step], cursor_data)
|
|
1571
|
+
if not ok then return H.stop_scroll(state_to) end
|
|
1572
|
+
|
|
1573
|
+
-- Update current scroll state for two reasons:
|
|
1574
|
+
-- - Be able to distinguish manual `WinScrolled` event from one created
|
|
1575
|
+
-- by `H.scroll_action()`.
|
|
1576
|
+
-- - Be able to start manual scrolling at any animation step.
|
|
1577
|
+
H.cache.scroll_state = H.get_scroll_state()
|
|
1578
|
+
|
|
1579
|
+
-- Properly stop animation if step is too big
|
|
1580
|
+
if n_steps <= step then return H.stop_scroll(state_to) end
|
|
1581
|
+
|
|
1582
|
+
return true
|
|
1583
|
+
end,
|
|
1584
|
+
step_timing = function(step) return timing(step, n_steps) end,
|
|
1585
|
+
}
|
|
1586
|
+
end
|
|
1587
|
+
|
|
1588
|
+
H.scroll_action = function(key, n, cursor_data)
|
|
1589
|
+
-- Scroll. Allow supplying non-valid `n` for initial "scroll" which sets
|
|
1590
|
+
-- cursor immediately, which reduces flicker.
|
|
1591
|
+
if n ~= nil and n > 0 then
|
|
1592
|
+
local command = string.format('normal! %d%s', n, key)
|
|
1593
|
+
vim.cmd(command)
|
|
1594
|
+
end
|
|
1595
|
+
|
|
1596
|
+
-- Set cursor to properly handle cursor position
|
|
1597
|
+
-- Computation of available top/bottom line depends on `scrolloff = 0`
|
|
1598
|
+
-- because otherwise it will go out of bounds causing scroll overshoot with
|
|
1599
|
+
-- later "bounce" back on view restore (see
|
|
1600
|
+
-- https://github.com/nvim-mini/mini.nvim/issues/177).
|
|
1601
|
+
local top, bottom = vim.fn.line('w0'), vim.fn.line('w$')
|
|
1602
|
+
local line = math.min(math.max(cursor_data.line, top), bottom)
|
|
1603
|
+
|
|
1604
|
+
-- Cursor can only be set using byte column. To place it in the most correct
|
|
1605
|
+
-- virtual column, tweak output of `virtcol2col()`
|
|
1606
|
+
local virtcol = cursor_data.virtcol
|
|
1607
|
+
local col = vim.fn.virtcol2col(0, line, virtcol)
|
|
1608
|
+
-- - Correct for virtual column being outside of line's last virtual column
|
|
1609
|
+
local virtcol_past_lineend = vim.fn.virtcol({ line, '$' })
|
|
1610
|
+
if virtcol_past_lineend <= virtcol then col = col + virtcol - virtcol_past_lineend + 1 end
|
|
1611
|
+
|
|
1612
|
+
pcall(vim.api.nvim_win_set_cursor, 0, { line, col - 1 })
|
|
1613
|
+
end
|
|
1614
|
+
|
|
1615
|
+
H.start_scroll = function(start_state)
|
|
1616
|
+
H.cache.scroll_is_active = true
|
|
1617
|
+
-- Disable scrolloff in order to be able to place cursor on top/bottom window
|
|
1618
|
+
-- line inside scroll step.
|
|
1619
|
+
-- Incorporating `vim.wo.scrolloff` in computation of available top and
|
|
1620
|
+
-- bottom window lines works, but only in absence of folds. It gets tricky
|
|
1621
|
+
-- otherwise, so disabling on scroll start and restore on scroll end is
|
|
1622
|
+
-- better solution.
|
|
1623
|
+
vim.wo.scrolloff = 0
|
|
1624
|
+
-- Allow placing cursor anywhere on screen for better cursor placing
|
|
1625
|
+
vim.wo.virtualedit = 'all'
|
|
1626
|
+
|
|
1627
|
+
if start_state ~= nil then
|
|
1628
|
+
vim.fn.winrestview(start_state.view)
|
|
1629
|
+
-- Track state because `winrestview()` later triggers `WinScrolled`.
|
|
1630
|
+
-- Otherwise mapping like `u<Cmd>lua _G.n = 0<CR>` (as in 'mini.bracketed')
|
|
1631
|
+
-- can result into "inverted scroll": from destination to current state.
|
|
1632
|
+
H.track_scroll_state()
|
|
1633
|
+
end
|
|
1634
|
+
|
|
1635
|
+
return true
|
|
1636
|
+
end
|
|
1637
|
+
|
|
1638
|
+
H.stop_scroll = function(end_state)
|
|
1639
|
+
if end_state ~= nil then
|
|
1640
|
+
vim.fn.winrestview(end_state.view)
|
|
1641
|
+
H.track_scroll_state()
|
|
1642
|
+
end
|
|
1643
|
+
|
|
1644
|
+
vim.wo.scrolloff = end_state.scrolloff
|
|
1645
|
+
vim.wo.virtualedit = end_state.virtualedit
|
|
1646
|
+
|
|
1647
|
+
H.cache.scroll_is_active = false
|
|
1648
|
+
H.trigger_done_event('scroll')
|
|
1649
|
+
|
|
1650
|
+
return false
|
|
1651
|
+
end
|
|
1652
|
+
|
|
1653
|
+
H.get_scroll_state = function()
|
|
1654
|
+
return {
|
|
1655
|
+
buf_id = vim.api.nvim_get_current_buf(),
|
|
1656
|
+
win_id = vim.api.nvim_get_current_win(),
|
|
1657
|
+
view = vim.fn.winsaveview(),
|
|
1658
|
+
cursor = { line = vim.fn.line('.'), virtcol = vim.fn.virtcol('.') },
|
|
1659
|
+
scrolloff = H.cache.scroll_is_active and H.cache.scroll_state.scrolloff or vim.wo.scrolloff,
|
|
1660
|
+
virtualedit = H.cache.scroll_is_active and H.cache.scroll_state.virtualedit or vim.wo.virtualedit,
|
|
1661
|
+
}
|
|
1662
|
+
end
|
|
1663
|
+
|
|
1664
|
+
-- Resize ---------------------------------------------------------------------
|
|
1665
|
+
H.make_resize_step = function(state_from, state_to, opts)
|
|
1666
|
+
-- Compute number of animation steps
|
|
1667
|
+
local step_sizes = opts.subresize(state_from.sizes, state_to.sizes)
|
|
1668
|
+
if step_sizes == nil or #step_sizes == 0 then return end
|
|
1669
|
+
local n_steps = #step_sizes
|
|
1670
|
+
|
|
1671
|
+
-- Create animation step
|
|
1672
|
+
local event_id, timing = H.cache.resize_event_id, opts.timing
|
|
1673
|
+
|
|
1674
|
+
return {
|
|
1675
|
+
step_action = function(step)
|
|
1676
|
+
-- Do nothing on initialization
|
|
1677
|
+
if step == 0 then return true end
|
|
1678
|
+
|
|
1679
|
+
-- Stop animation if another resize animation is active. Don't use
|
|
1680
|
+
-- `stop_resize()` because it will also stop parallel animation.
|
|
1681
|
+
if H.cache.resize_event_id ~= event_id then return false end
|
|
1682
|
+
|
|
1683
|
+
-- Perform animation. Possibly stop on error.
|
|
1684
|
+
-- Use `false` to not restore cursor position to avoid horizontal flicker
|
|
1685
|
+
local ok, _ = pcall(H.apply_resize_state, { sizes = step_sizes[step] }, false)
|
|
1686
|
+
if not ok then return H.stop_resize(state_to) end
|
|
1687
|
+
|
|
1688
|
+
-- Properly stop animation if step is too big
|
|
1689
|
+
if n_steps <= step then return H.stop_resize(state_to) end
|
|
1690
|
+
|
|
1691
|
+
return true
|
|
1692
|
+
end,
|
|
1693
|
+
step_timing = function(step) return timing(step, n_steps) end,
|
|
1694
|
+
}
|
|
1695
|
+
end
|
|
1696
|
+
|
|
1697
|
+
H.start_resize = function(start_state)
|
|
1698
|
+
H.cache.resize_is_active = true
|
|
1699
|
+
-- Don't restore cursor position to avoid horizontal flicker
|
|
1700
|
+
if start_state ~= nil then H.apply_resize_state(start_state, false) end
|
|
1701
|
+
return true
|
|
1702
|
+
end
|
|
1703
|
+
|
|
1704
|
+
H.stop_resize = function(end_state)
|
|
1705
|
+
if end_state ~= nil then H.apply_resize_state(end_state, true) end
|
|
1706
|
+
H.cache.resize_is_active = false
|
|
1707
|
+
H.trigger_done_event('resize')
|
|
1708
|
+
return false
|
|
1709
|
+
end
|
|
1710
|
+
|
|
1711
|
+
H.get_resize_state = function()
|
|
1712
|
+
local layout = vim.fn.winlayout()
|
|
1713
|
+
|
|
1714
|
+
local windows = H.get_layout_windows(layout)
|
|
1715
|
+
local sizes, views = {}, {}
|
|
1716
|
+
for _, win_id in ipairs(windows) do
|
|
1717
|
+
sizes[win_id] = { height = vim.api.nvim_win_get_height(win_id), width = vim.api.nvim_win_get_width(win_id) }
|
|
1718
|
+
views[win_id] = vim.api.nvim_win_call(win_id, function() return vim.fn.winsaveview() end)
|
|
1719
|
+
end
|
|
1720
|
+
|
|
1721
|
+
return { layout = layout, sizes = sizes, views = views }
|
|
1722
|
+
end
|
|
1723
|
+
|
|
1724
|
+
H.is_equal_resize_state = function(state_1, state_2)
|
|
1725
|
+
return {
|
|
1726
|
+
layout = vim.deep_equal(state_1.layout, state_2.layout),
|
|
1727
|
+
sizes = vim.deep_equal(state_1.sizes, state_2.sizes),
|
|
1728
|
+
}
|
|
1729
|
+
end
|
|
1730
|
+
|
|
1731
|
+
H.get_layout_windows = function(layout)
|
|
1732
|
+
local res = {}
|
|
1733
|
+
local traverse
|
|
1734
|
+
traverse = function(l)
|
|
1735
|
+
if l[1] == 'leaf' then
|
|
1736
|
+
table.insert(res, l[2])
|
|
1737
|
+
return
|
|
1738
|
+
end
|
|
1739
|
+
for _, sub_l in ipairs(l[2]) do
|
|
1740
|
+
traverse(sub_l)
|
|
1741
|
+
end
|
|
1742
|
+
end
|
|
1743
|
+
traverse(layout)
|
|
1744
|
+
|
|
1745
|
+
return res
|
|
1746
|
+
end
|
|
1747
|
+
|
|
1748
|
+
H.apply_resize_state = function(state, full_view)
|
|
1749
|
+
for win_id, dims in pairs(state.sizes) do
|
|
1750
|
+
vim.api.nvim_win_set_height(win_id, dims.height)
|
|
1751
|
+
vim.api.nvim_win_set_width(win_id, dims.width)
|
|
1752
|
+
end
|
|
1753
|
+
|
|
1754
|
+
-- Use `or {}` to allow states without `view` (mainly inside animation)
|
|
1755
|
+
for win_id, view in pairs(state.views or {}) do
|
|
1756
|
+
vim.api.nvim_win_call(win_id, function()
|
|
1757
|
+
-- Allow to not restore full view. It mainly solves horizontal flickering
|
|
1758
|
+
-- when resizing from small to big width and cursor is on the end of long
|
|
1759
|
+
-- line. This is especially visible for high 'winwidth'.
|
|
1760
|
+
-- Example: `set winwidth=120 winheight=40` and hop between two
|
|
1761
|
+
-- vertically split windows with cursor on `$` of long line.
|
|
1762
|
+
if full_view then
|
|
1763
|
+
vim.fn.winrestview(view)
|
|
1764
|
+
return
|
|
1765
|
+
end
|
|
1766
|
+
|
|
1767
|
+
-- This triggers `CursorMoved` event, but nothing can be done
|
|
1768
|
+
-- (`noautocmd` is of no use, see https://github.com/vim/vim/issues/2084)
|
|
1769
|
+
pcall(vim.api.nvim_win_set_cursor, win_id, { view.lnum, view.leftcol })
|
|
1770
|
+
vim.fn.winrestview({ topline = view.topline, leftcol = view.leftcol })
|
|
1771
|
+
end)
|
|
1772
|
+
end
|
|
1773
|
+
|
|
1774
|
+
-- Update current resize state to be able to start another resize animation
|
|
1775
|
+
-- at any current animation step. Recompute state to also capture `view`.
|
|
1776
|
+
H.cache.resize_state = H.get_resize_state()
|
|
1777
|
+
end
|
|
1778
|
+
|
|
1779
|
+
-- Open/close -----------------------------------------------------------------
|
|
1780
|
+
H.make_openclose_step = function(action_type, win_id, config)
|
|
1781
|
+
-- Compute winconfig progression
|
|
1782
|
+
local step_winconfigs = config.winconfig(win_id)
|
|
1783
|
+
if step_winconfigs == nil or #step_winconfigs == 0 then return end
|
|
1784
|
+
|
|
1785
|
+
-- Produce animation steps.
|
|
1786
|
+
local n_steps, event_id_name = #step_winconfigs, action_type .. '_event_id'
|
|
1787
|
+
local timing, winblend, event_id = config.timing, config.winblend, H.cache[event_id_name]
|
|
1788
|
+
local float_win_id
|
|
1789
|
+
|
|
1790
|
+
return {
|
|
1791
|
+
step_action = function(step)
|
|
1792
|
+
-- Stop animation if another similar animation is active. Don't use
|
|
1793
|
+
-- `stop_openclose()` because it will also stop parallel animation.
|
|
1794
|
+
if H.cache[event_id_name] ~= event_id then
|
|
1795
|
+
pcall(vim.api.nvim_win_close, float_win_id, true)
|
|
1796
|
+
return false
|
|
1797
|
+
end
|
|
1798
|
+
|
|
1799
|
+
-- Stop animation if exceeded number of steps
|
|
1800
|
+
if n_steps <= step then
|
|
1801
|
+
pcall(vim.api.nvim_win_close, float_win_id, true)
|
|
1802
|
+
return H.stop_openclose(action_type)
|
|
1803
|
+
end
|
|
1804
|
+
|
|
1805
|
+
-- Empty buffer should always be valid (might have been closed by user command)
|
|
1806
|
+
if H.empty_buf_id == nil or not vim.api.nvim_buf_is_loaded(H.empty_buf_id) then
|
|
1807
|
+
pcall(vim.api.nvim_buf_delete, H.empty_buf_id, { force = true })
|
|
1808
|
+
H.empty_buf_id = vim.api.nvim_create_buf(false, true)
|
|
1809
|
+
H.set_buf_name(H.empty_buf_id, 'open-close-scratch')
|
|
1810
|
+
end
|
|
1811
|
+
|
|
1812
|
+
-- Set step config to window. Possibly (re)open (it could have been
|
|
1813
|
+
-- manually closed like after `:only`)
|
|
1814
|
+
local float_config = step_winconfigs[step + 1]
|
|
1815
|
+
if step == 0 or not vim.api.nvim_win_is_valid(float_win_id) then
|
|
1816
|
+
float_win_id = vim.api.nvim_open_win(H.empty_buf_id, false, float_config)
|
|
1817
|
+
vim.wo[float_win_id].winhighlight = 'Normal:MiniAnimateNormalFloat'
|
|
1818
|
+
else
|
|
1819
|
+
vim.api.nvim_win_set_config(float_win_id, float_config)
|
|
1820
|
+
end
|
|
1821
|
+
|
|
1822
|
+
vim.wo[float_win_id].winblend = H.round(winblend(step, n_steps))
|
|
1823
|
+
|
|
1824
|
+
return true
|
|
1825
|
+
end,
|
|
1826
|
+
step_timing = function(step) return timing(step, n_steps) end,
|
|
1827
|
+
}
|
|
1828
|
+
end
|
|
1829
|
+
|
|
1830
|
+
H.start_openclose = function(action_type)
|
|
1831
|
+
H.cache[action_type .. '_is_active'] = true
|
|
1832
|
+
return true
|
|
1833
|
+
end
|
|
1834
|
+
|
|
1835
|
+
H.stop_openclose = function(action_type)
|
|
1836
|
+
H.cache[action_type .. '_is_active'] = false
|
|
1837
|
+
H.trigger_done_event(action_type)
|
|
1838
|
+
return false
|
|
1839
|
+
end
|
|
1840
|
+
|
|
1841
|
+
-- Animation timings ----------------------------------------------------------
|
|
1842
|
+
H.normalize_timing_opts = function(x)
|
|
1843
|
+
x = vim.tbl_deep_extend('force', H.get_config(), { easing = 'in-out', duration = 20, unit = 'step' }, x or {})
|
|
1844
|
+
H.validate_if(H.is_valid_timing_opts, x, 'opts')
|
|
1845
|
+
return x
|
|
1846
|
+
end
|
|
1847
|
+
|
|
1848
|
+
H.is_valid_timing_opts = function(x)
|
|
1849
|
+
if type(x.duration) ~= 'number' or x.duration < 0 then
|
|
1850
|
+
return false, [[In `gen_timing` option `duration` should be a positive number.]]
|
|
1851
|
+
end
|
|
1852
|
+
|
|
1853
|
+
if not vim.tbl_contains({ 'in', 'out', 'in-out' }, x.easing) then
|
|
1854
|
+
return false, [[In `gen_timing` option `easing` should be one of 'in', 'out', or 'in-out'.]]
|
|
1855
|
+
end
|
|
1856
|
+
|
|
1857
|
+
if not vim.tbl_contains({ 'total', 'step' }, x.unit) then
|
|
1858
|
+
return false, [[In `gen_timing` option `unit` should be one of 'step' or 'total'.]]
|
|
1859
|
+
end
|
|
1860
|
+
|
|
1861
|
+
return true
|
|
1862
|
+
end
|
|
1863
|
+
|
|
1864
|
+
--- Imitate common power easing function
|
|
1865
|
+
---
|
|
1866
|
+
--- Every step is preceded by waiting time decreasing/increasing in power
|
|
1867
|
+
--- series fashion (`d` is "delta", ensures total duration time):
|
|
1868
|
+
--- - "in": d*n^p; d*(n-1)^p; ... ; d*2^p; d*1^p
|
|
1869
|
+
--- - "out": d*1^p; d*2^p; ... ; d*(n-1)^p; d*n^p
|
|
1870
|
+
--- - "in-out": "in" until 0.5*n, "out" afterwards
|
|
1871
|
+
---
|
|
1872
|
+
--- This way it imitates `power + 1` common easing function because animation
|
|
1873
|
+
--- progression behaves as sum of `power` elements.
|
|
1874
|
+
---
|
|
1875
|
+
---@param power number Power of series.
|
|
1876
|
+
---@param opts table Options from `MiniAnimate.gen_timing` entry.
|
|
1877
|
+
---@private
|
|
1878
|
+
H.timing_arithmetic = function(power, opts)
|
|
1879
|
+
-- Sum of first `n_steps` natural numbers raised to `power`
|
|
1880
|
+
local arith_power_sum = ({
|
|
1881
|
+
[0] = function(n_steps) return n_steps end,
|
|
1882
|
+
[1] = function(n_steps) return n_steps * (n_steps + 1) / 2 end,
|
|
1883
|
+
[2] = function(n_steps) return n_steps * (n_steps + 1) * (2 * n_steps + 1) / 6 end,
|
|
1884
|
+
[3] = function(n_steps) return n_steps ^ 2 * (n_steps + 1) ^ 2 / 4 end,
|
|
1885
|
+
})[power]
|
|
1886
|
+
|
|
1887
|
+
-- Function which computes common delta so that overall duration will have
|
|
1888
|
+
-- desired value (based on supplied `opts`)
|
|
1889
|
+
local duration_unit, duration_value = opts.unit, opts.duration
|
|
1890
|
+
local make_delta = function(n_steps, is_in_out)
|
|
1891
|
+
local total_time = duration_unit == 'total' and duration_value or (duration_value * n_steps)
|
|
1892
|
+
local total_parts
|
|
1893
|
+
if is_in_out then
|
|
1894
|
+
-- Examples:
|
|
1895
|
+
-- - n_steps=5: 3^d, 2^d, 1^d, 2^d, 3^d
|
|
1896
|
+
-- - n_steps=6: 3^d, 2^d, 1^d, 1^d, 2^d, 3^d
|
|
1897
|
+
total_parts = 2 * arith_power_sum(math.ceil(0.5 * n_steps)) - (n_steps % 2 == 1 and 1 or 0)
|
|
1898
|
+
else
|
|
1899
|
+
total_parts = arith_power_sum(n_steps)
|
|
1900
|
+
end
|
|
1901
|
+
return total_time / total_parts
|
|
1902
|
+
end
|
|
1903
|
+
|
|
1904
|
+
return ({
|
|
1905
|
+
['in'] = function(s, n) return make_delta(n) * (n - s + 1) ^ power end,
|
|
1906
|
+
['out'] = function(s, n) return make_delta(n) * s ^ power end,
|
|
1907
|
+
['in-out'] = function(s, n)
|
|
1908
|
+
local n_half = math.ceil(0.5 * n)
|
|
1909
|
+
local s_halved
|
|
1910
|
+
if n % 2 == 0 then
|
|
1911
|
+
s_halved = s <= n_half and (n_half - s + 1) or (s - n_half)
|
|
1912
|
+
else
|
|
1913
|
+
s_halved = s < n_half and (n_half - s + 1) or (s - n_half + 1)
|
|
1914
|
+
end
|
|
1915
|
+
return make_delta(n, true) * s_halved ^ power
|
|
1916
|
+
end,
|
|
1917
|
+
})[opts.easing]
|
|
1918
|
+
end
|
|
1919
|
+
|
|
1920
|
+
--- Imitate common exponential easing function
|
|
1921
|
+
---
|
|
1922
|
+
--- Every step is preceded by waiting time decreasing/increasing in geometric
|
|
1923
|
+
--- progression fashion (`d` is 'delta', ensures total duration time):
|
|
1924
|
+
--- - 'in': (d-1)*d^(n-1); (d-1)*d^(n-2); ...; (d-1)*d^1; (d-1)*d^0
|
|
1925
|
+
--- - 'out': (d-1)*d^0; (d-1)*d^1; ...; (d-1)*d^(n-2); (d-1)*d^(n-1)
|
|
1926
|
+
--- - 'in-out': 'in' until 0.5*n, 'out' afterwards
|
|
1927
|
+
---
|
|
1928
|
+
---@param opts table Options from `MiniAnimate.gen_timing` entry.
|
|
1929
|
+
---@private
|
|
1930
|
+
H.timing_geometrical = function(opts)
|
|
1931
|
+
-- Function which computes common delta so that overall duration will have
|
|
1932
|
+
-- desired value (based on supplied `opts`)
|
|
1933
|
+
local duration_unit, duration_value = opts.unit, opts.duration
|
|
1934
|
+
local make_delta = function(n_steps, is_in_out)
|
|
1935
|
+
local total_time = duration_unit == 'step' and (duration_value * n_steps) or duration_value
|
|
1936
|
+
-- Exact solution to avoid possible (bad) approximation
|
|
1937
|
+
if n_steps == 1 then return total_time + 1 end
|
|
1938
|
+
if is_in_out then
|
|
1939
|
+
local n_half = math.ceil(0.5 * n_steps)
|
|
1940
|
+
if n_steps % 2 == 1 then total_time = total_time + math.pow(0.5 * total_time + 1, 1 / n_half) - 1 end
|
|
1941
|
+
return math.pow(0.5 * total_time + 1, 1 / n_half)
|
|
1942
|
+
end
|
|
1943
|
+
return math.pow(total_time + 1, 1 / n_steps)
|
|
1944
|
+
end
|
|
1945
|
+
|
|
1946
|
+
return ({
|
|
1947
|
+
['in'] = function(s, n)
|
|
1948
|
+
local delta = make_delta(n)
|
|
1949
|
+
return (delta - 1) * delta ^ (n - s)
|
|
1950
|
+
end,
|
|
1951
|
+
['out'] = function(s, n)
|
|
1952
|
+
local delta = make_delta(n)
|
|
1953
|
+
return (delta - 1) * delta ^ (s - 1)
|
|
1954
|
+
end,
|
|
1955
|
+
['in-out'] = function(s, n)
|
|
1956
|
+
local n_half, delta = math.ceil(0.5 * n), make_delta(n, true)
|
|
1957
|
+
local s_halved
|
|
1958
|
+
if n % 2 == 0 then
|
|
1959
|
+
s_halved = s <= n_half and (n_half - s) or (s - n_half - 1)
|
|
1960
|
+
else
|
|
1961
|
+
s_halved = s < n_half and (n_half - s) or (s - n_half)
|
|
1962
|
+
end
|
|
1963
|
+
return (delta - 1) * delta ^ s_halved
|
|
1964
|
+
end,
|
|
1965
|
+
})[opts.easing]
|
|
1966
|
+
end
|
|
1967
|
+
|
|
1968
|
+
-- Animation path -------------------------------------------------------------
|
|
1969
|
+
H.path_line = function(destination, opts)
|
|
1970
|
+
-- Don't animate in case of false predicate
|
|
1971
|
+
if not opts.predicate(destination) then return {} end
|
|
1972
|
+
|
|
1973
|
+
-- Travel along the biggest horizontal/vertical difference, but stop one
|
|
1974
|
+
-- step before destination
|
|
1975
|
+
local l, c = destination[1], destination[2]
|
|
1976
|
+
local l_abs, c_abs = math.abs(l), math.abs(c)
|
|
1977
|
+
local max_diff = math.min(math.max(l_abs, c_abs), opts.max_output_steps)
|
|
1978
|
+
|
|
1979
|
+
local res = {}
|
|
1980
|
+
for i = 0, max_diff - 1 do
|
|
1981
|
+
local prop = i / max_diff
|
|
1982
|
+
table.insert(res, { H.round(prop * l), H.round(prop * c) })
|
|
1983
|
+
end
|
|
1984
|
+
return res
|
|
1985
|
+
end
|
|
1986
|
+
|
|
1987
|
+
H.default_path_predicate = function(destination) return destination[1] < -1 or 1 < destination[1] end
|
|
1988
|
+
|
|
1989
|
+
-- Animation subscroll --------------------------------------------------------
|
|
1990
|
+
H.subscroll_equal = function(total_scroll, opts)
|
|
1991
|
+
-- Don't animate in case of false predicate
|
|
1992
|
+
if not opts.predicate(total_scroll) then return {} end
|
|
1993
|
+
|
|
1994
|
+
-- Make equal steps, but no more than `max_output_steps`
|
|
1995
|
+
local n_steps = math.min(total_scroll, opts.max_output_steps)
|
|
1996
|
+
local res, coef = {}, total_scroll / n_steps
|
|
1997
|
+
for i = 1, n_steps do
|
|
1998
|
+
res[i] = math.floor(i * coef) - math.floor((i - 1) * coef)
|
|
1999
|
+
end
|
|
2000
|
+
return res
|
|
2001
|
+
end
|
|
2002
|
+
|
|
2003
|
+
H.default_subscroll_predicate = function(total_scroll) return total_scroll > 1 end
|
|
2004
|
+
|
|
2005
|
+
-- Animation subresize --------------------------------------------------------
|
|
2006
|
+
H.subresize_equal = function(sizes_from, sizes_to, opts)
|
|
2007
|
+
-- Don't animate in case of false predicate
|
|
2008
|
+
if not opts.predicate(sizes_from, sizes_to) then return {} end
|
|
2009
|
+
|
|
2010
|
+
-- Don't animate single window
|
|
2011
|
+
if #vim.tbl_keys(sizes_from) == 1 then return {} end
|
|
2012
|
+
|
|
2013
|
+
-- Compute number of steps
|
|
2014
|
+
local n_steps = 0
|
|
2015
|
+
for win_id, dims_from in pairs(sizes_from) do
|
|
2016
|
+
local height_absidff = math.abs(sizes_to[win_id].height - dims_from.height)
|
|
2017
|
+
local width_absidff = math.abs(sizes_to[win_id].width - dims_from.width)
|
|
2018
|
+
n_steps = math.max(n_steps, height_absidff, width_absidff)
|
|
2019
|
+
end
|
|
2020
|
+
if n_steps <= 1 then return {} end
|
|
2021
|
+
|
|
2022
|
+
-- Make subresize array
|
|
2023
|
+
local res = {}
|
|
2024
|
+
for i = 1, n_steps do
|
|
2025
|
+
local coef = i / n_steps
|
|
2026
|
+
local sub_res = {}
|
|
2027
|
+
for win_id, dims_from in pairs(sizes_from) do
|
|
2028
|
+
sub_res[win_id] = {
|
|
2029
|
+
height = H.convex_point(dims_from.height, sizes_to[win_id].height, coef),
|
|
2030
|
+
width = H.convex_point(dims_from.width, sizes_to[win_id].width, coef),
|
|
2031
|
+
}
|
|
2032
|
+
end
|
|
2033
|
+
res[i] = sub_res
|
|
2034
|
+
end
|
|
2035
|
+
|
|
2036
|
+
return res
|
|
2037
|
+
end
|
|
2038
|
+
|
|
2039
|
+
H.default_subresize_predicate = function(sizes_from, sizes_to) return true end
|
|
2040
|
+
|
|
2041
|
+
-- Animation winconfig --------------------------------------------------------
|
|
2042
|
+
H.winconfig_static = function(win_id, opts)
|
|
2043
|
+
-- Don't animate in case of false predicate
|
|
2044
|
+
if not opts.predicate(win_id) then return {} end
|
|
2045
|
+
|
|
2046
|
+
local pos = vim.fn.win_screenpos(win_id)
|
|
2047
|
+
local width, height = vim.api.nvim_win_get_width(win_id), vim.api.nvim_win_get_height(win_id)
|
|
2048
|
+
local res = {}
|
|
2049
|
+
for i = 1, opts.n_steps do
|
|
2050
|
+
--stylua: ignore
|
|
2051
|
+
res[i] = {
|
|
2052
|
+
relative = 'editor',
|
|
2053
|
+
anchor = 'NW',
|
|
2054
|
+
row = pos[1] - 1,
|
|
2055
|
+
col = pos[2] - 1,
|
|
2056
|
+
width = width,
|
|
2057
|
+
height = height,
|
|
2058
|
+
focusable = false,
|
|
2059
|
+
zindex = 1,
|
|
2060
|
+
border = 'none',
|
|
2061
|
+
style = 'minimal',
|
|
2062
|
+
}
|
|
2063
|
+
end
|
|
2064
|
+
return res
|
|
2065
|
+
end
|
|
2066
|
+
|
|
2067
|
+
H.get_window_parent_container = function(win_id)
|
|
2068
|
+
local f
|
|
2069
|
+
f = function(layout, parent_container)
|
|
2070
|
+
local container, second = layout[1], layout[2]
|
|
2071
|
+
if container == 'leaf' then
|
|
2072
|
+
if second == win_id then return parent_container end
|
|
2073
|
+
return
|
|
2074
|
+
end
|
|
2075
|
+
|
|
2076
|
+
for _, sub_layout in ipairs(second) do
|
|
2077
|
+
local res = f(sub_layout, container)
|
|
2078
|
+
if res ~= nil then return res end
|
|
2079
|
+
end
|
|
2080
|
+
end
|
|
2081
|
+
|
|
2082
|
+
-- Important to get layout of tabpage window actually belongs to (as it can
|
|
2083
|
+
-- already be not current tabpage)
|
|
2084
|
+
-- NOTE: `winlayout()` takes tabpage number (non unique), not tabpage id
|
|
2085
|
+
local tabpage_id = vim.api.nvim_win_get_tabpage(win_id)
|
|
2086
|
+
local tabpage_nr = vim.api.nvim_tabpage_get_number(tabpage_id)
|
|
2087
|
+
return f(vim.fn.winlayout(tabpage_nr), 'single')
|
|
2088
|
+
end
|
|
2089
|
+
|
|
2090
|
+
H.default_winconfig_predicate = function(win_id) return true end
|
|
2091
|
+
|
|
2092
|
+
-- Utilities ------------------------------------------------------------------
|
|
2093
|
+
H.error = function(msg) error('(mini.animate) ' .. msg, 0) end
|
|
2094
|
+
|
|
2095
|
+
H.check_type = function(name, val, ref, allow_nil)
|
|
2096
|
+
if type(val) == ref or (ref == 'callable' and vim.is_callable(val)) or (allow_nil and val == nil) then return end
|
|
2097
|
+
H.error(string.format('`%s` should be %s, not %s', name, ref, type(val)))
|
|
2098
|
+
end
|
|
2099
|
+
|
|
2100
|
+
H.set_buf_name = function(buf_id, name) vim.api.nvim_buf_set_name(buf_id, 'minianimate://' .. buf_id .. '/' .. name) end
|
|
2101
|
+
|
|
2102
|
+
H.validate_if = function(predicate, x, x_name)
|
|
2103
|
+
local is_valid, msg = predicate(x, x_name)
|
|
2104
|
+
if not is_valid then H.error(msg) end
|
|
2105
|
+
end
|
|
2106
|
+
|
|
2107
|
+
H.get_n_visible_lines = function(from_line, to_line)
|
|
2108
|
+
local min_line, max_line = math.min(from_line, to_line), math.max(from_line, to_line)
|
|
2109
|
+
|
|
2110
|
+
-- If `max_line` is inside fold, scroll should stop on the fold (not after)
|
|
2111
|
+
local max_line_fold_start = vim.fn.foldclosed(max_line)
|
|
2112
|
+
local target_line = max_line_fold_start == -1 and max_line or max_line_fold_start
|
|
2113
|
+
|
|
2114
|
+
local i, res = min_line, 1
|
|
2115
|
+
while i < target_line do
|
|
2116
|
+
res = res + 1
|
|
2117
|
+
local end_fold_line = vim.fn.foldclosedend(i)
|
|
2118
|
+
i = (end_fold_line == -1 and i or end_fold_line) + 1
|
|
2119
|
+
end
|
|
2120
|
+
return res
|
|
2121
|
+
end
|
|
2122
|
+
|
|
2123
|
+
H.round = function(x) return math.floor(x + 0.5) end
|
|
2124
|
+
|
|
2125
|
+
H.convex_point = function(x, y, coef) return H.round((1 - coef) * x + coef * y) end
|
|
2126
|
+
|
|
2127
|
+
return MiniAnimate
|