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,1737 @@
|
|
|
1
|
+
--- *mini.git* Git integration
|
|
2
|
+
---
|
|
3
|
+
--- MIT License Copyright (c) 2024 Evgeni Chasnovski
|
|
4
|
+
|
|
5
|
+
--- Features:
|
|
6
|
+
---
|
|
7
|
+
--- - Automated tracking of Git related data: root path, status, HEAD, etc.
|
|
8
|
+
--- Exposes buffer-local variables for convenient use in statusline.
|
|
9
|
+
--- See |MiniGit.enable()| and |MiniGit.get_buf_data()| for more information.
|
|
10
|
+
---
|
|
11
|
+
--- - |:Git| command for executing any `git` call inside file's repository root with
|
|
12
|
+
--- deeper current instance integration (show output as notification/buffer,
|
|
13
|
+
--- use to edit commit messages, etc.).
|
|
14
|
+
---
|
|
15
|
+
--- - Helper functions to inspect Git history:
|
|
16
|
+
--- - |MiniGit.show_range_history()| shows how certain line range evolved.
|
|
17
|
+
--- - |MiniGit.show_diff_source()| shows file state as it was at diff entry.
|
|
18
|
+
--- - |MiniGit.show_at_cursor()| shows Git related data depending on context.
|
|
19
|
+
---
|
|
20
|
+
--- What it doesn't do:
|
|
21
|
+
---
|
|
22
|
+
--- - Replace fully featured Git client. Rule of thumb: if feature does not rely
|
|
23
|
+
--- on a state of current Neovim (opened buffers, etc.), it is out of scope.
|
|
24
|
+
--- For more functionality, use either |mini.diff| or fully featured Git client.
|
|
25
|
+
---
|
|
26
|
+
--- Sources with more details:
|
|
27
|
+
--- - |:Git|
|
|
28
|
+
--- - |MiniGit-examples|
|
|
29
|
+
--- - |MiniGit.enable()|
|
|
30
|
+
--- - |MiniGit.get_buf_data()|
|
|
31
|
+
---
|
|
32
|
+
--- # Setup ~
|
|
33
|
+
---
|
|
34
|
+
--- This module needs a setup with `require('mini.git').setup({})` (replace `{}` with
|
|
35
|
+
--- your `config` table). It will create global Lua table `MiniGit` which you can use
|
|
36
|
+
--- for scripting or manually (with `:lua MiniGit.*`).
|
|
37
|
+
---
|
|
38
|
+
--- See |MiniGit.config| for `config` structure and default values.
|
|
39
|
+
---
|
|
40
|
+
--- # Comparisons ~
|
|
41
|
+
---
|
|
42
|
+
--- - [tpope/vim-fugitive](https://github.com/tpope/vim-fugitive):
|
|
43
|
+
--- - Mostly a dedicated Git client, while this module is not (by design).
|
|
44
|
+
--- - Provides buffer-local Git data only through fixed statusline component,
|
|
45
|
+
--- while this module has richer data in the form of a Lua table.
|
|
46
|
+
--- - Both provide |:Git| command with `vim-fugitive` treating some cases
|
|
47
|
+
--- extra specially (like `:Git blame`, etc.), while this module mostly
|
|
48
|
+
--- treats all cases the same. See |MiniGit-examples| for how they can be
|
|
49
|
+
--- manually customized.
|
|
50
|
+
--- Also this module provides slightly different (usually richer)
|
|
51
|
+
--- completion suggestions.
|
|
52
|
+
---
|
|
53
|
+
--- - [NeogitOrg/neogit](https://github.com/NeogitOrg/neogit):
|
|
54
|
+
--- - Similar to `tpope/vim-fugitive`, but without `:Git` command.
|
|
55
|
+
---
|
|
56
|
+
--- - [lewis6991/gitsigns.nvim](https://github.com/lewis6991/gitsigns.nvim):
|
|
57
|
+
--- - Provides buffer-local Git data with emphasis on granular diff status,
|
|
58
|
+
--- while this module is more oriented towards repository and file level
|
|
59
|
+
--- data (root, HEAD, file status, etc.). Use |mini.diff| for diff tracking.
|
|
60
|
+
---
|
|
61
|
+
--- # Disabling ~
|
|
62
|
+
---
|
|
63
|
+
--- To prevent buffer(s) from being tracked, set `vim.g.minigit_disable` (globally)
|
|
64
|
+
--- or `vim.b.minigit_disable` (for a buffer) to `true`. Considering high number of
|
|
65
|
+
--- different scenarios and customization intentions, writing exact rules for
|
|
66
|
+
--- disabling module's functionality is left to user.
|
|
67
|
+
--- See |mini.nvim-disabling-recipes| for common recipes.
|
|
68
|
+
---@tag MiniGit
|
|
69
|
+
|
|
70
|
+
--- # Statusline component ~
|
|
71
|
+
---
|
|
72
|
+
--- Tracked buffer data can be used in statusline via `vim.b.minigit_summary_string`
|
|
73
|
+
--- buffer-local variable. It is expected to be used as is. To show another info,
|
|
74
|
+
--- tweak buffer-local variable directly inside `MiniGitUpdated` `User` event: >lua
|
|
75
|
+
---
|
|
76
|
+
--- -- Use only HEAD name as summary string
|
|
77
|
+
--- local format_summary = function(data)
|
|
78
|
+
--- -- Utilize buffer-local table summary
|
|
79
|
+
--- local summary = vim.b[data.buf].minigit_summary
|
|
80
|
+
--- vim.b[data.buf].minigit_summary_string = summary.head_name or ''
|
|
81
|
+
--- end
|
|
82
|
+
---
|
|
83
|
+
--- local au_opts = { pattern = 'MiniGitUpdated', callback = format_summary }
|
|
84
|
+
--- vim.api.nvim_create_autocmd('User', au_opts)
|
|
85
|
+
--- <
|
|
86
|
+
--- # Tweaking command output ~
|
|
87
|
+
---
|
|
88
|
+
--- Buffer output of |:Git| command can be tweaked inside autocommand for
|
|
89
|
+
--- `MiniGitCommandSplit` `User` event (see |MiniGit-command-events|).
|
|
90
|
+
--- For example, to make `:vertical Git blame -- %` align blame output with the
|
|
91
|
+
--- current window state, use the following code: >lua
|
|
92
|
+
---
|
|
93
|
+
--- local align_blame = function(au_data)
|
|
94
|
+
--- if au_data.data.git_subcommand ~= 'blame' then return end
|
|
95
|
+
---
|
|
96
|
+
--- -- Align blame output with source
|
|
97
|
+
--- local win_src = au_data.data.win_source
|
|
98
|
+
--- vim.wo.wrap = false
|
|
99
|
+
--- vim.fn.winrestview({ topline = vim.fn.line('w0', win_src) })
|
|
100
|
+
--- vim.api.nvim_win_set_cursor(0, { vim.fn.line('.', win_src), 0 })
|
|
101
|
+
---
|
|
102
|
+
--- -- Bind both windows so that they scroll together
|
|
103
|
+
--- vim.wo[win_src].scrollbind, vim.wo.scrollbind = true, true
|
|
104
|
+
--- end
|
|
105
|
+
---
|
|
106
|
+
--- local au_opts = { pattern = 'MiniGitCommandSplit', callback = align_blame }
|
|
107
|
+
--- vim.api.nvim_create_autocmd('User', au_opts)
|
|
108
|
+
--- <
|
|
109
|
+
--- # History navigation ~
|
|
110
|
+
---
|
|
111
|
+
--- Function |MiniGit.show_at_cursor()| is specifically exported to make Git
|
|
112
|
+
--- history navigation easier. Here are some different ways it can be used:
|
|
113
|
+
---
|
|
114
|
+
--- - Call inside buffer for already committed file to show the evolution of
|
|
115
|
+
--- the current line (or visually selected range) through history.
|
|
116
|
+
--- It is essentially a `:Git log HEAD` with proper `-L` flag.
|
|
117
|
+
--- This also works inside output of |MiniGit.show_diff_source()|.
|
|
118
|
+
---
|
|
119
|
+
--- - Call with cursor on commit hash to inspect that commit in full.
|
|
120
|
+
--- This is usually helpful in the output of `:Git log`.
|
|
121
|
+
---
|
|
122
|
+
--- - Call with cursor inside diff entry to inspect its file in the state how it
|
|
123
|
+
--- was at certain commit. By default it shows state after commit, unless cursor
|
|
124
|
+
--- is on the "deleted" line (i.e. line starting with "-") in which case
|
|
125
|
+
--- state before commit is shown.
|
|
126
|
+
---
|
|
127
|
+
--- This workflow can be made more interactive when used with mapping, like this: >lua
|
|
128
|
+
---
|
|
129
|
+
--- local rhs = '<Cmd>lua MiniGit.show_at_cursor()<CR>'
|
|
130
|
+
--- vim.keymap.set({ 'n', 'x' }, '<Leader>gs', rhs, { desc = 'Show at cursor' })
|
|
131
|
+
--- <
|
|
132
|
+
---@tag MiniGit-examples
|
|
133
|
+
|
|
134
|
+
--- The `:Git` user command runs `git` CLI call with extra integration for currently
|
|
135
|
+
--- opened Neovim process:
|
|
136
|
+
--- - Command is executed inside repository root of the currently active file
|
|
137
|
+
--- (or |current-directory| if file is not tracked by this module).
|
|
138
|
+
---
|
|
139
|
+
--- - Command output is shown either in dedicated buffer in window split or as
|
|
140
|
+
--- notification via |vim.notify()|. Which method is used depends on whether
|
|
141
|
+
--- particular Git subcommand is supposed to show data for user to inspect
|
|
142
|
+
--- (like `log`, `status`, etc.) or not (like `commit`, `push`, etc.). This is
|
|
143
|
+
--- determined automatically based on the data Git itself provides.
|
|
144
|
+
--- Split window is made current after command execution.
|
|
145
|
+
---
|
|
146
|
+
--- Use split-related |:command-modifiers| (|:vertical|, |:horizontal|, or |:tab|)
|
|
147
|
+
--- to force output in a particular type of split. Default split direction is
|
|
148
|
+
--- controlled by `command.split` in |MiniGit.config|.
|
|
149
|
+
---
|
|
150
|
+
--- Use |:silent| command modifier to not show any output.
|
|
151
|
+
---
|
|
152
|
+
--- Errors and warnings are always shown as notifications.
|
|
153
|
+
---
|
|
154
|
+
--- See |MiniGit-examples| for the example of tweaking command output.
|
|
155
|
+
---
|
|
156
|
+
--- - Editor for tasks that require interactive user input (like `:Git commit` or
|
|
157
|
+
--- `:Git rebase --interactive`) is opened inside current session in a separate
|
|
158
|
+
--- split. Make modifications as in regular buffer, |:write| changes followed by
|
|
159
|
+
--- |:close| / |:quit| for Git CLI command to resume.
|
|
160
|
+
---
|
|
161
|
+
--- Examples of usage:
|
|
162
|
+
--- - `:Git log --oneline` - show compact log of current repository.
|
|
163
|
+
--- - `:vert Git blame -- %` - show latest commits per line in vertical split.
|
|
164
|
+
--- - `:Git help rebase` - show help page for `rebase` subcommand.
|
|
165
|
+
--- - `:Git -C <cwd> status` - execute `git status` inside |current-directory|.
|
|
166
|
+
---
|
|
167
|
+
--- There is also a context aware completion which can be invoked with `<Tab>`:
|
|
168
|
+
--- - If completed word starts with "-", options for the current Git subcommand
|
|
169
|
+
--- are shown. Like completion at `:Git log -` will suggest `-L`, `--oneline`, etc.
|
|
170
|
+
--- - If there is an explicit " -- " to the cursor's left, incremental path
|
|
171
|
+
--- suggestions will be shown.
|
|
172
|
+
--- - If there is no recognized Git subcommand yet, show list of subcommands.
|
|
173
|
+
--- Otherwise for some common subcommands list of its targets will be suggested:
|
|
174
|
+
--- like for `:Git branch` it will be list of branches, etc.
|
|
175
|
+
---
|
|
176
|
+
--- Notes:
|
|
177
|
+
--- - Paths are always treated as relative to command's execution directory
|
|
178
|
+
--- (file's repository root or |current-directory| if absent).
|
|
179
|
+
--- - Don't use quotes for entries containing space, escape it with `\` directly.
|
|
180
|
+
--- Like `:Git commit -m Hello\ world` and not `:Git commit -m 'Hello world'`
|
|
181
|
+
--- (which treats `'Hello` and `world'` as separate arguments).
|
|
182
|
+
---
|
|
183
|
+
--- # Events ~
|
|
184
|
+
--- *MiniGit-command-events*
|
|
185
|
+
---
|
|
186
|
+
--- There are several `User` events triggered during command execution:
|
|
187
|
+
---
|
|
188
|
+
--- - `MiniGitCommandDone` - after command is done executing. For Lua callbacks it
|
|
189
|
+
--- provides a special `data` table with the following fields:
|
|
190
|
+
--- - <cmd_input> `(table)` - structured data about executed command.
|
|
191
|
+
--- Has same structure as Lua function input in |nvim_create_user_command()|.
|
|
192
|
+
--- - <cwd> `(string)` - directory path inside which Git command was executed.
|
|
193
|
+
--- - <exit_code> `(number)` - exit code of CLI process.
|
|
194
|
+
--- - <git_command> `(table)` - array with arguments of full executed command.
|
|
195
|
+
--- - <git_subcommand> `(string)` - detected Git subcommand (like "log", etc.).
|
|
196
|
+
--- - <stderr> `(string)` - `stderr` process output.
|
|
197
|
+
--- - <stdout> `(string)` - `stdout` process output.
|
|
198
|
+
---
|
|
199
|
+
--- - `MiniGitCommandSplit` - after command showed its output in a split. Triggered
|
|
200
|
+
--- after `MiniGitCommandDone` and provides similar `data` table with extra fields:
|
|
201
|
+
--- - <win_source> `(number)` - window identifier of "source" window (current at
|
|
202
|
+
--- the moment before command execution).
|
|
203
|
+
--- - <win_stdout> `(number)` - window identifier of command output.
|
|
204
|
+
---@tag :Git
|
|
205
|
+
|
|
206
|
+
---@alias __git_buf_id number Target buffer identifier. Default: 0 for current buffer.
|
|
207
|
+
---@alias __git_split_field <split> `(string)` - split direction. One of "horizontal", "vertical",
|
|
208
|
+
--- "tab", or "auto" (default). Value "auto" uses |:vertical| if only |mini.git|
|
|
209
|
+
--- buffers are shown in the tabpage and |:tab| otherwise.
|
|
210
|
+
|
|
211
|
+
---@diagnostic disable:undefined-field
|
|
212
|
+
---@diagnostic disable:discard-returns
|
|
213
|
+
---@diagnostic disable:unused-local
|
|
214
|
+
---@diagnostic disable:cast-local-type
|
|
215
|
+
---@diagnostic disable:undefined-doc-name
|
|
216
|
+
---@diagnostic disable:luadoc-miss-type-name
|
|
217
|
+
|
|
218
|
+
-- Module definition ==========================================================
|
|
219
|
+
local MiniGit = {}
|
|
220
|
+
local H = {}
|
|
221
|
+
|
|
222
|
+
--- Module setup
|
|
223
|
+
---
|
|
224
|
+
--- Besides general side effects (see |mini.nvim|), it also:
|
|
225
|
+
--- - Sets up auto enabling in every normal buffer for an actual file on disk.
|
|
226
|
+
--- - Creates |:Git| command.
|
|
227
|
+
---
|
|
228
|
+
---@param config table|nil Module config table. See |MiniGit.config|.
|
|
229
|
+
---
|
|
230
|
+
---@usage >lua
|
|
231
|
+
--- require('mini.git').setup() -- use default config
|
|
232
|
+
--- -- OR
|
|
233
|
+
--- require('mini.git').setup({}) -- replace {} with your config table
|
|
234
|
+
--- <
|
|
235
|
+
MiniGit.setup = function(config)
|
|
236
|
+
-- TODO: Remove after Neovim=0.9 support is dropped
|
|
237
|
+
if vim.fn.has('nvim-0.10') == 0 then
|
|
238
|
+
vim.notify(
|
|
239
|
+
'(mini.git) Neovim<0.10 is soft deprecated (module works but is not supported).'
|
|
240
|
+
.. " It will be deprecated after the next 'mini.nvim' release (module might not work)."
|
|
241
|
+
.. ' Please update your Neovim version.'
|
|
242
|
+
)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
-- Export module
|
|
246
|
+
_G.MiniGit = MiniGit
|
|
247
|
+
|
|
248
|
+
-- Setup config
|
|
249
|
+
config = H.setup_config(config)
|
|
250
|
+
|
|
251
|
+
-- Apply config
|
|
252
|
+
H.apply_config(config)
|
|
253
|
+
|
|
254
|
+
-- Ensure proper Git executable
|
|
255
|
+
local exec = config.job.git_executable
|
|
256
|
+
H.has_git = vim.fn.executable(exec) == 1
|
|
257
|
+
if not H.has_git then H.notify('There is no `' .. exec .. '` executable', 'WARN') end
|
|
258
|
+
|
|
259
|
+
-- Define behavior
|
|
260
|
+
H.create_autocommands()
|
|
261
|
+
for _, buf_id in ipairs(vim.api.nvim_list_bufs()) do
|
|
262
|
+
H.auto_enable({ buf = buf_id })
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
-- Create user commands
|
|
266
|
+
H.create_user_commands()
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
--stylua: ignore
|
|
270
|
+
--- Defaults ~
|
|
271
|
+
---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section)
|
|
272
|
+
---@text # Job ~
|
|
273
|
+
---
|
|
274
|
+
--- `config.job` contains options for customizing CLI executions.
|
|
275
|
+
---
|
|
276
|
+
--- `job.git_executable` defines a full path to Git executable. Default: "git".
|
|
277
|
+
---
|
|
278
|
+
--- `job.timeout` is a duration (in ms) from job start until it is forced to stop.
|
|
279
|
+
--- Default: 30000.
|
|
280
|
+
---
|
|
281
|
+
--- # Command ~
|
|
282
|
+
---
|
|
283
|
+
--- `config.command` contains options for customizing |:Git| command.
|
|
284
|
+
---
|
|
285
|
+
--- `command.split` defines default split direction for |:Git| command output. Can be
|
|
286
|
+
--- one of "horizontal", "vertical", "tab", or "auto". Value "auto" uses |:vertical|
|
|
287
|
+
--- if only |mini.git| buffers are shown in the tabpage and |:tab| otherwise.
|
|
288
|
+
--- Default: "auto".
|
|
289
|
+
MiniGit.config = {
|
|
290
|
+
-- General CLI execution
|
|
291
|
+
job = {
|
|
292
|
+
-- Path to Git executable
|
|
293
|
+
git_executable = 'git',
|
|
294
|
+
|
|
295
|
+
-- Timeout (in ms) for each job before force quit
|
|
296
|
+
timeout = 30000,
|
|
297
|
+
},
|
|
298
|
+
|
|
299
|
+
-- Options for `:Git` command
|
|
300
|
+
command = {
|
|
301
|
+
-- Default split direction
|
|
302
|
+
split = 'auto',
|
|
303
|
+
},
|
|
304
|
+
}
|
|
305
|
+
--minidoc_afterlines_end
|
|
306
|
+
|
|
307
|
+
--- Show Git related data at cursor
|
|
308
|
+
---
|
|
309
|
+
--- - If inside |mini.deps| confirmation buffer, show in split relevant commit data.
|
|
310
|
+
--- - If there is a commit-like |<cword>|, show it in split.
|
|
311
|
+
--- - If possible, show diff source via |MiniGit.show_diff_source()|.
|
|
312
|
+
--- - If possible, show range history via |MiniGit.show_range_history()|.
|
|
313
|
+
--- - Otherwise throw an error.
|
|
314
|
+
---
|
|
315
|
+
---@param opts table|nil Options. Possible values:
|
|
316
|
+
--- - __git_split_field
|
|
317
|
+
--- - Fields appropriate for forwarding to other functions.
|
|
318
|
+
MiniGit.show_at_cursor = function(opts)
|
|
319
|
+
-- Try showing commit data at cursor
|
|
320
|
+
local commit, cwd
|
|
321
|
+
if vim.bo.filetype == 'minideps-confirm' then
|
|
322
|
+
commit, cwd = H.deps_pos_to_source()
|
|
323
|
+
else
|
|
324
|
+
local cword = vim.fn.expand('<cword>')
|
|
325
|
+
local is_commit = string.find(cword, '^%x%x%x%x%x%x%x+$') ~= nil and string.lower(cword) == cword
|
|
326
|
+
commit = is_commit and cword or nil
|
|
327
|
+
cwd = is_commit and H.get_git_cwd() or nil
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
if commit ~= nil and cwd ~= nil then
|
|
331
|
+
local split = H.normalize_split_opt((opts or {}).split or 'auto', 'opts.split')
|
|
332
|
+
local args = { 'show', '--stat', '--patch', commit }
|
|
333
|
+
local lines = H.git_cli_output(args, cwd)
|
|
334
|
+
if #lines == 0 then return H.notify('Can not show commit ' .. commit .. ' in repo ' .. cwd, 'WARN') end
|
|
335
|
+
H.show_in_split(split, lines, 'show', table.concat(args, ' '))
|
|
336
|
+
vim.bo.filetype = 'git'
|
|
337
|
+
return
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
-- Try showing diff source
|
|
341
|
+
if H.diff_pos_to_source() ~= nil then return MiniGit.show_diff_source(opts) end
|
|
342
|
+
|
|
343
|
+
-- Try showing range history if possible: either in Git repo (tracked or not;
|
|
344
|
+
-- after resolving symlinks) or diff source output.
|
|
345
|
+
local buf_id, buf_name = vim.api.nvim_get_current_buf(), vim.api.nvim_buf_get_name(0)
|
|
346
|
+
local path = vim.loop.fs_realpath(buf_name)
|
|
347
|
+
local path_dir = path == nil and '' or vim.fn.fnamemodify(path, ':h')
|
|
348
|
+
|
|
349
|
+
local is_in_git = H.is_buf_enabled(buf_id) or #H.git_cli_output({ 'rev-parse', '--show-toplevel' }, path_dir) > 0
|
|
350
|
+
local is_diff_source_output = H.parse_diff_source_buf_name(buf_name) ~= nil
|
|
351
|
+
if is_in_git or is_diff_source_output then return MiniGit.show_range_history(opts) end
|
|
352
|
+
|
|
353
|
+
H.notify('Nothing Git-related to show at cursor', 'WARN')
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
--- Show diff source
|
|
357
|
+
---
|
|
358
|
+
--- When buffer contains text formatted as unified patch (like after
|
|
359
|
+
--- `:Git log --patch`, `:Git diff`, or |MiniGit.show_range_history()|),
|
|
360
|
+
--- show state of the file at the particular state. Target commit/state, path,
|
|
361
|
+
--- and line number are deduced from cursor position.
|
|
362
|
+
---
|
|
363
|
+
--- Notes:
|
|
364
|
+
--- - Needs |current-directory| to be the Git root for relative paths to work.
|
|
365
|
+
--- - Needs cursor to be inside hunk lines or on "---" / "+++" lines with paths.
|
|
366
|
+
--- - Only basic forms of `:Git diff` output is supported: `:Git diff`,
|
|
367
|
+
--- `:Git diff --cached`, and `:Git diff <commit>`.
|
|
368
|
+
---
|
|
369
|
+
---@param opts table|nil Options. Possible values:
|
|
370
|
+
--- - __git_split_field
|
|
371
|
+
--- - <target> `(string)` - which file state to show. One of "before", "after",
|
|
372
|
+
--- "both" (both states in vertical split), "auto" (default). Value "auto"
|
|
373
|
+
--- shows "before" state if cursor line starts with "-", otherwise - "after".
|
|
374
|
+
MiniGit.show_diff_source = function(opts)
|
|
375
|
+
opts = vim.tbl_deep_extend('force', { split = 'auto', target = 'auto' }, opts or {})
|
|
376
|
+
local split = H.normalize_split_opt(opts.split, 'opts.split')
|
|
377
|
+
local target = opts.target
|
|
378
|
+
if not (target == 'auto' or target == 'before' or target == 'after' or target == 'both') then
|
|
379
|
+
H.error('`opts.target` should be one of "auto", "before", "after", "both".')
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
local src = H.diff_pos_to_source()
|
|
383
|
+
if src == nil then
|
|
384
|
+
return H.notify('Could not find diff source. Ensure that cursor is inside a valid diff lines of git log.', 'WARN')
|
|
385
|
+
end
|
|
386
|
+
if target == 'auto' then target = src.init_prefix == '-' and 'before' or 'after' end
|
|
387
|
+
|
|
388
|
+
local cwd = H.get_git_cwd()
|
|
389
|
+
local show = function(commit, path, mods)
|
|
390
|
+
local is_worktree, args, lines = commit == true, nil, nil
|
|
391
|
+
if is_worktree then
|
|
392
|
+
args, lines = { 'edit', vim.fn.fnameescape(path) }, vim.fn.readfile(path)
|
|
393
|
+
else
|
|
394
|
+
args = { 'show', commit .. ':' .. path }
|
|
395
|
+
lines = H.git_cli_output(args, cwd)
|
|
396
|
+
end
|
|
397
|
+
if #lines == 0 and not is_worktree then
|
|
398
|
+
return H.notify('Can not show ' .. path .. ' at commit ' .. commit, 'WARN')
|
|
399
|
+
end
|
|
400
|
+
H.show_in_split(mods, lines, 'show', table.concat(args, ' '))
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
local has_before_shown = false
|
|
404
|
+
if target ~= 'after' then
|
|
405
|
+
if src.path_before == nil then
|
|
406
|
+
H.notify('No "before" as file was created', 'WARN')
|
|
407
|
+
else
|
|
408
|
+
show(src.commit_before, src.path_before, split)
|
|
409
|
+
vim.api.nvim_win_set_cursor(0, { src.lnum_before, 0 })
|
|
410
|
+
has_before_shown = true
|
|
411
|
+
end
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
if target ~= 'before' then
|
|
415
|
+
if src.path_after == nil then
|
|
416
|
+
H.notify('No "after" as file was deleted', 'WARN')
|
|
417
|
+
else
|
|
418
|
+
local mods_after = has_before_shown and 'belowright vertical' or split
|
|
419
|
+
show(src.commit_after, src.path_after, mods_after)
|
|
420
|
+
vim.api.nvim_win_set_cursor(0, { src.lnum_after, 0 })
|
|
421
|
+
end
|
|
422
|
+
end
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
--- Show range history
|
|
426
|
+
---
|
|
427
|
+
--- Compute and show in split data about how particular line range in current
|
|
428
|
+
--- buffer evolved through Git history. Essentially a `git log` with `-L` flag.
|
|
429
|
+
---
|
|
430
|
+
--- Notes:
|
|
431
|
+
--- - Works well with |MiniGit.diff_foldexpr()|.
|
|
432
|
+
--- - Does not work if there are uncommitted changes, as there is no easy way to
|
|
433
|
+
--- compute effective range line numbers.
|
|
434
|
+
---
|
|
435
|
+
---@param opts table|nil Options. Possible fields:
|
|
436
|
+
--- - <line_start> `(number)` - range start line.
|
|
437
|
+
--- - <line_end> `(number)` - range end line.
|
|
438
|
+
--- If both <line_start> and <line_end> are not supplied, they default to
|
|
439
|
+
--- current line in Normal mode and visual selection in Visual mode.
|
|
440
|
+
--- - <log_args> `(table)` - array of options to append to `git log` call.
|
|
441
|
+
--- - __git_split_field
|
|
442
|
+
MiniGit.show_range_history = function(opts)
|
|
443
|
+
local default_opts = { line_start = nil, line_end = nil, log_args = nil, split = 'auto' }
|
|
444
|
+
opts = vim.tbl_deep_extend('force', default_opts, opts or {})
|
|
445
|
+
local line_start, line_end = H.normalize_range_lines(opts.line_start, opts.line_end)
|
|
446
|
+
local log_args = opts.log_args or {}
|
|
447
|
+
if not H.islist(log_args) then H.error('`opts.log_args` should be an array.') end
|
|
448
|
+
local split = H.normalize_split_opt(opts.split, 'opts.split')
|
|
449
|
+
|
|
450
|
+
-- Construct `:Git log` command that works both with regular files and
|
|
451
|
+
-- buffers from `show_diff_source()`
|
|
452
|
+
local buf_name, cwd = vim.api.nvim_buf_get_name(0), H.get_git_cwd()
|
|
453
|
+
local commit, rel_path = H.parse_diff_source_buf_name(buf_name)
|
|
454
|
+
if commit == nil then
|
|
455
|
+
commit = 'HEAD'
|
|
456
|
+
local cwd_pattern = '^' .. vim.pesc(cwd:gsub('\\', '/')) .. '/'
|
|
457
|
+
rel_path = H.get_buf_realpath(0):gsub('\\', '/'):gsub(cwd_pattern, '')
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
-- Ensure no uncommitted changes as they might result into improper `-L` arg
|
|
461
|
+
local diff = commit == 'HEAD' and H.git_cli_output({ 'diff', '-U0', 'HEAD', '--', rel_path }, cwd) or {}
|
|
462
|
+
if #diff ~= 0 then
|
|
463
|
+
return H.notify('Current file has uncommitted lines. Commit or stash before exploring history.', 'WARN')
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
-- Show log in split
|
|
467
|
+
local range_flag = string.format('-L%d,%d:%s', line_start, line_end, rel_path)
|
|
468
|
+
local args = { 'log', range_flag, commit, unpack(log_args) }
|
|
469
|
+
local history = H.git_cli_output(args, cwd)
|
|
470
|
+
if #history == 0 then return H.notify('Could not get range history', 'WARN') end
|
|
471
|
+
H.show_in_split(split, history, 'log', table.concat(args, ' '))
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
--- Fold expression for Git logs
|
|
475
|
+
---
|
|
476
|
+
--- Folds contents of hunks, file patches, and log entries in unified diff.
|
|
477
|
+
--- Useful for filetypes "diff" (like after `:Git diff`) and "git" (like after
|
|
478
|
+
--- `:Git log --patch` or `:Git show` for commit).
|
|
479
|
+
--- Works well with |MiniGit.show_range_history()|.
|
|
480
|
+
---
|
|
481
|
+
--- General idea of folding levels (use |zr| and |zm| to adjust interactively):
|
|
482
|
+
--- - At level 0 there is one line per whole patch or log entry.
|
|
483
|
+
--- - At level 1 there is one line per patched file.
|
|
484
|
+
--- - At level 2 there is one line per hunk.
|
|
485
|
+
--- - At level 3 there is no folds.
|
|
486
|
+
---
|
|
487
|
+
--- For automated setup, set the following for "git" and "diff" filetypes (either
|
|
488
|
+
--- inside |FileType| autocommand or |ftplugin|): >vim
|
|
489
|
+
---
|
|
490
|
+
--- setlocal foldmethod=expr foldexpr=v:lua.MiniGit.diff_foldexpr()
|
|
491
|
+
--- <
|
|
492
|
+
---@param lnum number|nil Line number for which fold level is computed.
|
|
493
|
+
--- Default: |v:lnum|.
|
|
494
|
+
---
|
|
495
|
+
---@return number|string Line fold level. See |fold-expr|.
|
|
496
|
+
MiniGit.diff_foldexpr = function(lnum)
|
|
497
|
+
lnum = lnum or vim.v.lnum
|
|
498
|
+
if H.is_log_entry_header(lnum + 1) or H.is_log_entry_header(lnum) then return 0 end
|
|
499
|
+
if H.is_file_entry_header(lnum) then return 1 end
|
|
500
|
+
if H.is_hunk_header(lnum) then return 2 end
|
|
501
|
+
if H.is_hunk_header(lnum - 1) then return 3 end
|
|
502
|
+
return '='
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
--- Enable Git tracking in a file buffer
|
|
506
|
+
---
|
|
507
|
+
--- Tracking is done by reacting to changes in file content or file's repository
|
|
508
|
+
--- in the form of keeping buffer data up to date. The data can be used via:
|
|
509
|
+
--- - |MiniGit.get_buf_data()|. See its help for a list of actually tracked data.
|
|
510
|
+
--- - `vim.b.minigit_summary` (table) and `vim.b.minigit_summary_string` (string)
|
|
511
|
+
--- buffer-local variables which are more suitable for statusline.
|
|
512
|
+
--- `vim.b.minigit_summary_string` contains information about HEAD, file status,
|
|
513
|
+
--- and in progress action (see |MiniGit.get_buf_data()| for more details).
|
|
514
|
+
--- See |MiniGit-examples| for how it can be tweaked and used in statusline.
|
|
515
|
+
---
|
|
516
|
+
--- Note: this function is called automatically for all new normal buffers.
|
|
517
|
+
--- Use it explicitly if buffer was disabled.
|
|
518
|
+
---
|
|
519
|
+
--- `User` event `MiniGitUpdated` is triggered whenever tracking data is updated.
|
|
520
|
+
--- Note that not all data listed in |MiniGit.get_buf_data()| can be present (yet)
|
|
521
|
+
--- at the point of event being triggered.
|
|
522
|
+
---
|
|
523
|
+
---@param buf_id __git_buf_id
|
|
524
|
+
MiniGit.enable = function(buf_id)
|
|
525
|
+
buf_id = H.validate_buf_id(buf_id)
|
|
526
|
+
|
|
527
|
+
-- Don't enable more than once
|
|
528
|
+
if H.is_buf_enabled(buf_id) or H.is_disabled(buf_id) or not H.has_git then return end
|
|
529
|
+
|
|
530
|
+
-- Enable only in buffers which *can* be part of Git repo
|
|
531
|
+
local path = H.get_buf_realpath(buf_id)
|
|
532
|
+
if path == '' or vim.fn.filereadable(path) ~= 1 then return end
|
|
533
|
+
|
|
534
|
+
-- Start tracking
|
|
535
|
+
H.cache[buf_id] = {}
|
|
536
|
+
H.setup_buf_behavior(buf_id)
|
|
537
|
+
H.start_tracking(buf_id, path)
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
--- Disable Git tracking in buffer
|
|
541
|
+
---
|
|
542
|
+
---@param buf_id __git_buf_id
|
|
543
|
+
MiniGit.disable = function(buf_id)
|
|
544
|
+
buf_id = H.validate_buf_id(buf_id)
|
|
545
|
+
|
|
546
|
+
local buf_cache = H.cache[buf_id]
|
|
547
|
+
if buf_cache == nil then return end
|
|
548
|
+
H.cache[buf_id] = nil
|
|
549
|
+
|
|
550
|
+
-- Cleanup
|
|
551
|
+
pcall(vim.api.nvim_del_augroup_by_id, buf_cache.augroup)
|
|
552
|
+
vim.b[buf_id].minigit_summary, vim.b[buf_id].minigit_summary_string = nil, nil
|
|
553
|
+
|
|
554
|
+
-- - Unregister buffer from repo watching with possibly more cleanup
|
|
555
|
+
local repo = buf_cache.repo
|
|
556
|
+
if H.repos[repo] == nil then return end
|
|
557
|
+
H.repos[repo].buffers[buf_id] = nil
|
|
558
|
+
if vim.tbl_count(H.repos[repo].buffers) == 0 then
|
|
559
|
+
H.teardown_repo_watch(repo)
|
|
560
|
+
H.repos[repo] = nil
|
|
561
|
+
end
|
|
562
|
+
end
|
|
563
|
+
|
|
564
|
+
--- Toggle Git tracking in buffer
|
|
565
|
+
---
|
|
566
|
+
--- Enable if disabled, disable if enabled.
|
|
567
|
+
---
|
|
568
|
+
---@param buf_id __git_buf_id
|
|
569
|
+
MiniGit.toggle = function(buf_id)
|
|
570
|
+
buf_id = H.validate_buf_id(buf_id)
|
|
571
|
+
if H.is_buf_enabled(buf_id) then return MiniGit.disable(buf_id) end
|
|
572
|
+
return MiniGit.enable(buf_id)
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
--- Get buffer data
|
|
576
|
+
---
|
|
577
|
+
---@param buf_id __git_buf_id
|
|
578
|
+
---
|
|
579
|
+
---@return table|nil Table with buffer Git data or `nil` if buffer is not enabled.
|
|
580
|
+
--- If the file is not part of Git repo, table will be empty.
|
|
581
|
+
--- Table has the following fields:
|
|
582
|
+
--- - <repo> `(string)` - full path to `.git` directory.
|
|
583
|
+
--- - <root> `(string)` - full path to worktree root.
|
|
584
|
+
--- - <head> `(string)` - full commit of current HEAD.
|
|
585
|
+
--- - <head_name> `(string)` - short name of current HEAD (like "master").
|
|
586
|
+
--- For detached HEAD it is "HEAD".
|
|
587
|
+
--- - <status> `(string)` - two character file status as returned by `git status`.
|
|
588
|
+
--- - <in_progress> `(string)` - name of action(s) currently in progress
|
|
589
|
+
--- (bisect, merge, etc.). Can be a combination of those separated by ",".
|
|
590
|
+
MiniGit.get_buf_data = function(buf_id)
|
|
591
|
+
buf_id = H.validate_buf_id(buf_id)
|
|
592
|
+
local buf_cache = H.cache[buf_id]
|
|
593
|
+
if buf_cache == nil then return nil end
|
|
594
|
+
--stylua: ignore
|
|
595
|
+
return {
|
|
596
|
+
repo = buf_cache.repo, root = buf_cache.root,
|
|
597
|
+
head = buf_cache.head, head_name = buf_cache.head_name,
|
|
598
|
+
status = buf_cache.status, in_progress = buf_cache.in_progress,
|
|
599
|
+
}
|
|
600
|
+
end
|
|
601
|
+
|
|
602
|
+
-- Helper data ================================================================
|
|
603
|
+
-- Module default config
|
|
604
|
+
H.default_config = MiniGit.config
|
|
605
|
+
|
|
606
|
+
-- Cache per enabled buffer. Values are tables with fields:
|
|
607
|
+
-- - <augroup> - identifier of augroup defining buffer behavior.
|
|
608
|
+
-- - <repo> - path to buffer's repo ('.git' directory).
|
|
609
|
+
-- - <root> - path to worktree root.
|
|
610
|
+
-- - <head> - full commit of `HEAD`.
|
|
611
|
+
-- - <head_name> - short name of `HEAD` (`'HEAD'` for detached head).
|
|
612
|
+
-- - <status> - current file status.
|
|
613
|
+
-- - <in_progress> - string name of action in progress (bisect, merge, etc.)
|
|
614
|
+
H.cache = {}
|
|
615
|
+
|
|
616
|
+
-- Cache per repo (git directory) path. Values are tables with fields:
|
|
617
|
+
-- - <fs_event> - `vim.loop` event for watching repo dir.
|
|
618
|
+
-- - <timer> - timer to debounce repo changes.
|
|
619
|
+
-- - <buffers> - map of buffers which should are part of repo.
|
|
620
|
+
H.repos = {}
|
|
621
|
+
|
|
622
|
+
-- Temporary file used as config for `GIT_EDITOR`
|
|
623
|
+
H.git_editor_config = nil
|
|
624
|
+
|
|
625
|
+
-- Data about supported Git subcommands. Initialized lazily. Fields:
|
|
626
|
+
-- - <supported> - array of supported one word commands.
|
|
627
|
+
-- - <complete> - array of commands to complete directly after `:Git`.
|
|
628
|
+
-- - <info> - map with fields as commands which show something to user.
|
|
629
|
+
-- - <options> - map of cached options per command; initialized lazily.
|
|
630
|
+
-- - <alias> - map of alias command name to command it implements.
|
|
631
|
+
H.git_subcommands = nil
|
|
632
|
+
|
|
633
|
+
-- Whether to temporarily skip some checks (like when inside `GIT_EDITOR`)
|
|
634
|
+
H.skip_timeout = false
|
|
635
|
+
H.skip_sync = false
|
|
636
|
+
|
|
637
|
+
-- Helper functionality =======================================================
|
|
638
|
+
-- Settings -------------------------------------------------------------------
|
|
639
|
+
H.setup_config = function(config)
|
|
640
|
+
H.check_type('config', config, 'table', true)
|
|
641
|
+
config = vim.tbl_deep_extend('force', vim.deepcopy(H.default_config), config or {})
|
|
642
|
+
|
|
643
|
+
H.check_type('job', config.job, 'table')
|
|
644
|
+
H.check_type('command', config.command, 'table')
|
|
645
|
+
|
|
646
|
+
H.check_type('job.git_executable', config.job.git_executable, 'string')
|
|
647
|
+
H.check_type('job.timeout', config.job.timeout, 'number')
|
|
648
|
+
if not pcall(H.normalize_split_opt, config.command.split) then
|
|
649
|
+
H.error('`command.split` should be one of "auto", "horizontal", "vertical", "tab"')
|
|
650
|
+
end
|
|
651
|
+
|
|
652
|
+
return config
|
|
653
|
+
end
|
|
654
|
+
|
|
655
|
+
H.apply_config = function(config) MiniGit.config = config end
|
|
656
|
+
|
|
657
|
+
H.create_autocommands = function()
|
|
658
|
+
local gr = vim.api.nvim_create_augroup('MiniGit', {})
|
|
659
|
+
|
|
660
|
+
local au = function(event, pattern, callback, desc)
|
|
661
|
+
vim.api.nvim_create_autocmd(event, { group = gr, pattern = pattern, callback = callback, desc = desc })
|
|
662
|
+
end
|
|
663
|
+
|
|
664
|
+
-- NOTE: Try auto enabling buffer on every `BufEnter` to not have `:edit`
|
|
665
|
+
-- disabling buffer, as it calls `on_detach()` from buffer watcher
|
|
666
|
+
au('BufEnter', '*', H.auto_enable, 'Enable Git tracking')
|
|
667
|
+
end
|
|
668
|
+
|
|
669
|
+
H.is_disabled = function(buf_id) return vim.g.minigit_disable == true or vim.b[buf_id or 0].minigit_disable == true end
|
|
670
|
+
|
|
671
|
+
H.create_user_commands = function()
|
|
672
|
+
local opts = { bang = true, nargs = '+', complete = H.command_complete, desc = 'Execute Git command' }
|
|
673
|
+
vim.api.nvim_create_user_command('Git', H.command_impl, opts)
|
|
674
|
+
end
|
|
675
|
+
|
|
676
|
+
-- Autocommands ---------------------------------------------------------------
|
|
677
|
+
H.auto_enable = vim.schedule_wrap(function(data)
|
|
678
|
+
local buf = data.buf
|
|
679
|
+
if not (vim.api.nvim_buf_is_loaded(buf) and vim.bo[buf].buftype == '' and vim.bo[buf].buflisted) then return end
|
|
680
|
+
MiniGit.enable(data.buf)
|
|
681
|
+
end)
|
|
682
|
+
|
|
683
|
+
-- Command --------------------------------------------------------------------
|
|
684
|
+
H.command_impl = function(input)
|
|
685
|
+
if not H.has_git then
|
|
686
|
+
return H.notify('There is no `' .. MiniGit.config.job.git_executable .. '` executable', 'ERROR')
|
|
687
|
+
end
|
|
688
|
+
|
|
689
|
+
H.ensure_git_subcommands()
|
|
690
|
+
|
|
691
|
+
-- Define Git editor to be used if needed. The way it works is: execute
|
|
692
|
+
-- command, wait for it to exit, use content of edited file. So to properly
|
|
693
|
+
-- wait for user to finish edit, start fresh headless process which opens
|
|
694
|
+
-- file in current session/process. It exits after the user is done editing
|
|
695
|
+
-- (deletes the buffer or closes the window).
|
|
696
|
+
H.ensure_git_editor(input.mods)
|
|
697
|
+
-- NOTE: use `vim.v.progpath` to have same runtime
|
|
698
|
+
local editor = H.cli_escape(vim.v.progpath) .. ' --clean --headless -u ' .. H.cli_escape(H.git_editor_config)
|
|
699
|
+
|
|
700
|
+
-- Setup custom environment variables for better reproducibility
|
|
701
|
+
local env_vars = {}
|
|
702
|
+
-- - Use Git related variables to use instance for editing
|
|
703
|
+
env_vars.GIT_EDITOR, env_vars.GIT_SEQUENCE_EDITOR, env_vars.GIT_PAGER = editor, editor, ''
|
|
704
|
+
-- - Make output as much machine readable as possible
|
|
705
|
+
env_vars.NO_COLOR, env_vars.TERM = 1, 'dumb'
|
|
706
|
+
local env = H.make_spawn_env(env_vars)
|
|
707
|
+
|
|
708
|
+
-- Setup spawn arguments
|
|
709
|
+
local args = vim.tbl_map(H.expandcmd, input.fargs)
|
|
710
|
+
local command = { MiniGit.config.job.git_executable, unpack(args) }
|
|
711
|
+
local cwd = H.get_git_cwd()
|
|
712
|
+
|
|
713
|
+
local cmd_data = { cmd_input = input, git_command = command, cwd = cwd }
|
|
714
|
+
local is_done_track = { done = false }
|
|
715
|
+
local on_done = H.command_make_on_done(cmd_data, is_done_track)
|
|
716
|
+
|
|
717
|
+
H.cli_run(command, cwd, on_done, { env = env })
|
|
718
|
+
|
|
719
|
+
-- If needed, synchronously wait for job to finish
|
|
720
|
+
local sync_check = function() return H.skip_sync or is_done_track.done end
|
|
721
|
+
if not input.bang then vim.wait(MiniGit.config.job.timeout + 10, sync_check, 1) end
|
|
722
|
+
end
|
|
723
|
+
|
|
724
|
+
--stylua: ignore
|
|
725
|
+
H.ensure_git_subcommands = function()
|
|
726
|
+
if H.git_subcommands ~= nil then return end
|
|
727
|
+
local git_subcommands = {}
|
|
728
|
+
|
|
729
|
+
-- Compute all supported commands. All 'list-' are taken from Git source
|
|
730
|
+
-- 'command-list.txt' file. Be so granular and not just `main,nohelpers` in
|
|
731
|
+
-- order to not include purely man-page worthy items (like "remote-ext").
|
|
732
|
+
local lists_all = {
|
|
733
|
+
'list-mainporcelain',
|
|
734
|
+
'list-ancillarymanipulators', 'list-ancillaryinterrogators',
|
|
735
|
+
'list-foreignscminterface',
|
|
736
|
+
'list-plumbingmanipulators', 'list-plumbinginterrogators',
|
|
737
|
+
'others', 'alias',
|
|
738
|
+
}
|
|
739
|
+
local supported = H.git_cli_output({ '--list-cmds=' .. table.concat(lists_all, ',') })
|
|
740
|
+
if #supported == 0 then
|
|
741
|
+
-- Fall back only on basics if previous one failed for some reason
|
|
742
|
+
supported = {
|
|
743
|
+
'add', 'bisect', 'branch', 'clone', 'commit', 'diff', 'fetch', 'grep', 'init', 'log', 'merge',
|
|
744
|
+
'mv', 'pull', 'push', 'rebase', 'reset', 'restore', 'rm', 'show', 'status', 'switch', 'tag',
|
|
745
|
+
}
|
|
746
|
+
end
|
|
747
|
+
table.sort(supported)
|
|
748
|
+
git_subcommands.supported = supported
|
|
749
|
+
|
|
750
|
+
-- Compute complete list for commands by enhancing with two word commands.
|
|
751
|
+
-- Keep those lists manual as there is no good way to compute lazily.
|
|
752
|
+
local complete = vim.deepcopy(supported)
|
|
753
|
+
local add_twoword = function(prefix, suffixes)
|
|
754
|
+
if not vim.tbl_contains(supported, prefix) then return end
|
|
755
|
+
for _, suf in ipairs(suffixes) do table.insert(complete, prefix .. ' ' .. suf) end
|
|
756
|
+
end
|
|
757
|
+
add_twoword('bundle', { 'create', 'list-heads', 'unbundle', 'verify' })
|
|
758
|
+
add_twoword('bisect', { 'bad', 'good', 'log', 'replay', 'reset', 'run', 'skip', 'start', 'terms', 'view', 'visualize' })
|
|
759
|
+
add_twoword('commit-graph', { 'verify', 'write' })
|
|
760
|
+
add_twoword('maintenance', { 'run', 'start', 'stop', 'register', 'unregister' })
|
|
761
|
+
add_twoword('multi-pack-index', { 'expire', 'repack', 'verify', 'write' })
|
|
762
|
+
add_twoword('notes', { 'add', 'append', 'copy', 'edit', 'get-ref', 'list', 'merge', 'prune', 'remove', 'show' })
|
|
763
|
+
add_twoword('p4', { 'clone', 'rebase', 'submit', 'sync' })
|
|
764
|
+
add_twoword('reflog', { 'delete', 'exists', 'expire', 'show' })
|
|
765
|
+
add_twoword('remote', { 'add', 'get-url', 'prune', 'remove', 'rename', 'rm', 'set-branches', 'set-head', 'set-url', 'show', 'update' })
|
|
766
|
+
add_twoword('rerere', { 'clear', 'diff', 'forget', 'gc', 'remaining', 'status' })
|
|
767
|
+
add_twoword('sparse-checkout', { 'add', 'check-rules', 'disable', 'init', 'list', 'reapply', 'set' })
|
|
768
|
+
add_twoword('stash', { 'apply', 'branch', 'clear', 'create', 'drop', 'list', 'pop', 'save', 'show', 'store' })
|
|
769
|
+
add_twoword('submodule', { 'absorbgitdirs', 'add', 'deinit', 'foreach', 'init', 'set-branch', 'set-url', 'status', 'summary', 'sync', 'update' })
|
|
770
|
+
add_twoword('subtree', { 'add', 'merge', 'pull', 'push', 'split' })
|
|
771
|
+
add_twoword('worktree', { 'add', 'list', 'lock', 'move', 'prune', 'remove', 'repair', 'unlock' })
|
|
772
|
+
git_subcommands.complete = complete
|
|
773
|
+
|
|
774
|
+
-- Compute commands which are meant to show information. These will show CLI
|
|
775
|
+
-- output in separate buffer opposed to `vim.notify`.
|
|
776
|
+
local info_args = { '--list-cmds=list-info,list-ancillaryinterrogators,list-plumbinginterrogators' }
|
|
777
|
+
local info_commands = H.git_cli_output(info_args)
|
|
778
|
+
if #info_commands == 0 then info_commands = { 'bisect', 'diff', 'grep', 'log', 'show', 'status' } end
|
|
779
|
+
local info = {}
|
|
780
|
+
for _, cmd in ipairs(info_commands) do
|
|
781
|
+
info[cmd] = true
|
|
782
|
+
end
|
|
783
|
+
git_subcommands.info = info
|
|
784
|
+
|
|
785
|
+
-- Compute commands which aliases rely on
|
|
786
|
+
local alias_data = H.git_cli_output({ 'config', '--get-regexp', 'alias.*' })
|
|
787
|
+
local alias = {}
|
|
788
|
+
for _, l in ipairs(alias_data) do
|
|
789
|
+
-- Assume simple alias of the form `alias.xxx subcommand ...`
|
|
790
|
+
local alias_cmd, cmd = string.match(l, '^alias%.(%S+) (%S+)')
|
|
791
|
+
if vim.tbl_contains(supported, cmd) then alias[alias_cmd] = cmd end
|
|
792
|
+
end
|
|
793
|
+
git_subcommands.alias = alias
|
|
794
|
+
|
|
795
|
+
-- Initialize cache for command options. Initialize with `false` so that
|
|
796
|
+
-- actual values are computed lazily when needed for a command.
|
|
797
|
+
local options = { git = false }
|
|
798
|
+
for _, command in ipairs(supported) do
|
|
799
|
+
options[command] = false
|
|
800
|
+
end
|
|
801
|
+
git_subcommands.options = options
|
|
802
|
+
|
|
803
|
+
-- Cache results
|
|
804
|
+
H.git_subcommands = git_subcommands
|
|
805
|
+
end
|
|
806
|
+
|
|
807
|
+
H.ensure_git_editor = function(mods)
|
|
808
|
+
if H.git_editor_config == nil or not vim.fn.filereadable(H.git_editor_config) == 0 then
|
|
809
|
+
H.git_editor_config = vim.fn.tempname()
|
|
810
|
+
end
|
|
811
|
+
|
|
812
|
+
-- Create a private function responsible for editing Git file
|
|
813
|
+
MiniGit._edit = function(path, servername)
|
|
814
|
+
-- Define editor state before and after editing path
|
|
815
|
+
H.skip_timeout, H.skip_sync = true, true
|
|
816
|
+
local cleanup = function()
|
|
817
|
+
local _, channel = pcall(vim.fn.sockconnect, 'pipe', servername, { rpc = true })
|
|
818
|
+
pcall(vim.rpcnotify, channel, 'nvim_exec2', 'quitall!', {})
|
|
819
|
+
H.skip_timeout, H.skip_sync = false, false
|
|
820
|
+
end
|
|
821
|
+
|
|
822
|
+
-- Start file edit with proper modifiers in a special window
|
|
823
|
+
mods = H.ensure_mods_is_split(mods)
|
|
824
|
+
vim.cmd(mods .. ' split ' .. vim.fn.fnameescape(path))
|
|
825
|
+
H.define_minigit_window(cleanup)
|
|
826
|
+
end
|
|
827
|
+
|
|
828
|
+
-- Start editing file from first argument (as how `GIT_EDITOR` works) in
|
|
829
|
+
-- current instance and don't close until explicitly closed later from this
|
|
830
|
+
-- instance as set up in `MiniGit._edit()`
|
|
831
|
+
local lines = {
|
|
832
|
+
'lua << EOF',
|
|
833
|
+
string.format('local channel = vim.fn.sockconnect("pipe", %s, { rpc = true })', vim.inspect(vim.v.servername)),
|
|
834
|
+
'local ins = vim.inspect',
|
|
835
|
+
'local lua_cmd = string.format("MiniGit._edit(%s, %s)", ins(vim.fn.argv(0)), ins(vim.v.servername))',
|
|
836
|
+
'vim.rpcrequest(channel, "nvim_exec_lua", lua_cmd, {})',
|
|
837
|
+
'EOF',
|
|
838
|
+
}
|
|
839
|
+
vim.fn.writefile(lines, H.git_editor_config)
|
|
840
|
+
end
|
|
841
|
+
|
|
842
|
+
H.get_git_cwd = function()
|
|
843
|
+
local buf_cache = H.cache[vim.api.nvim_get_current_buf()] or {}
|
|
844
|
+
return buf_cache.root or vim.fn.getcwd()
|
|
845
|
+
end
|
|
846
|
+
|
|
847
|
+
H.command_make_on_done = function(cmd_data, is_done_track)
|
|
848
|
+
return vim.schedule_wrap(function(code, out, err)
|
|
849
|
+
-- Register that command is done executing (to enable sync execution)
|
|
850
|
+
is_done_track.done = true
|
|
851
|
+
|
|
852
|
+
-- Trigger "done" event
|
|
853
|
+
cmd_data.git_subcommand = H.command_parse_subcommand(cmd_data.git_command)
|
|
854
|
+
cmd_data.exit_code, cmd_data.stdout, cmd_data.stderr = code, out, err
|
|
855
|
+
H.trigger_event('MiniGitCommandDone', cmd_data)
|
|
856
|
+
|
|
857
|
+
-- Show stderr and stdout
|
|
858
|
+
if H.cli_err_notify(code, out, err) then return end
|
|
859
|
+
H.command_show_stdout(cmd_data)
|
|
860
|
+
|
|
861
|
+
-- Ensure that all buffers are up to date (avoids "The file has been
|
|
862
|
+
-- changed since reading it" warning)
|
|
863
|
+
vim.tbl_map(function(buf_id) vim.cmd('checktime ' .. buf_id) end, vim.api.nvim_list_bufs())
|
|
864
|
+
end)
|
|
865
|
+
end
|
|
866
|
+
|
|
867
|
+
H.command_show_stdout = function(cmd_data)
|
|
868
|
+
local stdout, mods, subcommand = cmd_data.stdout, cmd_data.cmd_input.mods, cmd_data.git_subcommand
|
|
869
|
+
if stdout == '' or (mods:find('silent') ~= nil and mods:find('unsilent') == nil) then return end
|
|
870
|
+
|
|
871
|
+
-- Show in split if explicitly forced or the command shows info.
|
|
872
|
+
-- Use `vim.notify` otherwise.
|
|
873
|
+
local should_split = H.mods_is_split(mods) or H.git_subcommands.info[subcommand]
|
|
874
|
+
if not should_split then return H.notify(stdout, 'INFO') end
|
|
875
|
+
|
|
876
|
+
local lines = vim.split(stdout, '\n')
|
|
877
|
+
local name = table.concat(cmd_data.git_command, ' ')
|
|
878
|
+
cmd_data.win_source, cmd_data.win_stdout = H.show_in_split(mods, lines, subcommand, name)
|
|
879
|
+
|
|
880
|
+
-- Trigger "split" event
|
|
881
|
+
H.trigger_event('MiniGitCommandSplit', cmd_data)
|
|
882
|
+
end
|
|
883
|
+
|
|
884
|
+
H.command_parse_subcommand = function(command)
|
|
885
|
+
local res
|
|
886
|
+
for _, cmd in ipairs(command) do
|
|
887
|
+
if res == nil and vim.tbl_contains(H.git_subcommands.supported, cmd) then res = cmd end
|
|
888
|
+
end
|
|
889
|
+
return H.git_subcommands.alias[res] or res
|
|
890
|
+
end
|
|
891
|
+
|
|
892
|
+
H.command_complete = function(_, line, col)
|
|
893
|
+
-- Compute completion base manually to be "at cursor" and respect `\ `
|
|
894
|
+
local base = H.get_complete_base(line:sub(1, col))
|
|
895
|
+
local candidates, compl_type = H.command_get_complete_candidates(line, col, base)
|
|
896
|
+
-- Allow several "//" at the end for path completion for easier "chaining"
|
|
897
|
+
if compl_type == 'path' then base = base:gsub('/+$', '/') end
|
|
898
|
+
return vim.tbl_filter(function(x) return vim.startswith(x, base) end, candidates)
|
|
899
|
+
end
|
|
900
|
+
|
|
901
|
+
H.get_complete_base = function(line)
|
|
902
|
+
local from, _, res = line:find('(%S*)$')
|
|
903
|
+
while from ~= nil do
|
|
904
|
+
local cur_from, _, cur_res = line:sub(1, from - 1):find('(%S*\\ )$')
|
|
905
|
+
if cur_res ~= nil then res = cur_res .. res end
|
|
906
|
+
from = cur_from
|
|
907
|
+
end
|
|
908
|
+
return (res:gsub([[\ ]], ' '))
|
|
909
|
+
end
|
|
910
|
+
|
|
911
|
+
H.command_get_complete_candidates = function(line, col, base)
|
|
912
|
+
H.ensure_git_subcommands()
|
|
913
|
+
|
|
914
|
+
-- Determine current Git subcommand as the earliest present supported one
|
|
915
|
+
local subcmd, subcmd_end = nil, math.huge
|
|
916
|
+
for _, cmd in pairs(H.git_subcommands.supported) do
|
|
917
|
+
local _, ind = line:find(' ' .. cmd .. ' ', 1, true)
|
|
918
|
+
if ind ~= nil and ind < subcmd_end then
|
|
919
|
+
subcmd, subcmd_end = cmd, ind
|
|
920
|
+
end
|
|
921
|
+
end
|
|
922
|
+
|
|
923
|
+
subcmd = subcmd or 'git'
|
|
924
|
+
local cwd = H.get_git_cwd()
|
|
925
|
+
|
|
926
|
+
-- Determine command candidates:
|
|
927
|
+
-- - Command options if complete base starts with "-".
|
|
928
|
+
-- - Paths if after explicit "--".
|
|
929
|
+
-- - Git commands if there is none fully formed yet or cursor is at the end
|
|
930
|
+
-- of the command (to also suggest subcommands).
|
|
931
|
+
-- - Command targets specific for each command (if present).
|
|
932
|
+
if vim.startswith(base, '-') then return H.command_complete_option(subcmd) end
|
|
933
|
+
if line:sub(1, col):find(' -- ') ~= nil then return H.command_complete_path(cwd, base) end
|
|
934
|
+
if subcmd_end == math.huge or (subcmd_end - 1) == col then return H.git_subcommands.complete, 'subcommand' end
|
|
935
|
+
|
|
936
|
+
subcmd = H.git_subcommands.alias[subcmd] or subcmd
|
|
937
|
+
local complete_targets = H.command_complete_subcommand_targets[subcmd]
|
|
938
|
+
if complete_targets == nil then return {}, nil end
|
|
939
|
+
return complete_targets(cwd, base, line)
|
|
940
|
+
end
|
|
941
|
+
|
|
942
|
+
H.command_complete_option = function(command)
|
|
943
|
+
local cached_candidates = H.git_subcommands.options[command]
|
|
944
|
+
if cached_candidates == nil then return {} end
|
|
945
|
+
if type(cached_candidates) == 'table' then return cached_candidates end
|
|
946
|
+
|
|
947
|
+
-- Use alias's command to compute the options but store cache for alias
|
|
948
|
+
local orig_command = command
|
|
949
|
+
command = H.git_subcommands.alias[command] or command
|
|
950
|
+
|
|
951
|
+
-- Find command's flag options by parsing its help page. Needs a bit
|
|
952
|
+
-- heuristic approach and ensuring proper `git help` output (as it is done
|
|
953
|
+
-- through `man`), but seems to work good enough.
|
|
954
|
+
-- Alternative is to call command with `--git-completion-helper-all` flag (as
|
|
955
|
+
-- is done in bash and vim-fugitive completion). This has both pros and cons:
|
|
956
|
+
-- - Pros: faster; more targeted suggestions (like for two word subcommands);
|
|
957
|
+
-- presumably more reliable.
|
|
958
|
+
-- - Cons: works on smaller number of commands (for example, `rev-parse` or
|
|
959
|
+
-- pure `git` do not work); does not provide single dash suggestions;
|
|
960
|
+
-- does not work when not inside Git repo; needs recognizing two word
|
|
961
|
+
-- commands before asking for completion.
|
|
962
|
+
local env = H.make_spawn_env({ MANPAGER = 'cat', NO_COLOR = 1, PAGER = 'cat' })
|
|
963
|
+
local lines = H.git_cli_output({ 'help', '--man', command }, nil, env)
|
|
964
|
+
-- - Exit early before caching to try again later
|
|
965
|
+
if #lines == 0 then return {} end
|
|
966
|
+
-- - On some systems (like Mac), output still might contain formatting
|
|
967
|
+
-- sequences, like "a\ba" and "_\ba" meaning bold and italic.
|
|
968
|
+
-- See https://github.com/nvim-mini/mini.nvim/issues/918
|
|
969
|
+
lines = vim.tbl_map(function(l) return l:gsub('.\b', '') end, lines)
|
|
970
|
+
|
|
971
|
+
-- Construct non-duplicating candidates by parsing lines of help page
|
|
972
|
+
local candidates_map = {}
|
|
973
|
+
|
|
974
|
+
-- Options are assumed to be listed inside "OPTIONS" or "XXX OPTIONS" (like
|
|
975
|
+
-- "MODE OPTIONS" of `git rebase`) section on dedicated lines. Whether a line
|
|
976
|
+
-- contains only options is determined heuristically: it is assumed to start
|
|
977
|
+
-- exactly with " -" indicating proper indent for subsection start.
|
|
978
|
+
-- Known not parsable options:
|
|
979
|
+
-- - `git reset <mode>` (--soft, --hard, etc.): not listed in "OPTIONS".
|
|
980
|
+
-- - All -<number> options, as they are not really completable.
|
|
981
|
+
local is_in_options_section = false
|
|
982
|
+
for _, l in ipairs(lines) do
|
|
983
|
+
if is_in_options_section and l:find('^%u[%u ]+$') ~= nil then is_in_options_section = false end
|
|
984
|
+
if not is_in_options_section and l:find('^%u?[%u ]*OPTIONS$') ~= nil then is_in_options_section = true end
|
|
985
|
+
if is_in_options_section and l:find('^ %-') ~= nil then H.parse_options(candidates_map, l) end
|
|
986
|
+
end
|
|
987
|
+
|
|
988
|
+
-- Finalize candidates. Should not contain "almost duplicates".
|
|
989
|
+
-- Should also be sorted by relevance: short flags before regular flags.
|
|
990
|
+
-- Inside groups sort alphabetically ignoring case.
|
|
991
|
+
candidates_map['--'] = nil
|
|
992
|
+
for cmd, _ in pairs(candidates_map) do
|
|
993
|
+
-- There can be two explicitly documented options "--xxx" and "--xxx=".
|
|
994
|
+
-- Use only one of them (without "=").
|
|
995
|
+
if cmd:sub(-1, -1) == '=' and candidates_map[cmd:sub(1, -2)] ~= nil then candidates_map[cmd] = nil end
|
|
996
|
+
end
|
|
997
|
+
|
|
998
|
+
local res = vim.tbl_keys(candidates_map)
|
|
999
|
+
table.sort(res, function(a, b)
|
|
1000
|
+
local a2, b2 = a:sub(2, 2) == '-', b:sub(2, 2) == '-'
|
|
1001
|
+
if a2 and not b2 then return false end
|
|
1002
|
+
if not a2 and b2 then return true end
|
|
1003
|
+
local a_low, b_low = a:lower(), b:lower()
|
|
1004
|
+
return a_low < b_low or (a_low == b_low and a < b)
|
|
1005
|
+
end)
|
|
1006
|
+
|
|
1007
|
+
-- Cache and return
|
|
1008
|
+
H.git_subcommands.options[orig_command] = res
|
|
1009
|
+
return res, 'option'
|
|
1010
|
+
end
|
|
1011
|
+
|
|
1012
|
+
H.parse_options = function(map, line)
|
|
1013
|
+
-- Options are standalone words starting as "-xxx" or "--xxx"
|
|
1014
|
+
-- Include possible "=" at the end indicating mandatory value
|
|
1015
|
+
line:gsub('%s(%-[-%w][-%w]*=?)', function(match) map[match] = true end)
|
|
1016
|
+
|
|
1017
|
+
-- Make exceptions for commonly documented "--[no-]xxx" two options
|
|
1018
|
+
line:gsub('%s%-%-%[no%-%]([-%w]+=?)', function(match)
|
|
1019
|
+
map['--' .. match], map['--no-' .. match] = true, true
|
|
1020
|
+
end)
|
|
1021
|
+
end
|
|
1022
|
+
|
|
1023
|
+
H.command_complete_path = function(cwd, base)
|
|
1024
|
+
-- Treat base only as path relative to the command's cwd
|
|
1025
|
+
cwd = cwd:gsub('/+$', '') .. '/'
|
|
1026
|
+
local cwd_len = cwd:len()
|
|
1027
|
+
|
|
1028
|
+
-- List elements from (absolute) target directory
|
|
1029
|
+
local target_dir = vim.fn.fnamemodify(base, ':h')
|
|
1030
|
+
target_dir = (cwd .. target_dir:gsub('^%.$', '')):gsub('/+$', '') .. '/'
|
|
1031
|
+
local ok, fs_entries = pcall(vim.fn.readdir, target_dir)
|
|
1032
|
+
if not ok then return {} end
|
|
1033
|
+
|
|
1034
|
+
-- List directories and files separately
|
|
1035
|
+
local dirs, files = {}, {}
|
|
1036
|
+
for _, entry in ipairs(fs_entries) do
|
|
1037
|
+
local entry_abs = target_dir .. entry
|
|
1038
|
+
local arr = vim.fn.isdirectory(entry_abs) == 1 and dirs or files
|
|
1039
|
+
table.insert(arr, entry_abs)
|
|
1040
|
+
end
|
|
1041
|
+
dirs = vim.tbl_map(function(x) return x .. '/' end, dirs)
|
|
1042
|
+
|
|
1043
|
+
-- List ordered directories first followed by ordered files
|
|
1044
|
+
local order_ignore_case = function(a, b) return a:lower() < b:lower() end
|
|
1045
|
+
table.sort(dirs, order_ignore_case)
|
|
1046
|
+
table.sort(files, order_ignore_case)
|
|
1047
|
+
|
|
1048
|
+
-- Return candidates relative to command's cwd
|
|
1049
|
+
local all = dirs
|
|
1050
|
+
vim.list_extend(all, files)
|
|
1051
|
+
local res = vim.tbl_map(function(x) return x:sub(cwd_len + 1) end, all)
|
|
1052
|
+
return res, 'path'
|
|
1053
|
+
end
|
|
1054
|
+
|
|
1055
|
+
H.command_complete_pullpush = function(cwd, _, line)
|
|
1056
|
+
-- Suggest remotes at `Git push |` and `Git push or|`, otherwise - references
|
|
1057
|
+
-- Ignore options when deciding which suggestion to compute
|
|
1058
|
+
local _, n_words = line:gsub(' (%-%S+)', ''):gsub('%S+ ', '')
|
|
1059
|
+
if n_words <= 2 then return H.git_cli_output({ 'remote' }, cwd), 'remote' end
|
|
1060
|
+
return H.git_cli_output({ 'rev-parse', '--symbolic', '--branches', '--tags' }, cwd), 'ref'
|
|
1061
|
+
end
|
|
1062
|
+
|
|
1063
|
+
H.make_git_cli_complete = function(args, complete_type)
|
|
1064
|
+
return function(cwd, _) return H.git_cli_output(args, cwd), complete_type end
|
|
1065
|
+
end
|
|
1066
|
+
|
|
1067
|
+
-- Cover at least all subcommands listed in `git help`
|
|
1068
|
+
--stylua: ignore
|
|
1069
|
+
H.command_complete_subcommand_targets = {
|
|
1070
|
+
-- clone - no targets
|
|
1071
|
+
-- init - no targets
|
|
1072
|
+
|
|
1073
|
+
-- Worktree
|
|
1074
|
+
add = H.command_complete_path,
|
|
1075
|
+
mv = H.command_complete_path,
|
|
1076
|
+
restore = H.command_complete_path,
|
|
1077
|
+
rm = H.command_complete_path,
|
|
1078
|
+
|
|
1079
|
+
-- Examine history
|
|
1080
|
+
-- bisect - no targets
|
|
1081
|
+
diff = H.command_complete_path,
|
|
1082
|
+
grep = H.command_complete_path,
|
|
1083
|
+
log = H.make_git_cli_complete({ 'rev-parse', '--symbolic', '--branches', '--tags' }, 'ref'),
|
|
1084
|
+
show = H.make_git_cli_complete({ 'rev-parse', '--symbolic', '--branches', '--tags' }, 'ref'),
|
|
1085
|
+
-- status - no targets
|
|
1086
|
+
|
|
1087
|
+
-- Modify history
|
|
1088
|
+
branch = H.make_git_cli_complete({ 'rev-parse', '--symbolic', '--branches' }, 'branch'),
|
|
1089
|
+
commit = H.command_complete_path,
|
|
1090
|
+
merge = H.make_git_cli_complete({ 'rev-parse', '--symbolic', '--branches' }, 'branch'),
|
|
1091
|
+
rebase = H.make_git_cli_complete({ 'rev-parse', '--symbolic', '--branches' }, 'branch'),
|
|
1092
|
+
reset = H.make_git_cli_complete({ 'rev-parse', '--symbolic', '--branches', '--tags' }, 'ref'),
|
|
1093
|
+
switch = H.make_git_cli_complete({ 'rev-parse', '--symbolic', '--branches' }, 'branch'),
|
|
1094
|
+
tag = H.make_git_cli_complete({ 'rev-parse', '--symbolic', '--tags' }, 'tag'),
|
|
1095
|
+
|
|
1096
|
+
-- Collaborate
|
|
1097
|
+
fetch = H.make_git_cli_complete({ 'remote' }, 'remote'),
|
|
1098
|
+
push = H.command_complete_pullpush,
|
|
1099
|
+
pull = H.command_complete_pullpush,
|
|
1100
|
+
|
|
1101
|
+
-- Miscellaneous
|
|
1102
|
+
checkout = H.make_git_cli_complete({ 'rev-parse', '--symbolic', '--branches', '--tags', '--remotes' }, 'checkout'),
|
|
1103
|
+
config = H.make_git_cli_complete({ 'help', '--config-for-completion' }, 'config'),
|
|
1104
|
+
help = function()
|
|
1105
|
+
local res = { 'git', 'everyday' }
|
|
1106
|
+
vim.list_extend(res, H.git_subcommands.supported)
|
|
1107
|
+
return res, 'help'
|
|
1108
|
+
end,
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
H.ensure_mods_is_split = function(mods)
|
|
1112
|
+
if not H.mods_is_split(mods) then
|
|
1113
|
+
local split_val = H.normalize_split_opt(MiniGit.config.command.split, '`config.command.split`')
|
|
1114
|
+
mods = split_val .. ' ' .. mods
|
|
1115
|
+
end
|
|
1116
|
+
return mods
|
|
1117
|
+
end
|
|
1118
|
+
|
|
1119
|
+
-- NOTE: `mods` is already expanded, so this also covers abbreviated mods
|
|
1120
|
+
H.mods_is_split = function(mods) return mods:find('vertical') or mods:find('horizontal') or mods:find('tab') end
|
|
1121
|
+
|
|
1122
|
+
-- Show stdout ----------------------------------------------------------------
|
|
1123
|
+
H.show_in_split = function(mods, lines, subcmd, name)
|
|
1124
|
+
-- Create a target window split
|
|
1125
|
+
mods = H.ensure_mods_is_split(mods)
|
|
1126
|
+
local win_source = vim.api.nvim_get_current_win()
|
|
1127
|
+
vim.cmd(mods .. ' split')
|
|
1128
|
+
local win_stdout = vim.api.nvim_get_current_win()
|
|
1129
|
+
|
|
1130
|
+
-- Prepare buffer
|
|
1131
|
+
local buf_id = vim.api.nvim_create_buf(false, true)
|
|
1132
|
+
H.set_buf_name(buf_id, name)
|
|
1133
|
+
vim.api.nvim_buf_set_lines(buf_id, 0, -1, false, lines)
|
|
1134
|
+
|
|
1135
|
+
vim.api.nvim_set_current_buf(buf_id)
|
|
1136
|
+
H.define_minigit_window()
|
|
1137
|
+
|
|
1138
|
+
-- NOTE: set filetype when buffer is in window to allow setting window-local
|
|
1139
|
+
-- options in autocommands for `FileType` events
|
|
1140
|
+
local filetype
|
|
1141
|
+
if subcmd == 'diff' then filetype = 'diff' end
|
|
1142
|
+
if subcmd == 'log' or subcmd == 'blame' then filetype = 'git' end
|
|
1143
|
+
if subcmd == 'show' then
|
|
1144
|
+
-- Try detecting 'git' filetype by content first, as filetype detection can
|
|
1145
|
+
-- rely on the buffer name (i.e. command) having proper extension. It isn't
|
|
1146
|
+
-- good for cases like `:Git show HEAD file.lua` (which should be 'git').
|
|
1147
|
+
local l = lines[1]
|
|
1148
|
+
local is_diff = l:find(string.rep('%x', 40)) or l:find('ref:')
|
|
1149
|
+
filetype = is_diff and 'git' or vim.filetype.match({ buf = buf_id })
|
|
1150
|
+
end
|
|
1151
|
+
|
|
1152
|
+
local has_filetype = not (filetype == nil or filetype == '')
|
|
1153
|
+
if has_filetype then vim.bo[buf_id].filetype = filetype end
|
|
1154
|
+
|
|
1155
|
+
-- Completely unfold for no filetype output (like `:Git help`)
|
|
1156
|
+
if not has_filetype then vim.wo[win_stdout].foldlevel = 999 end
|
|
1157
|
+
|
|
1158
|
+
return win_source, win_stdout
|
|
1159
|
+
end
|
|
1160
|
+
|
|
1161
|
+
H.define_minigit_window = function(cleanup)
|
|
1162
|
+
local buf_id, win_id = vim.api.nvim_get_current_buf(), vim.api.nvim_get_current_win()
|
|
1163
|
+
vim.bo.swapfile, vim.bo.buflisted = false, false
|
|
1164
|
+
|
|
1165
|
+
-- Define action to finish editing Git related file
|
|
1166
|
+
local finish_au_id
|
|
1167
|
+
local finish = function(data)
|
|
1168
|
+
local should_close = data.buf == buf_id or (data.event == 'WinClosed' and tonumber(data.match) == win_id)
|
|
1169
|
+
if not should_close then return end
|
|
1170
|
+
|
|
1171
|
+
pcall(vim.api.nvim_del_autocmd, finish_au_id)
|
|
1172
|
+
pcall(vim.api.nvim_win_close, win_id, true)
|
|
1173
|
+
vim.schedule(function() pcall(vim.api.nvim_buf_delete, buf_id, { force = true }) end)
|
|
1174
|
+
|
|
1175
|
+
if vim.is_callable(cleanup) then vim.schedule(cleanup) end
|
|
1176
|
+
end
|
|
1177
|
+
-- - Use `nested` to allow other events (`WinEnter` for 'mini.statusline')
|
|
1178
|
+
local events = { 'WinClosed', 'BufDelete', 'BufWipeout', 'VimLeave' }
|
|
1179
|
+
local opts = { nested = true, callback = finish, desc = 'Cleanup window and buffer' }
|
|
1180
|
+
finish_au_id = vim.api.nvim_create_autocmd(events, opts)
|
|
1181
|
+
end
|
|
1182
|
+
|
|
1183
|
+
H.git_cli_output = function(args, cwd, env)
|
|
1184
|
+
if cwd ~= nil and (vim.fn.isdirectory(cwd) ~= 1 or cwd == '') then return {} end
|
|
1185
|
+
local command = { MiniGit.config.job.git_executable, '--no-pager', unpack(args) }
|
|
1186
|
+
local res = H.cli_run(command, cwd, nil, { env = env }).out
|
|
1187
|
+
if res == '' then return {} end
|
|
1188
|
+
return vim.split(res, '\n')
|
|
1189
|
+
end
|
|
1190
|
+
|
|
1191
|
+
-- Validators -----------------------------------------------------------------
|
|
1192
|
+
H.validate_buf_id = function(x)
|
|
1193
|
+
if x == nil or x == 0 then return vim.api.nvim_get_current_buf() end
|
|
1194
|
+
if not (type(x) == 'number' and vim.api.nvim_buf_is_valid(x)) then
|
|
1195
|
+
H.error('`buf_id` should be `nil` or valid buffer id.')
|
|
1196
|
+
end
|
|
1197
|
+
return x
|
|
1198
|
+
end
|
|
1199
|
+
|
|
1200
|
+
H.normalize_split_opt = function(x, x_name)
|
|
1201
|
+
if x == 'auto' then
|
|
1202
|
+
-- Show in same tabpage if only minigit buffers visible. Otherwise in new.
|
|
1203
|
+
for _, win_id in ipairs(vim.api.nvim_tabpage_list_wins(0)) do
|
|
1204
|
+
local win_buf_id = vim.api.nvim_win_get_buf(win_id)
|
|
1205
|
+
local win_buf_name = vim.api.nvim_buf_get_name(win_buf_id)
|
|
1206
|
+
local is_minigit_win = win_buf_name:find('^minigit://%d+/') ~= nil
|
|
1207
|
+
local is_normal_win = vim.api.nvim_win_get_config(win_id).relative == ''
|
|
1208
|
+
if not is_minigit_win and is_normal_win then return 'tab' end
|
|
1209
|
+
end
|
|
1210
|
+
return 'vertical'
|
|
1211
|
+
end
|
|
1212
|
+
if x == 'horizontal' or x == 'vertical' or x == 'tab' then return x end
|
|
1213
|
+
H.error('`' .. x_name .. '` should be one of "auto", "horizontal", "vertical", "tab"')
|
|
1214
|
+
end
|
|
1215
|
+
|
|
1216
|
+
H.normalize_range_lines = function(line_start, line_end)
|
|
1217
|
+
if line_start == nil and line_end == nil then
|
|
1218
|
+
line_start = vim.fn.line('.')
|
|
1219
|
+
local is_visual = vim.tbl_contains({ 'v', 'V', '\22' }, vim.fn.mode())
|
|
1220
|
+
line_end = is_visual and vim.fn.line('v') or vim.fn.line('.')
|
|
1221
|
+
line_start, line_end = math.min(line_start, line_end), math.max(line_start, line_end)
|
|
1222
|
+
end
|
|
1223
|
+
|
|
1224
|
+
if not (type(line_start) == 'number' and type(line_end) == 'number' and line_start <= line_end) then
|
|
1225
|
+
H.error('`line_start` and `line_end` should be non-decreasing numbers.')
|
|
1226
|
+
end
|
|
1227
|
+
return line_start, line_end
|
|
1228
|
+
end
|
|
1229
|
+
|
|
1230
|
+
-- Enabling -------------------------------------------------------------------
|
|
1231
|
+
H.is_buf_enabled = function(buf_id) return H.cache[buf_id] ~= nil and vim.api.nvim_buf_is_valid(buf_id) end
|
|
1232
|
+
|
|
1233
|
+
H.setup_buf_behavior = function(buf_id)
|
|
1234
|
+
local augroup = vim.api.nvim_create_augroup('MiniGitBuffer' .. buf_id, { clear = true })
|
|
1235
|
+
H.cache[buf_id].augroup = augroup
|
|
1236
|
+
|
|
1237
|
+
vim.api.nvim_buf_attach(buf_id, false, {
|
|
1238
|
+
-- Called when buffer content is changed outside of current session
|
|
1239
|
+
-- Needed as otherwise `on_detach()` is called without later auto enabling
|
|
1240
|
+
on_reload = function()
|
|
1241
|
+
local buf_cache = H.cache[buf_id]
|
|
1242
|
+
if buf_cache == nil or buf_cache.root == nil then return end
|
|
1243
|
+
-- Don't update repo/root as it is tracked in 'BufFilePost' autocommand
|
|
1244
|
+
H.update_git_head(buf_cache.root, { buf_id })
|
|
1245
|
+
H.update_git_in_progress(buf_cache.repo, { buf_id })
|
|
1246
|
+
-- Don't update status as it is tracked in file watcher
|
|
1247
|
+
end,
|
|
1248
|
+
|
|
1249
|
+
-- Called when buffer is unloaded from memory (`:h nvim_buf_detach_event`),
|
|
1250
|
+
-- **including** `:edit` command. Together with auto enabling it makes
|
|
1251
|
+
-- `:edit` command serve as "restart".
|
|
1252
|
+
on_detach = function() MiniGit.disable(buf_id) end,
|
|
1253
|
+
})
|
|
1254
|
+
|
|
1255
|
+
local reset_if_enabled = vim.schedule_wrap(function(data)
|
|
1256
|
+
if not H.is_buf_enabled(data.buf) then return end
|
|
1257
|
+
MiniGit.disable(data.buf)
|
|
1258
|
+
MiniGit.enable(data.buf)
|
|
1259
|
+
end)
|
|
1260
|
+
local bufrename_opts = { group = augroup, buffer = buf_id, callback = reset_if_enabled, desc = 'Reset on rename' }
|
|
1261
|
+
-- NOTE: `BufFilePost` does not look like a proper event, but it (yet) works
|
|
1262
|
+
vim.api.nvim_create_autocmd('BufFilePost', bufrename_opts)
|
|
1263
|
+
|
|
1264
|
+
local buf_disable = function() MiniGit.disable(buf_id) end
|
|
1265
|
+
local bufdelete_opts = { group = augroup, buffer = buf_id, callback = buf_disable, desc = 'Disable on delete' }
|
|
1266
|
+
vim.api.nvim_create_autocmd('BufDelete', bufdelete_opts)
|
|
1267
|
+
end
|
|
1268
|
+
|
|
1269
|
+
-- Tracking -------------------------------------------------------------------
|
|
1270
|
+
H.start_tracking = function(buf_id, path)
|
|
1271
|
+
local command = H.git_cmd({ 'rev-parse', '--path-format=absolute', '--git-dir', '--show-toplevel' })
|
|
1272
|
+
|
|
1273
|
+
-- If path is not in Git, disable buffer but make sure that it will not try
|
|
1274
|
+
-- to re-attach until buffer is properly disabled
|
|
1275
|
+
local on_not_in_git = function()
|
|
1276
|
+
if H.is_buf_enabled(buf_id) then MiniGit.disable(buf_id) end
|
|
1277
|
+
H.cache[buf_id] = {}
|
|
1278
|
+
end
|
|
1279
|
+
|
|
1280
|
+
local on_done = vim.schedule_wrap(function(code, out, err)
|
|
1281
|
+
-- Watch git directory only if there was no error retrieving path to it
|
|
1282
|
+
if code ~= 0 then return on_not_in_git() end
|
|
1283
|
+
H.cli_err_notify(code, out, err)
|
|
1284
|
+
|
|
1285
|
+
-- Update buf data
|
|
1286
|
+
local repo, root = string.match(out, '^(.-)\n(.*)$')
|
|
1287
|
+
if repo == nil or root == nil then return H.notify('No initial data for buffer ' .. buf_id, 'WARN') end
|
|
1288
|
+
H.update_buf_data(buf_id, { repo = repo, root = root })
|
|
1289
|
+
|
|
1290
|
+
-- Set up repo watching to react to Git index changes
|
|
1291
|
+
H.setup_repo_watch(buf_id, repo)
|
|
1292
|
+
|
|
1293
|
+
-- Set up worktree watching to react to file changes
|
|
1294
|
+
H.setup_path_watch(buf_id)
|
|
1295
|
+
|
|
1296
|
+
-- Immediately update buffer tracking data
|
|
1297
|
+
H.update_git_head(root, { buf_id })
|
|
1298
|
+
H.update_git_in_progress(repo, { buf_id })
|
|
1299
|
+
H.update_git_status(root, { buf_id })
|
|
1300
|
+
end)
|
|
1301
|
+
|
|
1302
|
+
H.cli_run(command, vim.fn.fnamemodify(path, ':h'), on_done)
|
|
1303
|
+
end
|
|
1304
|
+
|
|
1305
|
+
H.setup_repo_watch = function(buf_id, repo)
|
|
1306
|
+
local repo_cache = H.repos[repo] or {}
|
|
1307
|
+
|
|
1308
|
+
-- Ensure repo is watched
|
|
1309
|
+
local is_set_up = repo_cache.fs_event ~= nil and repo_cache.fs_event:is_active()
|
|
1310
|
+
if not is_set_up then
|
|
1311
|
+
H.teardown_repo_watch(repo)
|
|
1312
|
+
local fs_event, timer = vim.loop.new_fs_event(), vim.loop.new_timer()
|
|
1313
|
+
|
|
1314
|
+
local on_change = vim.schedule_wrap(function() H.on_repo_change(repo) end)
|
|
1315
|
+
local watch = function(_, filename, _)
|
|
1316
|
+
-- Ignore temporary changes
|
|
1317
|
+
if vim.endswith(filename or '', 'lock') then return end
|
|
1318
|
+
|
|
1319
|
+
-- Debounce to not overload during incremental staging (like in script)
|
|
1320
|
+
timer:stop()
|
|
1321
|
+
timer:start(50, 0, on_change)
|
|
1322
|
+
end
|
|
1323
|
+
-- Watch only '.git' dir (non-recursively), as this seems to be both enough
|
|
1324
|
+
-- and not supported by libuv (`recursive` flag does nothing,
|
|
1325
|
+
-- see https://github.com/libuv/libuv/issues/1778)
|
|
1326
|
+
fs_event:start(repo, {}, watch)
|
|
1327
|
+
|
|
1328
|
+
repo_cache.fs_event, repo_cache.timer = fs_event, timer
|
|
1329
|
+
H.repos[repo] = repo_cache
|
|
1330
|
+
end
|
|
1331
|
+
|
|
1332
|
+
-- Register buffer to be updated on repo change
|
|
1333
|
+
local repo_buffers = repo_cache.buffers or {}
|
|
1334
|
+
repo_buffers[buf_id] = true
|
|
1335
|
+
repo_cache.buffers = repo_buffers
|
|
1336
|
+
end
|
|
1337
|
+
|
|
1338
|
+
H.teardown_repo_watch = function(repo)
|
|
1339
|
+
if H.repos[repo] == nil then return end
|
|
1340
|
+
pcall(vim.loop.fs_event_stop, H.repos[repo].fs_event)
|
|
1341
|
+
pcall(vim.loop.timer_stop, H.repos[repo].timer)
|
|
1342
|
+
end
|
|
1343
|
+
|
|
1344
|
+
H.setup_path_watch = function(buf_id, repo)
|
|
1345
|
+
if not H.is_buf_enabled(buf_id) then return end
|
|
1346
|
+
|
|
1347
|
+
local on_file_change = function(data) H.update_git_status(H.cache[buf_id].root, { buf_id }) end
|
|
1348
|
+
local opts =
|
|
1349
|
+
{ desc = 'Update Git status', group = H.cache[buf_id].augroup, buffer = buf_id, callback = on_file_change }
|
|
1350
|
+
vim.api.nvim_create_autocmd({ 'BufWritePost', 'FileChangedShellPost' }, opts)
|
|
1351
|
+
end
|
|
1352
|
+
|
|
1353
|
+
H.on_repo_change = function(repo)
|
|
1354
|
+
if H.repos[repo] == nil then return end
|
|
1355
|
+
|
|
1356
|
+
-- Collect repo's worktrees with their buffers while doing cleanup
|
|
1357
|
+
local repo_bufs, root_bufs = H.repos[repo].buffers, {}
|
|
1358
|
+
for buf_id, _ in pairs(repo_bufs) do
|
|
1359
|
+
if H.is_buf_enabled(buf_id) then
|
|
1360
|
+
local root = H.cache[buf_id].root
|
|
1361
|
+
local bufs = root_bufs[root] or {}
|
|
1362
|
+
table.insert(bufs, buf_id)
|
|
1363
|
+
root_bufs[root] = bufs
|
|
1364
|
+
else
|
|
1365
|
+
repo_bufs[buf_id] = nil
|
|
1366
|
+
MiniGit.disable(buf_id)
|
|
1367
|
+
end
|
|
1368
|
+
end
|
|
1369
|
+
|
|
1370
|
+
-- Update Git data
|
|
1371
|
+
H.update_git_in_progress(repo, vim.tbl_keys(repo_bufs))
|
|
1372
|
+
for root, bufs in pairs(root_bufs) do
|
|
1373
|
+
H.update_git_head(root, bufs)
|
|
1374
|
+
-- Status could have also changed as it depends on the index
|
|
1375
|
+
H.update_git_status(root, bufs)
|
|
1376
|
+
end
|
|
1377
|
+
end
|
|
1378
|
+
|
|
1379
|
+
H.update_git_head = function(root, bufs)
|
|
1380
|
+
local command = H.git_cmd({ 'rev-parse', 'HEAD', '--abbrev-ref', 'HEAD' })
|
|
1381
|
+
|
|
1382
|
+
local on_done = vim.schedule_wrap(function(code, out, err)
|
|
1383
|
+
-- Ensure proper data
|
|
1384
|
+
if code ~= 0 then return end
|
|
1385
|
+
H.cli_err_notify(code, out, err)
|
|
1386
|
+
|
|
1387
|
+
local head, head_name = string.match(out, '^(.-)\n(.*)$')
|
|
1388
|
+
if head == nil or head_name == nil then
|
|
1389
|
+
return H.notify('Could not parse HEAD data for root ' .. root .. '\n' .. out, 'WARN')
|
|
1390
|
+
end
|
|
1391
|
+
|
|
1392
|
+
-- Update data for all buffers from target `root`
|
|
1393
|
+
local new_data = { head = head, head_name = head_name }
|
|
1394
|
+
for _, buf_id in ipairs(bufs) do
|
|
1395
|
+
H.update_buf_data(buf_id, new_data)
|
|
1396
|
+
end
|
|
1397
|
+
|
|
1398
|
+
-- Redraw statusline to have possible statusline component up to date
|
|
1399
|
+
H.redrawstatus()
|
|
1400
|
+
end)
|
|
1401
|
+
|
|
1402
|
+
H.cli_run(command, root, on_done)
|
|
1403
|
+
end
|
|
1404
|
+
|
|
1405
|
+
H.update_git_in_progress = function(repo, bufs)
|
|
1406
|
+
-- Get data about what process is in progress
|
|
1407
|
+
local in_progress = {}
|
|
1408
|
+
if H.is_fs_present(repo .. '/BISECT_LOG') then table.insert(in_progress, 'bisect') end
|
|
1409
|
+
if H.is_fs_present(repo .. '/CHERRY_PICK_HEAD') then table.insert(in_progress, 'cherry-pick') end
|
|
1410
|
+
if H.is_fs_present(repo .. '/MERGE_HEAD') then table.insert(in_progress, 'merge') end
|
|
1411
|
+
if H.is_fs_present(repo .. '/REVERT_HEAD') then table.insert(in_progress, 'revert') end
|
|
1412
|
+
if H.is_fs_present(repo .. '/rebase-apply') then table.insert(in_progress, 'apply') end
|
|
1413
|
+
if H.is_fs_present(repo .. '/rebase-merge') then table.insert(in_progress, 'rebase') end
|
|
1414
|
+
|
|
1415
|
+
-- Update data for all buffers from target `root`
|
|
1416
|
+
local new_data = { in_progress = table.concat(in_progress, ',') }
|
|
1417
|
+
for _, buf_id in ipairs(bufs) do
|
|
1418
|
+
H.update_buf_data(buf_id, new_data)
|
|
1419
|
+
end
|
|
1420
|
+
|
|
1421
|
+
-- Redraw statusline to have possible statusline component up to date
|
|
1422
|
+
H.redrawstatus()
|
|
1423
|
+
end
|
|
1424
|
+
|
|
1425
|
+
H.update_git_status = function(root, bufs)
|
|
1426
|
+
--stylua: ignore
|
|
1427
|
+
local command = H.git_cmd({
|
|
1428
|
+
-- NOTE: Use `--no-optional-locks` to reduce conflicts with other Git tasks
|
|
1429
|
+
'--no-optional-locks', 'status',
|
|
1430
|
+
'--verbose', '--untracked-files=all', '--ignored', '--porcelain', '-z',
|
|
1431
|
+
'--',
|
|
1432
|
+
})
|
|
1433
|
+
local root_len, path_data = string.len(root), {}
|
|
1434
|
+
for _, buf_id in ipairs(bufs) do
|
|
1435
|
+
-- Use paths relative to the root as in `git status --porcelain` output
|
|
1436
|
+
local rel_path = H.get_buf_realpath(buf_id):sub(root_len + 2)
|
|
1437
|
+
table.insert(command, rel_path)
|
|
1438
|
+
-- Completely not modified paths should be the only ones missing in the
|
|
1439
|
+
-- output. Use this status as default.
|
|
1440
|
+
path_data[rel_path] = { status = ' ', buf_id = buf_id }
|
|
1441
|
+
end
|
|
1442
|
+
|
|
1443
|
+
local on_done = vim.schedule_wrap(function(code, out, err)
|
|
1444
|
+
if code ~= 0 then return end
|
|
1445
|
+
H.cli_err_notify(code, out, err)
|
|
1446
|
+
|
|
1447
|
+
-- Parse CLI output, which is separated by `\0` to not escape "bad" paths
|
|
1448
|
+
for _, l in ipairs(vim.split(out, '\0')) do
|
|
1449
|
+
local status, rel_path = string.match(l, '^(..) (.*)$')
|
|
1450
|
+
if path_data[rel_path] ~= nil then path_data[rel_path].status = status end
|
|
1451
|
+
end
|
|
1452
|
+
|
|
1453
|
+
-- Update data for all buffers
|
|
1454
|
+
for _, data in pairs(path_data) do
|
|
1455
|
+
local new_data = { status = data.status }
|
|
1456
|
+
H.update_buf_data(data.buf_id, new_data)
|
|
1457
|
+
end
|
|
1458
|
+
|
|
1459
|
+
-- Redraw statusline to have possible statusline component up to date
|
|
1460
|
+
H.redrawstatus()
|
|
1461
|
+
end)
|
|
1462
|
+
|
|
1463
|
+
H.cli_run(command, root, on_done)
|
|
1464
|
+
end
|
|
1465
|
+
|
|
1466
|
+
H.update_buf_data = function(buf_id, new_data)
|
|
1467
|
+
if not H.is_buf_enabled(buf_id) then return end
|
|
1468
|
+
|
|
1469
|
+
local summary = vim.b[buf_id].minigit_summary or {}
|
|
1470
|
+
for key, val in pairs(new_data) do
|
|
1471
|
+
H.cache[buf_id][key], summary[key] = val, val
|
|
1472
|
+
end
|
|
1473
|
+
vim.b[buf_id].minigit_summary = summary
|
|
1474
|
+
|
|
1475
|
+
-- Format summary string
|
|
1476
|
+
local head = summary.head_name or ''
|
|
1477
|
+
head = head == 'HEAD' and summary.head:sub(1, 7) or head
|
|
1478
|
+
|
|
1479
|
+
local in_progress = summary.in_progress or ''
|
|
1480
|
+
if in_progress ~= '' then head = head .. '|' .. in_progress end
|
|
1481
|
+
|
|
1482
|
+
local summary_string = head
|
|
1483
|
+
local status = summary.status or ''
|
|
1484
|
+
if status ~= ' ' and status ~= '' then summary_string = string.format('%s (%s)', head, status) end
|
|
1485
|
+
vim.b[buf_id].minigit_summary_string = summary_string
|
|
1486
|
+
|
|
1487
|
+
-- Trigger dedicated event with target current buffer (for proper `data.buf`)
|
|
1488
|
+
vim.api.nvim_buf_call(buf_id, function() H.trigger_event('MiniGitUpdated') end)
|
|
1489
|
+
end
|
|
1490
|
+
|
|
1491
|
+
-- History navigation ---------------------------------------------------------
|
|
1492
|
+
-- Assuming buffer contains unified combined diff (with "commit" header),
|
|
1493
|
+
-- compute path, line number, and commit of both "before" and "after" files.
|
|
1494
|
+
-- Allow cursor to be between "--- a/xxx" line and last line of a hunk.
|
|
1495
|
+
H.diff_pos_to_source = function()
|
|
1496
|
+
local lines, lnum = vim.api.nvim_buf_get_lines(0, 0, -1, false), vim.fn.line('.')
|
|
1497
|
+
|
|
1498
|
+
local res = { init_prefix = lines[lnum]:sub(1, 1) }
|
|
1499
|
+
local paths_lnum = H.diff_parse_paths(res, lines, lnum)
|
|
1500
|
+
local hunk_lnum = H.diff_parse_hunk(res, lines, lnum)
|
|
1501
|
+
local commit_lnum = H.diff_parse_commits(res, lines, lnum)
|
|
1502
|
+
|
|
1503
|
+
-- Try fall back to inferring target commits from 'mini.git' buffer name
|
|
1504
|
+
if res.commit_before == nil or res.commit_after == nil then H.diff_parse_bufname(res) end
|
|
1505
|
+
|
|
1506
|
+
local all_present = (res.lnum_before and res.path_before and res.commit_before)
|
|
1507
|
+
or (res.lnum_after and res.path_after and res.commit_after)
|
|
1508
|
+
local is_in_order = commit_lnum <= paths_lnum and paths_lnum <= hunk_lnum
|
|
1509
|
+
if not (all_present and is_in_order) then return nil end
|
|
1510
|
+
|
|
1511
|
+
return res
|
|
1512
|
+
end
|
|
1513
|
+
|
|
1514
|
+
H.diff_parse_paths = function(out, lines, lnum)
|
|
1515
|
+
-- NOTE: with `diff.mnemonicPrefix=true` source and destination prefixes can
|
|
1516
|
+
-- be not only `a`/`b`, but other characters or none (if added/deleted file)
|
|
1517
|
+
local pattern_before, pattern_after = '^%-%-%- ([acio]?)/(.*)$', '^%+%+%+ ([biw]?)/(.*)$'
|
|
1518
|
+
|
|
1519
|
+
-- Allow placing cursor directly on path defining lines
|
|
1520
|
+
local cur_line = lines[lnum]
|
|
1521
|
+
local prefix_before, path_before = string.match(cur_line, pattern_before)
|
|
1522
|
+
local prefix_after, path_after = string.match(cur_line, pattern_after)
|
|
1523
|
+
if path_before ~= nil then
|
|
1524
|
+
out.path_before = path_before
|
|
1525
|
+
prefix_after, out.path_after = string.match(lines[lnum + 1] or '', pattern_after)
|
|
1526
|
+
out.lnum_before, out.lnum_after = 1, 1
|
|
1527
|
+
elseif path_after ~= nil then
|
|
1528
|
+
prefix_before, out.path_before = string.match(lines[lnum - 1] or '', pattern_before)
|
|
1529
|
+
out.path_after = path_after
|
|
1530
|
+
out.lnum_before, out.lnum_after = 1, 1
|
|
1531
|
+
else
|
|
1532
|
+
-- Iterate lines upward to find path patterns
|
|
1533
|
+
while out.path_after == nil and lnum > 0 do
|
|
1534
|
+
prefix_after, out.path_after = string.match(lines[lnum] or '', pattern_after)
|
|
1535
|
+
lnum = lnum - 1
|
|
1536
|
+
end
|
|
1537
|
+
prefix_before, out.path_before = string.match(lines[lnum] or '', pattern_before)
|
|
1538
|
+
end
|
|
1539
|
+
|
|
1540
|
+
-- - Don't treat '--- /dev/null' and '+++ /dev/null' matches as paths
|
|
1541
|
+
-- Need to check prefix to work in cases like '--- a/dev/null'
|
|
1542
|
+
if prefix_before == '' then out.path_before = nil end
|
|
1543
|
+
if prefix_after == '' then out.path_after = nil end
|
|
1544
|
+
|
|
1545
|
+
return lnum
|
|
1546
|
+
end
|
|
1547
|
+
|
|
1548
|
+
H.diff_parse_hunk = function(out, lines, lnum)
|
|
1549
|
+
if out.lnum_after ~= nil then return lnum end
|
|
1550
|
+
|
|
1551
|
+
local offsets = { [' '] = 0, ['-'] = 0, ['+'] = 0 }
|
|
1552
|
+
while lnum > 0 do
|
|
1553
|
+
local prefix = lines[lnum]:sub(1, 1)
|
|
1554
|
+
if not (prefix == ' ' or prefix == '-' or prefix == '+') then break end
|
|
1555
|
+
offsets[prefix] = offsets[prefix] + 1
|
|
1556
|
+
lnum = lnum - 1
|
|
1557
|
+
end
|
|
1558
|
+
|
|
1559
|
+
local hunk_start_before, hunk_start_after = string.match(lines[lnum] or '', '^@@ %-(%d+),?%d* %+(%d+),?%d* @@')
|
|
1560
|
+
if hunk_start_before ~= nil then
|
|
1561
|
+
out.lnum_before = math.max(1, tonumber(hunk_start_before) + offsets[' '] + offsets['-'] - 1)
|
|
1562
|
+
out.lnum_after = math.max(1, tonumber(hunk_start_after) + offsets[' '] + offsets['+'] - 1)
|
|
1563
|
+
end
|
|
1564
|
+
return lnum
|
|
1565
|
+
end
|
|
1566
|
+
|
|
1567
|
+
H.diff_parse_commits = function(out, lines, lnum)
|
|
1568
|
+
while out.commit_after == nil and lnum > 0 do
|
|
1569
|
+
out.commit_after = string.match(lines[lnum], '^commit (%x+)$')
|
|
1570
|
+
lnum = lnum - 1
|
|
1571
|
+
end
|
|
1572
|
+
if out.commit_after ~= nil then out.commit_before = out.commit_after .. '~' end
|
|
1573
|
+
return lnum + 1
|
|
1574
|
+
end
|
|
1575
|
+
|
|
1576
|
+
H.diff_parse_bufname = function(out)
|
|
1577
|
+
local buf_name = vim.api.nvim_buf_get_name(0)
|
|
1578
|
+
local diff_command = string.match(buf_name, '^minigit://%d+/.* diff ?(.*)$')
|
|
1579
|
+
if diff_command == nil then return end
|
|
1580
|
+
|
|
1581
|
+
-- Work with output of common `:Git diff` commands
|
|
1582
|
+
diff_command = vim.trim(diff_command)
|
|
1583
|
+
-- `Git diff` - compares index and work tree
|
|
1584
|
+
if diff_command == '' then
|
|
1585
|
+
out.commit_before, out.commit_after = ':0', true
|
|
1586
|
+
end
|
|
1587
|
+
-- `Git diff --cached` - compares HEAD and index
|
|
1588
|
+
if diff_command == '--cached' then
|
|
1589
|
+
out.commit_before, out.commit_after = 'HEAD', ':0'
|
|
1590
|
+
end
|
|
1591
|
+
-- `Git diff HEAD` - compares commit and work tree
|
|
1592
|
+
if diff_command:find('^[^-]%S*$') ~= nil then
|
|
1593
|
+
out.commit_before, out.commit_after = diff_command, true
|
|
1594
|
+
end
|
|
1595
|
+
end
|
|
1596
|
+
|
|
1597
|
+
H.parse_diff_source_buf_name = function(buf_name) return string.match(buf_name, '^minigit://%d+/.*show (%x+~?):(.*)$') end
|
|
1598
|
+
|
|
1599
|
+
H.deps_pos_to_source = function()
|
|
1600
|
+
local lines = vim.api.nvim_buf_get_lines(0, 0, vim.fn.line('.'), false)
|
|
1601
|
+
-- Do nothing if on the title (otherwise it operates on previous plugin info)
|
|
1602
|
+
if lines[#lines]:find('^[%+%-!]') ~= nil then return end
|
|
1603
|
+
|
|
1604
|
+
-- Locate lines with commit and repo path data
|
|
1605
|
+
local commit, commit_lnum = nil, #lines
|
|
1606
|
+
while commit == nil and commit_lnum >= 1 do
|
|
1607
|
+
local l = lines[commit_lnum]
|
|
1608
|
+
commit = l:match('^[><] (%x%x%x%x%x%x%x%x*) |') or l:match('^State[^:]*: %s*(%x+)')
|
|
1609
|
+
commit_lnum = commit_lnum - 1
|
|
1610
|
+
end
|
|
1611
|
+
|
|
1612
|
+
local cwd, cwd_lnum = nil, #lines
|
|
1613
|
+
while cwd == nil and cwd_lnum >= 1 do
|
|
1614
|
+
cwd, cwd_lnum = lines[cwd_lnum]:match('^Path: %s*(%S+)$'), cwd_lnum - 1
|
|
1615
|
+
end
|
|
1616
|
+
|
|
1617
|
+
-- Do nothing if something is not found or path corresponds to next repo
|
|
1618
|
+
if commit == nil or cwd == nil or commit_lnum <= cwd_lnum then return end
|
|
1619
|
+
return commit, cwd
|
|
1620
|
+
end
|
|
1621
|
+
|
|
1622
|
+
-- Folding --------------------------------------------------------------------
|
|
1623
|
+
H.is_hunk_header = function(lnum) return vim.fn.getline(lnum):find('^@@.*@@') ~= nil end
|
|
1624
|
+
|
|
1625
|
+
H.is_log_entry_header = function(lnum) return vim.fn.getline(lnum):find('^commit ') ~= nil end
|
|
1626
|
+
|
|
1627
|
+
H.is_file_entry_header = function(lnum) return vim.fn.getline(lnum):find('^diff %-%-git') ~= nil end
|
|
1628
|
+
|
|
1629
|
+
-- CLI ------------------------------------------------------------------------
|
|
1630
|
+
H.git_cmd = function(args)
|
|
1631
|
+
-- Use '-c gc.auto=0' to disable `stderr` "Auto packing..." messages
|
|
1632
|
+
return { MiniGit.config.job.git_executable, '-c', 'gc.auto=0', unpack(args) }
|
|
1633
|
+
end
|
|
1634
|
+
|
|
1635
|
+
H.make_spawn_env = function(env_vars)
|
|
1636
|
+
-- Setup all environment variables (`vim.loop.spawn()` by default has none)
|
|
1637
|
+
local environ = vim.tbl_deep_extend('force', vim.loop.os_environ(), env_vars)
|
|
1638
|
+
local res = {}
|
|
1639
|
+
for k, v in pairs(environ) do
|
|
1640
|
+
table.insert(res, string.format('%s=%s', k, tostring(v)))
|
|
1641
|
+
end
|
|
1642
|
+
return res
|
|
1643
|
+
end
|
|
1644
|
+
|
|
1645
|
+
H.cli_run = function(command, cwd, on_done, opts)
|
|
1646
|
+
local spawn_opts = opts or {}
|
|
1647
|
+
local executable, args = command[1], vim.list_slice(command, 2, #command)
|
|
1648
|
+
local process, stdout, stderr = nil, vim.loop.new_pipe(), vim.loop.new_pipe()
|
|
1649
|
+
spawn_opts.args, spawn_opts.cwd, spawn_opts.stdio = args, cwd or vim.fn.getcwd(), { nil, stdout, stderr }
|
|
1650
|
+
|
|
1651
|
+
-- Allow `on_done = nil` to mean synchronous execution
|
|
1652
|
+
local is_sync, res = false, nil
|
|
1653
|
+
if on_done == nil then
|
|
1654
|
+
is_sync = true
|
|
1655
|
+
on_done = function(code, out, err) res = { code = code, out = out, err = err } end
|
|
1656
|
+
end
|
|
1657
|
+
|
|
1658
|
+
local out, err, is_done = {}, {}, false
|
|
1659
|
+
local on_exit = function(code)
|
|
1660
|
+
-- Ensure calling this only once
|
|
1661
|
+
if is_done then return end
|
|
1662
|
+
is_done = true
|
|
1663
|
+
|
|
1664
|
+
if process:is_closing() then return end
|
|
1665
|
+
process:close()
|
|
1666
|
+
|
|
1667
|
+
-- Convert to strings appropriate for notifications
|
|
1668
|
+
out = H.cli_stream_tostring(out)
|
|
1669
|
+
err = H.cli_stream_tostring(err):gsub('\r+', '\n'):gsub('\n%s+\n', '\n\n')
|
|
1670
|
+
on_done(code, out, err)
|
|
1671
|
+
end
|
|
1672
|
+
|
|
1673
|
+
process = vim.loop.spawn(executable, spawn_opts, on_exit)
|
|
1674
|
+
H.cli_read_stream(stdout, out)
|
|
1675
|
+
H.cli_read_stream(stderr, err)
|
|
1676
|
+
vim.defer_fn(function()
|
|
1677
|
+
if H.skip_timeout or not process:is_active() then return end
|
|
1678
|
+
H.notify('PROCESS REACHED TIMEOUT', 'WARN')
|
|
1679
|
+
on_exit(1)
|
|
1680
|
+
end, MiniGit.config.job.timeout)
|
|
1681
|
+
|
|
1682
|
+
if is_sync then vim.wait(MiniGit.config.job.timeout + 10, function() return is_done end, 1) end
|
|
1683
|
+
return res
|
|
1684
|
+
end
|
|
1685
|
+
|
|
1686
|
+
H.cli_read_stream = function(stream, feed)
|
|
1687
|
+
local callback = function(err, data)
|
|
1688
|
+
if err then return table.insert(feed, 1, 'ERROR: ' .. err) end
|
|
1689
|
+
if data ~= nil then return table.insert(feed, data) end
|
|
1690
|
+
stream:close()
|
|
1691
|
+
end
|
|
1692
|
+
stream:read_start(callback)
|
|
1693
|
+
end
|
|
1694
|
+
|
|
1695
|
+
H.cli_stream_tostring = function(stream) return (table.concat(stream):gsub('\n+$', '')) end
|
|
1696
|
+
|
|
1697
|
+
H.cli_err_notify = function(code, out, err)
|
|
1698
|
+
local should_stop = code ~= 0
|
|
1699
|
+
if should_stop then H.notify(err .. (out == '' and '' or ('\n' .. out)), 'ERROR') end
|
|
1700
|
+
if not should_stop and err ~= '' then H.notify(err, 'WARN') end
|
|
1701
|
+
return should_stop
|
|
1702
|
+
end
|
|
1703
|
+
|
|
1704
|
+
H.cli_escape = function(x) return (string.gsub(x, '([ \\])', '\\%1')) end
|
|
1705
|
+
|
|
1706
|
+
-- Utilities ------------------------------------------------------------------
|
|
1707
|
+
H.error = function(msg) error('(mini.git) ' .. msg, 0) end
|
|
1708
|
+
|
|
1709
|
+
H.check_type = function(name, val, ref, allow_nil)
|
|
1710
|
+
if type(val) == ref or (ref == 'callable' and vim.is_callable(val)) or (allow_nil and val == nil) then return end
|
|
1711
|
+
H.error(string.format('`%s` should be %s, not %s', name, ref, type(val)))
|
|
1712
|
+
end
|
|
1713
|
+
|
|
1714
|
+
H.set_buf_name = function(buf_id, name) vim.api.nvim_buf_set_name(buf_id, 'minigit://' .. buf_id .. '/' .. name) end
|
|
1715
|
+
|
|
1716
|
+
H.notify = function(msg, level_name) vim.notify('(mini.git) ' .. msg, vim.log.levels[level_name]) end
|
|
1717
|
+
|
|
1718
|
+
H.trigger_event = function(event_name, data) vim.api.nvim_exec_autocmds('User', { pattern = event_name, data = data }) end
|
|
1719
|
+
|
|
1720
|
+
H.is_fs_present = function(path) return vim.loop.fs_stat(path) ~= nil end
|
|
1721
|
+
|
|
1722
|
+
H.expandcmd = function(x)
|
|
1723
|
+
if x == '<cwd>' then return vim.fn.getcwd() end
|
|
1724
|
+
local ok, res = pcall(vim.fn.expandcmd, x)
|
|
1725
|
+
return ok and res or x
|
|
1726
|
+
end
|
|
1727
|
+
|
|
1728
|
+
-- TODO: Remove after compatibility with Neovim=0.9 is dropped
|
|
1729
|
+
H.islist = vim.fn.has('nvim-0.10') == 1 and vim.islist or vim.tbl_islist
|
|
1730
|
+
|
|
1731
|
+
-- Try getting buffer's full real path (after resolving symlinks)
|
|
1732
|
+
H.get_buf_realpath = function(buf_id) return vim.loop.fs_realpath(vim.api.nvim_buf_get_name(buf_id)) or '' end
|
|
1733
|
+
|
|
1734
|
+
H.redrawstatus = function() vim.cmd('redrawstatus') end
|
|
1735
|
+
if vim.api.nvim__redraw ~= nil then H.redrawstatus = function() vim.api.nvim__redraw({ statusline = true }) end end
|
|
1736
|
+
|
|
1737
|
+
return MiniGit
|