retrovim 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.conda/retrovim.yaml +259 -0
- package/.conda/terax.icns +0 -0
- package/.conda/terax.ico +0 -0
- package/.conda/terax.png +0 -0
- package/.github/workflows/release.yml +95 -0
- package/.gitmodules +3 -0
- package/.npm/kanata_touchcursor.js +28 -0
- package/.npm/nvim.js +26 -0
- package/.npm/yazi.js +26 -0
- package/.npm/zsh.js +17 -0
- package/.pip/kanata_touchcursor/__init__.py +28 -0
- package/.pip/nvim/__init__.py +31 -0
- package/.pip/yazi/__init__.py +31 -0
- package/.pip/zsh/__init__.py +22 -0
- package/CHANGELOG.md +26 -0
- package/LICENSE +674 -0
- package/README.md +336 -0
- package/kanata/touchcursor.kbd +164 -0
- package/nvim/init.lua +813 -0
- package/nvim/plugins/mini.nvim/.github/DISCUSSION_TEMPLATE/q-a.yml +72 -0
- package/nvim/plugins/mini.nvim/.github/ISSUE_TEMPLATE/bug-report.yml +136 -0
- package/nvim/plugins/mini.nvim/.github/ISSUE_TEMPLATE/config.yml +5 -0
- package/nvim/plugins/mini.nvim/.github/ISSUE_TEMPLATE/feature-request.yml +75 -0
- package/nvim/plugins/mini.nvim/.github/PULL_REQUEST_TEMPLATE.md +2 -0
- package/nvim/plugins/mini.nvim/.github/workflows/quality-control.yml +150 -0
- package/nvim/plugins/mini.nvim/.pre-commit-config.yaml +20 -0
- package/nvim/plugins/mini.nvim/.stylua.toml +7 -0
- package/nvim/plugins/mini.nvim/.styluaignore +1 -0
- package/nvim/plugins/mini.nvim/CHANGELOG.md +1894 -0
- package/nvim/plugins/mini.nvim/CODE_OF_CONDUCT.md +132 -0
- package/nvim/plugins/mini.nvim/CONTRIBUTING.md +331 -0
- package/nvim/plugins/mini.nvim/LICENSE +21 -0
- package/nvim/plugins/mini.nvim/MAINTAINING.md +330 -0
- package/nvim/plugins/mini.nvim/Makefile +51 -0
- package/nvim/plugins/mini.nvim/README.md +261 -0
- package/nvim/plugins/mini.nvim/TESTING.md +1011 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/README.md +35 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/benchmark.sh +56 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_dashboard-alpha.lua +21 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_dashboard-original.lua +17 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_dashboard-starter.lua +18 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_empty.lua +4 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_starter-default.lua +7 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_startify-alpha.lua +10 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_startify-original.lua +7 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_startify-starter.lua +20 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/install.sh +10 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/make_summary.py +68 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/startup-summary.md +10 -0
- package/nvim/plugins/mini.nvim/colors/miniautumn.lua +55 -0
- package/nvim/plugins/mini.nvim/colors/minicyan.lua +95 -0
- package/nvim/plugins/mini.nvim/colors/minischeme.lua +95 -0
- package/nvim/plugins/mini.nvim/colors/minispring.lua +53 -0
- package/nvim/plugins/mini.nvim/colors/minisummer.lua +49 -0
- package/nvim/plugins/mini.nvim/colors/miniwinter.lua +51 -0
- package/nvim/plugins/mini.nvim/colors/randomhue.lua +16 -0
- package/nvim/plugins/mini.nvim/doc/mini-ai.txt +889 -0
- package/nvim/plugins/mini.nvim/doc/mini-align.txt +923 -0
- package/nvim/plugins/mini.nvim/doc/mini-animate.txt +937 -0
- package/nvim/plugins/mini.nvim/doc/mini-base16.txt +285 -0
- package/nvim/plugins/mini.nvim/doc/mini-basics.txt +359 -0
- package/nvim/plugins/mini.nvim/doc/mini-bracketed.txt +622 -0
- package/nvim/plugins/mini.nvim/doc/mini-bufremove.txt +123 -0
- package/nvim/plugins/mini.nvim/doc/mini-clue.txt +760 -0
- package/nvim/plugins/mini.nvim/doc/mini-cmdline.txt +392 -0
- package/nvim/plugins/mini.nvim/doc/mini-colors.txt +880 -0
- package/nvim/plugins/mini.nvim/doc/mini-comment.txt +230 -0
- package/nvim/plugins/mini.nvim/doc/mini-completion.txt +477 -0
- package/nvim/plugins/mini.nvim/doc/mini-cursorword.txt +105 -0
- package/nvim/plugins/mini.nvim/doc/mini-deps.txt +590 -0
- package/nvim/plugins/mini.nvim/doc/mini-diff.txt +661 -0
- package/nvim/plugins/mini.nvim/doc/mini-doc.txt +434 -0
- package/nvim/plugins/mini.nvim/doc/mini-extra.txt +856 -0
- package/nvim/plugins/mini.nvim/doc/mini-files.txt +1018 -0
- package/nvim/plugins/mini.nvim/doc/mini-fuzzy.txt +160 -0
- package/nvim/plugins/mini.nvim/doc/mini-git.txt +425 -0
- package/nvim/plugins/mini.nvim/doc/mini-hipatterns.txt +489 -0
- package/nvim/plugins/mini.nvim/doc/mini-hues.txt +420 -0
- package/nvim/plugins/mini.nvim/doc/mini-icons.txt +508 -0
- package/nvim/plugins/mini.nvim/doc/mini-indentscope.txt +485 -0
- package/nvim/plugins/mini.nvim/doc/mini-input.txt +932 -0
- package/nvim/plugins/mini.nvim/doc/mini-jump.txt +202 -0
- package/nvim/plugins/mini.nvim/doc/mini-jump2d.txt +525 -0
- package/nvim/plugins/mini.nvim/doc/mini-keymap.txt +449 -0
- package/nvim/plugins/mini.nvim/doc/mini-map.txt +717 -0
- package/nvim/plugins/mini.nvim/doc/mini-misc.txt +431 -0
- package/nvim/plugins/mini.nvim/doc/mini-move.txt +181 -0
- package/nvim/plugins/mini.nvim/doc/mini-notify.txt +434 -0
- package/nvim/plugins/mini.nvim/doc/mini-nvim.txt +431 -0
- package/nvim/plugins/mini.nvim/doc/mini-operators.txt +472 -0
- package/nvim/plugins/mini.nvim/doc/mini-pairs.txt +346 -0
- package/nvim/plugins/mini.nvim/doc/mini-pick.txt +1535 -0
- package/nvim/plugins/mini.nvim/doc/mini-sessions.txt +239 -0
- package/nvim/plugins/mini.nvim/doc/mini-snippets.txt +1291 -0
- package/nvim/plugins/mini.nvim/doc/mini-splitjoin.txt +536 -0
- package/nvim/plugins/mini.nvim/doc/mini-starter.txt +618 -0
- package/nvim/plugins/mini.nvim/doc/mini-statusline.txt +361 -0
- package/nvim/plugins/mini.nvim/doc/mini-surround.txt +863 -0
- package/nvim/plugins/mini.nvim/doc/mini-tabline.txt +151 -0
- package/nvim/plugins/mini.nvim/doc/mini-test.txt +966 -0
- package/nvim/plugins/mini.nvim/doc/mini-trailspace.txt +91 -0
- package/nvim/plugins/mini.nvim/doc/mini-visits.txt +937 -0
- package/nvim/plugins/mini.nvim/logo.png +0 -0
- package/nvim/plugins/mini.nvim/lua/mini/ai.lua +2244 -0
- package/nvim/plugins/mini.nvim/lua/mini/align.lua +2076 -0
- package/nvim/plugins/mini.nvim/lua/mini/animate.lua +2127 -0
- package/nvim/plugins/mini.nvim/lua/mini/base16.lua +1752 -0
- package/nvim/plugins/mini.nvim/lua/mini/basics.lua +772 -0
- package/nvim/plugins/mini.nvim/lua/mini/bracketed.lua +2013 -0
- package/nvim/plugins/mini.nvim/lua/mini/bufremove.lua +283 -0
- package/nvim/plugins/mini.nvim/lua/mini/clue.lua +2096 -0
- package/nvim/plugins/mini.nvim/lua/mini/cmdline.lua +1182 -0
- package/nvim/plugins/mini.nvim/lua/mini/colors.lua +2483 -0
- package/nvim/plugins/mini.nvim/lua/mini/comment.lua +586 -0
- package/nvim/plugins/mini.nvim/lua/mini/completion.lua +2094 -0
- package/nvim/plugins/mini.nvim/lua/mini/cursorword.lua +311 -0
- package/nvim/plugins/mini.nvim/lua/mini/deps.lua +1660 -0
- package/nvim/plugins/mini.nvim/lua/mini/diff.lua +1938 -0
- package/nvim/plugins/mini.nvim/lua/mini/doc.lua +1371 -0
- package/nvim/plugins/mini.nvim/lua/mini/extra.lua +2290 -0
- package/nvim/plugins/mini.nvim/lua/mini/files.lua +3145 -0
- package/nvim/plugins/mini.nvim/lua/mini/fuzzy.lua +356 -0
- package/nvim/plugins/mini.nvim/lua/mini/git.lua +1737 -0
- package/nvim/plugins/mini.nvim/lua/mini/hipatterns.lua +1042 -0
- package/nvim/plugins/mini.nvim/lua/mini/hues.lua +2088 -0
- package/nvim/plugins/mini.nvim/lua/mini/icons.lua +2254 -0
- package/nvim/plugins/mini.nvim/lua/mini/indentscope.lua +1158 -0
- package/nvim/plugins/mini.nvim/lua/mini/init.lua +427 -0
- package/nvim/plugins/mini.nvim/lua/mini/input.lua +2183 -0
- package/nvim/plugins/mini.nvim/lua/mini/jump.lua +609 -0
- package/nvim/plugins/mini.nvim/lua/mini/jump2d.lua +1244 -0
- package/nvim/plugins/mini.nvim/lua/mini/keymap.lua +880 -0
- package/nvim/plugins/mini.nvim/lua/mini/map.lua +1709 -0
- package/nvim/plugins/mini.nvim/lua/mini/misc.lua +969 -0
- package/nvim/plugins/mini.nvim/lua/mini/move.lua +509 -0
- package/nvim/plugins/mini.nvim/lua/mini/notify.lua +938 -0
- package/nvim/plugins/mini.nvim/lua/mini/operators.lua +1349 -0
- package/nvim/plugins/mini.nvim/lua/mini/pairs.lua +657 -0
- package/nvim/plugins/mini.nvim/lua/mini/pick.lua +3876 -0
- package/nvim/plugins/mini.nvim/lua/mini/sessions.lua +689 -0
- package/nvim/plugins/mini.nvim/lua/mini/snippets.lua +2828 -0
- package/nvim/plugins/mini.nvim/lua/mini/splitjoin.lua +1132 -0
- package/nvim/plugins/mini.nvim/lua/mini/starter.lua +1599 -0
- package/nvim/plugins/mini.nvim/lua/mini/statusline.lua +686 -0
- package/nvim/plugins/mini.nvim/lua/mini/surround.lua +2388 -0
- package/nvim/plugins/mini.nvim/lua/mini/tabline.lua +564 -0
- package/nvim/plugins/mini.nvim/lua/mini/test.lua +2516 -0
- package/nvim/plugins/mini.nvim/lua/mini/trailspace.lua +215 -0
- package/nvim/plugins/mini.nvim/lua/mini/visits.lua +1577 -0
- package/nvim/plugins/mini.nvim/readmes/mini-ai.md +190 -0
- package/nvim/plugins/mini.nvim/readmes/mini-align.md +203 -0
- package/nvim/plugins/mini.nvim/readmes/mini-animate.md +214 -0
- package/nvim/plugins/mini.nvim/readmes/mini-base16.md +198 -0
- package/nvim/plugins/mini.nvim/readmes/mini-basics.md +184 -0
- package/nvim/plugins/mini.nvim/readmes/mini-bracketed.md +193 -0
- package/nvim/plugins/mini.nvim/readmes/mini-bufremove.md +139 -0
- package/nvim/plugins/mini.nvim/readmes/mini-clue.md +260 -0
- package/nvim/plugins/mini.nvim/readmes/mini-cmdline.md +183 -0
- package/nvim/plugins/mini.nvim/readmes/mini-colors.md +169 -0
- package/nvim/plugins/mini.nvim/readmes/mini-comment.md +183 -0
- package/nvim/plugins/mini.nvim/readmes/mini-completion.md +217 -0
- package/nvim/plugins/mini.nvim/readmes/mini-cursorword.md +137 -0
- package/nvim/plugins/mini.nvim/readmes/mini-deps.md +271 -0
- package/nvim/plugins/mini.nvim/readmes/mini-diff.md +289 -0
- package/nvim/plugins/mini.nvim/readmes/mini-doc.md +198 -0
- package/nvim/plugins/mini.nvim/readmes/mini-extra.md +158 -0
- package/nvim/plugins/mini.nvim/readmes/mini-files.md +247 -0
- package/nvim/plugins/mini.nvim/readmes/mini-fuzzy.md +134 -0
- package/nvim/plugins/mini.nvim/readmes/mini-git.md +168 -0
- package/nvim/plugins/mini.nvim/readmes/mini-hipatterns.md +176 -0
- package/nvim/plugins/mini.nvim/readmes/mini-hues.md +285 -0
- package/nvim/plugins/mini.nvim/readmes/mini-icons.md +183 -0
- package/nvim/plugins/mini.nvim/readmes/mini-indentscope.md +191 -0
- package/nvim/plugins/mini.nvim/readmes/mini-input.md +163 -0
- package/nvim/plugins/mini.nvim/readmes/mini-jump.md +161 -0
- package/nvim/plugins/mini.nvim/readmes/mini-jump2d.md +197 -0
- package/nvim/plugins/mini.nvim/readmes/mini-keymap.md +205 -0
- package/nvim/plugins/mini.nvim/readmes/mini-map.md +191 -0
- package/nvim/plugins/mini.nvim/readmes/mini-misc.md +145 -0
- package/nvim/plugins/mini.nvim/readmes/mini-move.md +163 -0
- package/nvim/plugins/mini.nvim/readmes/mini-notify.md +175 -0
- package/nvim/plugins/mini.nvim/readmes/mini-operators.md +193 -0
- package/nvim/plugins/mini.nvim/readmes/mini-pairs.md +158 -0
- package/nvim/plugins/mini.nvim/readmes/mini-pick.md +340 -0
- package/nvim/plugins/mini.nvim/readmes/mini-sessions.md +164 -0
- package/nvim/plugins/mini.nvim/readmes/mini-snippets.md +373 -0
- package/nvim/plugins/mini.nvim/readmes/mini-splitjoin.md +194 -0
- package/nvim/plugins/mini.nvim/readmes/mini-starter.md +176 -0
- package/nvim/plugins/mini.nvim/readmes/mini-statusline.md +155 -0
- package/nvim/plugins/mini.nvim/readmes/mini-surround.md +190 -0
- package/nvim/plugins/mini.nvim/readmes/mini-tabline.md +156 -0
- package/nvim/plugins/mini.nvim/readmes/mini-test.md +175 -0
- package/nvim/plugins/mini.nvim/readmes/mini-trailspace.md +137 -0
- package/nvim/plugins/mini.nvim/readmes/mini-visits.md +243 -0
- package/nvim/plugins/mini.nvim/scripts/dual_log.sh +13 -0
- package/nvim/plugins/mini.nvim/scripts/dual_push.sh +15 -0
- package/nvim/plugins/mini.nvim/scripts/dual_release.sh +102 -0
- package/nvim/plugins/mini.nvim/scripts/dual_sync.sh +115 -0
- package/nvim/plugins/mini.nvim/scripts/init-deps-example.lua +59 -0
- package/nvim/plugins/mini.nvim/scripts/lint-filename-length.sh +16 -0
- package/nvim/plugins/mini.nvim/scripts/lintcommit-ci.sh +18 -0
- package/nvim/plugins/mini.nvim/scripts/lintcommit.lua +393 -0
- package/nvim/plugins/mini.nvim/scripts/minidoc.lua +57 -0
- package/nvim/plugins/mini.nvim/scripts/minimal_init.lua +28 -0
- package/nvim/plugins/mini.nvim/scripts/minitest.lua +4 -0
- package/package.json +16 -0
- package/pyproject.toml +43 -0
- package/yazi/init.lua +12 -0
- package/yazi/keymap.toml +114 -0
- package/yazi/lesskey +5 -0
- package/yazi/plugins/bookmarks.yazi/main.lua +86 -0
- package/yazi/plugins/cd-git-root.yazi/main.lua +31 -0
- package/yazi/plugins/smart-enter.yazi/main.lua +11 -0
- package/yazi/theme.toml +26 -0
- package/yazi/yazi.toml +15 -0
- package/zsh/.zshrc +145 -0
- package/zsh/starship.toml +20 -0
- package/zsh/zsh-patina.toml +2 -0
|
@@ -0,0 +1,3876 @@
|
|
|
1
|
+
--- *mini.pick* Pick anything
|
|
2
|
+
---
|
|
3
|
+
--- MIT License Copyright (c) 2023 Evgeni Chasnovski
|
|
4
|
+
|
|
5
|
+
--- Features:
|
|
6
|
+
---
|
|
7
|
+
--- - Single window general purpose interface for picking element from any array.
|
|
8
|
+
---
|
|
9
|
+
--- - On demand toggleable preview and info views.
|
|
10
|
+
---
|
|
11
|
+
--- - Interactive query matching (filter+sort) with fast non-blocking default
|
|
12
|
+
--- which does fuzzy matching and allows other modes (|MiniPick.default_match()|).
|
|
13
|
+
---
|
|
14
|
+
--- - Built-in pickers (see |MiniPick.builtin|):
|
|
15
|
+
--- - Files.
|
|
16
|
+
--- - Pattern match (for fixed pattern or with live feedback; both allow
|
|
17
|
+
--- file filtering via glob patterns).
|
|
18
|
+
--- - Buffers.
|
|
19
|
+
--- - Help tags.
|
|
20
|
+
--- - CLI output.
|
|
21
|
+
--- - Resume latest picker.
|
|
22
|
+
---
|
|
23
|
+
--- - |:Pick| command to work with extensible |MiniPick.registry|.
|
|
24
|
+
---
|
|
25
|
+
--- - |vim.ui.select()| implementation. To adjust, use |MiniPick.ui_select()|
|
|
26
|
+
--- or save-restore `vim.ui.select` manually after calling |MiniPick.setup()|.
|
|
27
|
+
---
|
|
28
|
+
--- - Rich and customizable built-in |MiniPick-actions| when picker is active:
|
|
29
|
+
--- - Manually change currently focused item.
|
|
30
|
+
--- - Scroll vertically and horizontally.
|
|
31
|
+
--- - Toggle preview or info view.
|
|
32
|
+
--- - Mark/unmark items to choose later.
|
|
33
|
+
--- - Refine current matches (make them part of a new picker).
|
|
34
|
+
--- - And many more.
|
|
35
|
+
---
|
|
36
|
+
--- - Minimal yet flexible |MiniPick-source| specification with:
|
|
37
|
+
--- - Items (array, callable, or manually set later).
|
|
38
|
+
--- - Source name.
|
|
39
|
+
--- - Working directory.
|
|
40
|
+
--- - Matching algorithm.
|
|
41
|
+
--- - Way matches are shown in main window.
|
|
42
|
+
--- - Item preview.
|
|
43
|
+
--- - "On choice" action for current and marked items.
|
|
44
|
+
---
|
|
45
|
+
--- - Custom actions/keys can be configured globally, per buffer, or per picker.
|
|
46
|
+
---
|
|
47
|
+
--- - Out of the box support for |'ignorecase'| and |'smartcase'|.
|
|
48
|
+
---
|
|
49
|
+
--- - Match caching to increase responsiveness on repeated prompts.
|
|
50
|
+
---
|
|
51
|
+
--- Notes:
|
|
52
|
+
--- - Works on all supported versions but Neovim>=0.10 will give more visual
|
|
53
|
+
--- feedback in floating window footer.
|
|
54
|
+
---
|
|
55
|
+
--- - For more pickers see |MiniExtra.pickers|.
|
|
56
|
+
---
|
|
57
|
+
--- Sources with more details:
|
|
58
|
+
--- - |MiniPick-overview|
|
|
59
|
+
--- - |MiniPick-source|
|
|
60
|
+
--- - |MiniPick-actions|
|
|
61
|
+
--- - |MiniPick-examples|
|
|
62
|
+
--- - |MiniPick.builtin|
|
|
63
|
+
--- - |MiniPick-in-other-plugins| (for plugin authors)
|
|
64
|
+
---
|
|
65
|
+
--- # Dependencies ~
|
|
66
|
+
---
|
|
67
|
+
--- Suggested dependencies (provide extra functionality, will work without them):
|
|
68
|
+
---
|
|
69
|
+
--- - Enabled |mini.icons| module to show icons near the items for actual paths.
|
|
70
|
+
--- Falls back to `nvim-tree/nvim-web-devicons` plugin or no icons will be used.
|
|
71
|
+
---
|
|
72
|
+
--- - *MiniPick-cli-tools* CLI tool(s) to power |MiniPick.builtin.files()|,
|
|
73
|
+
--- |MiniPick.builtin.grep()|, and |MiniPick.builtin.grep_live()| built-in pickers:
|
|
74
|
+
--- - `rg` (github.com/BurntSushi/ripgrep; enough for all three; recommended).
|
|
75
|
+
--- - `fd` (github.com/sharkdp/fd; for `files` only).
|
|
76
|
+
--- - `git` (github.com/git/git; enough for all three).
|
|
77
|
+
---
|
|
78
|
+
--- Note: CLI tools are called only with basic arguments needed to get items.
|
|
79
|
+
--- To customize the output, use their respective configuration approaches.
|
|
80
|
+
--- Here are some examples of where to start:
|
|
81
|
+
--- - github.com/BurntSushi/ripgrep/blob/master/GUIDE.md#configuration-file
|
|
82
|
+
--- - github.com/sharkdp/fd#excluding-specific-files-or-directories
|
|
83
|
+
--- - git-scm.com/docs/gitignore
|
|
84
|
+
---
|
|
85
|
+
--- # Setup ~
|
|
86
|
+
---
|
|
87
|
+
--- This module needs a setup with `require('mini.pick').setup({})` (replace
|
|
88
|
+
--- `{}` with your `config` table). It will create global Lua table `MiniPick`
|
|
89
|
+
--- which you can use for scripting or manually (with `:lua MiniPick.*`).
|
|
90
|
+
---
|
|
91
|
+
--- See |MiniPick.config| for available config settings.
|
|
92
|
+
---
|
|
93
|
+
--- You can override runtime config settings locally to buffer inside
|
|
94
|
+
--- `vim.b.minipick_config` which should have same structure as `MiniPick.config`.
|
|
95
|
+
--- See |mini.nvim-buffer-local-config| for more details.
|
|
96
|
+
---
|
|
97
|
+
--- # Comparisons ~
|
|
98
|
+
---
|
|
99
|
+
--- - [nvim-telescope/telescope.nvim](https://github.com/nvim-telescope/telescope.nvim):
|
|
100
|
+
--- - The main inspiration for this module, so there is significant overlap.
|
|
101
|
+
--- - Has three (or two) window UI (prompt, matches, preview), while this
|
|
102
|
+
--- module combines everything in one window. It allows more straightforward
|
|
103
|
+
--- customization for unusual scenarios.
|
|
104
|
+
--- - Default match algorithm is somewhat slow, while this module should
|
|
105
|
+
--- match relatively lag-free for at least 100K+ items.
|
|
106
|
+
--- - Has many built-in pickers, while this module has handful at its core
|
|
107
|
+
--- relying on other |mini.nvim| modules to provide more (see |mini.extra|).
|
|
108
|
+
---
|
|
109
|
+
--- - [ibhagwan/fzf-lua](https://github.com/ibhagwan/fzf-lua):
|
|
110
|
+
--- - Mostly same comparison as with `nvim-telescope/telescope.nvim`.
|
|
111
|
+
--- - Requires [junegunn/fzf](https://github.com/junegunn/fzf) installed to
|
|
112
|
+
--- power fuzzy matching, while this module provides built-in Lua matching.
|
|
113
|
+
---
|
|
114
|
+
--- # Highlight groups ~
|
|
115
|
+
--- *MiniPick-hl-groups*
|
|
116
|
+
---
|
|
117
|
+
--- - `MiniPickBorder` - window border.
|
|
118
|
+
--- - `MiniPickBorderBusy` - window border while picker is busy processing.
|
|
119
|
+
--- - `MiniPickBorderText` - non-prompt on border.
|
|
120
|
+
--- - `MiniPickCursor` - cursor during active picker (hidden by default).
|
|
121
|
+
--- - `MiniPickIconDirectory` - default icon for directory.
|
|
122
|
+
--- - `MiniPickIconFile` - default icon for file.
|
|
123
|
+
--- - `MiniPickHeader` - headers in info buffer and previews.
|
|
124
|
+
--- - `MiniPickMatchCurrent` - current matched item.
|
|
125
|
+
--- - `MiniPickMatchMarked` - marked matched items.
|
|
126
|
+
--- - `MiniPickMatchRanges` - ranges matching query elements.
|
|
127
|
+
--- - `MiniPickNormal` - basic foreground/background highlighting.
|
|
128
|
+
--- - `MiniPickPreviewLine` - target line in preview.
|
|
129
|
+
--- - `MiniPickPreviewRegion` - target region in preview.
|
|
130
|
+
--- - `MiniPickPrompt` - prompt.
|
|
131
|
+
--- - `MiniPickPromptCaret` - caret in prompt.
|
|
132
|
+
--- - `MiniPickPromptPrefix` - prefix of the prompt.
|
|
133
|
+
---
|
|
134
|
+
--- To change any highlight group, set it directly with |nvim_set_hl()|.
|
|
135
|
+
---
|
|
136
|
+
--- # Using in other plugins ~
|
|
137
|
+
--- *MiniPick-in-other-plugins*
|
|
138
|
+
---
|
|
139
|
+
--- - Prefer using |vim.ui.select()| for more user coverage. Use |MiniPick.start()|
|
|
140
|
+
--- when synchronous select or its extra capabilities is absolutely necessary.
|
|
141
|
+
---
|
|
142
|
+
--- - Perform a `_G.MiniPick ~= nil` check before using any feature. This ensures
|
|
143
|
+
--- that user explicitly set up the module.
|
|
144
|
+
---@tag MiniPick
|
|
145
|
+
|
|
146
|
+
--- To allow user customization and integration of external tools, certain |User|
|
|
147
|
+
--- autocommand events are triggered under common circumstances:
|
|
148
|
+
---
|
|
149
|
+
--- - `MiniPickMatch` - just after updating query matches or setting items.
|
|
150
|
+
--- - `MiniPickStart` - just after picker has started.
|
|
151
|
+
--- - `MiniPickStop` - just before picker is stopped.
|
|
152
|
+
---@tag MiniPick-events
|
|
153
|
+
|
|
154
|
+
--- General idea is to take array of objects, display them with interactive
|
|
155
|
+
--- filter/sort/navigate/preview, and allow to choose one or more items.
|
|
156
|
+
---
|
|
157
|
+
--- # How to start a picker ~
|
|
158
|
+
---
|
|
159
|
+
--- - Use |MiniPick.start()| with `opts.source` defining |MiniPick-source|.
|
|
160
|
+
--- Example: `MiniPick.start({ source = { items = vim.fn.readdir('.') } })`
|
|
161
|
+
---
|
|
162
|
+
--- - Use any of |MiniPick.builtin| pickers directly.
|
|
163
|
+
--- Example: `MiniPick.builtin.files({ tool = 'git' })`
|
|
164
|
+
---
|
|
165
|
+
--- - Use |:Pick| command which uses customizable pickers from |MiniPick.registry|.
|
|
166
|
+
--- Example: `:Pick files tool='git'`
|
|
167
|
+
---
|
|
168
|
+
--- # User interface ~
|
|
169
|
+
---
|
|
170
|
+
--- UI consists from a single window capable of displaying three different views:
|
|
171
|
+
--- - "Main" - where current query matches are shown.
|
|
172
|
+
--- - "Preview" - preview of current item (toggle with `<Tab>`).
|
|
173
|
+
--- - "Info" - general info about picker and its state (toggle with `<S-Tab>`).
|
|
174
|
+
---
|
|
175
|
+
--- Current prompt is displayed at the top left of the window border with vertical
|
|
176
|
+
--- line indicating caret (current input position).
|
|
177
|
+
---
|
|
178
|
+
--- Bottom part of window border displays (in Neovim>=0.10) extra visual feedback:
|
|
179
|
+
--- - Left part is a picker name.
|
|
180
|
+
--- - Right part contains information in the format >
|
|
181
|
+
---
|
|
182
|
+
--- <current index in matches> | <match count> | <marked count> / <total count>
|
|
183
|
+
--- <
|
|
184
|
+
--- When picker is busy (like if there are no items yet set or matching is active)
|
|
185
|
+
--- window border changes color to be `MiniPickBorderBusy` after `config.delay.busy`
|
|
186
|
+
--- milliseconds of idle time.
|
|
187
|
+
---
|
|
188
|
+
--- # Life cycle ~
|
|
189
|
+
---
|
|
190
|
+
--- - Type characters to filter and sort matches. It uses |MiniPick.default_match()|
|
|
191
|
+
--- with `query` being an array of pressed characters.
|
|
192
|
+
--- Overview of how it matches:
|
|
193
|
+
--- - If query starts with `'`, the match is exact.
|
|
194
|
+
--- - If query starts with `^`, the match is exact at start.
|
|
195
|
+
--- - If query ends with `$`, the match is exact at end.
|
|
196
|
+
--- - If query starts with `*`, the match is forced to be fuzzy.
|
|
197
|
+
--- - Otherwise match is fuzzy.
|
|
198
|
+
--- - Sorting is done to first minimize match width and then match start.
|
|
199
|
+
--- Nothing more: no favoring certain places in string, etc.
|
|
200
|
+
---
|
|
201
|
+
--- - Type special keys to perform |MiniPick-actions|. Here are some basic ones:
|
|
202
|
+
--- - `<C-n>` / `<Down>` moves down; `<C-p>` / `<Up>` moves up.
|
|
203
|
+
--- - `<Left>` / `<Right>` moves prompt caret left / right.
|
|
204
|
+
--- - `<S-Tab>` toggles information window with all available mappings.
|
|
205
|
+
--- - `<Tab>` toggles preview.
|
|
206
|
+
--- - `<C-x>` / `<C-a>` toggles current / all item(s) as (un)marked.
|
|
207
|
+
--- - `<C-Space>` / `<M-Space>` makes all matches or marked items as new picker.
|
|
208
|
+
--- - `<CR>` / `<M-CR>` chooses current/marked item(s).
|
|
209
|
+
--- - `<Esc>` / `<C-c>` stops picker.
|
|
210
|
+
---
|
|
211
|
+
--- # Implementation details ~
|
|
212
|
+
---
|
|
213
|
+
--- - Processing key typing is done via a dedicated key query process for more
|
|
214
|
+
--- control over their side effects. As a result, regular mappings don't work
|
|
215
|
+
--- here and picker's window needs to be current as long as it is shown.
|
|
216
|
+
--- Changing window focus leads to automatic picker stop (after small delay).
|
|
217
|
+
--- Not picker related screen changes require explicit |:redraw|.
|
|
218
|
+
--- - Any picker is non-blocking but waits to return the chosen item. Example:
|
|
219
|
+
--- `file = MiniPick.builtin.files()` allows other actions to be executed when
|
|
220
|
+
--- picker is shown while still assigning `file` with value of the chosen item.
|
|
221
|
+
---@tag MiniPick-overview
|
|
222
|
+
|
|
223
|
+
--- Source is defined as a `source` field inside one of (in increasing priority):
|
|
224
|
+
--- - |MiniPick.config| - has global effect.
|
|
225
|
+
--- - `vim.b.minipick_config` - has buffer-local effect.
|
|
226
|
+
--- - `opts.source` in picker call - has effect for that particular call.
|
|
227
|
+
---
|
|
228
|
+
--- Example of source to choose from |arglist|: >lua
|
|
229
|
+
---
|
|
230
|
+
--- { items = vim.fn.argv, name = 'Arglist' }
|
|
231
|
+
--- <
|
|
232
|
+
--- Note: this is mostly useful for writing pickers. Can safely skip if you
|
|
233
|
+
--- want to just use provided pickers.
|
|
234
|
+
---
|
|
235
|
+
--- # Items ~
|
|
236
|
+
--- *MiniPick-source.items*
|
|
237
|
+
---
|
|
238
|
+
--- `source.items` defines items to choose from. It should be one of the following:
|
|
239
|
+
--- - Array of objects which can have different types. Any type is allowed.
|
|
240
|
+
--- - `nil`. Picker waits for explicit |MiniPick.set_picker_items()| call.
|
|
241
|
+
--- - Callable returning any of the previous types. Will be called once on start
|
|
242
|
+
--- with source's `cwd` set as |current-directory|.
|
|
243
|
+
---
|
|
244
|
+
--- *MiniPick-source.items-stritems*
|
|
245
|
+
--- Matching is done for items array based on the string representation of its
|
|
246
|
+
--- elements (here called "stritems"). For single item it is computed as follows:
|
|
247
|
+
--- - Callable is called once with output used in next steps.
|
|
248
|
+
--- - String item is used as is.
|
|
249
|
+
--- - String <text> field of table item is used (if present).
|
|
250
|
+
--- - Use output of |vim.inspect()|.
|
|
251
|
+
---
|
|
252
|
+
--- Example: >lua
|
|
253
|
+
---
|
|
254
|
+
--- items = { 'aaa.txt', { text = 'bbb' }, function() return 'ccc' end }
|
|
255
|
+
--- -- corresponding stritems are { 'aaa.txt', 'bbb', 'ccc' }
|
|
256
|
+
--- <
|
|
257
|
+
--- Default value is `nil`, assuming it always be supplied by the caller.
|
|
258
|
+
---
|
|
259
|
+
--- *MiniPick-source.items-common*
|
|
260
|
+
--- There are some recommendations for common item types in order for them to work
|
|
261
|
+
--- out of the box with |MiniPick.default_show()|, |MiniPick.default_preview()|,
|
|
262
|
+
--- |MiniPick.default_choose()|, |MiniPick.default_choose_marked()|:
|
|
263
|
+
---
|
|
264
|
+
--- - Path (file or directory). Use string or `path` field of a table. Path can
|
|
265
|
+
--- be either absolute, relative to the `source.cwd`, or have a general URI format
|
|
266
|
+
--- (only if supplied as table field).
|
|
267
|
+
--- Examples: `'aaa.txt'`, `{ path = 'aaa.txt' }`
|
|
268
|
+
---
|
|
269
|
+
--- - Buffer. Use buffer id as number, string, or `bufnr` / `buf_id` / `buf`
|
|
270
|
+
--- field of a table (any name is allowed).
|
|
271
|
+
--- Examples: `1`, `'1'`, `{ bufnr = 1 }`, `{ buf_id = 1 }`, `{ buf = 1 }`
|
|
272
|
+
---
|
|
273
|
+
--- - Line in file or buffer. Use table representation with `lnum` field with line
|
|
274
|
+
--- number (starting from 1) or string in "<path>\0<line>" format (`\0` is
|
|
275
|
+
--- an actual null character; don't escape the slash; may need to be `\000`).
|
|
276
|
+
--- Examples: >lua
|
|
277
|
+
---
|
|
278
|
+
--- { path = 'aaa.txt', lnum = 2 }, 'aaa.txt\0002', { bufnr = 1, lnum = 3 }
|
|
279
|
+
--- <
|
|
280
|
+
--- - Position in file or buffer. Use table representation with `lnum` and `col`
|
|
281
|
+
--- fields with line and column numbers (starting from 1) or string in
|
|
282
|
+
--- "<path>\0<line>\0<col>" format (`\0` is an actual null character, don't
|
|
283
|
+
--- escape the slash; may need to be `\000`).
|
|
284
|
+
--- Examples: >lua
|
|
285
|
+
---
|
|
286
|
+
--- { path = 'aaa.txt', lnum = 2, col = 3 }, 'aaa.txt\0' .. '2\0003',
|
|
287
|
+
--- { bufnr = 1, lnum = 3, col = 4 }
|
|
288
|
+
--- <
|
|
289
|
+
--- - Region in file or buffer. Use table representation with `lnum`, `col`,
|
|
290
|
+
--- `end_lnum`, `end_col` fields for start and end line/column. All numbers
|
|
291
|
+
--- start from 1, end line is inclusive, end column is exclusive.
|
|
292
|
+
--- This naming is similar to |getqflist()| and |diagnostic-structure|.
|
|
293
|
+
--- Examples: >lua
|
|
294
|
+
---
|
|
295
|
+
--- { path = 'aaa.txt', lnum = 2, col = 3, end_lnum = 4, end_col = 5 },
|
|
296
|
+
--- { bufnr = 1, lnum = 3, col = 4, end_lnum = 5, end_col = 6 }
|
|
297
|
+
--- <
|
|
298
|
+
--- Note: all table items will benefit from having `text` field for better matching.
|
|
299
|
+
---
|
|
300
|
+
--- # Name ~
|
|
301
|
+
--- *MiniPick-source.name*
|
|
302
|
+
---
|
|
303
|
+
--- `source.name` defines the name of the picker to be used for visual feedback.
|
|
304
|
+
---
|
|
305
|
+
--- Default value is "<No name>".
|
|
306
|
+
---
|
|
307
|
+
--- # Current working directory ~
|
|
308
|
+
--- *MiniPick-source.cwd*
|
|
309
|
+
---
|
|
310
|
+
--- `source.cwd` is a string defining the current working directory in which
|
|
311
|
+
--- picker operates. It should point to a valid actually present directory path.
|
|
312
|
+
--- This is a part of source to allow persistent way to use relative paths,
|
|
313
|
+
--- i.e. not depend on current directory being constant after picker start.
|
|
314
|
+
--- It also makes the |MiniPick.builtin.resume()| picker more robust.
|
|
315
|
+
---
|
|
316
|
+
--- It will be set as local |current-directory| (|:lcd|) of picker's main window
|
|
317
|
+
--- to allow simpler code for "in window" functions (choose/preview/custom/etc.).
|
|
318
|
+
---
|
|
319
|
+
--- Default value is |current-directory|.
|
|
320
|
+
---
|
|
321
|
+
--- # Match ~
|
|
322
|
+
--- *MiniPick-source.match*
|
|
323
|
+
---
|
|
324
|
+
--- `source.match` is a callable defining how stritems
|
|
325
|
+
--- (see |MiniPick-source.items-stritems|) are matched (filtered and sorted) based
|
|
326
|
+
--- on the query.
|
|
327
|
+
---
|
|
328
|
+
--- It will be called with the following arguments:
|
|
329
|
+
--- - `stritems` - all available stritems for current picker.
|
|
330
|
+
--- - `inds` - array of `stritems` indexes usually pointing at current matches.
|
|
331
|
+
--- It does point to current matches in the case of interactively appending
|
|
332
|
+
--- character at the end of the query. It assumes that matches for such bigger
|
|
333
|
+
--- query is a subset of previous matches (implementation can ignore it).
|
|
334
|
+
--- This can be utilized to increase performance by checking fewer stritems.
|
|
335
|
+
--- - `query` - array of strings. Usually (like is common case of user interactively
|
|
336
|
+
--- typing query) each string represents one character. However, any strings are
|
|
337
|
+
--- allowed, as query can be set with |MiniPick.set_picker_query()|.
|
|
338
|
+
---
|
|
339
|
+
--- It should either return array of match indexes for stritems elements matching
|
|
340
|
+
--- the query (synchronous) or explicitly use |MiniPick.set_picker_match_inds()|
|
|
341
|
+
--- to set them (may be asynchronous).
|
|
342
|
+
---
|
|
343
|
+
--- Notes:
|
|
344
|
+
--- - The result can be any array of `stritems` indexes, i.e. not necessarily
|
|
345
|
+
--- a subset of input `inds`.
|
|
346
|
+
---
|
|
347
|
+
--- - Both `stritems` and `query` depend on values of |'ignorecase'| and |'smartcase'|.
|
|
348
|
+
--- If query shows "ignore case" properties (only |'ignorecase'| is set or both
|
|
349
|
+
--- |'ignorecase'| / |'smartcase'| are set and query has only lowercase characters),
|
|
350
|
+
--- then `stritems` and `query` will have only lowercase characters.
|
|
351
|
+
--- This allows automatic support for case insensitive matching while being
|
|
352
|
+
--- faster and having simpler match function implementation.
|
|
353
|
+
---
|
|
354
|
+
--- - Writing custom `source.match` usually means also changing |MiniPick-source.show|
|
|
355
|
+
--- because it is used to highlight stritems parts actually matching the query.
|
|
356
|
+
---
|
|
357
|
+
--- Example of simple "exact" `match()` preserving initial order: >lua
|
|
358
|
+
---
|
|
359
|
+
--- local match_exact = function(stritems, inds, query)
|
|
360
|
+
--- local prompt_pattern = vim.pesc(table.concat(query))
|
|
361
|
+
--- local f = function(i) return stritems[i]:find(prompt_pattern) ~= nil end
|
|
362
|
+
--- return vim.tbl_filter(f, inds)
|
|
363
|
+
--- end
|
|
364
|
+
--- -- For non-blocking version see `:h MiniPick.poke_is_picker_active()`
|
|
365
|
+
--- <
|
|
366
|
+
--- Default value is |MiniPick.default_match()|.
|
|
367
|
+
---
|
|
368
|
+
--- # Show ~
|
|
369
|
+
--- *MiniPick-source.show*
|
|
370
|
+
---
|
|
371
|
+
--- `source.show` is a callable defining how matched items are shown in the window.
|
|
372
|
+
---
|
|
373
|
+
--- It will be called with the following arguments:
|
|
374
|
+
--- - `buf_id` - identifier of the target buffer.
|
|
375
|
+
--- - `items_to_show` - array of actual items to be shown in `buf_id`. This is
|
|
376
|
+
--- a subset of currently matched items computed to fit in current window view.
|
|
377
|
+
--- - `query` - array of strings. Same as in `source.match`.
|
|
378
|
+
---
|
|
379
|
+
--- It should update buffer `buf_id` to visually represent `items_to_show`
|
|
380
|
+
--- __one item per line starting from line one__ (it shouldn't depend on
|
|
381
|
+
--- `options.content_from_bottom`). This also includes possible visualization
|
|
382
|
+
--- of which parts of stritem actually matched query.
|
|
383
|
+
---
|
|
384
|
+
--- Example (assuming string items; without highlighting): >lua
|
|
385
|
+
---
|
|
386
|
+
--- local show_prepend = function(buf_id, items_arr, query)
|
|
387
|
+
--- local lines = vim.tbl_map(function(x) return 'Item: ' .. x end, items_arr)
|
|
388
|
+
--- vim.api.nvim_buf_set_lines(buf_id, 0, -1, false, lines)
|
|
389
|
+
--- end
|
|
390
|
+
--- <
|
|
391
|
+
--- Default value is |MiniPick.default_show()|.
|
|
392
|
+
---
|
|
393
|
+
--- # Preview ~
|
|
394
|
+
--- *MiniPick-source.preview*
|
|
395
|
+
---
|
|
396
|
+
--- `source.preview` is a callable defining how item preview is done.
|
|
397
|
+
---
|
|
398
|
+
--- It will be called with the following arguments:
|
|
399
|
+
--- - `buf_id` - identifier of the target buffer. Note: for every separate instance
|
|
400
|
+
--- of item previewing new scratch buffer is be created.
|
|
401
|
+
--- - `item` - item to preview.
|
|
402
|
+
---
|
|
403
|
+
--- It should update buffer `buf_id` to visually represent `item`. It can also
|
|
404
|
+
--- directly set another buffer in picker's main window, but usually it is more
|
|
405
|
+
--- robust to update given `buf_id` directly.
|
|
406
|
+
---
|
|
407
|
+
--- Example: >lua
|
|
408
|
+
---
|
|
409
|
+
--- local preview_inspect = function(buf_id, item)
|
|
410
|
+
--- local lines = vim.split(vim.inspect(item), '\n')
|
|
411
|
+
--- vim.api.nvim_buf_set_lines(buf_id, 0, -1, false, lines)
|
|
412
|
+
--- end
|
|
413
|
+
--- <
|
|
414
|
+
--- Default value is |MiniPick.default_preview()|.
|
|
415
|
+
---
|
|
416
|
+
--- # Choose an item ~
|
|
417
|
+
--- *MiniPick-source.choose*
|
|
418
|
+
---
|
|
419
|
+
--- `source.choose` is a callable defining what to do when an item is chosen.
|
|
420
|
+
---
|
|
421
|
+
--- It will be called with the following arguments:
|
|
422
|
+
--- - `item` - chosen item. Always non-`nil`.
|
|
423
|
+
---
|
|
424
|
+
--- It should perform any intended "choose" action for an item and return
|
|
425
|
+
--- a value indicating whether picker should continue (i.e. not stop):
|
|
426
|
+
--- `nil` and `false` will stop picker, other values will continue.
|
|
427
|
+
---
|
|
428
|
+
--- Notes:
|
|
429
|
+
--- - It is called when picker window is still current. Use `windows.target` value
|
|
430
|
+
--- from |MiniPick.get_picker_state()| output to do something with target window.
|
|
431
|
+
---
|
|
432
|
+
--- Example: >lua
|
|
433
|
+
---
|
|
434
|
+
--- local choose_file_continue = function(item)
|
|
435
|
+
--- if vim.fn.filereadable(item) == 0 then return end
|
|
436
|
+
--- vim.api.nvim_win_call(
|
|
437
|
+
--- MiniPick.get_picker_state().windows.target,
|
|
438
|
+
--- function() vim.cmd('edit ' .. item) end
|
|
439
|
+
--- )
|
|
440
|
+
--- return true
|
|
441
|
+
--- end
|
|
442
|
+
--- <
|
|
443
|
+
--- Default value is |MiniPick.default_choose()|.
|
|
444
|
+
---
|
|
445
|
+
--- # Choose marked items ~
|
|
446
|
+
--- *MiniPick-source.choose_marked*
|
|
447
|
+
---
|
|
448
|
+
--- `source.choose_marked` is a callable defining what to do when marked items
|
|
449
|
+
--- (see |MiniPick-actions-mark|) are chosen. Serves as a companion to
|
|
450
|
+
--- `source.choose` which can choose several items.
|
|
451
|
+
---
|
|
452
|
+
--- It will be called with the following arguments:
|
|
453
|
+
--- - `items_marked` - array of marked items. Can be empty.
|
|
454
|
+
---
|
|
455
|
+
--- It should perform any intended "choose" action for several items and return
|
|
456
|
+
--- a value indicating whether picker should continue (i.e. not stop):
|
|
457
|
+
--- `nil` and `false` will stop picker, other values will continue.
|
|
458
|
+
---
|
|
459
|
+
--- Notes:
|
|
460
|
+
--- - It is called when picker window is still current. Use `windows.target` value
|
|
461
|
+
--- from |MiniPick.get_picker_state()| output to do something with target window.
|
|
462
|
+
---
|
|
463
|
+
--- Example: >lua
|
|
464
|
+
---
|
|
465
|
+
--- local choose_marked_print = function(items) print(vim.inspect(items)) end
|
|
466
|
+
--- <
|
|
467
|
+
--- Default value is |MiniPick.default_choose_marked()|.
|
|
468
|
+
---@tag MiniPick-source
|
|
469
|
+
|
|
470
|
+
--- When picker is active, `mappings` table defines a set of special keys which when
|
|
471
|
+
--- pressed will execute certain actions. Those can be of two types:
|
|
472
|
+
--- - Built-in: actions present in default `config.mappings`. Can be only overridden
|
|
473
|
+
--- with a different key.
|
|
474
|
+
--- - Custom: user defined actions. Should be a table with `char` and `func` fields.
|
|
475
|
+
---
|
|
476
|
+
---
|
|
477
|
+
--- # Built-in ~
|
|
478
|
+
---
|
|
479
|
+
--- ## Caret ~
|
|
480
|
+
--- *MiniPick-actions-caret*
|
|
481
|
+
---
|
|
482
|
+
--- User can add character not only at query end, but more generally at caret.
|
|
483
|
+
---
|
|
484
|
+
--- - `mappings.caret_left` - move caret to left.
|
|
485
|
+
--- - `mappings.caret_right` - move caret to right.
|
|
486
|
+
---
|
|
487
|
+
--- ## Choose ~
|
|
488
|
+
--- *MiniPick-actions-choose*
|
|
489
|
+
---
|
|
490
|
+
--- Choose is a fundamental action that actually implements the intent of
|
|
491
|
+
--- calling a picker, i.e. pick an item.
|
|
492
|
+
---
|
|
493
|
+
--- - `mappings.choose` - choose as is, i.e. apply `source.choose` for current item.
|
|
494
|
+
--- - `mappings.choose_in_split` - make horizontal split at target window, update
|
|
495
|
+
--- target window to the new split, and choose.
|
|
496
|
+
--- - `mappings.choose_in_tabpage` - same as `choose_in_split`, but create tabpage.
|
|
497
|
+
--- - `mappings.choose_in_vsplit` - same as `choose_in_split`, but split vertically.
|
|
498
|
+
--- - `mappings.choose_marked` - choose marked items as is, i.e.
|
|
499
|
+
--- apply `source.choose_marked` at current marked items.
|
|
500
|
+
---
|
|
501
|
+
--- ## Delete ~
|
|
502
|
+
--- *MiniPick-actions-delete*
|
|
503
|
+
---
|
|
504
|
+
--- Delete actions are for deleting elements from query.
|
|
505
|
+
---
|
|
506
|
+
--- - `mappings.delete_char` - delete one character to the left.
|
|
507
|
+
--- - `mappings.delete_char_right` - delete one character to the right.
|
|
508
|
+
--- - `mappings.delete_left` - delete everything to the left (like |i_CTRL-U|).
|
|
509
|
+
--- - `mappings.delete_word` - delete word to the left (like |i_CTRL-W|).
|
|
510
|
+
---
|
|
511
|
+
--- ## Mark ~
|
|
512
|
+
--- *MiniPick-actions-mark*
|
|
513
|
+
---
|
|
514
|
+
--- Marking is an action of adding certain items to a separate list which then can
|
|
515
|
+
--- be chosen with `mappings.choose_marked` (for example, sent to quickfix list).
|
|
516
|
+
--- This is a companion to a regular choosing which can pick only one item.
|
|
517
|
+
---
|
|
518
|
+
--- - `mappings.mark` - toggle marked/unmarked state of current item.
|
|
519
|
+
--- - `mappings.mark_all` - toggle marked/unmarked state (mark all if not all
|
|
520
|
+
--- marked; unmark all otherwise) of all currently matched items.
|
|
521
|
+
---
|
|
522
|
+
--- Notes:
|
|
523
|
+
--- - Marks persist across queries and matches. For example, user can make a query
|
|
524
|
+
--- with marking all matches several times and marked items from all queries
|
|
525
|
+
--- will be preserved.
|
|
526
|
+
---
|
|
527
|
+
--- ## Move ~
|
|
528
|
+
--- *MiniPick-actions-move*
|
|
529
|
+
---
|
|
530
|
+
--- Move is a fundamental action of changing which item is current.
|
|
531
|
+
---
|
|
532
|
+
--- - `mappings.move_down` - change focus to the item below.
|
|
533
|
+
--- - `mappings.move_start` change focus to the first currently matched item.
|
|
534
|
+
--- - `mappings.move_up` - change focus to the item above.
|
|
535
|
+
---
|
|
536
|
+
--- Notes:
|
|
537
|
+
--- - Up and down wrap around edges: `move_down` on last item moves to first,
|
|
538
|
+
--- `move_up` on first moves to last.
|
|
539
|
+
--- - Moving when preview or info view is shown updates the view with new item.
|
|
540
|
+
--- - There are also hard-coded alternative keys (can be used for other actions):
|
|
541
|
+
--- - `<Down>` moves down.
|
|
542
|
+
--- - `<Home>` moves to first currently matched item.
|
|
543
|
+
--- - `<Up>` moves up.
|
|
544
|
+
---
|
|
545
|
+
--- ## Paste ~
|
|
546
|
+
--- *MiniPick-actions-paste*
|
|
547
|
+
---
|
|
548
|
+
--- Paste is an action to paste content of |registers| at caret.
|
|
549
|
+
---
|
|
550
|
+
--- - `mappings.paste` - paste from register defined by the next key press.
|
|
551
|
+
---
|
|
552
|
+
--- Notes:
|
|
553
|
+
--- - Does not support expression register `=`.
|
|
554
|
+
--- - Supports special cases of register: <C-f> (as |c_CTRL-R_CTRL-F|),
|
|
555
|
+
--- <C-w> (as |c_CTRL-R_CTRL-W|), <C-a> (as |c_CTRL-R_CTRL-A|),
|
|
556
|
+
--- <C-l> (as |c_CTRL-R_CTRL-L|).
|
|
557
|
+
--- - Pasting from system |clipboard| is supported for "non-streaming" paste (as
|
|
558
|
+
--- described in |vim.paste()|). Use terminal's key combo instead of this action.
|
|
559
|
+
---
|
|
560
|
+
--- ## Refine ~
|
|
561
|
+
--- *MiniPick-actions-refine*
|
|
562
|
+
---
|
|
563
|
+
--- Refine is an action that primarily executes the following:
|
|
564
|
+
--- - Takes certain items and makes them be all items (in order they are present).
|
|
565
|
+
--- - Resets query.
|
|
566
|
+
--- - Updates `source.match` to be the one from config.
|
|
567
|
+
---
|
|
568
|
+
--- - `mappings.refine` - refine currently matched items.
|
|
569
|
+
--- - `mappings.refine_marked` - refine currently marked items.
|
|
570
|
+
---
|
|
571
|
+
--- This action is useful in at least two cases:
|
|
572
|
+
--- - Perform consecutive "narrowing" queries. Example: to get items that contain
|
|
573
|
+
--- both `hello` and `world` exact matches (in no particular order) with default
|
|
574
|
+
--- matching, type `'hello` (notice `'` at the start) followed by <C-Space> and
|
|
575
|
+
--- another `'world`.
|
|
576
|
+
--- - Reset `match` to default. Particularly useful in |MiniPick.builtin.grep_live()|
|
|
577
|
+
--- and |MiniExtra.pickers.lsp()| with "workspace_symbol_live" scope.
|
|
578
|
+
---
|
|
579
|
+
--- ## Scroll ~
|
|
580
|
+
--- *MiniPick-actions-scroll*
|
|
581
|
+
---
|
|
582
|
+
--- Scroll is an action to either move current item focus further than to the
|
|
583
|
+
--- neighbor item or adjust window view to see more information.
|
|
584
|
+
---
|
|
585
|
+
--- - `mappings.scroll_down` - when matches are shown, go down by the amount of
|
|
586
|
+
--- visible matches. In preview and info view - scroll down as with |CTRL-F|.
|
|
587
|
+
--- - `mappings.scroll_left` - scroll left as with |zH|.
|
|
588
|
+
--- - `mappings.scroll_right` - scroll right as with |zL|.
|
|
589
|
+
--- - `mappings.scroll_up` - when matches are shown, go up by the amount of
|
|
590
|
+
--- visible matches. In preview and info view - scroll up as with |CTRL-B|.
|
|
591
|
+
---
|
|
592
|
+
--- ## Stop ~
|
|
593
|
+
--- *MiniPick-actions-stop*
|
|
594
|
+
---
|
|
595
|
+
--- `mappings.stop` stops the picker. <C-c> also always stops the picker.
|
|
596
|
+
---
|
|
597
|
+
---
|
|
598
|
+
--- ## Toggle ~
|
|
599
|
+
--- *MiniPick-actions-toggle*
|
|
600
|
+
---
|
|
601
|
+
--- Toggle action is a way to change view: show if target is not shown, reset to
|
|
602
|
+
--- main view otherwise.
|
|
603
|
+
---
|
|
604
|
+
--- - `mappings.toggle_info` - toggle info view.
|
|
605
|
+
--- - `mappings.toggle_preview` - toggle preview.
|
|
606
|
+
---
|
|
607
|
+
--- Note:
|
|
608
|
+
--- - Updating query in any way resets window view to show matches.
|
|
609
|
+
--- - Moving current item focus keeps preview or info view with updated item.
|
|
610
|
+
---
|
|
611
|
+
--- # Custom ~
|
|
612
|
+
--- *MiniPick-actions-custom*
|
|
613
|
+
---
|
|
614
|
+
--- Along with built-in actions, users can define custom actions. This can be
|
|
615
|
+
--- done by supplying custom elements to `mappings` table. The field defines action
|
|
616
|
+
--- name (used to infer an action description in info view). The value is a table
|
|
617
|
+
--- with the following fields:
|
|
618
|
+
--- - <char> `(string)` - single character acting as action trigger.
|
|
619
|
+
--- - <func> `(function)` - callable to be executed without arguments after
|
|
620
|
+
--- user presses <char>. Its return value is treated as "should stop picker
|
|
621
|
+
--- after execution", i.e. returning nothing, `nil`, or `false` continues
|
|
622
|
+
--- picker while everything else (prefer `true`) stops it.
|
|
623
|
+
---
|
|
624
|
+
--- Example of `execute` custom mapping: >lua
|
|
625
|
+
---
|
|
626
|
+
--- execute = {
|
|
627
|
+
--- char = '<C-e>',
|
|
628
|
+
--- func = function() vim.cmd(vim.fn.input('Execute: ')) end,
|
|
629
|
+
--- }
|
|
630
|
+
--- <
|
|
631
|
+
---@tag MiniPick-actions
|
|
632
|
+
|
|
633
|
+
--- # Disable icons ~
|
|
634
|
+
---
|
|
635
|
+
--- Disable icons in |MiniPick.builtin| pickers related to paths: >lua
|
|
636
|
+
---
|
|
637
|
+
--- local pick = require('mini.pick')
|
|
638
|
+
--- pick.setup({ source = { show = pick.default_show } })
|
|
639
|
+
--- <
|
|
640
|
+
--- # Switch toggle and move keys ~
|
|
641
|
+
--- >lua
|
|
642
|
+
--- require('mini.pick').setup({
|
|
643
|
+
--- mappings = {
|
|
644
|
+
--- toggle_info = '<C-k>',
|
|
645
|
+
--- toggle_preview = '<C-p>',
|
|
646
|
+
--- move_down = '<Tab>',
|
|
647
|
+
--- move_up = '<S-Tab>',
|
|
648
|
+
--- }
|
|
649
|
+
--- })
|
|
650
|
+
--- <
|
|
651
|
+
--- # Different window styles ~
|
|
652
|
+
--- >lua
|
|
653
|
+
--- -- Different border
|
|
654
|
+
--- { window = { config = { border = 'double' } } }
|
|
655
|
+
---
|
|
656
|
+
--- -- "Cursor tooltip"
|
|
657
|
+
--- {
|
|
658
|
+
--- window = {
|
|
659
|
+
--- config = {
|
|
660
|
+
--- relative = 'cursor', anchor = 'NW',
|
|
661
|
+
--- row = 0, col = 0, width = 40, height = 20,
|
|
662
|
+
--- },
|
|
663
|
+
--- },
|
|
664
|
+
--- }
|
|
665
|
+
---
|
|
666
|
+
--- -- Centered on screen
|
|
667
|
+
--- local win_config = function()
|
|
668
|
+
--- local height = math.floor(0.618 * vim.o.lines)
|
|
669
|
+
--- local width = math.floor(0.618 * vim.o.columns)
|
|
670
|
+
--- return {
|
|
671
|
+
--- anchor = 'NW', height = height, width = width,
|
|
672
|
+
--- row = math.floor(0.5 * (vim.o.lines - height)),
|
|
673
|
+
--- col = math.floor(0.5 * (vim.o.columns - width)),
|
|
674
|
+
--- }
|
|
675
|
+
--- end
|
|
676
|
+
--- { window = { config = win_config } }
|
|
677
|
+
--- <
|
|
678
|
+
---@tag MiniPick-examples
|
|
679
|
+
|
|
680
|
+
---@alias __pick_builtin_opts table|nil Options forwarded to |MiniPick.start()|.
|
|
681
|
+
---@alias __pick_builtin_local_opts table|nil Options defining behavior of this particular picker.
|
|
682
|
+
---@alias __pick_builtin_grep_globs <globs> `(table)` - array of string glob patterns to restrict search to
|
|
683
|
+
--- matching files. Supported only by "rg" and "git" tools, respects their
|
|
684
|
+
--- specific glob syntax and effects. Default: `{}` (no restriction).
|
|
685
|
+
--- Example: `{ '*.lua', 'lua/**' }` for Lua files and files in "lua" directory.
|
|
686
|
+
---@alias __pick_builtin_grep_method <method> `(string)` - pattern matching method: `"regex"` (default) or `"plain"`.
|
|
687
|
+
|
|
688
|
+
---@diagnostic disable:undefined-field
|
|
689
|
+
---@diagnostic disable:discard-returns
|
|
690
|
+
---@diagnostic disable:unused-local
|
|
691
|
+
---@diagnostic disable:cast-local-type
|
|
692
|
+
|
|
693
|
+
-- Module definition ==========================================================
|
|
694
|
+
local MiniPick = {}
|
|
695
|
+
local H = {}
|
|
696
|
+
|
|
697
|
+
--- Module setup
|
|
698
|
+
---
|
|
699
|
+
--- # :Pick ~
|
|
700
|
+
--- *:Pick*
|
|
701
|
+
---
|
|
702
|
+
--- Calling this function creates a `:Pick` user command. It takes picker name
|
|
703
|
+
--- from |MiniPick.registry| as mandatory first argument and executes it with
|
|
704
|
+
--- following (expanded, |expandcmd()|) |<f-args>| combined in a single table.
|
|
705
|
+
--- To add custom pickers, update |MiniPick.registry|.
|
|
706
|
+
---
|
|
707
|
+
--- Example: >vim
|
|
708
|
+
---
|
|
709
|
+
--- :Pick files tool='git'
|
|
710
|
+
--- :Pick grep pattern='<cword>'
|
|
711
|
+
--- <
|
|
712
|
+
---
|
|
713
|
+
--- It also sets custom |vim.ui.select()| implementation to use the module.
|
|
714
|
+
--- See |MiniPick.ui_select()|.
|
|
715
|
+
---
|
|
716
|
+
---@param config table|nil Module config table. See |MiniPick.config|.
|
|
717
|
+
---
|
|
718
|
+
---@usage >lua
|
|
719
|
+
--- require('mini.pick').setup() -- use default config
|
|
720
|
+
--- -- OR
|
|
721
|
+
--- require('mini.pick').setup({}) -- replace {} with your config table
|
|
722
|
+
--- <
|
|
723
|
+
MiniPick.setup = function(config)
|
|
724
|
+
-- TODO: Remove after Neovim=0.9 support is dropped
|
|
725
|
+
if vim.fn.has('nvim-0.10') == 0 then
|
|
726
|
+
vim.notify(
|
|
727
|
+
'(mini.pick) Neovim<0.10 is soft deprecated (module works but is not supported).'
|
|
728
|
+
.. " It will be deprecated after the next 'mini.nvim' release (module might not work)."
|
|
729
|
+
.. ' Please update your Neovim version.'
|
|
730
|
+
)
|
|
731
|
+
end
|
|
732
|
+
|
|
733
|
+
-- Export module
|
|
734
|
+
_G.MiniPick = MiniPick
|
|
735
|
+
|
|
736
|
+
-- Setup config
|
|
737
|
+
config = H.setup_config(config)
|
|
738
|
+
|
|
739
|
+
-- Apply config
|
|
740
|
+
H.apply_config(config)
|
|
741
|
+
|
|
742
|
+
-- Define behavior
|
|
743
|
+
H.create_autocommands()
|
|
744
|
+
|
|
745
|
+
-- Create default highlighting
|
|
746
|
+
H.create_default_hl()
|
|
747
|
+
|
|
748
|
+
-- Create user commands
|
|
749
|
+
H.create_user_commands()
|
|
750
|
+
|
|
751
|
+
-- Adjust terminal emulator's pasting with active picker
|
|
752
|
+
H.adjust_vim_paste()
|
|
753
|
+
|
|
754
|
+
-- Set custom implementation
|
|
755
|
+
vim.ui.select = MiniPick.ui_select
|
|
756
|
+
end
|
|
757
|
+
|
|
758
|
+
--stylua: ignore
|
|
759
|
+
--- Defaults ~
|
|
760
|
+
---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section)
|
|
761
|
+
---@text # Delays ~
|
|
762
|
+
---
|
|
763
|
+
--- `config.delay` defines plugin delays (in ms). All should be strictly positive.
|
|
764
|
+
---
|
|
765
|
+
--- `delay.async` is a delay between forcing asynchronous behavior. This usually
|
|
766
|
+
--- means forcing |:redraw| in preview (several but limited number of times to
|
|
767
|
+
--- ensure visible async highlighting) and using |MiniPick.poke_is_picker_active()|
|
|
768
|
+
--- (for example, to stop current matching if query has updated).
|
|
769
|
+
--- Smaller values give smoother user experience at the cost of more computations.
|
|
770
|
+
---
|
|
771
|
+
--- `delay.busy` is a delay between when some computation starts and showing
|
|
772
|
+
--- visual feedback about it by making window border to have `MiniPickBorderBusy`
|
|
773
|
+
--- highlight group.
|
|
774
|
+
--- Smaller values will give feedback faster at the cost of feeling like flicker.
|
|
775
|
+
---
|
|
776
|
+
--- # Mappings ~
|
|
777
|
+
---
|
|
778
|
+
--- `config.mappings` defines keys for special actions to be triggered after certain
|
|
779
|
+
--- keys. See |MiniPick-actions| for more information.
|
|
780
|
+
---
|
|
781
|
+
--- # Options ~
|
|
782
|
+
---
|
|
783
|
+
--- `config.options` contains some general purpose options.
|
|
784
|
+
---
|
|
785
|
+
--- `options.content_from_bottom` is a boolean indicating whether content should be
|
|
786
|
+
--- shown from bottom to top. That means that best matches will be shown at
|
|
787
|
+
--- the bottom. Note: for better experience use Neovim>=0.10, which has floating
|
|
788
|
+
--- window footer capability. Default: `false`.
|
|
789
|
+
---
|
|
790
|
+
--- `options.use_cache` is a boolean indicating whether match results should be
|
|
791
|
+
--- cached per prompt (i.e. concatenated query). This results into faster response
|
|
792
|
+
--- on repeated prompts (like when deleting query entries) at the cost of using
|
|
793
|
+
--- more memory. Default: `false`.
|
|
794
|
+
---
|
|
795
|
+
--- # Source ~
|
|
796
|
+
---
|
|
797
|
+
--- `config.source` defines fallbacks for source specification. For example, this
|
|
798
|
+
--- can be used to change default `match` to use different implementation or `show`
|
|
799
|
+
--- to not show icons for some |MiniPick.builtin| pickers (see |MiniPick-examples|).
|
|
800
|
+
--- See |MiniPick-source| for more information.
|
|
801
|
+
---
|
|
802
|
+
--- # Window ~
|
|
803
|
+
---
|
|
804
|
+
--- `config.window` contains window specific configurations.
|
|
805
|
+
---
|
|
806
|
+
--- `window.config` defines a (parts of) default floating window config for the main
|
|
807
|
+
--- picker window. This can be either a table overriding some parts or a callable
|
|
808
|
+
--- returning such table. See |MiniPick-examples| for some examples.
|
|
809
|
+
---
|
|
810
|
+
--- `window.prompt_caret` defines how caret is displayed in window's prompt.
|
|
811
|
+
--- Default: `'▏'`.
|
|
812
|
+
---
|
|
813
|
+
--- `window.prompt_prefix` defines what prefix is used in window's prompt.
|
|
814
|
+
--- Default: `'> '`.
|
|
815
|
+
MiniPick.config = {
|
|
816
|
+
-- Delays (in ms; should be at least 1)
|
|
817
|
+
delay = {
|
|
818
|
+
-- Delay between forcing asynchronous behavior
|
|
819
|
+
async = 10,
|
|
820
|
+
|
|
821
|
+
-- Delay between computation start and visual feedback about it
|
|
822
|
+
busy = 50,
|
|
823
|
+
},
|
|
824
|
+
|
|
825
|
+
-- Keys for performing actions. See `:h MiniPick-actions`.
|
|
826
|
+
mappings = {
|
|
827
|
+
caret_left = '<Left>',
|
|
828
|
+
caret_right = '<Right>',
|
|
829
|
+
|
|
830
|
+
choose = '<CR>',
|
|
831
|
+
choose_in_split = '<C-s>',
|
|
832
|
+
choose_in_tabpage = '<C-t>',
|
|
833
|
+
choose_in_vsplit = '<C-v>',
|
|
834
|
+
choose_marked = '<M-CR>',
|
|
835
|
+
|
|
836
|
+
delete_char = '<BS>',
|
|
837
|
+
delete_char_right = '<Del>',
|
|
838
|
+
delete_left = '<C-u>',
|
|
839
|
+
delete_word = '<C-w>',
|
|
840
|
+
|
|
841
|
+
mark = '<C-x>',
|
|
842
|
+
mark_all = '<C-a>',
|
|
843
|
+
|
|
844
|
+
move_down = '<C-n>',
|
|
845
|
+
move_start = '<C-g>',
|
|
846
|
+
move_up = '<C-p>',
|
|
847
|
+
|
|
848
|
+
paste = '<C-r>',
|
|
849
|
+
|
|
850
|
+
refine = '<C-Space>',
|
|
851
|
+
refine_marked = '<M-Space>',
|
|
852
|
+
|
|
853
|
+
scroll_down = '<C-f>',
|
|
854
|
+
scroll_left = '<C-h>',
|
|
855
|
+
scroll_right = '<C-l>',
|
|
856
|
+
scroll_up = '<C-b>',
|
|
857
|
+
|
|
858
|
+
stop = '<Esc>',
|
|
859
|
+
|
|
860
|
+
toggle_info = '<S-Tab>',
|
|
861
|
+
toggle_preview = '<Tab>',
|
|
862
|
+
},
|
|
863
|
+
|
|
864
|
+
-- General options
|
|
865
|
+
options = {
|
|
866
|
+
-- Whether to show content from bottom to top
|
|
867
|
+
content_from_bottom = false,
|
|
868
|
+
|
|
869
|
+
-- Whether to cache matches (more speed and memory on repeated prompts)
|
|
870
|
+
use_cache = false,
|
|
871
|
+
},
|
|
872
|
+
|
|
873
|
+
-- Source definition. See `:h MiniPick-source`.
|
|
874
|
+
source = {
|
|
875
|
+
items = nil,
|
|
876
|
+
name = nil,
|
|
877
|
+
cwd = nil,
|
|
878
|
+
|
|
879
|
+
match = nil,
|
|
880
|
+
show = nil,
|
|
881
|
+
preview = nil,
|
|
882
|
+
|
|
883
|
+
choose = nil,
|
|
884
|
+
choose_marked = nil,
|
|
885
|
+
},
|
|
886
|
+
|
|
887
|
+
-- Window related options
|
|
888
|
+
window = {
|
|
889
|
+
-- Float window config (table or callable returning it)
|
|
890
|
+
config = nil,
|
|
891
|
+
|
|
892
|
+
-- String to use as caret in prompt
|
|
893
|
+
prompt_caret = '▏',
|
|
894
|
+
|
|
895
|
+
-- String to use as prefix in prompt
|
|
896
|
+
prompt_prefix = '> ',
|
|
897
|
+
},
|
|
898
|
+
}
|
|
899
|
+
--minidoc_afterlines_end
|
|
900
|
+
|
|
901
|
+
--- Start picker
|
|
902
|
+
---
|
|
903
|
+
--- Notes:
|
|
904
|
+
--- - If there is currently an active picker, it is properly stopped and new one
|
|
905
|
+
--- is started "soon" in the main event-loop (see |vim.schedule()|).
|
|
906
|
+
--- - Current window at the moment of this function call is treated as "target".
|
|
907
|
+
--- It will be set back as current after |MiniPick.stop()|.
|
|
908
|
+
--- See |MiniPick.get_picker_state()| and |MiniPick.set_picker_target_window()|.
|
|
909
|
+
---
|
|
910
|
+
---@param opts table|nil Options. Should have same structure as |MiniPick.config|.
|
|
911
|
+
--- Default values are inferred from there.
|
|
912
|
+
--- Usually should have proper |MiniPick-source.items| defined.
|
|
913
|
+
---
|
|
914
|
+
---@return any Item which was current when picker is stopped; `nil` if aborted.
|
|
915
|
+
MiniPick.start = function(opts)
|
|
916
|
+
if MiniPick.is_picker_active() then
|
|
917
|
+
-- Try proper 'key query process' stop
|
|
918
|
+
MiniPick.stop()
|
|
919
|
+
-- NOTE: Needs `defer_fn()` for `stop()` to properly finish code flow and
|
|
920
|
+
-- not be executed before it
|
|
921
|
+
return vim.defer_fn(function()
|
|
922
|
+
-- NOTE: if `MiniPick.stop()` still didn't stop, force abort
|
|
923
|
+
if MiniPick.is_picker_active() then H.picker_stop(H.pickers.active, true) end
|
|
924
|
+
MiniPick.start(opts)
|
|
925
|
+
end, 0.5)
|
|
926
|
+
end
|
|
927
|
+
|
|
928
|
+
if vim.fn.getcmdwintype() ~= '' then return H.notify('Can not open a picker with active command window', 'WARN') end
|
|
929
|
+
|
|
930
|
+
H.cache = {}
|
|
931
|
+
opts = H.validate_picker_opts(opts)
|
|
932
|
+
local picker = H.picker_new(opts)
|
|
933
|
+
H.pickers.active = picker
|
|
934
|
+
|
|
935
|
+
H.picker_set_busy(picker, true)
|
|
936
|
+
local items = H.expand_callable(opts.source.items)
|
|
937
|
+
-- - Set items on next event loop to not block when computing stritems
|
|
938
|
+
if H.islist(items) then vim.schedule(function() MiniPick.set_picker_items(items) end) end
|
|
939
|
+
|
|
940
|
+
H.picker_track_lost_focus(picker)
|
|
941
|
+
return H.picker_advance(picker)
|
|
942
|
+
end
|
|
943
|
+
|
|
944
|
+
--- Stop active picker
|
|
945
|
+
MiniPick.stop = function()
|
|
946
|
+
if not MiniPick.is_picker_active() then return end
|
|
947
|
+
H.cache.is_force_stop_advance = true
|
|
948
|
+
if H.cache.is_in_getcharstr then vim.api.nvim_feedkeys('\3', 't', true) end
|
|
949
|
+
end
|
|
950
|
+
|
|
951
|
+
--- Refresh active picker
|
|
952
|
+
MiniPick.refresh = function()
|
|
953
|
+
if not MiniPick.is_picker_active() then return end
|
|
954
|
+
H.picker_update(H.pickers.active, false, true)
|
|
955
|
+
-- Needed for something like `VimResized` (as `getcharstr` blocks redraw)
|
|
956
|
+
H.ensure_redraw(H.pickers.active)
|
|
957
|
+
end
|
|
958
|
+
|
|
959
|
+
--- Default match
|
|
960
|
+
---
|
|
961
|
+
--- Filter target stritems to contain query and sort from best to worst matches.
|
|
962
|
+
---
|
|
963
|
+
--- Implements default value for |MiniPick-source.match|.
|
|
964
|
+
---
|
|
965
|
+
--- By default (if no special modes apply) it does the following fuzzy matching:
|
|
966
|
+
---
|
|
967
|
+
--- - Stritem contains query if it contains all its elements verbatim in the same
|
|
968
|
+
--- order (possibly with gaps, i.e. not strictly one after another).
|
|
969
|
+
--- Note: empty query and empty string element is contained in any string.
|
|
970
|
+
---
|
|
971
|
+
--- - Sorting is done with the following ordering (same as in |mini.fuzzy|):
|
|
972
|
+
--- - The smaller the match width (end column minus start column) the better.
|
|
973
|
+
--- - Among same match width, the smaller start column the better.
|
|
974
|
+
--- - Among same match width and start column, preserve original order.
|
|
975
|
+
---
|
|
976
|
+
--- Notes:
|
|
977
|
+
--- - Most common interactive usage results into `query` containing one typed
|
|
978
|
+
--- character per element.
|
|
979
|
+
---
|
|
980
|
+
--- # Special modes ~
|
|
981
|
+
---
|
|
982
|
+
--- - Forced modes:
|
|
983
|
+
--- - Query starts with `*`: match the rest fuzzy (without other modes).
|
|
984
|
+
--- - Query starts with `'`: match the rest exactly (without gaps).
|
|
985
|
+
---
|
|
986
|
+
--- - Place modes:
|
|
987
|
+
--- - Query starts with `^`: match the rest exactly at start.
|
|
988
|
+
--- - Query ends with `$`: match the rest exactly at end.
|
|
989
|
+
--- - Both modes can be used simultaneously.
|
|
990
|
+
---
|
|
991
|
+
--- - Grouped: query contains at least one whitespace element. Output is computed
|
|
992
|
+
--- as if query is split at whitespace indexes with concatenation between them.
|
|
993
|
+
---
|
|
994
|
+
--- Precedence of modes:
|
|
995
|
+
--- "forced exact" = "forced fuzzy" > "place start/end" > "grouped" > "default"
|
|
996
|
+
---
|
|
997
|
+
--- # Examples ~
|
|
998
|
+
---
|
|
999
|
+
--- Assuming `stritems` are `{ '_abc', 'a_bc', 'ab_c', 'abc_' }`, here are some
|
|
1000
|
+
--- example matches based on prompt (concatenated query): >
|
|
1001
|
+
---
|
|
1002
|
+
--- | Prompt | Matches |
|
|
1003
|
+
--- |--------|------------------------|
|
|
1004
|
+
--- | abc | All |
|
|
1005
|
+
--- | *abc | All |
|
|
1006
|
+
--- | | |
|
|
1007
|
+
--- | 'abc | abc_, _abc |
|
|
1008
|
+
--- | *'abc | None (no "'" in items) |
|
|
1009
|
+
--- | | |
|
|
1010
|
+
--- | ^abc | abc_ |
|
|
1011
|
+
--- | *^abc | None (no "^" in items) |
|
|
1012
|
+
--- | | |
|
|
1013
|
+
--- | abc$ | _abc |
|
|
1014
|
+
--- | *abc$ | None (no "$" in items) |
|
|
1015
|
+
--- | | |
|
|
1016
|
+
--- | ab c | abc_, _abc, ab_c |
|
|
1017
|
+
--- | *ab c | None (no " " in items) |
|
|
1018
|
+
--- <
|
|
1019
|
+
--- Having query `{ 'ab', 'c' }` is the same as "ab c" prompt.
|
|
1020
|
+
---
|
|
1021
|
+
--- You can have a feel of how this works with this command: >lua
|
|
1022
|
+
---
|
|
1023
|
+
--- MiniPick.start({ source = { items = { '_abc', 'a_bc', 'ab_c', 'abc_' } } })
|
|
1024
|
+
--- <
|
|
1025
|
+
---@param stritems table Array of all stritems.
|
|
1026
|
+
---@param inds table Array of `stritems` indexes to match. All of them should point
|
|
1027
|
+
--- at string elements of `stritems`. No check is done for performance reasons.
|
|
1028
|
+
---@param query table Array of strings.
|
|
1029
|
+
---@param opts table|nil Options. Possible fields:
|
|
1030
|
+
--- - <sync> `(boolean)` - Whether to match synchronously. Default: `false`.
|
|
1031
|
+
--- - <preserve_order> `(boolean)` - Whether to skip sort step. Default: `false`.
|
|
1032
|
+
---
|
|
1033
|
+
---@return table|nil Depending on whether computation is synchronous (either `opts.sync`
|
|
1034
|
+
--- is `true` or there is an active picker):
|
|
1035
|
+
--- - If yes, array of `stritems` indexes matching the `query` (from best to worst).
|
|
1036
|
+
--- - If no, `nil` is returned with |MiniPick.set_picker_match_inds()| used later.
|
|
1037
|
+
MiniPick.default_match = function(stritems, inds, query, opts)
|
|
1038
|
+
opts = opts or {}
|
|
1039
|
+
local is_sync = opts.sync or not MiniPick.is_picker_active()
|
|
1040
|
+
local set_match_inds = is_sync and function(x) return x end or MiniPick.set_picker_match_inds
|
|
1041
|
+
local f = function()
|
|
1042
|
+
if #query == 0 then return set_match_inds(H.seq_along(stritems)) end
|
|
1043
|
+
local match_data, match_type = H.match_filter(inds, stritems, query)
|
|
1044
|
+
if match_data == nil then return end
|
|
1045
|
+
if match_type == 'useall' then return set_match_inds(H.seq_along(stritems)) end
|
|
1046
|
+
if opts.preserve_order then return set_match_inds(H.match_no_sort(match_data)) end
|
|
1047
|
+
local match_inds = H.match_sort(match_data)
|
|
1048
|
+
if match_inds == nil then return end
|
|
1049
|
+
return set_match_inds(match_inds)
|
|
1050
|
+
end
|
|
1051
|
+
|
|
1052
|
+
if is_sync then return f() end
|
|
1053
|
+
coroutine.resume(coroutine.create(f))
|
|
1054
|
+
end
|
|
1055
|
+
|
|
1056
|
+
--- Default show
|
|
1057
|
+
---
|
|
1058
|
+
--- Show items in a buffer and highlight parts that actually match query (assuming
|
|
1059
|
+
--- match is done with |MiniPick.default_match()|). Lines are computed based on
|
|
1060
|
+
--- the |MiniPick-source.items-stritems|.
|
|
1061
|
+
---
|
|
1062
|
+
--- Implements default value for |MiniPick-source.show|.
|
|
1063
|
+
---
|
|
1064
|
+
--- Uses the following highlight groups (see |mini.pick| for their description):
|
|
1065
|
+
---
|
|
1066
|
+
--- - `MiniPickIconDirectory`
|
|
1067
|
+
--- - `MiniPickIconFile`
|
|
1068
|
+
--- - `MiniPickMatchCurrent`
|
|
1069
|
+
--- - `MiniPickMatchMarked`
|
|
1070
|
+
--- - `MiniPickMatchRanges`
|
|
1071
|
+
---
|
|
1072
|
+
---@param buf_id number Identifier of target buffer.
|
|
1073
|
+
---@param items table Array of items to show.
|
|
1074
|
+
---@param query table Array of strings representing query.
|
|
1075
|
+
---@param opts table|nil Options. Possible fields:
|
|
1076
|
+
--- - <show_icons> `(boolean)` - whether to show icons for entries recognized as
|
|
1077
|
+
--- valid actually present paths on disk (see |MiniPick-source.items-common|),
|
|
1078
|
+
--- empty space otherwise. Tries to use `text` field as fallback for path.
|
|
1079
|
+
--- Default: `false`. Note: |MiniPick.builtin| pickers showing file/directory
|
|
1080
|
+
--- paths use `true` by default.
|
|
1081
|
+
--- - <icons> `(table)` - table with fallback icons used if icon provider
|
|
1082
|
+
--- does not itself supply default icons for category. Can have fields:
|
|
1083
|
+
--- - <directory> `(string)` - icon for directory. Default: " ".
|
|
1084
|
+
--- - <file> `(string)` - icon for file. Default: " ".
|
|
1085
|
+
--- - <none> `(string)` - icon for non-valid path. Default: " ".
|
|
1086
|
+
MiniPick.default_show = function(buf_id, items, query, opts)
|
|
1087
|
+
local default_icons = { directory = ' ', file = ' ', none = ' ' }
|
|
1088
|
+
opts = vim.tbl_deep_extend('force', { show_icons = false, icons = default_icons }, opts or {})
|
|
1089
|
+
|
|
1090
|
+
-- Compute and set lines. Compute prefix based on the whole items to allow
|
|
1091
|
+
-- separate `text` and `path` table fields (preferring second one).
|
|
1092
|
+
local get_prefix_data = opts.show_icons and function(item) return H.get_icon(item, opts.icons) end
|
|
1093
|
+
or function() return { text = '' } end
|
|
1094
|
+
local prefix_data = vim.tbl_map(get_prefix_data, items)
|
|
1095
|
+
|
|
1096
|
+
local lines = vim.tbl_map(H.item_to_string, items)
|
|
1097
|
+
local tab_spaces = string.rep(' ', vim.o.tabstop)
|
|
1098
|
+
lines = vim.tbl_map(function(l) return l:gsub('%z', '│'):gsub('[\r\n]', ' '):gsub('\t', tab_spaces) end, lines)
|
|
1099
|
+
|
|
1100
|
+
local lines_to_show = {}
|
|
1101
|
+
for i, l in ipairs(lines) do
|
|
1102
|
+
lines_to_show[i] = prefix_data[i].text .. l
|
|
1103
|
+
end
|
|
1104
|
+
|
|
1105
|
+
H.set_buflines(buf_id, lines_to_show)
|
|
1106
|
+
|
|
1107
|
+
-- Extract match ranges
|
|
1108
|
+
local ns_id = H.ns_id.ranges
|
|
1109
|
+
H.clear_namespace(buf_id, ns_id)
|
|
1110
|
+
|
|
1111
|
+
if H.query_is_ignorecase(query) then
|
|
1112
|
+
lines, query = vim.tbl_map(H.tolower, lines), vim.tbl_map(H.tolower, query)
|
|
1113
|
+
end
|
|
1114
|
+
local match_data, match_type, query_adjusted = H.match_filter(H.seq_along(lines), lines, query)
|
|
1115
|
+
if match_data == nil then return end
|
|
1116
|
+
|
|
1117
|
+
local match_ranges_fun = match_type == 'fuzzy' and H.match_ranges_fuzzy or H.match_ranges_exact
|
|
1118
|
+
local match_ranges = match_ranges_fun(match_data, query_adjusted, lines)
|
|
1119
|
+
|
|
1120
|
+
-- Place range highlights accounting for possible shift due to prefixes
|
|
1121
|
+
local extmark_opts = { hl_group = 'MiniPickMatchRanges', hl_mode = 'combine', priority = 200 }
|
|
1122
|
+
for i = 1, #match_data do
|
|
1123
|
+
local row, ranges = match_data[i][3], match_ranges[i]
|
|
1124
|
+
local start_offset = prefix_data[row].text:len()
|
|
1125
|
+
for _, range in ipairs(ranges) do
|
|
1126
|
+
extmark_opts.end_row, extmark_opts.end_col = row - 1, start_offset + range[2]
|
|
1127
|
+
H.set_extmark(buf_id, ns_id, row - 1, start_offset + range[1] - 1, extmark_opts)
|
|
1128
|
+
end
|
|
1129
|
+
end
|
|
1130
|
+
|
|
1131
|
+
-- Highlight prefixes
|
|
1132
|
+
if not opts.show_icons then return end
|
|
1133
|
+
local icon_extmark_opts = { hl_mode = 'combine', priority = 200 }
|
|
1134
|
+
for i = 1, #prefix_data do
|
|
1135
|
+
icon_extmark_opts.hl_group = prefix_data[i].hl
|
|
1136
|
+
icon_extmark_opts.end_row, icon_extmark_opts.end_col = i - 1, prefix_data[i].text:len()
|
|
1137
|
+
H.set_extmark(buf_id, ns_id, i - 1, 0, icon_extmark_opts)
|
|
1138
|
+
end
|
|
1139
|
+
end
|
|
1140
|
+
|
|
1141
|
+
--- Default preview
|
|
1142
|
+
---
|
|
1143
|
+
--- Preview item. Logic follows the rules in |MiniPick-source.items-common|:
|
|
1144
|
+
--- - File and buffer are shown at the start.
|
|
1145
|
+
--- - Directory has its content listed.
|
|
1146
|
+
--- - Line/position/region in file or buffer is shown at start.
|
|
1147
|
+
--- - Others are shown directly with |vim.inspect()|.
|
|
1148
|
+
---
|
|
1149
|
+
--- Implements default value for |MiniPick-source.preview|.
|
|
1150
|
+
---
|
|
1151
|
+
--- Uses the following highlight groups (see |mini.pick| for their description):
|
|
1152
|
+
---
|
|
1153
|
+
--- - `MiniPickPreviewLine`
|
|
1154
|
+
--- - `MiniPickPreviewRegion`
|
|
1155
|
+
---
|
|
1156
|
+
---@param buf_id number Identifier of target buffer.
|
|
1157
|
+
---@param item any Item to preview.
|
|
1158
|
+
---@param opts table|nil Options. Possible values:
|
|
1159
|
+
--- - <n_context_lines> `(number)` - number of lines to load past target position
|
|
1160
|
+
--- when reading from disk. Useful to explore context. Default: |'lines'| twice.
|
|
1161
|
+
--- - <line_position> `(string)` - where in the window to show item position.
|
|
1162
|
+
--- One of "top", "center", "bottom". Default: "top".
|
|
1163
|
+
MiniPick.default_preview = function(buf_id, item, opts)
|
|
1164
|
+
opts = vim.tbl_deep_extend('force', { n_context_lines = 2 * vim.o.lines, line_position = 'top' }, opts or {})
|
|
1165
|
+
local item_data = H.parse_item(item)
|
|
1166
|
+
if item_data.type == 'file' then return H.preview_file(buf_id, item_data, opts) end
|
|
1167
|
+
if item_data.type == 'directory' then return H.preview_directory(buf_id, item_data) end
|
|
1168
|
+
if item_data.type == 'buffer' then return H.preview_buffer(buf_id, item_data, opts) end
|
|
1169
|
+
if item_data.type == 'uri' then return H.preview_uri(buf_id, item_data, opts) end
|
|
1170
|
+
H.preview_inspect(buf_id, item)
|
|
1171
|
+
end
|
|
1172
|
+
|
|
1173
|
+
--- Default choose
|
|
1174
|
+
---
|
|
1175
|
+
--- Choose item. Logic follows the rules in |MiniPick-source.items-common|:
|
|
1176
|
+
--- - File uses |bufadd()| and sets cursor at the start of line/position/region.
|
|
1177
|
+
--- - Buffer is set as current in target window and sets cursor similarly.
|
|
1178
|
+
--- - Directory is called with |:edit| in the target window.
|
|
1179
|
+
--- - Others have the output of |vim.inspect()| printed in Command line.
|
|
1180
|
+
---
|
|
1181
|
+
--- Implements default value for |MiniPick-source.choose|.
|
|
1182
|
+
---
|
|
1183
|
+
---@param item any Item to choose.
|
|
1184
|
+
MiniPick.default_choose = function(item)
|
|
1185
|
+
if item == nil then return end
|
|
1186
|
+
local picker_state = MiniPick.get_picker_state()
|
|
1187
|
+
local win_target = picker_state ~= nil and picker_state.windows.target or vim.api.nvim_get_current_win()
|
|
1188
|
+
if not H.is_valid_win(win_target) then win_target = H.get_first_valid_normal_window() end
|
|
1189
|
+
|
|
1190
|
+
local item_data = H.parse_item(item)
|
|
1191
|
+
if item_data.type == 'file' or item_data.type == 'directory' or item_data.type == 'uri' then
|
|
1192
|
+
return H.choose_path(win_target, item_data)
|
|
1193
|
+
end
|
|
1194
|
+
if item_data.type == 'buffer' then return H.choose_buffer(win_target, item_data) end
|
|
1195
|
+
H.choose_print(item)
|
|
1196
|
+
end
|
|
1197
|
+
|
|
1198
|
+
--- Default choose marked items
|
|
1199
|
+
---
|
|
1200
|
+
--- Choose marked items. Logic follows the rules in |MiniPick-source.items-common|:
|
|
1201
|
+
--- - If among items there is at least one file or buffer, quickfix list is opened
|
|
1202
|
+
--- with all file or buffer lines/positions/regions.
|
|
1203
|
+
--- - Otherwise, picker's `source.choose` is called on the first item.
|
|
1204
|
+
---
|
|
1205
|
+
--- Implements default value for |MiniPick-source.choose_marked|.
|
|
1206
|
+
---
|
|
1207
|
+
---@param items table Array of items to choose.
|
|
1208
|
+
---@param opts table|nil Options. Possible fields:
|
|
1209
|
+
--- - <list_type> `(string)` - which type of list to open. One of "quickfix"
|
|
1210
|
+
--- or "location". Default: "quickfix".
|
|
1211
|
+
MiniPick.default_choose_marked = function(items, opts)
|
|
1212
|
+
if not H.islist(items) then H.error('`items` should be an array') end
|
|
1213
|
+
if #items == 0 then return end
|
|
1214
|
+
opts = vim.tbl_deep_extend('force', { list_type = 'quickfix' }, opts or {})
|
|
1215
|
+
|
|
1216
|
+
-- Construct a potential quickfix/location list
|
|
1217
|
+
local list = {}
|
|
1218
|
+
for _, item in ipairs(items) do
|
|
1219
|
+
local item_data = H.parse_item(item)
|
|
1220
|
+
if item_data.type == 'file' or item_data.type == 'buffer' or item_data.type == 'uri' then
|
|
1221
|
+
local entry = { bufnr = item_data.buf_id, filename = H.parse_uri(item_data.path) or item_data.path }
|
|
1222
|
+
entry.lnum, entry.col = item_data.lnum or 1, item_data.col or 1
|
|
1223
|
+
entry.text = (item_data.text or ''):gsub('%z', '│')
|
|
1224
|
+
entry.end_lnum, entry.end_col = item_data.end_lnum, item_data.end_col
|
|
1225
|
+
table.insert(list, entry)
|
|
1226
|
+
end
|
|
1227
|
+
end
|
|
1228
|
+
|
|
1229
|
+
-- Fall back to choosing first item if no quickfix list was constructed
|
|
1230
|
+
local is_active = MiniPick.is_picker_active()
|
|
1231
|
+
if #list == 0 then
|
|
1232
|
+
if not is_active then return end
|
|
1233
|
+
local choose = MiniPick.get_picker_opts().source.choose
|
|
1234
|
+
return choose(items[1])
|
|
1235
|
+
end
|
|
1236
|
+
|
|
1237
|
+
-- Set as quickfix or location list
|
|
1238
|
+
local title = '<No picker>'
|
|
1239
|
+
if is_active then
|
|
1240
|
+
---@diagnostic disable:param-type-mismatch
|
|
1241
|
+
local source_name, prompt = MiniPick.get_picker_opts().source.name, table.concat(MiniPick.get_picker_query())
|
|
1242
|
+
title = source_name .. (prompt == '' and '' or (' : ' .. prompt))
|
|
1243
|
+
end
|
|
1244
|
+
local list_data = { items = list, title = title, nr = '$' }
|
|
1245
|
+
|
|
1246
|
+
if opts.list_type == 'location' then
|
|
1247
|
+
local win_target = MiniPick.get_picker_state().windows.target
|
|
1248
|
+
if not H.is_valid_win(win_target) then win_target = H.get_first_valid_normal_window() end
|
|
1249
|
+
vim.fn.setloclist(win_target, {}, ' ', list_data)
|
|
1250
|
+
vim.schedule(function() vim.cmd('lopen') end)
|
|
1251
|
+
else
|
|
1252
|
+
vim.fn.setqflist({}, ' ', list_data)
|
|
1253
|
+
vim.schedule(function() vim.cmd('copen') end)
|
|
1254
|
+
end
|
|
1255
|
+
end
|
|
1256
|
+
|
|
1257
|
+
--- Select rewrite
|
|
1258
|
+
---
|
|
1259
|
+
--- Function which can be used to directly override |vim.ui.select()| to use
|
|
1260
|
+
--- |mini.pick| for any "select" type of tasks.
|
|
1261
|
+
--- Set automatically in |MiniPick.setup()|.
|
|
1262
|
+
---
|
|
1263
|
+
--- Implements required by `vim.ui.select()` signature, with some differences:
|
|
1264
|
+
--- - Allows `opts.preview_item` that returns one of:
|
|
1265
|
+
--- - Array of lines for item preview.
|
|
1266
|
+
--- - Preview data described in |vim.ui.select()| (on Neovim>=0.12.3).
|
|
1267
|
+
--- - Allows fourth `start_opts` argument to customize |MiniPick.start()| call.
|
|
1268
|
+
---
|
|
1269
|
+
--- Notes:
|
|
1270
|
+
--- - `on_choice` with selected item is called when target window is current.
|
|
1271
|
+
--- - If a picker is active, start a new one after the current one is stopped.
|
|
1272
|
+
---
|
|
1273
|
+
---@usage >lua
|
|
1274
|
+
--- -- Customize with fourth argument inside a function wrapper
|
|
1275
|
+
--- vim.ui.select = function(items, opts, on_choice)
|
|
1276
|
+
--- local start_opts = { window = { config = { width = vim.o.columns } } }
|
|
1277
|
+
--- return MiniPick.ui_select(items, opts, on_choice, start_opts)
|
|
1278
|
+
--- end
|
|
1279
|
+
--- <
|
|
1280
|
+
--- To preserve original `vim.ui.select()`: >lua
|
|
1281
|
+
---
|
|
1282
|
+
--- local ui_select_orig = vim.ui.select
|
|
1283
|
+
--- require('mini.pick').setup()
|
|
1284
|
+
--- vim.ui.select = ui_select_orig
|
|
1285
|
+
--- <
|
|
1286
|
+
MiniPick.ui_select = function(items, opts, on_choice, start_opts)
|
|
1287
|
+
if MiniPick.is_picker_active() then
|
|
1288
|
+
local cb = vim.schedule_wrap(function() MiniPick.ui_select(items, opts, on_choice, start_opts) end)
|
|
1289
|
+
vim.api.nvim_create_autocmd('User', { pattern = 'MiniPickStop', once = true, callback = cb })
|
|
1290
|
+
return
|
|
1291
|
+
end
|
|
1292
|
+
|
|
1293
|
+
local format_item = opts.format_item or H.item_to_string
|
|
1294
|
+
local items_ext = {}
|
|
1295
|
+
for i = 1, #items do
|
|
1296
|
+
table.insert(items_ext, { text = format_item(items[i]), item = items[i], index = i })
|
|
1297
|
+
end
|
|
1298
|
+
|
|
1299
|
+
local preview_item = vim.is_callable(opts.preview_item) and opts.preview_item
|
|
1300
|
+
or function(x) return vim.split(vim.inspect(x), '\n') end
|
|
1301
|
+
local preview = function(buf_id, item)
|
|
1302
|
+
local data = preview_item(item.item)
|
|
1303
|
+
if H.islist(data) then return H.set_buflines(buf_id, data) end
|
|
1304
|
+
if not (type(data) == 'table' and H.is_valid_buf(data.buf)) then return end
|
|
1305
|
+
vim.api.nvim_buf_call(buf_id, function()
|
|
1306
|
+
vim.api.nvim_set_current_buf(data.buf)
|
|
1307
|
+
pcall(vim.api.nvim_win_set_cursor, 0, data.pos)
|
|
1308
|
+
vim.cmd('normal! zt')
|
|
1309
|
+
end)
|
|
1310
|
+
end
|
|
1311
|
+
|
|
1312
|
+
local choose = function(item)
|
|
1313
|
+
local win_target = MiniPick.get_picker_state().windows.target
|
|
1314
|
+
if not H.is_valid_win(win_target) then win_target = H.get_first_valid_normal_window() end
|
|
1315
|
+
vim.api.nvim_win_call(win_target, function()
|
|
1316
|
+
on_choice(items[item.index], item.index)
|
|
1317
|
+
MiniPick.set_picker_target_window(vim.api.nvim_get_current_win())
|
|
1318
|
+
end)
|
|
1319
|
+
end
|
|
1320
|
+
|
|
1321
|
+
local source = { items = items_ext, name = opts.prompt or opts.kind, preview = preview, choose = choose }
|
|
1322
|
+
start_opts = vim.tbl_deep_extend('force', start_opts or {}, { source = source })
|
|
1323
|
+
local item = MiniPick.start(start_opts)
|
|
1324
|
+
if item == nil then on_choice(nil) end
|
|
1325
|
+
end
|
|
1326
|
+
|
|
1327
|
+
--- Table with built-in pickers
|
|
1328
|
+
MiniPick.builtin = {}
|
|
1329
|
+
|
|
1330
|
+
--- Pick from files
|
|
1331
|
+
---
|
|
1332
|
+
--- Lists all files recursively in all subdirectories. Tries to use one of the
|
|
1333
|
+
--- CLI tools to create items (see |MiniPick-cli-tools|): `rg`, `fd`, `git`.
|
|
1334
|
+
--- If none is present, uses fallback which utilizes |vim.fs.dir()|.
|
|
1335
|
+
---
|
|
1336
|
+
--- To customize CLI tool search, either use tool's global configuration approach
|
|
1337
|
+
--- or directly |MiniPick.builtin.cli()| with specific command.
|
|
1338
|
+
---
|
|
1339
|
+
---@param local_opts __pick_builtin_local_opts
|
|
1340
|
+
--- Possible fields:
|
|
1341
|
+
--- - <tool> `(string)` - which tool to use. One of "rg", "fd", "git", "fallback".
|
|
1342
|
+
--- Default: whichever tool is present, trying in that same order.
|
|
1343
|
+
---@param opts __pick_builtin_opts
|
|
1344
|
+
MiniPick.builtin.files = function(local_opts, opts)
|
|
1345
|
+
local_opts = vim.tbl_deep_extend('force', { tool = nil }, local_opts or {})
|
|
1346
|
+
local tool = local_opts.tool or H.files_get_tool()
|
|
1347
|
+
local show = H.get_config().source.show or H.show_with_icons
|
|
1348
|
+
local default_opts = { source = { name = string.format('Files (%s)', tool), show = show } }
|
|
1349
|
+
opts = vim.tbl_deep_extend('force', default_opts, opts or {})
|
|
1350
|
+
|
|
1351
|
+
if tool == 'fallback' then
|
|
1352
|
+
local cwd = H.full_path(opts.source.cwd or vim.fn.getcwd())
|
|
1353
|
+
opts.source.items = function() H.files_fallback_items(cwd) end
|
|
1354
|
+
return MiniPick.start(opts)
|
|
1355
|
+
end
|
|
1356
|
+
|
|
1357
|
+
return MiniPick.builtin.cli({ command = H.files_get_command(tool) }, opts)
|
|
1358
|
+
end
|
|
1359
|
+
|
|
1360
|
+
--- Pick from pattern matches
|
|
1361
|
+
---
|
|
1362
|
+
--- Lists all pattern matches recursively in all subdirectories.
|
|
1363
|
+
--- Tries to use one of the CLI tools to create items (see |MiniPick-cli-tools|):
|
|
1364
|
+
--- `rg`, `git`. If none is present, uses fallback which utilizes |vim.fs.dir()| and
|
|
1365
|
+
--- Lua pattern matches (NOT recommended in large directories).
|
|
1366
|
+
---
|
|
1367
|
+
--- To customize CLI tool search, either use tool's global configuration approach
|
|
1368
|
+
--- or directly |MiniPick.builtin.cli()| with specific command.
|
|
1369
|
+
--- Options |'ignorecase'| and |'smartcase'| are respected via forcing appropriate
|
|
1370
|
+
--- flags to CLI tool (i.e. overriding tool's global config).
|
|
1371
|
+
---
|
|
1372
|
+
---@param local_opts __pick_builtin_local_opts
|
|
1373
|
+
--- Possible fields:
|
|
1374
|
+
--- - <tool> `(string)` - which tool to use. One of "rg", "git", "fallback".
|
|
1375
|
+
--- Default: whichever tool is present, trying in that same order.
|
|
1376
|
+
--- - <pattern> `(string)` - string pattern to search. If not given, asks user
|
|
1377
|
+
--- interactively with |MiniInput.get()| (if enabled) or |input()|.
|
|
1378
|
+
--- - __pick_builtin_grep_globs
|
|
1379
|
+
--- - __pick_builtin_grep_method
|
|
1380
|
+
---@param opts __pick_builtin_opts
|
|
1381
|
+
MiniPick.builtin.grep = function(local_opts, opts)
|
|
1382
|
+
local_opts = vim.tbl_extend('force', { tool = nil, pattern = nil, globs = {}, method = 'regex' }, local_opts or {})
|
|
1383
|
+
local tool = local_opts.tool or H.grep_get_tool()
|
|
1384
|
+
local globs = H.is_array_of(local_opts.globs, 'string') and local_opts.globs or {}
|
|
1385
|
+
local globs_suffix = #globs == 0 and '' or (' | ' .. table.concat(globs, ', '))
|
|
1386
|
+
local name = string.format('Grep (%s %s%s)', tool, local_opts.method, globs_suffix)
|
|
1387
|
+
local default_opts = { source = { name = name, show = H.get_config().source.show or H.show_with_icons } }
|
|
1388
|
+
opts = vim.tbl_deep_extend('force', default_opts, opts or {})
|
|
1389
|
+
|
|
1390
|
+
local pattern = local_opts.pattern
|
|
1391
|
+
if type(local_opts.pattern) ~= 'string' then pattern = H.user_input('Grep pattern', nil, 'file', 'editor') end
|
|
1392
|
+
if pattern == nil then return end
|
|
1393
|
+
if tool == 'fallback' then
|
|
1394
|
+
local cwd = H.full_path(opts.source.cwd or vim.fn.getcwd())
|
|
1395
|
+
opts.source.items = function() H.grep_fallback_items(pattern, cwd) end
|
|
1396
|
+
return MiniPick.start(opts)
|
|
1397
|
+
end
|
|
1398
|
+
|
|
1399
|
+
return MiniPick.builtin.cli({ command = H.grep_get_command(tool, pattern, globs, local_opts.method) }, opts)
|
|
1400
|
+
end
|
|
1401
|
+
|
|
1402
|
+
--- Pick from pattern matches with live feedback
|
|
1403
|
+
---
|
|
1404
|
+
--- Perform pattern matching treating prompt as pattern. Gives live feedback on
|
|
1405
|
+
--- which matches are found. Use |MiniPick-actions-refine| to revert to regular
|
|
1406
|
+
--- matching. Use <C-o> to restrict search to files matching glob patterns.
|
|
1407
|
+
--- Use <C-e> to change pattern matching method. Tries to use one of the CLI
|
|
1408
|
+
--- tools to create items (see |MiniPick-cli-tools|): `rg`, `git`. If none is
|
|
1409
|
+
--- present, error is thrown (for performance reasons).
|
|
1410
|
+
---
|
|
1411
|
+
--- To customize search, use tool's global configuration approach.
|
|
1412
|
+
--- Options |'ignorecase'| and |'smartcase'| are respected via forcing appropriate
|
|
1413
|
+
--- flags to CLI tool (i.e. overriding tool's global config).
|
|
1414
|
+
---
|
|
1415
|
+
---@param local_opts __pick_builtin_local_opts
|
|
1416
|
+
--- Possible fields:
|
|
1417
|
+
--- - <tool> `(string)` - which tool to use. One of "rg", "git".
|
|
1418
|
+
--- Default: whichever tool is present, trying in that same order.
|
|
1419
|
+
--- - __pick_builtin_grep_globs
|
|
1420
|
+
--- Use <C-o> custom mapping to add glob to the array. It uses |MiniInput.get()|
|
|
1421
|
+
--- (if enabled) to show input in |'winbar'| or |'statusline'| (depending of
|
|
1422
|
+
--- whether content is from bottom).
|
|
1423
|
+
--- - __pick_builtin_grep_method
|
|
1424
|
+
--- Use <C-e> custom mapping to add glob to the array.
|
|
1425
|
+
---@param opts __pick_builtin_opts
|
|
1426
|
+
MiniPick.builtin.grep_live = function(local_opts, opts)
|
|
1427
|
+
local_opts = vim.tbl_extend('force', { tool = nil, globs = {}, method = 'regex' }, local_opts or {})
|
|
1428
|
+
local tool = local_opts.tool or H.grep_get_tool()
|
|
1429
|
+
if tool == 'fallback' or not H.is_executable(tool) then H.error('`grep_live` needs non-fallback executable tool.') end
|
|
1430
|
+
|
|
1431
|
+
local globs = H.is_array_of(local_opts.globs, 'string') and local_opts.globs or {}
|
|
1432
|
+
local globs_suffix = #globs == 0 and '' or (' | ' .. table.concat(globs, ', '))
|
|
1433
|
+
local method = local_opts.method
|
|
1434
|
+
local get_name = function() return string.format('Grep live (%s %s%s)', tool, method, globs_suffix) end
|
|
1435
|
+
local default_source = { name = get_name(), show = H.get_config().source.show or H.show_with_icons }
|
|
1436
|
+
opts = vim.tbl_deep_extend('force', { source = default_source }, opts or {})
|
|
1437
|
+
|
|
1438
|
+
local cwd = H.full_path(opts.source.cwd or vim.fn.getcwd())
|
|
1439
|
+
local set_items_opts, spawn_opts = { do_match = false, querytick = H.querytick }, { cwd = cwd }
|
|
1440
|
+
local sys = { kill = function() end }
|
|
1441
|
+
local match = function(_, _, query)
|
|
1442
|
+
sys:kill()
|
|
1443
|
+
if H.querytick == set_items_opts.querytick then return end
|
|
1444
|
+
if #query == 0 then
|
|
1445
|
+
sys = { kill = function() end }
|
|
1446
|
+
return MiniPick.set_picker_items({}, set_items_opts)
|
|
1447
|
+
end
|
|
1448
|
+
|
|
1449
|
+
set_items_opts.querytick = H.querytick
|
|
1450
|
+
local command = H.grep_get_command(tool, table.concat(query), globs, method)
|
|
1451
|
+
sys = MiniPick.set_picker_items_from_cli(command, { set_items_opts = set_items_opts, spawn_opts = spawn_opts })
|
|
1452
|
+
end
|
|
1453
|
+
|
|
1454
|
+
local ag = function()
|
|
1455
|
+
table.insert(globs, H.user_input('Glob pattern', nil, 'file', 'window'))
|
|
1456
|
+
globs_suffix = #globs == 0 and '' or (' | ' .. table.concat(globs, ', '))
|
|
1457
|
+
MiniPick.set_picker_opts({ source = { name = get_name() } })
|
|
1458
|
+
MiniPick.set_picker_query(MiniPick.get_picker_query())
|
|
1459
|
+
end
|
|
1460
|
+
local sm = function()
|
|
1461
|
+
method = method == 'regex' and 'plain' or 'regex'
|
|
1462
|
+
MiniPick.set_picker_opts({ source = { name = get_name() } })
|
|
1463
|
+
MiniPick.set_picker_query(MiniPick.get_picker_query())
|
|
1464
|
+
end
|
|
1465
|
+
local mappings = { add_glob = { char = '<C-o>', func = ag }, switch_method = { char = '<C-e>', func = sm } }
|
|
1466
|
+
|
|
1467
|
+
opts = vim.tbl_deep_extend('force', opts or {}, { source = { items = {}, match = match }, mappings = mappings })
|
|
1468
|
+
return MiniPick.start(opts)
|
|
1469
|
+
end
|
|
1470
|
+
|
|
1471
|
+
--- Pick from help tags
|
|
1472
|
+
---
|
|
1473
|
+
--- Notes:
|
|
1474
|
+
--- - On choose directly executes |:help| command with appropriate modifier
|
|
1475
|
+
--- (none, |:vertical|, |:tab|). This is done through custom mappings named
|
|
1476
|
+
--- `show_help_in_{split,vsplit,tab}`. Not `choose_in_{split,vsplit,tab}` because
|
|
1477
|
+
--- there is no split guarantee (like if there is already help window opened).
|
|
1478
|
+
---
|
|
1479
|
+
---@param local_opts __pick_builtin_local_opts
|
|
1480
|
+
--- Possible fields:
|
|
1481
|
+
--- - <default_split> `(string)` - direction of a split for `choose` action.
|
|
1482
|
+
--- One of "horizontal", "vertical", "tab". Default: "horizontal".
|
|
1483
|
+
---@param opts __pick_builtin_opts
|
|
1484
|
+
MiniPick.builtin.help = function(local_opts, opts)
|
|
1485
|
+
local_opts = vim.tbl_deep_extend('force', { default_split = 'horizontal' }, local_opts or {})
|
|
1486
|
+
local default_modifier = ({ horizontal = '', vertical = 'vert ', tab = 'tab ' })[local_opts.default_split]
|
|
1487
|
+
if default_modifier == nil then H.error('`opts.default_split` should be one of "horizontal", "vertical", "tab"') end
|
|
1488
|
+
|
|
1489
|
+
-- Get all tags
|
|
1490
|
+
local help_buf = vim.api.nvim_create_buf(false, true)
|
|
1491
|
+
vim.bo[help_buf].buftype = 'help'
|
|
1492
|
+
-- - NOTE: no dedicated buffer name because it is immediately wiped out
|
|
1493
|
+
local tags = vim.api.nvim_buf_call(help_buf, function() return vim.fn.taglist('.*') end)
|
|
1494
|
+
vim.api.nvim_buf_delete(help_buf, { force = true })
|
|
1495
|
+
vim.tbl_map(function(t) t.text = t.name end, tags)
|
|
1496
|
+
|
|
1497
|
+
-- NOTE: Choosing is done on next event loop to properly overcome special
|
|
1498
|
+
-- nature of `:help {subject}` command. For example, it didn't quite work
|
|
1499
|
+
-- when choosing tags in same file consecutively.
|
|
1500
|
+
local choose = function(item, modifier)
|
|
1501
|
+
if item == nil then return end
|
|
1502
|
+
vim.schedule(function() vim.cmd((modifier or default_modifier) .. 'help ' .. (item.name or '')) end)
|
|
1503
|
+
end
|
|
1504
|
+
local preview = function(buf_id, item)
|
|
1505
|
+
-- Take advantage of `taglist` output on how to open tag
|
|
1506
|
+
vim.api.nvim_buf_call(buf_id, function()
|
|
1507
|
+
vim.cmd('noautocmd edit ' .. vim.fn.fnameescape(item.filename))
|
|
1508
|
+
vim.bo.buftype, vim.bo.buflisted, vim.bo.bufhidden = 'nofile', false, 'wipe'
|
|
1509
|
+
local has_ts = pcall(vim.treesitter.start, 0)
|
|
1510
|
+
if not has_ts then vim.bo.syntax = 'help' end
|
|
1511
|
+
|
|
1512
|
+
local cache_hlsearch = vim.v.hlsearch
|
|
1513
|
+
-- Make a "very nomagic" search to account for special characters in tag
|
|
1514
|
+
local search_cmd = string.gsub(item.cmd, '^/', '/\\V')
|
|
1515
|
+
vim.cmd('silent keeppatterns ' .. search_cmd)
|
|
1516
|
+
-- Here `vim.v` doesn't work: https://github.com/neovim/neovim/issues/25294
|
|
1517
|
+
vim.cmd('let v:hlsearch=' .. cache_hlsearch)
|
|
1518
|
+
vim.cmd('normal! zt')
|
|
1519
|
+
end)
|
|
1520
|
+
end
|
|
1521
|
+
|
|
1522
|
+
-- Modify default mappings to work with special `:help` command
|
|
1523
|
+
local map_custom = function(char, modifier)
|
|
1524
|
+
local f = function()
|
|
1525
|
+
choose(MiniPick.get_picker_matches().current, modifier)
|
|
1526
|
+
return true
|
|
1527
|
+
end
|
|
1528
|
+
return { char = char, func = f }
|
|
1529
|
+
end
|
|
1530
|
+
|
|
1531
|
+
local config_mappings = H.get_config().mappings
|
|
1532
|
+
--stylua: ignore
|
|
1533
|
+
local mappings = {
|
|
1534
|
+
choose_in_split = '', show_help_in_split = map_custom(config_mappings.choose_in_split, ''),
|
|
1535
|
+
choose_in_vsplit = '', show_help_in_vsplit = map_custom(config_mappings.choose_in_vsplit, 'vertical '),
|
|
1536
|
+
choose_in_tabpage = '', show_help_in_tabpage = map_custom(config_mappings.choose_in_tabpage, 'tab '),
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
local source = { items = tags, name = 'Help', choose = choose, preview = preview }
|
|
1540
|
+
opts = vim.tbl_deep_extend('force', { source = source, mappings = mappings }, opts or {})
|
|
1541
|
+
return MiniPick.start(opts)
|
|
1542
|
+
end
|
|
1543
|
+
|
|
1544
|
+
--- Pick from buffers
|
|
1545
|
+
---
|
|
1546
|
+
--- Notes:
|
|
1547
|
+
--- - There are not built-in mappings for buffer manipulation. Here is an example
|
|
1548
|
+
--- of how to call this function with mapping to wipeout the current item: >lua
|
|
1549
|
+
---
|
|
1550
|
+
--- local wipeout_cur = function()
|
|
1551
|
+
--- vim.api.nvim_buf_delete(MiniPick.get_picker_matches().current.bufnr, {})
|
|
1552
|
+
--- end
|
|
1553
|
+
--- local buffer_mappings = { wipeout = { char = '<C-d>', func = wipeout_cur } }
|
|
1554
|
+
--- MiniPick.builtin.buffers(local_opts, { mappings = buffer_mappings })
|
|
1555
|
+
--- <
|
|
1556
|
+
---@param local_opts __pick_builtin_local_opts
|
|
1557
|
+
--- Possible fields:
|
|
1558
|
+
--- - <include_current> `(boolean)` - whether to include current buffer in
|
|
1559
|
+
--- the output. Default: `true`.
|
|
1560
|
+
--- - <include_unlisted> `(boolean)` - whether to include |unlisted-buffer|s in
|
|
1561
|
+
--- the output. Default: `false`.
|
|
1562
|
+
---@param opts __pick_builtin_opts
|
|
1563
|
+
MiniPick.builtin.buffers = function(local_opts, opts)
|
|
1564
|
+
local_opts = vim.tbl_deep_extend('force', { include_current = true, include_unlisted = false }, local_opts or {})
|
|
1565
|
+
|
|
1566
|
+
local buffers_output = vim.api.nvim_exec('buffers' .. (local_opts.include_unlisted and '!' or ''), true)
|
|
1567
|
+
local cur_buf_id, include_current = vim.api.nvim_get_current_buf(), local_opts.include_current
|
|
1568
|
+
local items = {}
|
|
1569
|
+
for _, l in ipairs(vim.split(buffers_output, '\n')) do
|
|
1570
|
+
local buf_str, name = l:match('^%s*%d+'), l:match('"(.*)"')
|
|
1571
|
+
local buf_id = tonumber(buf_str)
|
|
1572
|
+
local item = { text = name, bufnr = buf_id }
|
|
1573
|
+
if buf_id ~= cur_buf_id or include_current then table.insert(items, item) end
|
|
1574
|
+
end
|
|
1575
|
+
|
|
1576
|
+
local show = H.get_config().source.show or H.show_with_icons
|
|
1577
|
+
local default_opts = { source = { name = 'Buffers', show = show } }
|
|
1578
|
+
opts = vim.tbl_deep_extend('force', default_opts, opts or {}, { source = { items = items } })
|
|
1579
|
+
return MiniPick.start(opts)
|
|
1580
|
+
end
|
|
1581
|
+
|
|
1582
|
+
--- Pick from CLI output
|
|
1583
|
+
---
|
|
1584
|
+
--- Executes command line tool and constructs items based on its output.
|
|
1585
|
+
--- Uses |MiniPick.set_picker_items_from_cli()|.
|
|
1586
|
+
---
|
|
1587
|
+
--- Example: `MiniPick.builtin.cli({ command = { 'echo', 'a\nb\nc' } })`
|
|
1588
|
+
---
|
|
1589
|
+
---@param local_opts __pick_builtin_local_opts
|
|
1590
|
+
--- Possible fields:
|
|
1591
|
+
--- - <command> `(table)` - forwarded to `set_picker_items_from_cli()`.
|
|
1592
|
+
--- - <postprocess> `(function)` - forwarded to `set_picker_items_from_cli()`.
|
|
1593
|
+
--- - <spawn_opts> `(table)` - forwarded to `set_picker_items_from_cli()`.
|
|
1594
|
+
--- Note: if `cwd` field is absent, it is inferred from |MiniPick-source.cwd|.
|
|
1595
|
+
---@param opts __pick_builtin_opts
|
|
1596
|
+
MiniPick.builtin.cli = function(local_opts, opts)
|
|
1597
|
+
local_opts = vim.tbl_deep_extend('force', { command = {}, postprocess = nil, spawn_opts = {} }, local_opts or {})
|
|
1598
|
+
local name = string.format('CLI (%s)', tostring(local_opts.command[1] or ''))
|
|
1599
|
+
opts = vim.tbl_deep_extend('force', { source = { name = name } }, opts or {})
|
|
1600
|
+
-- Explicitly use full path to not conflict with `set_picker_items_from_cli`
|
|
1601
|
+
-- behavior of treating `spawn_opts.cwd` relative to source's cwd
|
|
1602
|
+
local_opts.spawn_opts.cwd = H.full_path(local_opts.spawn_opts.cwd or opts.source.cwd or vim.fn.getcwd())
|
|
1603
|
+
|
|
1604
|
+
local command = local_opts.command
|
|
1605
|
+
local set_from_cli_opts = { postprocess = local_opts.postprocess, spawn_opts = local_opts.spawn_opts }
|
|
1606
|
+
opts.source.items = vim.schedule_wrap(function() MiniPick.set_picker_items_from_cli(command, set_from_cli_opts) end)
|
|
1607
|
+
return MiniPick.start(opts)
|
|
1608
|
+
end
|
|
1609
|
+
|
|
1610
|
+
--- Resume latest picker
|
|
1611
|
+
MiniPick.builtin.resume = function()
|
|
1612
|
+
local picker = H.pickers.latest
|
|
1613
|
+
if picker == nil then H.error('There is no picker to resume.') end
|
|
1614
|
+
|
|
1615
|
+
H.cache = {}
|
|
1616
|
+
local buf_id = H.picker_new_buf()
|
|
1617
|
+
local win_target = vim.api.nvim_get_current_win()
|
|
1618
|
+
local win_id = H.picker_new_win(buf_id, picker.opts.window.config, picker.opts.source.cwd)
|
|
1619
|
+
picker.buffers = { main = buf_id }
|
|
1620
|
+
picker.windows = { main = win_id, target = win_target }
|
|
1621
|
+
picker.view_state = 'main'
|
|
1622
|
+
H.pickers.active = picker
|
|
1623
|
+
|
|
1624
|
+
return H.picker_advance(picker)
|
|
1625
|
+
end
|
|
1626
|
+
|
|
1627
|
+
--- Picker registry
|
|
1628
|
+
---
|
|
1629
|
+
--- Place for users and extensions to manage pickers with their commonly used
|
|
1630
|
+
--- local options. By default contains all |MiniPick.builtin| pickers.
|
|
1631
|
+
--- All entries should accept only a single `local_opts` table argument.
|
|
1632
|
+
---
|
|
1633
|
+
--- Serves as a source for |:Pick| command.
|
|
1634
|
+
---
|
|
1635
|
+
--- Customization examples: >lua
|
|
1636
|
+
---
|
|
1637
|
+
--- -- Adding custom picker to pick `register` entries
|
|
1638
|
+
--- MiniPick.registry.registry = function()
|
|
1639
|
+
--- local items = vim.tbl_keys(MiniPick.registry)
|
|
1640
|
+
--- table.sort(items)
|
|
1641
|
+
--- local source = {items = items, name = 'Registry', choose = function() end}
|
|
1642
|
+
--- local chosen_picker_name = MiniPick.start({ source = source })
|
|
1643
|
+
--- if chosen_picker_name == nil then return end
|
|
1644
|
+
--- return MiniPick.registry[chosen_picker_name]()
|
|
1645
|
+
--- end
|
|
1646
|
+
---
|
|
1647
|
+
--- -- Make `:Pick files` accept `cwd`
|
|
1648
|
+
--- MiniPick.registry.files = function(local_opts)
|
|
1649
|
+
--- local opts = { source = { cwd = local_opts.cwd } }
|
|
1650
|
+
--- local_opts.cwd = nil
|
|
1651
|
+
--- return MiniPick.builtin.files(local_opts, opts)
|
|
1652
|
+
--- end
|
|
1653
|
+
--- <
|
|
1654
|
+
MiniPick.registry = {}
|
|
1655
|
+
|
|
1656
|
+
for name, f in pairs(MiniPick.builtin) do
|
|
1657
|
+
MiniPick.registry[name] = function(local_opts) return f(local_opts) end
|
|
1658
|
+
end
|
|
1659
|
+
|
|
1660
|
+
--- Get items of active picker
|
|
1661
|
+
---
|
|
1662
|
+
---@return table|nil Picker items or `nil` if no active picker.
|
|
1663
|
+
---
|
|
1664
|
+
---@seealso |MiniPick.set_picker_items()| and |MiniPick.set_picker_items_from_cli()|
|
|
1665
|
+
MiniPick.get_picker_items = function() return H.copy_tables((H.pickers.active or {}).items) end
|
|
1666
|
+
|
|
1667
|
+
--- Get stritems of active picker
|
|
1668
|
+
---
|
|
1669
|
+
---@return table|nil Picker stritems (|MiniPick-source.items-stritems|) or `nil` if
|
|
1670
|
+
--- no active picker.
|
|
1671
|
+
---
|
|
1672
|
+
---@seealso |MiniPick.set_picker_items()| and |MiniPick.set_picker_items_from_cli()|
|
|
1673
|
+
MiniPick.get_picker_stritems = function() return vim.deepcopy((H.pickers.active or {}).stritems) end
|
|
1674
|
+
|
|
1675
|
+
--- Get matches of active picker
|
|
1676
|
+
---
|
|
1677
|
+
---@return table|nil Picker matches or `nil` if no active picker. Matches is a table
|
|
1678
|
+
--- with the following fields:
|
|
1679
|
+
--- - <all> `(table|nil)` - all currently matched items.
|
|
1680
|
+
--- - <all_inds> `(table|nil)` - indexes of all currently matched items.
|
|
1681
|
+
--- - <current> `(any)` - current matched item.
|
|
1682
|
+
--- - <current_ind> `(number|nil)` - index of current matched item.
|
|
1683
|
+
--- - <marked> `(table|nil)` - marked items.
|
|
1684
|
+
--- - <marked_inds> `(table|nil)` - indexes of marked items.
|
|
1685
|
+
--- - <shown> `(table|nil)` - shown items (from top to bottom).
|
|
1686
|
+
--- - <shown_inds> `(table|nil)` - indexes of shown items (from top to bottom).
|
|
1687
|
+
---
|
|
1688
|
+
---@seealso |MiniPick.set_picker_match_inds()|
|
|
1689
|
+
MiniPick.get_picker_matches = function()
|
|
1690
|
+
if not MiniPick.is_picker_active() then return end
|
|
1691
|
+
local picker = H.pickers.active
|
|
1692
|
+
local items = picker.items
|
|
1693
|
+
if items == nil or #items == 0 then return {} end
|
|
1694
|
+
|
|
1695
|
+
local match_inds = vim.deepcopy(picker.match_inds)
|
|
1696
|
+
local res = { all_inds = match_inds, current_ind = match_inds[picker.current_ind] }
|
|
1697
|
+
res.all = vim.tbl_map(function(ind) return items[ind] end, match_inds)
|
|
1698
|
+
res.current = picker.items[res.current_ind]
|
|
1699
|
+
local marked_inds = vim.tbl_keys(picker.marked_inds_map)
|
|
1700
|
+
table.sort(marked_inds)
|
|
1701
|
+
res.marked_inds, res.marked = marked_inds, vim.tbl_map(function(ind) return items[ind] end, marked_inds)
|
|
1702
|
+
res.shown_inds = vim.tbl_map(function(ind) return match_inds[ind] end, picker.shown_inds)
|
|
1703
|
+
res.shown = vim.tbl_map(function(ind) return items[ind] end, res.shown_inds)
|
|
1704
|
+
return res
|
|
1705
|
+
end
|
|
1706
|
+
|
|
1707
|
+
--- Get config of active picker
|
|
1708
|
+
---
|
|
1709
|
+
---@return table|nil Picker config (`opts` table of |MiniPick.start()|) or `nil` if
|
|
1710
|
+
--- no active picker.
|
|
1711
|
+
---
|
|
1712
|
+
---@seealso |MiniPick.set_picker_opts()|
|
|
1713
|
+
MiniPick.get_picker_opts = function() return vim.deepcopy((H.pickers.active or {}).opts) end
|
|
1714
|
+
|
|
1715
|
+
--- Get state data of active picker
|
|
1716
|
+
---
|
|
1717
|
+
---@return table|nil Table with picker state data or `nil` if no active picker.
|
|
1718
|
+
--- State data is a table with the following fields:
|
|
1719
|
+
--- - <buffers> `(table)` - table with `main`, `preview`, `info` fields representing
|
|
1720
|
+
--- buffer identifier (or `nil`) for corresponding view.
|
|
1721
|
+
--- - <windows> `(table)` - table with `main` and `target` fields representing
|
|
1722
|
+
--- window identifiers for main and target windows.
|
|
1723
|
+
--- - <caret> `(number)` - caret column.
|
|
1724
|
+
--- - <is_busy> `(boolean)` - whether picker is busy with computations.
|
|
1725
|
+
---
|
|
1726
|
+
---@seealso |MiniPick.set_picker_target_window()|
|
|
1727
|
+
MiniPick.get_picker_state = function()
|
|
1728
|
+
if not MiniPick.is_picker_active() then return end
|
|
1729
|
+
local picker = H.pickers.active
|
|
1730
|
+
--stylua: ignore
|
|
1731
|
+
return vim.deepcopy({
|
|
1732
|
+
buffers = picker.buffers, windows = picker.windows, caret = picker.caret, is_busy = picker.is_busy
|
|
1733
|
+
})
|
|
1734
|
+
end
|
|
1735
|
+
|
|
1736
|
+
--- Get query of active picker
|
|
1737
|
+
---
|
|
1738
|
+
---@return table|nil Array of picker query or `nil` if no active picker.
|
|
1739
|
+
---
|
|
1740
|
+
---@seealso |MiniPick.set_picker_query()|
|
|
1741
|
+
MiniPick.get_picker_query = function() return vim.deepcopy((H.pickers.active or {}).query) end
|
|
1742
|
+
|
|
1743
|
+
--- Set items for active picker
|
|
1744
|
+
---
|
|
1745
|
+
--- Note: sets items asynchronously in non-blocking fashion.
|
|
1746
|
+
---
|
|
1747
|
+
---@param items table Array of items.
|
|
1748
|
+
---@param opts table|nil Options. Possible fields:
|
|
1749
|
+
--- - <do_match> `(boolean)` - whether to perform match after setting items.
|
|
1750
|
+
--- Default: `true`.
|
|
1751
|
+
--- - <querytick> `(number|nil)` - value of querytick (|MiniPick.get_querytick()|)
|
|
1752
|
+
--- to periodically check against when setting items. If checked querytick
|
|
1753
|
+
--- differs from supplied, no items are set.
|
|
1754
|
+
---
|
|
1755
|
+
---@seealso |MiniPick.get_picker_items()| and |MiniPick.get_picker_stritems()|
|
|
1756
|
+
MiniPick.set_picker_items = function(items, opts)
|
|
1757
|
+
if not H.islist(items) then H.error('`items` should be an array.') end
|
|
1758
|
+
opts = vim.tbl_deep_extend('force', { do_match = true, querytick = nil }, opts or {})
|
|
1759
|
+
if not MiniPick.is_picker_active() then return end
|
|
1760
|
+
|
|
1761
|
+
-- Set items in async because computing lower `stritems` can block much time
|
|
1762
|
+
coroutine.wrap(H.picker_set_items)(H.pickers.active, items, opts)
|
|
1763
|
+
end
|
|
1764
|
+
|
|
1765
|
+
--- Set items for active picker based on CLI output
|
|
1766
|
+
---
|
|
1767
|
+
--- Asynchronously executes `command` and sets items to its postprocessed output.
|
|
1768
|
+
---
|
|
1769
|
+
--- Example: >lua
|
|
1770
|
+
---
|
|
1771
|
+
--- local items = vim.schedule_wrap(function()
|
|
1772
|
+
--- MiniPick.set_picker_items_from_cli({ 'echo', 'a\nb\nc' })
|
|
1773
|
+
--- end)
|
|
1774
|
+
--- MiniPick.start({ source = { items = items, name = 'Echo abc' } })
|
|
1775
|
+
--- <
|
|
1776
|
+
---@param command table Array with (at least one) string command parts.
|
|
1777
|
+
---@param opts table|nil Options. Possible fields:
|
|
1778
|
+
--- - <postprocess> `(function)` - callable performing postprocessing of output.
|
|
1779
|
+
--- Will be called with array of lines as input, should return array of items.
|
|
1780
|
+
--- Default: removes trailing empty lines and uses rest as string items.
|
|
1781
|
+
--- - <spawn_opts> `(table)` - `options` for |uv.spawn()|, except `args` and `stdio`.
|
|
1782
|
+
--- Note: relative `cwd` path is resolved against active picker's `cwd`.
|
|
1783
|
+
--- - <set_items_opts> `(table)` - table forwarded to |MiniPick.set_picker_items()|.
|
|
1784
|
+
---
|
|
1785
|
+
---@seealso |MiniPick.get_picker_items()| and |MiniPick.get_picker_stritems()|
|
|
1786
|
+
MiniPick.set_picker_items_from_cli = function(command, opts)
|
|
1787
|
+
local is_valid_command = H.is_array_of(command, 'string') and #command >= 1
|
|
1788
|
+
if not is_valid_command then H.error('`command` should be an array of strings.') end
|
|
1789
|
+
local default_opts = { postprocess = H.cli_postprocess, set_items_opts = {}, spawn_opts = {} }
|
|
1790
|
+
opts = vim.tbl_deep_extend('force', default_opts, opts or {})
|
|
1791
|
+
if not MiniPick.is_picker_active() then return end
|
|
1792
|
+
|
|
1793
|
+
local executable, args = command[1], vim.list_slice(command, 2, #command)
|
|
1794
|
+
local stdout, data_feed = vim.loop.new_pipe(), {}
|
|
1795
|
+
local spawn_opts = vim.tbl_deep_extend('force', opts.spawn_opts, { args = args, stdio = { nil, stdout, nil } })
|
|
1796
|
+
if type(spawn_opts.cwd) == 'string' then spawn_opts.cwd = H.full_path(spawn_opts.cwd) end
|
|
1797
|
+
|
|
1798
|
+
local process, pid
|
|
1799
|
+
process, pid = vim.loop.spawn(executable, spawn_opts, function()
|
|
1800
|
+
if not process:is_closing() then process:close() end
|
|
1801
|
+
end)
|
|
1802
|
+
-- NOTE: `cancel` is better name, but go with `vim.system():kill` for future
|
|
1803
|
+
local kill = function()
|
|
1804
|
+
if stdout:is_active() then stdout:read_stop() end
|
|
1805
|
+
data_feed = nil
|
|
1806
|
+
if not stdout:is_closing() then stdout:close() end
|
|
1807
|
+
if process:is_active() then process:kill() end
|
|
1808
|
+
end
|
|
1809
|
+
|
|
1810
|
+
-- Make sure to fully stop the process if picker is stopped
|
|
1811
|
+
vim.api.nvim_create_autocmd('User', { pattern = 'MiniPickStop', once = true, callback = kill })
|
|
1812
|
+
|
|
1813
|
+
stdout:read_start(function(err, data)
|
|
1814
|
+
assert(not err, err)
|
|
1815
|
+
if data ~= nil then return table.insert(data_feed, data) end
|
|
1816
|
+
stdout:close()
|
|
1817
|
+
coroutine.wrap(H.set_picker_items_from_feed)(data_feed, '\r?\n', opts)
|
|
1818
|
+
end)
|
|
1819
|
+
|
|
1820
|
+
return { pid = pid, kill = kill }
|
|
1821
|
+
end
|
|
1822
|
+
|
|
1823
|
+
--- Set match indexes for active picker
|
|
1824
|
+
---
|
|
1825
|
+
--- There are two intended use cases:
|
|
1826
|
+
--- - Inside custom asynchronous |MiniPick-source.match| function to set which of
|
|
1827
|
+
--- picker's stritems match the query. See |MiniPick.poke_is_picker_active()|.
|
|
1828
|
+
--- - To programmatically set current match and marked items.
|
|
1829
|
+
--- See |MiniPick.get_picker_matches()|.
|
|
1830
|
+
---
|
|
1831
|
+
---@param match_inds table Array of numbers with picker's items indexes.
|
|
1832
|
+
---@param match_type string|nil Type of match indexes to set. One of:
|
|
1833
|
+
--- - `"all"` (default) - indexes of items that match query.
|
|
1834
|
+
--- - `"current"` - index of current match. Only first element is used and should
|
|
1835
|
+
--- also be present among query matches.
|
|
1836
|
+
--- - `"marked"` - indexes of marked items. Values can be not among query matches.
|
|
1837
|
+
--- Will make only input indexes be marked, i.e. current marks are reset.
|
|
1838
|
+
--- Note: no `"shown"` match type as those indexes are computed automatically.
|
|
1839
|
+
---
|
|
1840
|
+
---@seealso |MiniPick.get_picker_matches()|
|
|
1841
|
+
MiniPick.set_picker_match_inds = function(match_inds, match_type)
|
|
1842
|
+
if not H.is_array_of(match_inds, 'number') then H.error('`match_inds` should be an array of numbers.') end
|
|
1843
|
+
local set = H.picker_set_inds[match_type or 'all']
|
|
1844
|
+
if set == nil then H.error('`match_type` should be one of "all", "marked", "current"') end
|
|
1845
|
+
if not MiniPick.is_picker_active() then return end
|
|
1846
|
+
|
|
1847
|
+
set(H.pickers.active, match_inds)
|
|
1848
|
+
H.picker_update(H.pickers.active, false)
|
|
1849
|
+
end
|
|
1850
|
+
|
|
1851
|
+
--- Set config for active picker
|
|
1852
|
+
---
|
|
1853
|
+
---@param opts table Table overriding initial `opts` input of |MiniPick.start()|.
|
|
1854
|
+
---
|
|
1855
|
+
---@seealso |MiniPick.get_picker_opts()|
|
|
1856
|
+
MiniPick.set_picker_opts = function(opts)
|
|
1857
|
+
if not MiniPick.is_picker_active() then return end
|
|
1858
|
+
local picker, cur_cwd = H.pickers.active, H.pickers.active.opts.source.cwd
|
|
1859
|
+
picker.opts = vim.tbl_deep_extend('force', picker.opts, opts or {})
|
|
1860
|
+
picker.action_keys = H.normalize_mappings(picker.opts.mappings)
|
|
1861
|
+
if cur_cwd ~= picker.opts.source.cwd then H.win_set_cwd(picker.windows.main, picker.opts.source.cwd) end
|
|
1862
|
+
local do_match = ((opts or {}).source or {}).match ~= nil
|
|
1863
|
+
H.picker_update(picker, do_match, true)
|
|
1864
|
+
end
|
|
1865
|
+
|
|
1866
|
+
--- Set target window for active picker
|
|
1867
|
+
---
|
|
1868
|
+
---@param win_id number Valid window identifier to be used as the new target window.
|
|
1869
|
+
---
|
|
1870
|
+
---@seealso |MiniPick.get_picker_state()|
|
|
1871
|
+
MiniPick.set_picker_target_window = function(win_id)
|
|
1872
|
+
if not H.is_valid_win(win_id) then H.error('`win_id` is not a valid window identifier.') end
|
|
1873
|
+
if not MiniPick.is_picker_active() then return end
|
|
1874
|
+
H.pickers.active.windows.target = win_id
|
|
1875
|
+
end
|
|
1876
|
+
|
|
1877
|
+
--- Set query for active picker
|
|
1878
|
+
---
|
|
1879
|
+
---@param query table Array of strings to be set as the new picker query.
|
|
1880
|
+
---
|
|
1881
|
+
---@seealso |MiniPick.get_picker_query()|
|
|
1882
|
+
MiniPick.set_picker_query = function(query)
|
|
1883
|
+
if not H.is_array_of(query, 'string') then H.error('`query` should be an array of strings.') end
|
|
1884
|
+
if not MiniPick.is_picker_active() then return end
|
|
1885
|
+
|
|
1886
|
+
H.pickers.active.query, H.pickers.active.caret = vim.deepcopy(query), #query + 1
|
|
1887
|
+
H.querytick = H.querytick + 1
|
|
1888
|
+
H.pickers.active.match_inds = H.seq_along(MiniPick.get_picker_items())
|
|
1889
|
+
H.picker_update(H.pickers.active, true)
|
|
1890
|
+
end
|
|
1891
|
+
|
|
1892
|
+
--- Get query tick
|
|
1893
|
+
---
|
|
1894
|
+
--- Query tick is a unique query identifier. Intended to be used to detect user
|
|
1895
|
+
--- activity during and between |MiniPick.start()| calls for efficient non-blocking
|
|
1896
|
+
--- functionality. Updates after any query change, picker start and stop.
|
|
1897
|
+
---
|
|
1898
|
+
--- See |MiniPick.poke_is_picker_active()| for usage example.
|
|
1899
|
+
---
|
|
1900
|
+
---@return number Query tick.
|
|
1901
|
+
MiniPick.get_querytick = function() return H.querytick end
|
|
1902
|
+
|
|
1903
|
+
--- Check if there is an active picker
|
|
1904
|
+
---
|
|
1905
|
+
---@return boolean Whether there is currently an active picker.
|
|
1906
|
+
---
|
|
1907
|
+
---@seealso |MiniPick.poke_is_picker_active()|
|
|
1908
|
+
MiniPick.is_picker_active = function() return H.pickers.active ~= nil end
|
|
1909
|
+
|
|
1910
|
+
--- Poke if picker is active
|
|
1911
|
+
---
|
|
1912
|
+
--- Intended to be used for non-blocking implementation of source methods.
|
|
1913
|
+
--- Returns an output of |MiniPick.is_picker_active()|, but depending on
|
|
1914
|
+
--- whether there is a coroutine running:
|
|
1915
|
+
--- - If no, return it immediately.
|
|
1916
|
+
--- - If yes, return it after `coroutine.yield()` with `coroutine.resume()`
|
|
1917
|
+
--- called "soon" by the main event-loop (see |vim.schedule()|).
|
|
1918
|
+
---
|
|
1919
|
+
--- Example of non-blocking exact `match` (as demo; can be optimized further): >lua
|
|
1920
|
+
---
|
|
1921
|
+
--- local match_nonblock = function(match_inds, stritems, query)
|
|
1922
|
+
--- local prompt, querytick = table.concat(query), MiniPick.get_querytick()
|
|
1923
|
+
--- local f = function()
|
|
1924
|
+
--- local res = {}
|
|
1925
|
+
--- for _, ind in ipairs(match_inds) do
|
|
1926
|
+
--- local should_stop = not MiniPick.poke_is_picker_active() or
|
|
1927
|
+
--- MiniPick.get_querytick() ~= querytick
|
|
1928
|
+
--- if should_stop then return end
|
|
1929
|
+
---
|
|
1930
|
+
--- if stritems[ind]:find(prompt) ~= nil then table.insert(res, ind) end
|
|
1931
|
+
--- end
|
|
1932
|
+
---
|
|
1933
|
+
--- MiniPick.set_picker_match_inds(res)
|
|
1934
|
+
--- end
|
|
1935
|
+
---
|
|
1936
|
+
--- coroutine.resume(coroutine.create(f))
|
|
1937
|
+
--- end
|
|
1938
|
+
--- <
|
|
1939
|
+
---@return boolean Whether there is an active picker.
|
|
1940
|
+
---
|
|
1941
|
+
---@seealso |MiniPick.is_picker_active()|
|
|
1942
|
+
MiniPick.poke_is_picker_active = function()
|
|
1943
|
+
local co = coroutine.running()
|
|
1944
|
+
if co == nil then return MiniPick.is_picker_active() end
|
|
1945
|
+
H.schedule_resume_is_active(co)
|
|
1946
|
+
return coroutine.yield()
|
|
1947
|
+
end
|
|
1948
|
+
|
|
1949
|
+
-- Helper data ================================================================
|
|
1950
|
+
-- Module default config
|
|
1951
|
+
H.default_config = vim.deepcopy(MiniPick.config)
|
|
1952
|
+
|
|
1953
|
+
-- Namespaces
|
|
1954
|
+
H.ns_id = {
|
|
1955
|
+
matches = vim.api.nvim_create_namespace('MiniPickMatches'),
|
|
1956
|
+
headers = vim.api.nvim_create_namespace('MiniPickHeaders'),
|
|
1957
|
+
preview = vim.api.nvim_create_namespace('MiniPickPreview'),
|
|
1958
|
+
ranges = vim.api.nvim_create_namespace('MiniPickRanges'),
|
|
1959
|
+
input = vim.api.nvim_create_namespace('MiniPickInput'),
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
-- Timers
|
|
1963
|
+
H.timers = {
|
|
1964
|
+
busy = vim.loop.new_timer(),
|
|
1965
|
+
focus = vim.loop.new_timer(),
|
|
1966
|
+
redraw = vim.loop.new_timer(),
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1969
|
+
-- Pickers
|
|
1970
|
+
H.pickers = { active = nil, latest = nil }
|
|
1971
|
+
|
|
1972
|
+
-- Picker-independent counter of query updates
|
|
1973
|
+
H.querytick = 0
|
|
1974
|
+
|
|
1975
|
+
-- General purpose cache
|
|
1976
|
+
H.cache = {}
|
|
1977
|
+
|
|
1978
|
+
-- File system information
|
|
1979
|
+
H.is_windows = vim.loop.os_uname().sysname == 'Windows_NT'
|
|
1980
|
+
|
|
1981
|
+
-- Helper functionality =======================================================
|
|
1982
|
+
-- Settings -------------------------------------------------------------------
|
|
1983
|
+
H.setup_config = function(config)
|
|
1984
|
+
H.check_type('config', config, 'table', true)
|
|
1985
|
+
config = vim.tbl_deep_extend('force', vim.deepcopy(H.default_config), config or {})
|
|
1986
|
+
|
|
1987
|
+
H.check_type('delay', config.delay, 'table')
|
|
1988
|
+
H.check_type('delay.async', config.delay.async, 'number')
|
|
1989
|
+
H.check_type('delay.busy', config.delay.busy, 'number')
|
|
1990
|
+
|
|
1991
|
+
H.check_type('mappings', config.mappings, 'table')
|
|
1992
|
+
H.check_type('mappings.caret_left', config.mappings.caret_left, 'string')
|
|
1993
|
+
H.check_type('mappings.caret_right', config.mappings.caret_right, 'string')
|
|
1994
|
+
H.check_type('mappings.choose', config.mappings.choose, 'string')
|
|
1995
|
+
H.check_type('mappings.choose_in_split', config.mappings.choose_in_split, 'string')
|
|
1996
|
+
H.check_type('mappings.choose_in_tabpage', config.mappings.choose_in_tabpage, 'string')
|
|
1997
|
+
H.check_type('mappings.choose_in_vsplit', config.mappings.choose_in_vsplit, 'string')
|
|
1998
|
+
H.check_type('mappings.choose_marked', config.mappings.choose_marked, 'string')
|
|
1999
|
+
H.check_type('mappings.delete_char', config.mappings.delete_char, 'string')
|
|
2000
|
+
H.check_type('mappings.delete_char_right', config.mappings.delete_char_right, 'string')
|
|
2001
|
+
H.check_type('mappings.delete_left', config.mappings.delete_left, 'string')
|
|
2002
|
+
H.check_type('mappings.delete_word', config.mappings.delete_word, 'string')
|
|
2003
|
+
H.check_type('mappings.mark', config.mappings.mark, 'string')
|
|
2004
|
+
H.check_type('mappings.mark_all', config.mappings.mark_all, 'string')
|
|
2005
|
+
H.check_type('mappings.move_down', config.mappings.move_down, 'string')
|
|
2006
|
+
H.check_type('mappings.move_start', config.mappings.move_start, 'string')
|
|
2007
|
+
H.check_type('mappings.move_up', config.mappings.move_up, 'string')
|
|
2008
|
+
H.check_type('mappings.paste', config.mappings.paste, 'string')
|
|
2009
|
+
H.check_type('mappings.refine', config.mappings.refine, 'string')
|
|
2010
|
+
H.check_type('mappings.refine_marked', config.mappings.refine_marked, 'string')
|
|
2011
|
+
H.check_type('mappings.scroll_down', config.mappings.scroll_down, 'string')
|
|
2012
|
+
H.check_type('mappings.scroll_up', config.mappings.scroll_up, 'string')
|
|
2013
|
+
H.check_type('mappings.scroll_left', config.mappings.scroll_left, 'string')
|
|
2014
|
+
H.check_type('mappings.scroll_right', config.mappings.scroll_right, 'string')
|
|
2015
|
+
H.check_type('mappings.stop', config.mappings.stop, 'string')
|
|
2016
|
+
H.check_type('mappings.toggle_info', config.mappings.toggle_info, 'string')
|
|
2017
|
+
H.check_type('mappings.toggle_preview', config.mappings.toggle_preview, 'string')
|
|
2018
|
+
|
|
2019
|
+
H.check_type('options', config.options, 'table')
|
|
2020
|
+
H.check_type('options.content_from_bottom', config.options.content_from_bottom, 'boolean')
|
|
2021
|
+
H.check_type('options.use_cache', config.options.use_cache, 'boolean')
|
|
2022
|
+
|
|
2023
|
+
H.check_type('source', config.source, 'table')
|
|
2024
|
+
H.check_type('source.items', config.source.items, 'table', true)
|
|
2025
|
+
H.check_type('source.name', config.source.name, 'string', true)
|
|
2026
|
+
H.check_type('source.cwd', config.source.cwd, 'string', true)
|
|
2027
|
+
H.check_type('source.match', config.source.match, 'function', true)
|
|
2028
|
+
H.check_type('source.show', config.source.show, 'function', true)
|
|
2029
|
+
H.check_type('source.preview', config.source.preview, 'function', true)
|
|
2030
|
+
H.check_type('source.choose', config.source.choose, 'function', true)
|
|
2031
|
+
H.check_type('source.choose_marked', config.source.choose_marked, 'function', true)
|
|
2032
|
+
|
|
2033
|
+
H.check_type('window', config.window, 'table')
|
|
2034
|
+
local is_table_or_callable = function(x) return x == nil or type(x) == 'table' or vim.is_callable(x) end
|
|
2035
|
+
if not is_table_or_callable(config.window.config) then
|
|
2036
|
+
H.error('`window.config` should be table or callable, not ' .. type(config.window.config))
|
|
2037
|
+
end
|
|
2038
|
+
-- TODO: Remove after releasing 'mini.nvim' 0.16.0
|
|
2039
|
+
if config.window.prompt_cursor ~= nil then
|
|
2040
|
+
local msg = '`prompt_cursor` in `config.window` is renamed to `prompt_caret` for better naming consistency.'
|
|
2041
|
+
.. ' It works for now, but will stop in the next release. Sorry for the inconvenience.'
|
|
2042
|
+
H.notify(msg, 'WARN')
|
|
2043
|
+
config.window.prompt_caret = config.window.prompt_cursor
|
|
2044
|
+
config.window.prompt_cursor = nil
|
|
2045
|
+
end
|
|
2046
|
+
H.check_type('window.prompt_caret', config.window.prompt_caret, 'string')
|
|
2047
|
+
H.check_type('window.prompt_prefix', config.window.prompt_prefix, 'string')
|
|
2048
|
+
|
|
2049
|
+
return config
|
|
2050
|
+
end
|
|
2051
|
+
|
|
2052
|
+
H.apply_config = function(config)
|
|
2053
|
+
MiniPick.config = config
|
|
2054
|
+
|
|
2055
|
+
-- Register 'mini.extra' pickers
|
|
2056
|
+
if type(_G.MiniExtra) == 'table' then
|
|
2057
|
+
for name, f in pairs(_G.MiniExtra.pickers) do
|
|
2058
|
+
MiniPick.registry[name] = MiniPick.registry[name] or function(local_opts) return f(local_opts) end
|
|
2059
|
+
end
|
|
2060
|
+
end
|
|
2061
|
+
end
|
|
2062
|
+
|
|
2063
|
+
H.get_config = function(config)
|
|
2064
|
+
return vim.tbl_deep_extend('force', MiniPick.config, vim.b.minipick_config or {}, config or {})
|
|
2065
|
+
end
|
|
2066
|
+
|
|
2067
|
+
H.create_autocommands = function()
|
|
2068
|
+
local gr = vim.api.nvim_create_augroup('MiniPick', {})
|
|
2069
|
+
|
|
2070
|
+
local au = function(event, pattern, callback, desc)
|
|
2071
|
+
vim.api.nvim_create_autocmd(event, { group = gr, pattern = pattern, callback = callback, desc = desc })
|
|
2072
|
+
end
|
|
2073
|
+
|
|
2074
|
+
au('VimResized', '*', MiniPick.refresh, 'Refresh on resize')
|
|
2075
|
+
au('ColorScheme', '*', H.create_default_hl, 'Ensure colors')
|
|
2076
|
+
end
|
|
2077
|
+
|
|
2078
|
+
--stylua: ignore
|
|
2079
|
+
H.create_default_hl = function()
|
|
2080
|
+
local hi = function(name, opts)
|
|
2081
|
+
opts.default = true
|
|
2082
|
+
vim.api.nvim_set_hl(0, name, opts)
|
|
2083
|
+
end
|
|
2084
|
+
|
|
2085
|
+
hi('MiniPickBorder', { link = 'FloatBorder' })
|
|
2086
|
+
hi('MiniPickBorderBusy', { link = 'DiagnosticFloatingWarn' })
|
|
2087
|
+
hi('MiniPickBorderText', { link = 'FloatTitle' })
|
|
2088
|
+
hi('MiniPickCursor', { blend = 100, nocombine = true })
|
|
2089
|
+
hi('MiniPickIconDirectory', { link = 'Directory' })
|
|
2090
|
+
hi('MiniPickIconFile', { link = 'MiniPickNormal' })
|
|
2091
|
+
hi('MiniPickHeader', { link = 'DiagnosticFloatingHint' })
|
|
2092
|
+
hi('MiniPickMatchCurrent', { link = 'CursorLine' })
|
|
2093
|
+
hi('MiniPickMatchMarked', { link = 'Visual' })
|
|
2094
|
+
hi('MiniPickMatchRanges', { link = 'DiagnosticFloatingHint' })
|
|
2095
|
+
hi('MiniPickNormal', { link = 'NormalFloat' })
|
|
2096
|
+
hi('MiniPickPreviewLine', { link = 'CursorLine' })
|
|
2097
|
+
hi('MiniPickPreviewRegion', { link = 'IncSearch' })
|
|
2098
|
+
hi('MiniPickPrompt', { link = 'DiagnosticFloatingInfo' })
|
|
2099
|
+
hi('MiniPickPromptCaret', { link = 'MiniPickPrompt' })
|
|
2100
|
+
hi('MiniPickPromptPrefix', { link = 'MiniPickPrompt' })
|
|
2101
|
+
end
|
|
2102
|
+
|
|
2103
|
+
H.create_user_commands = function()
|
|
2104
|
+
local callback = function(input)
|
|
2105
|
+
local name, local_opts = H.command_parse_fargs(input.fargs)
|
|
2106
|
+
local f = MiniPick.registry[name]
|
|
2107
|
+
if f == nil then H.error(string.format('There is no picker named "%s" in registry.', name)) end
|
|
2108
|
+
f(local_opts)
|
|
2109
|
+
end
|
|
2110
|
+
local opts = { nargs = '+', complete = H.command_complete, desc = "Pick from 'mini.pick' registry" }
|
|
2111
|
+
vim.api.nvim_create_user_command('Pick', callback, opts)
|
|
2112
|
+
end
|
|
2113
|
+
|
|
2114
|
+
H.adjust_vim_paste = function()
|
|
2115
|
+
local paste_orig = vim.paste
|
|
2116
|
+
vim.paste = function(lines, phase)
|
|
2117
|
+
local picker = H.pickers.active
|
|
2118
|
+
if picker == nil then return paste_orig(lines, phase) end
|
|
2119
|
+
if phase ~= -1 then
|
|
2120
|
+
return H.notify('There is no streaming paste support. Use `mappings.paste` with "*" or "+" register.', 'WARN')
|
|
2121
|
+
end
|
|
2122
|
+
local text = table.concat(lines, ' '):gsub('[\n\t]', ' ')
|
|
2123
|
+
for i = 1, vim.fn.strchars(text) do
|
|
2124
|
+
H.picker_query_add(picker, vim.fn.strcharpart(text, i - 1, 1))
|
|
2125
|
+
end
|
|
2126
|
+
H.picker_update(picker, true, true)
|
|
2127
|
+
end
|
|
2128
|
+
end
|
|
2129
|
+
|
|
2130
|
+
-- Command --------------------------------------------------------------------
|
|
2131
|
+
H.command_parse_fargs = function(fargs)
|
|
2132
|
+
local name, opts_parts = fargs[1], vim.tbl_map(H.expandcmd, vim.list_slice(fargs, 2, #fargs))
|
|
2133
|
+
local tbl_string = string.format('{ %s }', table.concat(opts_parts, ', '))
|
|
2134
|
+
local lua_load = loadstring('return ' .. tbl_string)
|
|
2135
|
+
if lua_load == nil then H.error('Could not convert extra command arguments to table: ' .. tbl_string) end
|
|
2136
|
+
return name, lua_load()
|
|
2137
|
+
end
|
|
2138
|
+
|
|
2139
|
+
H.command_complete = function(_, line, col)
|
|
2140
|
+
local prefix_from, prefix_to, prefix = string.find(line, '^%S+%s+(%S*)')
|
|
2141
|
+
if col < prefix_from or prefix_to < col then return {} end
|
|
2142
|
+
local candidates = vim.tbl_filter(
|
|
2143
|
+
function(x) return tostring(x):find(prefix, 1, true) ~= nil end,
|
|
2144
|
+
vim.tbl_keys(MiniPick.registry)
|
|
2145
|
+
)
|
|
2146
|
+
table.sort(candidates)
|
|
2147
|
+
return candidates
|
|
2148
|
+
end
|
|
2149
|
+
|
|
2150
|
+
-- Picker object --------------------------------------------------------------
|
|
2151
|
+
H.validate_picker_opts = function(opts)
|
|
2152
|
+
opts = opts or {}
|
|
2153
|
+
if type(opts) ~= 'table' then H.error('Picker options should be table.') end
|
|
2154
|
+
|
|
2155
|
+
opts = H.copy_tables(H.get_config(opts))
|
|
2156
|
+
|
|
2157
|
+
local validate_callable = function(x, x_name)
|
|
2158
|
+
if not vim.is_callable(x) then H.error(string.format('`%s` should be callable.', x_name)) end
|
|
2159
|
+
end
|
|
2160
|
+
|
|
2161
|
+
-- Source
|
|
2162
|
+
local source = opts.source
|
|
2163
|
+
|
|
2164
|
+
local items = source.items or {}
|
|
2165
|
+
local is_valid_items = H.islist(items) or vim.is_callable(items)
|
|
2166
|
+
if not is_valid_items then H.error('`source.items` should be array or callable.') end
|
|
2167
|
+
|
|
2168
|
+
source.name = tostring(source.name or '<No name>')
|
|
2169
|
+
|
|
2170
|
+
if type(source.cwd) == 'string' then source.cwd = H.full_path(source.cwd) end
|
|
2171
|
+
if source.cwd == nil then source.cwd = vim.fn.getcwd() end
|
|
2172
|
+
if vim.fn.isdirectory(source.cwd) == 0 then H.error('`source.cwd` should be a valid directory path.') end
|
|
2173
|
+
|
|
2174
|
+
source.match = source.match or MiniPick.default_match
|
|
2175
|
+
validate_callable(source.match, 'source.match')
|
|
2176
|
+
|
|
2177
|
+
source.show = source.show or MiniPick.default_show
|
|
2178
|
+
validate_callable(source.show, 'source.show')
|
|
2179
|
+
|
|
2180
|
+
source.preview = source.preview or MiniPick.default_preview
|
|
2181
|
+
validate_callable(source.preview, 'source.preview')
|
|
2182
|
+
|
|
2183
|
+
source.choose = source.choose or MiniPick.default_choose
|
|
2184
|
+
validate_callable(source.choose, 'source.choose')
|
|
2185
|
+
|
|
2186
|
+
source.choose_marked = source.choose_marked or MiniPick.default_choose_marked
|
|
2187
|
+
validate_callable(source.choose_marked, 'source.choose_marked')
|
|
2188
|
+
|
|
2189
|
+
-- Delay
|
|
2190
|
+
for key, value in pairs(opts.delay) do
|
|
2191
|
+
local is_valid_value = type(value) == 'number' and value > 0
|
|
2192
|
+
if not is_valid_value then H.error(string.format('`delay.%s` should be a positive number.', key)) end
|
|
2193
|
+
end
|
|
2194
|
+
|
|
2195
|
+
-- Mappings
|
|
2196
|
+
local default_mappings = H.default_config.mappings
|
|
2197
|
+
for field, x in pairs(opts.mappings) do
|
|
2198
|
+
if type(field) ~= 'string' then H.error('`mappings` should have only string fields.') end
|
|
2199
|
+
local is_builtin_action = default_mappings[field] ~= nil
|
|
2200
|
+
if is_builtin_action and type(x) ~= 'string' then
|
|
2201
|
+
H.error(string.format('Mapping for built-in action "%s" should be string.', field))
|
|
2202
|
+
end
|
|
2203
|
+
if not is_builtin_action and not (type(x) == 'table' and type(x.char) == 'string' and vim.is_callable(x.func)) then
|
|
2204
|
+
H.error(string.format('Mapping for custom action "%s" should be table with `char` and `func`.', field))
|
|
2205
|
+
end
|
|
2206
|
+
end
|
|
2207
|
+
|
|
2208
|
+
-- Options
|
|
2209
|
+
local options = opts.options
|
|
2210
|
+
if type(options.content_from_bottom) ~= 'boolean' then H.error('`options.content_from_bottom` should be boolean.') end
|
|
2211
|
+
if type(options.use_cache) ~= 'boolean' then H.error('`options.use_cache` should be boolean.') end
|
|
2212
|
+
|
|
2213
|
+
-- Window
|
|
2214
|
+
local win_config = opts.window.config
|
|
2215
|
+
local is_valid_winconfig = win_config == nil or type(win_config) == 'table' or vim.is_callable(win_config)
|
|
2216
|
+
if not is_valid_winconfig then H.error('`window.config` should be table or callable.') end
|
|
2217
|
+
|
|
2218
|
+
return opts
|
|
2219
|
+
end
|
|
2220
|
+
|
|
2221
|
+
H.picker_new = function(opts)
|
|
2222
|
+
-- Create buffer
|
|
2223
|
+
local buf_id = H.picker_new_buf()
|
|
2224
|
+
|
|
2225
|
+
-- Create window
|
|
2226
|
+
local win_target = vim.api.nvim_get_current_win()
|
|
2227
|
+
local win_id = H.picker_new_win(buf_id, opts.window.config, opts.source.cwd)
|
|
2228
|
+
|
|
2229
|
+
-- Construct and return object
|
|
2230
|
+
local picker = {
|
|
2231
|
+
-- Permanent data about picker (should not change)
|
|
2232
|
+
opts = opts,
|
|
2233
|
+
|
|
2234
|
+
-- Items to pick from
|
|
2235
|
+
items = nil,
|
|
2236
|
+
stritems = nil,
|
|
2237
|
+
stritems_ignorecase = nil,
|
|
2238
|
+
|
|
2239
|
+
-- Associated Neovim objects
|
|
2240
|
+
buffers = { main = buf_id, preview = nil, info = nil },
|
|
2241
|
+
windows = { main = win_id, target = win_target },
|
|
2242
|
+
|
|
2243
|
+
-- Query data
|
|
2244
|
+
query = {},
|
|
2245
|
+
-- - Query index at which new entry will be inserted
|
|
2246
|
+
caret = 1,
|
|
2247
|
+
-- - Array of `stritems` indexes matching current query
|
|
2248
|
+
match_inds = nil,
|
|
2249
|
+
-- - Map of of currently marked `stritems` indexes (as keys)
|
|
2250
|
+
marked_inds_map = {},
|
|
2251
|
+
-- - Action keys which should be processed as described in mappings
|
|
2252
|
+
action_keys = H.normalize_mappings(opts.mappings),
|
|
2253
|
+
|
|
2254
|
+
-- Whether picker is currently busy processing data
|
|
2255
|
+
is_busy = false,
|
|
2256
|
+
|
|
2257
|
+
-- Cache for `matches` per prompt for more performant querying
|
|
2258
|
+
cache = {},
|
|
2259
|
+
|
|
2260
|
+
-- View data
|
|
2261
|
+
-- - Which buffer should currently be shown
|
|
2262
|
+
view_state = 'main',
|
|
2263
|
+
|
|
2264
|
+
-- - Index range of `match_inds` currently visible. Present for significant
|
|
2265
|
+
-- performance increase to render only what is visible.
|
|
2266
|
+
visible_range = { from = nil, to = nil },
|
|
2267
|
+
|
|
2268
|
+
-- - Index of `match_inds` pointing at current item
|
|
2269
|
+
current_ind = nil,
|
|
2270
|
+
-- - Array of indexes of `match_inds` pointing at currently shown items
|
|
2271
|
+
shown_inds = {},
|
|
2272
|
+
}
|
|
2273
|
+
|
|
2274
|
+
H.querytick = H.querytick + 1
|
|
2275
|
+
|
|
2276
|
+
return picker
|
|
2277
|
+
end
|
|
2278
|
+
|
|
2279
|
+
H.picker_advance = function(picker)
|
|
2280
|
+
vim.schedule(function() vim.api.nvim_exec_autocmds('User', { pattern = 'MiniPickStart' }) end)
|
|
2281
|
+
|
|
2282
|
+
local do_match, is_aborted = false, false
|
|
2283
|
+
local lmap = H.get_lmap()
|
|
2284
|
+
for _ = 1, 1000000 do
|
|
2285
|
+
if H.cache.is_force_stop_advance then break end
|
|
2286
|
+
H.picker_update(picker, do_match)
|
|
2287
|
+
|
|
2288
|
+
local char = H.getcharstr(lmap)
|
|
2289
|
+
if H.cache.is_force_stop_advance then break end
|
|
2290
|
+
|
|
2291
|
+
is_aborted = char == nil
|
|
2292
|
+
if is_aborted then break end
|
|
2293
|
+
|
|
2294
|
+
local cur_action = picker.action_keys[char] or {}
|
|
2295
|
+
do_match = cur_action.name == nil or vim.startswith(cur_action.name, 'delete') or cur_action.name == 'paste'
|
|
2296
|
+
is_aborted = cur_action.name == 'stop'
|
|
2297
|
+
|
|
2298
|
+
local should_stop
|
|
2299
|
+
if cur_action.is_custom then
|
|
2300
|
+
should_stop = cur_action.func()
|
|
2301
|
+
else
|
|
2302
|
+
should_stop = (cur_action.func or H.picker_query_add)(picker, char)
|
|
2303
|
+
end
|
|
2304
|
+
if should_stop then break end
|
|
2305
|
+
end
|
|
2306
|
+
|
|
2307
|
+
local item
|
|
2308
|
+
if not is_aborted then item = H.picker_get_current_item(picker) end
|
|
2309
|
+
H.cache.is_force_stop_advance = nil
|
|
2310
|
+
H.picker_stop(picker)
|
|
2311
|
+
return item
|
|
2312
|
+
end
|
|
2313
|
+
|
|
2314
|
+
H.picker_update = function(picker, do_match, update_window)
|
|
2315
|
+
if do_match then H.picker_match(picker) end
|
|
2316
|
+
if update_window then
|
|
2317
|
+
local config = H.picker_compute_win_config(picker.opts.window.config)
|
|
2318
|
+
vim.api.nvim_win_set_config(picker.windows.main, config)
|
|
2319
|
+
H.picker_set_current_ind(picker, picker.current_ind, true)
|
|
2320
|
+
end
|
|
2321
|
+
H.picker_set_bordertext(picker)
|
|
2322
|
+
H.picker_set_lines(picker)
|
|
2323
|
+
vim.cmd('redraw')
|
|
2324
|
+
end
|
|
2325
|
+
|
|
2326
|
+
H.picker_new_buf = function()
|
|
2327
|
+
local buf_id = H.create_scratch_buf('main')
|
|
2328
|
+
vim.bo[buf_id].filetype = 'minipick'
|
|
2329
|
+
return buf_id
|
|
2330
|
+
end
|
|
2331
|
+
|
|
2332
|
+
H.picker_new_win = function(buf_id, win_config, cwd)
|
|
2333
|
+
-- Hide cursor while picker is active (to not be visible in the window)
|
|
2334
|
+
-- This mostly follows a hack from 'folke/noice.nvim'
|
|
2335
|
+
H.cache.guicursor = vim.o.guicursor
|
|
2336
|
+
vim.o.guicursor = 'a:MiniPickCursor'
|
|
2337
|
+
|
|
2338
|
+
-- Create window and focus on it
|
|
2339
|
+
local win_id = vim.api.nvim_open_win(buf_id, true, H.picker_compute_win_config(win_config, true))
|
|
2340
|
+
|
|
2341
|
+
-- Set window-local data
|
|
2342
|
+
local opts_scope = { scope = 'local', win = win_id }
|
|
2343
|
+
vim.wo[win_id].foldenable = false
|
|
2344
|
+
vim.wo[win_id].foldmethod = 'manual'
|
|
2345
|
+
-- TODO: Use vim.wo[win_id][0] after compatibility with Neovim=0.9 is dropped
|
|
2346
|
+
vim.api.nvim_set_option_value('list', true, opts_scope)
|
|
2347
|
+
vim.api.nvim_set_option_value('listchars', 'extends:…,precedes:…', opts_scope)
|
|
2348
|
+
vim.wo[win_id].scrolloff = 0
|
|
2349
|
+
vim.wo[win_id].wrap = false
|
|
2350
|
+
H.win_update_hl(win_id, 'NormalFloat', 'MiniPickNormal')
|
|
2351
|
+
H.win_update_hl(win_id, 'FloatBorder', 'MiniPickBorder')
|
|
2352
|
+
vim.fn.clearmatches(win_id)
|
|
2353
|
+
|
|
2354
|
+
-- Set window's local "current directory" for easier choose/preview/etc.
|
|
2355
|
+
H.win_set_cwd(nil, cwd)
|
|
2356
|
+
|
|
2357
|
+
return win_id
|
|
2358
|
+
end
|
|
2359
|
+
|
|
2360
|
+
H.picker_compute_win_config = function(win_config, is_for_open)
|
|
2361
|
+
local has_tabline = vim.o.showtabline == 2 or (vim.o.showtabline == 1 and #vim.api.nvim_list_tabpages() > 1)
|
|
2362
|
+
local has_statusline = vim.o.laststatus > 0
|
|
2363
|
+
local max_height = vim.o.lines - vim.o.cmdheight - (has_tabline and 1 or 0) - (has_statusline and 1 or 0)
|
|
2364
|
+
local max_width = vim.o.columns
|
|
2365
|
+
|
|
2366
|
+
local winborder = vim.fn.exists('+winborder') == 0 and '' or vim.o.winborder
|
|
2367
|
+
local default_config = {
|
|
2368
|
+
relative = 'editor',
|
|
2369
|
+
anchor = 'SW',
|
|
2370
|
+
width = math.floor(0.618 * max_width),
|
|
2371
|
+
height = math.floor(0.618 * max_height),
|
|
2372
|
+
col = 0,
|
|
2373
|
+
row = max_height + (has_tabline and 1 or 0),
|
|
2374
|
+
border = winborder == '' and 'single' or nil,
|
|
2375
|
+
style = 'minimal',
|
|
2376
|
+
noautocmd = is_for_open,
|
|
2377
|
+
-- Use high enough value to be on top of built-in windows (pmenu, etc.)
|
|
2378
|
+
zindex = 251,
|
|
2379
|
+
}
|
|
2380
|
+
local config = vim.tbl_deep_extend('force', default_config, H.expand_callable(win_config) or {})
|
|
2381
|
+
|
|
2382
|
+
-- Tweak config values to ensure they are proper
|
|
2383
|
+
if (config.border or winborder) == 'none' then config.border = { '', ' ', '', '', '', ' ', '', '' } end
|
|
2384
|
+
-- - Adjust dimensions accounting for actually present border parts
|
|
2385
|
+
local bor, n = config.border, type(config.border) == 'table' and #config.border or 0
|
|
2386
|
+
local height_offset = n == 0 and 2 or ((bor[1 % n + 1] == '' and 0 or 1) + (bor[5 % n + 1] == '' and 0 or 1))
|
|
2387
|
+
local width_offset = n == 0 and 2 or ((bor[3 % n + 1] == '' and 0 or 1) + (bor[7 % n + 1] == '' and 0 or 1))
|
|
2388
|
+
config.height = math.max(math.min(config.height, max_height - height_offset), 1)
|
|
2389
|
+
config.width = math.max(math.min(config.width, max_width - width_offset), 1)
|
|
2390
|
+
|
|
2391
|
+
return config
|
|
2392
|
+
end
|
|
2393
|
+
|
|
2394
|
+
H.picker_track_lost_focus = function(picker)
|
|
2395
|
+
local track = vim.schedule_wrap(function()
|
|
2396
|
+
local is_cur_win = vim.api.nvim_get_current_win() == picker.windows.main
|
|
2397
|
+
local is_proper_focus = is_cur_win and (H.cache.is_in_getcharstr or vim.fn.mode() ~= 'n')
|
|
2398
|
+
if is_proper_focus then return end
|
|
2399
|
+
if _G.MiniInput ~= nil and _G.MiniInput.get_state() ~= nil then return end
|
|
2400
|
+
if H.cache.is_in_getcharstr then return vim.api.nvim_feedkeys('\3', 't', true) end
|
|
2401
|
+
H.picker_stop(picker, true)
|
|
2402
|
+
end)
|
|
2403
|
+
H.timers.focus:start(1000, 1000, track)
|
|
2404
|
+
end
|
|
2405
|
+
|
|
2406
|
+
H.picker_set_items = function(picker, items, opts)
|
|
2407
|
+
-- Compute string items to work with (along with their lower variants)
|
|
2408
|
+
local stritems, stritems_ignorecase, tolower = {}, {}, H.tolower
|
|
2409
|
+
local poke_picker = H.poke_picker_throttle(opts.querytick)
|
|
2410
|
+
for i, x in ipairs(items) do
|
|
2411
|
+
if not poke_picker() then return end
|
|
2412
|
+
local to_add = H.item_to_string(x)
|
|
2413
|
+
table.insert(stritems, to_add)
|
|
2414
|
+
table.insert(stritems_ignorecase, tolower(to_add))
|
|
2415
|
+
end
|
|
2416
|
+
|
|
2417
|
+
picker.items, picker.stritems, picker.stritems_ignorecase = items, stritems, stritems_ignorecase
|
|
2418
|
+
picker.cache, picker.marked_inds_map = {}, {}
|
|
2419
|
+
H.picker_set_busy(picker, false)
|
|
2420
|
+
|
|
2421
|
+
H.picker_set_match_inds(picker, H.seq_along(items))
|
|
2422
|
+
-- Force update visible range for correct "show" lines computation
|
|
2423
|
+
H.picker_set_current_ind(picker, picker.current_ind, true)
|
|
2424
|
+
H.picker_update(picker, opts.do_match)
|
|
2425
|
+
end
|
|
2426
|
+
|
|
2427
|
+
H.item_to_string = function(item)
|
|
2428
|
+
item = H.expand_callable(item)
|
|
2429
|
+
if type(item) == 'string' then return item end
|
|
2430
|
+
if type(item) == 'table' and type(item.text) == 'string' then return item.text end
|
|
2431
|
+
return vim.inspect(item, { newline = ' ', indent = '' })
|
|
2432
|
+
end
|
|
2433
|
+
|
|
2434
|
+
H.picker_set_busy = function(picker, value)
|
|
2435
|
+
picker.is_busy = value
|
|
2436
|
+
|
|
2437
|
+
-- NOTE: Don't precompute highlight group to always set a valid one
|
|
2438
|
+
local update_border_hl = function()
|
|
2439
|
+
H.timers.busy:stop()
|
|
2440
|
+
H.win_update_hl(picker.windows.main, 'FloatBorder', picker.is_busy and 'MiniPickBorderBusy' or 'MiniPickBorder')
|
|
2441
|
+
end
|
|
2442
|
+
|
|
2443
|
+
if value then return H.timers.busy:start(picker.opts.delay.busy, 0, vim.schedule_wrap(update_border_hl)) end
|
|
2444
|
+
update_border_hl()
|
|
2445
|
+
end
|
|
2446
|
+
|
|
2447
|
+
H.picker_set_match_inds = function(picker, inds)
|
|
2448
|
+
if inds == nil then return end
|
|
2449
|
+
H.picker_set_busy(picker, false)
|
|
2450
|
+
|
|
2451
|
+
picker.match_inds = inds
|
|
2452
|
+
|
|
2453
|
+
local cache_prompt = table.concat(picker.query)
|
|
2454
|
+
if picker.opts.options.use_cache then picker.cache[cache_prompt] = { inds = inds } end
|
|
2455
|
+
|
|
2456
|
+
-- Always show result of updated matches
|
|
2457
|
+
H.picker_show_main(picker)
|
|
2458
|
+
|
|
2459
|
+
-- Reset current index if match indexes are updated
|
|
2460
|
+
H.picker_set_current_ind(picker, 1)
|
|
2461
|
+
|
|
2462
|
+
-- Trigger relevant event if not already inside it
|
|
2463
|
+
if not H.inside_minipickmatch then
|
|
2464
|
+
H.inside_minipickmatch = true
|
|
2465
|
+
vim.api.nvim_exec_autocmds('User', { pattern = 'MiniPickMatch' })
|
|
2466
|
+
H.inside_minipickmatch = nil
|
|
2467
|
+
end
|
|
2468
|
+
end
|
|
2469
|
+
|
|
2470
|
+
H.picker_set_current_ind = function(picker, ind, force_update)
|
|
2471
|
+
if picker.items == nil or #picker.match_inds == 0 then
|
|
2472
|
+
picker.current_ind, picker.visible_range = nil, {}
|
|
2473
|
+
return
|
|
2474
|
+
end
|
|
2475
|
+
|
|
2476
|
+
-- Wrap index around edges
|
|
2477
|
+
local n_matches = #picker.match_inds
|
|
2478
|
+
ind = (ind - 1) % n_matches + 1
|
|
2479
|
+
|
|
2480
|
+
-- (Re)Compute visible range (centers current index if it is currently outside)
|
|
2481
|
+
local from, to, querytick = picker.visible_range.from, picker.visible_range.to, picker.visible_range.querytick
|
|
2482
|
+
local needs_update = H.querytick ~= querytick or from == nil or to == nil or not (from <= ind and ind <= to)
|
|
2483
|
+
if (force_update or needs_update) and H.is_valid_win(picker.windows.main) then
|
|
2484
|
+
local win_height = vim.api.nvim_win_get_height(picker.windows.main)
|
|
2485
|
+
to = math.min(n_matches, math.floor(ind + 0.5 * win_height))
|
|
2486
|
+
from = math.max(1, to - win_height + 1)
|
|
2487
|
+
to = from + math.min(win_height, n_matches) - 1
|
|
2488
|
+
end
|
|
2489
|
+
|
|
2490
|
+
-- Set data
|
|
2491
|
+
picker.current_ind = ind
|
|
2492
|
+
picker.visible_range = { from = from, to = to, querytick = H.querytick }
|
|
2493
|
+
end
|
|
2494
|
+
|
|
2495
|
+
H.picker_set_inds = {
|
|
2496
|
+
all = function(picker, inds) H.picker_set_match_inds(H.pickers.active, inds) end,
|
|
2497
|
+
current = function(picker, inds)
|
|
2498
|
+
if inds[1] == nil or picker.match_inds == nil then return end
|
|
2499
|
+
local current_match_ind, current_abs_ind = nil, inds[1]
|
|
2500
|
+
for i, match_abs_ind in ipairs(picker.match_inds) do
|
|
2501
|
+
if match_abs_ind == current_abs_ind then current_match_ind = i end
|
|
2502
|
+
end
|
|
2503
|
+
if current_match_ind == nil then H.error('Current match index should be present among all current matches') end
|
|
2504
|
+
H.picker_set_current_ind(picker, current_match_ind, true)
|
|
2505
|
+
end,
|
|
2506
|
+
marked = function(picker, inds)
|
|
2507
|
+
if picker.items == nil then return end
|
|
2508
|
+
local marked_inds_map, n_items = {}, #picker.items
|
|
2509
|
+
for _, ind in ipairs(inds) do
|
|
2510
|
+
if not (1 <= ind and ind <= n_items) then H.error('Marked indexes should be from 1 to number of items') end
|
|
2511
|
+
marked_inds_map[ind] = true
|
|
2512
|
+
end
|
|
2513
|
+
picker.marked_inds_map = marked_inds_map
|
|
2514
|
+
end,
|
|
2515
|
+
}
|
|
2516
|
+
|
|
2517
|
+
H.picker_set_lines = function(picker)
|
|
2518
|
+
local buf_id, win_id = picker.buffers.main, picker.windows.main
|
|
2519
|
+
if not (H.is_valid_buf(buf_id) and H.is_valid_win(win_id)) then return end
|
|
2520
|
+
|
|
2521
|
+
if picker.is_busy then return end
|
|
2522
|
+
|
|
2523
|
+
local visible_range, query = picker.visible_range, picker.query
|
|
2524
|
+
if picker.items == nil or visible_range.from == nil or visible_range.to == nil then
|
|
2525
|
+
picker.shown_inds = {}
|
|
2526
|
+
picker.opts.source.show(buf_id, {}, query)
|
|
2527
|
+
H.clear_namespace(buf_id, H.ns_id.matches)
|
|
2528
|
+
return
|
|
2529
|
+
end
|
|
2530
|
+
|
|
2531
|
+
-- Construct target items
|
|
2532
|
+
local items_to_show, items, shown_inds, match_inds = {}, picker.items, {}, picker.match_inds
|
|
2533
|
+
local cur_ind, cur_line = picker.current_ind, nil
|
|
2534
|
+
local marked_inds_map, marked_lnums = picker.marked_inds_map, {}
|
|
2535
|
+
local is_from_bottom = picker.opts.options.content_from_bottom
|
|
2536
|
+
local from = is_from_bottom and visible_range.to or visible_range.from
|
|
2537
|
+
local to = is_from_bottom and visible_range.from or visible_range.to
|
|
2538
|
+
for i = from, to, (from <= to and 1 or -1) do
|
|
2539
|
+
table.insert(shown_inds, i)
|
|
2540
|
+
table.insert(items_to_show, items[match_inds[i]])
|
|
2541
|
+
if i == cur_ind then cur_line = #items_to_show end
|
|
2542
|
+
if marked_inds_map[match_inds[i]] then table.insert(marked_lnums, #items_to_show) end
|
|
2543
|
+
end
|
|
2544
|
+
|
|
2545
|
+
local n_empty_top_lines = is_from_bottom and (vim.api.nvim_win_get_height(win_id) - #items_to_show) or 0
|
|
2546
|
+
cur_line = cur_line + n_empty_top_lines
|
|
2547
|
+
marked_lnums = vim.tbl_map(function(x) return x + n_empty_top_lines end, marked_lnums)
|
|
2548
|
+
|
|
2549
|
+
-- Update visible lines accounting for "from_bottom" direction
|
|
2550
|
+
picker.shown_inds = shown_inds
|
|
2551
|
+
picker.opts.source.show(buf_id, items_to_show, query)
|
|
2552
|
+
if n_empty_top_lines > 0 then
|
|
2553
|
+
local empty_lines = vim.fn['repeat']({ '' }, n_empty_top_lines)
|
|
2554
|
+
vim.api.nvim_buf_set_lines(buf_id, 0, 0, true, empty_lines)
|
|
2555
|
+
end
|
|
2556
|
+
|
|
2557
|
+
local ns_id = H.ns_id.matches
|
|
2558
|
+
H.clear_namespace(buf_id, ns_id)
|
|
2559
|
+
|
|
2560
|
+
-- Add highlighting for marked lines
|
|
2561
|
+
local marked_opts = { end_col = 0, hl_group = 'MiniPickMatchMarked', priority = 202 }
|
|
2562
|
+
for _, lnum in ipairs(marked_lnums) do
|
|
2563
|
+
marked_opts.end_row = lnum
|
|
2564
|
+
H.set_extmark(buf_id, ns_id, lnum - 1, 0, marked_opts)
|
|
2565
|
+
end
|
|
2566
|
+
|
|
2567
|
+
-- Update current item
|
|
2568
|
+
if cur_line > vim.api.nvim_buf_line_count(buf_id) then return end
|
|
2569
|
+
|
|
2570
|
+
local cur_opts = { end_row = cur_line, end_col = 0, hl_eol = true, hl_group = 'MiniPickMatchCurrent', priority = 201 }
|
|
2571
|
+
H.set_extmark(buf_id, ns_id, cur_line - 1, 0, cur_opts)
|
|
2572
|
+
|
|
2573
|
+
-- - Update cursor if showing item matches (needed for 'scroll_{left,right}')
|
|
2574
|
+
local cursor = vim.api.nvim_win_get_cursor(win_id)
|
|
2575
|
+
if picker.view_state == 'main' and cursor[1] ~= cur_line then H.set_cursor(win_id, cur_line, cursor[2] + 1) end
|
|
2576
|
+
end
|
|
2577
|
+
|
|
2578
|
+
H.picker_match = function(picker)
|
|
2579
|
+
if picker.items == nil then return end
|
|
2580
|
+
|
|
2581
|
+
-- Try to use cache first
|
|
2582
|
+
local prompt_cache
|
|
2583
|
+
if picker.opts.options.use_cache then prompt_cache = picker.cache[table.concat(picker.query)] end
|
|
2584
|
+
if prompt_cache ~= nil then return H.picker_set_match_inds(picker, prompt_cache.inds) end
|
|
2585
|
+
|
|
2586
|
+
local is_ignorecase = H.query_is_ignorecase(picker.query)
|
|
2587
|
+
local stritems = is_ignorecase and picker.stritems_ignorecase or picker.stritems
|
|
2588
|
+
local query = is_ignorecase and vim.tbl_map(H.tolower, picker.query) or picker.query
|
|
2589
|
+
|
|
2590
|
+
H.picker_set_busy(picker, true)
|
|
2591
|
+
local new_inds = picker.opts.source.match(stritems, picker.match_inds, query)
|
|
2592
|
+
H.picker_set_match_inds(picker, new_inds)
|
|
2593
|
+
end
|
|
2594
|
+
|
|
2595
|
+
H.query_is_ignorecase = function(query)
|
|
2596
|
+
if not vim.o.ignorecase then return false end
|
|
2597
|
+
if not vim.o.smartcase then return true end
|
|
2598
|
+
local prompt = table.concat(query)
|
|
2599
|
+
return prompt == vim.fn.tolower(prompt)
|
|
2600
|
+
end
|
|
2601
|
+
|
|
2602
|
+
H.normalize_mappings = function(mappings)
|
|
2603
|
+
local make_data = function(char, name, func, is_custom)
|
|
2604
|
+
return { char = char, name = name, func = is_custom and func or H.actions[name], is_custom = is_custom }
|
|
2605
|
+
end
|
|
2606
|
+
|
|
2607
|
+
local res = {}
|
|
2608
|
+
for name, rhs in pairs(mappings) do
|
|
2609
|
+
local is_custom = type(rhs) == 'table'
|
|
2610
|
+
local char = is_custom and rhs.char or rhs
|
|
2611
|
+
local key = H.replace_termcodes(char)
|
|
2612
|
+
-- Omit disabled keys and prefer custom actions over built-ins
|
|
2613
|
+
if key ~= '' and (res[key] == nil or not res[key].is_custom) then
|
|
2614
|
+
if res[key] ~= nil then H.notify('Duplicating mapping keys: ' .. name .. ' and ' .. res[key].name, 'WARN') end
|
|
2615
|
+
res[key] = make_data(char, name, rhs.func, is_custom)
|
|
2616
|
+
end
|
|
2617
|
+
end
|
|
2618
|
+
|
|
2619
|
+
-- Populate alternative keys, but not force them
|
|
2620
|
+
local home, up, down = H.replace_termcodes('<Home>'), H.replace_termcodes('<Up>'), H.replace_termcodes('<Down>')
|
|
2621
|
+
res[home] = res[home] or make_data('<Home>', 'move_start', nil, false)
|
|
2622
|
+
res[up] = res[up] or make_data('<Up>', 'move_up', nil, false)
|
|
2623
|
+
res[down] = res[down] or make_data('<Down>', 'move_down', nil, false)
|
|
2624
|
+
|
|
2625
|
+
return res
|
|
2626
|
+
end
|
|
2627
|
+
|
|
2628
|
+
H.picker_set_bordertext = function(picker)
|
|
2629
|
+
local opts = picker.opts
|
|
2630
|
+
local win_id = picker.windows.main
|
|
2631
|
+
if not H.is_valid_win(win_id) then return end
|
|
2632
|
+
|
|
2633
|
+
-- Compute main text managing views separately and truncating from left
|
|
2634
|
+
local view_state, win_width = picker.view_state, vim.api.nvim_win_get_width(win_id)
|
|
2635
|
+
local config
|
|
2636
|
+
if view_state == 'main' then
|
|
2637
|
+
local caret, query = picker.caret, picker.query
|
|
2638
|
+
local prompt_prefix, prompt_caret = opts.window.prompt_prefix, opts.window.prompt_caret
|
|
2639
|
+
local max_width = math.max(1, win_width - vim.fn.strchars(prompt_prefix) - vim.fn.strchars(prompt_caret))
|
|
2640
|
+
|
|
2641
|
+
-- Try to put caret in the center if there is not enough room to show the
|
|
2642
|
+
-- whole query (as in 'mini.tabline'). Do that after concatenating query
|
|
2643
|
+
-- parts as (after `set_picker_query()`) they can have multiple characters.
|
|
2644
|
+
local before_caret = table.concat(vim.list_slice(query, 1, caret - 1))
|
|
2645
|
+
local after_caret = table.concat(vim.list_slice(query, caret, #query))
|
|
2646
|
+
local w_before, w_after = vim.fn.strchars(before_caret), vim.fn.strchars(after_caret)
|
|
2647
|
+
|
|
2648
|
+
local w_right = math.min(math.floor(0.5 * max_width), w_after)
|
|
2649
|
+
local w_left = math.min(math.max(max_width - w_right, 0), w_before)
|
|
2650
|
+
w_right = math.min(math.max(max_width - w_left, 0), w_after)
|
|
2651
|
+
|
|
2652
|
+
-- Show standard "there is more" padding symbols if needed
|
|
2653
|
+
local pad_left, pad_right = w_left == w_before and '' or '…', w_right == w_after and '' or '…'
|
|
2654
|
+
w_left, w_right = w_left - (pad_left == '' and 0 or 1), w_right - (pad_right == '' and 0 or 1)
|
|
2655
|
+
|
|
2656
|
+
before_caret = vim.fn.strcharpart(before_caret, w_before - w_left, w_left)
|
|
2657
|
+
after_caret = vim.fn.strcharpart(after_caret, 0, w_right)
|
|
2658
|
+
|
|
2659
|
+
local prompt = { { prompt_prefix, 'MiniPickPromptPrefix' }, { prompt_caret, 'MiniPickPromptCaret' } }
|
|
2660
|
+
if after_caret ~= '' then table.insert(prompt, 3, { after_caret .. pad_right, 'MiniPickPrompt' }) end
|
|
2661
|
+
if before_caret ~= '' then table.insert(prompt, 2, { pad_left .. before_caret, 'MiniPickPrompt' }) end
|
|
2662
|
+
config = { title = prompt }
|
|
2663
|
+
end
|
|
2664
|
+
|
|
2665
|
+
local has_items = picker.items ~= nil
|
|
2666
|
+
if view_state == 'preview' and has_items then
|
|
2667
|
+
local stritem_cur = picker.stritems[picker.match_inds[picker.current_ind]] or ''
|
|
2668
|
+
-- Sanitize title
|
|
2669
|
+
stritem_cur = stritem_cur:gsub('%z', '│'):gsub('%s', ' ')
|
|
2670
|
+
config = { title = { { H.fit_to_width(' ' .. stritem_cur .. ' ', win_width), 'MiniPickBorderText' } } }
|
|
2671
|
+
end
|
|
2672
|
+
|
|
2673
|
+
if view_state == 'info' then
|
|
2674
|
+
config = { title = { { H.fit_to_width(' Info ', win_width), 'MiniPickBorderText' } } }
|
|
2675
|
+
end
|
|
2676
|
+
|
|
2677
|
+
-- Compute helper footer only if Neovim version permits and not in busy
|
|
2678
|
+
-- picker (otherwise it will flicker number of matches data on char delete)
|
|
2679
|
+
local nvim_has_window_footer = vim.fn.has('nvim-0.10') == 1
|
|
2680
|
+
if nvim_has_window_footer and not picker.is_busy then
|
|
2681
|
+
config.footer, config.footer_pos = H.picker_compute_footer(picker, win_id), 'left'
|
|
2682
|
+
end
|
|
2683
|
+
|
|
2684
|
+
-- Respect `options.content_from_bottom`
|
|
2685
|
+
if nvim_has_window_footer and opts.options.content_from_bottom then
|
|
2686
|
+
config.title, config.footer = config.footer, config.title
|
|
2687
|
+
end
|
|
2688
|
+
|
|
2689
|
+
local win_list_option = vim.api.nvim_get_option_value('list', { scope = 'local', win = win_id })
|
|
2690
|
+
vim.api.nvim_win_set_config(win_id, config)
|
|
2691
|
+
vim.api.nvim_set_option_value('list', win_list_option, { scope = 'local', win = win_id })
|
|
2692
|
+
end
|
|
2693
|
+
|
|
2694
|
+
H.picker_compute_footer = function(picker, win_id)
|
|
2695
|
+
local info = H.picker_get_general_info(picker)
|
|
2696
|
+
local source_name = string.format(' %s ', info.source_name):gsub('[%z%s]', ' ')
|
|
2697
|
+
local n_marked_text = info.n_marked == 0 and '' or (info.n_marked .. '/')
|
|
2698
|
+
local inds = string.format(' %s|%s|%s%s ', info.relative_current_ind, info.n_matched, n_marked_text, info.n_total)
|
|
2699
|
+
local win_width, source_width, inds_width =
|
|
2700
|
+
vim.api.nvim_win_get_width(win_id), vim.fn.strchars(source_name), vim.fn.strchars(inds)
|
|
2701
|
+
|
|
2702
|
+
local footer = { { H.fit_to_width(source_name, win_width), 'MiniPickBorderText' } }
|
|
2703
|
+
local n_spaces_between = win_width - (source_width + inds_width)
|
|
2704
|
+
if n_spaces_between > 0 then
|
|
2705
|
+
local border_hl = picker.is_busy and 'MiniPickBorderBusy' or 'MiniPickBorder'
|
|
2706
|
+
local part_id = picker.opts.options.content_from_bottom and 2 or 6
|
|
2707
|
+
footer[2] = { H.win_get_bottom_border(win_id, part_id):rep(n_spaces_between), border_hl }
|
|
2708
|
+
footer[3] = { inds, 'MiniPickBorderText' }
|
|
2709
|
+
end
|
|
2710
|
+
return footer
|
|
2711
|
+
end
|
|
2712
|
+
|
|
2713
|
+
H.picker_stop = function(picker, abort)
|
|
2714
|
+
vim.tbl_map(function(timer) pcall(vim.loop.timer_stop, timer) end, H.timers)
|
|
2715
|
+
|
|
2716
|
+
-- Show cursor (work around `guicursor=''` actually leaving cursor hidden)
|
|
2717
|
+
if H.cache.guicursor == '' then vim.cmd('set guicursor=a: | redraw') end
|
|
2718
|
+
pcall(function() vim.o.guicursor = H.cache.guicursor end)
|
|
2719
|
+
|
|
2720
|
+
if picker == nil then return end
|
|
2721
|
+
|
|
2722
|
+
vim.api.nvim_exec_autocmds('User', { pattern = 'MiniPickStop' })
|
|
2723
|
+
|
|
2724
|
+
if abort then
|
|
2725
|
+
H.pickers = {}
|
|
2726
|
+
else
|
|
2727
|
+
local new_latest = H.copy_tables(picker)
|
|
2728
|
+
H.picker_free(H.pickers.latest)
|
|
2729
|
+
H.pickers = { active = nil, latest = new_latest }
|
|
2730
|
+
end
|
|
2731
|
+
|
|
2732
|
+
H.set_curwin(picker.windows.target)
|
|
2733
|
+
pcall(vim.api.nvim_win_close, picker.windows.main, true)
|
|
2734
|
+
pcall(vim.api.nvim_buf_delete, picker.buffers.main, { force = true })
|
|
2735
|
+
pcall(vim.api.nvim_buf_delete, picker.buffers.info, { force = true })
|
|
2736
|
+
picker.windows, picker.buffers = {}, {}
|
|
2737
|
+
|
|
2738
|
+
H.querytick = H.querytick + 1
|
|
2739
|
+
|
|
2740
|
+
if H.cache.getcharstr_errmsg ~= nil then error(H.cache.getcharstr_errmsg) end
|
|
2741
|
+
end
|
|
2742
|
+
|
|
2743
|
+
H.picker_free = function(picker)
|
|
2744
|
+
if picker == nil then return end
|
|
2745
|
+
picker.match_inds = nil
|
|
2746
|
+
picker.shown_inds = {}
|
|
2747
|
+
picker.cache = nil
|
|
2748
|
+
picker.stritems, picker.stritems_ignorecase, picker.marked_inds_map = nil, nil, nil
|
|
2749
|
+
picker.items = nil
|
|
2750
|
+
picker = nil
|
|
2751
|
+
vim.schedule(function() collectgarbage('collect') end)
|
|
2752
|
+
end
|
|
2753
|
+
|
|
2754
|
+
--stylua: ignore
|
|
2755
|
+
H.actions = {
|
|
2756
|
+
caret_left = function(picker, _) H.picker_move_caret(picker, -1) end,
|
|
2757
|
+
caret_right = function(picker, _) H.picker_move_caret(picker, 1) end,
|
|
2758
|
+
|
|
2759
|
+
choose = function(picker, _) return H.picker_choose(picker, nil) end,
|
|
2760
|
+
choose_in_split = function(picker, _) return H.picker_choose(picker, 'split') end,
|
|
2761
|
+
choose_in_tabpage = function(picker, _) return H.picker_choose(picker, 'tab split') end,
|
|
2762
|
+
choose_in_vsplit = function(picker, _) return H.picker_choose(picker, 'vsplit') end,
|
|
2763
|
+
choose_marked = function(picker, _)
|
|
2764
|
+
local ok, res = pcall(picker.opts.source.choose_marked, MiniPick.get_picker_matches().marked)
|
|
2765
|
+
if not ok then vim.schedule(function() H.error('Error during choose marked:\n' .. res) end) end
|
|
2766
|
+
return not (ok and res)
|
|
2767
|
+
end,
|
|
2768
|
+
|
|
2769
|
+
delete_char = function(picker, _) H.picker_query_delete(picker, 1) end,
|
|
2770
|
+
delete_char_right = function(picker, _) H.picker_query_delete(picker, 0) end,
|
|
2771
|
+
delete_left = function(picker, _) H.picker_query_delete(picker, picker.caret - 1) end,
|
|
2772
|
+
delete_word = function(picker, _)
|
|
2773
|
+
local init, n_del = picker.caret - 1, 0
|
|
2774
|
+
if init == 0 then return end
|
|
2775
|
+
local ref_is_keyword = vim.fn.match(picker.query[init], '[[:keyword:]]') >= 0
|
|
2776
|
+
for i = init, 1, -1 do
|
|
2777
|
+
local cur_is_keyword = vim.fn.match(picker.query[i], '[[:keyword:]]') >= 0
|
|
2778
|
+
if (ref_is_keyword and not cur_is_keyword) or (not ref_is_keyword and cur_is_keyword) then break end
|
|
2779
|
+
n_del = n_del + 1
|
|
2780
|
+
end
|
|
2781
|
+
H.picker_query_delete(picker, n_del)
|
|
2782
|
+
end,
|
|
2783
|
+
|
|
2784
|
+
mark = function(picker, _) H.picker_mark_indexes(picker, 'current') end,
|
|
2785
|
+
mark_all = function(picker, _) H.picker_mark_indexes(picker, 'all') end,
|
|
2786
|
+
|
|
2787
|
+
move_down = function(picker, _) H.picker_move_current(picker, 1) end,
|
|
2788
|
+
move_start = function(picker, _) H.picker_move_current(picker, nil, 1) end,
|
|
2789
|
+
move_up = function(picker, _) H.picker_move_current(picker, -1) end,
|
|
2790
|
+
|
|
2791
|
+
paste = function(picker, _)
|
|
2792
|
+
local reg_contents = H.picker_get_register_contents(picker):gsub('[\n\t]', ' ')
|
|
2793
|
+
for i = 1, vim.fn.strchars(reg_contents) do
|
|
2794
|
+
H.picker_query_add(picker, vim.fn.strcharpart(reg_contents, i - 1, 1))
|
|
2795
|
+
end
|
|
2796
|
+
end,
|
|
2797
|
+
|
|
2798
|
+
refine = function(picker, _) H.picker_refine(picker, 'all') end,
|
|
2799
|
+
refine_marked = function(picker, _) H.picker_refine(picker, 'marked') end,
|
|
2800
|
+
|
|
2801
|
+
scroll_down = function(picker, _) H.picker_scroll(picker, 'down') end,
|
|
2802
|
+
scroll_up = function(picker, _) H.picker_scroll(picker, 'up') end,
|
|
2803
|
+
scroll_left = function(picker, _) H.picker_scroll(picker, 'left') end,
|
|
2804
|
+
scroll_right = function(picker, _) H.picker_scroll(picker, 'right') end,
|
|
2805
|
+
|
|
2806
|
+
toggle_info = function(picker, _)
|
|
2807
|
+
if picker.view_state == 'info' then return H.picker_show_main(picker) end
|
|
2808
|
+
H.picker_show_info(picker)
|
|
2809
|
+
end,
|
|
2810
|
+
|
|
2811
|
+
toggle_preview = function(picker, _)
|
|
2812
|
+
if picker.view_state == 'preview' then return H.picker_show_main(picker) end
|
|
2813
|
+
H.picker_show_preview(picker)
|
|
2814
|
+
end,
|
|
2815
|
+
|
|
2816
|
+
stop = function(_, _) return true end,
|
|
2817
|
+
}
|
|
2818
|
+
|
|
2819
|
+
H.picker_query_add = function(picker, char)
|
|
2820
|
+
-- Determine if it **is** proper single character
|
|
2821
|
+
if not H.is_query_char(char) then return end
|
|
2822
|
+
table.insert(picker.query, picker.caret, char)
|
|
2823
|
+
picker.caret = picker.caret + 1
|
|
2824
|
+
H.querytick = H.querytick + 1
|
|
2825
|
+
|
|
2826
|
+
-- Adding character inside query might not result into narrowing matches, so
|
|
2827
|
+
-- reset match indexes. Use cache to speed this up.
|
|
2828
|
+
local should_reset = picker.items ~= nil and picker.caret <= #picker.query
|
|
2829
|
+
if should_reset then picker.match_inds = H.seq_along(picker.items) end
|
|
2830
|
+
end
|
|
2831
|
+
|
|
2832
|
+
H.is_query_char = function(char) return vim.fn.strchars(char) == 1 and vim.fn.char2nr(char) > 31 end
|
|
2833
|
+
|
|
2834
|
+
H.picker_query_delete = function(picker, n)
|
|
2835
|
+
local delete_to_left = n > 0
|
|
2836
|
+
local left = delete_to_left and math.max(picker.caret - n, 1) or picker.caret
|
|
2837
|
+
local right = delete_to_left and picker.caret - 1 or math.min(picker.caret + n, #picker.query)
|
|
2838
|
+
for i = right, left, -1 do
|
|
2839
|
+
table.remove(picker.query, i)
|
|
2840
|
+
end
|
|
2841
|
+
picker.caret = left
|
|
2842
|
+
H.querytick = H.querytick + 1
|
|
2843
|
+
|
|
2844
|
+
-- Deleting query character increases number of possible matches, so need to
|
|
2845
|
+
-- reset already matched indexes prior deleting. Use cache to speed this up.
|
|
2846
|
+
if picker.items ~= nil then picker.match_inds = H.seq_along(picker.items) end
|
|
2847
|
+
end
|
|
2848
|
+
|
|
2849
|
+
H.picker_choose = function(picker, pre_command)
|
|
2850
|
+
local cur_item = H.picker_get_current_item(picker)
|
|
2851
|
+
if cur_item == nil then return true end
|
|
2852
|
+
|
|
2853
|
+
local win_id_target = picker.windows.target
|
|
2854
|
+
if pre_command ~= nil and H.is_valid_win(win_id_target) then
|
|
2855
|
+
-- Work around Neovim not preserving cwd during `nvim_win_call`
|
|
2856
|
+
-- See: https://github.com/neovim/neovim/issues/32203
|
|
2857
|
+
local picker_cwd, global_cwd = vim.fn.getcwd(0), vim.fn.getcwd(-1, -1)
|
|
2858
|
+
vim.fn.chdir(global_cwd)
|
|
2859
|
+
vim.api.nvim_win_call(win_id_target, function()
|
|
2860
|
+
vim.cmd(pre_command)
|
|
2861
|
+
picker.windows.target = vim.api.nvim_get_current_win()
|
|
2862
|
+
end)
|
|
2863
|
+
vim.fn.chdir(picker_cwd)
|
|
2864
|
+
end
|
|
2865
|
+
|
|
2866
|
+
local ok, res = pcall(picker.opts.source.choose, cur_item)
|
|
2867
|
+
-- Delay error to have time to hide picker window
|
|
2868
|
+
if not ok then vim.schedule(function() H.error('Error during choose:\n' .. res) end) end
|
|
2869
|
+
-- Error or returning nothing, `nil`, or `false` should lead to picker stop
|
|
2870
|
+
return not (ok and res)
|
|
2871
|
+
end
|
|
2872
|
+
|
|
2873
|
+
H.picker_mark_indexes = function(picker, range_type)
|
|
2874
|
+
if picker.items == nil then return end
|
|
2875
|
+
local test_inds = range_type == 'current' and { picker.match_inds[picker.current_ind] } or picker.match_inds
|
|
2876
|
+
|
|
2877
|
+
-- Mark if not all marked, unmark otherwise
|
|
2878
|
+
local marked_inds_map, is_all_marked = picker.marked_inds_map, true
|
|
2879
|
+
for _, ind in ipairs(test_inds) do
|
|
2880
|
+
is_all_marked = is_all_marked and marked_inds_map[ind]
|
|
2881
|
+
end
|
|
2882
|
+
|
|
2883
|
+
-- NOTE: Set to `nil` and not `false` for easier counting of present values
|
|
2884
|
+
local new_val
|
|
2885
|
+
if not is_all_marked then new_val = true end
|
|
2886
|
+
for _, ind in ipairs(test_inds) do
|
|
2887
|
+
marked_inds_map[ind] = new_val
|
|
2888
|
+
end
|
|
2889
|
+
|
|
2890
|
+
if picker.view_state == 'info' then H.picker_show_info(picker) end
|
|
2891
|
+
end
|
|
2892
|
+
|
|
2893
|
+
H.picker_move_caret = function(picker, n) picker.caret = math.min(math.max(picker.caret + n, 1), #picker.query + 1) end
|
|
2894
|
+
|
|
2895
|
+
H.picker_move_current = function(picker, by, to)
|
|
2896
|
+
if picker.items == nil then return end
|
|
2897
|
+
local n_matches = #picker.match_inds
|
|
2898
|
+
if n_matches == 0 then return end
|
|
2899
|
+
|
|
2900
|
+
if to == nil then
|
|
2901
|
+
-- Account for content direction
|
|
2902
|
+
by = (picker.opts.options.content_from_bottom and -1 or 1) * by
|
|
2903
|
+
|
|
2904
|
+
-- Wrap around edges only if current index is at edge
|
|
2905
|
+
to = picker.current_ind
|
|
2906
|
+
if to == 1 and by < 0 then
|
|
2907
|
+
to = n_matches
|
|
2908
|
+
elseif to == n_matches and by > 0 then
|
|
2909
|
+
to = 1
|
|
2910
|
+
else
|
|
2911
|
+
to = to + by
|
|
2912
|
+
end
|
|
2913
|
+
to = math.min(math.max(to, 1), n_matches)
|
|
2914
|
+
end
|
|
2915
|
+
|
|
2916
|
+
H.picker_set_current_ind(picker, to)
|
|
2917
|
+
|
|
2918
|
+
-- Update not main buffer(s)
|
|
2919
|
+
if picker.view_state == 'info' then H.picker_show_info(picker) end
|
|
2920
|
+
if picker.view_state == 'preview' then H.picker_show_preview(picker) end
|
|
2921
|
+
end
|
|
2922
|
+
|
|
2923
|
+
H.picker_refine = function(picker, refine_type)
|
|
2924
|
+
if picker.items == nil then return end
|
|
2925
|
+
|
|
2926
|
+
-- Make current matches be new items to be matched with default match
|
|
2927
|
+
picker.opts.source.match = H.get_config().source.match or MiniPick.default_match
|
|
2928
|
+
picker.query, picker.caret = {}, 1
|
|
2929
|
+
MiniPick.set_picker_items(MiniPick.get_picker_matches()[refine_type] or {})
|
|
2930
|
+
|
|
2931
|
+
picker._refine = picker._refine or { orig_name = picker.opts.source.name, count = 0 }
|
|
2932
|
+
picker._refine.count = picker._refine.count + 1
|
|
2933
|
+
local count_suffix = picker._refine.count == 1 and '' or (' ' .. picker._refine.count)
|
|
2934
|
+
picker.opts.source.name = string.format('%s (Refine%s)', picker._refine.orig_name, count_suffix)
|
|
2935
|
+
end
|
|
2936
|
+
|
|
2937
|
+
H.picker_scroll = function(picker, direction)
|
|
2938
|
+
local win_id = picker.windows.main
|
|
2939
|
+
if picker.view_state == 'main' and (direction == 'down' or direction == 'up') then
|
|
2940
|
+
local n = (direction == 'down' and 1 or -1) * vim.api.nvim_win_get_height(win_id)
|
|
2941
|
+
H.picker_move_current(picker, n)
|
|
2942
|
+
else
|
|
2943
|
+
local keys = ({ down = '<C-f>', up = '<C-b>', left = 'zH', right = 'zL' })[direction]
|
|
2944
|
+
vim.api.nvim_win_call(win_id, function() vim.cmd('normal! ' .. H.replace_termcodes(keys)) end)
|
|
2945
|
+
end
|
|
2946
|
+
end
|
|
2947
|
+
|
|
2948
|
+
H.picker_get_current_item = function(picker)
|
|
2949
|
+
if picker.items == nil then return nil end
|
|
2950
|
+
return picker.items[picker.match_inds[picker.current_ind]]
|
|
2951
|
+
end
|
|
2952
|
+
|
|
2953
|
+
H.picker_get_register_contents = function(picker)
|
|
2954
|
+
local register = H.getcharstr({})
|
|
2955
|
+
-- Mimic some "insert object under cursor" behavior of Command-line mode
|
|
2956
|
+
local expand_var = ({ ['\1'] = '<cWORD>', ['\6'] = '<cfile>', ['\23'] = '<cword>' })[register]
|
|
2957
|
+
if expand_var then
|
|
2958
|
+
return vim.api.nvim_win_call(picker.windows.target, function() return vim.fn.expand(expand_var) end)
|
|
2959
|
+
end
|
|
2960
|
+
if register == '\f' then
|
|
2961
|
+
return vim.api.nvim_win_call(picker.windows.target, function() return vim.fn.getline('.') end)
|
|
2962
|
+
end
|
|
2963
|
+
local has_register, res = pcall(vim.fn.getreg, register)
|
|
2964
|
+
return has_register and res or ''
|
|
2965
|
+
end
|
|
2966
|
+
|
|
2967
|
+
H.picker_show_main = function(picker)
|
|
2968
|
+
H.set_winbuf(picker.windows.main, picker.buffers.main)
|
|
2969
|
+
vim.api.nvim_set_option_value('list', true, { scope = 'local', win = picker.windows.main })
|
|
2970
|
+
picker.view_state = 'main'
|
|
2971
|
+
end
|
|
2972
|
+
|
|
2973
|
+
H.picker_show_info = function(picker)
|
|
2974
|
+
-- General information
|
|
2975
|
+
local info = H.picker_get_general_info(picker)
|
|
2976
|
+
local lines = {
|
|
2977
|
+
'General',
|
|
2978
|
+
'Source name │ ' .. info.source_name,
|
|
2979
|
+
'Source cwd │ ' .. info.source_cwd,
|
|
2980
|
+
'Total items │ ' .. info.n_total,
|
|
2981
|
+
'Matched items │ ' .. info.n_matched,
|
|
2982
|
+
'Marked items │ ' .. info.n_marked,
|
|
2983
|
+
'Current index │ ' .. info.relative_current_ind,
|
|
2984
|
+
}
|
|
2985
|
+
local hl_lines = { 1 }
|
|
2986
|
+
|
|
2987
|
+
local append_char_data = function(data, header)
|
|
2988
|
+
if #data == 0 then return end
|
|
2989
|
+
table.insert(lines, '')
|
|
2990
|
+
table.insert(lines, header)
|
|
2991
|
+
table.insert(hl_lines, #lines)
|
|
2992
|
+
|
|
2993
|
+
local width_max = 0
|
|
2994
|
+
for _, t in ipairs(data) do
|
|
2995
|
+
local desc = t.name:gsub('[%s%p]', ' ')
|
|
2996
|
+
t.desc = vim.fn.toupper(desc:sub(1, 1)) .. desc:sub(2)
|
|
2997
|
+
t.width = vim.fn.strchars(t.desc)
|
|
2998
|
+
width_max = math.max(width_max, t.width)
|
|
2999
|
+
end
|
|
3000
|
+
table.sort(data, function(a, b) return a.desc < b.desc or (a.desc == b.desc and a.char < b.char) end)
|
|
3001
|
+
|
|
3002
|
+
for _, t in ipairs(data) do
|
|
3003
|
+
table.insert(lines, string.format('%s%s │ %s', t.desc, string.rep(' ', width_max - t.width), t.char))
|
|
3004
|
+
end
|
|
3005
|
+
end
|
|
3006
|
+
|
|
3007
|
+
local action_keys = H.normalize_mappings(picker.opts.mappings)
|
|
3008
|
+
append_char_data(vim.tbl_filter(function(x) return x.is_custom end, action_keys), 'Mappings (custom)')
|
|
3009
|
+
append_char_data(vim.tbl_filter(function(x) return not x.is_custom end, action_keys), 'Mappings (built-in)')
|
|
3010
|
+
|
|
3011
|
+
-- Manage buffer/window/state
|
|
3012
|
+
local buf_id_info = picker.buffers.info
|
|
3013
|
+
if not H.is_valid_buf(buf_id_info) then buf_id_info = H.create_scratch_buf('info') end
|
|
3014
|
+
picker.buffers.info = buf_id_info
|
|
3015
|
+
|
|
3016
|
+
H.set_buflines(buf_id_info, lines)
|
|
3017
|
+
H.set_winbuf(picker.windows.main, buf_id_info)
|
|
3018
|
+
picker.view_state = 'info'
|
|
3019
|
+
|
|
3020
|
+
local ns_id = H.ns_id.headers
|
|
3021
|
+
H.clear_namespace(buf_id_info, ns_id)
|
|
3022
|
+
for _, lnum in ipairs(hl_lines) do
|
|
3023
|
+
H.set_extmark(buf_id_info, ns_id, lnum - 1, 0, { end_row = lnum, end_col = 0, hl_group = 'MiniPickHeader' })
|
|
3024
|
+
end
|
|
3025
|
+
end
|
|
3026
|
+
|
|
3027
|
+
H.picker_get_general_info = function(picker)
|
|
3028
|
+
local has_items = picker.items ~= nil
|
|
3029
|
+
return {
|
|
3030
|
+
source_name = picker.opts.source.name or '---',
|
|
3031
|
+
source_cwd = vim.fn.fnamemodify(picker.opts.source.cwd, ':~') or '---',
|
|
3032
|
+
n_total = has_items and #picker.items or '-',
|
|
3033
|
+
n_matched = has_items and #picker.match_inds or '-',
|
|
3034
|
+
n_marked = has_items and vim.tbl_count(picker.marked_inds_map) or '-',
|
|
3035
|
+
relative_current_ind = has_items and picker.current_ind or '-',
|
|
3036
|
+
}
|
|
3037
|
+
end
|
|
3038
|
+
|
|
3039
|
+
H.picker_show_preview = function(picker)
|
|
3040
|
+
local preview = picker.opts.source.preview
|
|
3041
|
+
local item = H.picker_get_current_item(picker)
|
|
3042
|
+
if item == nil then return end
|
|
3043
|
+
|
|
3044
|
+
local win_id, buf_id = picker.windows.main, H.create_scratch_buf('preview')
|
|
3045
|
+
vim.bo[buf_id].bufhidden = 'wipe'
|
|
3046
|
+
H.set_winbuf(win_id, buf_id)
|
|
3047
|
+
vim.api.nvim_set_option_value('list', vim.go.list, { scope = 'local', win = win_id })
|
|
3048
|
+
preview(buf_id, item)
|
|
3049
|
+
picker.buffers.preview = vim.api.nvim_win_get_buf(win_id)
|
|
3050
|
+
picker.view_state = 'preview'
|
|
3051
|
+
H.ensure_redraw(picker)
|
|
3052
|
+
end
|
|
3053
|
+
|
|
3054
|
+
-- Default match --------------------------------------------------------------
|
|
3055
|
+
H.match_filter = function(inds, stritems, query)
|
|
3056
|
+
-- 'abc' and '*abc' - fuzzy; "'abc" and 'a' - exact substring;
|
|
3057
|
+
-- 'ab c' - grouped fuzzy; '^abc' and 'abc$' - exact substring at start/end.
|
|
3058
|
+
local is_fuzzy_forced, is_exact_plain, is_exact_start, is_exact_end =
|
|
3059
|
+
query[1] == '*', query[1] == "'", query[1] == '^', query[#query] == '$'
|
|
3060
|
+
local is_grouped, grouped_parts = H.match_query_group(query)
|
|
3061
|
+
|
|
3062
|
+
if is_fuzzy_forced or is_exact_plain or is_exact_start or is_exact_end then
|
|
3063
|
+
local start_offset = (is_fuzzy_forced or is_exact_plain or is_exact_start) and 2 or 1
|
|
3064
|
+
local end_offset = #query - ((not is_fuzzy_forced and not is_exact_plain and is_exact_end) and 1 or 0)
|
|
3065
|
+
query = vim.list_slice(query, start_offset, end_offset)
|
|
3066
|
+
elseif is_grouped then
|
|
3067
|
+
query = grouped_parts
|
|
3068
|
+
end
|
|
3069
|
+
|
|
3070
|
+
if #query == 0 then return {}, 'useall', query end
|
|
3071
|
+
|
|
3072
|
+
local is_fuzzy_plain = not (is_exact_plain or is_exact_start or is_exact_end) and #query > 1
|
|
3073
|
+
if is_fuzzy_forced or is_fuzzy_plain then return H.match_filter_fuzzy(inds, stritems, query), 'fuzzy', query end
|
|
3074
|
+
|
|
3075
|
+
local prefix = is_exact_start and '^' or ''
|
|
3076
|
+
local suffix = is_exact_end and '$' or ''
|
|
3077
|
+
local pattern = prefix .. vim.pesc(table.concat(query)) .. suffix
|
|
3078
|
+
|
|
3079
|
+
return H.match_filter_exact(inds, stritems, query, pattern), 'exact', query
|
|
3080
|
+
end
|
|
3081
|
+
|
|
3082
|
+
H.match_filter_exact = function(inds, stritems, query, pattern)
|
|
3083
|
+
local match_single = H.match_filter_exact_single
|
|
3084
|
+
local poke_picker = H.poke_picker_throttle(H.querytick)
|
|
3085
|
+
local match_data = {}
|
|
3086
|
+
for _, ind in ipairs(inds) do
|
|
3087
|
+
if not poke_picker() then return nil end
|
|
3088
|
+
local data = match_single(stritems[ind], ind, pattern)
|
|
3089
|
+
if data ~= nil then table.insert(match_data, data) end
|
|
3090
|
+
end
|
|
3091
|
+
|
|
3092
|
+
return match_data
|
|
3093
|
+
end
|
|
3094
|
+
|
|
3095
|
+
H.match_filter_exact_single = function(candidate, index, pattern)
|
|
3096
|
+
local start = string.find(candidate, pattern)
|
|
3097
|
+
if start == nil then return nil end
|
|
3098
|
+
|
|
3099
|
+
return { 0, start, index }
|
|
3100
|
+
end
|
|
3101
|
+
|
|
3102
|
+
H.match_ranges_exact = function(match_data, query)
|
|
3103
|
+
-- All matches have same match ranges relative to match start
|
|
3104
|
+
local cur_start, rel_ranges = 0, {}
|
|
3105
|
+
for i = 1, #query do
|
|
3106
|
+
rel_ranges[i] = { cur_start, cur_start + query[i]:len() - 1 }
|
|
3107
|
+
cur_start = rel_ranges[i][2] + 1
|
|
3108
|
+
end
|
|
3109
|
+
|
|
3110
|
+
local res = {}
|
|
3111
|
+
for i = 1, #match_data do
|
|
3112
|
+
local start = match_data[i][2]
|
|
3113
|
+
res[i] = vim.tbl_map(function(x) return { start + x[1], start + x[2] } end, rel_ranges)
|
|
3114
|
+
end
|
|
3115
|
+
|
|
3116
|
+
return res
|
|
3117
|
+
end
|
|
3118
|
+
|
|
3119
|
+
H.match_filter_fuzzy = function(inds, stritems, query)
|
|
3120
|
+
local match_single, find_query = H.match_filter_fuzzy_single, H.match_find_query
|
|
3121
|
+
local poke_picker = H.poke_picker_throttle(H.querytick)
|
|
3122
|
+
local match_data = {}
|
|
3123
|
+
for _, ind in ipairs(inds) do
|
|
3124
|
+
if not poke_picker() then return nil end
|
|
3125
|
+
local data = match_single(stritems[ind], ind, query, find_query)
|
|
3126
|
+
if data ~= nil then table.insert(match_data, data) end
|
|
3127
|
+
end
|
|
3128
|
+
return match_data
|
|
3129
|
+
end
|
|
3130
|
+
|
|
3131
|
+
H.match_filter_fuzzy_single = function(candidate, index, query, find_query)
|
|
3132
|
+
-- Search for query chars match positions with the following properties:
|
|
3133
|
+
-- - All are present in `candidate` in the same order.
|
|
3134
|
+
-- - Has smallest width among all such match positions.
|
|
3135
|
+
-- - Among same width has smallest first match.
|
|
3136
|
+
|
|
3137
|
+
-- Search forward to find matching positions with left-most last char match
|
|
3138
|
+
local first, last = find_query(candidate, query, 1)
|
|
3139
|
+
if first == nil then return nil end
|
|
3140
|
+
if first == last then return { 0, first, index, { first } } end
|
|
3141
|
+
|
|
3142
|
+
-- NOTE: This approach doesn't iterate **all** query matches. It is fine for
|
|
3143
|
+
-- width optimization but maybe not for more (like contiguous groups number).
|
|
3144
|
+
-- Example: for query {'a', 'b', 'c'} candidate 'aaxbbbc' will be matched as
|
|
3145
|
+
-- having 3 groups (indexes 2, 4, 7) but correct one is 2 groups (2, 6, 7).
|
|
3146
|
+
|
|
3147
|
+
-- Iteratively try to find better matches by advancing last match
|
|
3148
|
+
local best_first, best_last, best_width = first, last, last - first
|
|
3149
|
+
while last do
|
|
3150
|
+
local width = last - first
|
|
3151
|
+
if width < best_width then
|
|
3152
|
+
best_first, best_last, best_width = first, last, width
|
|
3153
|
+
end
|
|
3154
|
+
|
|
3155
|
+
first, last = find_query(candidate, query, first + 1)
|
|
3156
|
+
end
|
|
3157
|
+
|
|
3158
|
+
-- NOTE: No field names is not clear code, but consistently better performant
|
|
3159
|
+
return { best_last - best_first, best_first, index }
|
|
3160
|
+
end
|
|
3161
|
+
|
|
3162
|
+
H.match_ranges_fuzzy = function(match_data, query, stritems)
|
|
3163
|
+
local res, n_query, query_lens = {}, #query, vim.tbl_map(string.len, query)
|
|
3164
|
+
for i_match, data in ipairs(match_data) do
|
|
3165
|
+
local s, from, to = stritems[data[3]], data[2], data[2] + query_lens[1] - 1
|
|
3166
|
+
local ranges = { { from, to } }
|
|
3167
|
+
for j_query = 2, n_query do
|
|
3168
|
+
from, to = string.find(s, query[j_query], to + 1, true)
|
|
3169
|
+
ranges[j_query] = { from, to }
|
|
3170
|
+
end
|
|
3171
|
+
res[i_match] = ranges
|
|
3172
|
+
end
|
|
3173
|
+
return res
|
|
3174
|
+
end
|
|
3175
|
+
|
|
3176
|
+
H.match_find_query = function(s, query, init)
|
|
3177
|
+
local first, to = string.find(s, query[1], init, true)
|
|
3178
|
+
if first == nil then return nil, nil end
|
|
3179
|
+
|
|
3180
|
+
-- Both `first` and `last` indicate the start byte of first and last match
|
|
3181
|
+
local last = first
|
|
3182
|
+
for i = 2, #query do
|
|
3183
|
+
last, to = string.find(s, query[i], to + 1, true)
|
|
3184
|
+
if not last then return nil, nil end
|
|
3185
|
+
end
|
|
3186
|
+
return first, last
|
|
3187
|
+
end
|
|
3188
|
+
|
|
3189
|
+
H.match_query_group = function(query)
|
|
3190
|
+
local parts = { {} }
|
|
3191
|
+
for _, x in ipairs(query) do
|
|
3192
|
+
local is_whitespace = x:find('^%s+$') ~= nil
|
|
3193
|
+
if is_whitespace then table.insert(parts, {}) end
|
|
3194
|
+
if not is_whitespace then table.insert(parts[#parts], x) end
|
|
3195
|
+
end
|
|
3196
|
+
return #parts > 1, vim.tbl_map(table.concat, parts)
|
|
3197
|
+
end
|
|
3198
|
+
|
|
3199
|
+
H.match_sort = function(match_data)
|
|
3200
|
+
-- Spread indexes in width-start buckets
|
|
3201
|
+
local buckets, max_width, width_max_start = {}, 0, {}
|
|
3202
|
+
for i = 1, #match_data do
|
|
3203
|
+
local data, width, start = match_data[i], match_data[i][1], match_data[i][2]
|
|
3204
|
+
local buck_width = buckets[width] or {}
|
|
3205
|
+
local buck_start = buck_width[start] or {}
|
|
3206
|
+
table.insert(buck_start, data[3])
|
|
3207
|
+
buck_width[start] = buck_start
|
|
3208
|
+
buckets[width] = buck_width
|
|
3209
|
+
|
|
3210
|
+
max_width = math.max(max_width, width)
|
|
3211
|
+
width_max_start[width] = math.max(width_max_start[width] or 0, start)
|
|
3212
|
+
end
|
|
3213
|
+
|
|
3214
|
+
-- Sort index in place (to make stable sort) within buckets
|
|
3215
|
+
local poke_picker = H.poke_picker_throttle(H.querytick)
|
|
3216
|
+
for _, buck_width in pairs(buckets) do
|
|
3217
|
+
for _, buck_start in pairs(buck_width) do
|
|
3218
|
+
if not poke_picker() then return nil end
|
|
3219
|
+
table.sort(buck_start)
|
|
3220
|
+
end
|
|
3221
|
+
end
|
|
3222
|
+
|
|
3223
|
+
-- Gather indexes back in order
|
|
3224
|
+
local res = {}
|
|
3225
|
+
for width = 0, max_width do
|
|
3226
|
+
local buck_width = buckets[width]
|
|
3227
|
+
for start = 1, (width_max_start[width] or 0) do
|
|
3228
|
+
local buck_start = buck_width[start] or {}
|
|
3229
|
+
for i = 1, #buck_start do
|
|
3230
|
+
table.insert(res, buck_start[i])
|
|
3231
|
+
end
|
|
3232
|
+
end
|
|
3233
|
+
end
|
|
3234
|
+
|
|
3235
|
+
return res
|
|
3236
|
+
end
|
|
3237
|
+
|
|
3238
|
+
H.match_no_sort = function(match_data)
|
|
3239
|
+
return vim.tbl_map(function(x) return x[3] end, match_data)
|
|
3240
|
+
end
|
|
3241
|
+
|
|
3242
|
+
-- Default show ---------------------------------------------------------------
|
|
3243
|
+
H.get_icon = function(x, icons)
|
|
3244
|
+
local item_data = H.parse_item(x)
|
|
3245
|
+
local path = item_data.path or item_data.text or ''
|
|
3246
|
+
local path_type = H.get_fs_type(path)
|
|
3247
|
+
if path_type == 'none' then return { text = icons.none, hl = 'MiniPickNormal' } end
|
|
3248
|
+
|
|
3249
|
+
-- Prefer 'mini.icons'
|
|
3250
|
+
if _G.MiniIcons ~= nil then
|
|
3251
|
+
local category = path_type == 'directory' and 'directory' or 'file'
|
|
3252
|
+
local icon, hl = _G.MiniIcons.get(category, path)
|
|
3253
|
+
return { text = icon .. ' ', hl = hl }
|
|
3254
|
+
end
|
|
3255
|
+
|
|
3256
|
+
-- Try falling back to 'nvim-web-devicons'
|
|
3257
|
+
if path_type == 'directory' then return { text = icons.directory, hl = 'MiniPickIconDirectory' } end
|
|
3258
|
+
local has_devicons, devicons = pcall(require, 'nvim-web-devicons')
|
|
3259
|
+
if not has_devicons then return { text = icons.file, hl = 'MiniPickIconFile' } end
|
|
3260
|
+
|
|
3261
|
+
local icon, hl = devicons.get_icon(vim.fn.fnamemodify(path, ':t'), nil, { default = false })
|
|
3262
|
+
icon = type(icon) == 'string' and (icon .. ' ') or icons.file
|
|
3263
|
+
return { text = icon, hl = hl or 'MiniPickIconFile' }
|
|
3264
|
+
end
|
|
3265
|
+
|
|
3266
|
+
H.show_with_icons = function(buf_id, items, query) MiniPick.default_show(buf_id, items, query, { show_icons = true }) end
|
|
3267
|
+
|
|
3268
|
+
-- Items helpers for default functions ----------------------------------------
|
|
3269
|
+
H.parse_item = function(item)
|
|
3270
|
+
-- Try parsing table item first
|
|
3271
|
+
if type(item) == 'table' then return H.parse_item_table(item) end
|
|
3272
|
+
|
|
3273
|
+
-- Parse item's string representation
|
|
3274
|
+
local stritem = H.item_to_string(item)
|
|
3275
|
+
|
|
3276
|
+
-- - Buffer
|
|
3277
|
+
local ok, numitem = pcall(tonumber, stritem)
|
|
3278
|
+
if ok and H.is_valid_buf(numitem) then return { type = 'buffer', buf_id = numitem } end
|
|
3279
|
+
|
|
3280
|
+
-- File or Directory
|
|
3281
|
+
local path_type, path, lnum, col, rest = H.parse_path(stritem)
|
|
3282
|
+
if path_type ~= 'none' then return { type = path_type, path = path, lnum = lnum, col = col, text = rest } end
|
|
3283
|
+
|
|
3284
|
+
return {}
|
|
3285
|
+
end
|
|
3286
|
+
|
|
3287
|
+
H.parse_item_table = function(item)
|
|
3288
|
+
-- Buffer
|
|
3289
|
+
local buf_id = item.bufnr or item.buf_id or item.buf
|
|
3290
|
+
if H.is_valid_buf(buf_id) then
|
|
3291
|
+
--stylua: ignore
|
|
3292
|
+
return {
|
|
3293
|
+
type = 'buffer', buf_id = buf_id, path = item.path or vim.api.nvim_buf_get_name(buf_id),
|
|
3294
|
+
lnum = item.lnum, end_lnum = item.end_lnum,
|
|
3295
|
+
col = item.col, end_col = item.end_col,
|
|
3296
|
+
text = item.text,
|
|
3297
|
+
}
|
|
3298
|
+
end
|
|
3299
|
+
|
|
3300
|
+
-- File or Directory
|
|
3301
|
+
if type(item.path) == 'string' then
|
|
3302
|
+
local path_type = H.get_fs_type(item.path)
|
|
3303
|
+
if path_type == 'file' or path_type == 'uri' then
|
|
3304
|
+
--stylua: ignore
|
|
3305
|
+
return {
|
|
3306
|
+
type = path_type, path = item.path,
|
|
3307
|
+
lnum = item.lnum, end_lnum = item.end_lnum,
|
|
3308
|
+
col = item.col, end_col = item.end_col,
|
|
3309
|
+
text = item.text,
|
|
3310
|
+
}
|
|
3311
|
+
end
|
|
3312
|
+
|
|
3313
|
+
if path_type == 'directory' then return { type = 'directory', path = item.path } end
|
|
3314
|
+
end
|
|
3315
|
+
|
|
3316
|
+
return {}
|
|
3317
|
+
end
|
|
3318
|
+
|
|
3319
|
+
H.parse_path = function(x)
|
|
3320
|
+
if type(x) ~= 'string' or x == '' then return nil end
|
|
3321
|
+
-- Allow inputs like 'aa/bb', 'aa-5'. Also allow inputs for line/position
|
|
3322
|
+
-- separated by null character:
|
|
3323
|
+
-- - 'aa/bb\00010' (line 10).
|
|
3324
|
+
-- - 'aa/bb\00010\0005' (line 10, col 5).
|
|
3325
|
+
-- - 'aa/bb\00010\0005\000xx' (line 10, col 5, with "xx" description).
|
|
3326
|
+
local location_pattern = '()%z(%d+)%z?(%d*)%z?(.*)$'
|
|
3327
|
+
local from, lnum, col, rest = x:match(location_pattern)
|
|
3328
|
+
local path = x:sub(1, (from or 0) - 1)
|
|
3329
|
+
path = path:sub(1, 1) == '~' and ((vim.loop.os_homedir() or '~') .. path:sub(2)) or path
|
|
3330
|
+
|
|
3331
|
+
-- Verify that path is real
|
|
3332
|
+
local path_type = H.get_fs_type(path)
|
|
3333
|
+
if path_type == 'none' and path ~= '' then
|
|
3334
|
+
local cwd = H.pickers.active == nil and vim.fn.getcwd() or H.pickers.active.opts.source.cwd
|
|
3335
|
+
path = string.format('%s/%s', cwd, path)
|
|
3336
|
+
path_type = H.get_fs_type(path)
|
|
3337
|
+
end
|
|
3338
|
+
|
|
3339
|
+
return path_type, path, tonumber(lnum), tonumber(col), rest or ''
|
|
3340
|
+
end
|
|
3341
|
+
|
|
3342
|
+
H.get_fs_type = function(path)
|
|
3343
|
+
if path == '' then return 'none' end
|
|
3344
|
+
if vim.fn.filereadable(path) == 1 then return 'file' end
|
|
3345
|
+
if vim.fn.isdirectory(path) == 1 then return 'directory' end
|
|
3346
|
+
if H.parse_uri(path) ~= nil then return 'uri' end
|
|
3347
|
+
return 'none'
|
|
3348
|
+
end
|
|
3349
|
+
|
|
3350
|
+
-- Default preview ------------------------------------------------------------
|
|
3351
|
+
H.preview_file = function(buf_id, item_data, opts)
|
|
3352
|
+
-- Fully preview only accessible text files
|
|
3353
|
+
local is_text = H.is_file_text(item_data.path)
|
|
3354
|
+
if not is_text then return H.set_buflines(buf_id, { is_text == nil and '-No-access-' or '-Non-text-file-' }) end
|
|
3355
|
+
|
|
3356
|
+
-- Compute lines. Limit number of read lines to work better on large files.
|
|
3357
|
+
local has_lines, lines = pcall(vim.fn.readfile, item_data.path, '', (item_data.lnum or 1) + opts.n_context_lines)
|
|
3358
|
+
if not has_lines then return end
|
|
3359
|
+
|
|
3360
|
+
item_data.line_position = opts.line_position
|
|
3361
|
+
H.preview_set_lines(buf_id, lines, item_data)
|
|
3362
|
+
end
|
|
3363
|
+
|
|
3364
|
+
H.preview_directory = function(buf_id, item_data)
|
|
3365
|
+
local path = item_data.path
|
|
3366
|
+
local format = function(x) return x .. (vim.fn.isdirectory(path .. '/' .. x) == 1 and '/' or '') end
|
|
3367
|
+
local lines = vim.tbl_map(format, vim.fn.readdir(path))
|
|
3368
|
+
H.set_buflines(buf_id, lines)
|
|
3369
|
+
end
|
|
3370
|
+
|
|
3371
|
+
H.preview_buffer = function(buf_id, item_data, opts)
|
|
3372
|
+
-- NOTE: ideally just setting target buffer to window would be enough, but it
|
|
3373
|
+
-- has side effects. See https://github.com/neovim/neovim/issues/24973 .
|
|
3374
|
+
-- Reading lines and applying custom styling is a passable alternative.
|
|
3375
|
+
local buf_id_source = item_data.buf_id
|
|
3376
|
+
|
|
3377
|
+
-- Get lines from buffer ensuring it is loaded without important consequences
|
|
3378
|
+
local cache_eventignore = vim.o.eventignore
|
|
3379
|
+
vim.o.eventignore = 'BufEnter'
|
|
3380
|
+
vim.fn.bufload(buf_id_source)
|
|
3381
|
+
vim.o.eventignore = cache_eventignore
|
|
3382
|
+
local lines = vim.api.nvim_buf_get_lines(buf_id_source, 0, (item_data.lnum or 1) + opts.n_context_lines, false)
|
|
3383
|
+
|
|
3384
|
+
item_data.filetype, item_data.line_position = vim.bo[buf_id_source].filetype, opts.line_position
|
|
3385
|
+
H.preview_set_lines(buf_id, lines, item_data)
|
|
3386
|
+
end
|
|
3387
|
+
|
|
3388
|
+
H.preview_uri = function(buf_id, item_data, opts)
|
|
3389
|
+
item_data.buf_id = vim.uri_to_bufnr(item_data.path)
|
|
3390
|
+
H.preview_buffer(buf_id, item_data, opts)
|
|
3391
|
+
end
|
|
3392
|
+
|
|
3393
|
+
H.preview_inspect = function(buf_id, obj) H.set_buflines(buf_id, vim.split(vim.inspect(obj), '\n')) end
|
|
3394
|
+
|
|
3395
|
+
H.preview_set_lines = function(buf_id, lines, extra)
|
|
3396
|
+
-- Lines
|
|
3397
|
+
H.set_buflines(buf_id, lines)
|
|
3398
|
+
|
|
3399
|
+
-- Highlighting
|
|
3400
|
+
H.preview_highlight_region(buf_id, extra.lnum, extra.col, extra.end_lnum, extra.end_col)
|
|
3401
|
+
|
|
3402
|
+
if H.preview_should_highlight(buf_id) then
|
|
3403
|
+
local ft = extra.filetype or vim.filetype.match({ buf = buf_id, filename = extra.path })
|
|
3404
|
+
local has_lang, lang = pcall(vim.treesitter.language.get_lang, ft)
|
|
3405
|
+
lang = has_lang and lang or ft
|
|
3406
|
+
-- TODO: Remove `opts.error` after compatibility with Neovim=0.11 is dropped
|
|
3407
|
+
local has_parser, parser = pcall(vim.treesitter.get_parser, buf_id, lang, { error = false })
|
|
3408
|
+
has_parser = has_parser and parser ~= nil
|
|
3409
|
+
if has_parser then has_parser = pcall(vim.treesitter.start, buf_id, lang) end
|
|
3410
|
+
if not has_parser then vim.bo[buf_id].syntax = ft end
|
|
3411
|
+
end
|
|
3412
|
+
|
|
3413
|
+
-- Cursor position and window view. Find window (and not use picker window)
|
|
3414
|
+
-- for "outside window preview" (preview and main are different) to work.
|
|
3415
|
+
local win_id = vim.fn.bufwinid(buf_id)
|
|
3416
|
+
if win_id == -1 then return end
|
|
3417
|
+
H.set_cursor(win_id, extra.lnum, extra.col)
|
|
3418
|
+
local pos_keys = ({ top = 'zt', center = 'zz', bottom = 'zb' })[extra.line_position] or 'zt'
|
|
3419
|
+
pcall(vim.api.nvim_win_call, win_id, function() vim.cmd('normal! ' .. pos_keys) end)
|
|
3420
|
+
end
|
|
3421
|
+
|
|
3422
|
+
H.preview_should_highlight = function(buf_id)
|
|
3423
|
+
-- Highlight if buffer size is not too big, both in total and per line
|
|
3424
|
+
local buf_size = vim.api.nvim_buf_call(buf_id, function() return vim.fn.line2byte(vim.fn.line('$') + 1) end)
|
|
3425
|
+
return buf_size <= 1000000 and buf_size <= 1000 * vim.api.nvim_buf_line_count(buf_id)
|
|
3426
|
+
end
|
|
3427
|
+
|
|
3428
|
+
H.preview_highlight_region = function(buf_id, lnum, col, end_lnum, end_col)
|
|
3429
|
+
-- Highlight line
|
|
3430
|
+
if lnum == nil then return end
|
|
3431
|
+
local hl_line_opts = { end_row = lnum, end_col = 0, hl_eol = true, hl_group = 'MiniPickPreviewLine', priority = 201 }
|
|
3432
|
+
H.set_extmark(buf_id, H.ns_id.preview, lnum - 1, 0, hl_line_opts)
|
|
3433
|
+
|
|
3434
|
+
-- Highlight position/region
|
|
3435
|
+
if col == nil then return end
|
|
3436
|
+
|
|
3437
|
+
local ext_end_row, ext_end_col = lnum - 1, col
|
|
3438
|
+
if end_lnum ~= nil and end_col ~= nil then
|
|
3439
|
+
ext_end_row, ext_end_col = end_lnum - 1, end_col - 1
|
|
3440
|
+
end
|
|
3441
|
+
ext_end_col = H.get_next_char_bytecol(vim.fn.getbufline(buf_id, ext_end_row + 1)[1], ext_end_col)
|
|
3442
|
+
|
|
3443
|
+
local hl_region_opts = { end_row = ext_end_row, end_col = ext_end_col, priority = 202 }
|
|
3444
|
+
hl_region_opts.hl_group = 'MiniPickPreviewRegion'
|
|
3445
|
+
H.set_extmark(buf_id, H.ns_id.preview, lnum - 1, col - 1, hl_region_opts)
|
|
3446
|
+
end
|
|
3447
|
+
|
|
3448
|
+
-- Default choose -------------------------------------------------------------
|
|
3449
|
+
H.choose_path = function(win_target, item_data)
|
|
3450
|
+
local path = H.parse_uri(item_data.path) or item_data.path
|
|
3451
|
+
if item_data.type == 'directory' then
|
|
3452
|
+
return vim.api.nvim_win_call(win_target, function() vim.cmd('edit ' .. vim.fn.fnameescape(path)) end)
|
|
3453
|
+
end
|
|
3454
|
+
pcall(vim.api.nvim_win_call, win_target, function() vim.cmd("normal! m'") end)
|
|
3455
|
+
H.edit(path, win_target)
|
|
3456
|
+
H.choose_set_cursor(win_target, item_data.lnum, item_data.col)
|
|
3457
|
+
end
|
|
3458
|
+
|
|
3459
|
+
H.choose_buffer = function(win_target, item_data)
|
|
3460
|
+
pcall(vim.api.nvim_win_call, win_target, function() vim.cmd("normal! m'") end)
|
|
3461
|
+
H.set_winbuf(win_target, item_data.buf_id)
|
|
3462
|
+
H.choose_set_cursor(win_target, item_data.lnum, item_data.col)
|
|
3463
|
+
end
|
|
3464
|
+
|
|
3465
|
+
H.choose_print = function(x) print(vim.inspect(x)) end
|
|
3466
|
+
|
|
3467
|
+
H.choose_set_cursor = function(win_id, lnum, col)
|
|
3468
|
+
if lnum == nil then return end
|
|
3469
|
+
H.set_cursor(win_id, lnum, col)
|
|
3470
|
+
pcall(vim.api.nvim_win_call, win_id, function() vim.cmd('normal! zvzz') end)
|
|
3471
|
+
end
|
|
3472
|
+
|
|
3473
|
+
-- Builtins -------------------------------------------------------------------
|
|
3474
|
+
-- Stdout feed is the final result split into unknown places. This function is
|
|
3475
|
+
-- a longer version of `vim.split(table.concat(feed, ''), '\r?\n')` but allows
|
|
3476
|
+
-- to interrupt computation and doesn't create big string for the whole feed.
|
|
3477
|
+
H.set_picker_items_from_feed = function(feed, pattern, opts)
|
|
3478
|
+
-- Check active picker as `poke_picker()` will always be `true` in this case
|
|
3479
|
+
if not MiniPick.is_picker_active() then return end
|
|
3480
|
+
|
|
3481
|
+
-- Reuse input querytick to preserve intended reaction to query change
|
|
3482
|
+
-- NOTE: For intended effect, relies on presence of the active coroutine
|
|
3483
|
+
local poke_picker = H.poke_picker_throttle(opts.set_items_opts.querytick)
|
|
3484
|
+
|
|
3485
|
+
-- Realign feed so that each one ends in a pattern
|
|
3486
|
+
for i = 2, #feed do
|
|
3487
|
+
if not poke_picker() then return end
|
|
3488
|
+
local _, to = feed[i]:find(pattern)
|
|
3489
|
+
-- Account for chunks with no matches. Possibly several in a row.
|
|
3490
|
+
to = to or feed[i]:len()
|
|
3491
|
+
local j = i - 1
|
|
3492
|
+
while j > 1 and feed[j] == '' do
|
|
3493
|
+
j = j - 1
|
|
3494
|
+
end
|
|
3495
|
+
feed[j], feed[i] = feed[j] .. feed[i]:sub(1, to), feed[i]:sub(to + 1)
|
|
3496
|
+
end
|
|
3497
|
+
|
|
3498
|
+
local res, insert = {}, table.insert
|
|
3499
|
+
for i = 1, #feed do
|
|
3500
|
+
-- Poke once per feed item and not match, because latter is too much and
|
|
3501
|
+
-- might be less performant as each poke computes time.
|
|
3502
|
+
if not poke_picker() then return end
|
|
3503
|
+
for s in vim.gsplit(feed[i], pattern, { trimempty = true }) do
|
|
3504
|
+
insert(res, s)
|
|
3505
|
+
end
|
|
3506
|
+
-- Cleanup
|
|
3507
|
+
feed[i] = nil
|
|
3508
|
+
end
|
|
3509
|
+
|
|
3510
|
+
vim.schedule(function() MiniPick.set_picker_items(opts.postprocess(res), opts.set_items_opts) end)
|
|
3511
|
+
end
|
|
3512
|
+
|
|
3513
|
+
H.cli_postprocess = function(items)
|
|
3514
|
+
while items[#items] == '' do
|
|
3515
|
+
items[#items] = nil
|
|
3516
|
+
end
|
|
3517
|
+
return items
|
|
3518
|
+
end
|
|
3519
|
+
|
|
3520
|
+
H.is_executable = function(tool)
|
|
3521
|
+
if tool == 'fallback' then return true end
|
|
3522
|
+
return vim.fn.executable(tool) == 1
|
|
3523
|
+
end
|
|
3524
|
+
|
|
3525
|
+
H.files_get_tool = function()
|
|
3526
|
+
if H.is_executable('rg') then return 'rg' end
|
|
3527
|
+
if H.is_executable('fd') then return 'fd' end
|
|
3528
|
+
if H.is_executable('git') then return 'git' end
|
|
3529
|
+
return 'fallback'
|
|
3530
|
+
end
|
|
3531
|
+
|
|
3532
|
+
H.files_get_command = function(tool)
|
|
3533
|
+
if tool == 'rg' then return { 'rg', '--files', '--color=never' } end
|
|
3534
|
+
if tool == 'fd' then return { 'fd', '--type=f', '--color=never' } end
|
|
3535
|
+
if tool == 'git' then
|
|
3536
|
+
return { 'git', '-c', 'core.quotepath=false', 'ls-files', '--cached', '--others', '--exclude-standard' }
|
|
3537
|
+
end
|
|
3538
|
+
H.error([[Wrong 'tool' for `files` builtin.]])
|
|
3539
|
+
end
|
|
3540
|
+
|
|
3541
|
+
H.files_fallback_items = function(cwd)
|
|
3542
|
+
local poke_picker = H.poke_picker_throttle()
|
|
3543
|
+
local f = function()
|
|
3544
|
+
local items = {}
|
|
3545
|
+
for path, path_type in vim.fs.dir(cwd, { depth = math.huge }) do
|
|
3546
|
+
if not poke_picker() then return end
|
|
3547
|
+
if path_type == 'file' and H.is_file_text(string.format('%s/%s', cwd, path)) then table.insert(items, path) end
|
|
3548
|
+
end
|
|
3549
|
+
MiniPick.set_picker_items(items)
|
|
3550
|
+
end
|
|
3551
|
+
|
|
3552
|
+
vim.schedule(coroutine.wrap(f))
|
|
3553
|
+
end
|
|
3554
|
+
|
|
3555
|
+
H.grep_get_tool = function()
|
|
3556
|
+
if H.is_executable('rg') then return 'rg' end
|
|
3557
|
+
if H.is_executable('git') then return 'git' end
|
|
3558
|
+
return 'fallback'
|
|
3559
|
+
end
|
|
3560
|
+
|
|
3561
|
+
--stylua: ignore
|
|
3562
|
+
H.grep_get_command = function(tool, pattern, globs, method)
|
|
3563
|
+
if tool == 'rg' then
|
|
3564
|
+
local res = { 'rg', '--column', '--line-number', '--no-heading', '--field-match-separator', '\\x00', '--color=never' }
|
|
3565
|
+
table.insert(res, method == 'regex' and '--no-fixed-strings' or '--fixed-strings')
|
|
3566
|
+
for _, g in ipairs(globs) do
|
|
3567
|
+
table.insert(res, '--glob')
|
|
3568
|
+
-- NOTE: no `*` as default is important to not "override" ignoring files
|
|
3569
|
+
table.insert(res, g)
|
|
3570
|
+
end
|
|
3571
|
+
local case = vim.o.ignorecase and (vim.o.smartcase and 'smart-case' or 'ignore-case') or 'case-sensitive'
|
|
3572
|
+
vim.list_extend(res, { '--' .. case, '--', pattern })
|
|
3573
|
+
return res
|
|
3574
|
+
end
|
|
3575
|
+
if tool == 'git' then
|
|
3576
|
+
local res = { 'git', 'grep', '--column', '--line-number', '--null', '--color=never', '-e', pattern, '--', unpack(globs) }
|
|
3577
|
+
if vim.o.ignorecase then table.insert(res, 6, '--ignore-case') end
|
|
3578
|
+
if method == 'plain' then table.insert(res, 7, '--fixed-strings') end
|
|
3579
|
+
return res
|
|
3580
|
+
end
|
|
3581
|
+
H.error([[Wrong 'tool' for `grep` builtin.]])
|
|
3582
|
+
end
|
|
3583
|
+
|
|
3584
|
+
H.grep_fallback_items = function(pattern, cwd)
|
|
3585
|
+
local poke_picker = H.poke_picker_throttle()
|
|
3586
|
+
local f = function()
|
|
3587
|
+
local files, files_full = {}, {}
|
|
3588
|
+
for path, path_type in vim.fs.dir(cwd, { depth = math.huge }) do
|
|
3589
|
+
if not poke_picker() then return end
|
|
3590
|
+
local path_full = string.format('%s/%s', cwd, path)
|
|
3591
|
+
if path_type == 'file' and H.is_file_text(path_full) then
|
|
3592
|
+
table.insert(files, path)
|
|
3593
|
+
table.insert(files_full, path_full)
|
|
3594
|
+
end
|
|
3595
|
+
end
|
|
3596
|
+
|
|
3597
|
+
local items = {}
|
|
3598
|
+
for i, path in ipairs(files_full) do
|
|
3599
|
+
local file = files[i]
|
|
3600
|
+
if not poke_picker() then return end
|
|
3601
|
+
for lnum, l in ipairs(vim.fn.readfile(path)) do
|
|
3602
|
+
local col = string.find(l, pattern)
|
|
3603
|
+
if col ~= nil then table.insert(items, string.format('%s\0%d\0%d\0%s', file, lnum, col, l)) end
|
|
3604
|
+
end
|
|
3605
|
+
end
|
|
3606
|
+
|
|
3607
|
+
MiniPick.set_picker_items(items)
|
|
3608
|
+
end
|
|
3609
|
+
|
|
3610
|
+
vim.schedule(coroutine.wrap(f))
|
|
3611
|
+
end
|
|
3612
|
+
|
|
3613
|
+
-- Async ----------------------------------------------------------------------
|
|
3614
|
+
H.schedule_resume_is_active = vim.schedule_wrap(function(co) coroutine.resume(co, MiniPick.is_picker_active()) end)
|
|
3615
|
+
|
|
3616
|
+
H.poke_picker_throttle = function(querytick_ref)
|
|
3617
|
+
-- Allow calling this even if no picker is active
|
|
3618
|
+
if not MiniPick.is_picker_active() then
|
|
3619
|
+
return function() return true end
|
|
3620
|
+
end
|
|
3621
|
+
|
|
3622
|
+
local latest_time, dont_check_querytick = vim.loop.hrtime(), querytick_ref == nil
|
|
3623
|
+
local threshold = 1000000 * H.pickers.active.opts.delay.async
|
|
3624
|
+
local hrtime = vim.loop.hrtime
|
|
3625
|
+
local poke_is_picker_active = MiniPick.poke_is_picker_active
|
|
3626
|
+
return function()
|
|
3627
|
+
local now = hrtime()
|
|
3628
|
+
if (now - latest_time) < threshold then return true end
|
|
3629
|
+
latest_time = now
|
|
3630
|
+
-- Return positive if picker is active and no query updates (if asked)
|
|
3631
|
+
return poke_is_picker_active() and (dont_check_querytick or querytick_ref == H.querytick)
|
|
3632
|
+
end
|
|
3633
|
+
end
|
|
3634
|
+
|
|
3635
|
+
-- Utilities ------------------------------------------------------------------
|
|
3636
|
+
H.error = function(msg) error('(mini.pick) ' .. msg, 0) end
|
|
3637
|
+
|
|
3638
|
+
H.check_type = function(name, val, ref, allow_nil)
|
|
3639
|
+
if type(val) == ref or (ref == 'callable' and vim.is_callable(val)) or (allow_nil and val == nil) then return end
|
|
3640
|
+
H.error(string.format('`%s` should be %s, not %s', name, ref, type(val)))
|
|
3641
|
+
end
|
|
3642
|
+
|
|
3643
|
+
H.set_buf_name = function(buf_id, name) vim.api.nvim_buf_set_name(buf_id, 'minipick://' .. buf_id .. '/' .. name) end
|
|
3644
|
+
|
|
3645
|
+
H.notify = function(msg, level_name) vim.notify('(mini.pick) ' .. msg, vim.log.levels[level_name]) end
|
|
3646
|
+
|
|
3647
|
+
H.edit = function(path, win_id)
|
|
3648
|
+
if type(path) ~= 'string' then return end
|
|
3649
|
+
local b = vim.api.nvim_win_get_buf(win_id or 0)
|
|
3650
|
+
local try_mimic_buf_reuse = (vim.fn.bufname(b) == '' and vim.bo[b].buftype ~= 'quickfix' and not vim.bo[b].modified)
|
|
3651
|
+
and (#vim.fn.win_findbuf(b) == 1 and vim.deep_equal(vim.fn.getbufline(b, 1, '$'), { '' }))
|
|
3652
|
+
local buf_id = vim.fn.bufadd(vim.fn.fnamemodify(path, ':.'))
|
|
3653
|
+
-- Showing in window also loads. Use `pcall` to not error with swap messages.
|
|
3654
|
+
pcall(vim.api.nvim_win_set_buf, win_id or 0, buf_id)
|
|
3655
|
+
vim.bo[buf_id].buflisted = true
|
|
3656
|
+
if try_mimic_buf_reuse then pcall(vim.api.nvim_buf_delete, b, { unload = false }) end
|
|
3657
|
+
return buf_id
|
|
3658
|
+
end
|
|
3659
|
+
|
|
3660
|
+
H.is_valid_buf = function(buf_id) return type(buf_id) == 'number' and vim.api.nvim_buf_is_valid(buf_id) end
|
|
3661
|
+
|
|
3662
|
+
H.is_valid_win = function(win_id) return type(win_id) == 'number' and vim.api.nvim_win_is_valid(win_id) end
|
|
3663
|
+
|
|
3664
|
+
H.is_array_of = function(x, ref_type)
|
|
3665
|
+
if not H.islist(x) then return false end
|
|
3666
|
+
for i = 1, #x do
|
|
3667
|
+
if type(x[i]) ~= ref_type then return false end
|
|
3668
|
+
end
|
|
3669
|
+
return true
|
|
3670
|
+
end
|
|
3671
|
+
|
|
3672
|
+
H.create_scratch_buf = function(name)
|
|
3673
|
+
local buf_id = vim.api.nvim_create_buf(false, true)
|
|
3674
|
+
H.set_buf_name(buf_id, name)
|
|
3675
|
+
vim.bo[buf_id].matchpairs = ''
|
|
3676
|
+
vim.b[buf_id].minicursorword_disable = true
|
|
3677
|
+
vim.b[buf_id].miniindentscope_disable = true
|
|
3678
|
+
return buf_id
|
|
3679
|
+
end
|
|
3680
|
+
|
|
3681
|
+
H.get_first_valid_normal_window = function()
|
|
3682
|
+
for _, win_id in ipairs(vim.api.nvim_tabpage_list_wins(0)) do
|
|
3683
|
+
if vim.api.nvim_win_get_config(win_id).relative == '' then return win_id end
|
|
3684
|
+
end
|
|
3685
|
+
end
|
|
3686
|
+
|
|
3687
|
+
H.set_buflines = function(buf_id, lines) pcall(vim.api.nvim_buf_set_lines, buf_id, 0, -1, false, lines) end
|
|
3688
|
+
|
|
3689
|
+
H.set_winbuf = function(win_id, buf_id) vim.api.nvim_win_set_buf(win_id, buf_id) end
|
|
3690
|
+
|
|
3691
|
+
H.set_extmark = function(...) pcall(vim.api.nvim_buf_set_extmark, ...) end
|
|
3692
|
+
|
|
3693
|
+
H.set_cursor = function(win_id, lnum, col) pcall(vim.api.nvim_win_set_cursor, win_id, { lnum or 1, (col or 1) - 1 }) end
|
|
3694
|
+
|
|
3695
|
+
H.set_curwin = function(win_id)
|
|
3696
|
+
if not H.is_valid_win(win_id) then return end
|
|
3697
|
+
-- Explicitly preserve cursor to fix Neovim<0.10 after choosing position in
|
|
3698
|
+
-- already shown buffer
|
|
3699
|
+
local buf_id, cursor = vim.api.nvim_win_get_buf(win_id), vim.api.nvim_win_get_cursor(win_id)
|
|
3700
|
+
vim.api.nvim_set_current_win(win_id)
|
|
3701
|
+
if buf_id == vim.api.nvim_win_get_buf(win_id) then H.set_cursor(win_id, cursor[1], cursor[2] + 1) end
|
|
3702
|
+
end
|
|
3703
|
+
|
|
3704
|
+
H.clear_namespace = function(buf_id, ns_id) pcall(vim.api.nvim_buf_clear_namespace, buf_id, ns_id, 0, -1) end
|
|
3705
|
+
|
|
3706
|
+
H.replace_termcodes = function(x) return vim.api.nvim_replace_termcodes(x, true, true, true) end
|
|
3707
|
+
|
|
3708
|
+
H.expand_callable = function(x, ...)
|
|
3709
|
+
if vim.is_callable(x) then return x(...) end
|
|
3710
|
+
return x
|
|
3711
|
+
end
|
|
3712
|
+
|
|
3713
|
+
H.expandcmd = function(x)
|
|
3714
|
+
local ok, res = pcall(vim.fn.expandcmd, x)
|
|
3715
|
+
return ok and res or x
|
|
3716
|
+
end
|
|
3717
|
+
|
|
3718
|
+
H.getcharstr = function(lmap)
|
|
3719
|
+
H.cache.is_in_getcharstr = true
|
|
3720
|
+
local ok, char = pcall(vim.fn.getcharstr)
|
|
3721
|
+
H.cache.is_in_getcharstr = nil
|
|
3722
|
+
|
|
3723
|
+
-- Cache possible error if it doesn't come from pressing <C-c>
|
|
3724
|
+
local is_ctrl_c = (not ok and char == 'Keyboard interrupt') or (ok and char == '\3')
|
|
3725
|
+
if not ok and not is_ctrl_c then H.cache.getcharstr_errmsg = char end
|
|
3726
|
+
|
|
3727
|
+
-- Terminate if no input, on hard-coded <C-c>, or outside mouse click
|
|
3728
|
+
local main_win_id
|
|
3729
|
+
if H.pickers.active ~= nil then main_win_id = H.pickers.active.windows.main end
|
|
3730
|
+
local is_bad_mouse_click = vim.v.mouse_winid ~= 0 and vim.v.mouse_winid ~= main_win_id
|
|
3731
|
+
if not ok or char == '' or char == '\3' or is_bad_mouse_click then return end
|
|
3732
|
+
-- Respect language mappings only if needed
|
|
3733
|
+
return vim.o.iminsert == 0 and char or (lmap[char] or char)
|
|
3734
|
+
end
|
|
3735
|
+
|
|
3736
|
+
H.ensure_redraw = function(picker)
|
|
3737
|
+
-- Ensure only one sequence of scheduled redraws
|
|
3738
|
+
H.timers.redraw:stop()
|
|
3739
|
+
local n = 0
|
|
3740
|
+
local f = function()
|
|
3741
|
+
vim.cmd('redraw')
|
|
3742
|
+
-- Do several (but limited) `:redraw` for slow async changes
|
|
3743
|
+
n = n + 1
|
|
3744
|
+
if n >= 100 then H.timers.redraw:stop() end
|
|
3745
|
+
end
|
|
3746
|
+
H.timers.redraw:start(0, picker.opts.delay.async, vim.schedule_wrap(f))
|
|
3747
|
+
end
|
|
3748
|
+
|
|
3749
|
+
H.tolower = (function()
|
|
3750
|
+
-- Cache `tolower` for speed
|
|
3751
|
+
local tolower = vim.fn.tolower
|
|
3752
|
+
return function(x)
|
|
3753
|
+
-- `vim.fn.tolower` can throw errors on bad string (like with '\0')
|
|
3754
|
+
local ok, res = pcall(tolower, x)
|
|
3755
|
+
return ok and res or string.lower(x)
|
|
3756
|
+
end
|
|
3757
|
+
end)()
|
|
3758
|
+
|
|
3759
|
+
H.win_update_hl = function(win_id, new_from, new_to)
|
|
3760
|
+
if not H.is_valid_win(win_id) then return end
|
|
3761
|
+
|
|
3762
|
+
local new_entry = new_from .. ':' .. new_to
|
|
3763
|
+
local replace_pattern = string.format('(%s:[^,]*)', vim.pesc(new_from))
|
|
3764
|
+
local new_winhighlight, n_replace = vim.wo[win_id].winhighlight:gsub(replace_pattern, new_entry)
|
|
3765
|
+
if n_replace == 0 then new_winhighlight = new_winhighlight .. ',' .. new_entry end
|
|
3766
|
+
|
|
3767
|
+
vim.wo[win_id].winhighlight = new_winhighlight
|
|
3768
|
+
end
|
|
3769
|
+
|
|
3770
|
+
H.fit_to_width = function(text, width)
|
|
3771
|
+
local t_width = vim.fn.strchars(text)
|
|
3772
|
+
return t_width <= width and text or ('…' .. vim.fn.strcharpart(text, t_width - width + 1, width - 1))
|
|
3773
|
+
end
|
|
3774
|
+
|
|
3775
|
+
H.win_get_bottom_border = function(win_id, border_part_id)
|
|
3776
|
+
local border = vim.api.nvim_win_get_config(win_id).border or {}
|
|
3777
|
+
local res = border[border_part_id]
|
|
3778
|
+
return (type(res) == 'table' and res[1] or res) or ' '
|
|
3779
|
+
end
|
|
3780
|
+
|
|
3781
|
+
H.win_set_cwd = function(win_id, cwd)
|
|
3782
|
+
-- Avoid needlessly setting cwd as it has side effects (like for `:buffers`)
|
|
3783
|
+
if cwd == nil or vim.fn.getcwd(win_id or 0) == cwd then return end
|
|
3784
|
+
local f = function() vim.cmd('lcd ' .. vim.fn.fnameescape(cwd)) end
|
|
3785
|
+
if win_id == nil or win_id == vim.api.nvim_get_current_win() then return f() end
|
|
3786
|
+
vim.api.nvim_win_call(win_id, f)
|
|
3787
|
+
end
|
|
3788
|
+
|
|
3789
|
+
H.seq_along = function(arr)
|
|
3790
|
+
if arr == nil then return nil end
|
|
3791
|
+
local res = {}
|
|
3792
|
+
for i = 1, #arr do
|
|
3793
|
+
table.insert(res, i)
|
|
3794
|
+
end
|
|
3795
|
+
return res
|
|
3796
|
+
end
|
|
3797
|
+
|
|
3798
|
+
H.str_utfindex = function(s, i) return vim.str_utfindex(s, 'utf-32', i) end
|
|
3799
|
+
if vim.fn.has('nvim-0.11') == 0 then H.str_utfindex = function(s, i) return (vim.str_utfindex(s, i)) end end
|
|
3800
|
+
|
|
3801
|
+
H.str_byteindex = function(s, i) return vim.str_byteindex(s, 'utf-32', i) end
|
|
3802
|
+
if vim.fn.has('nvim-0.11') == 0 then H.str_byteindex = function(s, i) return vim.str_byteindex(s, i) end end
|
|
3803
|
+
|
|
3804
|
+
H.get_next_char_bytecol = function(line_str, col)
|
|
3805
|
+
if type(line_str) ~= 'string' then return col end
|
|
3806
|
+
local utf_index = H.str_utfindex(line_str, math.min(line_str:len(), col))
|
|
3807
|
+
return H.str_byteindex(line_str, utf_index)
|
|
3808
|
+
end
|
|
3809
|
+
|
|
3810
|
+
H.is_file_text = function(path)
|
|
3811
|
+
local fd = vim.loop.fs_open(path, 'r', 1)
|
|
3812
|
+
if fd == nil then return nil end
|
|
3813
|
+
local is_text = vim.loop.fs_read(fd, 1024):find('\0') == nil
|
|
3814
|
+
vim.loop.fs_close(fd)
|
|
3815
|
+
return is_text
|
|
3816
|
+
end
|
|
3817
|
+
|
|
3818
|
+
H.full_path = function(path) return (vim.fn.fnamemodify(path, ':p'):gsub('(.)/$', '%1')) end
|
|
3819
|
+
if H.is_windows then
|
|
3820
|
+
H.full_path = function(path) return (vim.fn.fnamemodify(path, ':p'):gsub('(.)[\\/]$', '%1')) end
|
|
3821
|
+
end
|
|
3822
|
+
|
|
3823
|
+
H.parse_uri = function(x)
|
|
3824
|
+
local ok, path = pcall(vim.uri_to_fname, x)
|
|
3825
|
+
if not ok then return nil end
|
|
3826
|
+
-- Don't accept Windows paths with volume letter as URI
|
|
3827
|
+
if H.is_windows and x:find('^%a:') ~= nil and path:find('^%a:') ~= nil then return nil end
|
|
3828
|
+
return path
|
|
3829
|
+
end
|
|
3830
|
+
|
|
3831
|
+
H.user_input = function(prompt, text, completion, scope)
|
|
3832
|
+
prompt = '(mini.pick) ' .. prompt
|
|
3833
|
+
if _G.MiniInput ~= nil then
|
|
3834
|
+
local opts = { prompt = prompt, scope = scope, init_keys = { text }, completion = completion }
|
|
3835
|
+
-- Show in UI line in active picker window as other styles are distracting
|
|
3836
|
+
if MiniPick.is_picker_active() then
|
|
3837
|
+
local from_bottom = MiniPick.get_picker_opts().options.content_from_bottom
|
|
3838
|
+
local style = (from_bottom and vim.fn.has('nvim-0.12') == 1) and 'statusline' or 'winbar'
|
|
3839
|
+
opts.handlers = { view = MiniInput.gen_view.uiline({ style = style }) }
|
|
3840
|
+
end
|
|
3841
|
+
return MiniInput.get(opts)
|
|
3842
|
+
end
|
|
3843
|
+
|
|
3844
|
+
-- Use `on_key` to distinguish cancel with `<Esc>` and immediate `<CR>`
|
|
3845
|
+
local was_cancelled = false
|
|
3846
|
+
vim.on_key(function(key) was_cancelled = was_cancelled or key == '\27' end, H.ns_id.input)
|
|
3847
|
+
|
|
3848
|
+
-- Ask for input. Use `pcall` to allow `<C-c>` to cancel user input
|
|
3849
|
+
vim.cmd('echohl Question')
|
|
3850
|
+
local ok, res = pcall(vim.fn.input, { prompt = prompt .. ': ', default = text or '', completion = completion })
|
|
3851
|
+
vim.cmd('echohl None | echo "" | redraw')
|
|
3852
|
+
|
|
3853
|
+
vim.on_key(nil, H.ns_id.input)
|
|
3854
|
+
return (ok and not was_cancelled) and res or nil
|
|
3855
|
+
end
|
|
3856
|
+
|
|
3857
|
+
-- TODO: Remove after compatibility with Neovim=0.9 is dropped
|
|
3858
|
+
H.islist = vim.fn.has('nvim-0.10') == 1 and vim.islist or vim.tbl_islist
|
|
3859
|
+
|
|
3860
|
+
H.get_lmap = function()
|
|
3861
|
+
local lmap = {}
|
|
3862
|
+
for _, map in ipairs(vim.fn.maplist()) do
|
|
3863
|
+
-- NOTE: Account only for characters that resolve to proper query character
|
|
3864
|
+
local is_query_lmap = map.mode == 'l' and H.is_query_char(map.rhs)
|
|
3865
|
+
if is_query_lmap then lmap[map.lhs] = map.rhs end
|
|
3866
|
+
end
|
|
3867
|
+
return lmap
|
|
3868
|
+
end
|
|
3869
|
+
if vim.fn.has('nvim-0.10') == 0 then H.get_lmap = function() return {} end end
|
|
3870
|
+
|
|
3871
|
+
-- A copy of `vim.deepcopy()` that doesn't error on userdata and threads
|
|
3872
|
+
H.copy_tables = function(x)
|
|
3873
|
+
return type(x) == 'table' and setmetatable(vim.tbl_map(H.copy_tables, x), getmetatable(x)) or x
|
|
3874
|
+
end
|
|
3875
|
+
|
|
3876
|
+
return MiniPick
|