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,1938 @@
|
|
|
1
|
+
--- *mini.diff* Work with diff hunks
|
|
2
|
+
---
|
|
3
|
+
--- MIT License Copyright (c) 2024 Evgeni Chasnovski
|
|
4
|
+
|
|
5
|
+
--- Features:
|
|
6
|
+
---
|
|
7
|
+
--- - Visualize difference between buffer text and its configurable reference
|
|
8
|
+
--- interactively (updates as you type). This is done per line showing whether
|
|
9
|
+
--- it is inside added, changed, or deleted part of difference (called hunk).
|
|
10
|
+
--- Visualization can be with customizable colored signs or line numbers.
|
|
11
|
+
---
|
|
12
|
+
--- - Special toggleable overlay view with more hunk details inside text area.
|
|
13
|
+
--- See |MiniDiff.toggle_overlay()|.
|
|
14
|
+
---
|
|
15
|
+
--- - Completely configurable per buffer source(s) of reference text used to keep
|
|
16
|
+
--- it up to date and define interactions with it. Can be array of sources which
|
|
17
|
+
--- are attempted to attach in order. See |MiniDiff-source-specification|.
|
|
18
|
+
--- By default uses Git source. See |MiniDiff.gen_source.git()|.
|
|
19
|
+
---
|
|
20
|
+
--- - Configurable mappings to manage diff hunks:
|
|
21
|
+
--- - Apply and reset hunks inside region (selected visually or with
|
|
22
|
+
--- a dot-repeatable operator).
|
|
23
|
+
--- - "Hunk range under cursor" textobject to be used as operator target.
|
|
24
|
+
--- - Navigate to first/previous/next/last hunk. See |MiniDiff.goto_hunk()|.
|
|
25
|
+
---
|
|
26
|
+
--- What it doesn't do:
|
|
27
|
+
---
|
|
28
|
+
--- - Provide functionality to work directly with Git outside of visualizing
|
|
29
|
+
--- and staging (applying) hunks with (default) Git source. In particular,
|
|
30
|
+
--- unstaging hunks is not supported. See |MiniDiff.gen_source.git()|.
|
|
31
|
+
---
|
|
32
|
+
--- Sources with more details:
|
|
33
|
+
--- - |MiniDiff-overview|
|
|
34
|
+
--- - |MiniDiff-source-specification|
|
|
35
|
+
--- - |MiniDiff-hunk-specification|
|
|
36
|
+
--- - |MiniDiff-diff-summary|
|
|
37
|
+
---
|
|
38
|
+
--- # Setup ~
|
|
39
|
+
---
|
|
40
|
+
--- This module needs a setup with `require('mini.diff').setup({})` (replace
|
|
41
|
+
--- `{}` with your `config` table). It will create global Lua table `MiniDiff`
|
|
42
|
+
--- which you can use for scripting or manually (with `:lua MiniDiff.*`).
|
|
43
|
+
---
|
|
44
|
+
--- See |MiniDiff.config| for `config` structure and default values.
|
|
45
|
+
---
|
|
46
|
+
--- You can override runtime config settings locally to buffer inside
|
|
47
|
+
--- `vim.b.minidiff_config` which should have same structure as
|
|
48
|
+
--- `MiniDiff.config`. See |mini.nvim-buffer-local-config| for more details.
|
|
49
|
+
---
|
|
50
|
+
--- # Comparisons ~
|
|
51
|
+
---
|
|
52
|
+
--- - [lewis6991/gitsigns.nvim](https://github.com/lewis6991/gitsigns.nvim):
|
|
53
|
+
--- - Main inspiration for this module, so there are many similarities.
|
|
54
|
+
--- - Can display only Git hunks, while this module has extensible design.
|
|
55
|
+
--- - Provides more functionality to work with Git outside of hunks.
|
|
56
|
+
--- This module does not (by design).
|
|
57
|
+
---
|
|
58
|
+
--- # Highlight groups ~
|
|
59
|
+
--- *MiniDiff-hl-groups*
|
|
60
|
+
---
|
|
61
|
+
--- - `MiniDiffSignAdd` - "add" hunk lines visualization.
|
|
62
|
+
--- - `MiniDiffSignChange` - "change" hunk lines visualization.
|
|
63
|
+
--- - `MiniDiffSignDelete` - "delete" hunk lines visualization.
|
|
64
|
+
--- - `MiniDiffOverAdd` - added buffer text shown in overlay.
|
|
65
|
+
--- - `MiniDiffOverChange` - changed reference text shown in overlay.
|
|
66
|
+
--- - `MiniDiffOverChangeBuf` - changed buffer text shown in overlay.
|
|
67
|
+
--- - `MiniDiffOverContext` - context of a change shown in reference overlay.
|
|
68
|
+
--- - `MiniDiffOverContextBuf` - context of a change shown in buffer overlay.
|
|
69
|
+
--- - `MiniDiffOverDelete` - deleted reference text shown in overlay.
|
|
70
|
+
---
|
|
71
|
+
--- To change any highlight group, set it directly with |nvim_set_hl()|.
|
|
72
|
+
---
|
|
73
|
+
--- # Disabling ~
|
|
74
|
+
---
|
|
75
|
+
--- To temporarily disable features without relying on |MiniDiff.disable()|,
|
|
76
|
+
--- set `vim.g.minidiff_disable` (globally) or `vim.b.minidiff_disable` (for
|
|
77
|
+
--- a buffer) to `true`. Considering high number of different scenarios and
|
|
78
|
+
--- customization intentions, writing exact rules for disabling module's
|
|
79
|
+
--- functionality is left to user.
|
|
80
|
+
--- See |mini.nvim-disabling-recipes| for common recipes.
|
|
81
|
+
---@tag MiniDiff
|
|
82
|
+
|
|
83
|
+
--- # Diffs and hunks ~
|
|
84
|
+
---
|
|
85
|
+
--- The "diff" (short for "difference") is a result of computing how two text
|
|
86
|
+
--- strings differ from one another. This is done on per line basis, i.e. the
|
|
87
|
+
--- goal is to compute sequences of lines common to both files, interspersed
|
|
88
|
+
--- with groups of differing lines (called "hunks").
|
|
89
|
+
---
|
|
90
|
+
--- Although computing diff is a general concept (used on its own, in Git, etc.),
|
|
91
|
+
--- this module computes difference between current text in a buffer and some
|
|
92
|
+
--- reference text which is kept up to date specifically for that buffer.
|
|
93
|
+
--- For example, default reference text is computed as file content in Git index.
|
|
94
|
+
--- This can be customized in `config.source` (see |MiniDiff-source-specification|).
|
|
95
|
+
---
|
|
96
|
+
--- # Hunk specification ~
|
|
97
|
+
--- *MiniDiff-hunk-specification*
|
|
98
|
+
---
|
|
99
|
+
--- Hunk describes two sets (one from buffer text, one - from reference) of
|
|
100
|
+
--- consecutive lines which are different. In this module hunk is stored as
|
|
101
|
+
--- a table with the following fields:
|
|
102
|
+
---
|
|
103
|
+
--- - <buf_start> `(number)` - start of hunk buffer lines. First line is 1.
|
|
104
|
+
--- Can be 0 if first reference lines are deleted.
|
|
105
|
+
---
|
|
106
|
+
--- - <buf_count> `(number)` - number of consecutive buffer lines. Can be 0 in
|
|
107
|
+
--- case reference lines are deleted.
|
|
108
|
+
---
|
|
109
|
+
--- - <ref_start> `(number)` - start of hunk reference lines. First line is 1.
|
|
110
|
+
--- Can be 0 if lines are added before first reference line.
|
|
111
|
+
---
|
|
112
|
+
--- - <ref_count> `(number)` - number of consecutive reference lines. Can be 0 in
|
|
113
|
+
--- case buffer lines are added.
|
|
114
|
+
---
|
|
115
|
+
--- - <type> `(string)` - hunk type. Can be one of:
|
|
116
|
+
--- - "add" - lines are present in buffer but absent in reference.
|
|
117
|
+
--- - "change" - lines are present in both buffer and reference.
|
|
118
|
+
--- - "delete" - lines are absent in buffer but present in reference.
|
|
119
|
+
---
|
|
120
|
+
--- # Life cycle ~
|
|
121
|
+
---
|
|
122
|
+
--- - When entering proper (not already enabled, valid, showing text) buffer,
|
|
123
|
+
--- it is attempted to be enabled for diff processing.
|
|
124
|
+
--- - During enabling, attempt attaching the source. This should set up how
|
|
125
|
+
--- reference text is kept up to date.
|
|
126
|
+
--- - On every text change, diff computation is scheduled in debounced fashion
|
|
127
|
+
--- after customizable delay (200 ms by default).
|
|
128
|
+
--- - After the diff is computed, do the following:
|
|
129
|
+
--- - Update visualization based on configurable style: either by placing
|
|
130
|
+
--- colored text in sign column or coloring line numbers. Colors for both
|
|
131
|
+
--- styles are defined per hunk type in corresponding `MiniDiffSign*`
|
|
132
|
+
--- highlight group (see |mini.diff|) and sign text for "sign" style can
|
|
133
|
+
--- be configured in `view.signs` of |MiniDiff.config|.
|
|
134
|
+
--- - Update overlay view (if it is enabled).
|
|
135
|
+
--- - Update `vim.b.minidiff_summary` and `vim.b.minidiff_summary_string`
|
|
136
|
+
--- buffer-local variables. These can be used, for example, in statusline.
|
|
137
|
+
--- - *MiniDiff-update-event* Trigger `MiniDiffUpdated` `User` event.
|
|
138
|
+
--- See |MiniDiff-diff-summary| for example of how to use it.
|
|
139
|
+
---
|
|
140
|
+
--- Notes:
|
|
141
|
+
--- - Use |:edit| to reset (disable and re-enable) current buffer.
|
|
142
|
+
--- - To work with BOM bytes, set |'bomb'| and have `ucs-bom` in |'fileencodings'|.
|
|
143
|
+
---
|
|
144
|
+
--- # Overlay ~
|
|
145
|
+
---
|
|
146
|
+
--- Along with basic visualization, there is a special view called "overlay".
|
|
147
|
+
--- Although it is meant for temporary overview of diff details and can be
|
|
148
|
+
--- manually toggled via |MiniDiff.toggle_overlay()|, text can be changed with
|
|
149
|
+
--- overlay reacting accordingly.
|
|
150
|
+
---
|
|
151
|
+
--- It shows more diff details inside text area:
|
|
152
|
+
---
|
|
153
|
+
--- - Added buffer lines are highlighted with `MiniDiffOverAdd` highlight group.
|
|
154
|
+
---
|
|
155
|
+
--- - Deleted reference lines are shown as virtual lines and highlighted with
|
|
156
|
+
--- `MiniDiffOverDelete` highlight group.
|
|
157
|
+
---
|
|
158
|
+
--- - "Change" hunks with equal number of buffer/reference lines show "word diff".
|
|
159
|
+
--- This is usually the case when `options.linematch` is enabled (as by default).
|
|
160
|
+
--- Reference line is shown next to its buffer counterpart. Changed parts are
|
|
161
|
+
--- highlighted with `MiniDiffOverChange` and `MiniDiffOverChangeBuf` in reference
|
|
162
|
+
--- and buffer lines. The rest of lines have `MiniDiffOverContext`
|
|
163
|
+
--- and `MiniDiffOverContextBuf` highlighting.
|
|
164
|
+
---
|
|
165
|
+
--- Change with unequal number of buffer/reference lines is shown with reference
|
|
166
|
+
--- part as virtual lines highlighted with `MiniDiffOverChange` group.
|
|
167
|
+
--- Corresponding buffer lines are treated as context for the change and are
|
|
168
|
+
--- highlighted with `MiniDiffOverContextBuf` group.
|
|
169
|
+
---
|
|
170
|
+
--- Notes:
|
|
171
|
+
--- - Word diff has non-zero context width. This means if changed characters
|
|
172
|
+
--- are close enough, whole range between them is also colored. This usually
|
|
173
|
+
--- reduces visual noise.
|
|
174
|
+
--- - Virtual lines above line 1 (like deleted or changed lines) need manual
|
|
175
|
+
--- scroll to become visible (with |CTRL-Y|).
|
|
176
|
+
---
|
|
177
|
+
--- # Mappings ~
|
|
178
|
+
---
|
|
179
|
+
--- This module provides mappings for common actions with diffs, like:
|
|
180
|
+
--- - Apply and reset hunks.
|
|
181
|
+
--- - "Hunk range under cursor" textobject.
|
|
182
|
+
--- - Go to first/previous/next/last hunk range.
|
|
183
|
+
---
|
|
184
|
+
--- Examples:
|
|
185
|
+
--- - `vip` followed by `gh` / `gH` applies/resets hunks inside current paragraph.
|
|
186
|
+
--- Same can be achieved in operator form `ghip` / `gHip`, which has the
|
|
187
|
+
--- advantage of being dot-repeatable (see |single-repeat|).
|
|
188
|
+
--- - `gh_` / `gH_` applies/resets current line (even if it is not a full hunk).
|
|
189
|
+
--- - `ghgh` / `gHgh` applies/resets hunk range under cursor.
|
|
190
|
+
--- - `dgh` deletes hunk range under cursor.
|
|
191
|
+
--- - `[H` / `[h` / `]h` / `]H` navigate cursor to the first / previous / next / last
|
|
192
|
+
--- hunk range of the current buffer.
|
|
193
|
+
---
|
|
194
|
+
--- Mappings for some functionality are assumed to be done manually.
|
|
195
|
+
--- See |MiniDiff.operator()|.
|
|
196
|
+
---
|
|
197
|
+
--- # Buffer-local variables ~
|
|
198
|
+
--- *MiniDiff-diff-summary*
|
|
199
|
+
---
|
|
200
|
+
--- Each enabled buffer has the following buffer-local variables which can be
|
|
201
|
+
--- used in custom statusline to show an overview of hunks in current buffer:
|
|
202
|
+
---
|
|
203
|
+
--- - `vim.b.minidiff_summary` is a table with the following fields:
|
|
204
|
+
--- - `source_name` - name of the active source. This is the only present field
|
|
205
|
+
--- if buffer's reference text is not (yet) set.
|
|
206
|
+
--- - `n_ranges` - number of hunk ranges (sequences of contiguous hunks).
|
|
207
|
+
--- - `add` - number of added lines.
|
|
208
|
+
--- - `change` - number of changed lines.
|
|
209
|
+
--- - `delete` - number of deleted lines.
|
|
210
|
+
---
|
|
211
|
+
--- - `vim.b.minidiff_summary_string` is a string representation of summary
|
|
212
|
+
--- with a fixed format. Empty string if there is no reference text (yet).
|
|
213
|
+
--- It is expected to be used as is. To achieve different formatting, use
|
|
214
|
+
--- `vim.b.minidiff_summary` to construct one. The best way to do this is by
|
|
215
|
+
--- overriding `vim.b.minidiff_summary_string` inside |MiniDiff-update-event|: >lua
|
|
216
|
+
---
|
|
217
|
+
--- local format_summary = function(data)
|
|
218
|
+
--- local summary = vim.b[data.buf].minidiff_summary
|
|
219
|
+
--- local t = {}
|
|
220
|
+
--- if summary.add > 0 then table.insert(t, '+' .. summary.add) end
|
|
221
|
+
--- if summary.change > 0 then table.insert(t, '~' .. summary.change) end
|
|
222
|
+
--- if summary.delete > 0 then table.insert(t, '-' .. summary.delete) end
|
|
223
|
+
--- vim.b[data.buf].minidiff_summary_string = table.concat(t, ' ')
|
|
224
|
+
--- end
|
|
225
|
+
--- local au_opts = { pattern = 'MiniDiffUpdated', callback = format_summary }
|
|
226
|
+
--- vim.api.nvim_create_autocmd('User', au_opts)
|
|
227
|
+
--- <
|
|
228
|
+
---@tag MiniDiff-overview
|
|
229
|
+
|
|
230
|
+
---@alias __diff_buf_id number Target buffer identifier. Default: 0 for current buffer.
|
|
231
|
+
|
|
232
|
+
---@diagnostic disable:undefined-field
|
|
233
|
+
---@diagnostic disable:discard-returns
|
|
234
|
+
---@diagnostic disable:unused-local
|
|
235
|
+
---@diagnostic disable:cast-local-type
|
|
236
|
+
---@diagnostic disable:undefined-doc-name
|
|
237
|
+
---@diagnostic disable:luadoc-miss-type-name
|
|
238
|
+
|
|
239
|
+
-- Module definition ==========================================================
|
|
240
|
+
local MiniDiff = {}
|
|
241
|
+
local H = {}
|
|
242
|
+
|
|
243
|
+
--- Module setup
|
|
244
|
+
---
|
|
245
|
+
---@param config table|nil Module config table. See |MiniDiff.config|.
|
|
246
|
+
---
|
|
247
|
+
---@usage >lua
|
|
248
|
+
--- require('mini.diff').setup() -- use default config
|
|
249
|
+
--- -- OR
|
|
250
|
+
--- require('mini.diff').setup({}) -- replace {} with your config table
|
|
251
|
+
--- <
|
|
252
|
+
MiniDiff.setup = function(config)
|
|
253
|
+
-- TODO: Remove after Neovim=0.9 support is dropped
|
|
254
|
+
if vim.fn.has('nvim-0.10') == 0 then
|
|
255
|
+
vim.notify(
|
|
256
|
+
'(mini.diff) Neovim<0.10 is soft deprecated (module works but is not supported).'
|
|
257
|
+
.. " It will be deprecated after the next 'mini.nvim' release (module might not work)."
|
|
258
|
+
.. ' Please update your Neovim version.'
|
|
259
|
+
)
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
-- Export module
|
|
263
|
+
_G.MiniDiff = MiniDiff
|
|
264
|
+
|
|
265
|
+
-- Setup config
|
|
266
|
+
config = H.setup_config(config)
|
|
267
|
+
|
|
268
|
+
-- Apply config
|
|
269
|
+
H.apply_config(config)
|
|
270
|
+
|
|
271
|
+
-- Define behavior
|
|
272
|
+
H.create_autocommands()
|
|
273
|
+
for _, buf_id in ipairs(vim.api.nvim_list_bufs()) do
|
|
274
|
+
H.auto_enable({ buf = buf_id })
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
-- Create default highlighting
|
|
278
|
+
H.create_default_hl()
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
--stylua: ignore
|
|
282
|
+
--- Defaults ~
|
|
283
|
+
---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section)
|
|
284
|
+
---@text # View ~
|
|
285
|
+
---
|
|
286
|
+
--- `config.view` contains settings for how diff hunks are visualized.
|
|
287
|
+
--- Example of using custom signs: >lua
|
|
288
|
+
---
|
|
289
|
+
--- require('mini.diff').setup({
|
|
290
|
+
--- view = {
|
|
291
|
+
--- style = 'sign',
|
|
292
|
+
--- signs = { add = '+', change = '~', delete = '-' },
|
|
293
|
+
--- },
|
|
294
|
+
--- })
|
|
295
|
+
--- <
|
|
296
|
+
--- `view.style` is a string defining visualization style. Can be one of "sign"
|
|
297
|
+
--- (as a colored sign in a |sign-column|) or "number" (colored line number).
|
|
298
|
+
--- Default: "number" if |'number'| option is enabled, "sign" otherwise.
|
|
299
|
+
--- Note: with "sign" style it is better to have |'signcolumn'| always shown.
|
|
300
|
+
---
|
|
301
|
+
--- `view.signs` is a table with one or two character strings used as signs for
|
|
302
|
+
--- corresponding ("add", "change", "delete") hunks.
|
|
303
|
+
--- Default: all hunks use "▒" character resulting in a contiguous colored lines.
|
|
304
|
+
---
|
|
305
|
+
--- `view.priority` is a number with priority used for visualization and
|
|
306
|
+
--- overlay |extmarks|.
|
|
307
|
+
--- Default: 199 which is one less than `user` in |vim.hl.priorities| (on Neovim<0.11
|
|
308
|
+
--- see |vim.hl.priorities|) to have higher priority than automated
|
|
309
|
+
--- extmarks but not as in user enabled ones.
|
|
310
|
+
---
|
|
311
|
+
--- # Source ~
|
|
312
|
+
--- *MiniDiff-source-specification*
|
|
313
|
+
---
|
|
314
|
+
--- `config.source` is a table with single source or array of them. Single source
|
|
315
|
+
--- defines how reference text is managed in a particular buffer. Sources in array
|
|
316
|
+
--- are attempted to attach in order; call |MiniDiff.disable()| if none attaches.
|
|
317
|
+
---
|
|
318
|
+
--- A single source table can have the following fields:
|
|
319
|
+
---
|
|
320
|
+
--- - <attach> `(function)` - callable which defines how and when reference text
|
|
321
|
+
--- is updated inside a particular buffer. It is used inside |MiniDiff.enable()|
|
|
322
|
+
--- with a buffer identifier as a single argument.
|
|
323
|
+
---
|
|
324
|
+
--- Should execute logic which results into calling |MiniDiff.set_ref_text()|
|
|
325
|
+
--- when reference text for buffer needs to be updated. Like inside callback
|
|
326
|
+
--- for an |autocommand| or file watcher (see |watch-file|).
|
|
327
|
+
---
|
|
328
|
+
--- For example, default Git source watches when ".git/index" file is changed
|
|
329
|
+
--- and computes reference text as the one from Git index for current file.
|
|
330
|
+
---
|
|
331
|
+
--- Can return `false` to indicate that attach has failed. If attach fail can
|
|
332
|
+
--- not be inferred immediately (for example, due to asynchronous execution),
|
|
333
|
+
--- should explicitly call |MiniDiff.fail_attach()| with appropriate arguments.
|
|
334
|
+
--- This is important to properly process array of sources.
|
|
335
|
+
---
|
|
336
|
+
--- No default value, should be always supplied.
|
|
337
|
+
---
|
|
338
|
+
--- - <name> `(string|nil)` - source name. String `"unknown"` is used if not supplied.
|
|
339
|
+
---
|
|
340
|
+
--- - <detach> `(function|nil)` - callable with cleanup action to be done when
|
|
341
|
+
--- buffer is disabled. It is called inside |MiniDiff.disable()| with a buffer
|
|
342
|
+
--- identifier as a single argument.
|
|
343
|
+
---
|
|
344
|
+
--- If not supplied, nothing is done during detaching.
|
|
345
|
+
---
|
|
346
|
+
--- - <apply_hunks> `(function|nil)` - callable which defines how hunks are applied.
|
|
347
|
+
--- It is called with buffer identifier as first argument and array of hunks
|
|
348
|
+
--- (see |MiniDiff-hunk-specification|) as second. It should eventually update
|
|
349
|
+
--- reference text: either by explicitly calling |MiniDiff.set_ref_text()| or
|
|
350
|
+
--- performing action triggering its call.
|
|
351
|
+
---
|
|
352
|
+
--- For example, default Git source computes patch based on the hunks and
|
|
353
|
+
--- applies it inside file's git repo.
|
|
354
|
+
---
|
|
355
|
+
--- If not supplied, applying hunks throws an error.
|
|
356
|
+
---
|
|
357
|
+
--- Default: a single |MiniDiff.gen_source.git()|.
|
|
358
|
+
---
|
|
359
|
+
--- # Delay ~
|
|
360
|
+
---
|
|
361
|
+
--- `config.delay` contains settings for delays in asynchronous processes.
|
|
362
|
+
---
|
|
363
|
+
--- `delay.text_change` is a number (in ms) defining how long to wait after latest
|
|
364
|
+
--- text change (in debounced fashion) before updating diff and visualization.
|
|
365
|
+
--- Default: 200.
|
|
366
|
+
---
|
|
367
|
+
--- # Mappings ~
|
|
368
|
+
---
|
|
369
|
+
--- `config.mappings` contains keys which are mapped during |MiniDiff.setup()|.
|
|
370
|
+
---
|
|
371
|
+
--- `mappings.apply` keys can be used to apply hunks inside visual/operator region.
|
|
372
|
+
--- What exactly "apply hunks" means depends on the source and its `apply_hunks()`.
|
|
373
|
+
--- For example, in default Git source it means stage hunks.
|
|
374
|
+
---
|
|
375
|
+
--- `mappings.reset` keys can be used to reset hunks inside visual/operator region.
|
|
376
|
+
--- Reset means replacing buffer text in region with corresponding reference text.
|
|
377
|
+
---
|
|
378
|
+
--- `mappings.textobject` keys define "hunk range under cursor" textobject
|
|
379
|
+
--- which can be used in Operator-pending mode as target for operator (like
|
|
380
|
+
--- |d|, |y|, apply/reset hunks, etc.). It is also set up in Visual mode if
|
|
381
|
+
--- keys do not conflict with `mappings.apply` and `mappings.reset`.
|
|
382
|
+
--- "Hunk range" is used in a sense that contiguous (back-to-back) hunks are
|
|
383
|
+
--- considered as parts of a same hunk range.
|
|
384
|
+
---
|
|
385
|
+
--- `mappings.goto_first` / `mappings.goto_prev` / `mappings.goto_next` /
|
|
386
|
+
--- `mappings.goto_last` keys can be used to navigate to first / previous / next /
|
|
387
|
+
--- last hunk range in the current buffer.
|
|
388
|
+
---
|
|
389
|
+
--- # Options ~
|
|
390
|
+
---
|
|
391
|
+
--- `config.options` contains various customization options.
|
|
392
|
+
---
|
|
393
|
+
--- `options.algorithm` is a string defining which diff algorithm to use.
|
|
394
|
+
--- Default: "histogram". See |vim.text.diff()| (|vim.diff()| on Neovim<=0.11)
|
|
395
|
+
--- for possible values.
|
|
396
|
+
---
|
|
397
|
+
--- `options.indent_heuristic` is a boolean defining whether to use indent
|
|
398
|
+
--- heuristic for a (possibly) more naturally aligned hunks.
|
|
399
|
+
--- Default: `true`.
|
|
400
|
+
---
|
|
401
|
+
--- `options.linematch` is a number defining hunk size for which a second
|
|
402
|
+
--- stage diff is executed for a better aligned and more granular hunks.
|
|
403
|
+
--- Default: 60. See |vim.text.diff()| (|vim.diff()| on Neovim<=0.11) and |'diffopt'|
|
|
404
|
+
--- for more details.
|
|
405
|
+
---
|
|
406
|
+
--- `options.wrap_goto` is a boolean indicating whether to wrap around edges during
|
|
407
|
+
--- hunk navigation (with |MiniDiff.goto_hunk()| or `goto_*` mappings). Like if
|
|
408
|
+
--- cursor is after the last hunk, going "next" will put cursor on the first hunk.
|
|
409
|
+
--- Default: `false`.
|
|
410
|
+
MiniDiff.config = {
|
|
411
|
+
-- Options for how hunks are visualized
|
|
412
|
+
view = {
|
|
413
|
+
-- Visualization style. Possible values are 'sign' and 'number'.
|
|
414
|
+
-- Default: 'number' if line numbers are enabled, 'sign' otherwise.
|
|
415
|
+
style = vim.go.number and 'number' or 'sign',
|
|
416
|
+
|
|
417
|
+
-- Signs used for hunks with 'sign' view
|
|
418
|
+
signs = { add = '▒', change = '▒', delete = '▒' },
|
|
419
|
+
|
|
420
|
+
-- Priority of used visualization extmarks
|
|
421
|
+
priority = 199,
|
|
422
|
+
},
|
|
423
|
+
|
|
424
|
+
-- Source(s) for how reference text is computed/updated/etc
|
|
425
|
+
-- Uses content from Git index by default
|
|
426
|
+
source = nil,
|
|
427
|
+
|
|
428
|
+
-- Delays (in ms) defining asynchronous processes
|
|
429
|
+
delay = {
|
|
430
|
+
-- How much to wait before update following every text change
|
|
431
|
+
text_change = 200,
|
|
432
|
+
},
|
|
433
|
+
|
|
434
|
+
-- Module mappings. Use `''` (empty string) to disable one.
|
|
435
|
+
mappings = {
|
|
436
|
+
-- Apply hunks inside a visual/operator region
|
|
437
|
+
apply = 'gh',
|
|
438
|
+
|
|
439
|
+
-- Reset hunks inside a visual/operator region
|
|
440
|
+
reset = 'gH',
|
|
441
|
+
|
|
442
|
+
-- Hunk range textobject to be used inside operator
|
|
443
|
+
-- Works also in Visual mode if mapping differs from apply and reset
|
|
444
|
+
textobject = 'gh',
|
|
445
|
+
|
|
446
|
+
-- Go to hunk range in corresponding direction
|
|
447
|
+
goto_first = '[H',
|
|
448
|
+
goto_prev = '[h',
|
|
449
|
+
goto_next = ']h',
|
|
450
|
+
goto_last = ']H',
|
|
451
|
+
},
|
|
452
|
+
|
|
453
|
+
-- Various options
|
|
454
|
+
options = {
|
|
455
|
+
-- Diff algorithm (see `:h vim.text.diff()`)
|
|
456
|
+
algorithm = 'histogram',
|
|
457
|
+
|
|
458
|
+
-- Whether to use "indent heuristic" (see `:h vim.text.diff()`)
|
|
459
|
+
indent_heuristic = true,
|
|
460
|
+
|
|
461
|
+
-- The amount of second-stage diff to align lines
|
|
462
|
+
linematch = 60,
|
|
463
|
+
|
|
464
|
+
-- Whether to wrap around edges during hunk navigation
|
|
465
|
+
wrap_goto = false,
|
|
466
|
+
},
|
|
467
|
+
}
|
|
468
|
+
--minidoc_afterlines_end
|
|
469
|
+
|
|
470
|
+
--- Enable diff processing in buffer
|
|
471
|
+
---
|
|
472
|
+
---@param buf_id __diff_buf_id
|
|
473
|
+
MiniDiff.enable = function(buf_id)
|
|
474
|
+
buf_id = H.validate_buf_id(buf_id)
|
|
475
|
+
|
|
476
|
+
-- Don't enable more than once
|
|
477
|
+
if H.is_buf_enabled(buf_id) or H.is_disabled(buf_id) then return end
|
|
478
|
+
|
|
479
|
+
-- Ensure buffer is loaded (to have up to date lines returned)
|
|
480
|
+
H.buf_ensure_loaded(buf_id)
|
|
481
|
+
|
|
482
|
+
-- Register enabled buffer with cached data for performance
|
|
483
|
+
H.update_buf_cache(buf_id)
|
|
484
|
+
|
|
485
|
+
-- Add buffer watchers
|
|
486
|
+
vim.api.nvim_buf_attach(buf_id, false, {
|
|
487
|
+
-- Called on every text change (`:h nvim_buf_lines_event`)
|
|
488
|
+
on_lines = function(_, _, _, from_line, _, to_line)
|
|
489
|
+
local buf_cache = H.cache[buf_id]
|
|
490
|
+
-- Properly detach if diffing is disabled
|
|
491
|
+
if buf_cache == nil then return true end
|
|
492
|
+
H.schedule_diff_update(buf_id, buf_cache.config.delay.text_change)
|
|
493
|
+
end,
|
|
494
|
+
|
|
495
|
+
-- Called when buffer content is changed outside of current session
|
|
496
|
+
on_reload = function() H.schedule_diff_update(buf_id, 0) end,
|
|
497
|
+
|
|
498
|
+
-- Called when buffer is unloaded from memory (`:h nvim_buf_detach_event`),
|
|
499
|
+
-- **including** `:edit` command
|
|
500
|
+
on_detach = function() MiniDiff.disable(buf_id) end,
|
|
501
|
+
})
|
|
502
|
+
|
|
503
|
+
-- Add buffer autocommands
|
|
504
|
+
H.setup_buf_autocommands(buf_id)
|
|
505
|
+
|
|
506
|
+
-- Try attaching source after all necessary watchers are set up. It is needed
|
|
507
|
+
-- to still have them set up if first source of many returned `false`.
|
|
508
|
+
local attach_output = H.get_active_source(H.cache[buf_id]).attach(buf_id)
|
|
509
|
+
if attach_output == false then MiniDiff.fail_attach(buf_id) end
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
--- Disable diff processing in buffer
|
|
513
|
+
---
|
|
514
|
+
---@param buf_id __diff_buf_id
|
|
515
|
+
MiniDiff.disable = function(buf_id)
|
|
516
|
+
buf_id = H.validate_buf_id(buf_id)
|
|
517
|
+
|
|
518
|
+
local buf_cache = H.cache[buf_id]
|
|
519
|
+
if buf_cache == nil then return end
|
|
520
|
+
H.cache[buf_id] = nil
|
|
521
|
+
|
|
522
|
+
pcall(vim.api.nvim_del_augroup_by_id, buf_cache.augroup)
|
|
523
|
+
vim.b[buf_id].minidiff_summary, vim.b[buf_id].minidiff_summary_string = nil, nil
|
|
524
|
+
H.clear_all_diff(buf_id)
|
|
525
|
+
pcall(H.get_active_source(buf_cache).detach, buf_id)
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
--- Toggle diff processing in buffer
|
|
529
|
+
---
|
|
530
|
+
--- Enable if disabled, disable if enabled.
|
|
531
|
+
---
|
|
532
|
+
---@param buf_id __diff_buf_id
|
|
533
|
+
MiniDiff.toggle = function(buf_id)
|
|
534
|
+
buf_id = H.validate_buf_id(buf_id)
|
|
535
|
+
if H.is_buf_enabled(buf_id) then return MiniDiff.disable(buf_id) end
|
|
536
|
+
return MiniDiff.enable(buf_id)
|
|
537
|
+
end
|
|
538
|
+
|
|
539
|
+
--- Toggle overlay view in buffer
|
|
540
|
+
---
|
|
541
|
+
---@param buf_id __diff_buf_id
|
|
542
|
+
MiniDiff.toggle_overlay = function(buf_id)
|
|
543
|
+
buf_id = H.validate_buf_id(buf_id)
|
|
544
|
+
local buf_cache = H.cache[buf_id]
|
|
545
|
+
if buf_cache == nil then H.error(string.format('Buffer %d is not enabled.', buf_id)) end
|
|
546
|
+
|
|
547
|
+
buf_cache.overlay = not buf_cache.overlay
|
|
548
|
+
H.clear_all_diff(buf_id)
|
|
549
|
+
H.schedule_diff_update(buf_id, 0)
|
|
550
|
+
end
|
|
551
|
+
|
|
552
|
+
--- Export hunks
|
|
553
|
+
---
|
|
554
|
+
--- Get and convert hunks from current/all buffers. Example of using it: >lua
|
|
555
|
+
---
|
|
556
|
+
--- -- Set quickfix list from all available hunks
|
|
557
|
+
--- vim.fn.setqflist(MiniDiff.export('qf'))
|
|
558
|
+
--- <
|
|
559
|
+
---@param format string Output format. Currently only `'qf'` value is supported.
|
|
560
|
+
---@param opts table|nil Options. Possible fields:
|
|
561
|
+
--- - <scope> `(string)` - scope defining from which buffers to use hunks.
|
|
562
|
+
--- One of "all" (all enabled buffers) or "current".
|
|
563
|
+
---
|
|
564
|
+
---@return table Result of export. Depends on the `format`:
|
|
565
|
+
--- - If "qf", an array compatible with |setqflist()| and |setloclist()|.
|
|
566
|
+
MiniDiff.export = function(format, opts)
|
|
567
|
+
opts = vim.tbl_deep_extend('force', { scope = 'all' }, opts or {})
|
|
568
|
+
if format == 'qf' then return H.export_qf(opts) end
|
|
569
|
+
H.error('`format` should be one of "qf".')
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
--- Get buffer data
|
|
573
|
+
---
|
|
574
|
+
---@param buf_id __diff_buf_id
|
|
575
|
+
---
|
|
576
|
+
---@return table|nil Table with buffer diff data or `nil` if buffer is not enabled.
|
|
577
|
+
--- Table has the following fields:
|
|
578
|
+
--- - <config> `(table)` - config used for this particular buffer.
|
|
579
|
+
--- - <hunks> `(table)` - array of hunks. See |MiniDiff-hunk-specification|.
|
|
580
|
+
--- - <overlay> `(boolean)` - whether an overlay view is shown.
|
|
581
|
+
--- - <ref_text> `(string|nil)` - current value of reference text. Lines are
|
|
582
|
+
--- separated with newline character (`'\n'`). Can be `nil` indicating that
|
|
583
|
+
--- reference text was not yet set (for example, if source did not yet react).
|
|
584
|
+
--- - <summary> `(table)` - overall diff summary. See |MiniDiff-diff-summary|.
|
|
585
|
+
MiniDiff.get_buf_data = function(buf_id)
|
|
586
|
+
buf_id = H.validate_buf_id(buf_id)
|
|
587
|
+
local buf_cache = H.cache[buf_id]
|
|
588
|
+
if buf_cache == nil then return nil end
|
|
589
|
+
return vim.deepcopy({
|
|
590
|
+
config = buf_cache.config,
|
|
591
|
+
hunks = buf_cache.hunks,
|
|
592
|
+
overlay = buf_cache.overlay,
|
|
593
|
+
ref_text = buf_cache.ref_text,
|
|
594
|
+
summary = buf_cache.summary,
|
|
595
|
+
})
|
|
596
|
+
end
|
|
597
|
+
|
|
598
|
+
--- Set reference text for the buffer
|
|
599
|
+
---
|
|
600
|
+
--- Note: this will call |MiniDiff.enable()| for target buffer if it is not
|
|
601
|
+
--- already enabled.
|
|
602
|
+
---
|
|
603
|
+
---@param buf_id __diff_buf_id
|
|
604
|
+
---@param text string|table New reference text. Either a string with `\n` used to
|
|
605
|
+
--- separate lines or array of lines. Use empty table to unset current
|
|
606
|
+
--- reference text (results into no hunks shown). Default: `{}`.
|
|
607
|
+
--- Note: newline character is appended at the end (if it is not there already)
|
|
608
|
+
--- for better diffs.
|
|
609
|
+
MiniDiff.set_ref_text = function(buf_id, text)
|
|
610
|
+
buf_id = H.validate_buf_id(buf_id)
|
|
611
|
+
if not (type(text) == 'table' or type(text) == 'string') then H.error('`text` should be either string or array.') end
|
|
612
|
+
if type(text) == 'table' then text = #text > 0 and table.concat(text, '\n') or nil end
|
|
613
|
+
|
|
614
|
+
-- Enable if not already enabled
|
|
615
|
+
if not H.is_buf_enabled(buf_id) then MiniDiff.enable(buf_id) end
|
|
616
|
+
if not H.is_buf_enabled(buf_id) then H.error('Can not set reference text for not enabled buffer.') end
|
|
617
|
+
|
|
618
|
+
-- Appending '\n' makes more intuitive diffs at end-of-file
|
|
619
|
+
if text ~= nil and string.sub(text, -1) ~= '\n' then text = text .. '\n' end
|
|
620
|
+
if text == nil then
|
|
621
|
+
H.clear_all_diff(buf_id)
|
|
622
|
+
vim.cmd('redraw')
|
|
623
|
+
end
|
|
624
|
+
|
|
625
|
+
-- Immediately update diff
|
|
626
|
+
H.cache[buf_id].ref_text = text
|
|
627
|
+
H.schedule_diff_update(buf_id, 0)
|
|
628
|
+
end
|
|
629
|
+
|
|
630
|
+
--- Generate builtin sources
|
|
631
|
+
---
|
|
632
|
+
--- This is a table with function elements. Call to actually get source.
|
|
633
|
+
--- Examples: >lua
|
|
634
|
+
---
|
|
635
|
+
--- local diff = require('mini.diff')
|
|
636
|
+
---
|
|
637
|
+
--- -- Single `save` source
|
|
638
|
+
--- diff.setup({ source = diff.gen_source.save() })
|
|
639
|
+
---
|
|
640
|
+
--- -- Multiple sources (attempted to attach in order)
|
|
641
|
+
--- diff.setup({ source = { diff.gen_source.git(), diff.gen_source.save() } })
|
|
642
|
+
--- <
|
|
643
|
+
MiniDiff.gen_source = {}
|
|
644
|
+
|
|
645
|
+
--- Git source
|
|
646
|
+
---
|
|
647
|
+
--- Default source. Uses file text from Git index as reference. This results in:
|
|
648
|
+
--- - "Add" hunks represent text present in current buffer, but not in index.
|
|
649
|
+
--- - "Change" hunks represent modified text already present in index.
|
|
650
|
+
--- - "Delete" hunks represent text deleted from index.
|
|
651
|
+
---
|
|
652
|
+
--- Applying hunks means staging, a.k.a adding to index.
|
|
653
|
+
--- Notes:
|
|
654
|
+
--- - Requires Git version at least 2.38.0.
|
|
655
|
+
--- - There is no capability for unstaging hunks. Use full Git client for that.
|
|
656
|
+
---
|
|
657
|
+
---@return table Source. See |MiniDiff-source-specification|.
|
|
658
|
+
MiniDiff.gen_source.git = function()
|
|
659
|
+
local attach = function(buf_id)
|
|
660
|
+
-- Try attaching to a buffer only once
|
|
661
|
+
if H.git_cache[buf_id] ~= nil then return false end
|
|
662
|
+
|
|
663
|
+
-- Possibly resolve symlinks to get data from the original repo
|
|
664
|
+
local path = H.get_buf_realpath(buf_id)
|
|
665
|
+
if path == '' then return false end
|
|
666
|
+
|
|
667
|
+
-- Claim Git cache to not try to attach to the non-Git path several times.
|
|
668
|
+
-- NOTE: Do this after `path==''` check since the same buffer can be reused
|
|
669
|
+
-- for opening actual file (`nvim` -> `:edit file`).
|
|
670
|
+
H.git_cache[buf_id] = {}
|
|
671
|
+
H.git_start_watching_index(buf_id, path)
|
|
672
|
+
end
|
|
673
|
+
|
|
674
|
+
local detach = function(buf_id)
|
|
675
|
+
local cache = H.git_cache[buf_id]
|
|
676
|
+
H.git_cache[buf_id] = nil
|
|
677
|
+
H.git_invalidate_cache(cache)
|
|
678
|
+
end
|
|
679
|
+
|
|
680
|
+
local apply_hunks = function(buf_id, hunks)
|
|
681
|
+
local path_data = H.git_get_path_data(H.get_buf_realpath(buf_id))
|
|
682
|
+
if path_data == nil or path_data.rel_path == nil then return end
|
|
683
|
+
local patch = H.git_format_patch(buf_id, hunks, path_data)
|
|
684
|
+
H.git_apply_patch(path_data, patch)
|
|
685
|
+
end
|
|
686
|
+
|
|
687
|
+
-- Ensure to clean source cache for `:edit` to force reattach, since regular
|
|
688
|
+
-- `disable()` from `on_detach` is not enough as there can be no source
|
|
689
|
+
-- attached (like for files not in Git repo).
|
|
690
|
+
local augroup = vim.api.nvim_create_augroup('MiniDiffSourceGit', { clear = true })
|
|
691
|
+
local clear_buf_cache = function(ev) H.git_cache[ev.buf] = nil end
|
|
692
|
+
local au_opts = { group = augroup, callback = clear_buf_cache, desc = 'Clear Git cache' }
|
|
693
|
+
vim.api.nvim_create_autocmd({ 'BufUnload' }, au_opts)
|
|
694
|
+
|
|
695
|
+
return { name = 'git', attach = attach, detach = detach, apply_hunks = apply_hunks }
|
|
696
|
+
end
|
|
697
|
+
|
|
698
|
+
--- "Do nothing" source
|
|
699
|
+
---
|
|
700
|
+
--- Allows buffers to be enabled while not setting any reference text.
|
|
701
|
+
--- Use this if the goal is to rely on manual |MiniDiff.set_ref_text()| calls.
|
|
702
|
+
---
|
|
703
|
+
---@return table Source. See |MiniDiff-source-specification|.
|
|
704
|
+
MiniDiff.gen_source.none = function()
|
|
705
|
+
return { name = 'none', attach = function() end }
|
|
706
|
+
end
|
|
707
|
+
|
|
708
|
+
--- Latest save source
|
|
709
|
+
---
|
|
710
|
+
--- Uses text at latest save as the reference. This results into diff showing
|
|
711
|
+
--- difference after the latest save.
|
|
712
|
+
---
|
|
713
|
+
---@return table Source. See |MiniDiff-source-specification|.
|
|
714
|
+
MiniDiff.gen_source.save = function()
|
|
715
|
+
local augroups = {}
|
|
716
|
+
local attach = function(buf_id)
|
|
717
|
+
local augroup = vim.api.nvim_create_augroup('MiniDiffSourceSaveBuffer' .. buf_id, { clear = true })
|
|
718
|
+
augroups[buf_id] = augroup
|
|
719
|
+
|
|
720
|
+
local set_ref = function()
|
|
721
|
+
if vim.bo[buf_id].modified then return end
|
|
722
|
+
MiniDiff.set_ref_text(buf_id, H.get_buftext(buf_id))
|
|
723
|
+
end
|
|
724
|
+
|
|
725
|
+
-- Autocommand are more efficient than file watcher as it doesn't read disk
|
|
726
|
+
local au_opts = { group = augroup, buffer = buf_id, callback = set_ref, desc = 'Set reference text after save' }
|
|
727
|
+
vim.api.nvim_create_autocmd({ 'BufWritePost', 'FileChangedShellPost' }, au_opts)
|
|
728
|
+
set_ref()
|
|
729
|
+
end
|
|
730
|
+
|
|
731
|
+
local detach = function(buf_id) pcall(vim.api.nvim_del_augroup_by_id, augroups[buf_id]) end
|
|
732
|
+
|
|
733
|
+
return { name = 'save', attach = attach, detach = detach }
|
|
734
|
+
end
|
|
735
|
+
|
|
736
|
+
--- Perform action on hunks in region
|
|
737
|
+
---
|
|
738
|
+
--- Compute hunks inside a target region (even for hunks only partially inside it)
|
|
739
|
+
--- and perform apply/reset/yank operation on them.
|
|
740
|
+
---
|
|
741
|
+
--- The "yank" action yanks all reference lines of target hunks into
|
|
742
|
+
--- a specified register (should be one of |registers|).
|
|
743
|
+
---
|
|
744
|
+
--- Notes:
|
|
745
|
+
--- - Whether hunk is inside a region is computed based on position of its
|
|
746
|
+
--- buffer lines.
|
|
747
|
+
--- - If "change" or "delete" is only partially inside a target region, all
|
|
748
|
+
--- reference lines are used in computed "intersection" hunk.
|
|
749
|
+
---
|
|
750
|
+
--- Used directly in `config.mappings.apply` and `config.mappings.reset`.
|
|
751
|
+
--- Usually there is no need to use this function manually.
|
|
752
|
+
--- See |MiniDiff.operator()| for how to set up a mapping for "yank".
|
|
753
|
+
---
|
|
754
|
+
---@param buf_id __diff_buf_id
|
|
755
|
+
---@param action string One of "apply", "reset", "yank".
|
|
756
|
+
---@param opts table|nil Options. Possible fields:
|
|
757
|
+
--- - <line_start> `(number)` - start line of the region. Default: 1.
|
|
758
|
+
--- - <line_end> `(number)` - start line of the region. Default: last buffer line.
|
|
759
|
+
--- - <register> `(string)` - register to yank reference lines into.
|
|
760
|
+
--- Default: |v:register|.
|
|
761
|
+
MiniDiff.do_hunks = function(buf_id, action, opts)
|
|
762
|
+
buf_id = H.validate_buf_id(buf_id)
|
|
763
|
+
local buf_cache = H.cache[buf_id]
|
|
764
|
+
if buf_cache == nil then H.error(string.format('Buffer %d is not enabled.', buf_id)) end
|
|
765
|
+
if type(buf_cache.ref_text) ~= 'string' then H.error(string.format('Buffer %d has no reference text.', buf_id)) end
|
|
766
|
+
|
|
767
|
+
if not (action == 'apply' or action == 'reset' or action == 'yank') then
|
|
768
|
+
H.error('`action` should be one of "apply", "reset", "yank".')
|
|
769
|
+
end
|
|
770
|
+
|
|
771
|
+
local default_opts = { line_start = 1, line_end = vim.api.nvim_buf_line_count(buf_id), register = vim.v.register }
|
|
772
|
+
opts = vim.tbl_deep_extend('force', default_opts, opts or {})
|
|
773
|
+
local line_start, line_end = H.validate_target_lines(buf_id, opts.line_start, opts.line_end)
|
|
774
|
+
if type(opts.register) ~= 'string' then H.error('`opts.register` should be string.') end
|
|
775
|
+
|
|
776
|
+
local hunks = H.get_hunks_in_range(buf_cache.hunks, line_start, line_end)
|
|
777
|
+
if #hunks == 0 then return H.notify('No hunks to ' .. action, 'INFO') end
|
|
778
|
+
if action == 'apply' then H.get_active_source(buf_cache).apply_hunks(buf_id, hunks) end
|
|
779
|
+
if action == 'reset' then H.reset_hunks(buf_id, hunks) end
|
|
780
|
+
if action == 'yank' then H.yank_hunks_ref(buf_cache.ref_text, hunks, opts.register) end
|
|
781
|
+
end
|
|
782
|
+
|
|
783
|
+
--- Go to hunk range in current buffer
|
|
784
|
+
---
|
|
785
|
+
---@param direction string One of "first", "prev", "next", "last".
|
|
786
|
+
---@param opts table|nil Options. A table with fields:
|
|
787
|
+
--- - <n_times> `(number)` - Number of times to advance. Default: |v:count1|.
|
|
788
|
+
--- - <line_start> `(number)` - Line number to start from for directions
|
|
789
|
+
--- "prev" and "next". Default: cursor line.
|
|
790
|
+
--- - <wrap> `(boolean)` - Whether to wrap around edges.
|
|
791
|
+
--- Default: `options.wrap` value of the config.
|
|
792
|
+
MiniDiff.goto_hunk = function(direction, opts)
|
|
793
|
+
local buf_id = vim.api.nvim_get_current_buf()
|
|
794
|
+
local buf_cache = H.cache[buf_id]
|
|
795
|
+
if buf_cache == nil then H.error(string.format('Buffer %d is not enabled.', buf_id)) end
|
|
796
|
+
|
|
797
|
+
if not vim.tbl_contains({ 'first', 'prev', 'next', 'last' }, direction) then
|
|
798
|
+
H.error('`direction` should be one of "first", "prev", "next", "last".')
|
|
799
|
+
end
|
|
800
|
+
|
|
801
|
+
local default_wrap = buf_cache.config.options.wrap_goto
|
|
802
|
+
local default_opts = { n_times = vim.v.count1, line_start = vim.fn.line('.'), wrap = default_wrap }
|
|
803
|
+
opts = vim.tbl_deep_extend('force', default_opts, opts or {})
|
|
804
|
+
if not (type(opts.n_times) == 'number' and opts.n_times >= 1) then
|
|
805
|
+
H.error('`opts.n_times` should be positive number.')
|
|
806
|
+
end
|
|
807
|
+
if type(opts.line_start) ~= 'number' then H.error('`opts.line_start` should be number.') end
|
|
808
|
+
if type(opts.wrap) ~= 'boolean' then H.error('`opts.wrap` should be boolean.') end
|
|
809
|
+
|
|
810
|
+
-- Prepare ranges to iterate.
|
|
811
|
+
local ranges = H.get_contiguous_hunk_ranges(buf_cache.hunks)
|
|
812
|
+
if #ranges == 0 then return H.notify('No hunks to go to', 'INFO') end
|
|
813
|
+
|
|
814
|
+
-- Iterate
|
|
815
|
+
local res_ind, did_wrap = H.iterate_hunk_ranges(ranges, direction, opts)
|
|
816
|
+
if res_ind == nil then return H.notify('No hunk ranges in direction ' .. vim.inspect(direction), 'INFO') end
|
|
817
|
+
local res_line = ranges[res_ind].from
|
|
818
|
+
if did_wrap then H.notify('Wrapped around edge in direction ' .. vim.inspect(direction), 'INFO') end
|
|
819
|
+
|
|
820
|
+
-- Add to jumplist
|
|
821
|
+
vim.cmd([[normal! m']])
|
|
822
|
+
|
|
823
|
+
-- Jump
|
|
824
|
+
local _, col = vim.fn.getline(res_line):find('^%s*')
|
|
825
|
+
vim.api.nvim_win_set_cursor(0, { res_line, col })
|
|
826
|
+
|
|
827
|
+
-- Open just enough folds
|
|
828
|
+
vim.cmd('normal! zv')
|
|
829
|
+
end
|
|
830
|
+
|
|
831
|
+
--- Perform action over region
|
|
832
|
+
---
|
|
833
|
+
--- Perform action over region defined by marks. Used in mappings.
|
|
834
|
+
---
|
|
835
|
+
--- Example of a mapping to yank reference lines of hunk range under cursor
|
|
836
|
+
--- (assuming default `config.mappings.textobject`): >lua
|
|
837
|
+
---
|
|
838
|
+
--- local rhs = function() return MiniDiff.operator('yank') .. 'gh' end
|
|
839
|
+
--- vim.keymap.set('n', 'ghy', rhs, { expr = true, remap = true })
|
|
840
|
+
--- <
|
|
841
|
+
---@param mode string One of "apply", "reset", "yank", or the ones used in |g@|.
|
|
842
|
+
MiniDiff.operator = function(mode)
|
|
843
|
+
local buf_id = vim.api.nvim_get_current_buf()
|
|
844
|
+
if H.is_disabled(buf_id) then return '' end
|
|
845
|
+
|
|
846
|
+
if mode == 'apply' or mode == 'reset' or mode == 'yank' then
|
|
847
|
+
H.operator_cache = { action = mode, win_view = vim.fn.winsaveview(), register = vim.v.register }
|
|
848
|
+
vim.o.operatorfunc = 'v:lua.MiniDiff.operator'
|
|
849
|
+
return 'g@'
|
|
850
|
+
end
|
|
851
|
+
local cache = H.operator_cache
|
|
852
|
+
|
|
853
|
+
-- NOTE: Using `[` / `]` marks also works in Visual mode as because it is
|
|
854
|
+
-- executed as part of `g@`, which treats visual selection as a result of
|
|
855
|
+
-- Operator-pending mode mechanics (for which visual selection is allowed to
|
|
856
|
+
-- define motion/textobject). The downside is that it sets 'operatorfunc',
|
|
857
|
+
-- but the upside is that it is "dot-repeatable" (for relative selection).
|
|
858
|
+
local opts = { line_start = vim.fn.line("'["), line_end = vim.fn.line("']"), register = cache.register }
|
|
859
|
+
if opts.line_end < opts.line_start then return H.notify('Not a proper textobject', 'INFO') end
|
|
860
|
+
MiniDiff.do_hunks(buf_id, cache.action, opts)
|
|
861
|
+
|
|
862
|
+
-- Restore window view for "apply" (as buffer text should not have changed)
|
|
863
|
+
if cache.action == 'apply' and cache.win_view ~= nil then
|
|
864
|
+
vim.fn.winrestview(cache.win_view)
|
|
865
|
+
-- NOTE: Restore only once because during dot-repeat it is not up to date
|
|
866
|
+
cache.win_view = nil
|
|
867
|
+
end
|
|
868
|
+
return ''
|
|
869
|
+
end
|
|
870
|
+
|
|
871
|
+
--- Select hunk range textobject
|
|
872
|
+
---
|
|
873
|
+
--- Selects all contiguous lines adjacent to cursor line which are in any (not
|
|
874
|
+
--- necessarily same) hunk (if cursor line itself is in hunk).
|
|
875
|
+
--- Used in default mappings.
|
|
876
|
+
MiniDiff.textobject = function()
|
|
877
|
+
local buf_id = vim.api.nvim_get_current_buf()
|
|
878
|
+
local buf_cache = H.cache[buf_id]
|
|
879
|
+
if buf_cache == nil or H.is_disabled(buf_id) then H.error('Current buffer is not enabled.') end
|
|
880
|
+
|
|
881
|
+
-- Get hunk range under cursor
|
|
882
|
+
local cur_line = vim.fn.line('.')
|
|
883
|
+
local regions, cur_region = H.get_contiguous_hunk_ranges(buf_cache.hunks), nil
|
|
884
|
+
for _, r in ipairs(regions) do
|
|
885
|
+
if r.from <= cur_line and cur_line <= r.to then cur_region = r end
|
|
886
|
+
end
|
|
887
|
+
if cur_region == nil then return H.notify('No hunk range under cursor', 'INFO') end
|
|
888
|
+
|
|
889
|
+
-- Select target region
|
|
890
|
+
local is_visual = vim.tbl_contains({ 'v', 'V', '\22' }, vim.fn.mode())
|
|
891
|
+
if is_visual then vim.cmd('normal! \27') end
|
|
892
|
+
vim.cmd(string.format('normal! %dGV%dG', cur_region.from, cur_region.to))
|
|
893
|
+
end
|
|
894
|
+
|
|
895
|
+
--- Indicate source attach fail
|
|
896
|
+
---
|
|
897
|
+
--- Try to attach next source; if there is none - call |MiniDiff.disable()|.
|
|
898
|
+
---
|
|
899
|
+
---@param buf_id integer Buffer identifier for which attach has failed.
|
|
900
|
+
MiniDiff.fail_attach = function(buf_id)
|
|
901
|
+
buf_id = H.validate_buf_id(buf_id)
|
|
902
|
+
|
|
903
|
+
-- Do nothing if there was no attempt to enable
|
|
904
|
+
local buf_cache = H.cache[buf_id]
|
|
905
|
+
if buf_cache == nil then return end
|
|
906
|
+
|
|
907
|
+
-- If no next source, disable buffer without calling any of `detach`
|
|
908
|
+
if buf_cache.source_id >= #buf_cache.source then
|
|
909
|
+
H.cache[buf_id].source_id = math.huge
|
|
910
|
+
return MiniDiff.disable(buf_id)
|
|
911
|
+
end
|
|
912
|
+
|
|
913
|
+
-- Try attaching next source
|
|
914
|
+
buf_cache.source_id = buf_cache.source_id + 1
|
|
915
|
+
local attach_output = H.get_active_source(H.cache[buf_id]).attach(buf_id)
|
|
916
|
+
if attach_output == false then MiniDiff.fail_attach(buf_id) end
|
|
917
|
+
end
|
|
918
|
+
|
|
919
|
+
-- Helper data ================================================================
|
|
920
|
+
-- Module default config
|
|
921
|
+
H.default_config = MiniDiff.config
|
|
922
|
+
|
|
923
|
+
H.default_source = { MiniDiff.gen_source.git() }
|
|
924
|
+
|
|
925
|
+
-- Timers
|
|
926
|
+
H.timer_diff_update = vim.loop.new_timer()
|
|
927
|
+
|
|
928
|
+
-- Namespaces per highlighter name
|
|
929
|
+
H.ns_id = {
|
|
930
|
+
viz = vim.api.nvim_create_namespace('MiniDiffViz'),
|
|
931
|
+
overlay = vim.api.nvim_create_namespace('MiniDiffOverlay'),
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
-- Cache of buffers waiting for debounced diff update
|
|
935
|
+
H.bufs_to_update = {}
|
|
936
|
+
|
|
937
|
+
-- Cache per enabled buffer
|
|
938
|
+
H.cache = {}
|
|
939
|
+
|
|
940
|
+
-- Cache per buffer for attached `git` source
|
|
941
|
+
H.git_cache = {}
|
|
942
|
+
|
|
943
|
+
-- Cache for operator
|
|
944
|
+
H.operator_cache = {}
|
|
945
|
+
|
|
946
|
+
-- Common extmark data for supported styles
|
|
947
|
+
--stylua: ignore
|
|
948
|
+
H.style_extmark_data = {
|
|
949
|
+
sign = { hl_group_prefix = 'MiniDiffSign', field = 'sign_hl_group' },
|
|
950
|
+
number = { hl_group_prefix = 'MiniDiffSign', field = 'number_hl_group' },
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
-- Suffix for overlay virtual lines to be highlighted as full line
|
|
954
|
+
H.overlay_suffix = string.rep(' ', vim.o.columns)
|
|
955
|
+
|
|
956
|
+
-- Flag for whether to invalidate extmarks
|
|
957
|
+
H.extmark_invalidate = vim.fn.has('nvim-0.10') == 1 and true or nil
|
|
958
|
+
|
|
959
|
+
-- Flag for whether to handle virtual lines overflow
|
|
960
|
+
H.extmark_virt_lines_overflow = vim.fn.has('nvim-0.11') == 1 and 'scroll' or nil
|
|
961
|
+
|
|
962
|
+
-- Permanent `vim.diff()` options
|
|
963
|
+
H.vimdiff_opts = { result_type = 'indices', ctxlen = 0, interhunkctxlen = 0 }
|
|
964
|
+
|
|
965
|
+
-- Options for `vim.diff()` during word diff. Use `interhunkctxlen = 4` to
|
|
966
|
+
-- reduce noisiness (chosen as slightly less than average English word length)
|
|
967
|
+
--stylua: ignore
|
|
968
|
+
H.worddiff_opts = {
|
|
969
|
+
algorithm = 'minimal', result_type = 'indices',
|
|
970
|
+
ctxlen = 0, interhunkctxlen = 4,
|
|
971
|
+
indent_heuristic = false, linematch = 0
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
-- BOM bytes prepended to buffer text if 'bomb' is enabled. See `:h bom-bytes`.
|
|
975
|
+
--stylua: ignore
|
|
976
|
+
H.bom_bytes = {
|
|
977
|
+
['utf-8'] = string.char(0xef, 0xbb, 0xbf),
|
|
978
|
+
['utf-16be'] = string.char(0xfe, 0xff),
|
|
979
|
+
['utf-16'] = string.char(0xfe, 0xff),
|
|
980
|
+
['utf-16le'] = string.char(0xff, 0xfe),
|
|
981
|
+
-- In 'fileencoding', 'utf-32' is transformed into 'ucs-4'
|
|
982
|
+
['utf-32be'] = string.char(0x00, 0x00, 0xfe, 0xff),
|
|
983
|
+
['ucs-4be'] = string.char(0x00, 0x00, 0xfe, 0xff),
|
|
984
|
+
['utf-32'] = string.char(0x00, 0x00, 0xfe, 0xff),
|
|
985
|
+
['ucs-4'] = string.char(0x00, 0x00, 0xfe, 0xff),
|
|
986
|
+
['utf-32le'] = string.char(0xff, 0xfe, 0x00, 0x00),
|
|
987
|
+
['ucs-4le'] = string.char(0xff, 0xfe, 0x00, 0x00),
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
-- Helper functionality =======================================================
|
|
991
|
+
-- Settings -------------------------------------------------------------------
|
|
992
|
+
H.setup_config = function(config)
|
|
993
|
+
H.check_type('config', config, 'table', true)
|
|
994
|
+
config = vim.tbl_deep_extend('force', vim.deepcopy(H.default_config), config or {})
|
|
995
|
+
|
|
996
|
+
H.check_type('view', config.view, 'table')
|
|
997
|
+
H.check_type('view.style', config.view.style, 'string')
|
|
998
|
+
H.check_type('view.signs', config.view.signs, 'table')
|
|
999
|
+
H.check_type('view.signs.add', config.view.signs.add, 'string')
|
|
1000
|
+
H.check_type('view.signs.change', config.view.signs.change, 'string')
|
|
1001
|
+
H.check_type('view.signs.delete', config.view.signs.delete, 'string')
|
|
1002
|
+
H.check_type('view.priority', config.view.priority, 'number')
|
|
1003
|
+
|
|
1004
|
+
H.check_type('source', config.source, 'table', true)
|
|
1005
|
+
|
|
1006
|
+
H.check_type('delay', config.delay, 'table')
|
|
1007
|
+
H.check_type('delay.text_change', config.delay.text_change, 'number')
|
|
1008
|
+
|
|
1009
|
+
H.check_type('mappings', config.mappings, 'table')
|
|
1010
|
+
H.check_type('mappings.apply', config.mappings.apply, 'string')
|
|
1011
|
+
H.check_type('mappings.reset', config.mappings.reset, 'string')
|
|
1012
|
+
H.check_type('mappings.textobject', config.mappings.textobject, 'string')
|
|
1013
|
+
H.check_type('mappings.goto_first', config.mappings.goto_first, 'string')
|
|
1014
|
+
H.check_type('mappings.goto_prev', config.mappings.goto_prev, 'string')
|
|
1015
|
+
H.check_type('mappings.goto_next', config.mappings.goto_next, 'string')
|
|
1016
|
+
H.check_type('mappings.goto_last', config.mappings.goto_last, 'string')
|
|
1017
|
+
|
|
1018
|
+
H.check_type('options', config.options, 'table')
|
|
1019
|
+
H.check_type('options.algorithm', config.options.algorithm, 'string')
|
|
1020
|
+
H.check_type('options.indent_heuristic', config.options.indent_heuristic, 'boolean')
|
|
1021
|
+
H.check_type('options.linematch', config.options.linematch, 'number')
|
|
1022
|
+
H.check_type('options.wrap_goto', config.options.wrap_goto, 'boolean')
|
|
1023
|
+
|
|
1024
|
+
return config
|
|
1025
|
+
end
|
|
1026
|
+
|
|
1027
|
+
H.apply_config = function(config)
|
|
1028
|
+
MiniDiff.config = config
|
|
1029
|
+
|
|
1030
|
+
-- Make mappings
|
|
1031
|
+
local mappings = config.mappings
|
|
1032
|
+
|
|
1033
|
+
local rhs_apply = function() return MiniDiff.operator('apply') end
|
|
1034
|
+
H.map({ 'n', 'x' }, mappings.apply, rhs_apply, { expr = true, desc = 'Apply hunks' })
|
|
1035
|
+
local rhs_reset = function() return MiniDiff.operator('reset') end
|
|
1036
|
+
H.map({ 'n', 'x' }, mappings.reset, rhs_reset, { expr = true, desc = 'Reset hunks' })
|
|
1037
|
+
|
|
1038
|
+
local is_tobj_conflict = mappings.textobject == mappings.apply or mappings.textobject == mappings.reset
|
|
1039
|
+
local modes = is_tobj_conflict and { 'o' } or { 'x', 'o' }
|
|
1040
|
+
H.map(modes, mappings.textobject, '<Cmd>lua MiniDiff.textobject()<CR>', { desc = 'Hunk range textobject' })
|
|
1041
|
+
|
|
1042
|
+
--stylua: ignore start
|
|
1043
|
+
H.map({ 'n', 'x' }, mappings.goto_first, "<Cmd>lua MiniDiff.goto_hunk('first')<CR>", { desc = 'First hunk' })
|
|
1044
|
+
H.map('o', mappings.goto_first, "V<Cmd>lua MiniDiff.goto_hunk('first')<CR>", { desc = 'First hunk' })
|
|
1045
|
+
H.map({ 'n', 'x' }, mappings.goto_prev, "<Cmd>lua MiniDiff.goto_hunk('prev')<CR>", { desc = 'Previous hunk' })
|
|
1046
|
+
H.map('o', mappings.goto_prev, "V<Cmd>lua MiniDiff.goto_hunk('prev')<CR>", { desc = 'Previous hunk' })
|
|
1047
|
+
H.map({ 'n', 'x' }, mappings.goto_next, "<Cmd>lua MiniDiff.goto_hunk('next')<CR>", { desc = 'Next hunk' })
|
|
1048
|
+
H.map('o', mappings.goto_next, "V<Cmd>lua MiniDiff.goto_hunk('next')<CR>", { desc = 'Next hunk' })
|
|
1049
|
+
H.map({ 'n', 'x' }, mappings.goto_last, "<Cmd>lua MiniDiff.goto_hunk('last')<CR>", { desc = 'Last hunk' })
|
|
1050
|
+
H.map('o', mappings.goto_last, "V<Cmd>lua MiniDiff.goto_hunk('last')<CR>", { desc = 'Last hunk' })
|
|
1051
|
+
--stylua: ignore end
|
|
1052
|
+
|
|
1053
|
+
-- Register decoration provider which actually makes visualization
|
|
1054
|
+
local ns_id_viz, ns_id_overlay = H.ns_id.viz, H.ns_id.overlay
|
|
1055
|
+
H.set_decoration_provider(ns_id_viz, ns_id_overlay)
|
|
1056
|
+
end
|
|
1057
|
+
|
|
1058
|
+
H.create_autocommands = function()
|
|
1059
|
+
local gr = vim.api.nvim_create_augroup('MiniDiff', {})
|
|
1060
|
+
|
|
1061
|
+
local au = function(event, pattern, callback, desc)
|
|
1062
|
+
vim.api.nvim_create_autocmd(event, { group = gr, pattern = pattern, callback = callback, desc = desc })
|
|
1063
|
+
end
|
|
1064
|
+
|
|
1065
|
+
-- NOTE: Try auto enabling buffer on every `BufEnter` to not have `:edit`
|
|
1066
|
+
-- disabling buffer, as it calls `on_detach()` from buffer watcher
|
|
1067
|
+
au('BufEnter', '*', H.auto_enable, 'Enable diff')
|
|
1068
|
+
au('VimResized', '*', H.on_resize, 'Track Neovim resizing')
|
|
1069
|
+
au('ColorScheme', '*', H.create_default_hl, 'Ensure colors')
|
|
1070
|
+
end
|
|
1071
|
+
|
|
1072
|
+
--stylua: ignore
|
|
1073
|
+
H.create_default_hl = function()
|
|
1074
|
+
local hi = function(name, opts)
|
|
1075
|
+
opts.default = true
|
|
1076
|
+
vim.api.nvim_set_hl(0, name, opts)
|
|
1077
|
+
end
|
|
1078
|
+
|
|
1079
|
+
local has_core_diff_hl = vim.fn.has('nvim-0.10') == 1
|
|
1080
|
+
hi('MiniDiffSignAdd', { link = has_core_diff_hl and 'Added' or 'diffAdded' })
|
|
1081
|
+
hi('MiniDiffSignChange', { link = has_core_diff_hl and 'Changed' or 'diffChanged' })
|
|
1082
|
+
hi('MiniDiffSignDelete', { link = has_core_diff_hl and 'Removed' or 'diffRemoved' })
|
|
1083
|
+
hi('MiniDiffOverAdd', { link = 'DiffAdd' })
|
|
1084
|
+
hi('MiniDiffOverChange', { link = 'DiffText' })
|
|
1085
|
+
hi('MiniDiffOverChangeBuf', { link = 'MiniDiffOverChange'})
|
|
1086
|
+
hi('MiniDiffOverContext', { link = 'DiffChange' })
|
|
1087
|
+
hi('MiniDiffOverContextBuf', {})
|
|
1088
|
+
hi('MiniDiffOverDelete', { link = 'DiffDelete' })
|
|
1089
|
+
end
|
|
1090
|
+
|
|
1091
|
+
H.is_disabled = function(buf_id)
|
|
1092
|
+
local buf_disable = H.get_buf_var(buf_id, 'minidiff_disable')
|
|
1093
|
+
return vim.g.minidiff_disable == true or buf_disable == true
|
|
1094
|
+
end
|
|
1095
|
+
|
|
1096
|
+
H.get_config = function(config, buf_id)
|
|
1097
|
+
local buf_config = H.get_buf_var(buf_id, 'minidiff_config') or {}
|
|
1098
|
+
return vim.tbl_deep_extend('force', MiniDiff.config, buf_config, config or {})
|
|
1099
|
+
end
|
|
1100
|
+
|
|
1101
|
+
H.get_buf_var = function(buf_id, name)
|
|
1102
|
+
if not vim.api.nvim_buf_is_valid(buf_id) then return nil end
|
|
1103
|
+
return vim.b[buf_id or 0][name]
|
|
1104
|
+
end
|
|
1105
|
+
|
|
1106
|
+
-- Autocommands ---------------------------------------------------------------
|
|
1107
|
+
H.auto_enable = vim.schedule_wrap(function(data)
|
|
1108
|
+
if H.is_buf_enabled(data.buf) or H.is_disabled(data.buf) then return end
|
|
1109
|
+
local buf = data.buf
|
|
1110
|
+
if not (vim.api.nvim_buf_is_loaded(buf) and vim.bo[buf].buftype == '' and vim.bo[buf].buflisted) then return end
|
|
1111
|
+
if not H.is_buf_text(buf) then return end
|
|
1112
|
+
MiniDiff.enable(buf)
|
|
1113
|
+
end)
|
|
1114
|
+
|
|
1115
|
+
H.on_resize = function()
|
|
1116
|
+
H.overlay_suffix = string.rep(' ', vim.o.columns)
|
|
1117
|
+
for buf_id, _ in pairs(H.cache) do
|
|
1118
|
+
if vim.api.nvim_buf_is_valid(buf_id) then
|
|
1119
|
+
H.clear_all_diff(buf_id)
|
|
1120
|
+
H.schedule_diff_update(buf_id, 0)
|
|
1121
|
+
end
|
|
1122
|
+
end
|
|
1123
|
+
end
|
|
1124
|
+
|
|
1125
|
+
-- Validators -----------------------------------------------------------------
|
|
1126
|
+
H.validate_buf_id = function(x)
|
|
1127
|
+
if x == nil or x == 0 then return vim.api.nvim_get_current_buf() end
|
|
1128
|
+
if not (type(x) == 'number' and vim.api.nvim_buf_is_valid(x)) then
|
|
1129
|
+
H.error('`buf_id` should be `nil` or valid buffer id.')
|
|
1130
|
+
end
|
|
1131
|
+
return x
|
|
1132
|
+
end
|
|
1133
|
+
|
|
1134
|
+
H.validate_target_lines = function(buf_id, line_start, line_end)
|
|
1135
|
+
local n_lines = vim.api.nvim_buf_line_count(buf_id)
|
|
1136
|
+
|
|
1137
|
+
if type(line_start) ~= 'number' then H.error('`line_start` should be number.') end
|
|
1138
|
+
if type(line_end) ~= 'number' then H.error('`line_end` should be number.') end
|
|
1139
|
+
|
|
1140
|
+
-- Allow negative lines to count from last line
|
|
1141
|
+
line_start = line_start < 0 and (n_lines + line_start + 1) or line_start
|
|
1142
|
+
line_end = line_end < 0 and (n_lines + line_end + 1) or line_end
|
|
1143
|
+
|
|
1144
|
+
-- Clamp to fit the allowed range
|
|
1145
|
+
line_start = math.min(math.max(line_start, 1), n_lines)
|
|
1146
|
+
line_end = math.min(math.max(line_end, 1), n_lines)
|
|
1147
|
+
if not (line_start <= line_end) then H.error('`line_start` should be less than or equal to `line_end`.') end
|
|
1148
|
+
|
|
1149
|
+
return line_start, line_end
|
|
1150
|
+
end
|
|
1151
|
+
|
|
1152
|
+
H.validate_callable = function(x, name)
|
|
1153
|
+
if vim.is_callable(x) then return x end
|
|
1154
|
+
H.error('`' .. name .. '` should be callable.')
|
|
1155
|
+
end
|
|
1156
|
+
|
|
1157
|
+
-- Enabling -------------------------------------------------------------------
|
|
1158
|
+
H.is_buf_enabled = function(buf_id) return H.cache[buf_id] ~= nil end
|
|
1159
|
+
|
|
1160
|
+
H.update_buf_cache = function(buf_id)
|
|
1161
|
+
local new_cache = H.cache[buf_id] or {}
|
|
1162
|
+
|
|
1163
|
+
local buf_config = H.get_config({}, buf_id)
|
|
1164
|
+
new_cache.config = buf_config
|
|
1165
|
+
new_cache.extmark_opts = H.convert_view_to_extmark_opts(buf_config.view)
|
|
1166
|
+
new_cache.source = H.normalize_source(buf_config.source or H.default_source)
|
|
1167
|
+
new_cache.source_id = new_cache.source_id or 1
|
|
1168
|
+
|
|
1169
|
+
new_cache.hunks = new_cache.hunks or {}
|
|
1170
|
+
new_cache.summary = new_cache.summary or {}
|
|
1171
|
+
new_cache.viz_lines = new_cache.viz_lines or {}
|
|
1172
|
+
|
|
1173
|
+
new_cache.overlay = false
|
|
1174
|
+
new_cache.overlay_lines = new_cache.overlay_lines or {}
|
|
1175
|
+
|
|
1176
|
+
H.cache[buf_id] = new_cache
|
|
1177
|
+
end
|
|
1178
|
+
|
|
1179
|
+
H.setup_buf_autocommands = function(buf_id)
|
|
1180
|
+
local augroup = vim.api.nvim_create_augroup('MiniDiffBuffer' .. buf_id, { clear = true })
|
|
1181
|
+
H.cache[buf_id].augroup = augroup
|
|
1182
|
+
|
|
1183
|
+
local buf_update = vim.schedule_wrap(function() H.update_buf_cache(buf_id) end)
|
|
1184
|
+
local bufwinenter_opts = { group = augroup, buffer = buf_id, callback = buf_update, desc = 'Update buffer cache' }
|
|
1185
|
+
vim.api.nvim_create_autocmd('BufWinEnter', bufwinenter_opts)
|
|
1186
|
+
|
|
1187
|
+
local reset_if_enabled = vim.schedule_wrap(function(data)
|
|
1188
|
+
if not H.is_buf_enabled(data.buf) then return end
|
|
1189
|
+
MiniDiff.disable(data.buf)
|
|
1190
|
+
MiniDiff.enable(data.buf)
|
|
1191
|
+
end)
|
|
1192
|
+
local bufrename_opts = { group = augroup, buffer = buf_id, callback = reset_if_enabled, desc = 'Reset on rename' }
|
|
1193
|
+
-- NOTE: `BufFilePost` does not look like a proper event, but it (yet) works
|
|
1194
|
+
vim.api.nvim_create_autocmd('BufFilePost', bufrename_opts)
|
|
1195
|
+
|
|
1196
|
+
local buf_disable = function() MiniDiff.disable(buf_id) end
|
|
1197
|
+
local bufdelete_opts = { group = augroup, buffer = buf_id, callback = buf_disable, desc = 'Disable on delete' }
|
|
1198
|
+
vim.api.nvim_create_autocmd('BufDelete', bufdelete_opts)
|
|
1199
|
+
end
|
|
1200
|
+
|
|
1201
|
+
H.normalize_source = function(source)
|
|
1202
|
+
-- Normalize to an array of sources
|
|
1203
|
+
if type(source) ~= 'table' then H.error('`source` should be table.') end
|
|
1204
|
+
if source[1] == nil then source = { source } end
|
|
1205
|
+
|
|
1206
|
+
local res = {}
|
|
1207
|
+
for i, s in ipairs(source) do
|
|
1208
|
+
local cur_s = { attach = s.attach }
|
|
1209
|
+
cur_s.name = s.name or 'unknown'
|
|
1210
|
+
cur_s.detach = s.detach or function(_) end
|
|
1211
|
+
cur_s.apply_hunks = s.apply_hunks or function(_) H.error('Current source does not support applying hunks.') end
|
|
1212
|
+
|
|
1213
|
+
if type(cur_s.name) ~= 'string' then H.error('`source.name` should be string.') end
|
|
1214
|
+
H.validate_callable(cur_s.attach, 'source.attach')
|
|
1215
|
+
H.validate_callable(cur_s.detach, 'source.detach')
|
|
1216
|
+
H.validate_callable(cur_s.apply_hunks, 'source.apply_hunks')
|
|
1217
|
+
|
|
1218
|
+
res[i] = cur_s
|
|
1219
|
+
end
|
|
1220
|
+
|
|
1221
|
+
return res
|
|
1222
|
+
end
|
|
1223
|
+
|
|
1224
|
+
H.get_active_source = function(buf_cache) return buf_cache.source[buf_cache.source_id] or {} end
|
|
1225
|
+
|
|
1226
|
+
H.convert_view_to_extmark_opts = function(view)
|
|
1227
|
+
local extmark_data = H.style_extmark_data[view.style]
|
|
1228
|
+
if extmark_data == nil then H.error('Style ' .. vim.inspect(view.style) .. ' is not supported.') end
|
|
1229
|
+
|
|
1230
|
+
local signs = view.style == 'sign' and view.signs or {}
|
|
1231
|
+
local field, hl_group_prefix = extmark_data.field, extmark_data.hl_group_prefix
|
|
1232
|
+
--stylua: ignore
|
|
1233
|
+
return {
|
|
1234
|
+
add = { [field] = hl_group_prefix .. 'Add', sign_text = signs.add, priority = view.priority, invalidate = H.extmark_invalidate },
|
|
1235
|
+
change = { [field] = hl_group_prefix .. 'Change', sign_text = signs.change, priority = view.priority, invalidate = H.extmark_invalidate },
|
|
1236
|
+
delete = { [field] = hl_group_prefix .. 'Delete', sign_text = signs.delete, priority = view.priority, invalidate = H.extmark_invalidate },
|
|
1237
|
+
}
|
|
1238
|
+
end
|
|
1239
|
+
|
|
1240
|
+
-- Processing -----------------------------------------------------------------
|
|
1241
|
+
H.set_decoration_provider = function(ns_id_viz, ns_id_overlay)
|
|
1242
|
+
local on_win = function(_, _, buf_id, top, bottom)
|
|
1243
|
+
local buf_cache = H.cache[buf_id]
|
|
1244
|
+
if buf_cache == nil then return false end
|
|
1245
|
+
|
|
1246
|
+
local viz_lines, overlay_lines = buf_cache.viz_lines, buf_cache.overlay_lines
|
|
1247
|
+
if buf_cache.needs_clear then
|
|
1248
|
+
H.clear_all_diff(buf_id)
|
|
1249
|
+
buf_cache.needs_clear, buf_cache.dummy_extmark = false, nil
|
|
1250
|
+
-- Ensure that sign column is visible even if hunks are outside of window
|
|
1251
|
+
-- view (matters with `signcolumn=auto`)
|
|
1252
|
+
if buf_cache.config.view.style == 'sign' and not vim.tbl_isempty(viz_lines) then
|
|
1253
|
+
local dummy_opts = { sign_text = ' ', priority = 0, right_gravity = false }
|
|
1254
|
+
dummy_opts.sign_hl_group, dummy_opts.cursorline_hl_group = 'SignColumn', 'CursorLineSign'
|
|
1255
|
+
buf_cache.dummy_extmark = vim.api.nvim_buf_set_extmark(buf_id, ns_id_viz, 0, 0, dummy_opts)
|
|
1256
|
+
end
|
|
1257
|
+
end
|
|
1258
|
+
|
|
1259
|
+
local has_viz_extmarks = false
|
|
1260
|
+
for i = top + 1, bottom + 1 do
|
|
1261
|
+
if viz_lines[i] ~= nil then
|
|
1262
|
+
H.set_extmark(buf_id, ns_id_viz, i - 1, 0, viz_lines[i])
|
|
1263
|
+
viz_lines[i] = nil
|
|
1264
|
+
has_viz_extmarks = true
|
|
1265
|
+
end
|
|
1266
|
+
if overlay_lines[i] ~= nil then
|
|
1267
|
+
-- Allow several overlays at one line (like for "delete" and "change")
|
|
1268
|
+
for j = 1, #overlay_lines[i] do
|
|
1269
|
+
H.draw_overlay_line(buf_id, ns_id_overlay, i - 1, overlay_lines[i][j])
|
|
1270
|
+
end
|
|
1271
|
+
overlay_lines[i] = nil
|
|
1272
|
+
end
|
|
1273
|
+
end
|
|
1274
|
+
|
|
1275
|
+
-- Make sure to clear dummy extmark when it is not needed (otherwise it
|
|
1276
|
+
-- affects signcolumn for cases like `yes:2` and `auto:2`)
|
|
1277
|
+
if buf_cache.dummy_extmark ~= nil and has_viz_extmarks then
|
|
1278
|
+
vim.api.nvim_buf_del_extmark(buf_id, ns_id_viz, buf_cache.dummy_extmark)
|
|
1279
|
+
buf_cache.dummy_extmark = nil
|
|
1280
|
+
end
|
|
1281
|
+
end
|
|
1282
|
+
vim.api.nvim_set_decoration_provider(ns_id_viz, { on_win = on_win })
|
|
1283
|
+
end
|
|
1284
|
+
|
|
1285
|
+
H.schedule_diff_update = vim.schedule_wrap(function(buf_id, delay_ms)
|
|
1286
|
+
H.bufs_to_update[buf_id] = true
|
|
1287
|
+
H.timer_diff_update:stop()
|
|
1288
|
+
H.timer_diff_update:start(delay_ms, 0, H.process_scheduled_buffers)
|
|
1289
|
+
end)
|
|
1290
|
+
|
|
1291
|
+
H.process_scheduled_buffers = vim.schedule_wrap(function()
|
|
1292
|
+
for buf_id, _ in pairs(H.bufs_to_update) do
|
|
1293
|
+
H.update_buf_diff(buf_id)
|
|
1294
|
+
end
|
|
1295
|
+
H.bufs_to_update = {}
|
|
1296
|
+
end)
|
|
1297
|
+
|
|
1298
|
+
H.update_buf_diff = vim.schedule_wrap(function(buf_id)
|
|
1299
|
+
-- Make early returns
|
|
1300
|
+
local buf_cache = H.cache[buf_id]
|
|
1301
|
+
if buf_cache == nil then return end
|
|
1302
|
+
if not vim.api.nvim_buf_is_valid(buf_id) then
|
|
1303
|
+
H.cache[buf_id] = nil
|
|
1304
|
+
return
|
|
1305
|
+
end
|
|
1306
|
+
if type(buf_cache.ref_text) ~= 'string' or H.is_disabled(buf_id) then
|
|
1307
|
+
local summary = { source_name = H.get_active_source(buf_cache).name }
|
|
1308
|
+
buf_cache.hunks, buf_cache.viz_lines, buf_cache.overlay_lines, buf_cache.summary = {}, {}, {}, summary
|
|
1309
|
+
vim.b[buf_id].minidiff_summary, vim.b[buf_id].minidiff_summary_string = summary, ''
|
|
1310
|
+
return
|
|
1311
|
+
end
|
|
1312
|
+
|
|
1313
|
+
-- Compute diff
|
|
1314
|
+
local options = buf_cache.config.options
|
|
1315
|
+
H.vimdiff_opts.algorithm = options.algorithm
|
|
1316
|
+
H.vimdiff_opts.indent_heuristic = options.indent_heuristic
|
|
1317
|
+
H.vimdiff_opts.linematch = options.linematch
|
|
1318
|
+
|
|
1319
|
+
local buf_text, buf_lines = H.get_buftext(buf_id)
|
|
1320
|
+
local diff = vim.diff(buf_cache.ref_text, buf_text, H.vimdiff_opts)
|
|
1321
|
+
|
|
1322
|
+
-- Recompute hunks with summary and draw information
|
|
1323
|
+
H.update_hunk_data(diff, buf_cache, buf_lines)
|
|
1324
|
+
|
|
1325
|
+
-- Set buffer-local variables with summary for easier external usage
|
|
1326
|
+
local summary = buf_cache.summary
|
|
1327
|
+
vim.b[buf_id].minidiff_summary = summary
|
|
1328
|
+
|
|
1329
|
+
local summary_string = {}
|
|
1330
|
+
if summary.n_ranges > 0 then table.insert(summary_string, '#' .. summary.n_ranges) end
|
|
1331
|
+
if summary.add > 0 then table.insert(summary_string, '+' .. summary.add) end
|
|
1332
|
+
if summary.change > 0 then table.insert(summary_string, '~' .. summary.change) end
|
|
1333
|
+
if summary.delete > 0 then table.insert(summary_string, '-' .. summary.delete) end
|
|
1334
|
+
vim.b[buf_id].minidiff_summary_string = table.concat(summary_string, ' ')
|
|
1335
|
+
|
|
1336
|
+
-- Request highlighting clear to be done in decoration provider
|
|
1337
|
+
buf_cache.needs_clear = true
|
|
1338
|
+
|
|
1339
|
+
-- Trigger event for users to possibly hook into. Ensure target buffer is
|
|
1340
|
+
-- current (for proper `buf` in event data)
|
|
1341
|
+
vim.api.nvim_buf_call(buf_id, function() vim.api.nvim_exec_autocmds('User', { pattern = 'MiniDiffUpdated' }) end)
|
|
1342
|
+
|
|
1343
|
+
-- Force redraw. NOTE: Using 'redraw' not always works (`<Cmd>update<CR>`
|
|
1344
|
+
-- from keymap with "save" source will not redraw) while 'redraw!' flickers.
|
|
1345
|
+
H.redraw_buffer(buf_id)
|
|
1346
|
+
end)
|
|
1347
|
+
|
|
1348
|
+
H.update_hunk_data = function(diff, buf_cache, buf_lines)
|
|
1349
|
+
local do_overlay = buf_cache.overlay
|
|
1350
|
+
local ref_lines = do_overlay and vim.split(buf_cache.ref_text, '\n') or nil
|
|
1351
|
+
|
|
1352
|
+
local extmark_opts, priority = buf_cache.extmark_opts, buf_cache.config.view.priority
|
|
1353
|
+
local hunks, viz_lines, overlay_lines = {}, {}, {}
|
|
1354
|
+
local n_add, n_change, n_delete = 0, 0, 0
|
|
1355
|
+
local n_ranges, last_range_to = 0, -math.huge
|
|
1356
|
+
for i, d in ipairs(diff) do
|
|
1357
|
+
-- Hunk
|
|
1358
|
+
local n_ref, n_buf = d[2], d[4]
|
|
1359
|
+
local hunk_type = n_ref == 0 and 'add' or (n_buf == 0 and 'delete' or 'change')
|
|
1360
|
+
local hunk = { type = hunk_type, ref_start = d[1], ref_count = n_ref, buf_start = d[3], buf_count = n_buf }
|
|
1361
|
+
hunks[i] = hunk
|
|
1362
|
+
|
|
1363
|
+
-- Hunk summary
|
|
1364
|
+
local hunk_n_change = math.min(n_ref, n_buf)
|
|
1365
|
+
n_add = n_add + n_buf - hunk_n_change
|
|
1366
|
+
n_change = n_change + hunk_n_change
|
|
1367
|
+
n_delete = n_delete + n_ref - hunk_n_change
|
|
1368
|
+
|
|
1369
|
+
-- Number of contiguous ranges.
|
|
1370
|
+
-- NOTE: this relies on `vim.diff()` output being sorted by `buf_start`.
|
|
1371
|
+
local range_from = math.max(d[3], 1)
|
|
1372
|
+
local range_to = range_from + math.max(n_buf, 1) - 1
|
|
1373
|
+
n_ranges = n_ranges + ((range_from <= last_range_to + 1) and 0 or 1)
|
|
1374
|
+
last_range_to = math.max(last_range_to, range_to)
|
|
1375
|
+
|
|
1376
|
+
-- Register lines for draw. At least one line should visualize hunk.
|
|
1377
|
+
local viz_ext_opts = extmark_opts[hunk_type]
|
|
1378
|
+
for l_num = range_from, range_to do
|
|
1379
|
+
-- Prefer showing "change" hunk over other types
|
|
1380
|
+
if viz_lines[l_num] == nil or hunk_type == 'change' then viz_lines[l_num] = viz_ext_opts end
|
|
1381
|
+
end
|
|
1382
|
+
|
|
1383
|
+
if do_overlay then
|
|
1384
|
+
if hunk_type == 'add' then H.append_overlay_add(overlay_lines, hunk, priority) end
|
|
1385
|
+
if hunk_type == 'change' then H.append_overlay_change(overlay_lines, hunk, ref_lines, buf_lines, priority) end
|
|
1386
|
+
if hunk_type == 'delete' then H.append_overlay_delete(overlay_lines, hunk, ref_lines, priority) end
|
|
1387
|
+
end
|
|
1388
|
+
end
|
|
1389
|
+
|
|
1390
|
+
buf_cache.hunks, buf_cache.viz_lines, buf_cache.overlay_lines = hunks, viz_lines, overlay_lines
|
|
1391
|
+
buf_cache.summary = { add = n_add, change = n_change, delete = n_delete, n_ranges = n_ranges }
|
|
1392
|
+
buf_cache.summary.source_name = H.get_active_source(buf_cache).name
|
|
1393
|
+
end
|
|
1394
|
+
|
|
1395
|
+
H.clear_all_diff = function(buf_id)
|
|
1396
|
+
H.clear_namespace(buf_id, H.ns_id.viz, 0, -1)
|
|
1397
|
+
H.clear_namespace(buf_id, H.ns_id.overlay, 0, -1)
|
|
1398
|
+
end
|
|
1399
|
+
|
|
1400
|
+
-- Overlay --------------------------------------------------------------------
|
|
1401
|
+
H.append_overlay = function(overlay_lines, l_num, data)
|
|
1402
|
+
local t = overlay_lines[l_num] or {}
|
|
1403
|
+
table.insert(t, data)
|
|
1404
|
+
overlay_lines[l_num] = t
|
|
1405
|
+
end
|
|
1406
|
+
|
|
1407
|
+
H.append_overlay_add = function(overlay_lines, hunk, priority)
|
|
1408
|
+
local data = { type = 'add', to = hunk.buf_start + hunk.buf_count - 1, priority = priority }
|
|
1409
|
+
H.append_overlay(overlay_lines, hunk.buf_start, data)
|
|
1410
|
+
end
|
|
1411
|
+
|
|
1412
|
+
H.append_overlay_change = function(overlay_lines, hunk, ref_lines, buf_lines, priority)
|
|
1413
|
+
-- For one-to-one change, show lines separately with word diff highlighted
|
|
1414
|
+
-- This is usually the case when `linematch` is on
|
|
1415
|
+
if hunk.buf_count == hunk.ref_count then
|
|
1416
|
+
for i = 0, hunk.ref_count - 1 do
|
|
1417
|
+
local ref_n, buf_n = hunk.ref_start + i, hunk.buf_start + i
|
|
1418
|
+
-- Defer actually computing word diff until in decoration provider as it
|
|
1419
|
+
-- will compute only for displayed lines
|
|
1420
|
+
local data =
|
|
1421
|
+
{ type = 'change_worddiff', ref_line = ref_lines[ref_n], buf_line = buf_lines[buf_n], priority = priority }
|
|
1422
|
+
H.append_overlay(overlay_lines, buf_n, data)
|
|
1423
|
+
end
|
|
1424
|
+
return
|
|
1425
|
+
end
|
|
1426
|
+
|
|
1427
|
+
-- If not one-to-one change, show reference lines above first real one
|
|
1428
|
+
local changed_lines = {}
|
|
1429
|
+
for i = hunk.ref_start, hunk.ref_start + hunk.ref_count - 1 do
|
|
1430
|
+
local l = { { ref_lines[i] .. H.overlay_suffix, 'MiniDiffOverChange' } }
|
|
1431
|
+
table.insert(changed_lines, l)
|
|
1432
|
+
end
|
|
1433
|
+
local to = hunk.buf_start + hunk.buf_count - 1
|
|
1434
|
+
local data = { type = 'change', to = to, lines = changed_lines, show_above = true, priority = priority }
|
|
1435
|
+
H.append_overlay(overlay_lines, hunk.buf_start, data)
|
|
1436
|
+
end
|
|
1437
|
+
|
|
1438
|
+
H.append_overlay_delete = function(overlay_lines, hunk, ref_lines, priority)
|
|
1439
|
+
local deleted_lines = {}
|
|
1440
|
+
for i = hunk.ref_start, hunk.ref_start + hunk.ref_count - 1 do
|
|
1441
|
+
table.insert(deleted_lines, { { ref_lines[i], 'MiniDiffOverDelete' }, { H.overlay_suffix, 'MiniDiffOverDelete' } })
|
|
1442
|
+
end
|
|
1443
|
+
local l_num, show_above = math.max(hunk.buf_start, 1), hunk.buf_start == 0
|
|
1444
|
+
local data = { type = 'delete', lines = deleted_lines, show_above = show_above, priority = priority }
|
|
1445
|
+
H.append_overlay(overlay_lines, l_num, data)
|
|
1446
|
+
end
|
|
1447
|
+
|
|
1448
|
+
H.draw_overlay_line = function(buf_id, ns_id, row, data)
|
|
1449
|
+
-- "Change worddif" hunk: compute word diff and show it above and over text
|
|
1450
|
+
if data.type == 'change_worddiff' then return H.draw_overlay_line_worddiff(buf_id, ns_id, row, data) end
|
|
1451
|
+
|
|
1452
|
+
local opts = { priority = data.priority }
|
|
1453
|
+
|
|
1454
|
+
-- "Add"/"Change" hunks highlight whole lines in affected buffer range
|
|
1455
|
+
if data.type ~= 'delete' then
|
|
1456
|
+
opts.end_row, opts.end_col, opts.hl_eol = data.to, 0, true
|
|
1457
|
+
opts.hl_group = data.type == 'add' and 'MiniDiffOverAdd' or 'MiniDiffOverContextBuf'
|
|
1458
|
+
end
|
|
1459
|
+
|
|
1460
|
+
-- "Change"/"Delete" hunks show affected reference range as virtual lines
|
|
1461
|
+
opts.virt_lines, opts.virt_lines_above, opts.virt_lines_overflow =
|
|
1462
|
+
data.lines, data.show_above, H.extmark_virt_lines_overflow
|
|
1463
|
+
H.set_extmark(buf_id, ns_id, row, 0, opts)
|
|
1464
|
+
end
|
|
1465
|
+
|
|
1466
|
+
H.draw_overlay_line_worddiff = function(buf_id, ns_id, row, data)
|
|
1467
|
+
local ref_line, buf_line, priority = data.ref_line, data.buf_line, data.priority
|
|
1468
|
+
local ref_parts, buf_parts = H.compute_worddiff_changed_parts(ref_line, buf_line)
|
|
1469
|
+
|
|
1470
|
+
-- Show changes in reference as two-colored virtual line above
|
|
1471
|
+
local virt_line, index = {}, 1
|
|
1472
|
+
for i = 1, #ref_parts do
|
|
1473
|
+
local part = ref_parts[i]
|
|
1474
|
+
if index < part[1] then table.insert(virt_line, { ref_line:sub(index, part[1] - 1), 'MiniDiffOverContext' }) end
|
|
1475
|
+
table.insert(virt_line, { ref_line:sub(part[1], part[2]), 'MiniDiffOverChange' })
|
|
1476
|
+
index = part[2] + 1
|
|
1477
|
+
end
|
|
1478
|
+
if index <= ref_line:len() then table.insert(virt_line, { ref_line:sub(index), 'MiniDiffOverContext' }) end
|
|
1479
|
+
table.insert(virt_line, { H.overlay_suffix, 'MiniDiffOverContext' })
|
|
1480
|
+
|
|
1481
|
+
--stylua: ignore
|
|
1482
|
+
local ref_opts = {
|
|
1483
|
+
virt_lines = { virt_line }, virt_lines_above = true, virt_lines_overflow = H.extmark_virt_lines_overflow,
|
|
1484
|
+
priority = priority,
|
|
1485
|
+
}
|
|
1486
|
+
H.set_extmark(buf_id, ns_id, row, 0, ref_opts)
|
|
1487
|
+
|
|
1488
|
+
-- Show changes in buffer line as one whole-line highlighting with separate
|
|
1489
|
+
-- highlighting for changed regions on top (as priority of context is lower)
|
|
1490
|
+
local off = vim.bo[buf_id].bomb and (H.bom_bytes[vim.bo[buf_id].fileencoding] or ''):len() or 0
|
|
1491
|
+
for i = 1, #buf_parts do
|
|
1492
|
+
local part = buf_parts[i]
|
|
1493
|
+
local buf_opts = { end_row = row, end_col = part[2] - off, hl_group = 'MiniDiffOverChangeBuf', priority = priority }
|
|
1494
|
+
H.set_extmark(buf_id, ns_id, row, part[1] - 1 - off, buf_opts)
|
|
1495
|
+
end
|
|
1496
|
+
local context_opts =
|
|
1497
|
+
{ end_row = row + 1, end_col = 0, hl_group = 'MiniDiffOverContextBuf', hl_eol = true, priority = priority - 1 }
|
|
1498
|
+
H.set_extmark(buf_id, ns_id, row, 0, context_opts)
|
|
1499
|
+
end
|
|
1500
|
+
|
|
1501
|
+
H.compute_worddiff_changed_parts = function(ref_line, buf_line)
|
|
1502
|
+
local ref_sliced, ref_byte_starts, ref_byte_ends = H.slice_line(ref_line)
|
|
1503
|
+
local buf_sliced, buf_byte_starts, buf_byte_ends = H.slice_line(buf_line)
|
|
1504
|
+
local diff = vim.diff(ref_sliced, buf_sliced, H.worddiff_opts)
|
|
1505
|
+
local ref_ranges, buf_ranges = {}, {}
|
|
1506
|
+
for i = 1, #diff do
|
|
1507
|
+
local d = diff[i]
|
|
1508
|
+
if d[2] > 0 then table.insert(ref_ranges, { ref_byte_starts[d[1]], ref_byte_ends[d[1] + d[2] - 1] }) end
|
|
1509
|
+
if d[4] > 0 then table.insert(buf_ranges, { buf_byte_starts[d[3]], buf_byte_ends[d[3] + d[4] - 1] }) end
|
|
1510
|
+
end
|
|
1511
|
+
|
|
1512
|
+
return ref_ranges, buf_ranges
|
|
1513
|
+
end
|
|
1514
|
+
|
|
1515
|
+
H.str_utfindex = function(s, i) return vim.str_utfindex(s, 'utf-32', i) end
|
|
1516
|
+
if vim.fn.has('nvim-0.11') == 0 then H.str_utfindex = function(s, i) return (vim.str_utfindex(s, i)) end end
|
|
1517
|
+
|
|
1518
|
+
H.slice_line = function(line)
|
|
1519
|
+
-- Intertwine every proper character with '\n'
|
|
1520
|
+
local line_len = line:len()
|
|
1521
|
+
local sliced, starts, ends
|
|
1522
|
+
-- Make short route for a very common case of no multibyte characters
|
|
1523
|
+
if H.str_utfindex(line) == line_len then
|
|
1524
|
+
sliced, starts, ends = line:gsub('(.)', '%1\n'), {}, {}
|
|
1525
|
+
for i = 1, string.len(line) do
|
|
1526
|
+
starts[i], ends[i] = i, i
|
|
1527
|
+
end
|
|
1528
|
+
else
|
|
1529
|
+
sliced, starts, ends = {}, vim.str_utf_pos(line), {}
|
|
1530
|
+
for i = 1, #starts - 1 do
|
|
1531
|
+
table.insert(sliced, line:sub(starts[i], starts[i + 1] - 1))
|
|
1532
|
+
table.insert(ends, starts[i + 1] - 1)
|
|
1533
|
+
end
|
|
1534
|
+
table.insert(sliced, line:sub(starts[#starts], line_len))
|
|
1535
|
+
table.insert(ends, line_len)
|
|
1536
|
+
sliced = table.concat(sliced, '\n') .. '\n'
|
|
1537
|
+
end
|
|
1538
|
+
|
|
1539
|
+
return sliced, starts, ends
|
|
1540
|
+
end
|
|
1541
|
+
|
|
1542
|
+
-- Hunks ----------------------------------------------------------------------
|
|
1543
|
+
H.get_hunk_buf_range = function(hunk)
|
|
1544
|
+
-- "Change" and "Add" hunks have the range `[from, from + buf_count - 1]`
|
|
1545
|
+
if hunk.buf_count > 0 then return hunk.buf_start, hunk.buf_start + hunk.buf_count - 1 end
|
|
1546
|
+
-- "Delete" hunks have `buf_count = 0` yet its range is `[from, from]`
|
|
1547
|
+
-- `buf_start` can be 0 for 'delete' hunk, yet range should be real lines
|
|
1548
|
+
local from = math.max(hunk.buf_start, 1)
|
|
1549
|
+
return from, from
|
|
1550
|
+
end
|
|
1551
|
+
|
|
1552
|
+
H.get_hunks_in_range = function(hunks, from, to)
|
|
1553
|
+
local res = {}
|
|
1554
|
+
for _, h in ipairs(hunks) do
|
|
1555
|
+
local h_from, h_to = H.get_hunk_buf_range(h)
|
|
1556
|
+
|
|
1557
|
+
local left, right = math.max(from, h_from), math.min(to, h_to)
|
|
1558
|
+
if left <= right then
|
|
1559
|
+
-- If any `cur` hunk part is selected, its `ref` part is used fully
|
|
1560
|
+
local new_h = { ref_start = h.ref_start, ref_count = h.ref_count }
|
|
1561
|
+
new_h.type = h.ref_count == 0 and 'add' or (h.buf_count == 0 and 'delete' or 'change')
|
|
1562
|
+
|
|
1563
|
+
-- It should be possible to work with only hunk part inside target range
|
|
1564
|
+
-- Also Treat "delete" hunks differently as they represent range differently
|
|
1565
|
+
-- and can have `buf_start=0`
|
|
1566
|
+
new_h.buf_start = new_h.type == 'delete' and h.buf_start or left
|
|
1567
|
+
new_h.buf_count = new_h.type == 'delete' and 0 or (right - left + 1)
|
|
1568
|
+
|
|
1569
|
+
table.insert(res, new_h)
|
|
1570
|
+
end
|
|
1571
|
+
end
|
|
1572
|
+
|
|
1573
|
+
table.sort(res, H.hunk_order)
|
|
1574
|
+
return res
|
|
1575
|
+
end
|
|
1576
|
+
|
|
1577
|
+
H.reset_hunks = function(buf_id, hunks)
|
|
1578
|
+
local ref_lines = vim.split(H.cache[buf_id].ref_text, '\n')
|
|
1579
|
+
local offset = 0
|
|
1580
|
+
for _, h in ipairs(hunks) do
|
|
1581
|
+
-- Replace current hunk lines with corresponding reference
|
|
1582
|
+
local new_lines = vim.list_slice(ref_lines, h.ref_start, h.ref_start + h.ref_count - 1)
|
|
1583
|
+
|
|
1584
|
+
-- Compute buffer offset from parts: result of previous replaces, "delete"
|
|
1585
|
+
-- hunk offset which starts below the `buf_start` line, zero-indexing.
|
|
1586
|
+
local buf_offset = offset + (h.buf_count == 0 and 1 or 0) - 1
|
|
1587
|
+
local from, to = h.buf_start + buf_offset, h.buf_start + h.buf_count + buf_offset
|
|
1588
|
+
vim.api.nvim_buf_set_lines(buf_id, from, to, false, new_lines)
|
|
1589
|
+
|
|
1590
|
+
-- Keep track of current hunk lines shift as a result of previous replaces
|
|
1591
|
+
offset = offset + (h.ref_count - h.buf_count)
|
|
1592
|
+
end
|
|
1593
|
+
end
|
|
1594
|
+
|
|
1595
|
+
H.yank_hunks_ref = function(ref_text, hunks, register)
|
|
1596
|
+
-- Collect reference lines
|
|
1597
|
+
local ref_lines, out_lines = vim.split(ref_text, '\n'), {}
|
|
1598
|
+
for _, h in ipairs(hunks) do
|
|
1599
|
+
for i = h.ref_start, h.ref_start + h.ref_count - 1 do
|
|
1600
|
+
out_lines[i] = ref_lines[i]
|
|
1601
|
+
end
|
|
1602
|
+
end
|
|
1603
|
+
|
|
1604
|
+
-- Construct reference lines in order
|
|
1605
|
+
local hunk_ref_lines = {}
|
|
1606
|
+
for i = 1, #ref_lines do
|
|
1607
|
+
table.insert(hunk_ref_lines, out_lines[i])
|
|
1608
|
+
end
|
|
1609
|
+
|
|
1610
|
+
-- Put lines into target register
|
|
1611
|
+
vim.fn.setreg(register, hunk_ref_lines, 'l')
|
|
1612
|
+
end
|
|
1613
|
+
|
|
1614
|
+
H.get_contiguous_hunk_ranges = function(hunks)
|
|
1615
|
+
if #hunks == 0 then return {} end
|
|
1616
|
+
hunks = vim.deepcopy(hunks)
|
|
1617
|
+
table.sort(hunks, H.hunk_order)
|
|
1618
|
+
|
|
1619
|
+
local h1_from, h1_to = H.get_hunk_buf_range(hunks[1])
|
|
1620
|
+
local res = { { from = h1_from, to = h1_to } }
|
|
1621
|
+
for i = 2, #hunks do
|
|
1622
|
+
local h, cur_region = hunks[i], res[#res]
|
|
1623
|
+
local h_from, h_to = H.get_hunk_buf_range(h)
|
|
1624
|
+
if h_from <= cur_region.to + 1 then
|
|
1625
|
+
cur_region.to = math.max(cur_region.to, h_to)
|
|
1626
|
+
else
|
|
1627
|
+
table.insert(res, { from = h_from, to = h_to })
|
|
1628
|
+
end
|
|
1629
|
+
end
|
|
1630
|
+
return res
|
|
1631
|
+
end
|
|
1632
|
+
|
|
1633
|
+
H.iterate_hunk_ranges = function(ranges, direction, opts)
|
|
1634
|
+
local n = #ranges
|
|
1635
|
+
|
|
1636
|
+
-- Compute initial index
|
|
1637
|
+
local init_ind
|
|
1638
|
+
if direction == 'first' then init_ind = 0 end
|
|
1639
|
+
if direction == 'prev' then init_ind = H.get_range_id_prev(ranges, opts.line_start) end
|
|
1640
|
+
if direction == 'next' then init_ind = H.get_range_id_next(ranges, opts.line_start) end
|
|
1641
|
+
if direction == 'last' then init_ind = n + 1 end
|
|
1642
|
+
|
|
1643
|
+
local is_on_edge = (direction == 'prev' and init_ind == 1) or (direction == 'next' and init_ind == n)
|
|
1644
|
+
if not opts.wrap and is_on_edge then return nil end
|
|
1645
|
+
|
|
1646
|
+
-- Compute destination index
|
|
1647
|
+
local is_move_forward = direction == 'first' or direction == 'next'
|
|
1648
|
+
local res_ind = init_ind + opts.n_times * (is_move_forward and 1 or -1)
|
|
1649
|
+
local did_wrap = opts.wrap and (res_ind < 1 or n < res_ind)
|
|
1650
|
+
res_ind = opts.wrap and ((res_ind - 1) % n + 1) or math.min(math.max(res_ind, 1), n)
|
|
1651
|
+
|
|
1652
|
+
return res_ind, did_wrap
|
|
1653
|
+
end
|
|
1654
|
+
|
|
1655
|
+
H.get_range_id_next = function(ranges, line_start)
|
|
1656
|
+
for i = #ranges, 1, -1 do
|
|
1657
|
+
if ranges[i].from <= line_start then return i end
|
|
1658
|
+
end
|
|
1659
|
+
return 0
|
|
1660
|
+
end
|
|
1661
|
+
|
|
1662
|
+
H.get_range_id_prev = function(ranges, line_start)
|
|
1663
|
+
for i = 1, #ranges do
|
|
1664
|
+
if line_start <= ranges[i].to then return i end
|
|
1665
|
+
end
|
|
1666
|
+
return #ranges + 1
|
|
1667
|
+
end
|
|
1668
|
+
|
|
1669
|
+
H.hunk_order = function(a, b)
|
|
1670
|
+
-- Ensure buffer order and that "change" hunks are listed earlier "delete"
|
|
1671
|
+
-- ones from the same line (important for `reset_hunks()`)
|
|
1672
|
+
return a.buf_start < b.buf_start or (a.buf_start == b.buf_start and a.type == 'change')
|
|
1673
|
+
end
|
|
1674
|
+
|
|
1675
|
+
-- Export ---------------------------------------------------------------------
|
|
1676
|
+
H.export_qf = function(opts)
|
|
1677
|
+
local buffers = opts.scope == 'current' and { vim.api.nvim_get_current_buf() } or vim.tbl_keys(H.cache)
|
|
1678
|
+
buffers = vim.tbl_filter(vim.api.nvim_buf_is_valid, buffers)
|
|
1679
|
+
table.sort(buffers)
|
|
1680
|
+
|
|
1681
|
+
local type_text = { add = 'Add', change = 'Change', delete = 'Delete' }
|
|
1682
|
+
|
|
1683
|
+
local res = {}
|
|
1684
|
+
for _, buf_id in ipairs(buffers) do
|
|
1685
|
+
local filename = vim.api.nvim_buf_get_name(buf_id)
|
|
1686
|
+
local buf_lines = vim.api.nvim_buf_get_lines(buf_id, 0, -1, false)
|
|
1687
|
+
for _, h in ipairs(H.cache[buf_id].hunks) do
|
|
1688
|
+
local text = type_text[h.type]
|
|
1689
|
+
local entry = { bufnr = buf_id, filename = filename, type = text:sub(1, 1), text = text }
|
|
1690
|
+
entry.lnum, entry.end_lnum = H.get_hunk_buf_range(h)
|
|
1691
|
+
-- Make 'add' and 'change' hunks represent actual buffer regions
|
|
1692
|
+
entry.col, entry.end_col = 1, h.type == 'delete' and 1 or buf_lines[entry.end_lnum]:len() + 1
|
|
1693
|
+
table.insert(res, entry)
|
|
1694
|
+
end
|
|
1695
|
+
end
|
|
1696
|
+
return res
|
|
1697
|
+
end
|
|
1698
|
+
|
|
1699
|
+
-- Git ------------------------------------------------------------------------
|
|
1700
|
+
H.git_start_watching_index = function(buf_id, path)
|
|
1701
|
+
-- NOTE: Watching single 'index' file is not enough as staging by Git is done
|
|
1702
|
+
-- via "create fresh 'index.lock' file, apply modifications, change file name
|
|
1703
|
+
-- to 'index'". Hence watch the whole '.git' (first level) and react only if
|
|
1704
|
+
-- change was in 'index' file.
|
|
1705
|
+
local stdout = vim.loop.new_pipe()
|
|
1706
|
+
local args = { 'rev-parse', '--path-format=absolute', '--git-dir' }
|
|
1707
|
+
local spawn_opts = { args = args, cwd = vim.fn.fnamemodify(path, ':h'), stdio = { nil, stdout, nil } }
|
|
1708
|
+
|
|
1709
|
+
-- If path is not in Git, disable buffer but make sure that it will not try
|
|
1710
|
+
-- to re-attach until buffer is properly disabled
|
|
1711
|
+
local on_not_in_git = vim.schedule_wrap(function()
|
|
1712
|
+
if not vim.api.nvim_buf_is_valid(buf_id) then
|
|
1713
|
+
H.cache[buf_id] = nil
|
|
1714
|
+
return
|
|
1715
|
+
end
|
|
1716
|
+
MiniDiff.fail_attach(buf_id)
|
|
1717
|
+
end)
|
|
1718
|
+
|
|
1719
|
+
local process, stdout_feed = nil, {}
|
|
1720
|
+
local on_exit = function(exit_code)
|
|
1721
|
+
process:close()
|
|
1722
|
+
|
|
1723
|
+
-- Watch index only if there was no error retrieving path to it
|
|
1724
|
+
if exit_code ~= 0 or stdout_feed[1] == nil then return on_not_in_git() end
|
|
1725
|
+
|
|
1726
|
+
-- Set up index watching
|
|
1727
|
+
local git_dir_path = table.concat(stdout_feed, ''):gsub('\n+$', '')
|
|
1728
|
+
H.git_setup_index_watch(buf_id, git_dir_path)
|
|
1729
|
+
|
|
1730
|
+
-- Set reference text immediately
|
|
1731
|
+
H.git_set_ref_text(buf_id)
|
|
1732
|
+
end
|
|
1733
|
+
|
|
1734
|
+
process = vim.loop.spawn('git', spawn_opts, on_exit)
|
|
1735
|
+
H.git_read_stream(stdout, stdout_feed)
|
|
1736
|
+
end
|
|
1737
|
+
|
|
1738
|
+
H.git_setup_index_watch = function(buf_id, git_dir_path)
|
|
1739
|
+
local buf_fs_event, timer = vim.loop.new_fs_event(), vim.loop.new_timer()
|
|
1740
|
+
local buf_git_set_ref_text = function() H.git_set_ref_text(buf_id) end
|
|
1741
|
+
|
|
1742
|
+
local watch_index = function(_, filename, _)
|
|
1743
|
+
if filename ~= 'index' then return end
|
|
1744
|
+
-- Debounce to not overload during incremental staging (like in script)
|
|
1745
|
+
timer:stop()
|
|
1746
|
+
timer:start(50, 0, buf_git_set_ref_text)
|
|
1747
|
+
end
|
|
1748
|
+
buf_fs_event:start(git_dir_path, { recursive = false }, watch_index)
|
|
1749
|
+
|
|
1750
|
+
H.git_invalidate_cache(H.git_cache[buf_id])
|
|
1751
|
+
H.git_cache[buf_id] = { fs_event = buf_fs_event, timer = timer }
|
|
1752
|
+
end
|
|
1753
|
+
|
|
1754
|
+
H.git_set_ref_text = vim.schedule_wrap(function(buf_id)
|
|
1755
|
+
if not vim.api.nvim_buf_is_valid(buf_id) then return end
|
|
1756
|
+
local buf_set_ref_text = vim.schedule_wrap(function(text) pcall(MiniDiff.set_ref_text, buf_id, text) end)
|
|
1757
|
+
|
|
1758
|
+
-- NOTE: Do not cache buffer's name to react to its possible rename
|
|
1759
|
+
local path = H.get_buf_realpath(buf_id)
|
|
1760
|
+
if path == '' then return buf_set_ref_text({}) end
|
|
1761
|
+
local cwd, basename = vim.fn.fnamemodify(path, ':h'), vim.fn.fnamemodify(path, ':t')
|
|
1762
|
+
|
|
1763
|
+
-- Set
|
|
1764
|
+
local stdout = vim.loop.new_pipe()
|
|
1765
|
+
local spawn_opts = { args = { 'show', ':0:./' .. basename }, cwd = cwd, stdio = { nil, stdout, nil } }
|
|
1766
|
+
|
|
1767
|
+
local process, stdout_feed = nil, {}
|
|
1768
|
+
local on_exit = function(exit_code)
|
|
1769
|
+
process:close()
|
|
1770
|
+
|
|
1771
|
+
-- Unset reference text in case of any error. This results into not showing
|
|
1772
|
+
-- hunks at all. Possible reasons to do so:
|
|
1773
|
+
-- - 'Not in index' files (new, ignored, etc.).
|
|
1774
|
+
-- - 'Neither in index nor on disk' files (after checking out commit which
|
|
1775
|
+
-- does not yet have file created).
|
|
1776
|
+
-- - 'Relative can not be used outside working tree' (when opening file
|
|
1777
|
+
-- inside '.git' directory).
|
|
1778
|
+
if exit_code ~= 0 or stdout_feed[1] == nil then return buf_set_ref_text({}) end
|
|
1779
|
+
|
|
1780
|
+
-- Set reference text accounting for possible 'crlf' end of line in index
|
|
1781
|
+
local text = table.concat(stdout_feed, ''):gsub('\r\n', '\n')
|
|
1782
|
+
buf_set_ref_text(text)
|
|
1783
|
+
end
|
|
1784
|
+
|
|
1785
|
+
process = vim.loop.spawn('git', spawn_opts, on_exit)
|
|
1786
|
+
H.git_read_stream(stdout, stdout_feed)
|
|
1787
|
+
end)
|
|
1788
|
+
|
|
1789
|
+
H.git_get_path_data = function(path)
|
|
1790
|
+
-- Get path data needed for proper patch header
|
|
1791
|
+
local cwd, basename = vim.fn.fnamemodify(path, ':h'), vim.fn.fnamemodify(path, ':t')
|
|
1792
|
+
local stdout = vim.loop.new_pipe()
|
|
1793
|
+
local args = { 'ls-files', '-z', '--full-name', '--format=%(objectmode) %(eolinfo:index) %(path)', '--', basename }
|
|
1794
|
+
local spawn_opts = { args = args, cwd = cwd, stdio = { nil, stdout, nil } }
|
|
1795
|
+
|
|
1796
|
+
local process, stdout_feed, res, did_exit = nil, {}, { cwd = cwd }, false
|
|
1797
|
+
local on_exit = function(exit_code)
|
|
1798
|
+
process:close()
|
|
1799
|
+
|
|
1800
|
+
did_exit = true
|
|
1801
|
+
if exit_code ~= 0 then return end
|
|
1802
|
+
-- Parse data about path
|
|
1803
|
+
local out = table.concat(stdout_feed, ''):gsub('(%z\n)+$', '')
|
|
1804
|
+
res.mode_bits, res.eol, res.rel_path = string.match(out, '^(%d+) (%S+) (.*)$')
|
|
1805
|
+
end
|
|
1806
|
+
|
|
1807
|
+
process = vim.loop.spawn('git', spawn_opts, on_exit)
|
|
1808
|
+
H.git_read_stream(stdout, stdout_feed)
|
|
1809
|
+
vim.wait(1000, function() return did_exit end, 1)
|
|
1810
|
+
return res
|
|
1811
|
+
end
|
|
1812
|
+
|
|
1813
|
+
H.git_format_patch = function(buf_id, hunks, path_data)
|
|
1814
|
+
local _, buf_lines = H.get_buftext(buf_id)
|
|
1815
|
+
local ref_lines = vim.split(H.cache[buf_id].ref_text, '\n')
|
|
1816
|
+
|
|
1817
|
+
local res = {
|
|
1818
|
+
string.format('diff --git a/%s b/%s', path_data.rel_path, path_data.rel_path),
|
|
1819
|
+
'index 000000..000000 ' .. path_data.mode_bits,
|
|
1820
|
+
'--- a/' .. path_data.rel_path,
|
|
1821
|
+
'+++ b/' .. path_data.rel_path,
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
-- Take into account changing target ref region as a result of previous hunks
|
|
1825
|
+
local offset = 0
|
|
1826
|
+
local cr_eol = path_data.eol == 'crlf' and '\r' or ''
|
|
1827
|
+
for _, h in ipairs(hunks) do
|
|
1828
|
+
-- "Add" hunks have reference line above target
|
|
1829
|
+
local start = h.ref_start + (h.ref_count == 0 and 1 or 0)
|
|
1830
|
+
|
|
1831
|
+
table.insert(res, string.format('@@ -%d,%d +%d,%d @@', start, h.ref_count, start + offset, h.buf_count))
|
|
1832
|
+
for i = h.ref_start, h.ref_start + h.ref_count - 1 do
|
|
1833
|
+
table.insert(res, '-' .. ref_lines[i] .. cr_eol)
|
|
1834
|
+
end
|
|
1835
|
+
for i = h.buf_start, h.buf_start + h.buf_count - 1 do
|
|
1836
|
+
table.insert(res, '+' .. buf_lines[i] .. cr_eol)
|
|
1837
|
+
end
|
|
1838
|
+
offset = offset + (h.buf_count - h.ref_count)
|
|
1839
|
+
end
|
|
1840
|
+
|
|
1841
|
+
return res
|
|
1842
|
+
end
|
|
1843
|
+
|
|
1844
|
+
H.git_apply_patch = function(path_data, patch)
|
|
1845
|
+
local stdin = vim.loop.new_pipe()
|
|
1846
|
+
local args = { 'apply', '--whitespace=nowarn', '--cached', '--unidiff-zero', '-' }
|
|
1847
|
+
local spawn_opts = { args = args, cwd = path_data.cwd, stdio = { stdin, nil, nil } }
|
|
1848
|
+
local process
|
|
1849
|
+
process = vim.loop.spawn('git', spawn_opts, function() process:close() end)
|
|
1850
|
+
|
|
1851
|
+
-- Write patch, notify that writing is finished (shutdown), and close
|
|
1852
|
+
for _, l in ipairs(patch) do
|
|
1853
|
+
stdin:write(l)
|
|
1854
|
+
stdin:write('\n')
|
|
1855
|
+
end
|
|
1856
|
+
stdin:shutdown(function() stdin:close() end)
|
|
1857
|
+
end
|
|
1858
|
+
|
|
1859
|
+
H.git_read_stream = function(stream, feed)
|
|
1860
|
+
local callback = function(err, data)
|
|
1861
|
+
if data ~= nil then return table.insert(feed, data) end
|
|
1862
|
+
if err then feed[1] = nil end
|
|
1863
|
+
stream:close()
|
|
1864
|
+
end
|
|
1865
|
+
stream:read_start(callback)
|
|
1866
|
+
end
|
|
1867
|
+
|
|
1868
|
+
H.git_invalidate_cache = function(cache)
|
|
1869
|
+
if cache == nil then return end
|
|
1870
|
+
pcall(vim.loop.fs_event_stop, cache.fs_event)
|
|
1871
|
+
pcall(vim.loop.timer_stop, cache.timer)
|
|
1872
|
+
end
|
|
1873
|
+
|
|
1874
|
+
-- Utilities ------------------------------------------------------------------
|
|
1875
|
+
H.error = function(msg) error('(mini.diff) ' .. msg, 0) end
|
|
1876
|
+
|
|
1877
|
+
H.check_type = function(name, val, ref, allow_nil)
|
|
1878
|
+
if type(val) == ref or (ref == 'callable' and vim.is_callable(val)) or (allow_nil and val == nil) then return end
|
|
1879
|
+
H.error(string.format('`%s` should be %s, not %s', name, ref, type(val)))
|
|
1880
|
+
end
|
|
1881
|
+
|
|
1882
|
+
H.notify = function(msg, level_name) vim.notify('(mini.diff) ' .. msg, vim.log.levels[level_name]) end
|
|
1883
|
+
|
|
1884
|
+
H.buf_ensure_loaded = function(buf_id)
|
|
1885
|
+
if type(buf_id) ~= 'number' or vim.api.nvim_buf_is_loaded(buf_id) then return end
|
|
1886
|
+
local cache_eventignore = vim.o.eventignore
|
|
1887
|
+
vim.o.eventignore = 'BufEnter,BufWinEnter'
|
|
1888
|
+
pcall(vim.fn.bufload, buf_id)
|
|
1889
|
+
vim.o.eventignore = cache_eventignore
|
|
1890
|
+
end
|
|
1891
|
+
|
|
1892
|
+
H.map = function(mode, lhs, rhs, opts)
|
|
1893
|
+
if lhs == '' then return end
|
|
1894
|
+
opts = vim.tbl_deep_extend('force', { silent = true }, opts or {})
|
|
1895
|
+
vim.keymap.set(mode, lhs, rhs, opts)
|
|
1896
|
+
end
|
|
1897
|
+
|
|
1898
|
+
H.set_extmark = function(...) pcall(vim.api.nvim_buf_set_extmark, ...) end
|
|
1899
|
+
|
|
1900
|
+
H.get_extmarks = function(...)
|
|
1901
|
+
local ok, res = pcall(vim.api.nvim_buf_get_extmarks, ...)
|
|
1902
|
+
if not ok then return {} end
|
|
1903
|
+
return res
|
|
1904
|
+
end
|
|
1905
|
+
|
|
1906
|
+
H.clear_namespace = function(...) pcall(vim.api.nvim_buf_clear_namespace, ...) end
|
|
1907
|
+
|
|
1908
|
+
H.is_buf_text = function(buf_id)
|
|
1909
|
+
local n = vim.api.nvim_buf_call(buf_id, function() return vim.fn.byte2line(1024) end)
|
|
1910
|
+
local lines = vim.api.nvim_buf_get_lines(buf_id, 0, n, false)
|
|
1911
|
+
return table.concat(lines, ''):find('\0') == nil
|
|
1912
|
+
end
|
|
1913
|
+
|
|
1914
|
+
H.get_buftext = function(buf_id)
|
|
1915
|
+
local lines = vim.api.nvim_buf_get_lines(buf_id, 0, -1, false)
|
|
1916
|
+
-- - NOTE: Appending '\n' makes more intuitive diffs at end-of-file
|
|
1917
|
+
local text = table.concat(lines, '\n') .. '\n'
|
|
1918
|
+
if not vim.bo[buf_id].bomb then return text, lines end
|
|
1919
|
+
local bytes = H.bom_bytes[vim.bo[buf_id].fileencoding] or ''
|
|
1920
|
+
lines[1] = bytes .. lines[1]
|
|
1921
|
+
return bytes .. text, lines
|
|
1922
|
+
end
|
|
1923
|
+
|
|
1924
|
+
-- Try getting buffer's full real path (after resolving symlinks)
|
|
1925
|
+
H.get_buf_realpath = function(buf_id) return vim.loop.fs_realpath(vim.api.nvim_buf_get_name(buf_id)) or '' end
|
|
1926
|
+
|
|
1927
|
+
-- nvim__redraw replaced nvim__buf_redraw_range during the 0.10 release cycle
|
|
1928
|
+
H.redraw_buffer = function(buf_id)
|
|
1929
|
+
vim.api.nvim__buf_redraw_range(buf_id, 0, -1)
|
|
1930
|
+
|
|
1931
|
+
-- Redraw statusline to have possible statusline component up to date
|
|
1932
|
+
vim.cmd('redrawstatus')
|
|
1933
|
+
end
|
|
1934
|
+
if vim.api.nvim__redraw ~= nil then
|
|
1935
|
+
H.redraw_buffer = function(buf_id) vim.api.nvim__redraw({ buf = buf_id, valid = true, statusline = true }) end
|
|
1936
|
+
end
|
|
1937
|
+
|
|
1938
|
+
return MiniDiff
|