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.
Files changed (218) hide show
  1. package/.conda/retrovim.yaml +259 -0
  2. package/.conda/terax.icns +0 -0
  3. package/.conda/terax.ico +0 -0
  4. package/.conda/terax.png +0 -0
  5. package/.github/workflows/release.yml +95 -0
  6. package/.gitmodules +3 -0
  7. package/.npm/kanata_touchcursor.js +28 -0
  8. package/.npm/nvim.js +26 -0
  9. package/.npm/yazi.js +26 -0
  10. package/.npm/zsh.js +17 -0
  11. package/.pip/kanata_touchcursor/__init__.py +28 -0
  12. package/.pip/nvim/__init__.py +31 -0
  13. package/.pip/yazi/__init__.py +31 -0
  14. package/.pip/zsh/__init__.py +22 -0
  15. package/CHANGELOG.md +26 -0
  16. package/LICENSE +674 -0
  17. package/README.md +336 -0
  18. package/kanata/touchcursor.kbd +164 -0
  19. package/nvim/init.lua +813 -0
  20. package/nvim/plugins/mini.nvim/.github/DISCUSSION_TEMPLATE/q-a.yml +72 -0
  21. package/nvim/plugins/mini.nvim/.github/ISSUE_TEMPLATE/bug-report.yml +136 -0
  22. package/nvim/plugins/mini.nvim/.github/ISSUE_TEMPLATE/config.yml +5 -0
  23. package/nvim/plugins/mini.nvim/.github/ISSUE_TEMPLATE/feature-request.yml +75 -0
  24. package/nvim/plugins/mini.nvim/.github/PULL_REQUEST_TEMPLATE.md +2 -0
  25. package/nvim/plugins/mini.nvim/.github/workflows/quality-control.yml +150 -0
  26. package/nvim/plugins/mini.nvim/.pre-commit-config.yaml +20 -0
  27. package/nvim/plugins/mini.nvim/.stylua.toml +7 -0
  28. package/nvim/plugins/mini.nvim/.styluaignore +1 -0
  29. package/nvim/plugins/mini.nvim/CHANGELOG.md +1894 -0
  30. package/nvim/plugins/mini.nvim/CODE_OF_CONDUCT.md +132 -0
  31. package/nvim/plugins/mini.nvim/CONTRIBUTING.md +331 -0
  32. package/nvim/plugins/mini.nvim/LICENSE +21 -0
  33. package/nvim/plugins/mini.nvim/MAINTAINING.md +330 -0
  34. package/nvim/plugins/mini.nvim/Makefile +51 -0
  35. package/nvim/plugins/mini.nvim/README.md +261 -0
  36. package/nvim/plugins/mini.nvim/TESTING.md +1011 -0
  37. package/nvim/plugins/mini.nvim/benchmarks/starter/README.md +35 -0
  38. package/nvim/plugins/mini.nvim/benchmarks/starter/benchmark.sh +56 -0
  39. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_dashboard-alpha.lua +21 -0
  40. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_dashboard-original.lua +17 -0
  41. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_dashboard-starter.lua +18 -0
  42. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_empty.lua +4 -0
  43. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_starter-default.lua +7 -0
  44. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_startify-alpha.lua +10 -0
  45. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_startify-original.lua +7 -0
  46. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_startify-starter.lua +20 -0
  47. package/nvim/plugins/mini.nvim/benchmarks/starter/install.sh +10 -0
  48. package/nvim/plugins/mini.nvim/benchmarks/starter/make_summary.py +68 -0
  49. package/nvim/plugins/mini.nvim/benchmarks/starter/startup-summary.md +10 -0
  50. package/nvim/plugins/mini.nvim/colors/miniautumn.lua +55 -0
  51. package/nvim/plugins/mini.nvim/colors/minicyan.lua +95 -0
  52. package/nvim/plugins/mini.nvim/colors/minischeme.lua +95 -0
  53. package/nvim/plugins/mini.nvim/colors/minispring.lua +53 -0
  54. package/nvim/plugins/mini.nvim/colors/minisummer.lua +49 -0
  55. package/nvim/plugins/mini.nvim/colors/miniwinter.lua +51 -0
  56. package/nvim/plugins/mini.nvim/colors/randomhue.lua +16 -0
  57. package/nvim/plugins/mini.nvim/doc/mini-ai.txt +889 -0
  58. package/nvim/plugins/mini.nvim/doc/mini-align.txt +923 -0
  59. package/nvim/plugins/mini.nvim/doc/mini-animate.txt +937 -0
  60. package/nvim/plugins/mini.nvim/doc/mini-base16.txt +285 -0
  61. package/nvim/plugins/mini.nvim/doc/mini-basics.txt +359 -0
  62. package/nvim/plugins/mini.nvim/doc/mini-bracketed.txt +622 -0
  63. package/nvim/plugins/mini.nvim/doc/mini-bufremove.txt +123 -0
  64. package/nvim/plugins/mini.nvim/doc/mini-clue.txt +760 -0
  65. package/nvim/plugins/mini.nvim/doc/mini-cmdline.txt +392 -0
  66. package/nvim/plugins/mini.nvim/doc/mini-colors.txt +880 -0
  67. package/nvim/plugins/mini.nvim/doc/mini-comment.txt +230 -0
  68. package/nvim/plugins/mini.nvim/doc/mini-completion.txt +477 -0
  69. package/nvim/plugins/mini.nvim/doc/mini-cursorword.txt +105 -0
  70. package/nvim/plugins/mini.nvim/doc/mini-deps.txt +590 -0
  71. package/nvim/plugins/mini.nvim/doc/mini-diff.txt +661 -0
  72. package/nvim/plugins/mini.nvim/doc/mini-doc.txt +434 -0
  73. package/nvim/plugins/mini.nvim/doc/mini-extra.txt +856 -0
  74. package/nvim/plugins/mini.nvim/doc/mini-files.txt +1018 -0
  75. package/nvim/plugins/mini.nvim/doc/mini-fuzzy.txt +160 -0
  76. package/nvim/plugins/mini.nvim/doc/mini-git.txt +425 -0
  77. package/nvim/plugins/mini.nvim/doc/mini-hipatterns.txt +489 -0
  78. package/nvim/plugins/mini.nvim/doc/mini-hues.txt +420 -0
  79. package/nvim/plugins/mini.nvim/doc/mini-icons.txt +508 -0
  80. package/nvim/plugins/mini.nvim/doc/mini-indentscope.txt +485 -0
  81. package/nvim/plugins/mini.nvim/doc/mini-input.txt +932 -0
  82. package/nvim/plugins/mini.nvim/doc/mini-jump.txt +202 -0
  83. package/nvim/plugins/mini.nvim/doc/mini-jump2d.txt +525 -0
  84. package/nvim/plugins/mini.nvim/doc/mini-keymap.txt +449 -0
  85. package/nvim/plugins/mini.nvim/doc/mini-map.txt +717 -0
  86. package/nvim/plugins/mini.nvim/doc/mini-misc.txt +431 -0
  87. package/nvim/plugins/mini.nvim/doc/mini-move.txt +181 -0
  88. package/nvim/plugins/mini.nvim/doc/mini-notify.txt +434 -0
  89. package/nvim/plugins/mini.nvim/doc/mini-nvim.txt +431 -0
  90. package/nvim/plugins/mini.nvim/doc/mini-operators.txt +472 -0
  91. package/nvim/plugins/mini.nvim/doc/mini-pairs.txt +346 -0
  92. package/nvim/plugins/mini.nvim/doc/mini-pick.txt +1535 -0
  93. package/nvim/plugins/mini.nvim/doc/mini-sessions.txt +239 -0
  94. package/nvim/plugins/mini.nvim/doc/mini-snippets.txt +1291 -0
  95. package/nvim/plugins/mini.nvim/doc/mini-splitjoin.txt +536 -0
  96. package/nvim/plugins/mini.nvim/doc/mini-starter.txt +618 -0
  97. package/nvim/plugins/mini.nvim/doc/mini-statusline.txt +361 -0
  98. package/nvim/plugins/mini.nvim/doc/mini-surround.txt +863 -0
  99. package/nvim/plugins/mini.nvim/doc/mini-tabline.txt +151 -0
  100. package/nvim/plugins/mini.nvim/doc/mini-test.txt +966 -0
  101. package/nvim/plugins/mini.nvim/doc/mini-trailspace.txt +91 -0
  102. package/nvim/plugins/mini.nvim/doc/mini-visits.txt +937 -0
  103. package/nvim/plugins/mini.nvim/logo.png +0 -0
  104. package/nvim/plugins/mini.nvim/lua/mini/ai.lua +2244 -0
  105. package/nvim/plugins/mini.nvim/lua/mini/align.lua +2076 -0
  106. package/nvim/plugins/mini.nvim/lua/mini/animate.lua +2127 -0
  107. package/nvim/plugins/mini.nvim/lua/mini/base16.lua +1752 -0
  108. package/nvim/plugins/mini.nvim/lua/mini/basics.lua +772 -0
  109. package/nvim/plugins/mini.nvim/lua/mini/bracketed.lua +2013 -0
  110. package/nvim/plugins/mini.nvim/lua/mini/bufremove.lua +283 -0
  111. package/nvim/plugins/mini.nvim/lua/mini/clue.lua +2096 -0
  112. package/nvim/plugins/mini.nvim/lua/mini/cmdline.lua +1182 -0
  113. package/nvim/plugins/mini.nvim/lua/mini/colors.lua +2483 -0
  114. package/nvim/plugins/mini.nvim/lua/mini/comment.lua +586 -0
  115. package/nvim/plugins/mini.nvim/lua/mini/completion.lua +2094 -0
  116. package/nvim/plugins/mini.nvim/lua/mini/cursorword.lua +311 -0
  117. package/nvim/plugins/mini.nvim/lua/mini/deps.lua +1660 -0
  118. package/nvim/plugins/mini.nvim/lua/mini/diff.lua +1938 -0
  119. package/nvim/plugins/mini.nvim/lua/mini/doc.lua +1371 -0
  120. package/nvim/plugins/mini.nvim/lua/mini/extra.lua +2290 -0
  121. package/nvim/plugins/mini.nvim/lua/mini/files.lua +3145 -0
  122. package/nvim/plugins/mini.nvim/lua/mini/fuzzy.lua +356 -0
  123. package/nvim/plugins/mini.nvim/lua/mini/git.lua +1737 -0
  124. package/nvim/plugins/mini.nvim/lua/mini/hipatterns.lua +1042 -0
  125. package/nvim/plugins/mini.nvim/lua/mini/hues.lua +2088 -0
  126. package/nvim/plugins/mini.nvim/lua/mini/icons.lua +2254 -0
  127. package/nvim/plugins/mini.nvim/lua/mini/indentscope.lua +1158 -0
  128. package/nvim/plugins/mini.nvim/lua/mini/init.lua +427 -0
  129. package/nvim/plugins/mini.nvim/lua/mini/input.lua +2183 -0
  130. package/nvim/plugins/mini.nvim/lua/mini/jump.lua +609 -0
  131. package/nvim/plugins/mini.nvim/lua/mini/jump2d.lua +1244 -0
  132. package/nvim/plugins/mini.nvim/lua/mini/keymap.lua +880 -0
  133. package/nvim/plugins/mini.nvim/lua/mini/map.lua +1709 -0
  134. package/nvim/plugins/mini.nvim/lua/mini/misc.lua +969 -0
  135. package/nvim/plugins/mini.nvim/lua/mini/move.lua +509 -0
  136. package/nvim/plugins/mini.nvim/lua/mini/notify.lua +938 -0
  137. package/nvim/plugins/mini.nvim/lua/mini/operators.lua +1349 -0
  138. package/nvim/plugins/mini.nvim/lua/mini/pairs.lua +657 -0
  139. package/nvim/plugins/mini.nvim/lua/mini/pick.lua +3876 -0
  140. package/nvim/plugins/mini.nvim/lua/mini/sessions.lua +689 -0
  141. package/nvim/plugins/mini.nvim/lua/mini/snippets.lua +2828 -0
  142. package/nvim/plugins/mini.nvim/lua/mini/splitjoin.lua +1132 -0
  143. package/nvim/plugins/mini.nvim/lua/mini/starter.lua +1599 -0
  144. package/nvim/plugins/mini.nvim/lua/mini/statusline.lua +686 -0
  145. package/nvim/plugins/mini.nvim/lua/mini/surround.lua +2388 -0
  146. package/nvim/plugins/mini.nvim/lua/mini/tabline.lua +564 -0
  147. package/nvim/plugins/mini.nvim/lua/mini/test.lua +2516 -0
  148. package/nvim/plugins/mini.nvim/lua/mini/trailspace.lua +215 -0
  149. package/nvim/plugins/mini.nvim/lua/mini/visits.lua +1577 -0
  150. package/nvim/plugins/mini.nvim/readmes/mini-ai.md +190 -0
  151. package/nvim/plugins/mini.nvim/readmes/mini-align.md +203 -0
  152. package/nvim/plugins/mini.nvim/readmes/mini-animate.md +214 -0
  153. package/nvim/plugins/mini.nvim/readmes/mini-base16.md +198 -0
  154. package/nvim/plugins/mini.nvim/readmes/mini-basics.md +184 -0
  155. package/nvim/plugins/mini.nvim/readmes/mini-bracketed.md +193 -0
  156. package/nvim/plugins/mini.nvim/readmes/mini-bufremove.md +139 -0
  157. package/nvim/plugins/mini.nvim/readmes/mini-clue.md +260 -0
  158. package/nvim/plugins/mini.nvim/readmes/mini-cmdline.md +183 -0
  159. package/nvim/plugins/mini.nvim/readmes/mini-colors.md +169 -0
  160. package/nvim/plugins/mini.nvim/readmes/mini-comment.md +183 -0
  161. package/nvim/plugins/mini.nvim/readmes/mini-completion.md +217 -0
  162. package/nvim/plugins/mini.nvim/readmes/mini-cursorword.md +137 -0
  163. package/nvim/plugins/mini.nvim/readmes/mini-deps.md +271 -0
  164. package/nvim/plugins/mini.nvim/readmes/mini-diff.md +289 -0
  165. package/nvim/plugins/mini.nvim/readmes/mini-doc.md +198 -0
  166. package/nvim/plugins/mini.nvim/readmes/mini-extra.md +158 -0
  167. package/nvim/plugins/mini.nvim/readmes/mini-files.md +247 -0
  168. package/nvim/plugins/mini.nvim/readmes/mini-fuzzy.md +134 -0
  169. package/nvim/plugins/mini.nvim/readmes/mini-git.md +168 -0
  170. package/nvim/plugins/mini.nvim/readmes/mini-hipatterns.md +176 -0
  171. package/nvim/plugins/mini.nvim/readmes/mini-hues.md +285 -0
  172. package/nvim/plugins/mini.nvim/readmes/mini-icons.md +183 -0
  173. package/nvim/plugins/mini.nvim/readmes/mini-indentscope.md +191 -0
  174. package/nvim/plugins/mini.nvim/readmes/mini-input.md +163 -0
  175. package/nvim/plugins/mini.nvim/readmes/mini-jump.md +161 -0
  176. package/nvim/plugins/mini.nvim/readmes/mini-jump2d.md +197 -0
  177. package/nvim/plugins/mini.nvim/readmes/mini-keymap.md +205 -0
  178. package/nvim/plugins/mini.nvim/readmes/mini-map.md +191 -0
  179. package/nvim/plugins/mini.nvim/readmes/mini-misc.md +145 -0
  180. package/nvim/plugins/mini.nvim/readmes/mini-move.md +163 -0
  181. package/nvim/plugins/mini.nvim/readmes/mini-notify.md +175 -0
  182. package/nvim/plugins/mini.nvim/readmes/mini-operators.md +193 -0
  183. package/nvim/plugins/mini.nvim/readmes/mini-pairs.md +158 -0
  184. package/nvim/plugins/mini.nvim/readmes/mini-pick.md +340 -0
  185. package/nvim/plugins/mini.nvim/readmes/mini-sessions.md +164 -0
  186. package/nvim/plugins/mini.nvim/readmes/mini-snippets.md +373 -0
  187. package/nvim/plugins/mini.nvim/readmes/mini-splitjoin.md +194 -0
  188. package/nvim/plugins/mini.nvim/readmes/mini-starter.md +176 -0
  189. package/nvim/plugins/mini.nvim/readmes/mini-statusline.md +155 -0
  190. package/nvim/plugins/mini.nvim/readmes/mini-surround.md +190 -0
  191. package/nvim/plugins/mini.nvim/readmes/mini-tabline.md +156 -0
  192. package/nvim/plugins/mini.nvim/readmes/mini-test.md +175 -0
  193. package/nvim/plugins/mini.nvim/readmes/mini-trailspace.md +137 -0
  194. package/nvim/plugins/mini.nvim/readmes/mini-visits.md +243 -0
  195. package/nvim/plugins/mini.nvim/scripts/dual_log.sh +13 -0
  196. package/nvim/plugins/mini.nvim/scripts/dual_push.sh +15 -0
  197. package/nvim/plugins/mini.nvim/scripts/dual_release.sh +102 -0
  198. package/nvim/plugins/mini.nvim/scripts/dual_sync.sh +115 -0
  199. package/nvim/plugins/mini.nvim/scripts/init-deps-example.lua +59 -0
  200. package/nvim/plugins/mini.nvim/scripts/lint-filename-length.sh +16 -0
  201. package/nvim/plugins/mini.nvim/scripts/lintcommit-ci.sh +18 -0
  202. package/nvim/plugins/mini.nvim/scripts/lintcommit.lua +393 -0
  203. package/nvim/plugins/mini.nvim/scripts/minidoc.lua +57 -0
  204. package/nvim/plugins/mini.nvim/scripts/minimal_init.lua +28 -0
  205. package/nvim/plugins/mini.nvim/scripts/minitest.lua +4 -0
  206. package/package.json +16 -0
  207. package/pyproject.toml +43 -0
  208. package/yazi/init.lua +12 -0
  209. package/yazi/keymap.toml +114 -0
  210. package/yazi/lesskey +5 -0
  211. package/yazi/plugins/bookmarks.yazi/main.lua +86 -0
  212. package/yazi/plugins/cd-git-root.yazi/main.lua +31 -0
  213. package/yazi/plugins/smart-enter.yazi/main.lua +11 -0
  214. package/yazi/theme.toml +26 -0
  215. package/yazi/yazi.toml +15 -0
  216. package/zsh/.zshrc +145 -0
  217. package/zsh/starship.toml +20 -0
  218. 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