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,3145 @@
|
|
|
1
|
+
--- *mini.files* Navigate and manipulate file system
|
|
2
|
+
---
|
|
3
|
+
--- MIT License Copyright (c) 2023 Evgeni Chasnovski
|
|
4
|
+
|
|
5
|
+
--- Features:
|
|
6
|
+
--- - Navigate file system using column view (Miller columns) to display nested
|
|
7
|
+
--- directories. See |MiniFiles-navigation| for overview.
|
|
8
|
+
---
|
|
9
|
+
--- - Opt-in preview of file or directory under cursor.
|
|
10
|
+
---
|
|
11
|
+
--- - Manipulate files and directories by editing text buffers: create, delete,
|
|
12
|
+
--- rename (all three are LSP aware), copy, move.
|
|
13
|
+
--- See |MiniFiles-manipulation| for an overview.
|
|
14
|
+
---
|
|
15
|
+
--- - Use as default file explorer instead of `netrw`.
|
|
16
|
+
---
|
|
17
|
+
--- - Configurable:
|
|
18
|
+
--- - Filter/prefix/sort of file system entries.
|
|
19
|
+
--- - Mappings used for common explorer actions.
|
|
20
|
+
--- - UI options: whether to show preview of file/directory under cursor, etc.
|
|
21
|
+
--- - Bookmarks for quicker navigation.
|
|
22
|
+
---
|
|
23
|
+
--- What it doesn't do:
|
|
24
|
+
--- - Try to be replacement of system file explorer. It is mostly designed to
|
|
25
|
+
--- be used within Neovim to quickly explore file system structure, open
|
|
26
|
+
--- files, and perform some quick file system edits.
|
|
27
|
+
---
|
|
28
|
+
--- - Work on remote locations. Only local file system is supported.
|
|
29
|
+
---
|
|
30
|
+
--- - Provide built-in interactive toggle of content `filter` and `sort`.
|
|
31
|
+
--- See |MiniFiles-examples| for some common examples.
|
|
32
|
+
---
|
|
33
|
+
--- - Provide out of the box extra information like git or diagnostic status.
|
|
34
|
+
--- This can be achieved by setting |extmarks| on appropriate event(s)
|
|
35
|
+
--- (see |MiniFiles-events|)
|
|
36
|
+
---
|
|
37
|
+
--- Notes:
|
|
38
|
+
--- - This module is written and thoroughly tested on Linux. Support for other
|
|
39
|
+
--- platform/OS (like Windows or MacOS) is a goal, but there is no guarantee.
|
|
40
|
+
---
|
|
41
|
+
--- - This module silently reacts to not enough permissions:
|
|
42
|
+
--- - In case of missing file, check its or its parent read permissions.
|
|
43
|
+
--- - In case of no manipulation result, check write permissions.
|
|
44
|
+
---
|
|
45
|
+
--- # Dependencies ~
|
|
46
|
+
---
|
|
47
|
+
--- Suggested dependencies (provide extra functionality, will work without them):
|
|
48
|
+
---
|
|
49
|
+
--- - Enabled |mini.icons| module to show icons near file/directory names.
|
|
50
|
+
--- Falls back to [nvim-tree/nvim-web-devicons](https://github.com/nvim-tree/nvim-web-devicons)
|
|
51
|
+
--- plugin or uses default icons.
|
|
52
|
+
---
|
|
53
|
+
--- # Setup ~
|
|
54
|
+
---
|
|
55
|
+
--- This module needs a setup with `require('mini.files').setup({})` (replace
|
|
56
|
+
--- `{}` with your `config` table). It will create global Lua table `MiniFiles`
|
|
57
|
+
--- which you can use for scripting or manually (with `:lua MiniFiles.*`).
|
|
58
|
+
---
|
|
59
|
+
--- See |MiniFiles.config| for available config settings.
|
|
60
|
+
---
|
|
61
|
+
--- You can override runtime config settings (like mappings or window options)
|
|
62
|
+
--- locally to buffer inside `vim.b.minifiles_config` which should have same
|
|
63
|
+
--- structure as `MiniFiles.config`. See |mini.nvim-buffer-local-config| for
|
|
64
|
+
--- more details.
|
|
65
|
+
---
|
|
66
|
+
--- # Comparisons ~
|
|
67
|
+
---
|
|
68
|
+
--- - [nvim-tree/nvim-tree.lua](https://github.com/nvim-tree/nvim-tree.lua):
|
|
69
|
+
--- - Provides tree view of file system, while this module uses column view.
|
|
70
|
+
--- - File system manipulation is done with custom set of mappings for each
|
|
71
|
+
--- action, while this module is designed to do that by editing text.
|
|
72
|
+
--- - Has more out of the box functionality with extra configuration, while
|
|
73
|
+
--- this module has not (by design).
|
|
74
|
+
---
|
|
75
|
+
--- - [stevearc/oil.nvim](https://github.com/stevearc/oil.nvim):
|
|
76
|
+
--- - Uses single window to show information only about currently explored
|
|
77
|
+
--- directory, while this module uses column view to show whole currently
|
|
78
|
+
--- explored branch.
|
|
79
|
+
--- - Also uses text editing to manipulate file system entries.
|
|
80
|
+
--- - Can work for remote file systems, while this module can not (by design).
|
|
81
|
+
--- - Both provide LSP integration.
|
|
82
|
+
---
|
|
83
|
+
--- - [nvim-neo-tree/neo-tree.nvim](https://github.com/nvim-neo-tree/neo-tree.nvim):
|
|
84
|
+
--- - Compares to this module mostly the same as `nvim-tree/nvim-tree.lua`.
|
|
85
|
+
---
|
|
86
|
+
--- # Highlight groups ~
|
|
87
|
+
--- *MiniFiles-hl-groups*
|
|
88
|
+
---
|
|
89
|
+
--- - `MiniFilesBorder` - border of regular windows.
|
|
90
|
+
--- - `MiniFilesBorderModified` - border of windows showing modified buffer.
|
|
91
|
+
--- - `MiniFilesCursorLine` - cursor line in explorer windows.
|
|
92
|
+
--- - `MiniFilesDirectory` - text and icon representing directory.
|
|
93
|
+
--- - `MiniFilesFile` - text representing file.
|
|
94
|
+
--- - `MiniFilesNormal` - basic foreground/background highlighting.
|
|
95
|
+
--- - `MiniFilesTitle` - title of regular windows.
|
|
96
|
+
--- - `MiniFilesTitleFocused` - title of focused window.
|
|
97
|
+
---
|
|
98
|
+
--- To change any highlight group, set it directly with |nvim_set_hl()|.
|
|
99
|
+
---
|
|
100
|
+
--- # Disabling ~
|
|
101
|
+
---
|
|
102
|
+
--- This plugin provides only manually started functionality, so no disabling
|
|
103
|
+
--- is available.
|
|
104
|
+
---@tag MiniFiles
|
|
105
|
+
|
|
106
|
+
--- Every navigation starts by calling |MiniFiles.open()|, either directly or via
|
|
107
|
+
--- mapping (see its help for examples of some common scenarios). It will show
|
|
108
|
+
--- an explorer consisting of side-by-side floating windows with the following
|
|
109
|
+
--- principles:
|
|
110
|
+
---
|
|
111
|
+
--- - Explorer shows one branch of nested directories at a time.
|
|
112
|
+
---
|
|
113
|
+
--- - Explorer consists from several windows:
|
|
114
|
+
---
|
|
115
|
+
--- - Each window displays entries of a single directory in a modifiable
|
|
116
|
+
--- scratch buffer.
|
|
117
|
+
---
|
|
118
|
+
--- - Windows are organized left to right: for any particular window the left
|
|
119
|
+
--- neighbor is its parent directory and right neighbor - its child.
|
|
120
|
+
---
|
|
121
|
+
--- - Explorer windows are the viewport to some part of current branch, meaning
|
|
122
|
+
--- that their opening/closing does not affect the branch. This matters, for
|
|
123
|
+
--- example, if there are more elements in the branch than can be shown windows.
|
|
124
|
+
---
|
|
125
|
+
--- - Every buffer line represents separate file system entry following certain
|
|
126
|
+
--- format (not visible for users by default; set |'conceallevel'| to 0 to see it)
|
|
127
|
+
---
|
|
128
|
+
--- - Once directory is shown, its buffer is not updated automatically following
|
|
129
|
+
--- external file system changes. Manually use |MiniFiles.synchronize()| for that.
|
|
130
|
+
---
|
|
131
|
+
--- After opening explorer, in-buffer navigation is done the same way as any
|
|
132
|
+
--- regular buffer, except without some keys reserved for built-in actions.
|
|
133
|
+
---
|
|
134
|
+
--- Most common ways to navigate are:
|
|
135
|
+
---
|
|
136
|
+
--- - Press `j` to move cursor onto next (lower) entry in current directory.
|
|
137
|
+
--- - Press `k` to move cursor onto previous (higher) entry in current directory.
|
|
138
|
+
--- - Press `l` to expand entry under cursor (see "Go in" action).
|
|
139
|
+
--- - Press `h` to focus on parent directory (see "Go out" action).
|
|
140
|
+
---
|
|
141
|
+
--- Cursor positions in each directory buffer are tracked and saved during
|
|
142
|
+
--- navigation. This allows for more convenient repeated navigation to some
|
|
143
|
+
--- previously visited branch.
|
|
144
|
+
---
|
|
145
|
+
--- Available built-in actions (see "Details" for more information): >
|
|
146
|
+
---
|
|
147
|
+
--- | Action | Keys | Description |
|
|
148
|
+
--- |-------------|------|------------------------------------------------|
|
|
149
|
+
--- | Close | q | Close explorer |
|
|
150
|
+
--- |-------------|------|------------------------------------------------|
|
|
151
|
+
--- | Go in | l | Expand entry (show directory or open file) |
|
|
152
|
+
--- |-------------|------|------------------------------------------------|
|
|
153
|
+
--- | Go in plus | L | Expand entry plus extra action |
|
|
154
|
+
--- |-------------|------|------------------------------------------------|
|
|
155
|
+
--- | Go out | h | Focus on parent directory |
|
|
156
|
+
--- |-------------|------|------------------------------------------------|
|
|
157
|
+
--- | Go out plus | H | Focus on parent directory plus extra action |
|
|
158
|
+
--- |-------------|------|------------------------------------------------|
|
|
159
|
+
--- | Go to mark | ' | Jump to bookmark (waits for single key id) |
|
|
160
|
+
--- |-------------|------|------------------------------------------------|
|
|
161
|
+
--- | Set mark | m | Set bookmark (waits for single key id) |
|
|
162
|
+
--- |-------------|------|------------------------------------------------|
|
|
163
|
+
--- | Reset | <BS> | Reset current explorer |
|
|
164
|
+
--- |-------------|------|------------------------------------------------|
|
|
165
|
+
--- | Reveal cwd | @ | Reset current current working directory |
|
|
166
|
+
--- |-------------|------|------------------------------------------------|
|
|
167
|
+
--- | Show help | g? | Show help window |
|
|
168
|
+
--- |-------------|------|------------------------------------------------|
|
|
169
|
+
--- | Synchronize | = | Synchronize user edits and/or external changes |
|
|
170
|
+
--- |-------------|------|------------------------------------------------|
|
|
171
|
+
--- | Trim left | < | Trim left part of branch |
|
|
172
|
+
--- |-------------|------|------------------------------------------------|
|
|
173
|
+
--- | Trim right | > | Trim right part of branch |
|
|
174
|
+
--- |-------------|------|------------------------------------------------|
|
|
175
|
+
--- <
|
|
176
|
+
--- Details:
|
|
177
|
+
---
|
|
178
|
+
--- - "Go in":
|
|
179
|
+
--- - Always opens file in the latest window before `MiniFiles.open()` call.
|
|
180
|
+
--- - Never closes explorer.
|
|
181
|
+
--- - Works in linewise Visual mode to expand multiple entries.
|
|
182
|
+
---
|
|
183
|
+
--- - "Go in plus" is regular "Go in" but closes explorer after opening a file.
|
|
184
|
+
---
|
|
185
|
+
--- - "Go out plus" is regular "Go out" but trims right part of branch.
|
|
186
|
+
---
|
|
187
|
+
--- - "Set mark" and "Go to mark" both wait for user to press a single character
|
|
188
|
+
--- of a bookmark id. Example: `ma` sets directory path of focused window as
|
|
189
|
+
--- bookmark "a"; `'a` jumps (sets as whole branch) to bookmark "a".
|
|
190
|
+
--- Special bookmark `'` always points to path before the latest bookmark jump.
|
|
191
|
+
---
|
|
192
|
+
--- - "Reset" focuses only on "anchor" directory (the one used to open current
|
|
193
|
+
--- explorer) and resets all stored directory cursor positions.
|
|
194
|
+
---
|
|
195
|
+
--- - "Reveal cwd" extends branch to include |current-directory|.
|
|
196
|
+
--- If it is not an ancestor of the current branch, nothing is done.
|
|
197
|
+
---
|
|
198
|
+
--- - "Show help" results into new window with helpful information about current
|
|
199
|
+
--- explorer (like buffer mappings and bookmarks). Press `q` to close it.
|
|
200
|
+
---
|
|
201
|
+
--- - "Synchronize" parses user edits in directory buffers, applies them (after
|
|
202
|
+
--- confirmation), and updates all directory buffers with the most relevant
|
|
203
|
+
--- file system information. Can also be used without user edits to show up
|
|
204
|
+
--- to date file system entries.
|
|
205
|
+
--- See |MiniFiles-manipulation| for more info about file system manipulation.
|
|
206
|
+
---
|
|
207
|
+
--- - "Trim left" and "Trim right" trim parts of the whole branch, not only its
|
|
208
|
+
--- currently visible parts.
|
|
209
|
+
---
|
|
210
|
+
--- Notes:
|
|
211
|
+
---
|
|
212
|
+
--- - Each action has exported function with more details about it.
|
|
213
|
+
---
|
|
214
|
+
--- - Keys can be configured with `mappings` table of |MiniFiles.config|.
|
|
215
|
+
---@tag MiniFiles-navigation
|
|
216
|
+
|
|
217
|
+
--- File system manipulation is done by editing text inside directory buffers,
|
|
218
|
+
--- which are shown inside dedicated window(s). See |MiniFiles-navigation| for
|
|
219
|
+
--- more information about navigating to a particular directory.
|
|
220
|
+
---
|
|
221
|
+
--- General workflow:
|
|
222
|
+
---
|
|
223
|
+
--- - Navigate to the directory in which manipulation should be done.
|
|
224
|
+
---
|
|
225
|
+
--- - Edit buffer in the way representing file system action.
|
|
226
|
+
---
|
|
227
|
+
--- - Repeat previous steps until all necessary file system actions are recorded.
|
|
228
|
+
--- Note: even if directory buffer is hidden, its modifications are preserved,
|
|
229
|
+
--- so you can navigate in and out of directory with modified buffer.
|
|
230
|
+
---
|
|
231
|
+
--- - Execute |MiniFiles.synchronize()| (default key is `=`). This will prompt
|
|
232
|
+
--- confirmation dialog listing all file system actions (per directory) it is
|
|
233
|
+
--- about to perform. READ IT CAREFULLY.
|
|
234
|
+
---
|
|
235
|
+
--- - Confirm by pressing `y` / `<CR>` (apply edits and update buffers) or
|
|
236
|
+
--- don't confirm by pressing `n` / `<Esc>` (update buffers without applying edits).
|
|
237
|
+
---
|
|
238
|
+
--- Note: prefer small and not related steps with more frequent synchronization
|
|
239
|
+
--- over single complex manipulation. There are (known) cases which won't work.
|
|
240
|
+
---
|
|
241
|
+
--- # How does it work ~
|
|
242
|
+
---
|
|
243
|
+
--- All manipulation functionality is powered by creating and keeping track of
|
|
244
|
+
--- path indexes: text of the form `/xxx` (`xxx` is the number path index) placed
|
|
245
|
+
--- at the start of every line representing file system entry.
|
|
246
|
+
---
|
|
247
|
+
--- By default they are hidden as concealed text (along with prefix separators)
|
|
248
|
+
--- for more convenience but you can see them by setting |'conceallevel'| to 0.
|
|
249
|
+
--- DO NOT modify text to the left of entry name.
|
|
250
|
+
---
|
|
251
|
+
--- During synchronization, actual text for entry name is compared to path index
|
|
252
|
+
--- at that line (if present) to deduce which file system action to perform.
|
|
253
|
+
--- Note that order of text manipulation steps does not affect performed actions.
|
|
254
|
+
---
|
|
255
|
+
--- # Supported file system actions ~
|
|
256
|
+
---
|
|
257
|
+
--- ## Create ~
|
|
258
|
+
---
|
|
259
|
+
--- - Create file by creating new line with file name (including extension).
|
|
260
|
+
---
|
|
261
|
+
--- - Create directory by creating new line with directory name followed by `/`.
|
|
262
|
+
---
|
|
263
|
+
--- - Create file or directory inside nested directories by creating new line
|
|
264
|
+
--- with text like `dir/nested-dir/` or `dir/nested-dir/file`.
|
|
265
|
+
--- Always use `/` on any OS.
|
|
266
|
+
---
|
|
267
|
+
--- ## Delete ~
|
|
268
|
+
---
|
|
269
|
+
--- - Delete file or directory by deleting WHOLE LINE describing it.
|
|
270
|
+
---
|
|
271
|
+
--- - If `options.permanent_delete` is `true`, delete is permanent. Otherwise
|
|
272
|
+
--- file system entry is moved to a module-specific trash directory
|
|
273
|
+
--- (see |MiniFiles.config| for more details).
|
|
274
|
+
---
|
|
275
|
+
--- ## Rename ~
|
|
276
|
+
---
|
|
277
|
+
--- - Rename file or directory by editing its name (not icon or path index to
|
|
278
|
+
--- the left of it).
|
|
279
|
+
---
|
|
280
|
+
--- - With default mappings for `h` / `l` it might be not convenient to rename
|
|
281
|
+
--- only part of an entry. You can adopt any of the following approaches:
|
|
282
|
+
--- - Use different motions, like |$|, |e|, |f|, etc.
|
|
283
|
+
--- - Go into Insert mode and navigate inside it.
|
|
284
|
+
--- - Change mappings to be more suited for manipulation and not navigation.
|
|
285
|
+
--- See "Mappings" section in |MiniFiles.config|.
|
|
286
|
+
---
|
|
287
|
+
--- - It is not needed to end directory name with `/`.
|
|
288
|
+
---
|
|
289
|
+
--- - Appending `/` to a file name will delete it and create empty directory
|
|
290
|
+
--- with the same name.
|
|
291
|
+
---
|
|
292
|
+
--- - Cyclic renames ("a" to "b" and "b" to "a") are not supported.
|
|
293
|
+
---
|
|
294
|
+
--- ## Copy ~
|
|
295
|
+
---
|
|
296
|
+
--- - Copy file or directory by copying WHOLE LINE describing it and pasting
|
|
297
|
+
--- it inside buffer of target directory.
|
|
298
|
+
---
|
|
299
|
+
--- - Change of target path is allowed. Edit only entry name in target location
|
|
300
|
+
--- (not icon or path index to the left of it).
|
|
301
|
+
---
|
|
302
|
+
--- - Copying inside same parent directory is supported only if target path has
|
|
303
|
+
--- different name.
|
|
304
|
+
---
|
|
305
|
+
--- - Copying inside child directory is supported.
|
|
306
|
+
---
|
|
307
|
+
--- ## Move ~
|
|
308
|
+
---
|
|
309
|
+
--- - Move file or directory by cutting WHOLE LINE describing it and then
|
|
310
|
+
--- pasting it inside target directory.
|
|
311
|
+
---
|
|
312
|
+
--- - Change of target path is allowed. Edit only entry name in target location
|
|
313
|
+
--- (not icon or path index to the left of it).
|
|
314
|
+
---
|
|
315
|
+
--- - Moving directory inside itself is not supported.
|
|
316
|
+
---
|
|
317
|
+
--- # LSP integration ~
|
|
318
|
+
---
|
|
319
|
+
--- Create, delete, and rename are LSP aware (on Neovim>=0.11): the information
|
|
320
|
+
--- is forwarded to all active LSP servers for them to perform additional actions.
|
|
321
|
+
--- This means that LSP servers can, for example, update imports after renaming
|
|
322
|
+
--- a file or populate a file with a boilerplate code after creation.
|
|
323
|
+
---
|
|
324
|
+
--- The actual changes depend entirely on the LSP server and whether it supports
|
|
325
|
+
--- relevant methods:
|
|
326
|
+
--- - `workspace/will{Create,Delete,Rename}Files` before a file system action.
|
|
327
|
+
--- - `workspace/did{Create,Delete,Rename}Files` after a file system action.
|
|
328
|
+
---
|
|
329
|
+
--- It can be disabled by setting `options.lsp_timeout = 0` in |MiniFiles.config|.
|
|
330
|
+
---@tag MiniFiles-manipulation
|
|
331
|
+
|
|
332
|
+
--- To allow user customization and integration of external tools, certain |User|
|
|
333
|
+
--- autocommand events are triggered under common circumstances.
|
|
334
|
+
---
|
|
335
|
+
--- # UI events ~
|
|
336
|
+
---
|
|
337
|
+
--- - `MiniFilesExplorerOpen` - just after explorer finishes opening.
|
|
338
|
+
---
|
|
339
|
+
--- - `MiniFilesExplorerClose` - just before explorer starts closing.
|
|
340
|
+
---
|
|
341
|
+
--- - `MiniFilesBufferCreate` - when buffer is created to show a particular
|
|
342
|
+
--- directory/file. Triggered once per path during explorer session.
|
|
343
|
+
--- Can be used to create buffer-local mappings.
|
|
344
|
+
---
|
|
345
|
+
--- - `MiniFilesBufferUpdate` - when path buffer is updated with new content.
|
|
346
|
+
--- Can be used for integrations to set useful |extmarks|.
|
|
347
|
+
---
|
|
348
|
+
--- - `MiniFilesWindowOpen` - when new window is opened. Can be used to set
|
|
349
|
+
--- window-local settings (like border, |'winblend'|, etc.)
|
|
350
|
+
---
|
|
351
|
+
--- - `MiniFilesWindowUpdate` - when a window is updated. Triggers VERY frequently.
|
|
352
|
+
--- At least after every cursor movement and "go in" / "go out" action.
|
|
353
|
+
---
|
|
354
|
+
--- Callback for each buffer/window UI event will receive <data> field
|
|
355
|
+
--- (see |nvim_create_autocmd()|) with the following information:
|
|
356
|
+
---
|
|
357
|
+
--- - <buf_id> - index of target buffer.
|
|
358
|
+
--- - <win_id> - index of target window. Can be `nil`, like in
|
|
359
|
+
--- `MiniFilesBufferCreate` and buffer's first `MiniFilesBufferUpdate` as
|
|
360
|
+
--- they are triggered before window is created.
|
|
361
|
+
---
|
|
362
|
+
--- # File action events ~
|
|
363
|
+
---
|
|
364
|
+
--- - `MiniFilesActionCreate` - after entry is successfully created.
|
|
365
|
+
---
|
|
366
|
+
--- - `MiniFilesActionDelete` - after entry is successfully deleted.
|
|
367
|
+
---
|
|
368
|
+
--- - `MiniFilesActionRename` - after entry is successfully renamed.
|
|
369
|
+
---
|
|
370
|
+
--- - `MiniFilesActionCopy` - after entry is successfully copied.
|
|
371
|
+
---
|
|
372
|
+
--- - `MiniFilesActionMove` - after entry is successfully moved.
|
|
373
|
+
---
|
|
374
|
+
--- Callback for each file action event will receive `data` field
|
|
375
|
+
--- (see |nvim_create_autocmd()|) with the following information:
|
|
376
|
+
---
|
|
377
|
+
--- - <action> - string with action name.
|
|
378
|
+
--- - <from> - full path of entry before action (`nil` for "create" action).
|
|
379
|
+
--- - <to> - full path of entry after action (`nil` for permanent "delete" action).
|
|
380
|
+
---@tag MiniFiles-events
|
|
381
|
+
|
|
382
|
+
--- # Toggle explorer ~
|
|
383
|
+
---
|
|
384
|
+
--- Use a combination of |MiniFiles.open()| and |MiniFiles.close()|: >lua
|
|
385
|
+
---
|
|
386
|
+
--- local minifiles_toggle = function(...)
|
|
387
|
+
--- if not MiniFiles.close() then MiniFiles.open(...) end
|
|
388
|
+
--- end
|
|
389
|
+
--- <
|
|
390
|
+
--- # Customize windows ~
|
|
391
|
+
---
|
|
392
|
+
--- For most of the common customizations using `MiniFilesWindowOpen` event
|
|
393
|
+
--- autocommand is the suggested approach: >lua
|
|
394
|
+
---
|
|
395
|
+
--- vim.api.nvim_create_autocmd('User', {
|
|
396
|
+
--- pattern = 'MiniFilesWindowOpen',
|
|
397
|
+
--- callback = function(args)
|
|
398
|
+
--- local win_id = args.data.win_id
|
|
399
|
+
---
|
|
400
|
+
--- -- Customize window-local settings
|
|
401
|
+
--- vim.wo[win_id].winblend = 50
|
|
402
|
+
--- local config = vim.api.nvim_win_get_config(win_id)
|
|
403
|
+
--- config.border, config.title_pos = 'double', 'right'
|
|
404
|
+
--- vim.api.nvim_win_set_config(win_id, config)
|
|
405
|
+
--- end,
|
|
406
|
+
--- })
|
|
407
|
+
--- <
|
|
408
|
+
--- However, some parts (like window title and height) of window config are later
|
|
409
|
+
--- updated internally. Use `MiniFilesWindowUpdate` event for them: >lua
|
|
410
|
+
---
|
|
411
|
+
--- vim.api.nvim_create_autocmd('User', {
|
|
412
|
+
--- pattern = 'MiniFilesWindowUpdate',
|
|
413
|
+
--- callback = function(args)
|
|
414
|
+
--- local config = vim.api.nvim_win_get_config(args.data.win_id)
|
|
415
|
+
---
|
|
416
|
+
--- -- Ensure fixed height
|
|
417
|
+
--- config.height = 10
|
|
418
|
+
---
|
|
419
|
+
--- -- Ensure no title padding
|
|
420
|
+
--- local n = #config.title
|
|
421
|
+
--- config.title[1][1] = config.title[1][1]:gsub('^ ', '')
|
|
422
|
+
--- config.title[n][1] = config.title[n][1]:gsub(' $', '')
|
|
423
|
+
---
|
|
424
|
+
--- vim.api.nvim_win_set_config(args.data.win_id, config)
|
|
425
|
+
--- end,
|
|
426
|
+
--- })
|
|
427
|
+
--- <
|
|
428
|
+
--- # Customize icons ~
|
|
429
|
+
---
|
|
430
|
+
--- Use different directory icon (if you don't use |mini.icons|): >lua
|
|
431
|
+
---
|
|
432
|
+
--- local my_prefix = function(fs_entry)
|
|
433
|
+
--- if fs_entry.fs_type == 'directory' then
|
|
434
|
+
--- -- NOTE: it is usually a good idea to use icon followed by space
|
|
435
|
+
--- return ' ', 'MiniFilesDirectory'
|
|
436
|
+
--- end
|
|
437
|
+
--- return MiniFiles.default_prefix(fs_entry)
|
|
438
|
+
--- end
|
|
439
|
+
---
|
|
440
|
+
--- require('mini.files').setup({ content = { prefix = my_prefix } })
|
|
441
|
+
--- <
|
|
442
|
+
--- Show no icons: >lua
|
|
443
|
+
---
|
|
444
|
+
--- require('mini.files').setup({ content = { prefix = function() end } })
|
|
445
|
+
--- <
|
|
446
|
+
--- # Create mapping to show/hide dot-files ~
|
|
447
|
+
---
|
|
448
|
+
--- Create an autocommand for `MiniFilesBufferCreate` event which calls
|
|
449
|
+
--- |MiniFiles.refresh()| with explicit `content.filter` functions: >lua
|
|
450
|
+
---
|
|
451
|
+
--- local show_dotfiles = true
|
|
452
|
+
---
|
|
453
|
+
--- local filter_show = function(fs_entry) return true end
|
|
454
|
+
---
|
|
455
|
+
--- local filter_hide = function(fs_entry)
|
|
456
|
+
--- return not vim.startswith(fs_entry.name, '.')
|
|
457
|
+
--- end
|
|
458
|
+
---
|
|
459
|
+
--- local toggle_dotfiles = function()
|
|
460
|
+
--- show_dotfiles = not show_dotfiles
|
|
461
|
+
--- local new_filter = show_dotfiles and filter_show or filter_hide
|
|
462
|
+
--- MiniFiles.refresh({ content = { filter = new_filter } })
|
|
463
|
+
--- end
|
|
464
|
+
---
|
|
465
|
+
--- vim.api.nvim_create_autocmd('User', {
|
|
466
|
+
--- pattern = 'MiniFilesBufferCreate',
|
|
467
|
+
--- callback = function(args)
|
|
468
|
+
--- local buf_id = args.data.buf_id
|
|
469
|
+
--- -- Tweak left-hand side of mapping to your liking
|
|
470
|
+
--- vim.keymap.set('n', 'g.', toggle_dotfiles, { buffer = buf_id })
|
|
471
|
+
--- end,
|
|
472
|
+
--- })
|
|
473
|
+
--- <
|
|
474
|
+
--- # Create mappings to modify target window via split ~
|
|
475
|
+
---
|
|
476
|
+
--- Combine |MiniFiles.get_explorer_state()| and |MiniFiles.set_target_window()|: >lua
|
|
477
|
+
---
|
|
478
|
+
--- local map_split = function(buf_id, lhs, direction)
|
|
479
|
+
--- local rhs = function()
|
|
480
|
+
--- -- Make new window and set it as target
|
|
481
|
+
--- local cur_target = MiniFiles.get_explorer_state().target_window
|
|
482
|
+
--- local new_target = vim.api.nvim_win_call(cur_target, function()
|
|
483
|
+
--- vim.cmd(direction .. ' split')
|
|
484
|
+
--- return vim.api.nvim_get_current_win()
|
|
485
|
+
--- end)
|
|
486
|
+
---
|
|
487
|
+
--- MiniFiles.set_target_window(new_target)
|
|
488
|
+
---
|
|
489
|
+
--- -- This intentionally doesn't act on file under cursor in favor of
|
|
490
|
+
--- -- explicit "go in" action (`l` / `L`). To immediately open file,
|
|
491
|
+
--- -- add appropriate `MiniFiles.go_in()` call instead of this comment.
|
|
492
|
+
--- end
|
|
493
|
+
---
|
|
494
|
+
--- -- Adding `desc` will result into `show_help` entries
|
|
495
|
+
--- local desc = 'Split ' .. direction
|
|
496
|
+
--- vim.keymap.set('n', lhs, rhs, { buffer = buf_id, desc = desc })
|
|
497
|
+
--- end
|
|
498
|
+
---
|
|
499
|
+
--- vim.api.nvim_create_autocmd('User', {
|
|
500
|
+
--- pattern = 'MiniFilesBufferCreate',
|
|
501
|
+
--- callback = function(args)
|
|
502
|
+
--- local buf_id = args.data.buf_id
|
|
503
|
+
--- -- Tweak keys to your liking
|
|
504
|
+
--- map_split(buf_id, '<C-s>', 'belowright horizontal')
|
|
505
|
+
--- map_split(buf_id, '<C-v>', 'belowright vertical')
|
|
506
|
+
--- map_split(buf_id, '<C-t>', 'tab')
|
|
507
|
+
--- end,
|
|
508
|
+
--- })
|
|
509
|
+
--- <
|
|
510
|
+
--- # Create mappings which use data from entry under cursor ~
|
|
511
|
+
---
|
|
512
|
+
--- Use |MiniFiles.get_fs_entry()|: >lua
|
|
513
|
+
---
|
|
514
|
+
--- -- Set focused directory as current working directory
|
|
515
|
+
--- local set_cwd = function()
|
|
516
|
+
--- local path = (MiniFiles.get_fs_entry() or {}).path
|
|
517
|
+
--- if path == nil then return vim.notify('Cursor is not on valid entry') end
|
|
518
|
+
--- vim.fn.chdir(vim.fs.dirname(path))
|
|
519
|
+
--- end
|
|
520
|
+
---
|
|
521
|
+
--- -- Yank in register full path of entry under cursor
|
|
522
|
+
--- local yank_path = function()
|
|
523
|
+
--- local path = (MiniFiles.get_fs_entry() or {}).path
|
|
524
|
+
--- if path == nil then return vim.notify('Cursor is not on valid entry') end
|
|
525
|
+
--- vim.fn.setreg(vim.v.register, path)
|
|
526
|
+
--- end
|
|
527
|
+
---
|
|
528
|
+
--- -- Open path with system default handler (useful for non-text files)
|
|
529
|
+
--- local ui_open = function() vim.ui.open(MiniFiles.get_fs_entry().path) end
|
|
530
|
+
---
|
|
531
|
+
--- vim.api.nvim_create_autocmd('User', {
|
|
532
|
+
--- pattern = 'MiniFilesBufferCreate',
|
|
533
|
+
--- callback = function(args)
|
|
534
|
+
--- local b = args.data.buf_id
|
|
535
|
+
--- vim.keymap.set('n', 'g~', set_cwd, { buffer = b, desc = 'Set cwd' })
|
|
536
|
+
--- vim.keymap.set('n', 'gX', ui_open, { buffer = b, desc = 'OS open' })
|
|
537
|
+
--- vim.keymap.set('n', 'gy', yank_path, { buffer = b, desc = 'Yank path' })
|
|
538
|
+
--- end,
|
|
539
|
+
--- })
|
|
540
|
+
--- <
|
|
541
|
+
--- # Set custom bookmarks ~
|
|
542
|
+
---
|
|
543
|
+
--- Use |MiniFiles.set_bookmark()| inside `MiniFilesExplorerOpen` event: >lua
|
|
544
|
+
---
|
|
545
|
+
--- local set_mark = function(id, path, desc)
|
|
546
|
+
--- MiniFiles.set_bookmark(id, path, { desc = desc })
|
|
547
|
+
--- end
|
|
548
|
+
--- vim.api.nvim_create_autocmd('User', {
|
|
549
|
+
--- pattern = 'MiniFilesExplorerOpen',
|
|
550
|
+
--- callback = function()
|
|
551
|
+
--- set_mark('c', vim.fn.stdpath('config'), 'Config') -- path
|
|
552
|
+
--- set_mark('w', vim.fn.getcwd, 'Working directory') -- callable
|
|
553
|
+
--- set_mark('~', '~', 'Home directory')
|
|
554
|
+
--- end,
|
|
555
|
+
--- })
|
|
556
|
+
--- <
|
|
557
|
+
---@tag MiniFiles-examples
|
|
558
|
+
|
|
559
|
+
---@diagnostic disable:luadoc-miss-type-name
|
|
560
|
+
---@alias __minifiles_fs_entry_data_fields - <fs_type> `(string)` - one of "file" or "directory".
|
|
561
|
+
--- - <name> `(string)` - basename of an entry (including extension).
|
|
562
|
+
--- - <path> `(string)` - full path of an entry.
|
|
563
|
+
|
|
564
|
+
---@diagnostic disable:undefined-field
|
|
565
|
+
---@diagnostic disable:discard-returns
|
|
566
|
+
---@diagnostic disable:unused-local
|
|
567
|
+
---@diagnostic disable:cast-local-type
|
|
568
|
+
|
|
569
|
+
-- Module definition ==========================================================
|
|
570
|
+
local MiniFiles = {}
|
|
571
|
+
local H = {}
|
|
572
|
+
|
|
573
|
+
--- Module setup
|
|
574
|
+
---
|
|
575
|
+
---@param config table|nil Module config table. See |MiniFiles.config|.
|
|
576
|
+
---
|
|
577
|
+
---@usage >lua
|
|
578
|
+
--- require('mini.files').setup() -- use default config
|
|
579
|
+
--- -- OR
|
|
580
|
+
--- require('mini.files').setup({}) -- replace {} with your config table
|
|
581
|
+
--- <
|
|
582
|
+
MiniFiles.setup = function(config)
|
|
583
|
+
-- TODO: Remove after Neovim=0.9 support is dropped
|
|
584
|
+
if vim.fn.has('nvim-0.10') == 0 then
|
|
585
|
+
vim.notify(
|
|
586
|
+
'(mini.files) Neovim<0.10 is soft deprecated (module works but is not supported).'
|
|
587
|
+
.. " It will be deprecated after the next 'mini.nvim' release (module might not work)."
|
|
588
|
+
.. ' Please update your Neovim version.'
|
|
589
|
+
)
|
|
590
|
+
end
|
|
591
|
+
|
|
592
|
+
-- Export module
|
|
593
|
+
_G.MiniFiles = MiniFiles
|
|
594
|
+
|
|
595
|
+
-- Setup config
|
|
596
|
+
config = H.setup_config(config)
|
|
597
|
+
|
|
598
|
+
-- Apply config
|
|
599
|
+
H.apply_config(config)
|
|
600
|
+
|
|
601
|
+
-- Define behavior
|
|
602
|
+
H.create_autocommands(config)
|
|
603
|
+
|
|
604
|
+
-- Create default highlighting
|
|
605
|
+
H.create_default_hl()
|
|
606
|
+
end
|
|
607
|
+
|
|
608
|
+
--stylua: ignore
|
|
609
|
+
--- Defaults ~
|
|
610
|
+
---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section)
|
|
611
|
+
---@text # Content ~
|
|
612
|
+
---
|
|
613
|
+
--- `content.filter` is a predicate which takes file system entry data as input
|
|
614
|
+
--- and returns `true`-ish value if it should be shown.
|
|
615
|
+
--- Uses |MiniFiles.default_filter()| by default.
|
|
616
|
+
---
|
|
617
|
+
--- A file system entry data is a table with the following fields:
|
|
618
|
+
--- __minifiles_fs_entry_data_fields
|
|
619
|
+
---
|
|
620
|
+
--- `content.highlight` describes how file system entry name should be highlighted.
|
|
621
|
+
--- Takes file system entry data as input and returns a highlight group name.
|
|
622
|
+
--- Uses |MiniFiles.default_highlight()| by default.
|
|
623
|
+
---
|
|
624
|
+
--- `content.prefix` describes what text (prefix) to show to the left of file
|
|
625
|
+
--- system entry name (if any) and how to highlight it. It also takes file
|
|
626
|
+
--- system entry data as input and returns tuple of text and highlight group
|
|
627
|
+
--- name to be used to highlight prefix. See |MiniFiles-examples| for common
|
|
628
|
+
--- examples of how to use it.
|
|
629
|
+
--- Note: due to how lines are parsed to detect user edits for file system
|
|
630
|
+
--- manipulation, output of `content.prefix` should not contain `/` character.
|
|
631
|
+
--- Uses |MiniFiles.default_prefix()| by default.
|
|
632
|
+
---
|
|
633
|
+
--- `content.sort` describes in which order directory entries should be shown
|
|
634
|
+
--- in directory buffer. Takes as input and returns as output an array of file
|
|
635
|
+
--- system entry data. Note: technically, it can be used to filter and modify
|
|
636
|
+
--- its elements as well.
|
|
637
|
+
--- Uses |MiniFiles.default_sort()| by default.
|
|
638
|
+
---
|
|
639
|
+
--- # Mappings ~
|
|
640
|
+
---
|
|
641
|
+
--- `mappings` table can be used to customize buffer-local mappings created in each
|
|
642
|
+
--- directory buffer for built-in actions. Entry name corresponds to the function
|
|
643
|
+
--- name of the action, value - right hand side of the mapping. Supply empty
|
|
644
|
+
--- string to not create a particular mapping.
|
|
645
|
+
---
|
|
646
|
+
--- Default mappings are mostly designed for consistent navigation experience.
|
|
647
|
+
--- Here are some alternatives: >lua
|
|
648
|
+
---
|
|
649
|
+
--- -- Close explorer after opening file with `l`
|
|
650
|
+
--- mappings = {
|
|
651
|
+
--- go_in = 'L',
|
|
652
|
+
--- go_in_plus = 'l',
|
|
653
|
+
--- }
|
|
654
|
+
---
|
|
655
|
+
--- -- Don't use `h`/`l` for easier cursor navigation during text edit
|
|
656
|
+
--- mappings = {
|
|
657
|
+
--- go_in = 'L',
|
|
658
|
+
--- go_in_plus = '',
|
|
659
|
+
--- go_out = 'H',
|
|
660
|
+
--- go_out_plus = '',
|
|
661
|
+
--- }
|
|
662
|
+
--- <
|
|
663
|
+
--- # Options ~
|
|
664
|
+
---
|
|
665
|
+
--- `options.use_as_default_explorer` is a boolean indicating whether this module
|
|
666
|
+
--- will be used as a default file explorer to edit directory (instead of `netrw`).
|
|
667
|
+
--- Note: to work with directory in |arglist|, do not lazy load this module.
|
|
668
|
+
---
|
|
669
|
+
--- `options.permanent_delete` is a boolean indicating whether to perform
|
|
670
|
+
--- permanent delete or move into special trash directory.
|
|
671
|
+
--- This is a module-specific variant of "remove to trash".
|
|
672
|
+
--- Target directory is `mini.files/trash` inside standard path of Neovim data
|
|
673
|
+
--- directory (execute `:echo stdpath('data')` to see its path in your case).
|
|
674
|
+
---
|
|
675
|
+
--- `options.lsp_timeout` is a number that defines a timeout for synchronous
|
|
676
|
+
--- LSP integration requests (see |MiniFiles-manipulation|).
|
|
677
|
+
--- Set to 0 to disable LSP integration.
|
|
678
|
+
---
|
|
679
|
+
--- # Windows ~
|
|
680
|
+
---
|
|
681
|
+
--- `windows.max_number` is a maximum number of windows allowed to be open
|
|
682
|
+
--- simultaneously. For example, use value 1 to always show single window.
|
|
683
|
+
--- There is no constraint by default.
|
|
684
|
+
---
|
|
685
|
+
--- `windows.preview` is a boolean indicating whether to show preview of
|
|
686
|
+
--- file/directory under cursor. Notes:
|
|
687
|
+
--- - It is always shown, even if current line is for not yet existing path.
|
|
688
|
+
--- - File preview is highlighted if its size is small enough (less than 1K
|
|
689
|
+
--- bytes per line or 1M bytes in total).
|
|
690
|
+
---
|
|
691
|
+
--- `windows.width_focus`, `windows.width_nofocus` and `windows.width_preview`
|
|
692
|
+
--- are number of columns used as `width` for the corresponding window type.
|
|
693
|
+
MiniFiles.config = {
|
|
694
|
+
-- Customization of shown content
|
|
695
|
+
content = {
|
|
696
|
+
-- Predicate for which file system entries to show
|
|
697
|
+
filter = nil,
|
|
698
|
+
-- Highlight group to use for a file system entry
|
|
699
|
+
highlight = nil,
|
|
700
|
+
-- Prefix text and highlight to show to the left of file system entry
|
|
701
|
+
prefix = nil,
|
|
702
|
+
-- Order in which to show file system entries
|
|
703
|
+
sort = nil,
|
|
704
|
+
},
|
|
705
|
+
|
|
706
|
+
-- Module mappings created only inside explorer.
|
|
707
|
+
-- Use `''` (empty string) to not create one.
|
|
708
|
+
mappings = {
|
|
709
|
+
close = 'q',
|
|
710
|
+
go_in = 'l',
|
|
711
|
+
go_in_plus = 'L',
|
|
712
|
+
go_out = 'h',
|
|
713
|
+
go_out_plus = 'H',
|
|
714
|
+
mark_goto = "'",
|
|
715
|
+
mark_set = 'm',
|
|
716
|
+
reset = '<BS>',
|
|
717
|
+
reveal_cwd = '@',
|
|
718
|
+
show_help = 'g?',
|
|
719
|
+
synchronize = '=',
|
|
720
|
+
trim_left = '<',
|
|
721
|
+
trim_right = '>',
|
|
722
|
+
},
|
|
723
|
+
|
|
724
|
+
-- General options
|
|
725
|
+
options = {
|
|
726
|
+
-- Whether to delete permanently or move into module-specific trash
|
|
727
|
+
permanent_delete = true,
|
|
728
|
+
-- Whether to use for editing directories
|
|
729
|
+
use_as_default_explorer = true,
|
|
730
|
+
-- Timeout for synchronous LSP integration requests
|
|
731
|
+
lsp_timeout = 1000,
|
|
732
|
+
},
|
|
733
|
+
|
|
734
|
+
-- Customization of explorer windows
|
|
735
|
+
windows = {
|
|
736
|
+
-- Maximum number of windows to show side by side
|
|
737
|
+
max_number = math.huge,
|
|
738
|
+
-- Whether to show preview of file/directory under cursor
|
|
739
|
+
preview = false,
|
|
740
|
+
-- Width of focused window
|
|
741
|
+
width_focus = 50,
|
|
742
|
+
-- Width of non-focused window
|
|
743
|
+
width_nofocus = 15,
|
|
744
|
+
-- Width of preview window
|
|
745
|
+
width_preview = 25,
|
|
746
|
+
},
|
|
747
|
+
}
|
|
748
|
+
--minidoc_afterlines_end
|
|
749
|
+
|
|
750
|
+
--- Open file explorer
|
|
751
|
+
---
|
|
752
|
+
--- Common ways to use this function: >lua
|
|
753
|
+
---
|
|
754
|
+
--- -- Open current working directory in a last used state
|
|
755
|
+
--- MiniFiles.open()
|
|
756
|
+
---
|
|
757
|
+
--- -- Fresh explorer in current working directory
|
|
758
|
+
--- MiniFiles.open(nil, false)
|
|
759
|
+
---
|
|
760
|
+
--- -- Open directory of current file (in last used state) focused on the file
|
|
761
|
+
--- MiniFiles.open(vim.api.nvim_buf_get_name(0))
|
|
762
|
+
---
|
|
763
|
+
--- -- Fresh explorer in directory of current file
|
|
764
|
+
--- MiniFiles.open(vim.api.nvim_buf_get_name(0), false)
|
|
765
|
+
---
|
|
766
|
+
--- -- Open last used `path` (per tabpage)
|
|
767
|
+
--- -- Current working directory for the first time
|
|
768
|
+
--- MiniFiles.open(MiniFiles.get_latest_path())
|
|
769
|
+
--- <
|
|
770
|
+
---@param path string|nil A valid file system path used as anchor.
|
|
771
|
+
--- If it is a path to directory, used directly.
|
|
772
|
+
--- If it is a path to file, its parent directory is used as anchor while
|
|
773
|
+
--- explorer will focus on the supplied file.
|
|
774
|
+
--- Default: path of |current-directory|.
|
|
775
|
+
---@param use_latest boolean|nil Whether to load explorer state from history
|
|
776
|
+
--- (based on the supplied anchor path). Default: `true`.
|
|
777
|
+
---@param opts table|nil Table of options overriding |MiniFiles.config| and
|
|
778
|
+
--- `vim.b.minifiles_config` for this particular explorer session.
|
|
779
|
+
MiniFiles.open = function(path, use_latest, opts)
|
|
780
|
+
-- Validate path: allow only valid file system path
|
|
781
|
+
path = H.fs_full_path(path or vim.fn.getcwd())
|
|
782
|
+
|
|
783
|
+
local fs_type = H.fs_get_type(path)
|
|
784
|
+
if fs_type == nil then H.error('`path` is not a valid path ("' .. path .. '")') end
|
|
785
|
+
|
|
786
|
+
-- - Allow file path to use its parent while focusing on file
|
|
787
|
+
local entry_name
|
|
788
|
+
if fs_type == 'file' then
|
|
789
|
+
path, entry_name = H.fs_get_parent(path), H.fs_get_basename(path)
|
|
790
|
+
end
|
|
791
|
+
|
|
792
|
+
-- Validate rest of the arguments
|
|
793
|
+
if use_latest == nil then use_latest = true end
|
|
794
|
+
|
|
795
|
+
-- Properly close possibly opened in the tabpage explorer
|
|
796
|
+
local did_close = MiniFiles.close()
|
|
797
|
+
if did_close == false then return end
|
|
798
|
+
|
|
799
|
+
-- Get explorer to open
|
|
800
|
+
local explorer
|
|
801
|
+
if use_latest then explorer = H.explorer_path_history[path] end
|
|
802
|
+
explorer = explorer or H.explorer_new(path)
|
|
803
|
+
|
|
804
|
+
-- Update explorer data. Don't use current explorer's data to allow more
|
|
805
|
+
-- interactive config change by modifying global/local configs.
|
|
806
|
+
explorer.opts = H.normalize_opts(nil, opts)
|
|
807
|
+
explorer.target_window = vim.api.nvim_get_current_win()
|
|
808
|
+
|
|
809
|
+
-- Possibly focus on file entry
|
|
810
|
+
explorer = H.explorer_focus_on_entry(explorer, path, entry_name)
|
|
811
|
+
|
|
812
|
+
-- Refresh and register as opened
|
|
813
|
+
H.explorer_refresh(explorer)
|
|
814
|
+
|
|
815
|
+
-- Register latest used path
|
|
816
|
+
H.latest_paths[vim.api.nvim_get_current_tabpage()] = path
|
|
817
|
+
|
|
818
|
+
-- Track lost focus
|
|
819
|
+
H.explorer_track_lost_focus()
|
|
820
|
+
|
|
821
|
+
-- Adjust some `vim.ui` methods to not track lost focus when they are active,
|
|
822
|
+
-- since most of their implementations use separate floating windows
|
|
823
|
+
H.explorer_adjust_vim_ui()
|
|
824
|
+
|
|
825
|
+
-- Trigger appropriate event
|
|
826
|
+
H.trigger_event('MiniFilesExplorerOpen')
|
|
827
|
+
end
|
|
828
|
+
|
|
829
|
+
--- Refresh explorer
|
|
830
|
+
---
|
|
831
|
+
--- Notes:
|
|
832
|
+
--- - If in `opts` at least one of `content` entry is not `nil`, all directory
|
|
833
|
+
--- buffers are forced to update.
|
|
834
|
+
---
|
|
835
|
+
---@param opts table|nil Table of options overriding local options of active
|
|
836
|
+
--- explorer session.
|
|
837
|
+
MiniFiles.refresh = function(opts)
|
|
838
|
+
local explorer = H.explorer_get()
|
|
839
|
+
if explorer == nil then return end
|
|
840
|
+
|
|
841
|
+
-- Decide whether buffers should be forcefully updated
|
|
842
|
+
local content_opts = (opts or {}).content or {}
|
|
843
|
+
local force_update = #vim.tbl_keys(content_opts) > 0
|
|
844
|
+
|
|
845
|
+
-- Confirm refresh if there is modified buffer
|
|
846
|
+
if force_update then force_update = H.explorer_ignore_pending_fs_actions(explorer, 'Update buffers') end
|
|
847
|
+
|
|
848
|
+
-- Respect explorer local options supplied inside its `open()` call but give
|
|
849
|
+
-- current `opts` higher precedence
|
|
850
|
+
explorer.opts = H.normalize_opts(explorer.opts, opts)
|
|
851
|
+
|
|
852
|
+
H.explorer_refresh(explorer, { force_update = force_update })
|
|
853
|
+
end
|
|
854
|
+
|
|
855
|
+
--- Synchronize explorer
|
|
856
|
+
---
|
|
857
|
+
--- - Parse user edits in directory buffers.
|
|
858
|
+
--- - Convert edits to file system actions and apply them after confirmation.
|
|
859
|
+
--- Choosing "No" skips application while "Cancel" stops synchronization.
|
|
860
|
+
--- - Update all directory buffers with the most relevant file system information.
|
|
861
|
+
--- Can be used without user edits to account for external file system changes.
|
|
862
|
+
---
|
|
863
|
+
---@return boolean Whether synchronization was done.
|
|
864
|
+
MiniFiles.synchronize = function()
|
|
865
|
+
local explorer = H.explorer_get()
|
|
866
|
+
if explorer == nil then return end
|
|
867
|
+
|
|
868
|
+
-- Parse and apply file system operations
|
|
869
|
+
local fs_actions = H.explorer_compute_fs_actions(explorer)
|
|
870
|
+
if fs_actions ~= nil then
|
|
871
|
+
local msg = table.concat(H.fs_actions_to_lines(fs_actions), '\n')
|
|
872
|
+
local confirm_res = vim.fn.confirm(msg, '&Yes\n&No\n&Cancel', 1, 'Question')
|
|
873
|
+
if confirm_res == 3 then return false end
|
|
874
|
+
if confirm_res == 1 then H.fs_actions_apply(fs_actions, explorer.opts.options.lsp_timeout) end
|
|
875
|
+
end
|
|
876
|
+
|
|
877
|
+
H.explorer_refresh(explorer, { force_update = true })
|
|
878
|
+
return true
|
|
879
|
+
end
|
|
880
|
+
|
|
881
|
+
--- Reset explorer
|
|
882
|
+
---
|
|
883
|
+
--- - Show single window focused on anchor directory (which was used as first
|
|
884
|
+
--- argument for |MiniFiles.open()|).
|
|
885
|
+
--- - Reset all tracked directory cursors to point at first entry.
|
|
886
|
+
MiniFiles.reset = function()
|
|
887
|
+
local explorer = H.explorer_get()
|
|
888
|
+
if explorer == nil then return end
|
|
889
|
+
|
|
890
|
+
-- Reset branch
|
|
891
|
+
explorer.branch = { explorer.anchor }
|
|
892
|
+
explorer.depth_focus = 1
|
|
893
|
+
|
|
894
|
+
-- Reset views
|
|
895
|
+
for _, view in pairs(explorer.views) do
|
|
896
|
+
view.cursor = { 1, 0 }
|
|
897
|
+
end
|
|
898
|
+
|
|
899
|
+
-- Skip update cursors, as they are already set
|
|
900
|
+
H.explorer_refresh(explorer, { skip_update_cursor = true })
|
|
901
|
+
end
|
|
902
|
+
|
|
903
|
+
--- Close explorer
|
|
904
|
+
---
|
|
905
|
+
---@return boolean|nil Whether closing was done or `nil` if there was nothing to close.
|
|
906
|
+
MiniFiles.close = function()
|
|
907
|
+
-- Stop possible tracking lost focus
|
|
908
|
+
pcall(vim.loop.timer_stop, H.timers.focus)
|
|
909
|
+
|
|
910
|
+
-- Act if there is explorer to close (even invisible after improper quit)
|
|
911
|
+
local explorer = H.explorer_get(nil, true)
|
|
912
|
+
if explorer == nil then return nil end
|
|
913
|
+
|
|
914
|
+
-- Confirm close if there is modified buffer
|
|
915
|
+
if not H.explorer_ignore_pending_fs_actions(explorer, 'Close') then return false end
|
|
916
|
+
|
|
917
|
+
-- Unadjust `vim.ui` methods
|
|
918
|
+
vim.ui.select = H.vim_ui.select or vim.ui.select
|
|
919
|
+
vim.ui.input = H.vim_ui.input or vim.ui.input
|
|
920
|
+
H.vim_ui = { select_active = false, input_active = false }
|
|
921
|
+
|
|
922
|
+
-- Trigger appropriate event
|
|
923
|
+
H.trigger_event('MiniFilesExplorerClose')
|
|
924
|
+
|
|
925
|
+
-- Focus on target window
|
|
926
|
+
explorer = H.explorer_ensure_target_window(explorer)
|
|
927
|
+
-- - Use `pcall()` because window might still be invalid
|
|
928
|
+
pcall(vim.api.nvim_set_current_win, explorer.target_window)
|
|
929
|
+
|
|
930
|
+
-- Update currently shown cursors
|
|
931
|
+
explorer = H.explorer_update_cursors(explorer)
|
|
932
|
+
|
|
933
|
+
-- Close shown explorer windows
|
|
934
|
+
for i, win_id in pairs(explorer.windows) do
|
|
935
|
+
H.window_close(win_id)
|
|
936
|
+
explorer.windows[i] = nil
|
|
937
|
+
end
|
|
938
|
+
|
|
939
|
+
-- Close possibly visible help window
|
|
940
|
+
for _, win_id in ipairs(vim.api.nvim_tabpage_list_wins(0)) do
|
|
941
|
+
local buf_id = vim.api.nvim_win_get_buf(win_id)
|
|
942
|
+
if vim.bo[buf_id].filetype == 'minifiles-help' then vim.api.nvim_win_close(win_id, true) end
|
|
943
|
+
end
|
|
944
|
+
|
|
945
|
+
-- Invalidate views
|
|
946
|
+
for path, view in pairs(explorer.views) do
|
|
947
|
+
explorer.views[path] = H.view_invalidate_buffer(H.view_encode_cursor(view))
|
|
948
|
+
end
|
|
949
|
+
|
|
950
|
+
-- Update histories and unmark as opened
|
|
951
|
+
H.explorer_path_history[explorer.anchor] = explorer
|
|
952
|
+
H.opened_explorers[explorer.tabpage_id] = nil
|
|
953
|
+
|
|
954
|
+
-- Return `true` indicating success in closing
|
|
955
|
+
return true
|
|
956
|
+
end
|
|
957
|
+
|
|
958
|
+
--- Go in entry under cursor
|
|
959
|
+
---
|
|
960
|
+
--- Depends on entry under cursor:
|
|
961
|
+
--- - If directory, focus on it in the window to the right.
|
|
962
|
+
--- - If file, open it in the window which was current during |MiniFiles.open()|.
|
|
963
|
+
--- Explorer is not closed after that.
|
|
964
|
+
---
|
|
965
|
+
---@param opts table|nil Options. Possible fields:
|
|
966
|
+
--- - <close_on_file> `(boolean)` - whether to close explorer after going
|
|
967
|
+
--- inside a file. Powers the `go_in_plus` mapping.
|
|
968
|
+
--- Default: `false`.
|
|
969
|
+
MiniFiles.go_in = function(opts)
|
|
970
|
+
local explorer = H.explorer_get()
|
|
971
|
+
if explorer == nil then return end
|
|
972
|
+
|
|
973
|
+
opts = vim.tbl_deep_extend('force', { close_on_file = false }, opts or {})
|
|
974
|
+
|
|
975
|
+
local should_close = opts.close_on_file
|
|
976
|
+
if should_close then
|
|
977
|
+
local fs_entry = MiniFiles.get_fs_entry()
|
|
978
|
+
should_close = fs_entry ~= nil and fs_entry.fs_type == 'file'
|
|
979
|
+
end
|
|
980
|
+
|
|
981
|
+
local cur_line = vim.fn.line('.')
|
|
982
|
+
explorer = H.explorer_go_in_range(explorer, vim.api.nvim_get_current_buf(), cur_line, cur_line)
|
|
983
|
+
|
|
984
|
+
H.explorer_refresh(explorer)
|
|
985
|
+
|
|
986
|
+
if should_close then MiniFiles.close() end
|
|
987
|
+
end
|
|
988
|
+
|
|
989
|
+
--- Go out to parent directory
|
|
990
|
+
---
|
|
991
|
+
--- - Focus on window to the left showing parent of current directory.
|
|
992
|
+
MiniFiles.go_out = function()
|
|
993
|
+
local explorer = H.explorer_get()
|
|
994
|
+
if explorer == nil then return end
|
|
995
|
+
|
|
996
|
+
if explorer.depth_focus == 1 then
|
|
997
|
+
explorer = H.explorer_open_root_parent(explorer)
|
|
998
|
+
else
|
|
999
|
+
explorer.depth_focus = explorer.depth_focus - 1
|
|
1000
|
+
end
|
|
1001
|
+
|
|
1002
|
+
H.explorer_refresh(explorer)
|
|
1003
|
+
end
|
|
1004
|
+
|
|
1005
|
+
--- Trim left part of branch
|
|
1006
|
+
---
|
|
1007
|
+
--- - Remove all branch paths to the left of currently focused one. This also
|
|
1008
|
+
--- results into current window becoming the most left one.
|
|
1009
|
+
MiniFiles.trim_left = function()
|
|
1010
|
+
local explorer = H.explorer_get()
|
|
1011
|
+
if explorer == nil then return end
|
|
1012
|
+
|
|
1013
|
+
explorer = H.explorer_trim_branch_left(explorer)
|
|
1014
|
+
H.explorer_refresh(explorer)
|
|
1015
|
+
end
|
|
1016
|
+
|
|
1017
|
+
--- Trim right part of branch
|
|
1018
|
+
---
|
|
1019
|
+
--- - Remove all branch paths to the right of currently focused one. This also
|
|
1020
|
+
--- results into current window becoming the most right one.
|
|
1021
|
+
MiniFiles.trim_right = function()
|
|
1022
|
+
local explorer = H.explorer_get()
|
|
1023
|
+
if explorer == nil then return end
|
|
1024
|
+
|
|
1025
|
+
explorer = H.explorer_trim_branch_right(explorer)
|
|
1026
|
+
H.explorer_refresh(explorer)
|
|
1027
|
+
end
|
|
1028
|
+
|
|
1029
|
+
--- Reveal current working directory
|
|
1030
|
+
---
|
|
1031
|
+
--- - Prepend branch with parent paths until current working directory is reached.
|
|
1032
|
+
--- Do nothing if not inside it.
|
|
1033
|
+
MiniFiles.reveal_cwd = function()
|
|
1034
|
+
local state = MiniFiles.get_explorer_state()
|
|
1035
|
+
if state == nil then return end
|
|
1036
|
+
local branch, depth_focus = state.branch, state.depth_focus
|
|
1037
|
+
|
|
1038
|
+
local cwd = H.fs_full_path(vim.fn.getcwd())
|
|
1039
|
+
local cwd_ancestor_pattern = string.format('^%s%s.', vim.pesc(cwd), cwd == '/' and '' or '/')
|
|
1040
|
+
while branch[1]:find(cwd_ancestor_pattern) ~= nil do
|
|
1041
|
+
table.insert(branch, 1, H.fs_get_parent(branch[1]))
|
|
1042
|
+
depth_focus = depth_focus + 1
|
|
1043
|
+
end
|
|
1044
|
+
|
|
1045
|
+
MiniFiles.set_branch(branch, { depth_focus = depth_focus })
|
|
1046
|
+
end
|
|
1047
|
+
|
|
1048
|
+
--- Show help window
|
|
1049
|
+
---
|
|
1050
|
+
--- - Open window with helpful information about currently shown explorer and
|
|
1051
|
+
--- focus on it. To close it, press `q`.
|
|
1052
|
+
MiniFiles.show_help = function()
|
|
1053
|
+
local explorer = H.explorer_get()
|
|
1054
|
+
if explorer == nil then return end
|
|
1055
|
+
|
|
1056
|
+
local buf_id = vim.api.nvim_get_current_buf()
|
|
1057
|
+
if not H.is_opened_buffer(buf_id) then return end
|
|
1058
|
+
|
|
1059
|
+
H.explorer_show_help(explorer, buf_id, vim.api.nvim_get_current_win())
|
|
1060
|
+
end
|
|
1061
|
+
|
|
1062
|
+
--- Get file system entry data
|
|
1063
|
+
---
|
|
1064
|
+
---@param buf_id number|nil Buffer identifier of valid directory buffer.
|
|
1065
|
+
--- Default: current buffer.
|
|
1066
|
+
---@param line number|nil Line number of entry for which to return information.
|
|
1067
|
+
--- Default: cursor line.
|
|
1068
|
+
---
|
|
1069
|
+
---@return table|nil Table of file system entry data with the following fields:
|
|
1070
|
+
--- __minifiles_fs_entry_data_fields
|
|
1071
|
+
---
|
|
1072
|
+
--- Returns `nil` if there is no proper file system entry path at the line.
|
|
1073
|
+
MiniFiles.get_fs_entry = function(buf_id, line)
|
|
1074
|
+
buf_id = H.validate_opened_buffer(buf_id)
|
|
1075
|
+
line = H.validate_line(buf_id, line)
|
|
1076
|
+
|
|
1077
|
+
local path_id = H.match_line_path_id(H.get_bufline(buf_id, line))
|
|
1078
|
+
return H.get_fs_entry_from_path_index(path_id)
|
|
1079
|
+
end
|
|
1080
|
+
|
|
1081
|
+
--- Get state of active explorer
|
|
1082
|
+
---
|
|
1083
|
+
---@return table|nil Table with explorer state data or `nil` if no active explorer.
|
|
1084
|
+
--- State data is a table with the following fields:
|
|
1085
|
+
--- - <anchor> `(string)` - anchor directory path (see |MiniFiles.open()|).
|
|
1086
|
+
--- - <bookmarks> `(table)` - map from bookmark id (single character) to its data:
|
|
1087
|
+
--- table with <path> and <desc> fields (see |MiniFiles.set_bookmark()|).
|
|
1088
|
+
--- - <branch> `(table)` - array of nested paths for currently opened branch.
|
|
1089
|
+
--- - <depth_focus> `(number)` - an index in <branch> for currently focused path.
|
|
1090
|
+
--- - <target_window> `(number)` - identifier of target window.
|
|
1091
|
+
--- - <windows> `(table)` - array with data about currently opened windows.
|
|
1092
|
+
--- Each element is a table with <win_id> (window identifier) and <path> (path
|
|
1093
|
+
--- shown in the window) fields.
|
|
1094
|
+
---
|
|
1095
|
+
---@seealso - |MiniFiles.set_bookmark()|
|
|
1096
|
+
--- - |MiniFiles.set_branch()|
|
|
1097
|
+
--- - |MiniFiles.set_target_window()|
|
|
1098
|
+
MiniFiles.get_explorer_state = function()
|
|
1099
|
+
local explorer = H.explorer_get()
|
|
1100
|
+
if explorer == nil then return end
|
|
1101
|
+
|
|
1102
|
+
H.explorer_ensure_target_window(explorer)
|
|
1103
|
+
local windows = {}
|
|
1104
|
+
for _, win_id in ipairs(explorer.windows) do
|
|
1105
|
+
local buf_id = vim.api.nvim_win_get_buf(win_id)
|
|
1106
|
+
local path = (H.opened_buffers[buf_id] or {}).path
|
|
1107
|
+
table.insert(windows, { win_id = win_id, path = path })
|
|
1108
|
+
end
|
|
1109
|
+
|
|
1110
|
+
return {
|
|
1111
|
+
anchor = explorer.anchor,
|
|
1112
|
+
bookmarks = vim.deepcopy(explorer.bookmarks),
|
|
1113
|
+
branch = vim.deepcopy(explorer.branch),
|
|
1114
|
+
depth_focus = explorer.depth_focus,
|
|
1115
|
+
target_window = explorer.target_window,
|
|
1116
|
+
windows = windows,
|
|
1117
|
+
}
|
|
1118
|
+
end
|
|
1119
|
+
|
|
1120
|
+
--- Set target window
|
|
1121
|
+
---
|
|
1122
|
+
---@param win_id number Window identifier inside which file will be opened.
|
|
1123
|
+
MiniFiles.set_target_window = function(win_id)
|
|
1124
|
+
if not H.is_valid_win(win_id) then H.error('`win_id` should be valid window identifier.') end
|
|
1125
|
+
|
|
1126
|
+
local explorer = H.explorer_get()
|
|
1127
|
+
if explorer == nil then return end
|
|
1128
|
+
|
|
1129
|
+
explorer.target_window = win_id
|
|
1130
|
+
end
|
|
1131
|
+
|
|
1132
|
+
--- Set branch
|
|
1133
|
+
---
|
|
1134
|
+
--- Set which paths to display. Preview (if enabled) is applied afterwards.
|
|
1135
|
+
---
|
|
1136
|
+
---@param branch table Array of strings representing actually present on disk paths.
|
|
1137
|
+
--- Each consecutive pair should represent direct parent-child paths.
|
|
1138
|
+
--- Should contain at least one directory path.
|
|
1139
|
+
--- May end with file path (will be previewed).
|
|
1140
|
+
--- Relative paths are resolved using |current-directory|.
|
|
1141
|
+
---@param opts table|nil Options. Possible fields:
|
|
1142
|
+
--- - <depth_focus> `(number)` - an index in `branch` for path to focus. Will
|
|
1143
|
+
--- be normalized to fit inside `branch`. Default: index of deepest directory.
|
|
1144
|
+
---
|
|
1145
|
+
---@seealso |MiniFiles.get_explorer_state()|
|
|
1146
|
+
MiniFiles.set_branch = function(branch, opts)
|
|
1147
|
+
local explorer = H.explorer_get()
|
|
1148
|
+
if explorer == nil then return end
|
|
1149
|
+
|
|
1150
|
+
-- Validate and normalize input
|
|
1151
|
+
branch = H.validate_branch(branch)
|
|
1152
|
+
opts = opts or {}
|
|
1153
|
+
local depth_focus = opts.depth_focus or math.huge
|
|
1154
|
+
if type(depth_focus) ~= 'number' then H.error('`depth_focus` should be a number') end
|
|
1155
|
+
local max_depth = #branch - (H.fs_get_type(branch[#branch]) == 'file' and 1 or 0)
|
|
1156
|
+
depth_focus = math.min(math.max(math.floor(depth_focus), 1), max_depth)
|
|
1157
|
+
|
|
1158
|
+
-- Set data and ensure cursors are on child entries
|
|
1159
|
+
explorer.branch, explorer.depth_focus = branch, depth_focus
|
|
1160
|
+
for i = 1, #branch - 1 do
|
|
1161
|
+
local parent, child = branch[i], H.fs_get_basename(branch[i + 1])
|
|
1162
|
+
local parent_view = explorer.views[parent] or {}
|
|
1163
|
+
parent_view.cursor = child
|
|
1164
|
+
explorer.views[parent] = parent_view
|
|
1165
|
+
end
|
|
1166
|
+
|
|
1167
|
+
-- Skip update cursors, as they are already set
|
|
1168
|
+
H.explorer_refresh(explorer, { skip_update_cursor = true })
|
|
1169
|
+
-- Refresh second time to ensure that preview is shown. Doing that in other
|
|
1170
|
+
-- way is not really feasible, as it requires knowing cursor at deepest path,
|
|
1171
|
+
-- which might not yet be set before first refresh.
|
|
1172
|
+
H.explorer_refresh(explorer)
|
|
1173
|
+
end
|
|
1174
|
+
|
|
1175
|
+
--- Set bookmark
|
|
1176
|
+
---
|
|
1177
|
+
---@param id string Single character bookmark id.
|
|
1178
|
+
---@param path string|function Path of a present on disk directory to set as
|
|
1179
|
+
--- a bookmark's path. If callable, should return such path.
|
|
1180
|
+
---@param opts table|nil Options. Possible fields:
|
|
1181
|
+
--- - <desc> `(string)` - bookmark description (used in help window).
|
|
1182
|
+
MiniFiles.set_bookmark = function(id, path, opts)
|
|
1183
|
+
local explorer = H.explorer_get()
|
|
1184
|
+
if explorer == nil then return end
|
|
1185
|
+
|
|
1186
|
+
if not (type(id) == 'string' and id:len() == 1) then H.error('Bookmark id should be single character') end
|
|
1187
|
+
local is_valid_path = vim.is_callable(path)
|
|
1188
|
+
or (type(path) == 'string' and H.fs_get_type(vim.fn.expand(path)) == 'directory')
|
|
1189
|
+
if not is_valid_path then H.error('Bookmark path should be a valid path to directory or a callable.') end
|
|
1190
|
+
opts = opts or {}
|
|
1191
|
+
if not (opts.desc == nil or type(opts.desc) == 'string') then H.error('Bookmark description should be string') end
|
|
1192
|
+
|
|
1193
|
+
explorer.bookmarks[id] = { path = path, desc = opts.desc }
|
|
1194
|
+
end
|
|
1195
|
+
|
|
1196
|
+
--- Get latest used anchor path
|
|
1197
|
+
---
|
|
1198
|
+
--- Note: if latest used `path` argument for |MiniFiles.open()| was for file,
|
|
1199
|
+
--- this will return its parent (as it was used as anchor path).
|
|
1200
|
+
MiniFiles.get_latest_path = function() return H.latest_paths[vim.api.nvim_get_current_tabpage()] end
|
|
1201
|
+
|
|
1202
|
+
--- Default filter of file system entries
|
|
1203
|
+
---
|
|
1204
|
+
--- Currently does not filter anything out.
|
|
1205
|
+
---
|
|
1206
|
+
---@param fs_entry table Table with the following fields:
|
|
1207
|
+
--- __minifiles_fs_entry_data_fields
|
|
1208
|
+
---
|
|
1209
|
+
---@return boolean Always `true`.
|
|
1210
|
+
MiniFiles.default_filter = function(fs_entry) return true end
|
|
1211
|
+
|
|
1212
|
+
--- Default prefix of file system entries
|
|
1213
|
+
---
|
|
1214
|
+
--- - If set up |mini.icons|, use |MiniIcons.get()| for "directory"/"file" category.
|
|
1215
|
+
--- - Otherwise:
|
|
1216
|
+
--- - For directory return fixed icon and "MiniFilesDirectory" group name.
|
|
1217
|
+
--- - For file try to use `get_icon()` from `nvim-tree/nvim-web-devicons`.
|
|
1218
|
+
--- If missing, return fixed icon and `MiniFilesFile` group name.
|
|
1219
|
+
---
|
|
1220
|
+
---@param fs_entry table Table with the following fields:
|
|
1221
|
+
--- __minifiles_fs_entry_data_fields
|
|
1222
|
+
---
|
|
1223
|
+
---@return ... Icon and highlight group name. For more details, see |MiniFiles.config|
|
|
1224
|
+
--- and |MiniFiles-examples|.
|
|
1225
|
+
MiniFiles.default_prefix = function(fs_entry)
|
|
1226
|
+
-- Prefer 'mini.icons'
|
|
1227
|
+
if _G.MiniIcons ~= nil then
|
|
1228
|
+
local category = fs_entry.fs_type == 'directory' and 'directory' or 'file'
|
|
1229
|
+
local icon, hl = _G.MiniIcons.get(category, fs_entry.path)
|
|
1230
|
+
return icon .. ' ', hl
|
|
1231
|
+
end
|
|
1232
|
+
|
|
1233
|
+
-- Try falling back to 'nvim-web-devicons'
|
|
1234
|
+
if fs_entry.fs_type == 'directory' then return ' ', 'MiniFilesDirectory' end
|
|
1235
|
+
local has_devicons, devicons = pcall(require, 'nvim-web-devicons')
|
|
1236
|
+
if not has_devicons then return ' ', 'MiniFilesFile' end
|
|
1237
|
+
|
|
1238
|
+
local icon, hl = devicons.get_icon(fs_entry.name, nil, { default = false })
|
|
1239
|
+
return (icon or '') .. ' ', hl or 'MiniFilesFile'
|
|
1240
|
+
end
|
|
1241
|
+
|
|
1242
|
+
--- Default sort of file system entries
|
|
1243
|
+
---
|
|
1244
|
+
--- Sort directories and files separately (alphabetically ignoring case) and
|
|
1245
|
+
--- put directories first.
|
|
1246
|
+
---
|
|
1247
|
+
---@param fs_entries table Array of file system entry data.
|
|
1248
|
+
--- Each one is a table with the following fields:
|
|
1249
|
+
--- __minifiles_fs_entry_data_fields
|
|
1250
|
+
---
|
|
1251
|
+
---@return table Sorted array of file system entries.
|
|
1252
|
+
MiniFiles.default_sort = function(fs_entries)
|
|
1253
|
+
-- Sort ignoring case
|
|
1254
|
+
local res = vim.tbl_map(
|
|
1255
|
+
function(x)
|
|
1256
|
+
return {
|
|
1257
|
+
fs_type = x.fs_type,
|
|
1258
|
+
name = x.name,
|
|
1259
|
+
path = x.path,
|
|
1260
|
+
lower_name = x.name:lower(),
|
|
1261
|
+
is_dir = x.fs_type == 'directory',
|
|
1262
|
+
}
|
|
1263
|
+
end,
|
|
1264
|
+
fs_entries
|
|
1265
|
+
)
|
|
1266
|
+
|
|
1267
|
+
-- Sort based on default order
|
|
1268
|
+
table.sort(res, H.compare_fs_entries)
|
|
1269
|
+
|
|
1270
|
+
return vim.tbl_map(function(x) return { name = x.name, fs_type = x.fs_type, path = x.path } end, res)
|
|
1271
|
+
end
|
|
1272
|
+
|
|
1273
|
+
--- Default file system entry highlight
|
|
1274
|
+
---
|
|
1275
|
+
--- Returns `'MiniFilesDirectory'` for directory and `'MiniFilesFile'` otherwise.
|
|
1276
|
+
---
|
|
1277
|
+
---@param fs_entry table Table with the following fields:
|
|
1278
|
+
--- __minifiles_fs_entry_data_fields
|
|
1279
|
+
---
|
|
1280
|
+
---@return string Highlight group name.
|
|
1281
|
+
MiniFiles.default_highlight = function(fs_entry)
|
|
1282
|
+
return fs_entry.fs_type == 'directory' and 'MiniFilesDirectory' or 'MiniFilesFile'
|
|
1283
|
+
end
|
|
1284
|
+
|
|
1285
|
+
-- Helper data ================================================================
|
|
1286
|
+
-- Module default config
|
|
1287
|
+
H.default_config = vim.deepcopy(MiniFiles.config)
|
|
1288
|
+
|
|
1289
|
+
-- Namespaces
|
|
1290
|
+
H.ns_id = {
|
|
1291
|
+
highlight = vim.api.nvim_create_namespace('MiniFilesHighlight'),
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
-- Timers
|
|
1295
|
+
H.timers = {
|
|
1296
|
+
focus = vim.loop.new_timer(),
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
-- Index of all visited files
|
|
1300
|
+
H.path_index = {}
|
|
1301
|
+
|
|
1302
|
+
-- History of explorers per root directory
|
|
1303
|
+
H.explorer_path_history = {}
|
|
1304
|
+
|
|
1305
|
+
-- Register of opened explorers per tabpage
|
|
1306
|
+
H.opened_explorers = {}
|
|
1307
|
+
|
|
1308
|
+
-- Register of latest used paths per tabpage
|
|
1309
|
+
H.latest_paths = {}
|
|
1310
|
+
|
|
1311
|
+
-- Register of opened buffer data for quick access. Tables per buffer id:
|
|
1312
|
+
-- - <path> - path which contents this buffer displays.
|
|
1313
|
+
-- - <children_path_ids> - array of shown children path ids.
|
|
1314
|
+
-- - <win_id> - id of window this buffer is shown. Can be `nil`.
|
|
1315
|
+
-- - <n_modified> - number of modifications since last update from this module.
|
|
1316
|
+
-- Values bigger than 0 can be treated as if buffer was modified by user.
|
|
1317
|
+
-- It uses number instead of boolean to overcome `TextChanged` event on
|
|
1318
|
+
-- initial `buf_set_lines` (`noautocmd` doesn't quick work for this event).
|
|
1319
|
+
H.opened_buffers = {}
|
|
1320
|
+
|
|
1321
|
+
-- Cache for `vim.ui` methods that are adjusted while explorer is open
|
|
1322
|
+
H.vim_ui = { select = nil, select_active = false, input = nil, input_active = false }
|
|
1323
|
+
|
|
1324
|
+
-- File system information
|
|
1325
|
+
H.is_windows = vim.loop.os_uname().sysname == 'Windows_NT'
|
|
1326
|
+
|
|
1327
|
+
-- Helper functionality =======================================================
|
|
1328
|
+
-- Settings -------------------------------------------------------------------
|
|
1329
|
+
H.setup_config = function(config)
|
|
1330
|
+
H.check_type('config', config, 'table', true)
|
|
1331
|
+
config = vim.tbl_deep_extend('force', vim.deepcopy(H.default_config), config or {})
|
|
1332
|
+
|
|
1333
|
+
H.check_type('content', config.content, 'table')
|
|
1334
|
+
H.check_type('content.filter', config.content.filter, 'function', true)
|
|
1335
|
+
H.check_type('content.highlight', config.content.highlight, 'function', true)
|
|
1336
|
+
H.check_type('content.prefix', config.content.prefix, 'function', true)
|
|
1337
|
+
H.check_type('content.sort', config.content.sort, 'function', true)
|
|
1338
|
+
|
|
1339
|
+
H.check_type('mappings', config.mappings, 'table')
|
|
1340
|
+
H.check_type('mappings.close', config.mappings.close, 'string')
|
|
1341
|
+
H.check_type('mappings.go_in', config.mappings.go_in, 'string')
|
|
1342
|
+
H.check_type('mappings.go_in_plus', config.mappings.go_in_plus, 'string')
|
|
1343
|
+
H.check_type('mappings.go_out', config.mappings.go_out, 'string')
|
|
1344
|
+
H.check_type('mappings.go_out_plus', config.mappings.go_out_plus, 'string')
|
|
1345
|
+
H.check_type('mappings.mark_goto', config.mappings.mark_goto, 'string')
|
|
1346
|
+
H.check_type('mappings.mark_set', config.mappings.mark_set, 'string')
|
|
1347
|
+
H.check_type('mappings.reset', config.mappings.reset, 'string')
|
|
1348
|
+
H.check_type('mappings.reveal_cwd', config.mappings.reveal_cwd, 'string')
|
|
1349
|
+
H.check_type('mappings.show_help', config.mappings.show_help, 'string')
|
|
1350
|
+
H.check_type('mappings.synchronize', config.mappings.synchronize, 'string')
|
|
1351
|
+
H.check_type('mappings.trim_left', config.mappings.trim_left, 'string')
|
|
1352
|
+
H.check_type('mappings.trim_right', config.mappings.trim_right, 'string')
|
|
1353
|
+
|
|
1354
|
+
H.check_type('options', config.options, 'table')
|
|
1355
|
+
H.check_type('options.use_as_default_explorer', config.options.use_as_default_explorer, 'boolean')
|
|
1356
|
+
H.check_type('options.permanent_delete', config.options.permanent_delete, 'boolean')
|
|
1357
|
+
H.check_type('options.lsp_timeout', config.options.lsp_timeout, 'number')
|
|
1358
|
+
|
|
1359
|
+
H.check_type('windows', config.windows, 'table')
|
|
1360
|
+
H.check_type('windows.max_number', config.windows.max_number, 'number')
|
|
1361
|
+
H.check_type('windows.preview', config.windows.preview, 'boolean')
|
|
1362
|
+
H.check_type('windows.width_focus', config.windows.width_focus, 'number')
|
|
1363
|
+
H.check_type('windows.width_nofocus', config.windows.width_nofocus, 'number')
|
|
1364
|
+
H.check_type('windows.width_preview', config.windows.width_preview, 'number')
|
|
1365
|
+
|
|
1366
|
+
return config
|
|
1367
|
+
end
|
|
1368
|
+
|
|
1369
|
+
H.apply_config = function(config) MiniFiles.config = config end
|
|
1370
|
+
|
|
1371
|
+
H.create_autocommands = function(config)
|
|
1372
|
+
local gr = vim.api.nvim_create_augroup('MiniFiles', {})
|
|
1373
|
+
|
|
1374
|
+
local au = function(event, pattern, callback, desc)
|
|
1375
|
+
vim.api.nvim_create_autocmd(event, { group = gr, pattern = pattern, callback = callback, desc = desc })
|
|
1376
|
+
end
|
|
1377
|
+
|
|
1378
|
+
if config.options.use_as_default_explorer then
|
|
1379
|
+
-- Stop 'netrw' from showing. Needs `VimEnter` event autocommand if
|
|
1380
|
+
-- this is called prior 'netrw' is set up
|
|
1381
|
+
vim.cmd('silent! autocmd! FileExplorer *')
|
|
1382
|
+
vim.cmd('autocmd VimEnter * ++once silent! autocmd! FileExplorer *')
|
|
1383
|
+
|
|
1384
|
+
au('BufEnter', '*', H.track_dir_edit, 'Track directory edit')
|
|
1385
|
+
end
|
|
1386
|
+
|
|
1387
|
+
au('VimResized', '*', MiniFiles.refresh, 'Refresh on resize')
|
|
1388
|
+
au('ColorScheme', '*', H.create_default_hl, 'Ensure colors')
|
|
1389
|
+
end
|
|
1390
|
+
|
|
1391
|
+
--stylua: ignore
|
|
1392
|
+
H.create_default_hl = function()
|
|
1393
|
+
local hi = function(name, opts)
|
|
1394
|
+
opts.default = true
|
|
1395
|
+
vim.api.nvim_set_hl(0, name, opts)
|
|
1396
|
+
end
|
|
1397
|
+
|
|
1398
|
+
hi('MiniFilesBorder', { link = 'FloatBorder' })
|
|
1399
|
+
hi('MiniFilesBorderModified', { link = 'DiagnosticFloatingWarn' })
|
|
1400
|
+
hi('MiniFilesCursorLine', { link = 'CursorLine' })
|
|
1401
|
+
hi('MiniFilesDirectory', { link = 'Directory' })
|
|
1402
|
+
hi('MiniFilesFile', {})
|
|
1403
|
+
hi('MiniFilesNormal', { link = 'NormalFloat' })
|
|
1404
|
+
hi('MiniFilesTitle', { link = 'FloatTitle' })
|
|
1405
|
+
hi('MiniFilesTitleFocused', { link = 'FloatTitle' })
|
|
1406
|
+
end
|
|
1407
|
+
|
|
1408
|
+
H.get_config = function(config)
|
|
1409
|
+
return vim.tbl_deep_extend('force', MiniFiles.config, vim.b.minifiles_config or {}, config or {})
|
|
1410
|
+
end
|
|
1411
|
+
|
|
1412
|
+
H.normalize_opts = function(explorer_opts, opts)
|
|
1413
|
+
opts = vim.tbl_deep_extend('force', H.get_config(), explorer_opts or {}, opts or {})
|
|
1414
|
+
opts.content.filter = opts.content.filter or MiniFiles.default_filter
|
|
1415
|
+
opts.content.highlight = opts.content.highlight or MiniFiles.default_highlight
|
|
1416
|
+
opts.content.prefix = opts.content.prefix or MiniFiles.default_prefix
|
|
1417
|
+
opts.content.sort = opts.content.sort or MiniFiles.default_sort
|
|
1418
|
+
|
|
1419
|
+
return opts
|
|
1420
|
+
end
|
|
1421
|
+
|
|
1422
|
+
-- Autocommands ---------------------------------------------------------------
|
|
1423
|
+
H.track_dir_edit = function(data)
|
|
1424
|
+
-- Make early returns
|
|
1425
|
+
if vim.api.nvim_get_current_buf() ~= data.buf then return end
|
|
1426
|
+
|
|
1427
|
+
if vim.b.minifiles_processed_dir then
|
|
1428
|
+
-- Smartly delete directory buffer if already visited
|
|
1429
|
+
local alt_buf = vim.fn.bufnr('#')
|
|
1430
|
+
-- - Setting alternative buffer is enough for the "directory buffer" to be
|
|
1431
|
+
-- wiped out, as it has `bufhidden=wipe`. Forcing delete after showing alt
|
|
1432
|
+
-- buffer might result in hard-to-track errors (like when opening directory
|
|
1433
|
+
-- in 'mini.pick' when there is an altbuf for the buffer in target window).
|
|
1434
|
+
if alt_buf ~= data.buf and vim.fn.buflisted(alt_buf) == 1 then return vim.api.nvim_win_set_buf(0, alt_buf) end
|
|
1435
|
+
return vim.api.nvim_buf_delete(data.buf, { force = true })
|
|
1436
|
+
end
|
|
1437
|
+
|
|
1438
|
+
local path = vim.api.nvim_buf_get_name(0)
|
|
1439
|
+
if vim.fn.isdirectory(path) ~= 1 then return end
|
|
1440
|
+
|
|
1441
|
+
-- Make directory buffer disappear when it is not needed
|
|
1442
|
+
vim.bo.bufhidden = 'wipe'
|
|
1443
|
+
vim.b.minifiles_processed_dir = true
|
|
1444
|
+
|
|
1445
|
+
-- Open directory without history
|
|
1446
|
+
vim.schedule(function() MiniFiles.open(path, false) end)
|
|
1447
|
+
end
|
|
1448
|
+
|
|
1449
|
+
-- Explorers ------------------------------------------------------------------
|
|
1450
|
+
---@class Explorer
|
|
1451
|
+
---
|
|
1452
|
+
---@field bookmarks table Map from single characters to bookmark data: table
|
|
1453
|
+
--- with <path> and <desc> fields.
|
|
1454
|
+
---@field branch table Array of absolute directory paths from parent to child.
|
|
1455
|
+
--- Its ids are called depth.
|
|
1456
|
+
---@field depth_focus number Depth to focus.
|
|
1457
|
+
---@field views table Views for paths. Each view is a table with:
|
|
1458
|
+
--- - <buf_id> where to show directory content.
|
|
1459
|
+
--- - <was_focused> - whether buffer was focused during current session.
|
|
1460
|
+
--- - <cursor> to position cursor; can be:
|
|
1461
|
+
--- - `{ line, col }` table to set cursor when buffer changes window.
|
|
1462
|
+
--- - `entry_name` string entry name to find inside directory buffer.
|
|
1463
|
+
---@field windows table Array of currently opened window ids (left to right).
|
|
1464
|
+
---@field anchor string Anchor directory of the explorer. Used as index in
|
|
1465
|
+
--- history and for `reset()` operation.
|
|
1466
|
+
---@field tabpage_id number Id of current tabpage
|
|
1467
|
+
---@field target_window number Id of window in which files will be opened.
|
|
1468
|
+
---@field opts table Options used for this particular explorer.
|
|
1469
|
+
---@field is_corrupted boolean Whether this particular explorer can not be
|
|
1470
|
+
--- normalized and should be closed.
|
|
1471
|
+
---@private
|
|
1472
|
+
H.explorer_new = function(path)
|
|
1473
|
+
return {
|
|
1474
|
+
branch = { path },
|
|
1475
|
+
depth_focus = 1,
|
|
1476
|
+
views = {},
|
|
1477
|
+
windows = {},
|
|
1478
|
+
anchor = path,
|
|
1479
|
+
tabpage_id = vim.api.nvim_get_current_tabpage(),
|
|
1480
|
+
target_window = vim.api.nvim_get_current_win(),
|
|
1481
|
+
bookmarks = {},
|
|
1482
|
+
opts = {},
|
|
1483
|
+
}
|
|
1484
|
+
end
|
|
1485
|
+
|
|
1486
|
+
H.explorer_get = function(tabpage_id, ignore_visibility)
|
|
1487
|
+
tabpage_id = tabpage_id or vim.api.nvim_get_current_tabpage()
|
|
1488
|
+
local res = H.opened_explorers[tabpage_id]
|
|
1489
|
+
|
|
1490
|
+
if ignore_visibility or H.explorer_is_visible(res) then return res end
|
|
1491
|
+
|
|
1492
|
+
H.opened_explorers[tabpage_id] = nil
|
|
1493
|
+
return nil
|
|
1494
|
+
end
|
|
1495
|
+
|
|
1496
|
+
H.explorer_is_visible = function(explorer)
|
|
1497
|
+
if explorer == nil then return nil end
|
|
1498
|
+
for _, win_id in ipairs(explorer.windows) do
|
|
1499
|
+
if H.is_valid_win(win_id) then return true end
|
|
1500
|
+
end
|
|
1501
|
+
return false
|
|
1502
|
+
end
|
|
1503
|
+
|
|
1504
|
+
H.explorer_refresh = function(explorer, opts)
|
|
1505
|
+
explorer = H.explorer_normalize(explorer)
|
|
1506
|
+
if explorer.is_corrupted then
|
|
1507
|
+
-- Make sure that same explorer can be opened later from history
|
|
1508
|
+
explorer.is_corrupted = false
|
|
1509
|
+
MiniFiles.close()
|
|
1510
|
+
return
|
|
1511
|
+
end
|
|
1512
|
+
if #explorer.branch == 0 then return end
|
|
1513
|
+
opts = opts or {}
|
|
1514
|
+
|
|
1515
|
+
-- Update cursor data in shown views. Do this prior to buffer updates for
|
|
1516
|
+
-- cursors to "stick" to current items.
|
|
1517
|
+
if not opts.skip_update_cursor then explorer = H.explorer_update_cursors(explorer) end
|
|
1518
|
+
|
|
1519
|
+
-- Ensure no outdated views
|
|
1520
|
+
for path, view in pairs(explorer.views) do
|
|
1521
|
+
if not H.fs_is_present_path(path) then
|
|
1522
|
+
H.buffer_delete(view.buf_id)
|
|
1523
|
+
explorer.views[path] = nil
|
|
1524
|
+
end
|
|
1525
|
+
end
|
|
1526
|
+
|
|
1527
|
+
-- Possibly force content updates on all explorer buffers. Doing it for *all*
|
|
1528
|
+
-- of them and not only on modified ones to allow sync changes from outside.
|
|
1529
|
+
if opts.force_update then
|
|
1530
|
+
for path, view in pairs(explorer.views) do
|
|
1531
|
+
-- Encode cursors to allow them to "stick" to current entry
|
|
1532
|
+
view = H.view_encode_cursor(view)
|
|
1533
|
+
-- Force update of shown path ids
|
|
1534
|
+
if H.opened_buffers[view.buf_id] then H.opened_buffers[view.buf_id].children_path_ids = nil end
|
|
1535
|
+
H.buffer_update(view.buf_id, path, explorer.opts, not view.was_focused)
|
|
1536
|
+
explorer.views[path] = view
|
|
1537
|
+
end
|
|
1538
|
+
end
|
|
1539
|
+
|
|
1540
|
+
-- Make sure that cursors point at paths to their right.
|
|
1541
|
+
-- NOTE: Doing this here and not relying on `CursorMoved` autocommand ensures
|
|
1542
|
+
-- that no more windows are opened than necessary (reduces flickering).
|
|
1543
|
+
for depth = 1, #explorer.branch do
|
|
1544
|
+
explorer = H.explorer_sync_cursor_and_branch(explorer, depth)
|
|
1545
|
+
end
|
|
1546
|
+
|
|
1547
|
+
-- Unregister windows from showed buffers, as they might get outdated
|
|
1548
|
+
for _, win_id in ipairs(explorer.windows) do
|
|
1549
|
+
-- NOTE: window can be invalid if it was showing buffer that was deleted
|
|
1550
|
+
if H.is_valid_win(win_id) then
|
|
1551
|
+
local buf_id = vim.api.nvim_win_get_buf(win_id)
|
|
1552
|
+
H.opened_buffers[buf_id].win_id = nil
|
|
1553
|
+
end
|
|
1554
|
+
end
|
|
1555
|
+
|
|
1556
|
+
-- Compute depth range which is possible to show in current window
|
|
1557
|
+
local depth_range = H.compute_visible_depth_range(explorer, explorer.opts)
|
|
1558
|
+
|
|
1559
|
+
-- Refresh window for every target depth keeping track of position column
|
|
1560
|
+
local cur_win_col, cur_win_count, opened_windows = 0, 0, {}
|
|
1561
|
+
for depth = depth_range.from, depth_range.to do
|
|
1562
|
+
cur_win_count = cur_win_count + 1
|
|
1563
|
+
local cur_width, was_opened = H.explorer_refresh_depth_window(explorer, depth, cur_win_count, cur_win_col)
|
|
1564
|
+
opened_windows[explorer.windows[cur_win_count]] = was_opened
|
|
1565
|
+
|
|
1566
|
+
-- Add 2 to account for left and right borders
|
|
1567
|
+
cur_win_col = cur_win_col + cur_width + 2
|
|
1568
|
+
end
|
|
1569
|
+
|
|
1570
|
+
-- Close possibly opened window that don't fit (like after `VimResized`)
|
|
1571
|
+
for depth = cur_win_count + 1, #explorer.windows do
|
|
1572
|
+
H.window_close(explorer.windows[depth])
|
|
1573
|
+
explorer.windows[depth] = nil
|
|
1574
|
+
end
|
|
1575
|
+
|
|
1576
|
+
-- Focus on proper window
|
|
1577
|
+
local win_focus_count = explorer.depth_focus - depth_range.from + 1
|
|
1578
|
+
local win_id_focused = explorer.windows[win_focus_count]
|
|
1579
|
+
H.window_focus(win_id_focused)
|
|
1580
|
+
|
|
1581
|
+
-- Register as currently opened
|
|
1582
|
+
explorer.tabpage_id = vim.api.nvim_win_get_tabpage(win_id_focused)
|
|
1583
|
+
H.opened_explorers[explorer.tabpage_id] = explorer
|
|
1584
|
+
|
|
1585
|
+
-- Trigger window related events after all windows are fully refreshed
|
|
1586
|
+
for _, win_id in ipairs(explorer.windows) do
|
|
1587
|
+
local data = { buf_id = vim.api.nvim_win_get_buf(win_id), win_id = win_id }
|
|
1588
|
+
if opened_windows[win_id] then H.trigger_event('MiniFilesWindowOpen', data) end
|
|
1589
|
+
H.trigger_event('MiniFilesWindowUpdate', data)
|
|
1590
|
+
end
|
|
1591
|
+
|
|
1592
|
+
return explorer
|
|
1593
|
+
end
|
|
1594
|
+
|
|
1595
|
+
H.explorer_track_lost_focus = function()
|
|
1596
|
+
local track = vim.schedule_wrap(function()
|
|
1597
|
+
if H.vim_ui.select_active or H.vim_ui.input_active then return end
|
|
1598
|
+
local ft = vim.bo.filetype
|
|
1599
|
+
if ft == 'minifiles' or ft == 'minifiles-help' then return end
|
|
1600
|
+
local cur_win_id = vim.api.nvim_get_current_win()
|
|
1601
|
+
MiniFiles.close()
|
|
1602
|
+
pcall(vim.api.nvim_set_current_win, cur_win_id)
|
|
1603
|
+
end)
|
|
1604
|
+
H.timers.focus:start(1000, 1000, track)
|
|
1605
|
+
end
|
|
1606
|
+
|
|
1607
|
+
H.explorer_adjust_vim_ui = function()
|
|
1608
|
+
H.vim_ui.select = vim.ui.select
|
|
1609
|
+
vim.ui.select = function(items, opts, on_choice)
|
|
1610
|
+
H.vim_ui.select_active = true
|
|
1611
|
+
H.vim_ui.select(items, opts, function(...)
|
|
1612
|
+
H.vim_ui.select_active = false
|
|
1613
|
+
on_choice(...)
|
|
1614
|
+
end)
|
|
1615
|
+
end
|
|
1616
|
+
|
|
1617
|
+
H.vim_ui.input = vim.ui.input
|
|
1618
|
+
vim.ui.input = function(opts, on_confirm)
|
|
1619
|
+
H.vim_ui.input_active = true
|
|
1620
|
+
H.vim_ui.input(opts, function(...)
|
|
1621
|
+
H.vim_ui.input_active = false
|
|
1622
|
+
on_confirm(...)
|
|
1623
|
+
end)
|
|
1624
|
+
end
|
|
1625
|
+
end
|
|
1626
|
+
|
|
1627
|
+
H.explorer_normalize = function(explorer)
|
|
1628
|
+
-- Ensure that all paths from branch are valid present paths
|
|
1629
|
+
local norm_branch = {}
|
|
1630
|
+
for _, path in ipairs(explorer.branch) do
|
|
1631
|
+
if not H.fs_is_present_path(path) then break end
|
|
1632
|
+
table.insert(norm_branch, path)
|
|
1633
|
+
end
|
|
1634
|
+
|
|
1635
|
+
local cur_max_depth = #norm_branch
|
|
1636
|
+
|
|
1637
|
+
explorer.branch = norm_branch
|
|
1638
|
+
explorer.depth_focus = math.min(math.max(explorer.depth_focus, 1), cur_max_depth)
|
|
1639
|
+
|
|
1640
|
+
-- Close all guaranteed to be unnecessary windows. NOTE: some windows might
|
|
1641
|
+
-- still get outdated later if branch is too deep to fit into Neovim's width.
|
|
1642
|
+
for i = cur_max_depth + 1, #explorer.windows do
|
|
1643
|
+
H.window_close(explorer.windows[i])
|
|
1644
|
+
explorer.windows[i] = nil
|
|
1645
|
+
end
|
|
1646
|
+
|
|
1647
|
+
-- Compute if explorer is corrupted and should not operate further
|
|
1648
|
+
for _, win_id in pairs(explorer.windows) do
|
|
1649
|
+
if not H.is_valid_win(win_id) then explorer.is_corrupted = true end
|
|
1650
|
+
end
|
|
1651
|
+
|
|
1652
|
+
return explorer
|
|
1653
|
+
end
|
|
1654
|
+
|
|
1655
|
+
H.explorer_sync_cursor_and_branch = function(explorer, depth)
|
|
1656
|
+
-- Compute helper data while making early returns
|
|
1657
|
+
if #explorer.branch < depth then return explorer end
|
|
1658
|
+
|
|
1659
|
+
local path, path_to_right = explorer.branch[depth], explorer.branch[depth + 1]
|
|
1660
|
+
local view = explorer.views[path]
|
|
1661
|
+
if view == nil then return explorer end
|
|
1662
|
+
|
|
1663
|
+
local buf_id, cursor = view.buf_id, view.cursor
|
|
1664
|
+
if cursor == nil then return explorer end
|
|
1665
|
+
|
|
1666
|
+
-- Compute if path at cursor and path to the right are equal (in sync)
|
|
1667
|
+
local cursor_path
|
|
1668
|
+
if type(cursor) == 'table' and H.is_valid_buf(buf_id) then
|
|
1669
|
+
local l = H.get_bufline(buf_id, cursor[1])
|
|
1670
|
+
-- Fall back to treating current line as full basename, but for not yet
|
|
1671
|
+
-- existing (a.k.a. "imaginary") path keep showing preview.
|
|
1672
|
+
cursor_path = H.path_index[H.match_line_path_id(l)] or H.fs_child_path(path, l .. '\000')
|
|
1673
|
+
elseif type(cursor) == 'string' then
|
|
1674
|
+
cursor_path = H.fs_child_path(path, cursor)
|
|
1675
|
+
else
|
|
1676
|
+
return explorer
|
|
1677
|
+
end
|
|
1678
|
+
|
|
1679
|
+
if cursor_path == path_to_right then return explorer end
|
|
1680
|
+
|
|
1681
|
+
-- Trim branch if cursor path is not in sync with path to the right
|
|
1682
|
+
for i = depth + 1, #explorer.branch do
|
|
1683
|
+
explorer.branch[i] = nil
|
|
1684
|
+
end
|
|
1685
|
+
explorer.depth_focus = math.min(explorer.depth_focus, #explorer.branch)
|
|
1686
|
+
|
|
1687
|
+
-- Show preview to the right of current buffer if needed
|
|
1688
|
+
local show_preview = explorer.opts.windows.preview
|
|
1689
|
+
local is_cur_buf = explorer.depth_focus == depth
|
|
1690
|
+
if show_preview and is_cur_buf then table.insert(explorer.branch, cursor_path) end
|
|
1691
|
+
|
|
1692
|
+
return explorer
|
|
1693
|
+
end
|
|
1694
|
+
|
|
1695
|
+
H.explorer_go_in_range = function(explorer, buf_id, from_line, to_line)
|
|
1696
|
+
-- Compute which entries to go in: all files and only last directory
|
|
1697
|
+
local files, path, line = {}, nil, nil
|
|
1698
|
+
for i = from_line, to_line do
|
|
1699
|
+
local fs_entry = MiniFiles.get_fs_entry(buf_id, i) or {}
|
|
1700
|
+
if fs_entry.fs_type == 'file' then table.insert(files, fs_entry.path) end
|
|
1701
|
+
if fs_entry.fs_type == 'directory' then
|
|
1702
|
+
path, line = fs_entry.path, i
|
|
1703
|
+
end
|
|
1704
|
+
if fs_entry.fs_type == nil and fs_entry.path == nil then
|
|
1705
|
+
local entry = vim.inspect(H.get_bufline(buf_id, i))
|
|
1706
|
+
H.notify('Line ' .. entry .. ' does not have proper format. Did you modify without synchronization?', 'WARN')
|
|
1707
|
+
end
|
|
1708
|
+
if fs_entry.fs_type == nil and fs_entry.path ~= nil then
|
|
1709
|
+
local path_resolved = vim.fn.resolve(fs_entry.path)
|
|
1710
|
+
local symlink_info = path_resolved == fs_entry.path and ''
|
|
1711
|
+
or (' Looks like miscreated symlink (resolved to ' .. path_resolved .. ').')
|
|
1712
|
+
H.notify('Path ' .. fs_entry.path .. ' is not present on disk.' .. symlink_info, 'WARN')
|
|
1713
|
+
end
|
|
1714
|
+
end
|
|
1715
|
+
|
|
1716
|
+
for _, file_path in ipairs(files) do
|
|
1717
|
+
explorer = H.explorer_open_file(explorer, file_path)
|
|
1718
|
+
end
|
|
1719
|
+
|
|
1720
|
+
if path ~= nil then
|
|
1721
|
+
explorer = H.explorer_open_directory(explorer, path, explorer.depth_focus + 1)
|
|
1722
|
+
|
|
1723
|
+
-- Ensure that cursor points to the directory in current window (can be not
|
|
1724
|
+
-- the case if cursor is not on the actually opened directory)
|
|
1725
|
+
local win_id = H.opened_buffers[buf_id].win_id
|
|
1726
|
+
if H.is_valid_win(win_id) then vim.api.nvim_win_set_cursor(win_id, { line, 0 }) end
|
|
1727
|
+
end
|
|
1728
|
+
|
|
1729
|
+
return explorer
|
|
1730
|
+
end
|
|
1731
|
+
|
|
1732
|
+
H.explorer_focus_on_entry = function(explorer, path, entry_name)
|
|
1733
|
+
if entry_name == nil then return explorer end
|
|
1734
|
+
|
|
1735
|
+
-- Set focus on directory. Reset if it is not in current branch.
|
|
1736
|
+
explorer.depth_focus = H.explorer_get_path_depth(explorer, path)
|
|
1737
|
+
if explorer.depth_focus == nil then
|
|
1738
|
+
explorer.branch, explorer.depth_focus = { path }, 1
|
|
1739
|
+
end
|
|
1740
|
+
|
|
1741
|
+
-- Set cursor on entry
|
|
1742
|
+
local path_view = explorer.views[path] or {}
|
|
1743
|
+
path_view.cursor = entry_name
|
|
1744
|
+
explorer.views[path] = path_view
|
|
1745
|
+
|
|
1746
|
+
return explorer
|
|
1747
|
+
end
|
|
1748
|
+
|
|
1749
|
+
H.explorer_compute_fs_actions = function(explorer)
|
|
1750
|
+
-- Compute differences
|
|
1751
|
+
local fs_diffs = {}
|
|
1752
|
+
for _, view in pairs(explorer.views) do
|
|
1753
|
+
local dir_fs_diff = H.buffer_compute_fs_diff(view.buf_id)
|
|
1754
|
+
if #dir_fs_diff > 0 then vim.list_extend(fs_diffs, dir_fs_diff) end
|
|
1755
|
+
end
|
|
1756
|
+
if #fs_diffs == 0 then return nil end
|
|
1757
|
+
|
|
1758
|
+
-- Convert differences into actions
|
|
1759
|
+
local create, delete_map, raw_copy = {}, {}, {}
|
|
1760
|
+
|
|
1761
|
+
-- - Differentiate between create, delete, and copy
|
|
1762
|
+
for _, diff in ipairs(fs_diffs) do
|
|
1763
|
+
if diff.from == nil then
|
|
1764
|
+
table.insert(create, { action = 'create', dir = diff.dir, to = diff.to })
|
|
1765
|
+
elseif diff.to == nil then
|
|
1766
|
+
delete_map[diff.from] = true
|
|
1767
|
+
else
|
|
1768
|
+
table.insert(raw_copy, diff)
|
|
1769
|
+
end
|
|
1770
|
+
end
|
|
1771
|
+
|
|
1772
|
+
-- - Narrow down copy action into rename or move: `delete + copy` is `rename`
|
|
1773
|
+
-- if in same directory and `move` otherwise
|
|
1774
|
+
local rename, move, copy = {}, {}, {}
|
|
1775
|
+
for _, diff in pairs(raw_copy) do
|
|
1776
|
+
local action, target = 'copy', copy
|
|
1777
|
+
if delete_map[diff.from] then
|
|
1778
|
+
-- Treat appending `/` to file name as file -> directory conversion
|
|
1779
|
+
-- (i.e. delete file + create directory)
|
|
1780
|
+
if diff.to == (diff.from .. '/') and H.fs_get_type(diff.from) == 'file' then
|
|
1781
|
+
target, action, diff.from = create, 'create', nil
|
|
1782
|
+
else
|
|
1783
|
+
action = H.fs_get_parent(diff.from) == H.fs_get_parent(diff.to) and 'rename' or 'move'
|
|
1784
|
+
target = action == 'rename' and rename or move
|
|
1785
|
+
-- NOTE: Use map instead of array to ensure single move/rename per path
|
|
1786
|
+
delete_map[diff.from] = nil
|
|
1787
|
+
end
|
|
1788
|
+
end
|
|
1789
|
+
table.insert(target, { action = action, dir = diff.dir, from = diff.from, to = diff.to })
|
|
1790
|
+
end
|
|
1791
|
+
|
|
1792
|
+
-- Compute delete actions accounting for (non) permanent delete
|
|
1793
|
+
local delete, is_trash = {}, not explorer.opts.options.permanent_delete
|
|
1794
|
+
local trash_dir = H.fs_child_path(vim.fn.stdpath('data'), 'mini.files/trash')
|
|
1795
|
+
for p, _ in pairs(delete_map) do
|
|
1796
|
+
local to = is_trash and H.fs_child_path(trash_dir, H.fs_get_basename(p)) or nil
|
|
1797
|
+
table.insert(delete, { action = 'delete', from = p, to = to })
|
|
1798
|
+
end
|
|
1799
|
+
|
|
1800
|
+
-- Construct final array with proper order of actions:
|
|
1801
|
+
-- - If action depends on the path which will be deleted, perform it first.
|
|
1802
|
+
-- - "Delete"/"move"/"rename" before "copy"/"create" to free space for them.
|
|
1803
|
+
-- - Move/rename (if successful) will later adjust next steps at execution.
|
|
1804
|
+
local before_delete, after_delete = {}, {}
|
|
1805
|
+
for _, arr in ipairs({ move, rename, copy, create }) do
|
|
1806
|
+
for _, diff in ipairs(arr) do
|
|
1807
|
+
local will_be_deleted = false
|
|
1808
|
+
for _, del in ipairs(delete) do
|
|
1809
|
+
local del_from_dir = del.from .. '/'
|
|
1810
|
+
local from_is_affected = del.from == diff.from or vim.startswith(diff.from or '', del_from_dir)
|
|
1811
|
+
-- Don't directly account for deleted path to allow "act on freed path"
|
|
1812
|
+
-- But make "append `/` to file" work as "delete file" + "create dir"
|
|
1813
|
+
local to_is_affected = vim.startswith(diff.to, del_from_dir) and diff.to ~= del_from_dir
|
|
1814
|
+
will_be_deleted = will_be_deleted or from_is_affected or to_is_affected
|
|
1815
|
+
end
|
|
1816
|
+
table.insert(will_be_deleted and before_delete or after_delete, diff)
|
|
1817
|
+
end
|
|
1818
|
+
end
|
|
1819
|
+
|
|
1820
|
+
local res = {}
|
|
1821
|
+
vim.list_extend(res, before_delete)
|
|
1822
|
+
vim.list_extend(res, delete)
|
|
1823
|
+
vim.list_extend(res, after_delete)
|
|
1824
|
+
return res
|
|
1825
|
+
end
|
|
1826
|
+
|
|
1827
|
+
H.explorer_update_cursors = function(explorer)
|
|
1828
|
+
for _, win_id in ipairs(explorer.windows) do
|
|
1829
|
+
if H.is_valid_win(win_id) then
|
|
1830
|
+
local buf_id = vim.api.nvim_win_get_buf(win_id)
|
|
1831
|
+
local path = H.opened_buffers[buf_id].path
|
|
1832
|
+
explorer.views[path].cursor = vim.api.nvim_win_get_cursor(win_id)
|
|
1833
|
+
end
|
|
1834
|
+
end
|
|
1835
|
+
|
|
1836
|
+
return explorer
|
|
1837
|
+
end
|
|
1838
|
+
|
|
1839
|
+
H.explorer_refresh_depth_window = function(explorer, depth, win_count, win_col)
|
|
1840
|
+
local path = explorer.branch[depth]
|
|
1841
|
+
local views, windows, opts = explorer.views, explorer.windows, explorer.opts
|
|
1842
|
+
|
|
1843
|
+
-- Compute width based on window role
|
|
1844
|
+
local win_is_focused = depth == explorer.depth_focus
|
|
1845
|
+
local win_is_preview = opts.windows.preview and (depth == (explorer.depth_focus + 1))
|
|
1846
|
+
local cur_width = win_is_focused and opts.windows.width_focus
|
|
1847
|
+
or (win_is_preview and opts.windows.width_preview or opts.windows.width_nofocus)
|
|
1848
|
+
|
|
1849
|
+
-- Prepare target view
|
|
1850
|
+
local view = views[path] or {}
|
|
1851
|
+
view = H.view_ensure_proper(view, path, opts, win_is_focused, win_is_preview)
|
|
1852
|
+
views[path] = view
|
|
1853
|
+
|
|
1854
|
+
-- Create relevant window config
|
|
1855
|
+
local config = {
|
|
1856
|
+
col = win_col,
|
|
1857
|
+
height = vim.api.nvim_buf_line_count(view.buf_id),
|
|
1858
|
+
width = cur_width,
|
|
1859
|
+
-- Use shortened full path in left most window
|
|
1860
|
+
title = win_count == 1 and H.fs_shorten_path(H.fs_full_path(path)) or H.fs_get_basename(path),
|
|
1861
|
+
}
|
|
1862
|
+
config.title = ' ' .. H.sanitize_string(config.title) .. ' '
|
|
1863
|
+
|
|
1864
|
+
-- Prepare and register window
|
|
1865
|
+
local win_id, was_opened = windows[win_count], false
|
|
1866
|
+
if not H.is_valid_win(win_id) then
|
|
1867
|
+
H.window_close(win_id)
|
|
1868
|
+
win_id = H.window_open(view.buf_id, config)
|
|
1869
|
+
windows[win_count] = win_id
|
|
1870
|
+
was_opened = true
|
|
1871
|
+
end
|
|
1872
|
+
|
|
1873
|
+
H.window_update(win_id, config)
|
|
1874
|
+
|
|
1875
|
+
-- Show view in window
|
|
1876
|
+
H.window_set_view(win_id, view)
|
|
1877
|
+
|
|
1878
|
+
-- Update explorer data
|
|
1879
|
+
explorer.views = views
|
|
1880
|
+
explorer.windows = windows
|
|
1881
|
+
|
|
1882
|
+
-- Return width of current window to keep track of window column
|
|
1883
|
+
return cur_width, was_opened
|
|
1884
|
+
end
|
|
1885
|
+
|
|
1886
|
+
H.explorer_get_path_depth = function(explorer, path)
|
|
1887
|
+
for depth, depth_path in pairs(explorer.branch) do
|
|
1888
|
+
if path == depth_path then return depth end
|
|
1889
|
+
end
|
|
1890
|
+
end
|
|
1891
|
+
|
|
1892
|
+
H.explorer_ignore_pending_fs_actions = function(explorer, action_name)
|
|
1893
|
+
-- Exit if nothing to ignore
|
|
1894
|
+
if H.explorer_compute_fs_actions(explorer) == nil then return true end
|
|
1895
|
+
|
|
1896
|
+
local msg = string.format('There are pending file system actions\n\n%s without synchronization?', action_name)
|
|
1897
|
+
local confirm_res = vim.fn.confirm(msg, '&Yes\n&No', 1, 'Question')
|
|
1898
|
+
return confirm_res == 1
|
|
1899
|
+
end
|
|
1900
|
+
|
|
1901
|
+
H.explorer_open_file = function(explorer, path)
|
|
1902
|
+
explorer = H.explorer_ensure_target_window(explorer)
|
|
1903
|
+
H.edit(path, explorer.target_window)
|
|
1904
|
+
return explorer
|
|
1905
|
+
end
|
|
1906
|
+
|
|
1907
|
+
H.explorer_ensure_target_window = function(explorer)
|
|
1908
|
+
if not H.is_valid_win(explorer.target_window) then explorer.target_window = H.get_first_valid_normal_window() end
|
|
1909
|
+
return explorer
|
|
1910
|
+
end
|
|
1911
|
+
|
|
1912
|
+
H.explorer_open_directory = function(explorer, path, target_depth)
|
|
1913
|
+
-- Update focused depth
|
|
1914
|
+
explorer.depth_focus = target_depth
|
|
1915
|
+
|
|
1916
|
+
-- Truncate rest of the branch if opening another path at target depth
|
|
1917
|
+
local show_new_path_at_depth = path ~= explorer.branch[target_depth]
|
|
1918
|
+
if show_new_path_at_depth then
|
|
1919
|
+
explorer.branch[target_depth] = path
|
|
1920
|
+
explorer = H.explorer_trim_branch_right(explorer)
|
|
1921
|
+
end
|
|
1922
|
+
|
|
1923
|
+
return explorer
|
|
1924
|
+
end
|
|
1925
|
+
|
|
1926
|
+
H.explorer_open_root_parent = function(explorer)
|
|
1927
|
+
local root = explorer.branch[1]
|
|
1928
|
+
local root_parent = H.fs_get_parent(root)
|
|
1929
|
+
if root_parent == nil then return explorer end
|
|
1930
|
+
|
|
1931
|
+
-- Update branch data
|
|
1932
|
+
table.insert(explorer.branch, 1, root_parent)
|
|
1933
|
+
|
|
1934
|
+
-- Focus on previous root entry in its parent
|
|
1935
|
+
return H.explorer_focus_on_entry(explorer, root_parent, H.fs_get_basename(root))
|
|
1936
|
+
end
|
|
1937
|
+
|
|
1938
|
+
H.explorer_trim_branch_right = function(explorer)
|
|
1939
|
+
for i = explorer.depth_focus + 1, #explorer.branch do
|
|
1940
|
+
explorer.branch[i] = nil
|
|
1941
|
+
end
|
|
1942
|
+
return explorer
|
|
1943
|
+
end
|
|
1944
|
+
|
|
1945
|
+
H.explorer_trim_branch_left = function(explorer)
|
|
1946
|
+
local new_branch = {}
|
|
1947
|
+
for i = explorer.depth_focus, #explorer.branch do
|
|
1948
|
+
table.insert(new_branch, explorer.branch[i])
|
|
1949
|
+
end
|
|
1950
|
+
explorer.branch = new_branch
|
|
1951
|
+
explorer.depth_focus = 1
|
|
1952
|
+
return explorer
|
|
1953
|
+
end
|
|
1954
|
+
|
|
1955
|
+
H.explorer_show_help = function(explorer, explorer_buf_id, explorer_win_id)
|
|
1956
|
+
-- Compute lines
|
|
1957
|
+
local buf_mappings = vim.api.nvim_buf_get_keymap(explorer_buf_id, 'n')
|
|
1958
|
+
local map_data, desc_width = {}, 0
|
|
1959
|
+
for _, data in ipairs(buf_mappings) do
|
|
1960
|
+
if data.desc ~= nil then
|
|
1961
|
+
map_data[data.desc] = data.lhs:lower() == '<lt>' and '<' or data.lhs
|
|
1962
|
+
desc_width = math.max(desc_width, data.desc:len())
|
|
1963
|
+
end
|
|
1964
|
+
end
|
|
1965
|
+
|
|
1966
|
+
local desc_arr = vim.tbl_keys(map_data)
|
|
1967
|
+
table.sort(desc_arr)
|
|
1968
|
+
local map_format = string.format('%%-%ds │ %%s', desc_width)
|
|
1969
|
+
|
|
1970
|
+
local lines = { 'Buffer mappings:', '' }
|
|
1971
|
+
for _, desc in ipairs(desc_arr) do
|
|
1972
|
+
table.insert(lines, string.format(map_format, desc, map_data[desc]))
|
|
1973
|
+
end
|
|
1974
|
+
table.insert(lines, '')
|
|
1975
|
+
|
|
1976
|
+
local bookmark_ids = vim.tbl_keys(explorer.bookmarks)
|
|
1977
|
+
if #bookmark_ids > 0 then
|
|
1978
|
+
table.insert(lines, 'Bookmarks:')
|
|
1979
|
+
table.insert(lines, '')
|
|
1980
|
+
table.sort(bookmark_ids)
|
|
1981
|
+
for _, id in ipairs(bookmark_ids) do
|
|
1982
|
+
local data = explorer.bookmarks[id]
|
|
1983
|
+
local desc = data.desc or (vim.is_callable(data.path) and data.path() or data.path)
|
|
1984
|
+
table.insert(lines, id .. ' │ ' .. desc)
|
|
1985
|
+
end
|
|
1986
|
+
table.insert(lines, '')
|
|
1987
|
+
end
|
|
1988
|
+
|
|
1989
|
+
table.insert(lines, '(Press `q` to close)')
|
|
1990
|
+
|
|
1991
|
+
-- Create buffer
|
|
1992
|
+
local buf_id = vim.api.nvim_create_buf(false, true)
|
|
1993
|
+
H.set_buflines(buf_id, lines)
|
|
1994
|
+
H.set_buf_name(buf_id, 'help')
|
|
1995
|
+
|
|
1996
|
+
vim.keymap.set('n', 'q', '<Cmd>close<CR>', { buffer = buf_id, desc = 'Close this window' })
|
|
1997
|
+
|
|
1998
|
+
vim.b[buf_id].minicursorword_disable = true
|
|
1999
|
+
vim.b[buf_id].miniindentscope_disable = true
|
|
2000
|
+
|
|
2001
|
+
vim.bo[buf_id].filetype = 'minifiles-help'
|
|
2002
|
+
vim.bo[buf_id].bufhidden = 'wipe'
|
|
2003
|
+
|
|
2004
|
+
-- Compute window data
|
|
2005
|
+
local line_widths = vim.tbl_map(vim.fn.strdisplaywidth, lines)
|
|
2006
|
+
local max_line_width = math.max(unpack(line_widths))
|
|
2007
|
+
|
|
2008
|
+
local config = vim.api.nvim_win_get_config(explorer_win_id)
|
|
2009
|
+
config.relative = 'win'
|
|
2010
|
+
config.row = 0
|
|
2011
|
+
config.col = 0
|
|
2012
|
+
config.width = max_line_width
|
|
2013
|
+
config.height = #lines
|
|
2014
|
+
config.title = " 'mini.files' help "
|
|
2015
|
+
config.zindex = config.zindex + 1
|
|
2016
|
+
local default_border = (vim.fn.exists('+winborder') == 0 or vim.o.winborder == '') and 'single' or nil
|
|
2017
|
+
config.border = config.border or default_border
|
|
2018
|
+
config.style = 'minimal'
|
|
2019
|
+
|
|
2020
|
+
-- Open window
|
|
2021
|
+
local win_id = vim.api.nvim_open_win(buf_id, false, config)
|
|
2022
|
+
H.window_update_highlight(win_id, 'NormalFloat', 'MiniFilesNormal')
|
|
2023
|
+
H.window_update_highlight(win_id, 'FloatTitle', 'MiniFilesTitle')
|
|
2024
|
+
H.window_update_highlight(win_id, 'CursorLine', 'MiniFilesCursorLine')
|
|
2025
|
+
vim.wo[win_id].cursorline = true
|
|
2026
|
+
local culopt = vim.wo[win_id].cursorlineopt
|
|
2027
|
+
if culopt:find('line') == nil then vim.wo[win_id].cursorlineopt = culopt .. ',line' end
|
|
2028
|
+
|
|
2029
|
+
vim.api.nvim_set_current_win(win_id)
|
|
2030
|
+
return win_id
|
|
2031
|
+
end
|
|
2032
|
+
|
|
2033
|
+
H.compute_visible_depth_range = function(explorer, opts)
|
|
2034
|
+
-- Compute maximum number of windows possible to fit in current Neovim width
|
|
2035
|
+
-- Add 2 to widths to take into account width of left and right borders
|
|
2036
|
+
local width_focus, width_nofocus = opts.windows.width_focus + 2, opts.windows.width_nofocus + 2
|
|
2037
|
+
|
|
2038
|
+
local has_preview = explorer.opts.windows.preview and explorer.depth_focus < #explorer.branch
|
|
2039
|
+
local width_preview = has_preview and (opts.windows.width_preview + 2) or width_nofocus
|
|
2040
|
+
|
|
2041
|
+
local max_number = 1
|
|
2042
|
+
if (width_focus + width_preview) <= vim.o.columns then max_number = max_number + 1 end
|
|
2043
|
+
if (width_focus + width_preview + width_nofocus) <= vim.o.columns then
|
|
2044
|
+
max_number = max_number + math.floor((vim.o.columns - width_focus - width_preview) / width_nofocus)
|
|
2045
|
+
end
|
|
2046
|
+
|
|
2047
|
+
-- - Account for dedicated option
|
|
2048
|
+
max_number = math.min(math.max(max_number, 1), opts.windows.max_number)
|
|
2049
|
+
|
|
2050
|
+
-- Compute which branch entries to show with the following idea:
|
|
2051
|
+
-- - Always show focused depth as centered as possible.
|
|
2052
|
+
-- - Show as much as possible.
|
|
2053
|
+
-- Logic is similar to how text for 'mini.tabline' is computed.
|
|
2054
|
+
local branch_depth, depth_focus = #explorer.branch, explorer.depth_focus
|
|
2055
|
+
local n_panes = math.min(branch_depth, max_number)
|
|
2056
|
+
|
|
2057
|
+
local to = math.min(branch_depth, math.floor(depth_focus + 0.5 * n_panes))
|
|
2058
|
+
local from = math.max(1, to - n_panes + 1)
|
|
2059
|
+
to = from + math.min(n_panes, branch_depth) - 1
|
|
2060
|
+
|
|
2061
|
+
return { from = from, to = to }
|
|
2062
|
+
end
|
|
2063
|
+
|
|
2064
|
+
-- Views ----------------------------------------------------------------------
|
|
2065
|
+
H.view_ensure_proper = function(view, path, opts, is_focused, is_preview)
|
|
2066
|
+
-- Ensure proper buffer
|
|
2067
|
+
local needs_recreate, needs_reprocess = not H.is_valid_buf(view.buf_id), not view.was_focused and is_focused
|
|
2068
|
+
if needs_recreate then
|
|
2069
|
+
H.buffer_delete(view.buf_id)
|
|
2070
|
+
view.buf_id = H.buffer_create(path, opts.mappings)
|
|
2071
|
+
end
|
|
2072
|
+
if needs_recreate or needs_reprocess then
|
|
2073
|
+
-- Make sure that pressing `u` in new buffer does nothing
|
|
2074
|
+
local cache_undolevels = vim.bo[view.buf_id].undolevels
|
|
2075
|
+
vim.bo[view.buf_id].undolevels = -1
|
|
2076
|
+
H.buffer_update(view.buf_id, path, opts, is_preview)
|
|
2077
|
+
vim.bo[view.buf_id].undolevels = cache_undolevels
|
|
2078
|
+
end
|
|
2079
|
+
view.was_focused = view.was_focused or is_focused
|
|
2080
|
+
|
|
2081
|
+
-- Ensure proper cursor. If string, find it as line in current buffer.
|
|
2082
|
+
view.cursor = view.cursor or { 1, 0 }
|
|
2083
|
+
if type(view.cursor) == 'string' then view = H.view_decode_cursor(view) end
|
|
2084
|
+
|
|
2085
|
+
return view
|
|
2086
|
+
end
|
|
2087
|
+
|
|
2088
|
+
H.view_encode_cursor = function(view)
|
|
2089
|
+
local buf_id, cursor = view.buf_id, view.cursor
|
|
2090
|
+
if not H.is_valid_buf(buf_id) or type(cursor) ~= 'table' then return view end
|
|
2091
|
+
|
|
2092
|
+
-- Replace exact cursor coordinates with entry name to try and find later.
|
|
2093
|
+
-- This allows more robust opening explorer from history (as directory
|
|
2094
|
+
-- content may have changed and exact cursor position would be not valid).
|
|
2095
|
+
local l = H.get_bufline(buf_id, cursor[1])
|
|
2096
|
+
view.cursor = H.match_line_entry_name(l)
|
|
2097
|
+
return view
|
|
2098
|
+
end
|
|
2099
|
+
|
|
2100
|
+
H.view_decode_cursor = function(view)
|
|
2101
|
+
local buf_id, cursor = view.buf_id, view.cursor
|
|
2102
|
+
if not H.is_valid_buf(buf_id) or type(cursor) ~= 'string' then return view end
|
|
2103
|
+
|
|
2104
|
+
-- Find entry name named as stored in `cursor`. If not - use {1, 0}.
|
|
2105
|
+
local lines = vim.api.nvim_buf_get_lines(buf_id, 0, -1, false)
|
|
2106
|
+
for i, l in ipairs(lines) do
|
|
2107
|
+
if cursor == H.match_line_entry_name(l) then view.cursor = { i, 0 } end
|
|
2108
|
+
end
|
|
2109
|
+
|
|
2110
|
+
if type(view.cursor) ~= 'table' then view.cursor = { 1, 0 } end
|
|
2111
|
+
|
|
2112
|
+
return view
|
|
2113
|
+
end
|
|
2114
|
+
|
|
2115
|
+
H.view_invalidate_buffer = function(view)
|
|
2116
|
+
H.buffer_delete(view.buf_id)
|
|
2117
|
+
view.buf_id = nil
|
|
2118
|
+
return view
|
|
2119
|
+
end
|
|
2120
|
+
|
|
2121
|
+
H.view_track_cursor = vim.schedule_wrap(function(data)
|
|
2122
|
+
-- Schedule this in order to react *after* all pending changes are applied
|
|
2123
|
+
local buf_id = data.buf
|
|
2124
|
+
local buf_data = H.opened_buffers[buf_id]
|
|
2125
|
+
if buf_data == nil then return end
|
|
2126
|
+
|
|
2127
|
+
local win_id = buf_data.win_id
|
|
2128
|
+
if not H.is_valid_win(win_id) then return end
|
|
2129
|
+
|
|
2130
|
+
-- Ensure cursor doesn't go over path id and icon
|
|
2131
|
+
local cur_cursor = H.window_tweak_cursor(win_id, buf_id)
|
|
2132
|
+
|
|
2133
|
+
-- Ensure cursor line doesn't contradict window on the right
|
|
2134
|
+
local tabpage_id = vim.api.nvim_win_get_tabpage(win_id)
|
|
2135
|
+
local explorer = H.explorer_get(tabpage_id)
|
|
2136
|
+
if explorer == nil then return end
|
|
2137
|
+
|
|
2138
|
+
local buf_depth = H.explorer_get_path_depth(explorer, buf_data.path)
|
|
2139
|
+
if buf_depth == nil then return end
|
|
2140
|
+
|
|
2141
|
+
-- Update cursor in view and sync it with branch
|
|
2142
|
+
local view = explorer.views[buf_data.path]
|
|
2143
|
+
if view ~= nil then
|
|
2144
|
+
view.cursor = cur_cursor
|
|
2145
|
+
explorer.views[buf_data.path] = view
|
|
2146
|
+
end
|
|
2147
|
+
|
|
2148
|
+
explorer = H.explorer_sync_cursor_and_branch(explorer, buf_depth)
|
|
2149
|
+
|
|
2150
|
+
H.explorer_refresh(explorer)
|
|
2151
|
+
end)
|
|
2152
|
+
|
|
2153
|
+
H.view_track_text_change = function(data)
|
|
2154
|
+
-- Track 'modified'
|
|
2155
|
+
local buf_id = data.buf
|
|
2156
|
+
local new_n_modified = H.opened_buffers[buf_id].n_modified + 1
|
|
2157
|
+
H.opened_buffers[buf_id].n_modified = new_n_modified
|
|
2158
|
+
local win_id = H.opened_buffers[buf_id].win_id
|
|
2159
|
+
if new_n_modified > 0 and H.is_valid_win(win_id) then H.window_update_border_hl(win_id) end
|
|
2160
|
+
|
|
2161
|
+
-- Track window height
|
|
2162
|
+
if not H.is_valid_win(win_id) then return end
|
|
2163
|
+
|
|
2164
|
+
local cur_height = vim.api.nvim_win_get_height(win_id)
|
|
2165
|
+
local n_lines = vim.api.nvim_buf_line_count(buf_id)
|
|
2166
|
+
local new_height = math.min(n_lines, H.window_get_max_height())
|
|
2167
|
+
vim.api.nvim_win_set_height(win_id, new_height)
|
|
2168
|
+
|
|
2169
|
+
-- Trigger appropriate event if window height has changed
|
|
2170
|
+
if cur_height ~= new_height then
|
|
2171
|
+
H.trigger_event('MiniFilesWindowUpdate', { buf_id = buf_id, win_id = win_id })
|
|
2172
|
+
new_height = vim.api.nvim_win_get_height(win_id)
|
|
2173
|
+
end
|
|
2174
|
+
|
|
2175
|
+
-- Ensure that only buffer lines are shown. This can be not the case if after
|
|
2176
|
+
-- text edit cursor moved past previous last line.
|
|
2177
|
+
local last_visible_line = vim.fn.line('w0', win_id) + new_height - 1
|
|
2178
|
+
local out_of_buf_lines = last_visible_line - n_lines
|
|
2179
|
+
-- - Possibly scroll window upward (`\25` is an escaped `<C-y>`)
|
|
2180
|
+
if out_of_buf_lines > 0 then
|
|
2181
|
+
-- Preserve cursor as scrolling might affect it (like in Insert mode)
|
|
2182
|
+
local cursor = vim.api.nvim_win_get_cursor(win_id)
|
|
2183
|
+
vim.cmd('normal! ' .. out_of_buf_lines .. '\25')
|
|
2184
|
+
vim.api.nvim_win_set_cursor(win_id, cursor)
|
|
2185
|
+
end
|
|
2186
|
+
end
|
|
2187
|
+
|
|
2188
|
+
-- Buffers --------------------------------------------------------------------
|
|
2189
|
+
H.buffer_create = function(path, mappings)
|
|
2190
|
+
-- Create buffer
|
|
2191
|
+
local buf_id = vim.api.nvim_create_buf(false, true)
|
|
2192
|
+
H.set_buf_name(buf_id, path)
|
|
2193
|
+
|
|
2194
|
+
-- Register buffer
|
|
2195
|
+
H.opened_buffers[buf_id] = { path = path }
|
|
2196
|
+
|
|
2197
|
+
-- Set buffer options
|
|
2198
|
+
vim.bo[buf_id].filetype = 'minifiles'
|
|
2199
|
+
|
|
2200
|
+
-- Do not set up tracking behavior for imaginary paths
|
|
2201
|
+
if H.fs_is_imaginary_path(path) then return buf_id end
|
|
2202
|
+
|
|
2203
|
+
-- Make buffer mappings
|
|
2204
|
+
H.buffer_make_mappings(buf_id, mappings)
|
|
2205
|
+
|
|
2206
|
+
-- Make buffer autocommands
|
|
2207
|
+
local augroup = vim.api.nvim_create_augroup('MiniFiles', { clear = false })
|
|
2208
|
+
local au = function(events, desc, callback)
|
|
2209
|
+
vim.api.nvim_create_autocmd(events, { group = augroup, buffer = buf_id, desc = desc, callback = callback })
|
|
2210
|
+
end
|
|
2211
|
+
|
|
2212
|
+
au({ 'CursorMoved', 'CursorMovedI', 'TextChangedP' }, 'Tweak cursor position', H.view_track_cursor)
|
|
2213
|
+
au({ 'TextChanged', 'TextChangedI', 'TextChangedP' }, 'Track buffer modification', H.view_track_text_change)
|
|
2214
|
+
|
|
2215
|
+
-- Tweak buffer to be used nicely with other 'mini.nvim' modules
|
|
2216
|
+
vim.b[buf_id].minicursorword_disable = true
|
|
2217
|
+
|
|
2218
|
+
-- Trigger dedicated event
|
|
2219
|
+
H.trigger_event('MiniFilesBufferCreate', { buf_id = buf_id })
|
|
2220
|
+
|
|
2221
|
+
return buf_id
|
|
2222
|
+
end
|
|
2223
|
+
|
|
2224
|
+
H.buffer_make_mappings = function(buf_id, mappings)
|
|
2225
|
+
local go_in_with_count = function()
|
|
2226
|
+
for _ = 1, vim.v.count1 do
|
|
2227
|
+
MiniFiles.go_in()
|
|
2228
|
+
end
|
|
2229
|
+
end
|
|
2230
|
+
|
|
2231
|
+
local go_in_plus = function()
|
|
2232
|
+
for _ = 1, vim.v.count1 do
|
|
2233
|
+
MiniFiles.go_in({ close_on_file = true })
|
|
2234
|
+
end
|
|
2235
|
+
end
|
|
2236
|
+
|
|
2237
|
+
local go_out_with_count = function()
|
|
2238
|
+
for _ = 1, vim.v.count1 do
|
|
2239
|
+
MiniFiles.go_out()
|
|
2240
|
+
end
|
|
2241
|
+
end
|
|
2242
|
+
|
|
2243
|
+
local go_out_plus = function()
|
|
2244
|
+
go_out_with_count()
|
|
2245
|
+
MiniFiles.trim_right()
|
|
2246
|
+
end
|
|
2247
|
+
|
|
2248
|
+
local go_in_visual = function()
|
|
2249
|
+
-- React only on linewise mode, as others can be used for editing
|
|
2250
|
+
if vim.fn.mode() ~= 'V' then return mappings.go_in end
|
|
2251
|
+
|
|
2252
|
+
-- Schedule actions because they are not allowed inside expression mapping
|
|
2253
|
+
local line_1, line_2 = vim.fn.line('v'), vim.fn.line('.')
|
|
2254
|
+
local from_line, to_line = math.min(line_1, line_2), math.max(line_1, line_2)
|
|
2255
|
+
vim.schedule(function()
|
|
2256
|
+
local explorer = H.explorer_get()
|
|
2257
|
+
explorer = H.explorer_go_in_range(explorer, buf_id, from_line, to_line)
|
|
2258
|
+
H.explorer_refresh(explorer)
|
|
2259
|
+
end)
|
|
2260
|
+
|
|
2261
|
+
-- Go to Normal mode. '\28\14' is an escaped version of `<C-\><C-n>`.
|
|
2262
|
+
return [[<C-\><C-n>]]
|
|
2263
|
+
end
|
|
2264
|
+
|
|
2265
|
+
local mark_goto = function()
|
|
2266
|
+
local id = H.getcharstr()
|
|
2267
|
+
if id == nil then return end
|
|
2268
|
+
local data = MiniFiles.get_explorer_state().bookmarks[id]
|
|
2269
|
+
if data == nil then return H.notify('No bookmark with id ' .. vim.inspect(id), 'WARN') end
|
|
2270
|
+
|
|
2271
|
+
local path = data.path
|
|
2272
|
+
if vim.is_callable(path) then path = path() end
|
|
2273
|
+
local is_valid_path = type(path) == 'string' and H.fs_get_type(vim.fn.expand(path)) == 'directory'
|
|
2274
|
+
if not is_valid_path then return H.notify('Bookmark path should be a valid path to directory', 'WARN') end
|
|
2275
|
+
|
|
2276
|
+
local state = MiniFiles.get_explorer_state()
|
|
2277
|
+
MiniFiles.set_bookmark("'", state.branch[state.depth_focus], { desc = 'Before latest jump' })
|
|
2278
|
+
MiniFiles.set_branch({ path })
|
|
2279
|
+
end
|
|
2280
|
+
|
|
2281
|
+
local mark_set = function()
|
|
2282
|
+
local id = H.getcharstr()
|
|
2283
|
+
if id == nil then return end
|
|
2284
|
+
local state = MiniFiles.get_explorer_state()
|
|
2285
|
+
MiniFiles.set_bookmark(id, state.branch[state.depth_focus])
|
|
2286
|
+
H.notify('Bookmark ' .. vim.inspect(id) .. ' is set', 'INFO')
|
|
2287
|
+
end
|
|
2288
|
+
|
|
2289
|
+
local buf_map = function(mode, lhs, rhs, desc)
|
|
2290
|
+
-- Use `nowait` to account for non-buffer mappings starting with `lhs`
|
|
2291
|
+
H.map(mode, lhs, rhs, { buffer = buf_id, desc = desc, nowait = true })
|
|
2292
|
+
end
|
|
2293
|
+
|
|
2294
|
+
--stylua: ignore start
|
|
2295
|
+
buf_map('n', mappings.close, MiniFiles.close, 'Close')
|
|
2296
|
+
buf_map('n', mappings.go_in, go_in_with_count, 'Go in entry')
|
|
2297
|
+
buf_map('n', mappings.go_in_plus, go_in_plus, 'Go in entry plus')
|
|
2298
|
+
buf_map('n', mappings.go_out, go_out_with_count, 'Go out of directory')
|
|
2299
|
+
buf_map('n', mappings.go_out_plus, go_out_plus, 'Go out of directory plus')
|
|
2300
|
+
buf_map('n', mappings.mark_goto, mark_goto, 'Go to bookmark')
|
|
2301
|
+
buf_map('n', mappings.mark_set, mark_set, 'Set bookmark')
|
|
2302
|
+
buf_map('n', mappings.reset, MiniFiles.reset, 'Reset')
|
|
2303
|
+
buf_map('n', mappings.reveal_cwd, MiniFiles.reveal_cwd, 'Reveal cwd')
|
|
2304
|
+
buf_map('n', mappings.show_help, MiniFiles.show_help, 'Show Help')
|
|
2305
|
+
buf_map('n', mappings.synchronize, MiniFiles.synchronize, 'Synchronize')
|
|
2306
|
+
buf_map('n', mappings.trim_left, MiniFiles.trim_left, 'Trim branch left')
|
|
2307
|
+
buf_map('n', mappings.trim_right, MiniFiles.trim_right, 'Trim branch right')
|
|
2308
|
+
|
|
2309
|
+
H.map('x', mappings.go_in, go_in_visual, { buffer = buf_id, desc = 'Go in selected entries', expr = true })
|
|
2310
|
+
--stylua: ignore end
|
|
2311
|
+
end
|
|
2312
|
+
|
|
2313
|
+
H.buffer_update = function(buf_id, path, opts, is_preview)
|
|
2314
|
+
if not H.is_valid_buf(buf_id) then return end
|
|
2315
|
+
|
|
2316
|
+
-- Perform entry type specific updates
|
|
2317
|
+
local fs_type = H.fs_get_type(path)
|
|
2318
|
+
if fs_type == 'directory' then H.buffer_update_directory(buf_id, path, opts, is_preview) end
|
|
2319
|
+
if fs_type == 'file' then H.buffer_update_file(buf_id, path, opts, is_preview) end
|
|
2320
|
+
if fs_type == nil then H.set_buflines(buf_id, { '-No-fs-entry-' .. string.rep('-', opts.windows.width_preview) }) end
|
|
2321
|
+
|
|
2322
|
+
-- Trigger dedicated event
|
|
2323
|
+
local data = { buf_id = buf_id, win_id = H.opened_buffers[buf_id].win_id }
|
|
2324
|
+
if fs_type ~= nil then H.trigger_event('MiniFilesBufferUpdate', data) end
|
|
2325
|
+
|
|
2326
|
+
-- Reset buffer as not modified
|
|
2327
|
+
H.opened_buffers[buf_id].n_modified = -1
|
|
2328
|
+
end
|
|
2329
|
+
|
|
2330
|
+
H.buffer_update_directory = function(buf_id, path, opts, is_preview)
|
|
2331
|
+
-- Compute and cache (to use during sync) shown file system entries
|
|
2332
|
+
local children_path_ids = H.opened_buffers[buf_id].children_path_ids
|
|
2333
|
+
local fs_entries = children_path_ids == nil and H.fs_read_dir(path, opts.content)
|
|
2334
|
+
or vim.tbl_map(H.get_fs_entry_from_path_index, children_path_ids)
|
|
2335
|
+
H.opened_buffers[buf_id].children_path_ids = children_path_ids
|
|
2336
|
+
or vim.tbl_map(function(x) return x.path_id end, fs_entries)
|
|
2337
|
+
|
|
2338
|
+
-- Compute format expression resulting into same width path ids
|
|
2339
|
+
local path_width = math.floor(math.log10(#H.path_index)) + 1
|
|
2340
|
+
local line_format = '/%0' .. path_width .. 'd/%s/%s'
|
|
2341
|
+
|
|
2342
|
+
-- Compute lines
|
|
2343
|
+
local lines, icon_hl, name_hl = {}, {}, {}
|
|
2344
|
+
local prefix_fun, hl_fun = opts.content.prefix, opts.content.highlight
|
|
2345
|
+
local n_computed_prefixes = is_preview and vim.o.lines or math.huge
|
|
2346
|
+
for i, entry in ipairs(fs_entries) do
|
|
2347
|
+
local prefix, hl, name
|
|
2348
|
+
-- Compute prefix only in visible preview (for performance).
|
|
2349
|
+
-- NOTE: limiting entries in `fs_read_dir()` is not possible because all
|
|
2350
|
+
-- entries are needed for a proper filter and sort.
|
|
2351
|
+
if i <= n_computed_prefixes then
|
|
2352
|
+
prefix, hl = prefix_fun(entry)
|
|
2353
|
+
end
|
|
2354
|
+
prefix, hl, name = prefix or '', hl or '', H.sanitize_string(entry.name)
|
|
2355
|
+
table.insert(lines, string.format(line_format, H.path_index[entry.path], prefix, name))
|
|
2356
|
+
table.insert(icon_hl, hl)
|
|
2357
|
+
table.insert(name_hl, hl_fun(entry) or 'MiniFilesNormal')
|
|
2358
|
+
end
|
|
2359
|
+
|
|
2360
|
+
-- Set lines
|
|
2361
|
+
H.set_buflines(buf_id, lines)
|
|
2362
|
+
|
|
2363
|
+
-- Add highlighting
|
|
2364
|
+
local ns_id = H.ns_id.highlight
|
|
2365
|
+
vim.api.nvim_buf_clear_namespace(buf_id, ns_id, 0, -1)
|
|
2366
|
+
|
|
2367
|
+
local set_hl = function(line, col, hl_opts) H.set_extmark(buf_id, ns_id, line, col, hl_opts) end
|
|
2368
|
+
|
|
2369
|
+
for i, l in ipairs(lines) do
|
|
2370
|
+
local icon_start, name_start = l:match('^/%d+/().-()/')
|
|
2371
|
+
|
|
2372
|
+
-- NOTE: Use `right_gravity = false` for persistent highlights during edit
|
|
2373
|
+
local icon_opts = { hl_group = icon_hl[i], end_col = name_start - 1, right_gravity = false }
|
|
2374
|
+
set_hl(i - 1, icon_start - 1, icon_opts)
|
|
2375
|
+
|
|
2376
|
+
local name_opts = { hl_group = name_hl[i], end_row = i, end_col = 0, right_gravity = false }
|
|
2377
|
+
set_hl(i - 1, name_start - 1, name_opts)
|
|
2378
|
+
end
|
|
2379
|
+
end
|
|
2380
|
+
|
|
2381
|
+
H.buffer_update_file = function(buf_id, path, opts, _)
|
|
2382
|
+
-- Work only with readable text file. This is not 100% proof, but good enough.
|
|
2383
|
+
-- Source: https://github.com/sharkdp/content_inspector
|
|
2384
|
+
local fd, width_preview = vim.loop.fs_open(path, 'r', 1), opts.windows.width_preview
|
|
2385
|
+
if fd == nil then return H.set_buflines(buf_id, { '-No-access' .. string.rep('-', width_preview) }) end
|
|
2386
|
+
local is_text = vim.loop.fs_read(fd, 1024):find('\0') == nil
|
|
2387
|
+
vim.loop.fs_close(fd)
|
|
2388
|
+
if not is_text then return H.set_buflines(buf_id, { '-Non-text-file' .. string.rep('-', width_preview) }) end
|
|
2389
|
+
|
|
2390
|
+
-- Compute lines. Limit number of read lines to work better on large files.
|
|
2391
|
+
local has_lines, read_res = pcall(vim.fn.readfile, path, '', vim.o.lines)
|
|
2392
|
+
-- - Make sure that lines don't contain '\n' (might happen in binary files).
|
|
2393
|
+
local lines = has_lines and vim.split(table.concat(read_res, '\n'), '\n') or {}
|
|
2394
|
+
|
|
2395
|
+
-- Set lines
|
|
2396
|
+
H.set_buflines(buf_id, lines)
|
|
2397
|
+
|
|
2398
|
+
-- Add highlighting if reasonable (for performance or functionality reasons)
|
|
2399
|
+
if H.buffer_should_highlight(buf_id) then
|
|
2400
|
+
local ft = vim.filetype.match({ buf = buf_id, filename = path })
|
|
2401
|
+
local has_lang, lang = pcall(vim.treesitter.language.get_lang, ft)
|
|
2402
|
+
lang = has_lang and lang or ft
|
|
2403
|
+
-- TODO: Remove `opts.error` after compatibility with Neovim=0.11 is dropped
|
|
2404
|
+
local has_parser, parser = pcall(vim.treesitter.get_parser, buf_id, lang, { error = false })
|
|
2405
|
+
has_parser = has_parser and parser ~= nil
|
|
2406
|
+
if has_parser then has_parser = pcall(vim.treesitter.start, buf_id, lang) end
|
|
2407
|
+
if not has_parser then vim.bo[buf_id].syntax = ft end
|
|
2408
|
+
end
|
|
2409
|
+
end
|
|
2410
|
+
|
|
2411
|
+
H.buffer_delete = function(buf_id)
|
|
2412
|
+
if buf_id == nil then return end
|
|
2413
|
+
pcall(vim.api.nvim_buf_delete, buf_id, { force = true })
|
|
2414
|
+
H.opened_buffers[buf_id] = nil
|
|
2415
|
+
end
|
|
2416
|
+
|
|
2417
|
+
H.buffer_compute_fs_diff = function(buf_id)
|
|
2418
|
+
if not H.is_modified_buffer(buf_id) then return {} end
|
|
2419
|
+
|
|
2420
|
+
local path = H.opened_buffers[buf_id].path
|
|
2421
|
+
local lines = vim.api.nvim_buf_get_lines(buf_id, 0, -1, false)
|
|
2422
|
+
local res, present_path_ids = {}, {}
|
|
2423
|
+
|
|
2424
|
+
-- Process present file system entries
|
|
2425
|
+
for _, l in ipairs(lines) do
|
|
2426
|
+
local path_id = H.match_line_path_id(l)
|
|
2427
|
+
local path_from = H.path_index[path_id]
|
|
2428
|
+
|
|
2429
|
+
-- Use whole line as name if no path id is detected
|
|
2430
|
+
local name_to = path_id ~= nil and l:sub(H.match_line_offset(l)) or l
|
|
2431
|
+
|
|
2432
|
+
-- Preserve trailing '/' to distinguish between creating file or directory
|
|
2433
|
+
local path_to = H.fs_child_path(path, name_to) .. (vim.endswith(name_to, '/') and '/' or '')
|
|
2434
|
+
|
|
2435
|
+
-- Ignore blank lines and already synced entries (even several user-copied)
|
|
2436
|
+
if l:find('^%s*$') == nil and H.sanitize_string(path_from) ~= H.sanitize_string(path_to) then
|
|
2437
|
+
table.insert(res, { from = path_from, to = path_to, dir = path })
|
|
2438
|
+
elseif path_id ~= nil then
|
|
2439
|
+
present_path_ids[path_id] = true
|
|
2440
|
+
end
|
|
2441
|
+
end
|
|
2442
|
+
|
|
2443
|
+
-- Detect missing file system entries
|
|
2444
|
+
local ref_path_ids = H.opened_buffers[buf_id].children_path_ids
|
|
2445
|
+
for _, ref_id in ipairs(ref_path_ids) do
|
|
2446
|
+
if not present_path_ids[ref_id] then table.insert(res, { from = H.path_index[ref_id], to = nil, dir = path }) end
|
|
2447
|
+
end
|
|
2448
|
+
|
|
2449
|
+
return res
|
|
2450
|
+
end
|
|
2451
|
+
|
|
2452
|
+
H.buffer_should_highlight = function(buf_id)
|
|
2453
|
+
-- Highlight if buffer size is not too big, both in total and per line
|
|
2454
|
+
local buf_size = vim.api.nvim_buf_call(buf_id, function() return vim.fn.line2byte(vim.fn.line('$') + 1) end)
|
|
2455
|
+
return buf_size <= 1000000 and buf_size <= 1000 * vim.api.nvim_buf_line_count(buf_id)
|
|
2456
|
+
end
|
|
2457
|
+
|
|
2458
|
+
H.is_opened_buffer = function(buf_id) return H.opened_buffers[buf_id] ~= nil end
|
|
2459
|
+
|
|
2460
|
+
H.is_modified_buffer = function(buf_id)
|
|
2461
|
+
local data = H.opened_buffers[buf_id]
|
|
2462
|
+
return data ~= nil and data.n_modified > 0
|
|
2463
|
+
end
|
|
2464
|
+
|
|
2465
|
+
H.match_line_entry_name = function(l)
|
|
2466
|
+
if l == nil then return nil end
|
|
2467
|
+
local offset = H.match_line_offset(l)
|
|
2468
|
+
-- Go up until first occurrence of path separator allowing to track entries
|
|
2469
|
+
-- like `a/b.lua` when creating nested structure
|
|
2470
|
+
local res = l:sub(offset):gsub('/.*$', '')
|
|
2471
|
+
return res
|
|
2472
|
+
end
|
|
2473
|
+
|
|
2474
|
+
H.match_line_offset = function(l)
|
|
2475
|
+
if l == nil then return nil end
|
|
2476
|
+
return l:match('^/.-/.-/()') or 1
|
|
2477
|
+
end
|
|
2478
|
+
|
|
2479
|
+
H.match_line_path_id = function(l)
|
|
2480
|
+
if l == nil then return nil end
|
|
2481
|
+
|
|
2482
|
+
local id_str = l:match('^/(%d+)')
|
|
2483
|
+
local ok, res = pcall(tonumber, id_str)
|
|
2484
|
+
if not ok then return nil end
|
|
2485
|
+
return res
|
|
2486
|
+
end
|
|
2487
|
+
|
|
2488
|
+
-- Windows --------------------------------------------------------------------
|
|
2489
|
+
H.window_open = function(buf_id, config)
|
|
2490
|
+
-- Add always the same extra data
|
|
2491
|
+
config.anchor = 'NW'
|
|
2492
|
+
config.border = (vim.fn.exists('+winborder') == 0 or vim.o.winborder == '') and 'single' or nil
|
|
2493
|
+
config.focusable = true
|
|
2494
|
+
config.relative = 'editor'
|
|
2495
|
+
config.style = 'minimal'
|
|
2496
|
+
-- - Use 99 to allow built-in completion to be on top
|
|
2497
|
+
config.zindex = 99
|
|
2498
|
+
|
|
2499
|
+
-- Add temporary data which will be updated later
|
|
2500
|
+
config.row = 1
|
|
2501
|
+
|
|
2502
|
+
-- Open without entering
|
|
2503
|
+
local win_id = vim.api.nvim_open_win(buf_id, false, config)
|
|
2504
|
+
|
|
2505
|
+
-- Set permanent window options
|
|
2506
|
+
vim.wo[win_id].concealcursor = 'nvic'
|
|
2507
|
+
vim.wo[win_id].foldenable = false
|
|
2508
|
+
vim.wo[win_id].foldmethod = 'manual'
|
|
2509
|
+
vim.wo[win_id].wrap = false
|
|
2510
|
+
|
|
2511
|
+
-- Conceal path id and prefix separators
|
|
2512
|
+
vim.api.nvim_win_call(win_id, function()
|
|
2513
|
+
vim.fn.matchadd('Conceal', [[^/\d\+/]])
|
|
2514
|
+
vim.fn.matchadd('Conceal', [[^/\d\+/[^/]*\zs/\ze]])
|
|
2515
|
+
end)
|
|
2516
|
+
|
|
2517
|
+
-- Set permanent window highlights
|
|
2518
|
+
H.window_update_highlight(win_id, 'NormalFloat', 'MiniFilesNormal')
|
|
2519
|
+
H.window_update_highlight(win_id, 'FloatTitle', 'MiniFilesTitle')
|
|
2520
|
+
H.window_update_highlight(win_id, 'CursorLine', 'MiniFilesCursorLine')
|
|
2521
|
+
|
|
2522
|
+
return win_id
|
|
2523
|
+
end
|
|
2524
|
+
|
|
2525
|
+
H.window_update = function(win_id, config)
|
|
2526
|
+
-- Preserve some config values
|
|
2527
|
+
local win_config = vim.api.nvim_win_get_config(win_id)
|
|
2528
|
+
config.border, config.title_pos = win_config.border, win_config.title_pos
|
|
2529
|
+
|
|
2530
|
+
-- Compute helper data
|
|
2531
|
+
local has_tabline = vim.o.showtabline == 2 or (vim.o.showtabline == 1 and #vim.api.nvim_list_tabpages() > 1)
|
|
2532
|
+
local max_height = H.window_get_max_height()
|
|
2533
|
+
local max_width = vim.o.columns - (config.border == 'none' and 0 or 2)
|
|
2534
|
+
|
|
2535
|
+
-- Ensure proper fit
|
|
2536
|
+
config.row = has_tabline and 1 or 0
|
|
2537
|
+
config.height = config.height ~= nil and math.min(config.height, max_height) or nil
|
|
2538
|
+
config.width = config.width ~= nil and math.min(config.width, max_width) or nil
|
|
2539
|
+
|
|
2540
|
+
-- Ensure proper title
|
|
2541
|
+
if type(config.title) == 'string' then config.title = H.fit_to_width(config.title, config.width) end
|
|
2542
|
+
|
|
2543
|
+
-- Update config
|
|
2544
|
+
config.relative = 'editor'
|
|
2545
|
+
vim.api.nvim_win_set_config(win_id, config)
|
|
2546
|
+
|
|
2547
|
+
-- Reset basic highlighting (removes possible "focused" highlight group)
|
|
2548
|
+
H.window_update_highlight(win_id, 'FloatTitle', 'MiniFilesTitle')
|
|
2549
|
+
|
|
2550
|
+
-- Make sure proper `conceallevel` (can be not the case with 'noice.nvim')
|
|
2551
|
+
vim.wo[win_id].conceallevel = 3
|
|
2552
|
+
end
|
|
2553
|
+
|
|
2554
|
+
H.window_update_highlight = function(win_id, new_from, new_to)
|
|
2555
|
+
local new_entry = new_from .. ':' .. new_to
|
|
2556
|
+
local replace_pattern = string.format('(%s:[^,]*)', vim.pesc(new_from))
|
|
2557
|
+
local new_winhighlight, n_replace = vim.wo[win_id].winhighlight:gsub(replace_pattern, new_entry)
|
|
2558
|
+
if n_replace == 0 then new_winhighlight = new_winhighlight .. ',' .. new_entry end
|
|
2559
|
+
|
|
2560
|
+
vim.wo[win_id].winhighlight = new_winhighlight
|
|
2561
|
+
end
|
|
2562
|
+
|
|
2563
|
+
H.window_focus = function(win_id)
|
|
2564
|
+
vim.api.nvim_set_current_win(win_id)
|
|
2565
|
+
H.window_update_highlight(win_id, 'FloatTitle', 'MiniFilesTitleFocused')
|
|
2566
|
+
end
|
|
2567
|
+
|
|
2568
|
+
H.window_close = function(win_id)
|
|
2569
|
+
if win_id == nil then return end
|
|
2570
|
+
local has_buffer, buf_id = pcall(vim.api.nvim_win_get_buf, win_id)
|
|
2571
|
+
if has_buffer then H.opened_buffers[buf_id].win_id = nil end
|
|
2572
|
+
pcall(vim.api.nvim_win_close, win_id, true)
|
|
2573
|
+
end
|
|
2574
|
+
|
|
2575
|
+
H.window_set_view = function(win_id, view)
|
|
2576
|
+
-- Set buffer
|
|
2577
|
+
local buf_id, buf_data = view.buf_id, H.opened_buffers[view.buf_id]
|
|
2578
|
+
H.win_set_buf(win_id, buf_id)
|
|
2579
|
+
-- - Update buffer register. No need to update previous buffer data, as it
|
|
2580
|
+
-- should already be invalidated.
|
|
2581
|
+
buf_data.win_id = win_id
|
|
2582
|
+
|
|
2583
|
+
-- Set cursor (if defined), visible only in directories
|
|
2584
|
+
pcall(H.window_set_cursor, win_id, view.cursor)
|
|
2585
|
+
-- NOTE: set 'cursorline[opt]' here because changing buffer might remove it
|
|
2586
|
+
vim.wo[win_id].cursorline = H.fs_get_type(buf_data.path) == 'directory'
|
|
2587
|
+
local culopt = vim.wo[win_id].cursorlineopt
|
|
2588
|
+
if culopt:find('line') == nil then vim.wo[win_id].cursorlineopt = culopt .. ',line' end
|
|
2589
|
+
|
|
2590
|
+
-- Respect global 'list' option, as it is disabled in floating windows
|
|
2591
|
+
-- TODO: Use vim.wo[win_id][0] after compatibility with Neovim=0.9 is dropped
|
|
2592
|
+
local opts_scope = { scope = 'local', win = win_id }
|
|
2593
|
+
vim.api.nvim_set_option_value('list', vim.go.list, opts_scope)
|
|
2594
|
+
vim.api.nvim_set_option_value('listchars', vim.go.listchars, opts_scope)
|
|
2595
|
+
|
|
2596
|
+
-- Update border highlight based on buffer status
|
|
2597
|
+
H.window_update_border_hl(win_id)
|
|
2598
|
+
end
|
|
2599
|
+
|
|
2600
|
+
H.window_set_cursor = function(win_id, cursor)
|
|
2601
|
+
if type(cursor) ~= 'table' then return end
|
|
2602
|
+
|
|
2603
|
+
vim.api.nvim_win_set_cursor(win_id, cursor)
|
|
2604
|
+
|
|
2605
|
+
-- Tweak cursor here and don't rely on `CursorMoved` event to reduce flicker
|
|
2606
|
+
H.window_tweak_cursor(win_id, vim.api.nvim_win_get_buf(win_id))
|
|
2607
|
+
end
|
|
2608
|
+
|
|
2609
|
+
H.window_tweak_cursor = function(win_id, buf_id)
|
|
2610
|
+
local cursor = vim.api.nvim_win_get_cursor(win_id)
|
|
2611
|
+
local l = H.get_bufline(buf_id, cursor[1])
|
|
2612
|
+
|
|
2613
|
+
local cur_offset = H.match_line_offset(l)
|
|
2614
|
+
if cursor[2] < (cur_offset - 1) then
|
|
2615
|
+
cursor[2] = cur_offset - 1
|
|
2616
|
+
vim.api.nvim_win_set_cursor(win_id, cursor)
|
|
2617
|
+
-- Ensure icons are shown (may be not the case after horizontal scroll)
|
|
2618
|
+
vim.cmd('normal! 1000zh')
|
|
2619
|
+
end
|
|
2620
|
+
|
|
2621
|
+
return cursor
|
|
2622
|
+
end
|
|
2623
|
+
|
|
2624
|
+
H.window_update_border_hl = function(win_id)
|
|
2625
|
+
if not H.is_valid_win(win_id) then return end
|
|
2626
|
+
local buf_id = vim.api.nvim_win_get_buf(win_id)
|
|
2627
|
+
|
|
2628
|
+
local border_hl = H.is_modified_buffer(buf_id) and 'MiniFilesBorderModified' or 'MiniFilesBorder'
|
|
2629
|
+
H.window_update_highlight(win_id, 'FloatBorder', border_hl)
|
|
2630
|
+
end
|
|
2631
|
+
|
|
2632
|
+
H.window_get_max_height = function()
|
|
2633
|
+
local has_tabline = vim.o.showtabline == 2 or (vim.o.showtabline == 1 and #vim.api.nvim_list_tabpages() > 1)
|
|
2634
|
+
local has_statusline = vim.o.laststatus > 0
|
|
2635
|
+
-- Remove 2 from maximum height to account for top and bottom borders
|
|
2636
|
+
return vim.o.lines - vim.o.cmdheight - (has_tabline and 1 or 0) - (has_statusline and 1 or 0) - 2
|
|
2637
|
+
end
|
|
2638
|
+
|
|
2639
|
+
-- File system ----------------------------------------------------------------
|
|
2640
|
+
---@class fs_entry
|
|
2641
|
+
---@field name string Base name.
|
|
2642
|
+
---@field fs_type string One of "directory" or "file".
|
|
2643
|
+
---@field path string Full path.
|
|
2644
|
+
---@field path_id number Id of full path.
|
|
2645
|
+
---@private
|
|
2646
|
+
H.fs_read_dir = function(path, content_opts)
|
|
2647
|
+
local fs = vim.loop.fs_scandir(path)
|
|
2648
|
+
local res = {}
|
|
2649
|
+
if not fs then return res end
|
|
2650
|
+
|
|
2651
|
+
-- Read all entries
|
|
2652
|
+
local name, fs_type = vim.loop.fs_scandir_next(fs)
|
|
2653
|
+
while name do
|
|
2654
|
+
if not (fs_type == 'file' or fs_type == 'directory') then fs_type = H.fs_get_type(H.fs_child_path(path, name)) end
|
|
2655
|
+
table.insert(res, { fs_type = fs_type, name = name, path = H.fs_child_path(path, name) })
|
|
2656
|
+
name, fs_type = vim.loop.fs_scandir_next(fs)
|
|
2657
|
+
end
|
|
2658
|
+
|
|
2659
|
+
-- Filter and sort entries
|
|
2660
|
+
res = content_opts.sort(vim.tbl_filter(content_opts.filter, res))
|
|
2661
|
+
|
|
2662
|
+
-- Add new data: absolute file path and its index
|
|
2663
|
+
for _, entry in ipairs(res) do
|
|
2664
|
+
entry.path_id = H.add_path_to_index(entry.path)
|
|
2665
|
+
end
|
|
2666
|
+
|
|
2667
|
+
return res
|
|
2668
|
+
end
|
|
2669
|
+
|
|
2670
|
+
H.add_path_to_index = function(path)
|
|
2671
|
+
local cur_id = H.path_index[path]
|
|
2672
|
+
if cur_id ~= nil then return cur_id end
|
|
2673
|
+
|
|
2674
|
+
local new_id = #H.path_index + 1
|
|
2675
|
+
H.path_index[new_id] = path
|
|
2676
|
+
H.path_index[path] = new_id
|
|
2677
|
+
|
|
2678
|
+
return new_id
|
|
2679
|
+
end
|
|
2680
|
+
|
|
2681
|
+
H.get_fs_entry_from_path_index = function(path_id)
|
|
2682
|
+
local path = H.path_index[path_id]
|
|
2683
|
+
if path == nil then return nil end
|
|
2684
|
+
return { fs_type = H.fs_get_type(path), name = H.fs_get_basename(path), path = path }
|
|
2685
|
+
end
|
|
2686
|
+
|
|
2687
|
+
H.replace_path_in_index = function(from, to)
|
|
2688
|
+
local from_id, to_id = H.path_index[from], H.path_index[to]
|
|
2689
|
+
H.path_index[from_id], H.path_index[to] = to, from_id
|
|
2690
|
+
if to_id then H.path_index[to_id] = nil end
|
|
2691
|
+
-- Remove `from` from index assuming it doesn't exist anymore (no duplicates)
|
|
2692
|
+
H.path_index[from] = nil
|
|
2693
|
+
end
|
|
2694
|
+
|
|
2695
|
+
H.compare_fs_entries = function(a, b)
|
|
2696
|
+
-- Put directory first
|
|
2697
|
+
if a.is_dir and not b.is_dir then return true end
|
|
2698
|
+
if not a.is_dir and b.is_dir then return false end
|
|
2699
|
+
|
|
2700
|
+
-- Otherwise order alphabetically ignoring case
|
|
2701
|
+
return a.lower_name < b.lower_name
|
|
2702
|
+
end
|
|
2703
|
+
|
|
2704
|
+
H.fs_normalize_path = function(path) return (path:gsub('/+', '/'):gsub('(.)/$', '%1')) end
|
|
2705
|
+
if H.is_windows then
|
|
2706
|
+
H.fs_normalize_path = function(path)
|
|
2707
|
+
return (path:gsub('\\', '/'):gsub('([^/:])/+', '%1/'):gsub('([^:])/+$', '%1'):gsub('^(%a):/+([^/])', '%1://%2'))
|
|
2708
|
+
end
|
|
2709
|
+
end
|
|
2710
|
+
|
|
2711
|
+
H.fs_is_imaginary_path = function(path) return path:sub(-1) == '\000' end
|
|
2712
|
+
|
|
2713
|
+
H.fs_is_present_path = function(path) return vim.loop.fs_stat(path) ~= nil and not H.fs_is_imaginary_path(path) end
|
|
2714
|
+
|
|
2715
|
+
H.fs_child_path = function(dir, name) return H.fs_normalize_path(string.format('%s/%s', dir, name)) end
|
|
2716
|
+
|
|
2717
|
+
H.fs_full_path = function(path) return H.fs_normalize_path(vim.fn.fnamemodify(path, ':p')) end
|
|
2718
|
+
|
|
2719
|
+
H.fs_shorten_path = function(path)
|
|
2720
|
+
-- Replace home directory with '~'
|
|
2721
|
+
path = H.fs_normalize_path(path)
|
|
2722
|
+
local home_dir = H.fs_normalize_path(vim.loop.os_homedir() or '~')
|
|
2723
|
+
return (path:gsub('^' .. vim.pesc(home_dir), '~'))
|
|
2724
|
+
end
|
|
2725
|
+
|
|
2726
|
+
H.fs_relcwd_path = function(path) return vim.fn.fnamemodify(path, ':.') end
|
|
2727
|
+
if H.is_windows then
|
|
2728
|
+
-- Make manually because `C://dir/file` paths conflict with `C:/dir` cwd path
|
|
2729
|
+
H.fs_relcwd_path = function(path)
|
|
2730
|
+
local cwd = H.fs_normalize_path(vim.fn.getcwd())
|
|
2731
|
+
return (H.fs_normalize_path(path):gsub('^' .. vim.pesc(cwd) .. '/', ''))
|
|
2732
|
+
end
|
|
2733
|
+
end
|
|
2734
|
+
|
|
2735
|
+
H.fs_get_basename = function(path) return H.fs_normalize_path(path):match('[^/]+$') end
|
|
2736
|
+
|
|
2737
|
+
H.fs_get_parent = function(path)
|
|
2738
|
+
path = H.fs_full_path(path)
|
|
2739
|
+
|
|
2740
|
+
-- Deal with top root paths
|
|
2741
|
+
local is_top = H.fs_is_windows_top(path) or path == '/'
|
|
2742
|
+
if is_top then return nil end
|
|
2743
|
+
|
|
2744
|
+
-- Compute parent
|
|
2745
|
+
local res = H.fs_normalize_path(path:match('^.*/'))
|
|
2746
|
+
-- - Deal with Windows top directory separately
|
|
2747
|
+
local suffix = H.fs_is_windows_top(res) and '/' or ''
|
|
2748
|
+
return res .. suffix
|
|
2749
|
+
end
|
|
2750
|
+
|
|
2751
|
+
H.fs_is_windows_top = function(path) return H.is_windows and path:find('^%w:[\\/]?$') ~= nil end
|
|
2752
|
+
|
|
2753
|
+
H.fs_get_type = function(path)
|
|
2754
|
+
if not (not H.fs_is_imaginary_path(path) and H.fs_is_present_path(path)) then return nil end
|
|
2755
|
+
return vim.fn.isdirectory(path) == 1 and 'directory' or 'file'
|
|
2756
|
+
end
|
|
2757
|
+
|
|
2758
|
+
-- File system actions --------------------------------------------------------
|
|
2759
|
+
H.fs_actions_to_lines = function(fs_actions)
|
|
2760
|
+
-- Gather actions per source directory
|
|
2761
|
+
local short = H.fs_shorten_path
|
|
2762
|
+
local dir
|
|
2763
|
+
local rel = function(p) return vim.startswith(p, dir .. '/') and p:sub(#dir + 2):gsub('/$', '') or short(p) end
|
|
2764
|
+
|
|
2765
|
+
local actions_per_dir = {}
|
|
2766
|
+
--stylua: ignore
|
|
2767
|
+
for _, diff in ipairs(fs_actions) do
|
|
2768
|
+
-- Set grouping directory to also be used to compute relative paths
|
|
2769
|
+
dir = diff.action == 'create' and diff.dir or H.fs_get_parent(diff.from)
|
|
2770
|
+
|
|
2771
|
+
-- Compute line depending on action
|
|
2772
|
+
local action, l = diff.action, nil
|
|
2773
|
+
local to_type = (diff.to or ''):sub(-1) == '/' and 'directory' or 'file'
|
|
2774
|
+
local del_type = diff.to == nil and 'permanently' or 'to trash'
|
|
2775
|
+
if action == 'create' then l = string.format("CREATE │ %s (%s)", rel(diff.to), to_type) end
|
|
2776
|
+
if action == 'delete' then l = string.format("DELETE │ %s (%s)", rel(diff.from), del_type) end
|
|
2777
|
+
if action == 'copy' then l = string.format("COPY │ %s => %s", rel(diff.from), rel(diff.to)) end
|
|
2778
|
+
if action == 'move' then l = string.format("MOVE │ %s => %s", rel(diff.from), rel(diff.to)) end
|
|
2779
|
+
if action == 'rename' then l = string.format("RENAME │ %s => %s", rel(diff.from), rel(diff.to)) end
|
|
2780
|
+
|
|
2781
|
+
-- Add to per directory lines
|
|
2782
|
+
local dir_actions = actions_per_dir[dir] or {}
|
|
2783
|
+
table.insert(dir_actions, ' ' .. H.sanitize_string(l))
|
|
2784
|
+
actions_per_dir[dir] = dir_actions
|
|
2785
|
+
end
|
|
2786
|
+
|
|
2787
|
+
-- Convert to final lines
|
|
2788
|
+
local res = { 'CONFIRM FILE SYSTEM ACTIONS', '' }
|
|
2789
|
+
for path, dir_actions in pairs(actions_per_dir) do
|
|
2790
|
+
table.insert(res, short(path))
|
|
2791
|
+
vim.list_extend(res, dir_actions)
|
|
2792
|
+
table.insert(res, '')
|
|
2793
|
+
end
|
|
2794
|
+
|
|
2795
|
+
return res
|
|
2796
|
+
end
|
|
2797
|
+
|
|
2798
|
+
H.fs_actions_apply = function(fs_actions, lsp_timeout)
|
|
2799
|
+
H.lsp_fs_hook('willCreate', fs_actions, lsp_timeout)
|
|
2800
|
+
H.lsp_fs_hook('willDelete', fs_actions, lsp_timeout)
|
|
2801
|
+
H.lsp_fs_hook('willRename', fs_actions, lsp_timeout)
|
|
2802
|
+
|
|
2803
|
+
local ok_actions = {}
|
|
2804
|
+
for i = 1, #fs_actions do
|
|
2805
|
+
local diff, action = fs_actions[i], fs_actions[i].action
|
|
2806
|
+
local ok, success = pcall(H.fs_do[action], diff.from, diff.to)
|
|
2807
|
+
if ok and success then
|
|
2808
|
+
table.insert(ok_actions, diff)
|
|
2809
|
+
|
|
2810
|
+
-- Trigger event
|
|
2811
|
+
local to = action == 'create' and diff.to:gsub('/$', '') or diff.to
|
|
2812
|
+
local data = { action = action, from = diff.from, to = to }
|
|
2813
|
+
local action_titlecase = action:sub(1, 1):upper() .. action:sub(2)
|
|
2814
|
+
H.trigger_event('MiniFilesAction' .. action_titlecase, data)
|
|
2815
|
+
|
|
2816
|
+
-- Modify later actions to account for file movement
|
|
2817
|
+
local has_moved = to ~= nil and not (action == 'copy' or action == 'create')
|
|
2818
|
+
if has_moved then H.adjust_after_move(diff.from, to, fs_actions, i + 1) end
|
|
2819
|
+
end
|
|
2820
|
+
end
|
|
2821
|
+
|
|
2822
|
+
H.lsp_fs_hook('didCreate', ok_actions, lsp_timeout)
|
|
2823
|
+
H.lsp_fs_hook('didDelete', ok_actions, lsp_timeout)
|
|
2824
|
+
H.lsp_fs_hook('didRename', ok_actions, lsp_timeout)
|
|
2825
|
+
end
|
|
2826
|
+
|
|
2827
|
+
H.lsp_fs_hook = function(method, diffs, lsp_timeout)
|
|
2828
|
+
if lsp_timeout == 0 then return end
|
|
2829
|
+
|
|
2830
|
+
local full_method = 'workspace/' .. method .. 'Files'
|
|
2831
|
+
local clients = vim.lsp.get_clients({ method = full_method })
|
|
2832
|
+
if #clients == 0 then return end
|
|
2833
|
+
|
|
2834
|
+
-- Transform 'mini.files' diffs into LSP file actions for the input method
|
|
2835
|
+
-- https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#createFilesParams
|
|
2836
|
+
-- https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#deleteFilesParams
|
|
2837
|
+
-- https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#renameFilesParams
|
|
2838
|
+
local files, to_uri = {}, vim.uri_from_fname
|
|
2839
|
+
local needs_check = method == 'willCreate' or method == 'willRename'
|
|
2840
|
+
local is_create, is_delete, is_rename =
|
|
2841
|
+
vim.endswith(method, 'Create'), vim.endswith(method, 'Delete'), vim.endswith(method, 'Rename')
|
|
2842
|
+
for _, d in ipairs(diffs) do
|
|
2843
|
+
local file = {}
|
|
2844
|
+
if is_create and d.action == 'create' then file = { uri = to_uri(d.to) } end
|
|
2845
|
+
if is_delete and d.action == 'delete' then file = { uri = to_uri(d.from) } end
|
|
2846
|
+
if is_rename and d.action == 'rename' then file = { oldUri = to_uri(d.from), newUri = to_uri(d.to) } end
|
|
2847
|
+
|
|
2848
|
+
-- Precompute LSP file type for filters (path can be not yet on disk)
|
|
2849
|
+
file.fs_type = (d.from or d.to):find('/$') ~= nil and 'folder' or 'file'
|
|
2850
|
+
|
|
2851
|
+
-- Some actions might not succeed, so make best effort check before that
|
|
2852
|
+
local pass_check = not needs_check or (needs_check and d.to ~= nil and not H.fs_is_present_path(d.to))
|
|
2853
|
+
if (file.uri or file.oldUri) ~= nil and pass_check then table.insert(files, file) end
|
|
2854
|
+
end
|
|
2855
|
+
|
|
2856
|
+
-- Execute LSP action for every currently existing client
|
|
2857
|
+
if #files == 0 then return end
|
|
2858
|
+
for _, client in ipairs(clients) do
|
|
2859
|
+
H.lsp_fs_hook_client(client, full_method, files, lsp_timeout)
|
|
2860
|
+
end
|
|
2861
|
+
end
|
|
2862
|
+
-- TODO: Remove after compatibility with Neovim=0.10 is dropped
|
|
2863
|
+
if vim.fn.has('nvim-0.11') == 0 then H.lsp_fs_hook = function() end end
|
|
2864
|
+
|
|
2865
|
+
H.lsp_fs_hook_client = function(client, full_method, lsp_files, timeout)
|
|
2866
|
+
-- Compute parameters of the LSP action by filtering all input file actions
|
|
2867
|
+
-- https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#fileOperationFilter
|
|
2868
|
+
local is_scheme = function(uri, scheme) return scheme == nil or vim.startswith(uri, scheme .. ':') end
|
|
2869
|
+
local is_fs_type = function(lsp_file, ref_fs_type) return ref_fs_type == nil or ref_fs_type == lsp_file.fs_type end
|
|
2870
|
+
local make_filter = function(scheme, ref_fs_type, glob, ignore_case)
|
|
2871
|
+
local adjust_case = ignore_case and vim.fn.tolower or function(x) return x end
|
|
2872
|
+
-- On Windows `uri_to_fname` forces `\`, but forcing / seems more robust
|
|
2873
|
+
local to_fname = H.is_windows and function(x) return (vim.uri_to_fname(x):gsub('\\', '/')) end or vim.uri_to_fname
|
|
2874
|
+
local glob_lpeg = vim.glob.to_lpeg(adjust_case(glob) or '**')
|
|
2875
|
+
return function(lsp_file)
|
|
2876
|
+
local uri = lsp_file.uri or lsp_file.oldUri
|
|
2877
|
+
local fname = adjust_case(to_fname(uri))
|
|
2878
|
+
return is_scheme(uri, scheme) and is_fs_type(lsp_file, ref_fs_type) and glob_lpeg:match(fname) ~= nil
|
|
2879
|
+
end
|
|
2880
|
+
end
|
|
2881
|
+
|
|
2882
|
+
local method = full_method:match('^workspace/(.+)Files$')
|
|
2883
|
+
local filter_configs = client.server_capabilities.workspace.fileOperations[method].filters
|
|
2884
|
+
local filters = {}
|
|
2885
|
+
for _, fc in ipairs(filter_configs) do
|
|
2886
|
+
local glob, matches, scheme = fc.pattern.glob, fc.pattern.matches, fc.scheme
|
|
2887
|
+
local ignore_case = type(fc.pattern.options) == 'table' and fc.pattern.options.ignoreCase
|
|
2888
|
+
|
|
2889
|
+
table.insert(filters, make_filter(scheme, matches, glob, ignore_case))
|
|
2890
|
+
end
|
|
2891
|
+
|
|
2892
|
+
local params = { files = {} }
|
|
2893
|
+
for _, f in ipairs(lsp_files) do
|
|
2894
|
+
-- https://github.com/microsoft/language-server-protocol/issues/2203
|
|
2895
|
+
-- Empty filters should match nothing, but this a useful default for misbehaving servers
|
|
2896
|
+
local ok = #filters == 0
|
|
2897
|
+
for _, filt in ipairs(filters) do
|
|
2898
|
+
-- It is not clear from LSP spec if it is `and` or `or`, but it needs to
|
|
2899
|
+
-- handle filters matching both `file` and `folder`. So `or`.
|
|
2900
|
+
ok = ok or filt(f)
|
|
2901
|
+
end
|
|
2902
|
+
-- Remove manually added field to comply with LSP spec
|
|
2903
|
+
f.fs_type = nil
|
|
2904
|
+
if ok then table.insert(params.files, f) end
|
|
2905
|
+
end
|
|
2906
|
+
|
|
2907
|
+
-- Perform an action
|
|
2908
|
+
if vim.startswith(method, 'did') then return client:notify(full_method, params) end
|
|
2909
|
+
-- - Use sync to comply with LSP spec (apply edit before file operations)
|
|
2910
|
+
local response, err = client:request_sync(full_method, params, timeout)
|
|
2911
|
+
if (response or {}).result ~= nil then vim.lsp.util.apply_workspace_edit(response.result, client.offset_encoding) end
|
|
2912
|
+
end
|
|
2913
|
+
|
|
2914
|
+
H.fs_do = {}
|
|
2915
|
+
|
|
2916
|
+
H.fs_do.create = function(_, path)
|
|
2917
|
+
-- Don't override existing path
|
|
2918
|
+
if H.fs_is_present_path(path) then return H.warn_existing_path(path, 'create') end
|
|
2919
|
+
|
|
2920
|
+
-- Create parent directory allowing nested names
|
|
2921
|
+
vim.fn.mkdir(H.fs_get_parent(path), 'p')
|
|
2922
|
+
|
|
2923
|
+
-- Create
|
|
2924
|
+
local fs_type = path:sub(-1) == '/' and 'directory' or 'file'
|
|
2925
|
+
if fs_type == 'directory' then return vim.fn.mkdir(path) == 1 end
|
|
2926
|
+
return vim.fn.writefile({}, path) == 0
|
|
2927
|
+
end
|
|
2928
|
+
|
|
2929
|
+
H.fs_do.copy = function(from, to)
|
|
2930
|
+
-- Don't override existing path
|
|
2931
|
+
if H.fs_is_present_path(to) then return H.warn_existing_path(from, 'copy') end
|
|
2932
|
+
|
|
2933
|
+
local from_type = H.fs_get_type(from)
|
|
2934
|
+
if from_type == nil then return false end
|
|
2935
|
+
|
|
2936
|
+
-- Allow copying inside non-existing directory
|
|
2937
|
+
vim.fn.mkdir(H.fs_get_parent(to), 'p')
|
|
2938
|
+
|
|
2939
|
+
-- Copy file directly
|
|
2940
|
+
if from_type == 'file' then return vim.loop.fs_copyfile(from, to) end
|
|
2941
|
+
|
|
2942
|
+
-- Recursively copy a directory
|
|
2943
|
+
local fs_entries = H.fs_read_dir(from, { filter = function() return true end, sort = function(x) return x end })
|
|
2944
|
+
-- NOTE: Create directory *after* reading entries to allow copy inside itself
|
|
2945
|
+
vim.fn.mkdir(to)
|
|
2946
|
+
|
|
2947
|
+
local success = true
|
|
2948
|
+
for _, entry in ipairs(fs_entries) do
|
|
2949
|
+
success = success and H.fs_do.copy(entry.path, H.fs_child_path(to, entry.name))
|
|
2950
|
+
end
|
|
2951
|
+
|
|
2952
|
+
return success
|
|
2953
|
+
end
|
|
2954
|
+
|
|
2955
|
+
H.fs_do.delete = function(from, to)
|
|
2956
|
+
-- Act based on whether delete is permanent or not
|
|
2957
|
+
if to == nil then return vim.fn.delete(from, 'rf') == 0 end
|
|
2958
|
+
pcall(vim.fn.delete, to, 'rf')
|
|
2959
|
+
-- Move to trash but skip renaming loaded buffer (same as with permanent
|
|
2960
|
+
-- delete). This also skips triggering extra autocommands that can have
|
|
2961
|
+
-- unwanted side effects (like loading LSP in the new "trash" project).
|
|
2962
|
+
return H.fs_do.move(from, to, true)
|
|
2963
|
+
end
|
|
2964
|
+
|
|
2965
|
+
H.fs_do.move = function(from, to, skip_buf_rename)
|
|
2966
|
+
-- Don't override existing path
|
|
2967
|
+
if H.fs_is_present_path(to) then return H.warn_existing_path(from, 'move or rename') end
|
|
2968
|
+
|
|
2969
|
+
-- Move while allowing to create directory
|
|
2970
|
+
vim.fn.mkdir(H.fs_get_parent(to), 'p')
|
|
2971
|
+
local success, _, err_code = vim.loop.fs_rename(from, to)
|
|
2972
|
+
|
|
2973
|
+
if err_code == 'EXDEV' then
|
|
2974
|
+
-- Handle cross-device move separately as `loop.fs_rename` does not work
|
|
2975
|
+
success = H.fs_do.copy(from, to)
|
|
2976
|
+
if success then success = pcall(vim.fn.delete, from, 'rf') end
|
|
2977
|
+
if not success then pcall(vim.fn.delete, to, 'rf') end
|
|
2978
|
+
end
|
|
2979
|
+
|
|
2980
|
+
if not success then return success end
|
|
2981
|
+
|
|
2982
|
+
-- Update path index to allow consecutive moves after undo (which also
|
|
2983
|
+
-- restores previous concealed path index)
|
|
2984
|
+
H.replace_path_in_index(from, to)
|
|
2985
|
+
|
|
2986
|
+
-- Rename in loaded buffers
|
|
2987
|
+
if not skip_buf_rename then
|
|
2988
|
+
for _, buf_id in ipairs(vim.api.nvim_list_bufs()) do
|
|
2989
|
+
H.rename_loaded_buffer(buf_id, from, to)
|
|
2990
|
+
end
|
|
2991
|
+
end
|
|
2992
|
+
|
|
2993
|
+
return success
|
|
2994
|
+
end
|
|
2995
|
+
|
|
2996
|
+
H.fs_do.rename = H.fs_do.move
|
|
2997
|
+
|
|
2998
|
+
H.rename_loaded_buffer = function(buf_id, from, to)
|
|
2999
|
+
if not (vim.api.nvim_buf_is_loaded(buf_id) and vim.bo[buf_id].buftype == '') then return end
|
|
3000
|
+
-- Make sure buffer name is normalized (same as `from` and `to`)
|
|
3001
|
+
local cur_name = H.fs_normalize_path(vim.api.nvim_buf_get_name(buf_id))
|
|
3002
|
+
|
|
3003
|
+
-- Use `gsub('^' ...)` to also take into account directory renames
|
|
3004
|
+
local new_name = cur_name:gsub('^' .. vim.pesc(from), to)
|
|
3005
|
+
if cur_name == new_name then return end
|
|
3006
|
+
|
|
3007
|
+
-- Rename buffer using relative form (for nicer `:buffers` output)
|
|
3008
|
+
vim.api.nvim_buf_set_name(buf_id, H.fs_relcwd_path(new_name))
|
|
3009
|
+
|
|
3010
|
+
-- Force write to avoid the 'overwrite existing file' error message on write
|
|
3011
|
+
-- for normal files
|
|
3012
|
+
vim.api.nvim_buf_call(buf_id, function() vim.cmd('silent! write! | edit') end)
|
|
3013
|
+
end
|
|
3014
|
+
|
|
3015
|
+
H.warn_existing_path = function(path, action)
|
|
3016
|
+
H.notify(string.format('Can not %s %s. Target path already exists.', action, path), 'WARN')
|
|
3017
|
+
return false
|
|
3018
|
+
end
|
|
3019
|
+
|
|
3020
|
+
H.adjust_after_move = function(from, to, fs_actions, start_ind)
|
|
3021
|
+
local from_dir_pattern, to_dir = '^' .. vim.pesc(from .. '/'), to .. '/'
|
|
3022
|
+
for i = start_ind, #fs_actions do
|
|
3023
|
+
local diff = fs_actions[i]
|
|
3024
|
+
-- Adjust completely to use entry at new location
|
|
3025
|
+
if diff.from ~= nil then diff.from = diff.from == from and to or diff.from:gsub(from_dir_pattern, to_dir) end
|
|
3026
|
+
-- Adjust only parent directory to correctly compute target
|
|
3027
|
+
if diff.to ~= nil then diff.to = diff.to:gsub(from_dir_pattern, to_dir) end
|
|
3028
|
+
end
|
|
3029
|
+
end
|
|
3030
|
+
|
|
3031
|
+
-- Validators -----------------------------------------------------------------
|
|
3032
|
+
H.validate_opened_buffer = function(x)
|
|
3033
|
+
if x == nil or x == 0 then x = vim.api.nvim_get_current_buf() end
|
|
3034
|
+
if not H.is_opened_buffer(x) then H.error('`buf_id` should be an identifier of an opened directory buffer.') end
|
|
3035
|
+
return x
|
|
3036
|
+
end
|
|
3037
|
+
|
|
3038
|
+
H.validate_line = function(buf_id, x)
|
|
3039
|
+
x = x or vim.fn.line('.')
|
|
3040
|
+
if not (type(x) == 'number' and 1 <= x and x <= vim.api.nvim_buf_line_count(buf_id)) then
|
|
3041
|
+
H.error('`line` should be a valid line number in buffer ' .. buf_id .. '.')
|
|
3042
|
+
end
|
|
3043
|
+
return x
|
|
3044
|
+
end
|
|
3045
|
+
|
|
3046
|
+
H.validate_branch = function(x)
|
|
3047
|
+
if not (H.islist(x) and x[1] ~= nil) then H.error('`branch` should be array with at least one element') end
|
|
3048
|
+
local res = {}
|
|
3049
|
+
for i, p in ipairs(x) do
|
|
3050
|
+
if type(p) ~= 'string' then H.error('`branch` contains not string: ' .. vim.inspect(p)) end
|
|
3051
|
+
p = H.fs_full_path(p)
|
|
3052
|
+
if not H.fs_is_present_path(p) then H.error('`branch` contains not present path: ' .. vim.inspect(p)) end
|
|
3053
|
+
res[i] = p
|
|
3054
|
+
end
|
|
3055
|
+
for i = 2, #res do
|
|
3056
|
+
local parent, child = res[i - 1], res[i]
|
|
3057
|
+
if H.fs_child_path(parent, child:match('[^/]+$')) ~= child then
|
|
3058
|
+
H.error('`branch` contains not a parent-child pair: ' .. vim.inspect(parent) .. ' and ' .. vim.inspect(child))
|
|
3059
|
+
end
|
|
3060
|
+
end
|
|
3061
|
+
if #res == 1 and H.fs_get_type(res[1]) == 'file' then H.error('`branch` should contain at least one directory') end
|
|
3062
|
+
return res
|
|
3063
|
+
end
|
|
3064
|
+
|
|
3065
|
+
-- Utilities ------------------------------------------------------------------
|
|
3066
|
+
H.error = function(msg) error('(mini.files) ' .. msg, 0) end
|
|
3067
|
+
|
|
3068
|
+
H.check_type = function(name, val, ref, allow_nil)
|
|
3069
|
+
if type(val) == ref or (ref == 'callable' and vim.is_callable(val)) or (allow_nil and val == nil) then return end
|
|
3070
|
+
H.error(string.format('`%s` should be %s, not %s', name, ref, type(val)))
|
|
3071
|
+
end
|
|
3072
|
+
|
|
3073
|
+
H.set_buf_name = function(buf_id, name) vim.api.nvim_buf_set_name(buf_id, 'minifiles://' .. buf_id .. '/' .. name) end
|
|
3074
|
+
|
|
3075
|
+
H.notify = function(msg, level_name) vim.notify('(mini.files) ' .. msg, vim.log.levels[level_name]) end
|
|
3076
|
+
|
|
3077
|
+
H.map = function(mode, lhs, rhs, opts)
|
|
3078
|
+
if lhs == '' then return end
|
|
3079
|
+
opts = vim.tbl_deep_extend('force', { silent = true }, opts or {})
|
|
3080
|
+
vim.keymap.set(mode, lhs, rhs, opts)
|
|
3081
|
+
end
|
|
3082
|
+
|
|
3083
|
+
H.edit = function(path, win_id)
|
|
3084
|
+
if type(path) ~= 'string' then return end
|
|
3085
|
+
local b = vim.api.nvim_win_get_buf(win_id or 0)
|
|
3086
|
+
local try_mimic_buf_reuse = (vim.fn.bufname(b) == '' and vim.bo[b].buftype ~= 'quickfix' and not vim.bo[b].modified)
|
|
3087
|
+
and (#vim.fn.win_findbuf(b) == 1 and vim.deep_equal(vim.fn.getbufline(b, 1, '$'), { '' }))
|
|
3088
|
+
local buf_id = vim.fn.bufadd(H.fs_relcwd_path(path))
|
|
3089
|
+
-- Showing in window also loads. Use `pcall` to not error with swap messages.
|
|
3090
|
+
pcall(vim.api.nvim_win_set_buf, win_id or 0, buf_id)
|
|
3091
|
+
vim.bo[buf_id].buflisted = true
|
|
3092
|
+
if try_mimic_buf_reuse then pcall(vim.api.nvim_buf_delete, b, { unload = false }) end
|
|
3093
|
+
return buf_id
|
|
3094
|
+
end
|
|
3095
|
+
|
|
3096
|
+
H.trigger_event = function(event_name, data) vim.api.nvim_exec_autocmds('User', { pattern = event_name, data = data }) end
|
|
3097
|
+
|
|
3098
|
+
H.is_valid_buf = function(buf_id) return type(buf_id) == 'number' and vim.api.nvim_buf_is_valid(buf_id) end
|
|
3099
|
+
|
|
3100
|
+
H.is_valid_win = function(win_id) return type(win_id) == 'number' and vim.api.nvim_win_is_valid(win_id) end
|
|
3101
|
+
|
|
3102
|
+
H.fit_to_width = function(text, width)
|
|
3103
|
+
local t_width = vim.fn.strchars(text)
|
|
3104
|
+
return t_width <= width and text or ('…' .. vim.fn.strcharpart(text, t_width - width + 1, width - 1))
|
|
3105
|
+
end
|
|
3106
|
+
|
|
3107
|
+
H.get_bufline = function(buf_id, line) return vim.api.nvim_buf_get_lines(buf_id, line - 1, line, false)[1] end
|
|
3108
|
+
|
|
3109
|
+
H.set_buflines = function(buf_id, lines)
|
|
3110
|
+
local cmd =
|
|
3111
|
+
string.format('lockmarks lua vim.api.nvim_buf_set_lines(%d, 0, -1, false, %s)', buf_id, vim.inspect(lines))
|
|
3112
|
+
vim.cmd(cmd)
|
|
3113
|
+
end
|
|
3114
|
+
|
|
3115
|
+
H.set_extmark = function(...) pcall(vim.api.nvim_buf_set_extmark, ...) end
|
|
3116
|
+
|
|
3117
|
+
H.win_set_buf = function(win_id, buf_id)
|
|
3118
|
+
vim.wo[win_id].winfixbuf = false
|
|
3119
|
+
-- Prevent `BufEnter,BufLeave` that come from `nvim_win_set_buf` and conflict
|
|
3120
|
+
-- with other modules (like 'mini.jump'). Use 'mini.files' events if needed.
|
|
3121
|
+
local cmd = string.format('noautocmd call nvim_win_set_buf(%d, %d)', win_id, buf_id)
|
|
3122
|
+
vim.cmd(cmd)
|
|
3123
|
+
vim.wo[win_id].winfixbuf = true
|
|
3124
|
+
end
|
|
3125
|
+
if vim.fn.has('nvim-0.10') == 0 then H.win_set_buf = vim.api.nvim_win_set_buf end
|
|
3126
|
+
|
|
3127
|
+
H.get_first_valid_normal_window = function()
|
|
3128
|
+
for _, win_id in ipairs(vim.api.nvim_tabpage_list_wins(0)) do
|
|
3129
|
+
if vim.api.nvim_win_get_config(win_id).relative == '' then return win_id end
|
|
3130
|
+
end
|
|
3131
|
+
end
|
|
3132
|
+
|
|
3133
|
+
H.getcharstr = function()
|
|
3134
|
+
local ok, char = pcall(vim.fn.getcharstr)
|
|
3135
|
+
-- Terminate if couldn't get input (like with <C-c>) or on `<Esc>`
|
|
3136
|
+
if not ok or char == '' or char == '\3' or char == '\27' then return nil end
|
|
3137
|
+
return char
|
|
3138
|
+
end
|
|
3139
|
+
|
|
3140
|
+
H.sanitize_string = function(x) return ((x or ''):gsub('\n', '<NL>'):gsub('%z', '')) end
|
|
3141
|
+
|
|
3142
|
+
-- TODO: Remove after compatibility with Neovim=0.9 is dropped
|
|
3143
|
+
H.islist = vim.fn.has('nvim-0.10') == 1 and vim.islist or vim.tbl_islist
|
|
3144
|
+
|
|
3145
|
+
return MiniFiles
|