pax_global_header 0000666 0000000 0000000 00000000064 14765572704 0014533 g ustar 00root root 0000000 0000000 52 comment=7010000889bfb6c26065e0b0f7f1e6aa9163edd9
lewis6991-gitsigns.nvim-7010000/ 0000775 0000000 0000000 00000000000 14765572704 0016155 5 ustar 00root root 0000000 0000000 lewis6991-gitsigns.nvim-7010000/.editorconfig 0000664 0000000 0000000 00000000426 14765572704 0020634 0 ustar 00root root 0000000 0000000 root = true
[*]
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
charset = utf-8
[*.lua]
indent_style = space
indent_size = 2
[Makefile]
indent_style = tab
lewis6991-gitsigns.nvim-7010000/.gitattributes 0000664 0000000 0000000 00000000035 14765572704 0021046 0 ustar 00root root 0000000 0000000 doc/* linguist-documentation
lewis6991-gitsigns.nvim-7010000/.github/ 0000775 0000000 0000000 00000000000 14765572704 0017515 5 ustar 00root root 0000000 0000000 lewis6991-gitsigns.nvim-7010000/.github/FUNDING.yml 0000664 0000000 0000000 00000000024 14765572704 0021326 0 ustar 00root root 0000000 0000000 github: [lewis6991]
lewis6991-gitsigns.nvim-7010000/.github/ISSUE_TEMPLATE/ 0000775 0000000 0000000 00000000000 14765572704 0021700 5 ustar 00root root 0000000 0000000 lewis6991-gitsigns.nvim-7010000/.github/ISSUE_TEMPLATE/bug_report.yaml 0000664 0000000 0000000 00000006114 14765572704 0024736 0 ustar 00root root 0000000 0000000 name: Bug report
description: Report a problem with Gitsigns
labels: [bug]
body:
- type: markdown
attributes:
value: >
Before reporting make sure that Gitsigns is updated to the latest version.
- type: textarea
attributes:
label: "Description"
description: "A comprehensive description of the problem you are reporting."
validations:
required: true
- type: input
attributes:
label: "Neovim version"
description: |
Output of `nvim --version`
validations:
required: true
- type: input
attributes:
label: "Operating system and version"
validations:
required: true
- type: textarea
attributes:
label: "Expected behavior"
description: "A description of the behavior you expected:"
- type: textarea
attributes:
label: "Actual behavior"
description: "Observed behavior (may optionally include logs, images, or videos)."
validations:
required: true
- type: textarea
attributes:
label: "Minimal config"
description: >
Minimal(!) configuration necessary to reproduce the issue. Save this as
`minimal.lua`. If necessary, add plugins and config options from your
`init.lua` at the indicated lines.
Please **DO NOT** include a package manager (such as `lazy.nvim`) in this.
render: Lua
value: |
for name, url in pairs{
gitsigns = 'https://github.com/lewis6991/gitsigns.nvim',
-- ADD OTHER PLUGINS _NECESSARY_ TO REPRODUCE THE ISSUE
} do
local install_path = vim.fn.fnamemodify('gitsigns_issue/'..name, ':p')
if vim.fn.isdirectory(install_path) == 0 then
vim.fn.system { 'git', 'clone', '--depth=1', url, install_path }
end
vim.opt.runtimepath:append(install_path)
end
require('gitsigns').setup{
debug_mode = true, -- You must add this to enable debug messages
-- ADD GITSIGNS CONFIG THAT IS _NECESSARY_ FOR REPRODUCING THE ISSUE
}
-- ADD INIT.LUA SETTINGS THAT IS _NECESSARY_ FOR REPRODUCING THE ISSUE
validations:
required: true
- type: textarea
attributes:
label: "Steps to reproduce"
description: >
Steps to reproduce using the minimal config provided.
If this is not provided, the issue may be closed without notice.
value: |
1. `mkdir gitsigns_issue`
2. `cd gitsigns_issue`
3. `git init`
4. `touch file`
5. `git add file`
6. `git commit -m 'initial commit'`
7. `nvim --clean -u minimal.lua file`
8. ...
validations:
required: true
- type: textarea
attributes:
label: "Gitsigns debug messages"
render: text
description: >
Please include the output of `:Gitsigns debug_messages`.
Note: You must have `debug_mode = true` in `setup()`.
- type: textarea
attributes:
label: "Gitsigns cache"
render: lua
description: >
If you think it's relevant maybe also provide the output of
`:Gitsigns dump_cache`.
lewis6991-gitsigns.nvim-7010000/.github/ISSUE_TEMPLATE/config.yml 0000664 0000000 0000000 00000000307 14765572704 0023670 0 ustar 00root root 0000000 0000000 blank_issues_enabled: true
contact_links:
- name: Question
url: https://gitter.im/gitsigns-nvim/community
about: Usage questions and support requests are answered in the Gitsigns Gitter
lewis6991-gitsigns.nvim-7010000/.github/workflows/ 0000775 0000000 0000000 00000000000 14765572704 0021552 5 ustar 00root root 0000000 0000000 lewis6991-gitsigns.nvim-7010000/.github/workflows/ci.yml 0000664 0000000 0000000 00000003775 14765572704 0022704 0 ustar 00root root 0000000 0000000 name: CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
commit_lint:
runs-on: ubuntu-latest
steps:
# Check commit messages
- uses: webiny/action-conventional-commits@v1.3.0
test:
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
# Nightly can often fail as it is a moving target so disable fail-fast so
# we can always see if the over versions pass or not.
fail-fast: false
matrix:
neovim_branch:
- 'v0.9.5'
- 'v0.10.0'
- 'nightly'
env:
NVIM_TEST_VERSION: ${{ matrix.neovim_branch }}
steps:
- name: Checkout
uses: actions/checkout@v4
- uses: leafo/gh-actions-lua@v9
with:
luaVersion: "5.1.5"
- uses: leafo/gh-actions-luarocks@v4
- name: Download nvim-test
run: make nvim-test
- name: Run Test
run: make test
stylua:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Download stylua
run: make stylua
- name: Lint
run: make stylua-check
luals:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- uses: leafo/gh-actions-lua@v9
with:
luaVersion: "5.1.5"
- uses: leafo/gh-actions-luarocks@v4
- name: Download nvim-test
run: make nvim-test
- name: Download LuaLS
run: make luals
- name: Lint
run: make luals-check
doc:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- uses: leafo/gh-actions-lua@v10
with:
luaVersion: "5.1.5"
- uses: leafo/gh-actions-luarocks@v4
- name: Download nvim-test
run: make nvim-test
- name: Doc Check
run: make doc-check
lewis6991-gitsigns.nvim-7010000/.github/workflows/release-please.yml 0000664 0000000 0000000 00000004713 14765572704 0025171 0 ustar 00root root 0000000 0000000 on:
push:
branches:
- main
name: release-please
permissions:
contents: write
pull-requests: write
jobs:
release-please:
runs-on: ubuntu-latest
outputs:
release_created: ${{ steps.release.outputs.release_created }}
tag_name: ${{ steps.release.outputs.tag_name }}
steps:
- uses: googleapis/release-please-action@v4
id: release
- uses: actions/checkout@v4
if: ${{ steps.release.outputs.release_created }}
- name: tag stable versions
if: ${{ steps.release.outputs.release_created }}
run: |
git config user.name github-actions[bot]
git config user.email github-actions[bot]@users.noreply.github.com
git remote add gh-token "https://${{ secrets.GITHUB_TOKEN }}@github.com/google-github-actions/release-please-action.git"
git tag -f -a release -m "Last Stable Release"
git push origin release -f
luarocks-upload:
needs: release-please
if: ${{ needs.release-please.outputs.release_created }}
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Required to count the commits
- name: Get Version
run: echo "LUAROCKS_VERSION=${{ needs.release-please.outputs.tag_name }}" >> $GITHUB_ENV
- name: LuaRocks Upload
uses: nvim-neorocks/luarocks-tag-release@v5
env:
LUAROCKS_API_KEY: ${{ secrets.LUAROCKS_API_KEY }}
with:
version: ${{ env.LUAROCKS_VERSION }}
update-doc:
needs: release-please
if: ${{ ! needs.release-please.outputs.release_created }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: release-please--branches--main
# Fetch the last 2 commits instead of just 1. (Fetching just 1 commit would overwrite the whole history)
fetch-depth: 2
- uses: leafo/gh-actions-lua@v10
with:
luaVersion: "5.1.5"
- uses: leafo/gh-actions-luarocks@v4
- name: Update doc
run: make gen_help
- name: Update PR
run: |
git config user.name github-actions[bot]
git config user.email github-actions[bot]@users.noreply.github.com
git remote add gh-token "https://${{ secrets.GITHUB_TOKEN }}@github.com/google-github-actions/release-please-action.git"
git diff
git add doc
git commit --amend --no-edit
git push --force
lewis6991-gitsigns.nvim-7010000/.github/workflows/response.yml 0000664 0000000 0000000 00000004013 14765572704 0024131 0 ustar 00root root 0000000 0000000 name: stale
on:
schedule:
- cron: '30 1 * * *' # Run every day at 01:30
issue_comment:
jobs:
stale:
if: github.event_name == 'schedule'
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v9
with:
stale-issue-message: >
This has been labeled stale since a request for information has
not been answered for 30 days.
close-issue-message: >
This has been closed since it has been marked stale for 5 days.
It can be reopened when the requested information is provided.
days-before-stale: 30
days-before-close: 5
any-of-labels: needs response
remove_label:
if: github.event_name == 'issue_comment'
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/checkout@v4
- uses: actions/github-script@v7
with:
script: |
const issue = await github.rest.issues.get({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const author = issue.data.user.login;
const commenter = context.actor;
if (author === commenter) {
const labels = issue.data.labels.map((e) => e.name);
if (labels.includes("needs response")) {
github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
name: "needs response",
});
}
if (labels.includes("Stale")) {
github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
name: "Stale",
});
}
}
lewis6991-gitsigns.nvim-7010000/.gitignore 0000664 0000000 0000000 00000000017 14765572704 0020143 0 ustar 00root root 0000000 0000000 doc/tags
deps
lewis6991-gitsigns.nvim-7010000/.luarc.json 0000664 0000000 0000000 00000001521 14765572704 0020233 0 ustar 00root root 0000000 0000000 {
"$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json",
"runtime": {
"version": "LuaJIT"
},
"workspace": {
"library": [
"lua",
"$VIMRUNTIME",
"nvim-test",
"${3rd}/busted/library",
"${3rd}/luassert/library",
"${3rd}/luv/library"
],
"checkThirdParty": false
},
"diagnostics": {
"libraryFiles": "Disable",
"groupFileStatus": {
"strict": "Opened",
"strong": "Opened",
"ambiguity" : "Opened",
"duplicate" : "Opened",
"global" : "Opened",
"luadoc" : "Opened",
"redefined" : "Opened",
"type-check" : "Opened",
"unbalanced" : "Opened",
"unused" : "Opened"
},
"groupSeverity": {
"strong": "Warning",
"strict": "Warning"
},
"unusedLocalExclude": [ "_*" ]
}
}
lewis6991-gitsigns.nvim-7010000/.release-please-manifest.json 0000664 0000000 0000000 00000000025 14765572704 0023616 0 ustar 00root root 0000000 0000000 {
".": "1.0.2"
}
lewis6991-gitsigns.nvim-7010000/.stylua.toml 0000664 0000000 0000000 00000000216 14765572704 0020450 0 ustar 00root root 0000000 0000000 column_width = 100
line_endings = "Unix"
indent_type = "Spaces"
indent_width = 2
quote_style = "AutoPreferSingle"
call_parentheses = "Always"
lewis6991-gitsigns.nvim-7010000/CHANGELOG.md 0000664 0000000 0000000 00000064535 14765572704 0020003 0 ustar 00root root 0000000 0000000 # Changelog
## [1.0.2](https://github.com/lewis6991/gitsigns.nvim/compare/v1.0.1...v1.0.2) (2025-03-16)
### Bug Fixes
* `stage_hunk` on staged hunk should behave like `undo_stage_hunk` ([5fefc7b](https://github.com/lewis6991/gitsigns.nvim/commit/5fefc7bf6966f9a1ca961ac2fca0f9d93118df18))
* change_base with empty base ([751bfae](https://github.com/lewis6991/gitsigns.nvim/commit/751bfae26a3561394afcafdf92b0dc52988ce436))
## [1.0.1](https://github.com/lewis6991/gitsigns.nvim/compare/v1.0.0...v1.0.1) (2025-02-15)
### Bug Fixes
* **blame:** fix popup menu shortcut mappings ([420b199](https://github.com/lewis6991/gitsigns.nvim/commit/420b19971c22ba7558dfd39ec1c1c2735c7db93f)), closes [#1215](https://github.com/lewis6991/gitsigns.nvim/issues/1215)
* **current_line_blame:** last line show not committed ([8b00147](https://github.com/lewis6991/gitsigns.nvim/commit/8b00147519d6f8353867d5d0b55f587306b0cfb6)), closes [#1213](https://github.com/lewis6991/gitsigns.nvim/issues/1213)
* stylua ([2bc3b47](https://github.com/lewis6991/gitsigns.nvim/commit/2bc3b472bbc2484214549af4d9f38c127b886a55))
## [1.0.0](https://github.com/lewis6991/gitsigns.nvim/compare/v0.9.0...v1.0.0) (2025-02-07)
### โ BREAKING CHANGES
* deprecate some functions
* **blame:** replace dot with dash in blame file type name
* remove current_line_blame_formatter_opts
* remove support for yadm
* **config:** deprecate highlight groups in config.signs
### Features
* add highlights for the current line ([b29cb58](https://github.com/lewis6991/gitsigns.nvim/commit/b29cb58126663569f6f34401fab513c2375e95d3))
* add staging and update locks ([e6e3c3a](https://github.com/lewis6991/gitsigns.nvim/commit/e6e3c3a1394d9e0a1c75d8620f8631e4a6ecde0e))
* add submodule support for gitsigns urls ([f074844](https://github.com/lewis6991/gitsigns.nvim/commit/f074844b60f9e151970fbcdbeb8a2cd52b6ef25a)), closes [#1095](https://github.com/lewis6991/gitsigns.nvim/issues/1095)
* add type annotations for modules ([ac5aba6](https://github.com/lewis6991/gitsigns.nvim/commit/ac5aba6dce8c06ea22bea2c9016f51a2dbf90dc7))
* **async:** add async.pcall ([562dc47](https://github.com/lewis6991/gitsigns.nvim/commit/562dc47189ad3c8696dbf460d38603a74d544849))
* **blame_line:** add option to show when not focused ([2667904](https://github.com/lewis6991/gitsigns.nvim/commit/2667904fb0ee62832c55b56acb9ade3e02a0c202))
* **blame:** add `Gitsigns blame` ([25b6ee4](https://github.com/lewis6991/gitsigns.nvim/commit/25b6ee4be514b38d5bfe950d790a67042e05ef35))
* **blame:** add reblame at commit parent ([f4928ba](https://github.com/lewis6991/gitsigns.nvim/commit/f4928ba14eb6c667786ac7d69927f6aee6719f1e))
* **blame:** run formatter with pcall ([9ca00df](https://github.com/lewis6991/gitsigns.nvim/commit/9ca00df1c84fc0a1ed18c79156c06b081dc1da1f))
* **blame:** set filetype to gitsigns.blame ([0dc8866](https://github.com/lewis6991/gitsigns.nvim/commit/0dc886637f9686b7cfd245a4726f93abeab19d4a)), closes [#1049](https://github.com/lewis6991/gitsigns.nvim/issues/1049)
* **config:** deprecate highlight groups in config.signs ([3d7e49c](https://github.com/lewis6991/gitsigns.nvim/commit/3d7e49c201537ee0293a1a3abe67b67f8e7648a5))
* **config:** improve deprecation message ([fa42613](https://github.com/lewis6991/gitsigns.nvim/commit/fa42613096ebfa5fee1ea87d70f8625ab9685d01))
* deprecate some functions ([8b74e56](https://github.com/lewis6991/gitsigns.nvim/commit/8b74e560f7cba19b45b7d72a3cf8fb769316d259))
* detect repo errors ([899e993](https://github.com/lewis6991/gitsigns.nvim/commit/899e993850084ea33d001ec229d237bc020c19ae))
* **nav:** add target option ([9291836](https://github.com/lewis6991/gitsigns.nvim/commit/929183666540e164fa74028954ade62fa703fa1a))
* nicer errors for failed stages ([9521fe8](https://github.com/lewis6991/gitsigns.nvim/commit/9521fe8be39255b9abc6ec54e352bf04c410f5cf))
* remove current_line_blame_formatter_opts ([92a8fbb](https://github.com/lewis6991/gitsigns.nvim/commit/92a8fbb8453571978468e4ad2d4f8cd302d79eab))
* remove support for yadm ([61f5b64](https://github.com/lewis6991/gitsigns.nvim/commit/61f5b6407611a25e2d407ac0bc60e5c87c25ad72))
* set bufname for commit buffers ([e4efe9b](https://github.com/lewis6991/gitsigns.nvim/commit/e4efe9b99b7c473e9f917edf441cec48c05fd99e))
* share Repo objects across buffers ([2593efa](https://github.com/lewis6991/gitsigns.nvim/commit/2593efa3c53f41987d99bf8727f67154e88c0c91))
* **signs:** able staged signs by default ([b8cf5e8](https://github.com/lewis6991/gitsigns.nvim/commit/b8cf5e8efaa0036d493a2e2dfed768c3a03fac73))
* **signs:** improve sign generation from hunks ([2d2156a](https://github.com/lewis6991/gitsigns.nvim/commit/2d2156a2f8c6babbf5f10aea6df23993416f0f28))
* tweak how commit buffers are processed ([47c8e3e](https://github.com/lewis6991/gitsigns.nvim/commit/47c8e3e571376b24de62408fd0c9d12f0a9fc0a3))
* use luajit buffers to serialize thread data ([233bcbf](https://github.com/lewis6991/gitsigns.nvim/commit/233bcbfda3a04e19ae4fb365a8cbd32d9aa8c0d1))
### Bug Fixes
* [#1182](https://github.com/lewis6991/gitsigns.nvim/issues/1182) ([632fda7](https://github.com/lewis6991/gitsigns.nvim/commit/632fda72df903255dc1683cd739dceaa7338128a))
* [#1182](https://github.com/lewis6991/gitsigns.nvim/issues/1182) (take 2) ([3868c17](https://github.com/lewis6991/gitsigns.nvim/commit/3868c176d406b217ec8961e47ad033105ddc486c))
* [#1185](https://github.com/lewis6991/gitsigns.nvim/issues/1185) ([8fb9e75](https://github.com/lewis6991/gitsigns.nvim/commit/8fb9e7515d38c042f26bfa894a0b7cb36e27c895))
* [#1185](https://github.com/lewis6991/gitsigns.nvim/issues/1185) (take 2) ([fd7457f](https://github.com/lewis6991/gitsigns.nvim/commit/fd7457fa13b7b5c63b5dc164c6cbf9192fbe72d1))
* [#1185](https://github.com/lewis6991/gitsigns.nvim/issues/1185) (take 3) ([f301005](https://github.com/lewis6991/gitsigns.nvim/commit/f301005d8eaa15ef61ed6e7dbaa8c5193541ac37))
* [#1187](https://github.com/lewis6991/gitsigns.nvim/issues/1187) ([d8918f0](https://github.com/lewis6991/gitsigns.nvim/commit/d8918f06624dd53b9a82bd0e29c31bcfd541b40d))
* add cli binding for show ([d9f997d](https://github.com/lewis6991/gitsigns.nvim/commit/d9f997dba757be01434ed3538d202f88286df476))
* add nil check ([9d80331](https://github.com/lewis6991/gitsigns.nvim/commit/9d803313b7384bd52e0a9ad19307e9ae774fc926))
* add nil check ([7178d1a](https://github.com/lewis6991/gitsigns.nvim/commit/7178d1a430dcfff8a4c92d78b9e39e0297a779c0))
* **attach:** do not attach to fugitive directory buffers ([cf1ffe6](https://github.com/lewis6991/gitsigns.nvim/commit/cf1ffe682d3ac3a3cb89a7bdf50cc15ff1fadb8e)), closes [#1198](https://github.com/lewis6991/gitsigns.nvim/issues/1198)
* **attach:** resolve error viewing fugitive trees ([#1058](https://github.com/lewis6991/gitsigns.nvim/issues/1058)) ([89a4dce](https://github.com/lewis6991/gitsigns.nvim/commit/89a4dce7c94c40c89774d3cb3a7788a9ecf412c0))
* **blame:** ensure blame object is valid when all lines are requested ([817bd84](https://github.com/lewis6991/gitsigns.nvim/commit/817bd848fffe82e697b4da656e3f2834cd0665c5)), closes [#1156](https://github.com/lewis6991/gitsigns.nvim/issues/1156)
* **blame:** handle incremental output with a buffered reader ([e9c4187](https://github.com/lewis6991/gitsigns.nvim/commit/e9c4187c3774a46df2d086a66cf3a7e6bea4c432)), closes [#1084](https://github.com/lewis6991/gitsigns.nvim/issues/1084)
* **blame:** include error message in error ([d03a1c9](https://github.com/lewis6991/gitsigns.nvim/commit/d03a1c9a1045122823af97e351719227ed3718eb))
* **blame:** parse blame info correctly ([0595724](https://github.com/lewis6991/gitsigns.nvim/commit/0595724fa9516a35696ff6b1e3cb95b6462b38b1)), closes [#1065](https://github.com/lewis6991/gitsigns.nvim/issues/1065)
* **blame:** popupmenu error ([93c38d9](https://github.com/lewis6991/gitsigns.nvim/commit/93c38d97260330e8501ccda1e6000c858af0d603)), closes [#1061](https://github.com/lewis6991/gitsigns.nvim/issues/1061)
* **blame:** render blame end_col out of range ([aa12bb9](https://github.com/lewis6991/gitsigns.nvim/commit/aa12bb9cd22f1a612dd9cda6c6fc26475e94fc4f))
* **blame:** replace dot with dash in blame file type name ([0ed4669](https://github.com/lewis6991/gitsigns.nvim/commit/0ed466953fe5885166e0d60799172a8b1f752d16))
* **blame:** respect original blame winbar ([d0db8ef](https://github.com/lewis6991/gitsigns.nvim/commit/d0db8ef6a0489ed6af0baacb101a7b733c5d5de1))
* **blame:** restore original options when blame window is closed ([564849a](https://github.com/lewis6991/gitsigns.nvim/commit/564849a17bf5c5569e0bae98c8328de9c7a1ed29))
* **blame:** show current buffer line blame immediately ([6b1a14e](https://github.com/lewis6991/gitsigns.nvim/commit/6b1a14eabcebbcca1b9e9163a26b2f8371364cb7))
* **blame:** show same commit twice or more ([#1136](https://github.com/lewis6991/gitsigns.nvim/issues/1136)) ([ee7634a](https://github.com/lewis6991/gitsigns.nvim/commit/ee7634ab4f0a6606438fe13e16cbf2065589a5ed))
* **blame:** show the winbar if the main window has it enabled ([17e8fd6](https://github.com/lewis6991/gitsigns.nvim/commit/17e8fd66182c9ad79dc129451ad015af3d27529c))
* **blame:** track buffers changes correctly in the cache ([0349546](https://github.com/lewis6991/gitsigns.nvim/commit/0349546134d8a3a3c3a33e2e781b8d7bd07ea156))
* **blame:** update current_line_blame when attaching ([8df63f2](https://github.com/lewis6991/gitsigns.nvim/commit/8df63f2ddc615feb71fd4aee45a4cee022876df1))
* change assert to eprint ([7c27a30](https://github.com/lewis6991/gitsigns.nvim/commit/7c27a30450130cd59c4994a6755e3c5d74d83e76))
* derive Staged*Cul highlight correctly ([1d2cb56](https://github.com/lewis6991/gitsigns.nvim/commit/1d2cb568a7105a860941ef45a01b13709d7aa9d2))
* diffthis vertical option ([dcdcfcb](https://github.com/lewis6991/gitsigns.nvim/commit/dcdcfcb15eb7c6fc6023dbf03e9644e9d5b2f484))
* do not mix staged signs with normal signs ([9541f5e](https://github.com/lewis6991/gitsigns.nvim/commit/9541f5e8e24571723cb02a5c2bf078aeacc5a711)), closes [#1152](https://github.com/lewis6991/gitsigns.nvim/issues/1152)
* do not show staged signs for different bases ([0edca9d](https://github.com/lewis6991/gitsigns.nvim/commit/0edca9d1a06db1ae95d79c210825711172fb2802)), closes [#1118](https://github.com/lewis6991/gitsigns.nvim/issues/1118)
* **docs:** Add signs_staged to default config in README ([d44a794](https://github.com/lewis6991/gitsigns.nvim/commit/d44a7948ffc717af578c424add818b7684c7ed68))
* fileformat autocmd bug ([f41b934](https://github.com/lewis6991/gitsigns.nvim/commit/f41b934e70e2ae9b0a7a3cb1a5a7d172a4d8f1fd)), closes [#1123](https://github.com/lewis6991/gitsigns.nvim/issues/1123)
* get the repo version of the username ([2e5719c](https://github.com/lewis6991/gitsigns.nvim/commit/2e5719c79aead05c4269d6bd250acbc9c4d26d37))
* GitSignsChanged autocmd for staged hunks ([ac38d78](https://github.com/lewis6991/gitsigns.nvim/commit/ac38d7860b258ec07085d8d1931e1a487bcee21d)), closes [#1168](https://github.com/lewis6991/gitsigns.nvim/issues/1168)
* handle repos with no commits ([0cd4f0a](https://github.com/lewis6991/gitsigns.nvim/commit/0cd4f0aa1067b7261f0649b3124e1159dac3df8b))
* handle terminal-only highlights ([356df59](https://github.com/lewis6991/gitsigns.nvim/commit/356df59308d8b87486644d2324d7558ac0f3db36))
* help triggering text autocmds ([2a7b39f](https://github.com/lewis6991/gitsigns.nvim/commit/2a7b39f4d282935f8b44cbe82879af69c7472f5c))
* improve support for worktrees in bare repos ([6811483](https://github.com/lewis6991/gitsigns.nvim/commit/68114837e81ca16d06514c3a997c9102d1b25c15)), closes [#1160](https://github.com/lewis6991/gitsigns.nvim/issues/1160)
* lint ([39b5b6f](https://github.com/lewis6991/gitsigns.nvim/commit/39b5b6f48bde0595ce68007ffce408c5d7ac1f79))
* more EOL fixes ([f10fdda](https://github.com/lewis6991/gitsigns.nvim/commit/f10fddafe06f7ab7931031b394a26b2f3f434f3e)), closes [#1145](https://github.com/lewis6991/gitsigns.nvim/issues/1145)
* **nav:** misc bugs ([7516bac](https://github.com/lewis6991/gitsigns.nvim/commit/7516bac5639a9ce8e7b199066199a02cb3057230))
* nil check for repo cache ([375c44b](https://github.com/lewis6991/gitsigns.nvim/commit/375c44bdfdde25585466a966f00c2e291db74f2d))
* nil check for repo info ([e784e5a](https://github.com/lewis6991/gitsigns.nvim/commit/e784e5a078f993f7218b8a857cb581d5b9ca42dc))
* partial staging of staged signs ([31d2dcd](https://github.com/lewis6991/gitsigns.nvim/commit/31d2dcd144c7404dacbd2ca36b5abd37cc9fa506))
* random errors from blame autocommands ([#1139](https://github.com/lewis6991/gitsigns.nvim/issues/1139)) ([2d725fd](https://github.com/lewis6991/gitsigns.nvim/commit/2d725fdd7fe4a612fa3171ca0a965f455d8dc325))
* **repo:** make sure --git-dir is always provided --work-tree ([310018d](https://github.com/lewis6991/gitsigns.nvim/commit/310018d54357b8a3cbbcd2b7f589d12e61d2db35))
* reset diff when quiting diff buffer ([b544bd6](https://github.com/lewis6991/gitsigns.nvim/commit/b544bd62623ca1b483d8b9bfb6d65805f112a320)), closes [#1155](https://github.com/lewis6991/gitsigns.nvim/issues/1155)
* revision buffer name parsing for index buffers ([76d88f3](https://github.com/lewis6991/gitsigns.nvim/commit/76d88f3b584e1f83b2aa51663a32cc6ee8d97eff))
* select hunk gets all adjacent linematch hunks ([abc6dec](https://github.com/lewis6991/gitsigns.nvim/commit/abc6dec92232944108250e321858014bf79de245)), closes [#1133](https://github.com/lewis6991/gitsigns.nvim/issues/1133)
* **select_hunk:** compatible with <cmd> mapping ([8974fd3](https://github.com/lewis6991/gitsigns.nvim/commit/8974fd397e854bfa13a5130dc32ee357dbade276))
* setqflist("all") should respect change_base ([58bd9e9](https://github.com/lewis6991/gitsigns.nvim/commit/58bd9e98d8e3c5a1c98af312e85247ee1afd3ed2))
* **signs:** avoid placing signs on lnum 0 ([2f9f20e](https://github.com/lewis6991/gitsigns.nvim/commit/2f9f20ea3baacc077e940b7878a46a8295129418))
* sort get_nav_hunks to handle mixed hunk states ([80214a8](https://github.com/lewis6991/gitsigns.nvim/commit/80214a857ce512cc64964abddc1d8eb5a3e28396))
* string.buffer not found ([8639036](https://github.com/lewis6991/gitsigns.nvim/commit/863903631e676b33e8be2acb17512fdc1b80b4fb)), closes [#1126](https://github.com/lewis6991/gitsigns.nvim/issues/1126)
* support blame for git < 2.41 ([a5b801e](https://github.com/lewis6991/gitsigns.nvim/commit/a5b801e7b16220e75d459919edcb5eb37b1de9cb)), closes [#1093](https://github.com/lewis6991/gitsigns.nvim/issues/1093)
* toggle_current_line_blame ([0e39e9a](https://github.com/lewis6991/gitsigns.nvim/commit/0e39e9afcfc180d55ac8f0691a230703683ddb0f)), closes [#1072](https://github.com/lewis6991/gitsigns.nvim/issues/1072)
* typo on dprint ([6f8dbdb](https://github.com/lewis6991/gitsigns.nvim/commit/6f8dbdbd41725fa11178e78d6e4c987038a8ece9))
* upstream fixes for system() ([c2a2739](https://github.com/lewis6991/gitsigns.nvim/commit/c2a273980eb2cbcabcd54690f06f041ea0c225c6))
* use non-deprecated versions of vim.validate ([0883d0f](https://github.com/lewis6991/gitsigns.nvim/commit/0883d0f67c1b728713deeddfcec4aabf71410801))
* use norm! to prevent user remapping ([4daf702](https://github.com/lewis6991/gitsigns.nvim/commit/4daf7022f1481edf1e8fb9947df13bb07c18e89a))
* **util:** ignore endofline when running blame ([def49e4](https://github.com/lewis6991/gitsigns.nvim/commit/def49e48c6329527e344d0c99a0d2cd9fdf6bb84))
* wait for buffer to attach in M.show ([1c128d4](https://github.com/lewis6991/gitsigns.nvim/commit/1c128d4585d89f39ddea9ef9f5f6b84edd3b66b9)), closes [#1091](https://github.com/lewis6991/gitsigns.nvim/issues/1091)
* **watcher:** do not ignore any updates ([5840f89](https://github.com/lewis6991/gitsigns.nvim/commit/5840f89c50b7af6b2f9c30e7fe37b797aef60ba9))
* **watcher:** fix debounce ([f846c50](https://github.com/lewis6991/gitsigns.nvim/commit/f846c507242a74d9a458bff2d029bd2eae8c0ca1)), closes [#1046](https://github.com/lewis6991/gitsigns.nvim/issues/1046)
* wipeout buf after closing the blame_line/preview_hunk window ([abcd00a](https://github.com/lewis6991/gitsigns.nvim/commit/abcd00a7d5bc1a9470cb21b023c575acade3e4db))
### Performance Improvements
* **blame:** some improvements ([9cdfcb5](https://github.com/lewis6991/gitsigns.nvim/commit/9cdfcb5f038586c36ad8b010f7e479f6a6f95a63))
## [0.9.0](https://github.com/lewis6991/gitsigns.nvim/compare/v0.8.1...v0.9.0) (2024-06-12)
### โ BREAKING CHANGES
* **setup:** make setup() synchronous
* drop support for nvim v0.8
### Features
* drop support for nvim v0.8 ([d9d94e0](https://github.com/lewis6991/gitsigns.nvim/commit/d9d94e055a19415767bb073e8dd86028105c4319))
* **setup:** make setup() synchronous ([720061a](https://github.com/lewis6991/gitsigns.nvim/commit/720061aa152faedfe4099dfb92d2b3fcb0e55edc))
### Bug Fixes
* add workaround for Lazy issue ([e31d214](https://github.com/lewis6991/gitsigns.nvim/commit/e31d2149d9f3fb056bfd5b3416b2e818be10aabe))
* **attach:** allow attaching inside .git/ ([9cafac3](https://github.com/lewis6991/gitsigns.nvim/commit/9cafac31a091267838e1e90fd6e083d37611f516)), closes [#923](https://github.com/lewis6991/gitsigns.nvim/issues/923)
* **attach:** detach on when the buffer name changes ([75dc649](https://github.com/lewis6991/gitsigns.nvim/commit/75dc649106827183547d3bedd4602442340d2f7f)), closes [#1021](https://github.com/lewis6991/gitsigns.nvim/issues/1021)
* **attach:** fix worktree attaching ([54b9df4](https://github.com/lewis6991/gitsigns.nvim/commit/54b9df401b8f21f4e6ca537ec47a109394aaccd7)), closes [#1020](https://github.com/lewis6991/gitsigns.nvim/issues/1020)
* **blame:** avoid right-aligned blame overlapping buftext ([20f305d](https://github.com/lewis6991/gitsigns.nvim/commit/20f305d63bc86852821ac47d9967e73931f7130b))
* handle untracked files for custom bases ([af3fdad](https://github.com/lewis6991/gitsigns.nvim/commit/af3fdad8ddcadbdad835975204f6503310526fd9)), closes [#1022](https://github.com/lewis6991/gitsigns.nvim/issues/1022)
* scheduling in cwd watching ([c96e3cf](https://github.com/lewis6991/gitsigns.nvim/commit/c96e3cf4767ee98030bff855e7a6f07cfc6d427f))
* **update:** always get object contents from object names ([a28bb1d](https://github.com/lewis6991/gitsigns.nvim/commit/a28bb1db506df663b063cc63f44fbbda178255a7)), closes [#847](https://github.com/lewis6991/gitsigns.nvim/issues/847)
* use latest api in 0.10 ([bc933d2](https://github.com/lewis6991/gitsigns.nvim/commit/bc933d24a669608968ff4791b14d2d9554813a65))
* **util:** close file after reading ([f65d1d8](https://github.com/lewis6991/gitsigns.nvim/commit/f65d1d82013e032ca6c199b62f08089b420b068c))
* **watcher:** throttle watcher handler ([de18f6b](https://github.com/lewis6991/gitsigns.nvim/commit/de18f6b749f6129eb9042a2038590872df4c94a9))
* **watcher:** workaround weird annoying libuv bug ([4b53134](https://github.com/lewis6991/gitsigns.nvim/commit/4b53134ce5fdd58e6c52c49fb906b6e7a347d137)), closes [#1027](https://github.com/lewis6991/gitsigns.nvim/issues/1027)
* wrong api name in stable ([805610a](https://github.com/lewis6991/gitsigns.nvim/commit/805610a9393fa231f2c2b49cb521bfa413fadb3d))
## [0.8.1](https://github.com/lewis6991/gitsigns.nvim/compare/v0.8.0...v0.8.1) (2024-04-30)
### Bug Fixes
* **blame:** check win is valid after running blame ([7e38f07](https://github.com/lewis6991/gitsigns.nvim/commit/7e38f07cab0e5387f9f41e92474db174a63a4725))
* **reset:** handle 'endofline' when resetting hunks ([7aa9a56](https://github.com/lewis6991/gitsigns.nvim/commit/7aa9a567127d679c6ca639e9e88c546d72924296))
* **yadm:** correct ls-files check ([035da03](https://github.com/lewis6991/gitsigns.nvim/commit/035da036e68e509ed158414416c827d022d914bd))
## [0.8.0](https://github.com/lewis6991/gitsigns.nvim/compare/v0.7.0...v0.8.0) (2024-04-17)
### โ BREAKING CHANGES
* **docs:** Use the new attached_to_untracked setting
* change default of attached_to_untracked to false
### Features
* **actions:** add callback to async actions ([4e90cf9](https://github.com/lewis6991/gitsigns.nvim/commit/4e90cf984ced787b7439c42678ec957da3583049))
* **blame:** add rev option to blame_line() ([0994d89](https://github.com/lewis6991/gitsigns.nvim/commit/0994d89323c2ebb4abb38cab15aad00913588b0f)), closes [#952](https://github.com/lewis6991/gitsigns.nvim/issues/952)
* **blame:** support extra options ([3358280](https://github.com/lewis6991/gitsigns.nvim/commit/3358280054808b45f711191df481fcffc12ca761)), closes [#953](https://github.com/lewis6991/gitsigns.nvim/issues/953) [#959](https://github.com/lewis6991/gitsigns.nvim/issues/959)
* change default of attached_to_untracked to false ([590d077](https://github.com/lewis6991/gitsigns.nvim/commit/590d077c551c0bd2fc8b9f658e4704ccd0423a2e))
* configurable auto attach ([#918](https://github.com/lewis6991/gitsigns.nvim/issues/918)) ([3e6e91b](https://github.com/lewis6991/gitsigns.nvim/commit/3e6e91b09f0468c32d3b96dcacf4b947f037ce25))
* enable the new version of inline_preview ([d195f0c](https://github.com/lewis6991/gitsigns.nvim/commit/d195f0c35ced5174d3ecce1c4c8ebb3b5bc23fa9))
* **nav:** add nav_hunk() ([59bdc18](https://github.com/lewis6991/gitsigns.nvim/commit/59bdc1851c7aba8a86ded87fe075ef6de499045c))
* **popup:** add `q` keymap to quit ([b45ff86](https://github.com/lewis6991/gitsigns.nvim/commit/b45ff86f5618d1421a88c12d4feb286b80a1e2d3))
* publish releases to luarocks ([070875f](https://github.com/lewis6991/gitsigns.nvim/commit/070875f9e4eb81eb20cb60996cd1d9086d94b05e))
* update issue templates ([e93a158](https://github.com/lewis6991/gitsigns.nvim/commit/e93a158b8773946dc9940a4321d35c1b52c8e293))
* **yadm:** deprecate ([1bb277b](https://github.com/lewis6991/gitsigns.nvim/commit/1bb277b41d65f68b091e4ab093f59e68a0def2a6))
### Bug Fixes
* [#986](https://github.com/lewis6991/gitsigns.nvim/issues/986) ([05226b4](https://github.com/lewis6991/gitsigns.nvim/commit/05226b4d41226af8045841b3e56b6cc12d7a1cd0))
* [#989](https://github.com/lewis6991/gitsigns.nvim/issues/989) ([36d961d](https://github.com/lewis6991/gitsigns.nvim/commit/36d961d3d11b72229aaa576dfc8e7f5e05510af8))
* **actions:** prev_hunk works with wrap on line 1 ([2b96835](https://github.com/lewis6991/gitsigns.nvim/commit/2b96835a2b700f31303ebad0696f0abdbe8477ed)), closes [#806](https://github.com/lewis6991/gitsigns.nvim/issues/806)
* attach to fugitive and gitsigns buffers ([81369ed](https://github.com/lewis6991/gitsigns.nvim/commit/81369ed5405ec0c5d55a9608b495dbf827415116)), closes [#593](https://github.com/lewis6991/gitsigns.nvim/issues/593)
* bad deprecation message ([a4db718](https://github.com/lewis6991/gitsigns.nvim/commit/a4db718c78bff65198e3b63f1043f1e7bb5e05c8)), closes [#965](https://github.com/lewis6991/gitsigns.nvim/issues/965)
* **blame:** check buffer still exists after loading ([70584ff](https://github.com/lewis6991/gitsigns.nvim/commit/70584ff9aae8078b64430c574079d79620b8f06d)), closes [#946](https://github.com/lewis6991/gitsigns.nvim/issues/946)
* **blame:** put ignore-revs-file in correct position ([5f267aa](https://github.com/lewis6991/gitsigns.nvim/commit/5f267aa2fec145eb9fa11be8ae7b3d8b1939fe00)), closes [#975](https://github.com/lewis6991/gitsigns.nvim/issues/975)
* changedelete symbol with linematch enabled ([41dc075](https://github.com/lewis6991/gitsigns.nvim/commit/41dc075ef67b556b0752ad3967649371bd95cb95))
* check bcache in get_hunks ([1a50b94](https://github.com/lewis6991/gitsigns.nvim/commit/1a50b94066def8591d5f65bd60a4233902e9def4)), closes [#979](https://github.com/lewis6991/gitsigns.nvim/issues/979) [#981](https://github.com/lewis6991/gitsigns.nvim/issues/981)
* check for WinResized ([c093623](https://github.com/lewis6991/gitsigns.nvim/commit/c0936237f24d01eb4974dd3de38df7888414be3e))
* **cli:** do not print result ([7e31d81](https://github.com/lewis6991/gitsigns.nvim/commit/7e31d8123f14d55f4a3f982d05ddae4f3bf9276a))
* **current_line_blame:** update on WinResized ([f0733b7](https://github.com/lewis6991/gitsigns.nvim/commit/f0733b793a5e2663fd6d101de5beda68eec33967)), closes [#966](https://github.com/lewis6991/gitsigns.nvim/issues/966)
* **diffthis:** populate b:gitsigns_head ([50577f0](https://github.com/lewis6991/gitsigns.nvim/commit/50577f0186686b404d12157d463fb6bc4abba726)), closes [#949](https://github.com/lewis6991/gitsigns.nvim/issues/949)
* do not error when cwd does not exist ([826ad69](https://github.com/lewis6991/gitsigns.nvim/commit/826ad6942907ff08b02b8310b783e7275fdfb761))
* **docs:** Use the new attached_to_untracked setting ([2c2463d](https://github.com/lewis6991/gitsigns.nvim/commit/2c2463dbd82eddd7dbab881c3a62cfbfbe3c67ae))
* **dos:** correct check for dos files ([aeab36f](https://github.com/lewis6991/gitsigns.nvim/commit/aeab36f4b5524a765381ef84a2c57b2e799c934d))
* followup ([690f298](https://github.com/lewis6991/gitsigns.nvim/commit/690f298c4cac9190ddb7eedeeee2a3cc446622f7))
* **git:** support older versions of git ([4e34864](https://github.com/lewis6991/gitsigns.nvim/commit/4e348641b8206c3b8d23080999e3ddbe4ca90efc))
* **hl:** highlights for Nvim v0.9 ([fb9fd53](https://github.com/lewis6991/gitsigns.nvim/commit/fb9fd5312476b51a42a98122616e1c448d823d5c)), closes [#939](https://github.com/lewis6991/gitsigns.nvim/issues/939)
* **manager:** manager.update() never resolve when buf_check() fails ([6e05045](https://github.com/lewis6991/gitsigns.nvim/commit/6e05045fb1a4845fe44f5c54aafe024444c422ba))
* **nav:** followup for [#976](https://github.com/lewis6991/gitsigns.nvim/issues/976) ([ee5b6ba](https://github.com/lewis6991/gitsigns.nvim/commit/ee5b6ba0b55707628704bcd8d3554d1a05207b99))
* release-please branch ([031abb0](https://github.com/lewis6991/gitsigns.nvim/commit/031abb065452248c30ce8d8fb4d4eb9eeb69d1f0))
* **setqflist:** CLI ([e20c96e](https://github.com/lewis6991/gitsigns.nvim/commit/e20c96e9c3b9b2241939ce437d03926ba7315eaa)), closes [#907](https://github.com/lewis6991/gitsigns.nvim/issues/907)
* **stage:** staging of files with no nl at eof ([c097cb2](https://github.com/lewis6991/gitsigns.nvim/commit/c097cb255096f333e14d341082a84f572b394fa2))
* trigger GitSignsUpdate autocmd more often ([1389134](https://github.com/lewis6991/gitsigns.nvim/commit/1389134ba94643dd3b8ce2e1bf142d1c0432a4f2))
* typo in README ([4aaacbf](https://github.com/lewis6991/gitsigns.nvim/commit/4aaacbf5e5e2218fd05eb75703fe9e0f85335803))
* update lua-guide link in README ([c5ff762](https://github.com/lewis6991/gitsigns.nvim/commit/c5ff7628e19a47ec14d3657294cc074ecae27b99))
* use documented highlight groups as fallback ([300a306](https://github.com/lewis6991/gitsigns.nvim/commit/300a306da9973e81c2c06460f71fd7a079df1f36))
* **version:** handle version checks more gracefully ([3cb0f84](https://github.com/lewis6991/gitsigns.nvim/commit/3cb0f8431f56996a4af2924d78a98a09b6add095)), closes [#948](https://github.com/lewis6991/gitsigns.nvim/issues/948) [#960](https://github.com/lewis6991/gitsigns.nvim/issues/960)
* **watcher:** improve buffer check in handler ([078041e](https://github.com/lewis6991/gitsigns.nvim/commit/078041e9d060a386b0c9d3a8c7a7b019a35d3fb0))
lewis6991-gitsigns.nvim-7010000/CONTRIBUTING.md 0000664 0000000 0000000 00000001646 14765572704 0020415 0 ustar 00root root 0000000 0000000 **Please do not raise PR's for new features, only bug fixes will be reviewed.**
## Requirements
- [Luarocks](https://luarocks.org/)
- `brew install luarocks`
## Generating docs
Most of the documentation is handwritten however the documentation for the configuration is generated from `lua/gitsigns/config.lua` which contains the configuration schema.
The documentation is generated with the lua script `gen_help.lua` which has been developed just enough to handle the current configuration schema so from time to time this script might need small improvements to handle new features but for the most part it works.
The documentation can be updated with:
```bash
make gen_help
```
## Testsuite
The testsuite uses the same framework as Neovims funcitonaltest suite.
This is just busted with lots of helper code to create headless neovim instances which are controlled via RPC.
To run the testsuite:
```bash
make test
```
lewis6991-gitsigns.nvim-7010000/LICENSE 0000664 0000000 0000000 00000002056 14765572704 0017165 0 ustar 00root root 0000000 0000000 MIT License
Copyright (c) 2020 Lewis Russell
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
lewis6991-gitsigns.nvim-7010000/Makefile 0000664 0000000 0000000 00000005147 14765572704 0017624 0 ustar 00root root 0000000 0000000
export PJ_ROOT=$(PWD)
FILTER ?= .*
export NVIM_RUNNER_VERSION := v0.10.3
export NVIM_TEST_VERSION ?= v0.10.3
ifeq ($(shell uname -s),Darwin)
UNAME ?= MACOS
else
UNAME ?= LINUX
endif
.DEFAULT_GOAL := build
NVIM_TEST := deps/nvim-test
.PHONY: nvim-test
nvim-test: $(NVIM_TEST)
$(NVIM_TEST):
git clone --depth 1 --branch v1.1.0 https://github.com/lewis6991/nvim-test $@
$@/bin/nvim-test --init
.PHONY: test
test: $(NVIM_TEST)
NVIM_TEST_VERSION=$(NVIM_TEST_VERSION) \
$(NVIM_TEST)/bin/nvim-test test \
--lpath=$(PWD)/lua/?.lua \
--verbose \
--filter="$(FILTER)"
-@stty sane
.PHONY: test-all
test-all: test-095 test-010 test-nightly
.PHONY: test-095
test-095:
$(MAKE) $(MAKEFLAGS) test NVIM_TEST_VERSION=v0.9.5
.PHONY: test-010
test-010:
$(MAKE) $(MAKEFLAGS) test NVIM_TEST_VERSION=v0.10.3
.PHONY: test-nightly
test-nightly:
$(MAKE) $(MAKEFLAGS) test NVIM_TEST_VERSION=nightly
export XDG_DATA_HOME ?= $(HOME)/.data
NVIM := $(XDG_DATA_HOME)/nvim-test/nvim-runner-$(NVIM_RUNNER_VERSION)/bin/nvim
.PHONY: gen_help
gen_help: $(NVIM_TEST)
$(NVIM) -l ./gen_help.lua
@echo Updated help
STYLUA_PLATFORM_MACOS := macos-aarch64
STYLUA_PLATFORM_LINUX := linux-x86_64
STYLUA_PLATFORM := $(STYLUA_PLATFORM_$(UNAME))
STYLUA_VERSION := v2.0.2
STYLUA_ZIP := stylua-$(STYLUA_PLATFORM).zip
STYLUA_URL_BASE := https://github.com/JohnnyMorganz/StyLua/releases/download
STYLUA_URL := $(STYLUA_URL_BASE)/$(STYLUA_VERSION)/$(STYLUA_ZIP)
STYLUA := deps/stylua
.INTERMEDIATE: $(STYLUA_ZIP)
$(STYLUA_ZIP):
wget $(STYLUA_URL)
.PHONY: stylua
stylua: $(STYLUA)
$(STYLUA): $(STYLUA_ZIP)
unzip $< -d $(dir $@)
LUA_FILES := $(shell git ls-files lua test)
.PHONY: stylua-check
stylua-check: $(STYLUA)
$(STYLUA) --check $(LUA_FILES)
.PHONY: stylua-run
stylua-run: $(STYLUA)
$(STYLUA) $(LUA_FILES)
.PHONY: build
build: gen_help stylua-run
.PHONY: doc-check
doc-check: gen_help
git diff --exit-code -- doc
ifeq ($(shell uname -m),arm64)
LUALS_ARCH ?= arm64
else
LUALS_ARCH ?= x64
endif
LUALS_VERSION := 3.13.8
LUALS := deps/lua-language-server-$(LUALS_VERSION)-$(shell uname -s)-$(LUALS_ARCH)
LUALS_TARBALL := $(LUALS).tar.gz
LUALS_URL := https://github.com/LuaLS/lua-language-server/releases/download/$(LUALS_VERSION)/$(notdir $(LUALS_TARBALL))
.PHONY: luals
luals: $(LUALS)
$(LUALS):
wget --directory-prefix=$(dir $@) $(LUALS_URL)
mkdir -p $@
tar -xf $(LUALS_TARBALL) -C $@
rm -rf $(LUALS_TARBALL)
.PHONY: luals-check
luals-check: $(LUALS) $(NVIM_TEST)
VIMRUNTIME=$(XDG_DATA_HOME)/nvim-test/nvim-test-$(NVIM_TEST_VERSION)/share/nvim/runtime \
$(LUALS)/bin/lua-language-server \
--configpath=../.luarc.json \
--check=lua
lewis6991-gitsigns.nvim-7010000/README.md 0000664 0000000 0000000 00000024671 14765572704 0017446 0 ustar 00root root 0000000 0000000 # gitsigns.nvim
[](https://github.com/lewis6991/gitsigns.nvim/actions?query=workflow%3ACI)
[](https://github.com/lewis6991/gitsigns.nvim/releases)
[](https://opensource.org/licenses/MIT)
[](https://gitter.im/gitsigns-nvim/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
[](https://dotfyle.com/plugins/lewis6991/gitsigns.nvim)
Deep buffer integration for Git
## ๐ Preview
| Hunk Actions | Line Blame |
| --- | ----------- |
|
|
|
## โจ Features
Signs
- Adds signs to the sign column to indicate added, changed, and deleted lines.

- Supports different signs for staged changes.

- Add counts to signs.

Hunk Actions
- Stage/unstage hunks with `:Gitsigns stage_hunk`.
- Reset hunks with `:Gitsigns reset_hunk`.
- Also works on partial hunks in visual mode.
- Preview hunks inline with `:Gitsigns preview_hunk_inline`

- Preview hunks in popup with `:Gitsigns preview_hunk`

- Navigate between hunks with `:Gitsigns nav_hunk next/prev`.
Blame
- Show blame of current buffer using `:Gitsigns blame`.

- Show blame information for the current line in popup with `:Gitsigns blame_line`.

- Show blame information for the current line in virtual text.

- Enable with `setup({ current_line_blame = true })`.
- Toggle with `:Gitsigns toggle_current_line_blame`
Diff
- Change the revision for the signs with `:Gitsigns change_base `.
- Show the diff of the current buffer with the index or any revision
with `:Gitsigns diffthis `.
- Show intra-line word-diff in the buffer.

- Enable with `setup({ word_diff = true })`.
- Toggle with `:Gitsigns toggle_word_diff`.
Show hunks Quickfix/Location List
- Set the quickfix/location list with changes with `:Gitsign setqflist/setloclist`.

Can show hunks for:
- whole repository (`target=all`)
- attached buffers (`target=attached`)
- a specific buffer (`target=[integer]`).
Text Object
- Select hunks as a text object.
- Can use `vim.keymap.set({'o', 'x'}, 'ih', 'Gitsigns select_hunk')`
Status Line Integration
Use `b:gitsigns_status` or `b:gitsigns_status_dict`. `b:gitsigns_status` is
formatted using `config.status_formatter`. `b:gitsigns_status_dict` is a
dictionary with the keys `added`, `removed`, `changed` and `head`.
Example:
```viml
set statusline+=%{get(b:,'gitsigns_status','')}
```
For the current branch use the variable `b:gitsigns_head`.
Show different revisions of buffers
- Use `:Gitsigns show ` to `:edit` the current buffer at ``
## ๐ Requirements
- Neovim >= 0.9.0
> [!TIP]
> If your version of Neovim is too old, then you can use a past [release].
> [!WARNING]
> If you are running a development version of Neovim (aka `master`), then
> breakage may occur if your build is behind latest.
- Newish version of git. Older versions may not work with some features.
## ๐ ๏ธ Installation & Usage
Install using your package manager of choice.
For recommended setup with all batteries included:
```lua
require('gitsigns').setup()
```
Configuration can be passed to the setup function. Here is an example with most of
the default settings:
```lua
require('gitsigns').setup {
signs = {
add = { text = 'โ' },
change = { text = 'โ' },
delete = { text = '_' },
topdelete = { text = 'โพ' },
changedelete = { text = '~' },
untracked = { text = 'โ' },
},
signs_staged = {
add = { text = 'โ' },
change = { text = 'โ' },
delete = { text = '_' },
topdelete = { text = 'โพ' },
changedelete = { text = '~' },
untracked = { text = 'โ' },
},
signs_staged_enable = true,
signcolumn = true, -- Toggle with `:Gitsigns toggle_signs`
numhl = false, -- Toggle with `:Gitsigns toggle_numhl`
linehl = false, -- Toggle with `:Gitsigns toggle_linehl`
word_diff = false, -- Toggle with `:Gitsigns toggle_word_diff`
watch_gitdir = {
follow_files = true
},
auto_attach = true,
attach_to_untracked = false,
current_line_blame = false, -- Toggle with `:Gitsigns toggle_current_line_blame`
current_line_blame_opts = {
virt_text = true,
virt_text_pos = 'eol', -- 'eol' | 'overlay' | 'right_align'
delay = 1000,
ignore_whitespace = false,
virt_text_priority = 100,
use_focus = true,
},
current_line_blame_formatter = ', - ',
sign_priority = 6,
update_debounce = 100,
status_formatter = nil, -- Use default
max_file_length = 40000, -- Disable if file is longer than this (in lines)
preview_config = {
-- Options passed to nvim_open_win
border = 'single',
style = 'minimal',
relative = 'cursor',
row = 0,
col = 1
},
}
```
For information on configuring Neovim via lua please see [nvim-lua-guide].
### ๐น Keymaps
Gitsigns provides an `on_attach` callback which can be used to setup buffer mappings.
Here is a suggested example:
```lua
require('gitsigns').setup{
...
on_attach = function(bufnr)
local gitsigns = require('gitsigns')
local function map(mode, l, r, opts)
opts = opts or {}
opts.buffer = bufnr
vim.keymap.set(mode, l, r, opts)
end
-- Navigation
map('n', ']c', function()
if vim.wo.diff then
vim.cmd.normal({']c', bang = true})
else
gitsigns.nav_hunk('next')
end
end)
map('n', '[c', function()
if vim.wo.diff then
vim.cmd.normal({'[c', bang = true})
else
gitsigns.nav_hunk('prev')
end
end)
-- Actions
map('n', 'hs', gitsigns.stage_hunk)
map('n', 'hr', gitsigns.reset_hunk)
map('v', 'hs', function()
gitsigns.stage_hunk({ vim.fn.line('.'), vim.fn.line('v') })
end)
map('v', 'hr', function()
gitsigns.reset_hunk({ vim.fn.line('.'), vim.fn.line('v') })
end)
map('n', 'hS', gitsigns.stage_buffer)
map('n', 'hR', gitsigns.reset_buffer)
map('n', 'hp', gitsigns.preview_hunk)
map('n', 'hi', gitsigns.preview_hunk_inline)
map('n', 'hb', function()
gitsigns.blame_line({ full = true })
end)
map('n', 'hd', gitsigns.diffthis)
map('n', 'hD', function()
gitsigns.diffthis('~')
end)
map('n', 'hQ', function() gitsigns.setqflist('all') end)
map('n', 'hq', gitsigns.setqflist)
-- Toggles
map('n', 'tb', gitsigns.toggle_current_line_blame)
map('n', 'td', gitsigns.toggle_deleted)
map('n', 'tw', gitsigns.toggle_word_diff)
-- Text object
map({'o', 'x'}, 'ih', gitsigns.select_hunk)
end
}
```
## ๐ Plugin Integrations
### [vim-fugitive]
When viewing revisions of a file (via `:0Gclog` for example), Gitsigns will attach to the fugitive buffer with the base set to the commit immediately before the commit of that revision.
This means the signs placed in the buffer reflect the changes introduced by that revision of the file.
### [trouble.nvim]
If installed and enabled (via `config.trouble`; defaults to true if installed), `:Gitsigns setqflist` or `:Gitsigns setloclist` will open Trouble instead of Neovim's built-in quickfix or location list windows.
## ๐ซ Non-Goals
### Implement every feature in [vim-fugitive]
This plugin is actively developed and by one of the most well regarded vim plugin developers.
Gitsigns will only implement features of this plugin if: it is simple, or, the technologies leveraged by Gitsigns (LuaJIT, Libuv, Neovim's API, etc) can provide a better experience.
### Support for other VCS
There aren't any active developers of this plugin who use other kinds of VCS, so adding support for them isn't feasible.
However a well written PR with a commitment of future support could change this.
## ๐ Similar plugins
- [mini.diff]
- [coc-git]
- [vim-gitgutter]
- [vim-signify]
[mini.diff]: https://github.com/echasnovski/mini.diff
[coc-git]: https://github.com/neoclide/coc-git
[diff-linematch]: https://github.com/neovim/neovim/pull/14537
[luv]: https://github.com/luvit/luv/blob/master/docs.md
[nvim-lua-guide]: https://neovim.io/doc/user/lua-guide.html
[release]: https://github.com/lewis6991/gitsigns.nvim/releases
[trouble.nvim]: https://github.com/folke/trouble.nvim
[vim-fugitive]: https://github.com/tpope/vim-fugitive
[vim-gitgutter]: https://github.com/airblade/vim-gitgutter
[vim-signify]: https://github.com/mhinz/vim-signify
[virtual lines]: https://github.com/neovim/neovim/pull/15351
[lspsaga.nvim]: https://github.com/glepnir/lspsaga.nvim
lewis6991-gitsigns.nvim-7010000/doc/ 0000775 0000000 0000000 00000000000 14765572704 0016722 5 ustar 00root root 0000000 0000000 lewis6991-gitsigns.nvim-7010000/doc/gitsigns.txt 0000664 0000000 0000000 00000164273 14765572704 0021327 0 ustar 00root root 0000000 0000000 *gitsigns.txt* Gitsigns
*gitsigns.nvim*
Author: Lewis Russell
Version: v1.0.2
Homepage:
License: MIT license
==============================================================================
INTRODUCTION *gitsigns*
Gitsigns is a plugin for Neovim that provides integration with Git via a
feature set which includes (but not limited to):
โข Provides signs in the |signcolumn| to show changed/added/removed lines.
โข Mappings to operate on hunks to stage, undo or reset against Git's index.
Gitsigns is implemented entirely in Lua which is built into Neovim and
requires no external dependencies other than git. This is unlike other plugins
that require python, node, etc, which need to communicate with Neovim using
|RPC|. By default, Gitsigns also uses Neovim's built-in diff library
(`vim.diff`) unlike other similar plugins that need to run `git-diff` as an
external process which is less efficient, has tighter bottlenecks and requires
file IO.
==============================================================================
USAGE *gitsigns-usage*
For basic setup with all batteries included:
>lua
require('gitsigns').setup()
<
Configuration can be passed to the setup function. Here is an example with most
of the default settings:
>lua
require('gitsigns').setup {
signs = {
add = { text = 'โ' },
change = { text = 'โ' },
delete = { text = '_' },
topdelete = { text = 'โพ' },
changedelete = { text = '~' },
untracked = { text = 'โ' },
},
signs_staged = {
add = { text = 'โ' },
change = { text = 'โ' },
delete = { text = '_' },
topdelete = { text = 'โพ' },
changedelete = { text = '~' },
untracked = { text = 'โ' },
},
signs_staged_enable = true,
signcolumn = true, -- Toggle with `:Gitsigns toggle_signs`
numhl = false, -- Toggle with `:Gitsigns toggle_numhl`
linehl = false, -- Toggle with `:Gitsigns toggle_linehl`
word_diff = false, -- Toggle with `:Gitsigns toggle_word_diff`
watch_gitdir = {
follow_files = true
},
auto_attach = true,
attach_to_untracked = false,
current_line_blame = false, -- Toggle with `:Gitsigns toggle_current_line_blame`
current_line_blame_opts = {
virt_text = true,
virt_text_pos = 'eol', -- 'eol' | 'overlay' | 'right_align'
delay = 1000,
ignore_whitespace = false,
virt_text_priority = 100,
use_focus = true,
},
current_line_blame_formatter = ', - ',
sign_priority = 6,
update_debounce = 100,
status_formatter = nil, -- Use default
max_file_length = 40000, -- Disable if file is longer than this (in lines)
preview_config = {
-- Options passed to nvim_open_win
border = 'single',
style = 'minimal',
relative = 'cursor',
row = 0,
col = 1
},
}
<
==============================================================================
MAPPINGS *gitsigns-mappings*
Custom mappings can be defined in the `on_attach` callback in the config table
passed to |gitsigns-setup()|. See |gitsigns-config-on_attach|.
Most actions can be repeated with `.` if you have |vim-repeat| installed.
==============================================================================
FUNCTIONS *gitsigns-functions*
Note functions with the {async} attribute are run asynchronously and accept
an optional {callback} argument.
setup({cfg}) *gitsigns.setup()*
Setup and start Gitsigns.
Parameters: ~
{cfg} (table|nil): Configuration for Gitsigns.
See |gitsigns-usage| for more details.
attach({bufnr}, {ctx}, {callback?}) *gitsigns.attach()*
Attach Gitsigns to the buffer.
Attributes: ~
{async}
Parameters: ~
{bufnr} (integer): Buffer number
{ctx} (table|nil):
Git context data that may optionally be used to attach to any
buffer that represents a real git object.
โข {file}: (string)
Path to the file represented by the buffer, relative to the
top-level.
โข {toplevel}: (string?)
Path to the top-level of the parent git repository.
โข {gitdir}: (string?)
Path to the git directory of the parent git repository
(typically the ".git/" directory).
โข {commit}: (string?)
The git revision that the file belongs to.
โข {base}: (string?)
The git revision that the file should be compared to.
detach({bufnr}) *gitsigns.detach()*
Detach Gitsigns from the buffer {bufnr}. If {bufnr} is not
provided then the current buffer is used.
Parameters: ~
{bufnr} (integer): Buffer number
detach_all() *gitsigns.detach_all()*
Detach Gitsigns from all buffers it is attached to.
refresh({callback?}) *gitsigns.refresh()*
Refresh all buffers.
Attributes: ~
{async}
get_actions() *gitsigns.get_actions()*
Get all the available line specific actions for the current
buffer at the cursor position.
Returns: ~
(table|nil): Dictionary of action name to function which when called
performs action.
setloclist({nr}, {target}) *gitsigns.setloclist()*
Populate the location list with hunks. Automatically opens the
location list window.
Alias for: `setqflist({target}, { use_location_list = true, nr = {nr} }`
Attributes: ~
{async}
Parameters: ~
{nr?} (integer): Window number or the |window-ID|.
`0` for the current window (default).
{target} (integer|string): See |gitsigns.setqflist()|.
setqflist({target}, {opts}, {callback?}) *gitsigns.setqflist()*
Populate the quickfix list with hunks. Automatically opens the
quickfix window.
Attributes: ~
{async}
Parameters: ~
{target} (integer|string):
Specifies which files hunks are collected from.
Possible values.
โข [integer]: The buffer with the matching buffer
number. `0` for current buffer (default).
โข `"attached"`: All attached buffers.
โข `"all"`: All modified files for each git
directory of all attached buffers in addition
to the current working directory.
{opts} (table|nil): Additional options:
โข {use_location_list}: (boolean)
Populate the location list instead of the
quickfix list. Default to `false`.
โข {nr}: (integer)
Window number or ID when using location list.
Expand folds when navigating to a hunk which is
inside a fold. Defaults to `0`.
โข {open}: (boolean)
Open the quickfix/location list viewer.
Defaults to `true`.
show({revision}, {callback}) *gitsigns.show()*
Show revision {base} of the current file, if it is given, or
with the currently set base (index by default).
If {base} is the index, then the opened buffer is editable and
any written changes will update the index accordingly.
Examples: >vim
" View the index version of the file
:Gitsigns show
" View revision of file in the last commit
:Gitsigns show ~1
<
For a more complete list of ways to specify bases, see
|gitsigns-revision|.
Attributes: ~
{async}
diffthis({base}, {opts}) *gitsigns.diffthis()*
Perform a |vimdiff| on the given file with {base} if it is
given, or with the currently set base (index by default).
If {base} is the index, then the opened buffer is editable and
any written changes will update the index accordingly.
Examples: >vim
" Diff against the index
:Gitsigns diffthis
" Diff against the last commit
:Gitsigns diffthis ~1
<
For a more complete list of ways to specify bases, see
|gitsigns-revision|.
Attributes: ~
{async}
Parameters: ~
{base} (string|nil): Revision to diff against. Defaults to index.
{opts} (table|nil): Additional options:
โข {vertical}: {boolean}. Split window vertically. Defaults to
config.diff_opts.vertical. If running via command line, then
this is taken from the command modifiers.
โข {split}: {string}. One of: 'aboveleft', 'belowright',
'botright', 'rightbelow', 'leftabove', 'topleft'. Defaults to
'aboveleft'. If running via command line, then this is taken
from the command modifiers.
reset_base({global}) *gitsigns.reset_base()*
Reset the base revision to diff against back to the
index.
Alias for `change_base(nil, {global})` .
change_base({base}, {global}, {callback?}) *gitsigns.change_base()*
Change the base revision to diff against. If {base} is not
given, then the original base is used. If {global} is given
and true, then change the base revision of all buffers,
including any new buffers.
Attributes: ~
{async}
Examples: >vim
" Change base to 1 commit behind head
:lua require('gitsigns').change_base('HEAD~1')
" Also works using the Gitsigns command
:Gitsigns change_base HEAD~1
" Other variations
:Gitsigns change_base ~1
:Gitsigns change_base ~
:Gitsigns change_base ^
" Commits work too
:Gitsigns change_base 92eb3dd
" Revert to original base
:Gitsigns change_base
<
For a more complete list of ways to specify bases, see
|gitsigns-revision|.
Parameters: ~
{base} (string|nil): The object/revision to diff against.
{global} (boolean|nil): Change the base of all buffers.
blame({callback?}) *gitsigns.blame()*
Run git-blame on the current file and open the results
in a scroll-bound vertical split.
Mappings:
is mapped to open a menu with the other mappings
Note: must be held to activate the mappings whilst the menu is
open.
s [Show commit] in a vertical split.
S [Show commit] in a new tab.
r [Reblame at commit]
Attributes: ~
{async}
blame_line({opts}, {callback?}) *gitsigns.blame_line()*
Run git blame on the current line and show the results in a
floating window. If already open, calling this will cause the
window to get focus.
Attributes: ~
{async}
Parameters: ~
{opts} (table|nil): Additional options:
โข {full}: (boolean)
Display full commit message with hunk.
โข {ignore_whitespace}: (boolean)
Ignore whitespace when running blame.
โข {extra_opts}: (string[])
Extra options passed to `git-blame`.
get_hunks({bufnr}) *gitsigns.get_hunks()*
Get hunk array for specified buffer.
Parameters: ~
{bufnr} (integer): Buffer number, if not provided (or 0)
will use current buffer.
Returns: ~
(table|nil): Array of hunk objects.
Each hunk object has keys:
โข `"type"`: String with possible values: "add", "change",
"delete"
โข `"head"`: Header that appears in the unified diff
output.
โข `"lines"`: Line contents of the hunks prefixed with
either `"-"` or `"+"`.
โข `"removed"`: Sub-table with fields:
โข `"start"`: Line number (1-based)
โข `"count"`: Line count
โข `"added"`: Sub-table with fields:
โข `"start"`: Line number (1-based)
โข `"count"`: Line count
select_hunk({opts}) *gitsigns.select_hunk()*
Select the hunk under the cursor.
Parameters: ~
{opts} (table|nil): Additional options:
โข {greedy}: (boolean)
Select all contiguous hunks. Only useful if 'diff_opts'
contains `linematch`. Defaults to `true`.
preview_hunk_inline({callback?}) *gitsigns.preview_hunk_inline()*
Preview the hunk at the cursor position inline in the buffer.
preview_hunk() *gitsigns.preview_hunk()*
Preview the hunk at the cursor position in a floating
window. If the preview is already open, calling this
will cause the window to get focus.
prev_hunk({opts}, {callback?}) *gitsigns.prev_hunk()*
DEPRECATED: use |gitsigns.nav_hunk()|
Jump to the previous hunk in the current buffer. If a hunk preview
(popup or inline) was previously opened, it will be re-opened
at the previous hunk.
Attributes: ~
{async}
Parameters: ~
See |gitsigns.nav_hunk()|.
next_hunk({opts}, {callback?}) *gitsigns.next_hunk()*
DEPRECATED: use |gitsigns.nav_hunk()|
Jump to the next hunk in the current buffer. If a hunk preview
(popup or inline) was previously opened, it will be re-opened
at the next hunk.
Attributes: ~
{async}
Parameters: ~
See |gitsigns.nav_hunk()|.
nav_hunk({direction}, {opts}, {callback?}) *gitsigns.nav_hunk()*
Jump to hunk in the current buffer. If a hunk preview
(popup or inline) was previously opened, it will be re-opened
at the next hunk.
Attributes: ~
{async}
Parameters: ~
{direction} ('first'|'last'|'next'|'prev'):
{opts} (table|nil): Configuration table. Keys:
โข {wrap}: (boolean)
Whether to loop around file or not. Defaults
to the value 'wrapscan'
โข {navigation_message}: (boolean)
Whether to show navigation messages or not.
Looks at 'shortmess' for default behaviour.
โข {foldopen}: (boolean)
Expand folds when navigating to a hunk which is
inside a fold. Defaults to `true` if 'foldopen'
contains `search`.
โข {preview}: (boolean)
Automatically open preview_hunk() upon navigating
to a hunk.
โข {greedy}: (boolean)
Only navigate between non-contiguous hunks. Only useful if
'diff_opts' contains `linematch`. Defaults to `true`.
โข {target}: (`'unstaged'|'staged'|'all'`)
Which kinds of hunks to target. Defaults to `'unstaged'`.
โข {count}: (integer)
Number of times to advance. Defaults to |v:count1|.
reset_buffer_index({callback?}) *gitsigns.reset_buffer_index()*
Unstage all hunks for current buffer in the index. Note:
Unlike |gitsigns.undo_stage_hunk()| this doesn't simply undo
stages, this runs an `git reset` on current buffers file.
Attributes: ~
{async}
stage_buffer({callback?}) *gitsigns.stage_buffer()*
Stage all hunks in current buffer.
Attributes: ~
{async}
undo_stage_hunk({callback?}) *gitsigns.undo_stage_hunk()*
DEPRECATED: use |gitsigns.stage_hunk()| on staged signs
Undo the last call of stage_hunk().
Note: only the calls to stage_hunk() performed in the current
session can be undone.
Attributes: ~
{async}
reset_buffer() *gitsigns.reset_buffer()*
Reset the lines of all hunks in the buffer.
reset_hunk({range}, {opts}, {callback?}) *gitsigns.reset_hunk()*
Reset the lines of the hunk at the cursor position, or all
lines in the given range. If {range} is provided, all lines in
the given range are reset. This supports partial-hunks,
meaning if a range only includes a portion of a particular
hunk, only the lines within the range will be reset.
Parameters: ~
{range} (table|nil): List-like table of two integers making
up the line range from which you want to reset the hunks.
If running via command line, then this is taken from the
command modifiers.
{opts} (table|nil): Additional options:
โข {greedy}: (boolean)
Stage all contiguous hunks. Only useful if 'diff_opts'
contains `linematch`. Defaults to `true`.
stage_hunk({range}, {opts}, {callback?}) *gitsigns.stage_hunk()*
Stage the hunk at the cursor position, or all lines in the
given range. If {range} is provided, all lines in the given
range are staged. This supports partial-hunks, meaning if a
range only includes a portion of a particular hunk, only the
lines within the range will be staged.
Attributes: ~
{async}
Parameters: ~
{range} (table|nil): List-like table of two integers making
up the line range from which you want to stage the hunks.
If running via command line, then this is taken from the
command modifiers.
{opts} (table|nil): Additional options:
โข {greedy}: (boolean)
Stage all contiguous hunks. Only useful if 'diff_opts'
contains `linematch`. Defaults to `true`.
toggle_deleted({value}) *gitsigns.toggle_deleted()*
DEPRECATED: Use |gitsigns.preview_hunk_inline()|
Toggle |gitsigns-config-show_deleted|
Parameters: ~
{value} (boolean|nil): Value to set toggle. If `nil`
the toggle value is inverted.
Returns: ~
(boolean): Current value of |gitsigns-config-show_deleted|
toggle_current_line_blame({value}) *gitsigns.toggle_current_line_blame()*
Toggle |gitsigns-config-current_line_blame|
Parameters: ~
{value} (boolean|nil): Value to set toggle. If `nil`
the toggle value is inverted.
Returns: ~
(boolean): Current value of |gitsigns-config-current_line_blame|
toggle_word_diff({value}) *gitsigns.toggle_word_diff()*
Toggle |gitsigns-config-word_diff|
Parameters: ~
{value} (boolean|nil): Value to set toggle. If `nil`
the toggle value is inverted.
Returns: ~
(boolean): Current value of |gitsigns-config-word_diff|
toggle_linehl({value}) *gitsigns.toggle_linehl()*
Toggle |gitsigns-config-linehl|
Parameters: ~
{value} (boolean|nil): Value to set toggle. If `nil`
the toggle value is inverted.
Returns: ~
(boolean): Current value of |gitsigns-config-linehl|
toggle_numhl({value}) *gitsigns.toggle_numhl()*
Toggle |gitsigns-config-numhl|
Parameters: ~
{value} (boolean|nil): Value to set toggle. If `nil`
the toggle value is inverted.
Returns: ~
(boolean): Current value of |gitsigns-config-numhl|
toggle_signs({value}) *gitsigns.toggle_signs()*
Toggle |gitsigns-config-signbooleancolumn|
Parameters: ~
{value} (boolean|nil): Value to set toggle. If `nil`
the toggle value is inverted.
Returns: ~
(boolean): Current value of |gitsigns-config-signcolumn|
==============================================================================
CONFIGURATION *gitsigns-config*
This section describes the configuration fields which can be passed to
|gitsigns.setup()|. Note fields of type `table` may be marked with extended
meaning the field is merged with the default, with the user value given higher
precedence. This allows only specific sub-fields to be configured without
having to redefine the whole field.
signs *gitsigns-config-signs*
Type: `table[extended]`
Default: >
{
add = { text = 'โ' },
change = { text = 'โ' },
delete = { text = 'โ' },
topdelete = { text = 'โ' },
changedelete = { text = '~' },
untracked = { text = 'โ' },
}
<
Configuration for signs:
โข `text` specifies the character to use for the sign.
โข `show_count` to enable showing count of hunk, e.g. number of deleted
lines.
The highlights `GitSigns[kind][type]` is used for each kind of sign. E.g.
'add' signs uses the highlights:
โข `GitSignsAdd` (for normal text signs)
โข `GitSignsAddNr` (for signs when `config.numhl == true`)
โข `GitSignsAddLn `(for signs when `config.linehl == true`)
โข `GitSignsAddCul `(for signs when `config.culhl == true`)
See |gitsigns-highlight-groups|.
signs_staged *gitsigns-config-signs_staged*
Type: `table[extended]`
Default: >
{
add = { text = 'โ' },
change = { text = 'โ' },
delete = { text = 'โ' },
topdelete = { text = 'โ' },
changedelete = { text = '~' },
}
<
Configuration for signs of staged hunks.
See |gitsigns-config-signs|.
signs_staged_enable *gitsigns-config-signs_staged_enable*
Type: `boolean`, Default: `true`
Show signs for staged hunks.
When enabled the signs defined in |git-config-signs_staged|` are used.
worktrees *gitsigns-config-worktrees*
Type: `table`, Default: `nil`
Detached working trees.
Array of tables with the keys `gitdir` and `toplevel`.
If normal attaching fails, then each entry in the table is attempted
with the work tree details set.
Example: >lua
worktrees = {
{
toplevel = vim.env.HOME,
gitdir = vim.env.HOME .. '/projects/dotfiles/.git'
}
}
on_attach *gitsigns-config-on_attach*
Type: `function`, Default: `nil`
Callback called when attaching to a buffer. Mainly used to setup keymaps.
The buffer number is passed as the first argument.
This callback can return `false` to prevent attaching to the buffer.
Example: >lua
on_attach = function(bufnr)
if vim.api.nvim_buf_get_name(bufnr):match() then
-- Don't attach to specific buffers whose name matches a pattern
return false
end
-- Setup keymaps
vim.api.nvim_buf_set_keymap(bufnr, 'n', 'hs', 'lua require"gitsigns".stage_hunk()', {})
... -- More keymaps
end
<
watch_gitdir *gitsigns-config-watch_gitdir*
Type: `table[extended]`
Default: >
`{
enable = true,
follow_files = true
}`
<
When opening a file, a libuv watcher is placed on the respective
`.git` directory to detect when changes happen to use as a trigger to
update signs.
Fields: ~
โข `enable`:
Whether the watcher is enabled.
โข `follow_files`:
If a file is moved with `git mv`, switch the buffer to the new location.
sign_priority *gitsigns-config-sign_priority*
Type: `number`, Default: `6`
Priority to use for signs.
signcolumn *gitsigns-config-signcolumn*
Type: `boolean`, Default: `true`
Enable/disable symbols in the sign column.
When enabled the highlights defined in `signs.*.hl` and symbols defined
in `signs.*.text` are used.
numhl *gitsigns-config-numhl*
Type: `boolean`, Default: `false`
Enable/disable line number highlights.
When enabled the highlights defined in `signs.*.numhl` are used. If
the highlight group does not exist, then it is automatically defined
and linked to the corresponding highlight group in `signs.*.hl`.
linehl *gitsigns-config-linehl*
Type: `boolean`, Default: `false`
Enable/disable line highlights.
When enabled the highlights defined in `signs.*.linehl` are used. If
the highlight group does not exist, then it is automatically defined
and linked to the corresponding highlight group in `signs.*.hl`.
culhl *gitsigns-config-culhl*
Type: `boolean`, Default: `false`
Enable/disable highlights for the sign column when the cursor is on
the same line.
When enabled the highlights defined in `signs.*.culhl` are used. If
the highlight group does not exist, then it is automatically defined
and linked to the corresponding highlight group in `signs.*.hl`.
show_deleted *gitsigns-config-show_deleted*
DEPRECATED
Type: `boolean`, Default: `false`
Show the old version of hunks inline in the buffer (via virtual lines).
Note: Virtual lines currently use the highlight `GitSignsDeleteVirtLn`.
diff_opts *gitsigns-config-diff_opts*
Type: `table[extended]`, Default: derived from 'diffopt'
Diff options. If the default value is used, then changes to 'diffopt' are
automatically applied.
Fields: ~
โข algorithm: string
Diff algorithm to use. Values:
โข "myers" the default algorithm
โข "minimal" spend extra time to generate the
smallest possible diff
โข "patience" patience diff algorithm
โข "histogram" histogram diff algorithm
โข internal: boolean
Use Neovim's built in xdiff library for running diffs.
โข indent_heuristic: boolean
Use the indent heuristic for the internal
diff library.
โข vertical: boolean
Start diff mode with vertical splits.
โข linematch: integer
Enable second-stage diff on hunks to align lines.
Requires `internal=true`.
โข ignore_blank_lines: boolean
Ignore changes where lines are blank.
โข ignore_whitespace_change: boolean
Ignore changes in amount of white space.
It should ignore adding trailing white space,
but not leading white space.
โข ignore_whitespace: boolean
Ignore all white space changes.
โข ignore_whitespace_change_at_eol: boolean
Ignore white space changes at end of line.
base *gitsigns-config-base*
Type: `string`, Default: index
The object/revision to diff against.
See |gitsigns-revision|.
count_chars *gitsigns-config-count_chars*
Type: `table`
Default: >
`{ "1", "2", "3", "4", "5", "6", "7", "8", "9",
["+"] = ">"
}`
<
The count characters used when `signs.*.show_count` is enabled. The
`+` entry is used as a fallback. With the default, any count outside
of 1-9 uses the `>` character in the sign.
Possible use cases for this field:
โข to specify unicode characters for the counts instead of 1-9.
โข to define characters to be used for counts greater than 9.
status_formatter *gitsigns-config-status_formatter*
Type: `function`
Default: >
function(status)
local added, changed, removed = status.added, status.changed, status.removed
local status_txt = {}
if added and added > 0 then table.insert(status_txt, '+'..added ) end
if changed and changed > 0 then table.insert(status_txt, '~'..changed) end
if removed and removed > 0 then table.insert(status_txt, '-'..removed) end
return table.concat(status_txt, ' ')
end
<
Function used to format `b:gitsigns_status`.
max_file_length *gitsigns-config-max_file_length*
Type: `number`, Default: `40000`
Max file length (in lines) to attach to.
preview_config *gitsigns-config-preview_config*
Type: `table[extended]`
Default: >
`{
border = "single",
col = 1,
relative = "cursor",
row = 0,
style = "minimal"
}`
<
Option overrides for the Gitsigns preview window. Table is passed directly
to `nvim_open_win`.
auto_attach *gitsigns-config-auto_attach*
Type: `boolean`, Default: `true`
Automatically attach to files.
attach_to_untracked *gitsigns-config-attach_to_untracked*
Type: `boolean`, Default: `false`
Attach to untracked files.
update_debounce *gitsigns-config-update_debounce*
Type: `number`, Default: `100`
Debounce time for updates (in milliseconds).
current_line_blame *gitsigns-config-current_line_blame*
Type: `boolean`, Default: `false`
Adds an unobtrusive and customisable blame annotation at the end of
the current line.
The highlight group used for the text is `GitSignsCurrentLineBlame`.
current_line_blame_opts *gitsigns-config-current_line_blame_opts*
Type: `table[extended]`
Default: >
`{
delay = 1000,
use_focus = true,
virt_text = true,
virt_text_pos = "eol",
virt_text_priority = 100
}`
<
Options for the current line blame annotation.
Fields: ~
โข virt_text: boolean
Whether to show a virtual text blame annotation.
โข virt_text_pos: string
Blame annotation position. Available values:
`eol` Right after eol character.
`overlay` Display over the specified column, without
shifting the underlying text.
`right_align` Display right aligned in the window.
โข delay: integer
Sets the delay (in milliseconds) before blame virtual text is
displayed.
โข ignore_whitespace: boolean
Ignore whitespace when running blame.
โข virt_text_priority: integer
Priority of virtual text.
โข use_focus: boolean
Enable only when buffer is in focus
โข extra_opts: string[]
Extra options passed to `git-blame`.
current_line_blame_formatter *gitsigns-config-current_line_blame_formatter*
Type: `string|function`, Default: `" , - "`
String or function used to format the virtual text of
|gitsigns-config-current_line_blame|.
When a string, accepts the following format specifiers:
โข ``
โข ``
โข ``
โข ``
โข ``
โข `` or ``
โข ``
โข ``
โข ``
โข `` or ``
โข ``
โข ``
โข ``
โข ``
For `` and ``, `FORMAT` can
be any valid date format that is accepted by `os.date()` with the
addition of `%R` (defaults to `%Y-%m-%d`):
โข `%a` abbreviated weekday name (e.g., Wed)
โข `%A` full weekday name (e.g., Wednesday)
โข `%b` abbreviated month name (e.g., Sep)
โข `%B` full month name (e.g., September)
โข `%c` date and time (e.g., 09/16/98 23:48:10)
โข `%d` day of the month (16) [01-31]
โข `%H` hour, using a 24-hour clock (23) [00-23]
โข `%I` hour, using a 12-hour clock (11) [01-12]
โข `%M` minute (48) [00-59]
โข `%m` month (09) [01-12]
โข `%p` either "am" or "pm" (pm)
โข `%S` second (10) [00-61]
โข `%w` weekday (3) [0-6 = Sunday-Saturday]
โข `%x` date (e.g., 09/16/98)
โข `%X` time (e.g., 23:48:10)
โข `%Y` full year (1998)
โข `%y` two-digit year (98) [00-99]
โข `%%` the character `%ยด
โข `%R` relative (e.g., 4 months ago)
When a function:
Parameters: ~
{name} Git user name returned from `git config user.name` .
{blame_info} Table with the following keys:
โข `abbrev_sha`: string
โข `orig_lnum`: integer
โข `final_lnum`: integer
โข `author`: string
โข `author_mail`: string
โข `author_time`: integer
โข `author_tz`: string
โข `committer`: string
โข `committer_mail`: string
โข `committer_time`: integer
โข `committer_tz`: string
โข `summary`: string
โข `previous`: string
โข `filename`: string
โข `boundary`: true?
Note that the keys map onto the output of:
`git blame --line-porcelain`
Return: ~
The result of this function is passed directly to the `opts.virt_text`
field of |nvim_buf_set_extmark| and thus must be a list of
[text, highlight] tuples.
current_line_blame_formatter_nc
*gitsigns-config-current_line_blame_formatter_nc*
Type: `string|function`, Default: `" "`
String or function used to format the virtual text of
|gitsigns-config-current_line_blame| for lines that aren't committed.
See |gitsigns-config-current_line_blame_formatter| for more information.
trouble *gitsigns-config-trouble*
Type: `boolean`, Default: true if installed
When using setqflist() or setloclist(), open Trouble instead of the
quickfix/location list window.
word_diff *gitsigns-config-word_diff*
Type: `boolean`, Default: `false`
Highlight intra-line word differences in the buffer.
Requires `config.diff_opts.internal = true` .
Uses the highlights:
โข For word diff in previews:
โข `GitSignsAddInline`
โข `GitSignsChangeInline`
โข `GitSignsDeleteInline`
โข For word diff in buffer:
โข `GitSignsAddLnInline`
โข `GitSignsChangeLnInline`
โข `GitSignsDeleteLnInline`
โข For word diff in virtual lines (e.g. show_deleted):
โข `GitSignsAddVirtLnInline`
โข `GitSignsChangeVirtLnInline`
โข `GitSignsDeleteVirtLnInline`
debug_mode *gitsigns-config-debug_mode*
Type: `boolean`, Default: `false`
Enables debug logging and makes the following functions
available: `dump_cache`, `debug_messages`, `clear_debug`.
==============================================================================
HIGHLIGHT GROUPS *gitsigns-highlight-groups*
These are the highlights groups used by Gitsigns.
Note if a highlight is not defined, it will be automatically derived by
searching for other defined highlights in order.
*hl-GitSignsAdd*
GitSignsAdd
Used for the text of 'add' signs.
Fallbacks: `GitGutterAdd`, `SignifySignAdd`, `DiffAddedGutter`, `Added`, `DiffAdd`
*hl-GitSignsChange*
GitSignsChange
Used for the text of 'change' signs.
Fallbacks: `GitGutterChange`, `SignifySignChange`, `DiffModifiedGutter`, `Changed`, `DiffChange`
*hl-GitSignsDelete*
GitSignsDelete
Used for the text of 'delete' signs.
Fallbacks: `GitGutterDelete`, `SignifySignDelete`, `DiffRemovedGutter`, `Removed`, `DiffDelete`
*hl-GitSignsChangedelete*
GitSignsChangedelete
Used for the text of 'changedelete' signs.
Fallbacks: `GitSignsChange`
*hl-GitSignsTopdelete*
GitSignsTopdelete
Used for the text of 'topdelete' signs.
Fallbacks: `GitSignsDelete`
*hl-GitSignsUntracked*
GitSignsUntracked
Used for the text of 'untracked' signs.
Fallbacks: `GitSignsAdd`
*hl-GitSignsAddNr*
GitSignsAddNr
Used for number column (when `config.numhl == true`) of 'add' signs.
Fallbacks: `GitGutterAddLineNr`, `GitSignsAdd`
*hl-GitSignsChangeNr*
GitSignsChangeNr
Used for number column (when `config.numhl == true`) of 'change' signs.
Fallbacks: `GitGutterChangeLineNr`, `GitSignsChange`
*hl-GitSignsDeleteNr*
GitSignsDeleteNr
Used for number column (when `config.numhl == true`) of 'delete' signs.
Fallbacks: `GitGutterDeleteLineNr`, `GitSignsDelete`
*hl-GitSignsChangedeleteNr*
GitSignsChangedeleteNr
Used for number column (when `config.numhl == true`) of 'changedelete' signs.
Fallbacks: `GitSignsChangeNr`
*hl-GitSignsTopdeleteNr*
GitSignsTopdeleteNr
Used for number column (when `config.numhl == true`) of 'topdelete' signs.
Fallbacks: `GitSignsDeleteNr`
*hl-GitSignsUntrackedNr*
GitSignsUntrackedNr
Used for number column (when `config.numhl == true`) of 'untracked' signs.
Fallbacks: `GitSignsAddNr`
*hl-GitSignsAddLn*
GitSignsAddLn
Used for buffer line (when `config.linehl == true`) of 'add' signs.
Fallbacks: `GitGutterAddLine`, `SignifyLineAdd`, `DiffAdd`
*hl-GitSignsChangeLn*
GitSignsChangeLn
Used for buffer line (when `config.linehl == true`) of 'change' signs.
Fallbacks: `GitGutterChangeLine`, `SignifyLineChange`, `DiffChange`
*hl-GitSignsChangedeleteLn*
GitSignsChangedeleteLn
Used for buffer line (when `config.linehl == true`) of 'changedelete' signs.
Fallbacks: `GitSignsChangeLn`
*hl-GitSignsTopdeleteLn*
GitSignsTopdeleteLn
Used for buffer line (when `config.linehl == true`) of 'topdelete' signs.
Fallbacks: `GitSignsDeleteLn`
*hl-GitSignsUntrackedLn*
GitSignsUntrackedLn
Used for buffer line (when `config.linehl == true`) of 'untracked' signs.
Fallbacks: `GitSignsAddLn`
*hl-GitSignsAddCul*
GitSignsAddCul
Used for the text (when the cursor is on the same line as the sign) of 'add' signs.
Fallbacks: `GitSignsAdd`
*hl-GitSignsChangeCul*
GitSignsChangeCul
Used for the text (when the cursor is on the same line as the sign) of 'change' signs.
Fallbacks: `GitSignsChange`
*hl-GitSignsDeleteCul*
GitSignsDeleteCul
Used for the text (when the cursor is on the same line as the sign) of 'delete' signs.
Fallbacks: `GitSignsDelete`
*hl-GitSignsChangedeleteCul*
GitSignsChangedeleteCul
Used for the text (when the cursor is on the same line as the sign) of 'changedelete' signs.
Fallbacks: `GitSignsChangeCul`
*hl-GitSignsTopdeleteCul*
GitSignsTopdeleteCul
Used for the text (when the cursor is on the same line as the sign) of 'topdelete' signs.
Fallbacks: `GitSignsDeleteCul`
*hl-GitSignsUntrackedCul*
GitSignsUntrackedCul
Used for the text (when the cursor is on the same line as the sign) of 'untracked' signs.
Fallbacks: `GitSignsAddCul`
*hl-GitSignsStagedAdd*
GitSignsStagedAdd
Used for the text of 'add' staged signs.
Fallbacks: `GitSignsAdd` (fg=50%)
*hl-GitSignsStagedChange*
GitSignsStagedChange
Used for the text of 'change' staged signs.
Fallbacks: `GitSignsChange` (fg=50%)
*hl-GitSignsStagedDelete*
GitSignsStagedDelete
Used for the text of 'delete' staged signs.
Fallbacks: `GitSignsDelete` (fg=50%)
*hl-GitSignsStagedChangedelete*
GitSignsStagedChangedelete
Used for the text of 'changedelete' staged signs.
Fallbacks: `GitSignsChangedelete` (fg=50%)
*hl-GitSignsStagedTopdelete*
GitSignsStagedTopdelete
Used for the text of 'topdelete' staged signs.
Fallbacks: `GitSignsTopdelete` (fg=50%)
*hl-GitSignsStagedUntracked*
GitSignsStagedUntracked
Used for the text of 'untracked' staged signs.
Fallbacks: `GitSignsUntracked` (fg=50%)
*hl-GitSignsStagedAddNr*
GitSignsStagedAddNr
Used for number column (when `config.numhl == true`) of 'add' staged signs.
Fallbacks: `GitSignsAddNr` (fg=50%)
*hl-GitSignsStagedChangeNr*
GitSignsStagedChangeNr
Used for number column (when `config.numhl == true`) of 'change' staged signs.
Fallbacks: `GitSignsChangeNr` (fg=50%)
*hl-GitSignsStagedDeleteNr*
GitSignsStagedDeleteNr
Used for number column (when `config.numhl == true`) of 'delete' staged signs.
Fallbacks: `GitSignsDeleteNr` (fg=50%)
*hl-GitSignsStagedChangedeleteNr*
GitSignsStagedChangedeleteNr
Used for number column (when `config.numhl == true`) of 'changedelete' staged signs.
Fallbacks: `GitSignsChangedeleteNr` (fg=50%)
*hl-GitSignsStagedTopdeleteNr*
GitSignsStagedTopdeleteNr
Used for number column (when `config.numhl == true`) of 'topdelete' staged signs.
Fallbacks: `GitSignsTopdeleteNr` (fg=50%)
*hl-GitSignsStagedUntrackedNr*
GitSignsStagedUntrackedNr
Used for number column (when `config.numhl == true`) of 'untracked' staged signs.
Fallbacks: `GitSignsUntrackedNr` (fg=50%)
*hl-GitSignsStagedAddLn*
GitSignsStagedAddLn
Used for buffer line (when `config.linehl == true`) of 'add' staged signs.
Fallbacks: `GitSignsAddLn` (fg=50%)
*hl-GitSignsStagedChangeLn*
GitSignsStagedChangeLn
Used for buffer line (when `config.linehl == true`) of 'change' staged signs.
Fallbacks: `GitSignsChangeLn` (fg=50%)
*hl-GitSignsStagedChangedeleteLn*
GitSignsStagedChangedeleteLn
Used for buffer line (when `config.linehl == true`) of 'changedelete' staged signs.
Fallbacks: `GitSignsChangedeleteLn` (fg=50%)
*hl-GitSignsStagedTopdeleteLn*
GitSignsStagedTopdeleteLn
Used for buffer line (when `config.linehl == true`) of 'topdelete' staged signs.
Fallbacks: `GitSignsTopdeleteLn` (fg=50%)
*hl-GitSignsStagedUntrackedLn*
GitSignsStagedUntrackedLn
Used for buffer line (when `config.linehl == true`) of 'untracked' staged signs.
Fallbacks: `GitSignsUntrackedLn` (fg=50%)
*hl-GitSignsStagedAddCul*
GitSignsStagedAddCul
Used for the text (when the cursor is on the same line as the sign) of 'add' staged signs.
Fallbacks: `GitSignsAddCul` (fg=50%)
*hl-GitSignsStagedChangeCul*
GitSignsStagedChangeCul
Used for the text (when the cursor is on the same line as the sign) of 'change' staged signs.
Fallbacks: `GitSignsChangeCul` (fg=50%)
*hl-GitSignsStagedDeleteCul*
GitSignsStagedDeleteCul
Used for the text (when the cursor is on the same line as the sign) of 'delete' staged signs.
Fallbacks: `GitSignsDeleteCul` (fg=50%)
*hl-GitSignsStagedChangedeleteCul*
GitSignsStagedChangedeleteCul
Used for the text (when the cursor is on the same line as the sign) of 'changedelete' staged signs.
Fallbacks: `GitSignsChangedeleteCul` (fg=50%)
*hl-GitSignsStagedTopdeleteCul*
GitSignsStagedTopdeleteCul
Used for the text (when the cursor is on the same line as the sign) of 'topdelete' staged signs.
Fallbacks: `GitSignsTopdeleteCul` (fg=50%)
*hl-GitSignsStagedUntrackedCul*
GitSignsStagedUntrackedCul
Used for the text (when the cursor is on the same line as the sign) of 'untracked' staged signs.
Fallbacks: `GitSignsUntrackedCul` (fg=50%)
*hl-GitSignsAddPreview*
GitSignsAddPreview
Used for added lines in previews.
Fallbacks: `GitGutterAddLine`, `SignifyLineAdd`, `DiffAdd`
*hl-GitSignsDeletePreview*
GitSignsDeletePreview
Used for deleted lines in previews.
Fallbacks: `GitGutterDeleteLine`, `SignifyLineDelete`, `DiffDelete`
*hl-GitSignsCurrentLineBlame*
GitSignsCurrentLineBlame
Used for current line blame.
Fallbacks: `NonText`
*hl-GitSignsAddInline*
GitSignsAddInline
Used for added word diff regions in inline previews.
Fallbacks: `TermCursor`
*hl-GitSignsDeleteInline*
GitSignsDeleteInline
Used for deleted word diff regions in inline previews.
Fallbacks: `TermCursor`
*hl-GitSignsChangeInline*
GitSignsChangeInline
Used for changed word diff regions in inline previews.
Fallbacks: `TermCursor`
*hl-GitSignsAddLnInline*
GitSignsAddLnInline
Used for added word diff regions when `config.word_diff == true`.
Fallbacks: `GitSignsAddInline`
*hl-GitSignsChangeLnInline*
GitSignsChangeLnInline
Used for changed word diff regions when `config.word_diff == true`.
Fallbacks: `GitSignsChangeInline`
*hl-GitSignsDeleteLnInline*
GitSignsDeleteLnInline
Used for deleted word diff regions when `config.word_diff == true`.
Fallbacks: `GitSignsDeleteInline`
*hl-GitSignsDeleteVirtLn*
GitSignsDeleteVirtLn
Used for deleted lines shown by inline `preview_hunk_inline()` or `show_deleted()`.
Fallbacks: `GitGutterDeleteLine`, `SignifyLineDelete`, `DiffDelete`
*hl-GitSignsDeleteVirtLnInLine*
GitSignsDeleteVirtLnInLine
Used for word diff regions in lines shown by inline `preview_hunk_inline()` or `show_deleted()`.
Fallbacks: `GitSignsDeleteLnInline`
*hl-GitSignsVirtLnum*
GitSignsVirtLnum
Used for line numbers in inline hunks previews.
Fallbacks: `GitSignsDeleteVirtLn`
==============================================================================
COMMAND *gitsigns-command*
*:Gitsigns*
:Gitsigns {subcmd} {args} Run a Gitsigns command. {subcmd} can be any
function documented in |gitsigns-functions|.
Each argument in {args} will be attempted to be
parsed as a Lua value using `loadstring`, however
if this fails the argument will remain as the
string given by ||.
Note this command is equivalent to: >vim
:lua require('gitsigns').{subcmd}({args})
<
Examples: >vim
:Gitsigns diffthis HEAD~1
:Gitsigns blame_line
:Gitsigns toggle_signs
:Gitsigns toggle_current_line_blame
:Gitsigns change_base ~
:Gitsigns reset_buffer
:Gitsigns change_base nil true
<
==============================================================================
SPECIFYING OBJECTS *gitsigns-object* *gitsigns-revision*
Gitsigns objects are Git revisions as defined in the "SPECIFYING REVISIONS"
section in the gitrevisions(7) man page. For commands that accept an optional
base, the default is the file in the index. Examples:
Additionally, Gitsigns also accepts the value `FILE` to specify the working
version of a file.
Object Meaning ~
@ Version of file in the commit referenced by @ aka HEAD
main Version of file in the commit referenced by main
main^ Version of file in the parent of the commit referenced by main
main~ "
main~1 "
main...other Version of file in the merge base of main and other
@^ Version of file in the parent of HEAD
@~2 Version of file in the grandparent of HEAD
92eb3dd Version of file in the commit 92eb3dd
:1 The file's common ancestor during a conflict
:2 The alternate file in the target branch during a conflict
==============================================================================
STATUSLINE *gitsigns-statusline*
*b:gitsigns_status* *b:gitsigns_status_dict*
The buffer variables `b:gitsigns_status` and `b:gitsigns_status_dict` are
provided. `b:gitsigns_status` is formatted using `config.status_formatter`
. `b:gitsigns_status_dict` is a dictionary with the keys:
โข `added` - Number of added lines.
โข `changed` - Number of changed lines.
โข `removed` - Number of removed lines.
โข `head` - Name of current HEAD (branch or short commit hash).
โข `root` - Top level directory of the working tree.
โข `gitdir` - .git directory.
Example:
>vim
set statusline+=%{get(b:,'gitsigns_status','')}
<
*b:gitsigns_head* *g:gitsigns_head*
Use `g:gitsigns_head` and `b:gitsigns_head` to return the name of the current
HEAD (usually branch name). If the current HEAD is detached then this will be
a short commit hash. `g:gitsigns_head` returns the current HEAD for the
current working directory, whereas `b:gitsigns_head` returns the current HEAD
for each buffer.
*b:gitsigns_blame_line* *b:gitsigns_blame_line_dict*
Provided if |gitsigns-config-current_line_blame| is enabled.
`b:gitsigns_blame_line` if formatted using
`config.current_line_blame_formatter`. `b:gitsigns_blame_line_dict` is a
dictionary containing of the blame object for the current line. For complete
list of keys, see the {blame_info} argument from
|gitsigns-config-current_line_blame_formatter|.
==============================================================================
TEXT OBJECTS *gitsigns-textobject*
Since text objects are defined via keymaps, these are exposed and configurable
via the config, see |gitsigns-config-keymaps|. The lua implementation is
exposed through |gitsigns.select_hunk()|.
==============================================================================
EVENTS *gitsigns-events*
|User| |autocommands| provided to allow extending behaviors.
Example: >lua
vim.api.nvim_create_autocmd('User', {
pattern = 'GitSignsUpdate',
callback = function(args)
print(os.time(), ' Gitsigns made an update on ', args.data.buffer)
end
})
<
*User_GitSignsUpdate*
GitSignsUpdate After Gitsigns updates its knowledge about hunks.
Provides `bufnr` in the autocmd user data.
*User_GitSignsChanged*
GitSignsChanged After any event in which Gitsigns can potentially change
the repository. Provides `file` in the autocmd user data.
------------------------------------------------------------------------------
vim:tw=78:ts=8:ft=help:norl:
lewis6991-gitsigns.nvim-7010000/etc/ 0000775 0000000 0000000 00000000000 14765572704 0016730 5 ustar 00root root 0000000 0000000 lewis6991-gitsigns.nvim-7010000/etc/doc_template.txt 0000664 0000000 0000000 00000020167 14765572704 0022137 0 ustar 00root root 0000000 0000000 *gitsigns.txt* Gitsigns
*gitsigns.nvim*
Author: Lewis Russell
Version: {{VERSION}}
Homepage:
License: MIT license
==============================================================================
INTRODUCTION *gitsigns*
Gitsigns is a plugin for Neovim that provides integration with Git via a
feature set which includes (but not limited to):
โข Provides signs in the |signcolumn| to show changed/added/removed lines.
โข Mappings to operate on hunks to stage, undo or reset against Git's index.
Gitsigns is implemented entirely in Lua which is built into Neovim and
requires no external dependencies other than git. This is unlike other plugins
that require python, node, etc, which need to communicate with Neovim using
|RPC|. By default, Gitsigns also uses Neovim's built-in diff library
(`vim.diff`) unlike other similar plugins that need to run `git-diff` as an
external process which is less efficient, has tighter bottlenecks and requires
file IO.
==============================================================================
USAGE *gitsigns-usage*
For basic setup with all batteries included:
>lua
require('gitsigns').setup()
<
Configuration can be passed to the setup function. Here is an example with most
of the default settings:
>lua
{{SETUP}}
<
==============================================================================
MAPPINGS *gitsigns-mappings*
Custom mappings can be defined in the `on_attach` callback in the config table
passed to |gitsigns-setup()|. See |gitsigns-config-on_attach|.
Most actions can be repeated with `.` if you have |vim-repeat| installed.
==============================================================================
FUNCTIONS *gitsigns-functions*
Note functions with the {async} attribute are run asynchronously and accept
an optional {callback} argument.
{{FUNCTIONS}}
==============================================================================
CONFIGURATION *gitsigns-config*
This section describes the configuration fields which can be passed to
|gitsigns.setup()|. Note fields of type `table` may be marked with extended
meaning the field is merged with the default, with the user value given higher
precedence. This allows only specific sub-fields to be configured without
having to redefine the whole field.
{{CONFIG}}
==============================================================================
HIGHLIGHT GROUPS *gitsigns-highlight-groups*
These are the highlights groups used by Gitsigns.
Note if a highlight is not defined, it will be automatically derived by
searching for other defined highlights in order.
{{HIGHLIGHTS}}
==============================================================================
COMMAND *gitsigns-command*
*:Gitsigns*
:Gitsigns {subcmd} {args} Run a Gitsigns command. {subcmd} can be any
function documented in |gitsigns-functions|.
Each argument in {args} will be attempted to be
parsed as a Lua value using `loadstring`, however
if this fails the argument will remain as the
string given by ||.
Note this command is equivalent to: >vim
:lua require('gitsigns').{subcmd}({args})
<
Examples: >vim
:Gitsigns diffthis HEAD~1
:Gitsigns blame_line
:Gitsigns toggle_signs
:Gitsigns toggle_current_line_blame
:Gitsigns change_base ~
:Gitsigns reset_buffer
:Gitsigns change_base nil true
<
==============================================================================
SPECIFYING OBJECTS *gitsigns-object* *gitsigns-revision*
Gitsigns objects are Git revisions as defined in the "SPECIFYING REVISIONS"
section in the gitrevisions(7) man page. For commands that accept an optional
base, the default is the file in the index. Examples:
Additionally, Gitsigns also accepts the value `FILE` to specify the working
version of a file.
Object Meaning ~
@ Version of file in the commit referenced by @ aka HEAD
main Version of file in the commit referenced by main
main^ Version of file in the parent of the commit referenced by main
main~ "
main~1 "
main...other Version of file in the merge base of main and other
@^ Version of file in the parent of HEAD
@~2 Version of file in the grandparent of HEAD
92eb3dd Version of file in the commit 92eb3dd
:1 The file's common ancestor during a conflict
:2 The alternate file in the target branch during a conflict
==============================================================================
STATUSLINE *gitsigns-statusline*
*b:gitsigns_status* *b:gitsigns_status_dict*
The buffer variables `b:gitsigns_status` and `b:gitsigns_status_dict` are
provided. `b:gitsigns_status` is formatted using `config.status_formatter`
. `b:gitsigns_status_dict` is a dictionary with the keys:
โข `added` - Number of added lines.
โข `changed` - Number of changed lines.
โข `removed` - Number of removed lines.
โข `head` - Name of current HEAD (branch or short commit hash).
โข `root` - Top level directory of the working tree.
โข `gitdir` - .git directory.
Example:
>vim
set statusline+=%{get(b:,'gitsigns_status','')}
<
*b:gitsigns_head* *g:gitsigns_head*
Use `g:gitsigns_head` and `b:gitsigns_head` to return the name of the current
HEAD (usually branch name). If the current HEAD is detached then this will be
a short commit hash. `g:gitsigns_head` returns the current HEAD for the
current working directory, whereas `b:gitsigns_head` returns the current HEAD
for each buffer.
*b:gitsigns_blame_line* *b:gitsigns_blame_line_dict*
Provided if |gitsigns-config-current_line_blame| is enabled.
`b:gitsigns_blame_line` if formatted using
`config.current_line_blame_formatter`. `b:gitsigns_blame_line_dict` is a
dictionary containing of the blame object for the current line. For complete
list of keys, see the {blame_info} argument from
|gitsigns-config-current_line_blame_formatter|.
==============================================================================
TEXT OBJECTS *gitsigns-textobject*
Since text objects are defined via keymaps, these are exposed and configurable
via the config, see |gitsigns-config-keymaps|. The lua implementation is
exposed through |gitsigns.select_hunk()|.
==============================================================================
EVENTS *gitsigns-events*
|User| |autocommands| provided to allow extending behaviors.
Example: >lua
vim.api.nvim_create_autocmd('User', {
pattern = 'GitSignsUpdate',
callback = function(args)
print(os.time(), ' Gitsigns made an update on ', args.data.buffer)
end
})
<
*User_GitSignsUpdate*
GitSignsUpdate After Gitsigns updates its knowledge about hunks.
Provides `bufnr` in the autocmd user data.
*User_GitSignsChanged*
GitSignsChanged After any event in which Gitsigns can potentially change
the repository. Provides `file` in the autocmd user data.
------------------------------------------------------------------------------
vim:tw=78:ts=8:ft=help:norl:
lewis6991-gitsigns.nvim-7010000/gen_help.lua 0000775 0000000 0000000 00000031432 14765572704 0020447 0 ustar 00root root 0000000 0000000 #!/usr/bin/env -S nvim -l
-- Simple script to update the help doc by reading the config schema.
local inspect = vim.inspect
local list_extend = vim.list_extend
local startswith = vim.startswith
local config = require('lua.gitsigns.config')
-- To make sure the output is consistent between runs (to minimise diffs), we
-- need to iterate through the schema keys in a deterministic way. To do this we
-- do a smple scan over the file the schema is defined in and collect the keys
-- in the order they are defined.
--- @return string[]
local function get_ordered_schema_keys()
local ci = io.lines('lua/gitsigns/config.lua') --- @type Iterator[string]
for l in ci do
if startswith(l, 'M.schema = {') then
break
end
end
local keys = {}
for l in ci do
if startswith(l, '}') then
break
end
if l:find('^ (%w+).*') then
local lc = l:gsub('^%s*([%w_]+).*', '%1')
table.insert(keys, lc)
end
end
return keys
end
--- @param dep_info boolean|{new_field: string, message: string, hard: boolean}
--- @param out fun(_: string?)
local function gen_config_doc_deprecated(dep_info, out)
if type(dep_info) == 'table' and dep_info.hard then
out(' HARD-DEPRECATED')
else
out(' DEPRECATED')
end
if type(dep_info) == 'table' then
if dep_info.message then
out(' ' .. dep_info.message)
end
if dep_info.new_field then
out('')
local opts_key, field = dep_info.new_field:match('(.*)%.(.*)')
if opts_key and field then
out(
(' Please instead use the field `%s` in |gitsigns-config-%s|.'):format(field, opts_key)
)
else
out((' Please instead use |gitsigns-config-%s|.'):format(dep_info.new_field))
end
end
end
out('')
end
--- @param field string
--- @param out fun(_: string?)
local function gen_config_doc_field(field, out)
local v = config.schema[field]
-- Field heading and tag
local t = ('*gitsigns-config-%s*'):format(field)
if #field + #t < 80 then
out(('%-29s %48s'):format(field, t))
else
out(('%-29s'):format(field))
out(('%78s'):format(t))
end
local deprecated = v.deprecated
if deprecated then
gen_config_doc_deprecated(deprecated, out)
end
if v.description then
local d --- @type string
local default_help = v.default_help
if default_help ~= nil then
d = default_help
else
d = inspect(v.default):gsub('\n', '\n ')
d = ('`%s`'):format(d)
end
local vtype = (function()
local ty = v.type_help or v.type
if ty == 'table' and v.deep_extend then
return 'table[extended]'
end
if type(ty) == 'table' then
v.type = table.concat(ty, '|')
end
return v.type
end)()
if d:find('\n') then
out((' Type: `%s`'):format(vtype))
out(' Default: >')
out(' ' .. d:gsub('\n([^\n\r])', '\n %1'))
out('<')
else
out((' Type: `%s`, Default: %s'):format(vtype, d))
out()
end
out(v.description:gsub(' +$', ''))
end
end
--- @return string
local function gen_config_doc()
local res = {} ---@type string[]
local function out(line)
res[#res + 1] = line or ''
end
for _, k in ipairs(get_ordered_schema_keys()) do
gen_config_doc_field(k, out)
end
return table.concat(res, '\n')
end
--- @param line string
--- @return string
local function parse_func_header(line)
-- match:
-- prefix.name = ...
-- function prefix.name(...
local func = line:match('^%w+%.([%w_]+) =')
or line:match('^function %w+%.([%w_]+)%(')
if not func then
error('Unable to parse: ' .. line)
end
local args_raw = line:match('function%((.*)%)') -- M.name = function(args)
or line:match('function%s+%w+%.[%w_]+%((.*)%)') -- function M.name(args)
local args = {} --- @type string[]
for k in string.gmatch(args_raw, '([%w_]+)') do
if k:sub(1, 1) ~= '_' then
args[#args + 1] = string.format('{%s}', k)
end
end
if line:match('async.create%(%d, function%(') then
args[#args + 1] = '{callback?}'
end
return string.format(
'%-40s%38s',
string.format('%s(%s)', func, table.concat(args, ', ')),
'*gitsigns.' .. func .. '()*'
)
end
--- @param x string
--- @return string? name
--- @return string? type
--- @return string? description
local function parse_param(x)
local name, ty, des = x:match('([^ ]+) +([^ ]+) *(.*)')
return name, ty, des
end
--- @param x string[]
--- @return string[]
local function trim_lines(x)
local min_pad --- @type integer?
for _, e in ipairs(x) do
local _, i = e:find('^ *')
if not min_pad or min_pad > i then
min_pad = i
end
end
local r = {} --- @type string[]
for _, e in ipairs(x) do
r[#r + 1] = e:sub(min_pad + 1)
end
return r
end
--- @param name string
--- @param ty string
--- @param desc string[]
--- @param name_pad? integer
--- @return string[]
local function render_param_or_return(name, ty, desc, name_pad)
ty = ty:gsub('Gitsigns%.%w+', 'table')
name_pad = name_pad and (name_pad + 3) or 0
local name_str --- @type string
if name == ':' then
name_str = ''
else
local nf = '%-' .. tostring(name_pad) .. 's'
name_str = nf:format(string.format('{%s} ', name))
end
if #desc == 0 then
return { string.format(' %s(%s)', name_str, ty) }
end
local r = {} --- @type string[]
local desc1 = desc[1] == '' and '' or ' ' .. desc[1]
r[#r + 1] = string.format(' %s(%s):%s', name_str, ty, desc1)
local remain_desc = trim_lines(vim.list_slice(desc, 2))
for _, d in ipairs(remain_desc) do
r[#r + 1] = ' ' .. string.rep(' ', name_pad) .. d
end
return r
end
--- @param x string[]
--- @param amount integer
--- @return string[]
local function pad(x, amount)
local pad_str = string.rep(' ', amount)
local r = {} --- @type string[]
for _, e in ipairs(x) do
r[#r + 1] = pad_str .. e
end
return r
end
--- @param state EmmyState
--- @param doc_comment string
--- @param desc string[]
--- @param params [string, string, string[]][]
--- @param returns [string, string, string[]][]
--- @return EmmyState
local function process_doc_comment(state, doc_comment, desc, params, returns)
if state == 'none' then
state = 'in_block'
end
local emmy_type, emmy_str = doc_comment:match(' ?@([a-z]+) (.*)')
if emmy_type == 'param' then
local name, ty, pdesc = parse_param(emmy_str)
params[#params + 1] = { name, ty, { pdesc } }
return 'in_param'
end
if emmy_type == 'return' then
local ty, name, rdesc = parse_param(emmy_str)
returns[#returns + 1] = { name, ty, { rdesc } }
return 'in_return'
end
if state == 'in_param' then
-- Consume any remaining doc document lines as the description for the
-- last parameter
local lastdes = params[#params][3]
lastdes[#lastdes + 1] = doc_comment
elseif state == 'in_return' then
-- Consume any remaining doc document lines as the description for the
-- last return
local lastdes = returns[#returns][3]
lastdes[#lastdes + 1] = doc_comment
else
if doc_comment ~= '' and doc_comment ~= '<' then
doc_comment = string.rep(' ', 16) .. doc_comment
end
desc[#desc + 1] = doc_comment
end
return state
end
--- @param header string
--- @param block string[]
--- @param params [string, string, string[]][]
--- @param returns [string, string, string[]][]
--- @param deprecated string?
--- @return string[]?
local function render_block(header, block, params, returns, deprecated)
if vim.startswith(header, '_') then
return
end
local res = { header }
if deprecated then
list_extend(res, {
' DEPRECATED: '..deprecated,
''
})
end
list_extend(res, block)
-- filter arguments beginning with '_'
params = vim.tbl_filter(
--- @param v [string, string, string[]]
--- @return boolean
function(v)
return not startswith(v[1], '_')
end,
params
)
if #params > 0 then
local param_block = { 'Parameters: ~' }
local name_pad = 0
for _, v in ipairs(params) do
if #v[1] > name_pad then
name_pad = #v[1]
end
end
for _, v in ipairs(params) do
local name, ty, desc = v[1], v[2], v[3]
list_extend(param_block, render_param_or_return(name, ty, desc, name_pad))
end
list_extend(res, pad(param_block, 16))
end
if #returns > 0 then
res[#res + 1] = ''
local param_block = { 'Returns: ~' }
for _, v in ipairs(returns) do
local name, ty, desc = v[1], v[2], v[3]
list_extend(param_block, render_param_or_return(name, ty, desc))
end
list_extend(res, pad(param_block, 16))
end
return res
end
--- @param path string
--- @return string
local function gen_functions_doc_from_file(path)
local i = io.lines(path) --- @type Iterator[string]
local blocks = {} --- @type string[][]
--- @alias EmmyState 'none'|'in_block'|'in_param'|'in_return'
local state = 'none' --- @type EmmyState
local desc = {} --- @type string[]
local params = {} --- @type [string, string, string[]][]
local returns = {} --- @type [string, string, string[]][]
local deprecated --- @type string?
for l in i do
local doc_comment = l:match('^%-%-%- ?(.*)') --- @type string?
if doc_comment then
local depre = doc_comment:match('@deprecated ?(.*)')
if depre then
deprecated = depre
else
state = process_doc_comment(state, doc_comment, desc, params, returns)
end
elseif state ~= 'none' then
-- First line after block
local ok, header = pcall(parse_func_header, l)
if ok then
blocks[#blocks + 1] = render_block(header, desc, params, returns, deprecated)
end
state = 'none'
desc = {}
params = {}
returns = {}
deprecated = nil
end
end
local res = {} --- @type string[]
for j = #blocks, 1, -1 do
local b = blocks[j]
for k = 1, #b do
res[#res + 1] = b[k]:match('^ *$') and '' or b[k]
end
res[#res + 1] = ''
end
return table.concat(res, '\n')
end
--- @param files string[]
--- @return string
local function gen_functions_doc(files)
local res = {} --- @type string[]
for _, path in ipairs(files) do
res[#res + 1] = gen_functions_doc_from_file(path)
end
return table.concat(res, '\n')
end
--- @return string
local function gen_highlights_doc()
local res = {} --- @type string[]
local highlights = require('lua.gitsigns.highlight')
local name_max = 0
for _, hl in ipairs(highlights.hls) do
for name, _ in pairs(hl) do
if name:len() > name_max then
name_max = name:len()
end
end
end
for _, hl in ipairs(highlights.hls) do
for name, spec in pairs(hl) do
if not spec.hidden then
local fallbacks_tbl = {} --- @type string[]
for _, f in ipairs(spec) do
fallbacks_tbl[#fallbacks_tbl + 1] = string.format('`%s`', f)
end
local fallbacks = table.concat(fallbacks_tbl, ', ')
if spec.fg_factor then
fallbacks = fallbacks .. (' (fg=%d%%)'):format(spec.fg_factor * 100)
end
res[#res + 1] = string.format('%s*hl-%s*', string.rep(' ', 56), name)
res[#res + 1] = string.format('%s', name)
if spec.desc then
res[#res + 1] = string.format('%s%s', string.rep(' ', 8), spec.desc)
res[#res + 1] = ''
end
res[#res + 1] = string.format('%sFallbacks: %s', string.rep(' ', 8), fallbacks)
end
end
end
return table.concat(res, '\n')
end
--- @return string
local function get_setup_from_readme()
local readme = io.lines('README.md') --- @type Iterator[string]
local res = {} --- @type string[]
local function append(line)
res[#res + 1] = line ~= '' and ' ' .. line or ''
end
for l in readme do
if l:match("require%('gitsigns'%).setup {") then
append(l)
break
end
end
for l in readme do
append(l)
if l == '}' then
break
end
end
return table.concat(res, '\n')
end
--- @param marker string
--- @return string|fun():string
local function get_marker_text(marker)
return ({
VERSION = 'v1.0.2', -- x-release-please-version
CONFIG = gen_config_doc,
FUNCTIONS = function()
return gen_functions_doc({
'lua/gitsigns.lua',
'lua/gitsigns/attach.lua',
'lua/gitsigns/actions.lua',
})
end,
HIGHLIGHTS = gen_highlights_doc,
SETUP = get_setup_from_readme,
})[marker]
end
local function main()
local template = io.lines('etc/doc_template.txt') --- @type Iterator[string]
local out = assert(io.open('doc/gitsigns.txt', 'w'))
for l in template do
local marker = l:match('{{(.*)}}')
if marker then
local sub = get_marker_text(marker)
if sub then
if type(sub) == 'function' then
sub = sub()
end
--- @type string
sub = sub:gsub('%%', '%%%%')
l = l:gsub('{{' .. marker .. '}}', sub)
end
end
out:write(l or '', '\n')
end
end
main()
lewis6991-gitsigns.nvim-7010000/gitsigns.nvim-scm-1.rockspec 0000664 0000000 0000000 00000001306 14765572704 0023425 0 ustar 00root root 0000000 0000000 local _MODREV, _SPECREV = 'scm', '-1'
rockspec_format = "3.0"
package = 'gitsigns.nvim'
version = _MODREV .. _SPECREV
description = {
summary = 'Git signs written in pure lua',
detailed = [[
Super fast git decorations implemented purely in Lua.
]],
homepage = 'http://github.com/lewis6991/gitsigns.nvim',
license = 'MIT/X11',
labels = { 'neovim' }
}
dependencies = {
'lua == 5.1',
}
source = {
url = 'http://github.com/lewis6991/gitsigns.nvim/archive/v' .. _MODREV .. '.zip',
dir = 'gitsigns.nvim-' .. _MODREV,
}
if _MODREV == 'scm' then
source = {
url = 'git://github.com/lewis6991/gitsigns.nvim',
}
end
build = {
type = 'builtin',
copy_directories = {
'doc'
}
}
lewis6991-gitsigns.nvim-7010000/lua/ 0000775 0000000 0000000 00000000000 14765572704 0016736 5 ustar 00root root 0000000 0000000 lewis6991-gitsigns.nvim-7010000/lua/gitsigns.lua 0000664 0000000 0000000 00000013337 14765572704 0021277 0 ustar 00root root 0000000 0000000 local async = require('gitsigns.async')
local log = require('gitsigns.debug.log')
local Config = require('gitsigns.config')
local config = Config.config
local api = vim.api
local uv = vim.uv or vim.loop
--- @class gitsigns.main
local M = {}
local cwd_watcher ---@type uv.uv_fs_event_t?
--- @async
--- @return string? gitdir
--- @return string? head
local function get_gitdir_and_head()
local cwd = assert(uv.cwd())
-- Run on the main loop to avoid:
-- https://github.com/LazyVim/LazyVim/discussions/3407#discussioncomment-9622211
async.schedule()
-- Look in the cache first
for _, bcache in pairs(require('gitsigns.cache').cache) do
local repo = bcache.git_obj.repo
if repo.toplevel == cwd then
return repo.gitdir, repo.abbrev_head
end
end
local info = require('gitsigns.git').Repo.get_info(cwd)
if info then
return info.gitdir, info.abbrev_head
end
end
local update_cwd_head = async.async(function()
local cwd = uv.cwd()
if not cwd then
return
end
local paths = vim.fs.find('.git', {
limit = 1,
upward = true,
type = 'directory',
})
if #paths == 0 then
return
end
local gitdir, head = get_gitdir_and_head()
async.schedule()
api.nvim_exec_autocmds('User', {
pattern = 'GitSignsUpdate',
modeline = false,
})
vim.g.gitsigns_head = head
if not gitdir then
return
end
local towatch = gitdir .. '/HEAD'
if cwd_watcher then
cwd_watcher:stop()
-- TODO(lewis6991): (#1027) Running `fs_event:stop()` -> `fs_event:start()`
-- in the same loop event, on Windows, causes Nvim to hang on quit.
if vim.fn.has('win32') then
async.schedule()
end
else
cwd_watcher = assert(uv.new_fs_event())
end
if cwd_watcher:getpath() == towatch then
-- Already watching
return
end
local debounce_trailing = require('gitsigns.debounce').debounce_trailing
local update_head = debounce_trailing(
100,
async.async(function()
local git = require('gitsigns.git')
local new_head = git.Repo.get_info(cwd).abbrev_head
async.schedule()
vim.g.gitsigns_head = new_head
end)
)
-- Watch .git/HEAD to detect branch changes
cwd_watcher:start(
towatch,
{},
async.async(function(err)
local __FUNC__ = 'cwd_watcher_cb'
if err then
log.dprintf('Git dir update error: %s', err)
return
end
log.dprint('Git cwd dir update')
update_head()
end)
)
end)
local function setup_cli()
api.nvim_create_user_command('Gitsigns', function(params)
require('gitsigns.cli').run(params)
end, {
force = true,
nargs = '*',
range = true,
complete = function(arglead, line)
return require('gitsigns.cli').complete(arglead, line)
end,
})
end
--- @async
local function setup_attach()
if not config.auto_attach then
return
end
local attach_autocmd_disabled = false
-- Need to attach in 'BufFilePost' since we always detach in 'BufFilePre'
api.nvim_create_autocmd({ 'BufFilePost', 'BufRead', 'BufNewFile', 'BufWritePost' }, {
group = 'gitsigns',
desc = 'Gitsigns: attach',
callback = function(args)
local bufnr = args.buf --[[@as integer]]
if attach_autocmd_disabled then
local __FUNC__ = 'attach_autocmd'
log.dprint('Attaching is disabled')
return
end
require('gitsigns.attach').attach(bufnr, nil, args.event)
end,
})
-- If the buffer name is about to change, then detach
api.nvim_create_autocmd('BufFilePre', {
group = 'gitsigns',
desc = 'Gitsigns: detach when changing buffer names',
callback = function(args)
require('gitsigns.attach').detach(args.buf)
end,
})
--- vimpgrep creates and deletes lots of buffers so attaching to each one will
--- waste lots of resource and slow down vimgrep.
api.nvim_create_autocmd({ 'QuickFixCmdPre', 'QuickFixCmdPost' }, {
group = 'gitsigns',
pattern = '*vimgrep*',
desc = 'Gitsigns: disable attach during vimgrep',
callback = function(args)
attach_autocmd_disabled = args.event == 'QuickFixCmdPre'
end,
})
-- Attach to all open buffers
for _, buf in ipairs(api.nvim_list_bufs()) do
if api.nvim_buf_is_loaded(buf) and api.nvim_buf_get_name(buf) ~= '' then
-- Make sure to run each attach in its on async context in case one of the
-- attaches is aborted.
require('gitsigns.attach').attach(buf, nil, 'setup')
end
end
end
local function setup_cwd_head()
local debounce = require('gitsigns.debounce').debounce_trailing
local update_cwd_head_debounced = debounce(100, update_cwd_head)
update_cwd_head_debounced()
-- Need to debounce in case some plugin changes the cwd too often
-- (like vim-grepper)
api.nvim_create_autocmd('DirChanged', {
group = 'gitsigns',
callback = function()
update_cwd_head_debounced()
end,
})
end
--- Setup and start Gitsigns.
---
--- @param cfg table|nil Configuration for Gitsigns.
--- See |gitsigns-usage| for more details.
function M.setup(cfg)
Config.build(cfg)
if vim.fn.executable('git') == 0 then
print('gitsigns: git not in path. Aborting setup')
return
end
api.nvim_create_augroup('gitsigns', {})
log.setup(config)
setup_cli()
require('gitsigns.highlight').setup()
setup_attach()
setup_cwd_head()
end
--- @type gitsigns.main|gitsigns.actions|gitsigns.attach|gitsigns.debug
M = setmetatable(M, {
__index = function(_, f)
local attach = require('gitsigns.attach')
if attach[f] then
return attach[f]
end
local actions = require('gitsigns.actions')
if actions[f] then
return actions[f]
end
if config.debug_mode then
local debug = require('gitsigns.debug')
if debug[f] then
return debug[f]
end
end
end,
})
return M
lewis6991-gitsigns.nvim-7010000/lua/gitsigns/ 0000775 0000000 0000000 00000000000 14765572704 0020565 5 ustar 00root root 0000000 0000000 lewis6991-gitsigns.nvim-7010000/lua/gitsigns/actions.lua 0000664 0000000 0000000 00000067365 14765572704 0022751 0 ustar 00root root 0000000 0000000 local async = require('gitsigns.async')
local Hunks = require('gitsigns.hunks')
local manager = require('gitsigns.manager')
local message = require('gitsigns.message')
local popup = require('gitsigns.popup')
local util = require('gitsigns.util')
local run_diff = require('gitsigns.diff')
local config = require('gitsigns.config').config
local mk_repeatable = require('gitsigns.repeat').mk_repeatable
local cache = require('gitsigns.cache').cache
local api = vim.api
local current_buf = api.nvim_get_current_buf
--- @class gitsigns.actions
local M = {}
--- @class Gitsigns.CmdParams.Smods
--- @field vertical boolean
--- @field split 'aboveleft'|'belowright'|'topleft'|'botright'
--- @class Gitsigns.CmdArgs
--- @field vertical? boolean
--- @field split? boolean
--- @field global? boolean
--- @class Gitsigns.CmdParams
--- @field range integer
--- @field line1 integer
--- @field line2 integer
--- @field count integer
--- @field smods Gitsigns.CmdParams.Smods
--- Variations of functions from M which are used for the Gitsigns command
--- @type table
local C = {}
local CP = {}
--- @param arglead string
--- @return string[]
local function complete_heads(arglead)
--- @type string[]
local all =
vim.fn.systemlist({ 'git', 'rev-parse', '--symbolic', '--branches', '--tags', '--remotes' })
return vim.tbl_filter(
--- @param x string
--- @return boolean
function(x)
return vim.startswith(x, arglead)
end,
all
)
end
--- Toggle |gitsigns-config-signbooleancolumn|
---
--- @param value boolean|nil Value to set toggle. If `nil`
--- the toggle value is inverted.
--- @return boolean : Current value of |gitsigns-config-signcolumn|
M.toggle_signs = function(value)
if value ~= nil then
config.signcolumn = value
else
config.signcolumn = not config.signcolumn
end
M.refresh()
return config.signcolumn
end
--- Toggle |gitsigns-config-numhl|
---
--- @param value boolean|nil Value to set toggle. If `nil`
--- the toggle value is inverted.
---
--- @return boolean : Current value of |gitsigns-config-numhl|
M.toggle_numhl = function(value)
if value ~= nil then
config.numhl = value
else
config.numhl = not config.numhl
end
M.refresh()
return config.numhl
end
--- Toggle |gitsigns-config-linehl|
---
--- @param value boolean|nil Value to set toggle. If `nil`
--- the toggle value is inverted.
--- @return boolean : Current value of |gitsigns-config-linehl|
M.toggle_linehl = function(value)
if value ~= nil then
config.linehl = value
else
config.linehl = not config.linehl
end
M.refresh()
return config.linehl
end
--- Toggle |gitsigns-config-word_diff|
---
--- @param value boolean|nil Value to set toggle. If `nil`
--- the toggle value is inverted.
--- @return boolean : Current value of |gitsigns-config-word_diff|
M.toggle_word_diff = function(value)
if value ~= nil then
config.word_diff = value
else
config.word_diff = not config.word_diff
end
-- Don't use refresh() to avoid flicker
util.redraw({ buf = 0, range = { vim.fn.line('w0') - 1, vim.fn.line('w$') } })
return config.word_diff
end
--- Toggle |gitsigns-config-current_line_blame|
---
--- @param value boolean|nil Value to set toggle. If `nil`
--- the toggle value is inverted.
--- @return boolean : Current value of |gitsigns-config-current_line_blame|
M.toggle_current_line_blame = function(value)
if value ~= nil then
config.current_line_blame = value
else
config.current_line_blame = not config.current_line_blame
end
M.refresh()
return config.current_line_blame
end
--- @deprecated Use |gitsigns.preview_hunk_inline()|
--- Toggle |gitsigns-config-show_deleted|
---
--- @param value boolean|nil Value to set toggle. If `nil`
--- the toggle value is inverted.
--- @return boolean : Current value of |gitsigns-config-show_deleted|
M.toggle_deleted = function(value)
if value ~= nil then
config.show_deleted = value
else
config.show_deleted = not config.show_deleted
end
M.refresh()
return config.show_deleted
end
--- @async
--- @param bufnr integer
local function update(bufnr)
local bcache = cache[bufnr]
if not bcache then
return
end
manager.update(bufnr)
if not bcache:schedule() then
return
end
if vim.wo.diff then
require('gitsigns.diffthis').update(bufnr)
end
end
--- @param params Gitsigns.CmdParams
--- @return [integer,integer]? range Range of lines to operate on.
local function get_range(params)
local range --- @type [integer, integer]?
if params.range > 0 then
range = { params.line1, params.line2 }
end
return range
end
--- Stage the hunk at the cursor position, or all lines in the
--- given range. If {range} is provided, all lines in the given
--- range are staged. This supports partial-hunks, meaning if a
--- range only includes a portion of a particular hunk, only the
--- lines within the range will be staged.
---
--- Attributes: ~
--- {async}
---
--- @param range table|nil List-like table of two integers making
--- up the line range from which you want to stage the hunks.
--- If running via command line, then this is taken from the
--- command modifiers.
--- @param opts table|nil Additional options:
--- โข {greedy}: (boolean)
--- Stage all contiguous hunks. Only useful if 'diff_opts'
--- contains `linematch`. Defaults to `true`.
M.stage_hunk = mk_repeatable(async.create(2, function(range, opts)
--- @cast range [integer, integer]?
opts = opts or {}
local bufnr = current_buf()
local bcache = cache[bufnr]
if not bcache then
return
end
if bcache:locked() then
print('Error: busy')
return
end
if not util.path_exists(bcache.file) then
print('Error: Cannot stage lines. Please add the file to the working tree.')
return
end
local hunk = bcache:get_hunk(range, opts.greedy ~= false, false)
local invert = false
if not hunk then
invert = true
hunk = bcache:get_hunk(range, opts.greedy ~= false, true)
end
if not hunk then
api.nvim_echo({ { 'No hunk to stage', 'WarningMsg' } }, false, {})
return
end
local err = bcache.git_obj:stage_hunks({ hunk }, invert)
if err then
message.error(err)
return
end
table.insert(bcache.staged_diffs, hunk)
bcache:invalidate(true)
update(bufnr)
end))
C.stage_hunk = function(_, params)
M.stage_hunk(get_range(params))
end
--- @param bufnr integer
--- @param hunk Gitsigns.Hunk.Hunk
local function reset_hunk(bufnr, hunk)
local lstart, lend --- @type integer, integer
if hunk.type == 'delete' then
lstart = hunk.added.start
lend = hunk.added.start
else
lstart = hunk.added.start - 1
lend = hunk.added.start - 1 + hunk.added.count
end
if hunk.removed.no_nl_at_eof ~= hunk.added.no_nl_at_eof then
local no_eol = hunk.added.no_nl_at_eof or false
vim.bo[bufnr].endofline = no_eol
vim.bo[bufnr].fixendofline = no_eol
end
util.set_lines(bufnr, lstart, lend, hunk.removed.lines)
end
--- Reset the lines of the hunk at the cursor position, or all
--- lines in the given range. If {range} is provided, all lines in
--- the given range are reset. This supports partial-hunks,
--- meaning if a range only includes a portion of a particular
--- hunk, only the lines within the range will be reset.
---
--- @param range table|nil List-like table of two integers making
--- up the line range from which you want to reset the hunks.
--- If running via command line, then this is taken from the
--- command modifiers.
--- @param opts table|nil Additional options:
--- โข {greedy}: (boolean)
--- Stage all contiguous hunks. Only useful if 'diff_opts'
--- contains `linematch`. Defaults to `true`.
M.reset_hunk = mk_repeatable(async.create(2, function(range, opts)
--- @cast range [integer, integer]?
opts = opts or {}
local bufnr = current_buf()
local bcache = cache[bufnr]
if not bcache then
return
end
local hunk = bcache:get_hunk(range, opts.greedy ~= false, false)
if not hunk then
api.nvim_echo({ { 'No hunk to reset', 'WarningMsg' } }, false, {})
return
end
reset_hunk(bufnr, hunk)
end))
C.reset_hunk = function(_, params)
M.reset_hunk(get_range(params))
end
--- Reset the lines of all hunks in the buffer.
M.reset_buffer = function()
local bufnr = current_buf()
local bcache = cache[bufnr]
if not bcache then
return
end
local hunks = bcache.hunks
if not hunks or #hunks == 0 then
api.nvim_echo({ { 'No unstaged changes in the buffer to reset', 'WarningMsg' } }, false, {})
return
end
for i = #hunks, 1, -1 do
reset_hunk(bufnr, hunks[i])
end
end
--- @deprecated use |gitsigns.stage_hunk()| on staged signs
--- Undo the last call of stage_hunk().
---
--- Note: only the calls to stage_hunk() performed in the current
--- session can be undone.
---
--- Attributes: ~
--- {async}
M.undo_stage_hunk = async.create(0, function()
local bufnr = current_buf()
local bcache = cache[bufnr]
if not bcache then
return
end
if bcache:locked() then
print('Error: busy')
return
end
local hunk = table.remove(bcache.staged_diffs)
if not hunk then
print('No hunks to undo')
return
end
local err = bcache.git_obj:stage_hunks({ hunk }, true)
if err then
message.error(err)
return
end
bcache:invalidate(true)
update(bufnr)
end)
--- Stage all hunks in current buffer.
---
--- Attributes: ~
--- {async}
M.stage_buffer = async.create(0, function()
local bufnr = current_buf()
local bcache = cache[bufnr]
if not bcache then
return
end
if bcache:locked() then
print('Error: busy')
return
end
-- Only process files with existing hunks
local hunks = bcache.hunks
if not hunks or #hunks == 0 then
print('No unstaged changes in file to stage')
return
end
if not util.path_exists(bcache.git_obj.file) then
print('Error: Cannot stage file. Please add it to the working tree.')
return
end
local err = bcache.git_obj:stage_hunks(hunks)
if err then
message.error(err)
return
end
for _, hunk in ipairs(hunks) do
table.insert(bcache.staged_diffs, hunk)
end
bcache:invalidate(true)
update(bufnr)
end)
--- Unstage all hunks for current buffer in the index. Note:
--- Unlike |gitsigns.undo_stage_hunk()| this doesn't simply undo
--- stages, this runs an `git reset` on current buffers file.
---
--- Attributes: ~
--- {async}
M.reset_buffer_index = async.create(0, function()
local bufnr = current_buf()
local bcache = cache[bufnr]
if not bcache then
return
end
if bcache:locked() then
print('Error: busy')
return
end
-- `bcache.staged_diffs` won't contain staged changes outside of current
-- neovim session so signs added from this unstage won't be complete They will
-- however be fixed by gitdir watcher and properly updated We should implement
-- some sort of initial population from git diff, after that this function can
-- be improved to check if any staged hunks exists and it can undo changes
-- using git apply line by line instead of resetting whole file
bcache.staged_diffs = {}
bcache.git_obj:unstage_file()
bcache:invalidate(true)
update(bufnr)
end)
--- Jump to hunk in the current buffer. If a hunk preview
--- (popup or inline) was previously opened, it will be re-opened
--- at the next hunk.
---
--- Attributes: ~
--- {async}
---
--- @param direction 'first'|'last'|'next'|'prev'
--- @param opts table|nil Configuration table. Keys:
--- โข {wrap}: (boolean)
--- Whether to loop around file or not. Defaults
--- to the value 'wrapscan'
--- โข {navigation_message}: (boolean)
--- Whether to show navigation messages or not.
--- Looks at 'shortmess' for default behaviour.
--- โข {foldopen}: (boolean)
--- Expand folds when navigating to a hunk which is
--- inside a fold. Defaults to `true` if 'foldopen'
--- contains `search`.
--- โข {preview}: (boolean)
--- Automatically open preview_hunk() upon navigating
--- to a hunk.
--- โข {greedy}: (boolean)
--- Only navigate between non-contiguous hunks. Only useful if
--- 'diff_opts' contains `linematch`. Defaults to `true`.
--- โข {target}: (`'unstaged'|'staged'|'all'`)
--- Which kinds of hunks to target. Defaults to `'unstaged'`.
--- โข {count}: (integer)
--- Number of times to advance. Defaults to |v:count1|.
M.nav_hunk = async.create(2, function(direction, opts)
--- @cast opts Gitsigns.NavOpts?
require('gitsigns.nav').nav_hunk(direction, opts)
end)
C.nav_hunk = function(args, _)
M.nav_hunk(args[1], args)
end
--- @deprecated use |gitsigns.nav_hunk()|
--- Jump to the next hunk in the current buffer. If a hunk preview
--- (popup or inline) was previously opened, it will be re-opened
--- at the next hunk.
---
--- Attributes: ~
--- {async}
---
--- Parameters: ~
--- See |gitsigns.nav_hunk()|.
M.next_hunk = async.create(1, function(opts)
require('gitsigns.nav').nav_hunk('next', opts)
end)
C.next_hunk = function(args, _)
M.nav_hunk('next', args)
end
--- @deprecated use |gitsigns.nav_hunk()|
--- Jump to the previous hunk in the current buffer. If a hunk preview
--- (popup or inline) was previously opened, it will be re-opened
--- at the previous hunk.
---
--- Attributes: ~
--- {async}
---
--- Parameters: ~
--- See |gitsigns.nav_hunk()|.
M.prev_hunk = async.create(1, function(opts)
require('gitsigns.nav').nav_hunk('prev', opts)
end)
C.prev_hunk = function(args, _)
M.nav_hunk('prev', args)
end
--- Preview the hunk at the cursor position in a floating
--- window. If the preview is already open, calling this
--- will cause the window to get focus.
M.preview_hunk = function()
require('gitsigns.preview').preview_hunk()
end
--- Preview the hunk at the cursor position inline in the buffer.
M.preview_hunk_inline = async.create(0, function()
require('gitsigns.preview').preview_hunk_inline()
end)
--- Select the hunk under the cursor.
---
--- @param opts table|nil Additional options:
--- โข {greedy}: (boolean)
--- Select all contiguous hunks. Only useful if 'diff_opts'
--- contains `linematch`. Defaults to `true`.
M.select_hunk = function(opts)
local bufnr = current_buf()
local bcache = cache[bufnr]
if not bcache then
return
end
opts = opts or {}
local hunk --- @type Gitsigns.Hunk.Hunk?
async
.arun(function()
hunk = bcache:get_hunk(nil, opts.greedy ~= false)
end)
:wait()
if not hunk then
return
end
if vim.fn.mode():find('v') ~= nil then
vim.cmd('normal! ' .. hunk.added.start .. 'GoV' .. hunk.vend .. 'G')
else
vim.cmd('normal! ' .. hunk.added.start .. 'GV' .. hunk.vend .. 'G')
end
end
--- Get hunk array for specified buffer.
---
--- @param bufnr integer Buffer number, if not provided (or 0)
--- will use current buffer.
--- @return table|nil : Array of hunk objects.
--- Each hunk object has keys:
--- โข `"type"`: String with possible values: "add", "change",
--- "delete"
--- โข `"head"`: Header that appears in the unified diff
--- output.
--- โข `"lines"`: Line contents of the hunks prefixed with
--- either `"-"` or `"+"`.
--- โข `"removed"`: Sub-table with fields:
--- โข `"start"`: Line number (1-based)
--- โข `"count"`: Line count
--- โข `"added"`: Sub-table with fields:
--- โข `"start"`: Line number (1-based)
--- โข `"count"`: Line count
M.get_hunks = function(bufnr)
bufnr = bufnr or current_buf()
if not cache[bufnr] then
return
end
local ret = {} --- @type Gitsigns.Hunk.Hunk_Public[]
-- TODO(lewis6991): allow this to accept a greedy option
for _, h in ipairs(cache[bufnr].hunks or {}) do
ret[#ret + 1] = {
head = h.head,
lines = Hunks.patch_lines(h, vim.bo[bufnr].fileformat),
type = h.type,
added = h.added,
removed = h.removed,
}
end
return ret
end
--- @async
--- @param repo Gitsigns.Repo
--- @param info Gitsigns.BlameInfoPublic
--- @return Gitsigns.Hunk.Hunk?, integer?, integer
local function get_blame_hunk(repo, info)
local a = {}
-- If no previous so sha of blame added the file
if info.previous_sha and info.previous_filename then
a = repo:get_show_text(info.previous_sha .. ':' .. info.previous_filename)
end
local b = repo:get_show_text(info.sha .. ':' .. info.filename)
local hunks = run_diff(a, b, false)
local hunk, i = Hunks.find_hunk(info.orig_lnum, hunks)
return hunk, i, #hunks
end
--- @param is_committed boolean
--- @param full boolean
--- @return [string, string][][]
local function create_blame_fmt(is_committed, full)
if not is_committed then
return {
{ { '', 'Label' } },
}
end
return {
{
{ ' ', 'Directory' },
{ ' ', 'MoreMsg' },
{ '()', 'Label' },
{ ':', 'NormalFloat' },
},
{ { full and '' or '', 'NormalFloat' } },
}
end
--- Run git blame on the current line and show the results in a
--- floating window. If already open, calling this will cause the
--- window to get focus.
---
--- Attributes: ~
--- {async}
---
--- @param opts table|nil Additional options:
--- โข {full}: (boolean)
--- Display full commit message with hunk.
--- โข {ignore_whitespace}: (boolean)
--- Ignore whitespace when running blame.
--- โข {extra_opts}: (string[])
--- Extra options passed to `git-blame`.
M.blame_line = async.create(1, function(opts)
if popup.focus_open('blame') then
return
end
--- @type Gitsigns.LineBlameOpts
opts = opts or {}
local bufnr = current_buf()
local bcache = cache[bufnr]
if not bcache then
return
end
local loading = vim.defer_fn(function()
popup.create({ { { 'Loading...', 'Title' } } }, config.preview_config)
end, 1000)
if not bcache:schedule() then
return
end
local fileformat = vim.bo[bufnr].fileformat
local lnum = api.nvim_win_get_cursor(0)[1]
local result = bcache:get_blame(lnum, opts)
pcall(function()
loading:close()
end)
if not bcache:schedule() then
return
end
result = util.convert_blame_info(assert(result))
local is_committed = result.sha and tonumber('0x' .. result.sha) ~= 0
local blame_linespec = create_blame_fmt(is_committed, opts.full)
if is_committed and opts.full then
local body = bcache.git_obj.repo:command(
{ 'show', '-s', '--format=%B', result.sha },
{ text = true }
)
local hunk, hunk_no, num_hunks = get_blame_hunk(bcache.git_obj.repo, result)
assert(hunk and hunk_no and num_hunks)
result.hunk_no = hunk_no
result.body = body
result.num_hunks = num_hunks
result.hunk_head = hunk.head
vim.list_extend(blame_linespec, {
{ { 'Hunk of ', 'Title' }, { ' ', 'LineNr' } },
unpack(Hunks.linespec_for_hunk(hunk, fileformat)),
})
end
if not bcache:schedule() then
return
end
popup.create(popup.lines_format(blame_linespec, result), config.preview_config, 'blame')
end)
C.blame_line = function(args, _)
M.blame_line(args)
end
--- Run git-blame on the current file and open the results
--- in a scroll-bound vertical split.
---
--- Mappings:
--- is mapped to open a menu with the other mappings
--- Note: must be held to activate the mappings whilst the menu is
--- open.
--- s [Show commit] in a vertical split.
--- S [Show commit] in a new tab.
--- r [Reblame at commit]
---
--- Attributes: ~
--- {async}
M.blame = async.create(0, function()
return require('gitsigns.blame').blame()
end)
--- @async
--- @param bcache Gitsigns.CacheEntry
--- @param base string?
local function update_buf_base(bcache, base)
bcache.file_mode = base == 'FILE'
if not bcache.file_mode then
bcache.git_obj:change_revision(base)
end
bcache:invalidate(true)
update(bcache.bufnr)
end
--- Change the base revision to diff against. If {base} is not
--- given, then the original base is used. If {global} is given
--- and true, then change the base revision of all buffers,
--- including any new buffers.
---
--- Attributes: ~
--- {async}
---
--- Examples: >vim
--- " Change base to 1 commit behind head
--- :lua require('gitsigns').change_base('HEAD~1')
---
--- " Also works using the Gitsigns command
--- :Gitsigns change_base HEAD~1
---
--- " Other variations
--- :Gitsigns change_base ~1
--- :Gitsigns change_base ~
--- :Gitsigns change_base ^
---
--- " Commits work too
--- :Gitsigns change_base 92eb3dd
---
--- " Revert to original base
--- :Gitsigns change_base
--- <
---
--- For a more complete list of ways to specify bases, see
--- |gitsigns-revision|.
---
--- @param base string|nil The object/revision to diff against.
--- @param global boolean|nil Change the base of all buffers.
M.change_base = async.create(2, function(base, global)
base = util.norm_base(base)
if global then
config.base = base
for _, bcache in pairs(cache) do
update_buf_base(bcache, base)
end
else
local bufnr = current_buf()
local bcache = cache[bufnr]
if not bcache then
return
end
update_buf_base(bcache, base)
end
end)
C.change_base = function(args, _)
M.change_base(args[1], (args[2] or args.global))
end
CP.change_base = complete_heads
--- Reset the base revision to diff against back to the
--- index.
---
--- Alias for `change_base(nil, {global})` .
M.reset_base = function(global)
M.change_base(nil, global)
end
C.reset_base = function(args, _)
M.change_base(nil, (args[1] or args.global))
end
--- Perform a |vimdiff| on the given file with {base} if it is
--- given, or with the currently set base (index by default).
---
--- If {base} is the index, then the opened buffer is editable and
--- any written changes will update the index accordingly.
---
--- Examples: >vim
--- " Diff against the index
--- :Gitsigns diffthis
---
--- " Diff against the last commit
--- :Gitsigns diffthis ~1
--- <
---
--- For a more complete list of ways to specify bases, see
--- |gitsigns-revision|.
---
--- Attributes: ~
--- {async}
---
--- @param base string|nil Revision to diff against. Defaults to index.
--- @param opts table|nil Additional options:
--- โข {vertical}: {boolean}. Split window vertically. Defaults to
--- config.diff_opts.vertical. If running via command line, then
--- this is taken from the command modifiers.
--- โข {split}: {string}. One of: 'aboveleft', 'belowright',
--- 'botright', 'rightbelow', 'leftabove', 'topleft'. Defaults to
--- 'aboveleft'. If running via command line, then this is taken
--- from the command modifiers.
M.diffthis = function(base, opts)
-- TODO(lewis6991): can't pass numbers as strings from the command line
if base ~= nil then
base = tostring(base)
end
opts = opts or {}
if opts.vertical == nil then
opts.vertical = config.diff_opts.vertical
end
require('gitsigns.diffthis').diffthis(base, opts)
end
C.diffthis = function(args, params)
-- TODO(lewis6991): validate these
local opts = {
vertical = args.vertical,
split = args.split,
}
if params.smods then
if params.smods.split ~= '' and opts.split == nil then
opts.split = params.smods.split
end
if opts.vertical == nil then
opts.vertical = params.smods.vertical
end
end
M.diffthis(args[1], opts)
end
CP.diffthis = complete_heads
-- C.test = function(pos_args: {any}, named_args: {string:any}, params: api.UserCmdParams)
-- print('POS ARGS:', vim.inspect(pos_args))
-- print('NAMED ARGS:', vim.inspect(named_args))
-- print('PARAMS:', vim.inspect(params))
-- end
--- Show revision {base} of the current file, if it is given, or
--- with the currently set base (index by default).
---
--- If {base} is the index, then the opened buffer is editable and
--- any written changes will update the index accordingly.
---
--- Examples: >vim
--- " View the index version of the file
--- :Gitsigns show
---
--- " View revision of file in the last commit
--- :Gitsigns show ~1
--- <
---
--- For a more complete list of ways to specify bases, see
--- |gitsigns-revision|.
---
--- Attributes: ~
--- {async}
M.show = function(revision, callback)
local bufnr = api.nvim_get_current_buf()
if not cache[bufnr] then
print('Error: Buffer is not attached.')
return
end
local diffthis = require('gitsigns.diffthis')
diffthis.show(bufnr, revision, callback)
end
C.show = function(args, _)
M.show(args[1])
end
CP.show = complete_heads
--- Populate the quickfix list with hunks. Automatically opens the
--- quickfix window.
---
--- Attributes: ~
--- {async}
---
--- @param target integer|string
--- Specifies which files hunks are collected from.
--- Possible values.
--- โข [integer]: The buffer with the matching buffer
--- number. `0` for current buffer (default).
--- โข `"attached"`: All attached buffers.
--- โข `"all"`: All modified files for each git
--- directory of all attached buffers in addition
--- to the current working directory.
--- @param opts table|nil Additional options:
--- โข {use_location_list}: (boolean)
--- Populate the location list instead of the
--- quickfix list. Default to `false`.
--- โข {nr}: (integer)
--- Window number or ID when using location list.
--- Expand folds when navigating to a hunk which is
--- inside a fold. Defaults to `0`.
--- โข {open}: (boolean)
--- Open the quickfix/location list viewer.
--- Defaults to `true`.
M.setqflist = async.create(2, function(target, opts)
require('gitsigns.qflist').setqflist(target, opts)
end)
C.setqflist = function(args, _)
local target = tonumber(args[1]) or args[1]
M.setqflist(target, args)
end
--- Populate the location list with hunks. Automatically opens the
--- location list window.
---
--- Alias for: `setqflist({target}, { use_location_list = true, nr = {nr} }`
---
--- Attributes: ~
--- {async}
---
--- @param nr? integer Window number or the |window-ID|.
--- `0` for the current window (default).
--- @param target integer|string See |gitsigns.setqflist()|.
M.setloclist = function(nr, target)
M.setqflist(target, {
nr = nr,
use_location_list = true,
})
end
C.setloclist = function(args, _)
local target = tonumber(args[2]) or args[2]
M.setloclist(tonumber(args[1]), target)
end
--- Get all the available line specific actions for the current
--- buffer at the cursor position.
---
--- @return table|nil : Dictionary of action name to function which when called
--- performs action.
M.get_actions = function()
local bufnr = current_buf()
local bcache = cache[bufnr]
if not bcache then
return
end
local hunk = bcache:get_cursor_hunk()
--- @type string[]
local actions_l = {}
if hunk then
vim.list_extend(actions_l, {
'stage_hunk',
'reset_hunk',
'preview_hunk',
'select_hunk',
})
else
actions_l[#actions_l + 1] = 'blame_line'
end
if not vim.tbl_isempty(bcache.staged_diffs) then
actions_l[#actions_l + 1] = 'undo_stage_hunk'
end
local actions = {} --- @type table
for _, a in ipairs(actions_l) do
actions[a] = M[a] --[[@as function]]
end
return actions
end
for name, f in
pairs(M --[[@as table]])
do
if vim.startswith(name, 'toggle') then
C[name] = function(args)
f(args[1])
end
end
end
--- Refresh all buffers.
---
--- Attributes: ~
--- {async}
M.refresh = async.create(0, function()
manager.reset_signs()
require('gitsigns.highlight').setup_highlights()
require('gitsigns.current_line_blame').setup()
for k, v in pairs(cache) do
v:invalidate(true)
manager.update(k)
end
end)
--- @param name string
--- @return fun(args: table, params: Gitsigns.CmdParams)
function M._get_cmd_func(name)
return C[name]
end
--- @param name string
--- @return fun(arglead: string): string[]
function M._get_cmp_func(name)
return CP[name]
end
return M
lewis6991-gitsigns.nvim-7010000/lua/gitsigns/async.lua 0000664 0000000 0000000 00000026617 14765572704 0022421 0 ustar 00root root 0000000 0000000 --- @type fun(...: any): { [integer]: any, n: integer }
local pack_len = vim.F.pack_len
--- @type fun(t: { [integer]: any, n: integer })
local unpack_len = vim.F.unpack_len
--- @class Gitsigns.async
local M = {}
--- Weak table to keep track of running tasks
--- @type table
local threads = setmetatable({}, { __mode = 'k' })
--- @return Gitsigns.async.Task?
local function running()
local task = threads[coroutine.running()]
if task and not (task:_completed() or task._closing) then
return task
end
end
--- Base class for async tasks. Async functions should return a subclass of
--- this. This is designed specifically to be a base class of uv_handle_t
--- @class Gitsigns.async.Handle
--- @field close fun(self: Gitsigns.async.Handle, callback: fun())
--- @field is_closing? fun(self: Gitsigns.async.Handle): boolean
--- @alias Gitsigns.async.CallbackFn fun(...: any): Gitsigns.async.Handle?
--- @class Gitsigns.async.Task : Gitsigns.async.Handle
--- @field private _callbacks table
--- @field private _thread thread
---
--- Tasks can call other async functions (task of callback functions)
--- when we are waiting on a child, we store the handle to it here so we can
--- cancel it.
--- @field private _current_child? {close: fun(self, callback: fun())}
---
--- Error result of the task is an error occurs.
--- Must use `await` to get the result.
--- @field private _err? any
---
--- Result of the task.
--- Must use `await` to get the result.
--- @field private _result? any[]
local Task = {}
Task.__index = Task
--- @package
--- @param func function
--- @return Gitsigns.async.Task
function Task._new(func)
local thread = coroutine.create(func)
local self = setmetatable({
_closing = false,
_thread = thread,
_callbacks = {},
}, Task)
threads[thread] = self
return self
end
--- @param callback fun(err?: any, result?: any[])
function Task:await(callback)
if self._closing then
callback('closing')
elseif self:_completed() then -- TODO(lewis6991): test
-- Already finished or closed
callback(self._err, self._result)
else
table.insert(self._callbacks, callback)
end
end
--- @package
function Task:_completed()
return (self._err or self._result) ~= nil
end
-- Use max 32-bit signed int value to avoid overflow on 32-bit systems.
local MAX_TIMEOUT = 2 ^ 31 - 1
--- Synchronously wait (protected) for a task to finish (blocking)
---
--- If an error is returned, `Task:traceback()` can be used to get the
--- stack trace of the error.
---
--- Example:
---
--- local ok, err_or_result = task:pwait(10)
---
--- local _, result = assert(task:pwait(10), task:traceback())
---
--- Can be called if a task is closing.
--- @param timeout? integer
--- @return boolean status
--- @return any ... result or error
function Task:pwait(timeout)
local done = vim.wait(timeout or MAX_TIMEOUT, function()
-- Note we use self:_completed() instead of self:await() to avoid creating a
-- callback. This avoids having to cleanup/unregister any callback in the
-- case of a timeout.
return self:_completed()
end)
if not done then
return false, 'timeout'
elseif self._err then
return false, self._err
else
-- TODO(lewis6991): test me
return true, unpack_len(assert(self._result))
end
end
--- Synchronously wait for a task to finish (blocking)
--- @param timeout? integer
--- @return any ... result
function Task:wait(timeout)
local res = pack_len(self:pwait(timeout))
local stat = table.remove(res, 1)
res.n = res.n - 1
if not stat then
error(res[1])
end
return unpack_len(res)
end
--- @param obj any
--- @return boolean
local function is_task(obj)
return type(obj) == 'table' and getmetatable(obj) == Task
end
--- @private
--- @param msg? string
--- @param _lvl? integer
--- @return string
function Task:_traceback(msg, _lvl)
_lvl = _lvl or 0
local thread = ('[%s] '):format(self._thread)
local child = self._current_child
if is_task(child) then
--- @cast child Gitsigns.async.Task
msg = child:_traceback(msg, _lvl + 1)
end
local tblvl = is_task(child) and 2 or nil
msg = msg .. debug.traceback(self._thread, '', tblvl):gsub('\n\t', '\n\t' .. thread)
if _lvl == 0 then
--- @type string
msg = msg
:gsub('\nstack traceback:\n', '\nSTACK TRACEBACK:\n', 1)
:gsub('\nstack traceback:\n', '\n')
:gsub('\nSTACK TRACEBACK:\n', '\nstack traceback:\n', 1)
end
return msg
end
--- @param msg? string
--- @return string
function Task:traceback(msg)
return self:_traceback(msg)
end
--- @package
--- @param err? any
--- @param result? any[]
function Task:_finish(err, result)
self._current_child = nil
self._err = err
self._result = result
threads[self._thread] = nil
for _, cb in pairs(self._callbacks) do
-- Needs to be pcall as step() (who calls this function) cannot error
pcall(cb, err, result)
end
end
--- @return boolean
function Task:is_closing()
return self._closing
end
--- @param callback? fun()
function Task:close(callback)
if self:_completed() then
if callback then
callback()
end
return
end
if callback then
self:await(function()
callback()
end)
end
if self._closing then
return
end
self._closing = true
if self._current_child then
self._current_child:close(function()
self:_finish('closed')
end)
else
self:_finish('closed')
end
end
--- @param callback function
--- @param ... any
--- @return fun()
local function wrap_cb(callback, ...)
local args = pack_len(...)
return function()
return callback(unpack_len(args))
end
end
--- @param obj any
--- @return boolean
local function is_async_handle(obj)
local ty = type(obj)
return (ty == 'table' or ty == 'userdata') and vim.is_callable(obj.close)
end
function Task:_resume(...)
--- @type [boolean, string|Gitsigns.async.CallbackFn]
local ret = { coroutine.resume(self._thread, ...) }
local stat = table.remove(ret, 1) --- @type boolean
--- @cast ret [string|Gitsigns.async.CallbackFn]
if not stat then
-- Coroutine had error
self:_finish(ret[1])
elseif coroutine.status(self._thread) == 'dead' then
-- Coroutine finished
self:_finish(nil, ret)
else
--- @cast ret [Gitsigns.async.CallbackFn]
local fn = ret[1]
-- TODO(lewis6991): refine error handler to be more specific
local ok, r
ok, r = pcall(fn, function(...)
if is_async_handle(r) then
--- @cast r Gitsigns.async.Handle
-- We must close children before we resume to ensure
-- all resources are collected.
r:close(wrap_cb(self._resume, self, ...))
else
self:_resume(...)
end
end)
if not ok then
self:_finish(r)
elseif is_async_handle(r) then
self._current_child = r
end
end
end
--- @package
function Task:_log(...)
print(self._thread, ...)
end
--- @return 'running'|'suspended'|'normal'|'dead'?
function Task:status()
return coroutine.status(self._thread)
end
--- @param func function
--- @param ... any
--- @return Gitsigns.async.Task
function M.arun(func, ...)
local task = Task._new(func)
task:_resume(...)
return task
end
--- Create an async function
function M.async(func)
return function(...)
return M.arun(func, ...)
end
end
--- Returns the status of a taskโs thread.
---
--- @param task? Gitsigns.async.Task
--- @return 'running'|'suspended'|'normal'|'dead'?
function M.status(task)
task = task or running()
if task then
assert(is_task(task), 'Expected Task')
return task:status()
end
end
--- @generic R1, R2, R3, R4
--- @param fun fun(callback: fun(r1: R1, r2: R2, r3: R3, r4: R4)): any?
--- @return R1, R2, R3, R4
local function yield(fun)
assert(type(fun) == 'function', 'Expected function')
return coroutine.yield(fun)
end
--- @param task Gitsigns.async.Task
--- @return any ...
local function await_task(task)
--- @param callback fun(err?: string, result?: any[])
--- @return function
local err, result = yield(function(callback)
task:await(callback)
return task
end)
if err then
-- TODO(lewis6991): what is the correct level to pass?
error(err, 0)
end
assert(result)
return (unpack(result, 1, table.maxn(result)))
end
--- Asynchronous blocking wait
--- @param argc integer
--- @param func Gitsigns.async.CallbackFn
--- @param ... any func arguments
--- @return any ...
local function await_cbfun(argc, func, ...)
local args = pack_len(...)
args.n = math.max(args.n, argc)
--- @param callback fun(success: boolean, result: any[])
--- @return any?
return yield(function(callback)
args[argc] = callback
return func(unpack_len(args))
end)
end
--- Asynchronous blocking wait
--- @overload fun(task: Gitsigns.async.Task): any ...
--- @overload fun(argc: integer, func: Gitsigns.async.CallbackFn, ...:any): any ...
function M.await(...)
assert(running(), 'Cannot await in non-async context')
local arg1 = select(1, ...)
if type(arg1) == 'number' then
return await_cbfun(...)
elseif is_task(arg1) then
return await_task(...)
else
error('Invalid arguments, expected Task or (argc, func) got: ' .. type(arg1), 2)
end
end
--- Creates an async function with a callback style function.
--- @param argc integer
--- @param func Gitsigns.async.CallbackFn
--- @return function
function M.awrap(argc, func)
assert(type(argc) == 'number')
assert(type(func) == 'function')
return function(...)
return M.await(argc, func, ...)
end
end
--- create([argc, ] func)
---
--- Use this to create a function which executes in an async context but
--- called from a non-async context. Inherently this cannot return anything
--- since it is non-blocking
---
--- If argc is not provided, then the created async function cannot be continued
---
--- @generic F: function
--- @param argc integer
--- @param func F
--- @return F
function M.create(argc, func)
assert(type(argc) == 'number')
assert(type(func) == 'function')
--- @param ... any
--- @return any ...
return function(...)
local task = Task._new(func)
local callback = argc and select(argc + 1, ...) or nil
if callback and type(callback) == 'function' then
task:await(callback)
end
task:_resume(unpack({ ... }, 1, argc))
return task
end
end
--- An async function that when called will yield to the Neovim scheduler to be
--- able to call the API.
M.schedule = M.awrap(1, vim.schedule)
--- @param tasks Gitsigns.async.Task[]
--- @return fun(): (integer?, any?, any[]?)
function M.iter(tasks)
local results = {} --- @type [integer, any, any[]][]
-- Iter shuold block in an async context so only one waiter is needed
local waiter = nil
local remaining = #tasks
for i, task in ipairs(tasks) do
task:await(function(err, result)
local callback = waiter
-- Clear waiter before calling it
waiter = nil
remaining = remaining - 1
if callback then
-- Iterator is waiting, yield to it
callback(i, err, result)
else
-- Task finished before Iterator was called. Store results.
table.insert(results, { i, err, result })
end
end)
end
--- @param callback fun(i?: integer, err?: any, result?: any)
return M.awrap(1, function(callback)
if next(results) then
local res = table.remove(results, 1)
callback(unpack(res, 1, table.maxn(res)))
elseif remaining == 0 then
callback() -- finish
else
assert(not waiter, 'internal error: waiter already set')
waiter = callback
end
end)
end
return M
lewis6991-gitsigns.nvim-7010000/lua/gitsigns/attach.lua 0000664 0000000 0000000 00000023673 14765572704 0022547 0 ustar 00root root 0000000 0000000 local Status = require('gitsigns.status')
local async = require('gitsigns.async')
local git = require('gitsigns.git')
local Cache = require('gitsigns.cache')
local log = require('gitsigns.debug.log')
local manager = require('gitsigns.manager')
local util = require('gitsigns.util')
local cache = Cache.cache
local config = require('gitsigns.config').config
local dprint = log.dprint
local dprintf = log.dprintf
local throttle_by_id = require('gitsigns.debounce').throttle_by_id
local api = vim.api
local uv = vim.loop
--- @class gitsigns.attach
local M = {}
--- @param name string
--- @return string? rel_path
--- @return string? commit
--- @return string? gitdir
local function parse_git_path(name)
if not vim.startswith(name, 'fugitive://') and not vim.startswith(name, 'gitsigns://') then
return
end
local proto, gitdir, tail = unpack(vim.split(name, '//'))
assert(proto and gitdir and tail)
local plugin = proto:sub(1, 1):upper() .. proto:sub(2, -2)
local commit, rel_path --- @type string?, string
if plugin == 'Gitsigns' then
commit = tail:match('^(:?[^:]+):')
rel_path = tail:match('^:?[^:]+:(.*)')
else -- Fugitive
commit = tail:match('^([^/]+)/')
if commit and commit:match('^[0-3]$') then
--- @diagnostic disable-next-line: no-unknown
commit = ':' .. commit
end
rel_path = tail:match('^[^/]+/(.*)')
end
dprintf("%s buffer for file '%s' from path '%s' on commit '%s'", plugin, rel_path, file, commit)
return rel_path, commit, gitdir
end
local function on_lines(_, bufnr, _, first, last_orig, last_new, byte_count)
if first == last_orig and last_orig == last_new and byte_count == 0 then
-- on_lines can be called twice for undo events; ignore the second
-- call which indicates no changes.
return
end
return manager.on_lines(bufnr, first, last_orig, last_new)
end
--- @param _ 'reload'
--- @param bufnr integer
local function on_reload(_, bufnr)
local __FUNC__ = 'on_reload'
assert(cache[bufnr]):invalidate()
dprint('Reload')
manager.update_debounced(bufnr)
end
--- @param _ 'detach'
--- @param bufnr integer
local function on_detach(_, bufnr)
api.nvim_clear_autocmds({ group = 'gitsigns', buffer = bufnr })
M.detach(bufnr, true)
end
--- @param bufnr integer
--- @return string?
--- @return string?
local function on_attach_pre(bufnr)
--- @type string?, string?
local gitdir, toplevel
if config._on_attach_pre then
--- @type {gitdir: string?, toplevel: string?}
local res = async.await(2, config._on_attach_pre, bufnr)
dprintf('ran on_attach_pre with result %s', vim.inspect(res))
if type(res) == 'table' then
if type(res.gitdir) == 'string' then
gitdir = res.gitdir
end
if type(res.toplevel) == 'string' then
toplevel = res.toplevel
end
end
end
return gitdir, toplevel
end
local setup = util.once(function()
manager.setup()
api.nvim_create_autocmd('OptionSet', {
group = 'gitsigns',
pattern = { 'fileformat', 'bomb', 'eol' },
callback = function()
local buf = vim.api.nvim_get_current_buf()
local bcache = cache[buf]
if not bcache then
return
end
bcache:invalidate(true)
async.arun(function()
manager.update(buf)
end)
end,
})
require('gitsigns.current_line_blame').setup()
api.nvim_create_autocmd('VimLeavePre', {
group = 'gitsigns',
callback = M.detach_all,
})
end)
--- @class Gitsigns.GitContext
--- @field file string
--- @field toplevel? string
--- @field gitdir? string
--- @field base? string
--- @param bufnr integer
--- @return Gitsigns.GitContext? ctx
--- @return string? err
local function get_buf_context(bufnr)
if api.nvim_buf_line_count(bufnr) > config.max_file_length then
return nil, 'Exceeds max_file_length'
end
local file = uv.fs_realpath(api.nvim_buf_get_name(bufnr))
or api.nvim_buf_call(bufnr, function()
return vim.fn.expand('%:p')
end)
local rel_path, commit, gitdir_from_bufname = parse_git_path(file)
if not gitdir_from_bufname then
if vim.bo[bufnr].buftype ~= '' then
return nil, 'Non-normal buffer'
end
local file_dir = util.dirname(file)
if not file_dir or not util.path_exists(file_dir) then
return nil, 'Not a path'
end
end
local gitdir_oap, toplevel_oap = on_attach_pre(bufnr)
return {
file = rel_path or file,
gitdir = gitdir_oap or gitdir_from_bufname,
toplevel = toplevel_oap,
-- Stage buffers always compare against the common ancestor (':1')
-- :0: index
-- :1: common ancestor
-- :2: target commit (HEAD)
-- :3: commit which is being merged
base = commit and (commit:match('^:[1-3]') and ':1' or commit) or nil,
}
end
--- @async
--- Ensure attaches cannot be interleaved for the same buffer.
--- Since attaches are asynchronous we need to make sure an attach isn't
--- performed whilst another one is in progress.
--- @param cbuf integer
--- @param ctx? Gitsigns.GitContext
--- @param aucmd? string
local attach_throttled = throttle_by_id(function(cbuf, ctx, aucmd)
local __FUNC__ = 'attach'
local passed_ctx = ctx ~= nil
setup()
if cache[cbuf] then
dprint('Already attached')
return
end
if aucmd then
dprintf('Attaching (trigger=%s)', aucmd)
else
dprint('Attaching')
end
if not api.nvim_buf_is_loaded(cbuf) then
dprint('Non-loaded buffer')
return
end
if not ctx then
local err
ctx, err = get_buf_context(cbuf)
if err then
dprint(err)
return
end
assert(ctx)
end
local encoding = vim.bo[cbuf].fileencoding
if encoding == '' then
encoding = 'utf-8'
end
local file = ctx.file
if not vim.startswith(file, '/') and ctx.toplevel then
file = ctx.toplevel .. util.path_sep .. file
end
local revision = ctx.base or config.base
local git_obj = git.Obj.new(file, revision, encoding, ctx.gitdir, ctx.toplevel)
if not git_obj and not passed_ctx then
for _, wt in ipairs(config.worktrees or {}) do
git_obj = git.Obj.new(file, revision, encoding, wt.gitdir, wt.toplevel)
if git_obj and git_obj.object_name then
dprintf('Using worktree %s', vim.inspect(wt))
break
end
end
end
if not git_obj then
dprint('Empty git obj')
return
end
async.schedule()
if not api.nvim_buf_is_valid(cbuf) then
return
end
Status:update(cbuf, {
head = git_obj.repo.abbrev_head,
root = git_obj.repo.toplevel,
gitdir = git_obj.repo.gitdir,
})
if not passed_ctx and (not util.path_exists(file) or uv.fs_stat(file).type == 'directory') then
dprint('Not a file')
return
end
if not git_obj.relpath then
dprint('Cannot resolve file in repo')
return
end
if not config.attach_to_untracked and git_obj.object_name == nil then
dprint('File is untracked')
return
end
-- On windows os.tmpname() crashes in callback threads so initialise this
-- variable on the main thread.
async.schedule()
if not api.nvim_buf_is_valid(cbuf) then
return
end
if config.on_attach and config.on_attach(cbuf) == false then
dprint('User on_attach() returned false')
return
end
cache[cbuf] = Cache.new({
bufnr = cbuf,
file = file,
git_obj = git_obj,
})
if config.watch_gitdir.enable then
local watcher = require('gitsigns.watcher')
cache[cbuf].gitdir_watcher = watcher.watch_gitdir(cbuf, git_obj.repo.gitdir)
end
if not api.nvim_buf_is_loaded(cbuf) then
dprint('Un-loaded buffer')
return
end
-- Make sure to attach before the first update (which is async) so we pick up
-- changes from BufReadCmd.
api.nvim_buf_attach(cbuf, false, {
on_lines = on_lines,
on_reload = on_reload,
on_detach = on_detach,
})
api.nvim_create_autocmd('BufWrite', {
group = 'gitsigns',
buffer = cbuf,
callback = function()
manager.update_debounced(cbuf)
end,
})
-- Initial update
manager.update(cbuf)
if config.current_line_blame then
require('gitsigns.current_line_blame').update(cbuf)
end
end)
--- Detach Gitsigns from all buffers it is attached to.
function M.detach_all()
for k, _ in pairs(cache) do
M.detach(k)
end
end
--- Detach Gitsigns from the buffer {bufnr}. If {bufnr} is not
--- provided then the current buffer is used.
---
--- @param bufnr integer Buffer number
--- @param _keep_signs? boolean
function M.detach(bufnr, _keep_signs)
-- When this is called interactively (with no arguments) we want to remove all
-- the signs, however if called via a detach event (due to nvim_buf_attach)
-- then we don't want to clear the signs in case the buffer is just being
-- updated due to the file externally changing. When this happens a detach and
-- attach event happen in sequence and so we keep the old signs to stop the
-- sign column width moving about between updates.
bufnr = bufnr or api.nvim_get_current_buf()
dprint('Detached')
local bcache = cache[bufnr]
if not bcache then
dprint('Cache was nil')
return
end
manager.detach(bufnr, _keep_signs)
-- Clear status variables
Status:clear(bufnr)
Cache.destroy(bufnr)
end
--- Attach Gitsigns to the buffer.
---
--- Attributes: ~
--- {async}
---
--- @param bufnr integer Buffer number
--- @param ctx Gitsigns.GitContext|nil
--- Git context data that may optionally be used to attach to any
--- buffer that represents a real git object.
--- โข {file}: (string)
--- Path to the file represented by the buffer, relative to the
--- top-level.
--- โข {toplevel}: (string?)
--- Path to the top-level of the parent git repository.
--- โข {gitdir}: (string?)
--- Path to the git directory of the parent git repository
--- (typically the ".git/" directory).
--- โข {commit}: (string?)
--- The git revision that the file belongs to.
--- โข {base}: (string?)
--- The git revision that the file should be compared to.
--- @param _trigger? string
M.attach = async.create(3, function(bufnr, ctx, _trigger)
attach_throttled(bufnr or api.nvim_get_current_buf(), ctx, _trigger)
end)
return M
lewis6991-gitsigns.nvim-7010000/lua/gitsigns/blame.lua 0000664 0000000 0000000 00000026720 14765572704 0022357 0 ustar 00root root 0000000 0000000 local async = require('gitsigns.async')
local cache = require('gitsigns.cache').cache
local log = require('gitsigns.debug.log')
local util = require('gitsigns.util')
local api = vim.api
local hash_colors = {} --- @type table
local ns = api.nvim_create_namespace('gitsigns_blame_win')
local ns_hl = api.nvim_create_namespace('gitsigns_blame_win_hl')
--- Convert a hex char to a rgb color component
---
--- Taken from vim-fugitive:
--- Avoid color components lower than 0x20 and higher than 0xdf to help
--- avoid colors that blend into the background, light or dark.
--- @param x string hex char
--- @return integer
local function mod(x)
local y = tonumber(x, 16)
return math.min(0xdf, 0x20 + math.floor((y * 0x10 + (15 - y)) * 0.75))
end
--- Taken from vim-fugitive
--- Use 3 characters of the commit hash, limiting the maximum total colors to
--- 4,096.
--- @param sha string
--- @return string
local function get_hash_color(sha)
local r, g, b = sha:match('(%x)%x(%x)%x(%x)')
local color = mod(r) * 0x10000 + mod(g) * 0x100 + mod(b)
if hash_colors[sha] then
return hash_colors[sha]
end
local hl_name = string.format('GitSignsBlameColor.%s%s%s', r, g, b)
api.nvim_set_hl(0, hl_name, { fg = color })
hash_colors[color] = hl_name
return hl_name
end
---@param amount integer
---@param text string
---@return string
local function lalign(amount, text)
local len = vim.str_utfindex(text)
return text .. string.rep(' ', math.max(0, amount - len))
end
local chars = {
first = 'โ',
mid = 'โ',
last = 'โ',
single = 'โบ',
}
local M = {}
--- @param blame table
--- @param win integer
--- @param main_win integer
--- @param buf_sha string
local function render(blame, win, main_win, buf_sha)
local max_author_len = 0
for _, blame_info in pairs(blame) do
max_author_len = math.max(max_author_len, (vim.str_utfindex(blame_info.commit.author)))
end
local lines = {} --- @type string[]
local last_sha --- @type string?
local cnt = 0
local commit_lines = {} --- @type table
for i, hl in pairs(blame) do
local sha = hl.commit.abbrev_sha
local next_sha = blame[i + 1] and blame[i + 1].commit.abbrev_sha or nil
if sha == last_sha then
cnt = cnt + 1
local c = sha == next_sha and chars.mid or chars.last
lines[i] = cnt == 1 and string.format('%s %s', c, hl.commit.summary) or c
else
cnt = 0
commit_lines[i] = true
lines[i] = string.format(
'%s %s %s %s',
chars.first,
sha,
lalign(max_author_len, hl.commit.author),
util.expand_format('', hl.commit)
)
end
last_sha = sha
end
local win_width = #lines[1]
api.nvim_win_set_width(win, win_width + 1)
local bufnr = api.nvim_win_get_buf(win)
local main_buf = api.nvim_win_get_buf(main_win)
api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
-- Apply highlights
for i, blame_info in ipairs(blame) do
local hash_color = get_hash_color(blame_info.commit.abbrev_sha)
api.nvim_buf_set_extmark(bufnr, ns, i - 1, 0, {
end_col = commit_lines[i] and 12 or 1,
hl_group = hash_color,
})
if commit_lines[i] then
local width = string.len(lines[i])
api.nvim_buf_set_extmark(bufnr, ns, i - 1, width - 10, {
end_col = width,
hl_group = 'Title',
})
else
api.nvim_buf_set_extmark(bufnr, ns, i - 1, 2, {
end_row = i,
end_col = 0,
hl_group = 'Comment',
})
end
if buf_sha == blame_info.commit.sha then
api.nvim_buf_set_extmark(bufnr, ns, i - 1, 0, {
line_hl_group = '@markup.italic',
})
end
if commit_lines[i] and commit_lines[i + 1] then
api.nvim_buf_set_extmark(bufnr, ns, i - 1, 0, {
virt_lines = {
{ { chars.last, hash_color }, { ' ' }, { blame_info.commit.summary, 'Comment' } },
},
})
local fillchar = string.rep(vim.opt.fillchars:get().diff or '-', 1000)
api.nvim_buf_set_extmark(main_buf, ns, i - 1, 0, {
virt_lines = { { { fillchar, 'Comment' } } },
virt_lines_leftcol = true,
})
end
end
end
--- @param blame table
--- @param win integer
--- @param revision? string
--- @param parent? boolean
local function reblame(blame, win, revision, parent)
local blm_win = api.nvim_get_current_win()
local lnum = unpack(api.nvim_win_get_cursor(blm_win))
local sha = blame[lnum].commit.sha
if parent then
sha = sha .. '^'
end
if sha == revision then
return
end
vim.cmd.quit()
api.nvim_set_current_win(win)
require('gitsigns').show(
sha,
vim.schedule_wrap(function()
local bufnr = api.nvim_get_current_buf()
local ok = vim.wait(1000, function()
return cache[bufnr] ~= nil
end)
if not ok then
error('Timeout waiting for attach')
end
async.arun(M.blame)
end)
)
end
--- @param win integer
--- @param open 'vsplit'|'tabnew'
--- @param bcache Gitsigns.CacheEntry
local show_commit = async.async(function(win, open, bcache)
local cursor = api.nvim_win_get_cursor(win)[1]
local sha = bcache.blame[cursor].commit.sha
local res = bcache.git_obj.repo:command({ 'show', sha })
async.schedule()
local buffer_name = bcache:get_rev_bufname(sha, true)
local commit_buf = nil
-- find preexisting commit buffer or create a new one
for _, bufnr in ipairs(api.nvim_list_bufs()) do
if api.nvim_buf_get_name(bufnr) == buffer_name then
commit_buf = bufnr
break
end
end
if commit_buf == nil then
commit_buf = api.nvim_create_buf(true, true)
api.nvim_buf_set_name(commit_buf, buffer_name)
api.nvim_buf_set_lines(commit_buf, 0, -1, false, res)
end
vim.cmd[open]({ mods = { keepalt = true } })
api.nvim_win_set_buf(0, commit_buf)
vim.bo[commit_buf].filetype = 'git'
vim.bo[commit_buf].bufhidden = 'wipe'
end)
--- @param augroup integer
--- @param wins integer[]
local function sync_cursors(augroup, wins)
local cursor_save --- @type integer?
---@param w integer
local function sync_cursor(w)
local b = api.nvim_win_get_buf(w)
api.nvim_create_autocmd('BufLeave', {
buffer = b,
group = augroup,
callback = function()
if api.nvim_win_is_valid(w) then
cursor_save = unpack(api.nvim_win_get_cursor(w))
end
end,
})
api.nvim_create_autocmd('BufEnter', {
group = augroup,
buffer = b,
callback = function()
if not api.nvim_win_is_valid(w) then
return
end
local cur_cursor, cur_cursor_col = unpack(api.nvim_win_get_cursor(w))
if cursor_save and cursor_save ~= cur_cursor then
api.nvim_win_set_cursor(w, { cursor_save, vim.o.startofline and 0 or cur_cursor_col })
end
end,
})
end
for _, w in ipairs(wins) do
sync_cursor(w)
end
end
--- @param name string
--- @param items [string, string][]
local function menu(name, items)
local max_len = 0
for _, item in ipairs(items) do
max_len = math.max(max_len, #item[1]) --- @type integer
end
for _, item in ipairs(items) do
local item_nm, action = item[1], item[2]
local pad = string.rep(' ', max_len - #item_nm)
local lhs = string.format('%s%s (%s)', item_nm, pad, action):gsub(' ', [[\ ]])
local cmd = string.format('nmenu ]%s.%s %s', name, lhs, action)
vim.cmd(cmd)
end
end
--- @param mode string
--- @param lhs string
--- @param cb fun()
--- @param opts vim.keymap.set.Opts
local function pmap(mode, lhs, cb, opts)
opts.expr = true
vim.keymap.set(mode, lhs, function()
vim.schedule(function()
cb()
end)
if vim.fn.pumvisible() == 0 then
return ''
else
return ''
end
end, opts)
end
--- @async
function M.blame()
local __FUNC__ = 'blame'
local bufnr = api.nvim_get_current_buf()
local win = api.nvim_get_current_win()
local bcache = cache[bufnr]
if not bcache then
log.dprint('Not attached')
return
end
bcache:get_blame()
local blame = assert(bcache.blame)
-- Save position to align 'scrollbind'
local top = vim.fn.line('w0') + vim.wo.scrolloff
local current = vim.fn.line('.')
vim.cmd.vsplit({ mods = { keepalt = true, split = 'aboveleft' } })
local blm_win = api.nvim_get_current_win()
local blm_bufnr = api.nvim_create_buf(false, true)
api.nvim_win_set_buf(blm_win, blm_bufnr)
render(blame, blm_win, win, bcache.git_obj.revision)
local blm_bo = vim.bo[blm_bufnr]
blm_bo.buftype = 'nofile'
blm_bo.bufhidden = 'wipe'
blm_bo.modifiable = false
blm_bo.filetype = 'gitsigns-blame'
local blm_wlo = vim.wo[blm_win][0]
blm_wlo.foldcolumn = '0'
blm_wlo.foldenable = false
blm_wlo.number = false
blm_wlo.relativenumber = false
blm_wlo.scrollbind = true
blm_wlo.signcolumn = 'no'
blm_wlo.spell = false
blm_wlo.winfixwidth = true
blm_wlo.wrap = false
if vim.wo[win].winbar ~= '' and blm_wlo.winbar == '' then
local name = api.nvim_buf_get_name(bufnr)
blm_wlo.winbar = vim.fn.fnamemodify(name, ':.')
end
if vim.fn.exists('&winfixbuf') then
blm_wlo.winfixbuf = true
end
vim.cmd(tostring(top))
vim.cmd('normal! zt')
vim.cmd(tostring(current))
vim.cmd('normal! 0')
local cur_wlo = vim.wo[win][0]
local cur_orig_wlo = { cur_wlo.foldenable, cur_wlo.scrollbind, cur_wlo.wrap }
cur_wlo.foldenable = false
cur_wlo.scrollbind = true
cur_wlo.wrap = false
vim.cmd.redraw()
vim.cmd.syncbind()
vim.keymap.set('n', '', function()
vim.cmd.popup(']GitsignsBlame')
end, {
desc = 'Open blame context menu',
buffer = blm_bufnr,
})
pmap('n', 'r', function()
reblame(blame, win, bcache.git_obj.revision)
end, {
desc = 'Reblame at commit',
buffer = blm_bufnr,
})
pmap('n', 'R', function()
reblame(blame, win, bcache.git_obj.revision, true)
end, {
desc = 'Reblame at commit parent',
buffer = blm_bufnr,
})
pmap('n', 's', function()
show_commit(blm_win, 'vsplit', bcache)
end, {
desc = 'Show commit in a vertical split',
buffer = blm_bufnr,
})
pmap('n', 'S', function()
show_commit(blm_win, 'tabnew', bcache)
end, {
desc = 'Show commit in a new tab',
buffer = blm_bufnr,
})
menu('GitsignsBlame', {
{ 'Reblame at commit', 'r' },
{ 'Reblame at commit parent', 'R' },
{ 'Show commit (vsplit)', 's' },
{ ' (tab)', 'S' },
})
local group = api.nvim_create_augroup('GitsignsBlame', {})
api.nvim_create_autocmd({ 'CursorMoved', 'BufLeave' }, {
buffer = blm_bufnr,
group = group,
callback = function()
api.nvim_buf_clear_namespace(blm_bufnr, ns_hl, 0, -1)
api.nvim_buf_clear_namespace(bufnr, ns_hl, 0, -1)
end,
})
-- Highlight the same commit under the cursor
api.nvim_create_autocmd('CursorMoved', {
buffer = blm_bufnr,
group = group,
callback = function()
local cursor = unpack(api.nvim_win_get_cursor(blm_win))
local cur_sha = blame[cursor].commit.abbrev_sha
for i, info in pairs(blame) do
if info.commit.abbrev_sha == cur_sha then
api.nvim_buf_set_extmark(blm_bufnr, ns_hl, i - 1, 0, {
line_hl_group = '@markup.strong',
})
end
end
end,
})
api.nvim_create_autocmd('WinClosed', {
pattern = tostring(blm_win),
group = group,
callback = function()
api.nvim_buf_clear_namespace(bufnr, ns, 0, -1)
if api.nvim_win_is_valid(win) then
cur_wlo.foldenable, cur_wlo.scrollbind, cur_wlo.wrap = unpack(cur_orig_wlo)
end
end,
})
sync_cursors(group, { win, blm_win })
end
return M
lewis6991-gitsigns.nvim-7010000/lua/gitsigns/cache.lua 0000664 0000000 0000000 00000017641 14765572704 0022344 0 ustar 00root root 0000000 0000000 local async = require('gitsigns.async')
local config = require('gitsigns.config').config
local log = require('gitsigns.debug.log')
local util = require('gitsigns.util')
local api = vim.api
local M = {
CacheEntry = {},
}
--- @class (exact) Gitsigns.CacheEntry
--- @field bufnr integer
--- @field file string
--- @field compare_text? string[]
--- @field hunks? Gitsigns.Hunk.Hunk[]
--- @field force_next_update? boolean
--- @field file_mode? boolean
---
--- @field compare_text_head? string[]
--- @field hunks_staged? Gitsigns.Hunk.Hunk[]
---
--- @field staged_diffs? Gitsigns.Hunk.Hunk[]
--- @field gitdir_watcher? uv.uv_fs_event_t
--- @field git_obj Gitsigns.GitObj
--- @field blame? table
---
--- @field update_lock? true Update in progress
local CacheEntry = M.CacheEntry
function CacheEntry:get_rev_bufname(rev, nofile)
rev = rev or self.git_obj.revision or ':0'
if nofile then
return string.format('gitsigns://%s//%s', self.git_obj.repo.gitdir, rev)
end
return string.format('gitsigns://%s//%s:%s', self.git_obj.repo.gitdir, rev, self.git_obj.relpath)
end
function CacheEntry:locked()
return self.git_obj.lock or self.update_lock or false
end
--- Invalidate any state dependent on the buffer content.
--- If 'all' is passed, then invalidate everything.
--- @param all? boolean
function CacheEntry:invalidate(all)
self.hunks = nil
self.hunks_staged = nil
self.blame = nil
if all then
-- The below doesn't need to be invalidated
-- if the buffer changes
self.compare_text = nil
self.compare_text_head = nil
end
end
--- @param o Gitsigns.CacheEntry
--- @return Gitsigns.CacheEntry
function M.new(o)
o.staged_diffs = o.staged_diffs or {}
return setmetatable(o, { __index = CacheEntry })
end
local sleep = async.awrap(2, function(duration, cb)
vim.defer_fn(cb, duration)
end)
--- @async
--- @private
function CacheEntry:wait_for_hunks()
local loop_protect = 0
while not self.hunks and loop_protect < 10 do
loop_protect = loop_protect + 1
sleep(100)
end
end
-- If a file contains has up to this amount of lines, then
-- always blame the whole file, otherwise only blame one line
-- at a time.
local BLAME_THRESHOLD_LEN = 10000
--- @async
--- @private
--- @param lnum? integer
--- @param opts? Gitsigns.BlameOpts
--- @return table
--- @return boolean? full
function CacheEntry:run_blame(lnum, opts)
local bufnr = self.bufnr
-- Always send contents if buffer represents an editable file on disk.
-- Otherwise do not sent contents buffer revision is from tree and git version
-- is below 2.41.
--
-- This avoids the error:
-- "fatal: cannot use --contents with final commit object name"
local send_contents = vim.bo[bufnr].buftype == ''
or (not self.git_obj:from_tree() and not require('gitsigns.git.version').check(2, 41))
while true do
local contents = send_contents and util.buf_lines(bufnr) or nil
local tick = vim.b[bufnr].changedtick
local lnum0 = api.nvim_buf_line_count(bufnr) > BLAME_THRESHOLD_LEN and lnum or nil
-- TODO(lewis6991): Cancel blame on changedtick
local blame = self.git_obj:run_blame(contents, lnum0, self.git_obj.revision, opts)
async.schedule()
if not api.nvim_buf_is_valid(bufnr) then
return {}
end
if vim.b[bufnr].changedtick == tick then
return blame, lnum0 == nil
end
end
end
--- @private
--- @param lnum? integer
--- @return boolean
function CacheEntry:blame_valid(lnum)
local blame = self.blame
if not blame then
return false
end
if lnum then
return blame[lnum] ~= nil
end
-- Need to check we have blame info for all lines
for i = 1, api.nvim_buf_line_count(self.bufnr) do
if not blame[i] then
return false
end
end
return true
end
--- If lnum is nil then run blame for the entire buffer.
--- @async
--- @param lnum? integer
--- @param opts? Gitsigns.BlameOpts
--- @return Gitsigns.BlameInfo?
function CacheEntry:get_blame(lnum, opts)
local blame = self.blame
if not blame or not self:blame_valid(lnum) then
self:wait_for_hunks()
blame = blame or {}
local Hunks = require('gitsigns.hunks')
if lnum and Hunks.find_hunk(lnum, self.hunks) then
--- Bypass running blame (which can be expensive) if we know lnum is in a hunk
local Blame = require('gitsigns.git.blame')
local relpath = assert(self.git_obj.relpath)
blame[lnum] = Blame.get_blame_nc(relpath, lnum)
else
-- Refresh/update cache
local b, full = self:run_blame(lnum, opts)
if lnum and not full then
blame[lnum] = b[lnum]
else
blame = b
end
end
self.blame = blame
end
return blame[lnum]
end
--- @async
--- @nodiscard
--- @param check_compare_text? boolean
--- @return boolean
function CacheEntry:schedule(check_compare_text)
async.schedule()
local bufnr = self.bufnr
if not api.nvim_buf_is_valid(bufnr) then
log.dprint('Buffer not valid, aborting')
return false
end
if not M.cache[bufnr] then
log.dprint('Has detached, aborting')
return false
end
if check_compare_text and not M.cache[bufnr].compare_text then
log.dprint('compare_text was invalid, aborting')
return false
end
return true
end
--- @async
function CacheEntry:get_hunks(greedy, staged)
if greedy and config.diff_opts.linematch then
-- Re-run the diff without linematch
local buftext = util.buf_lines(self.bufnr)
local text --- @type string[]?
if staged then
text = self.compare_text_head
else
text = self.compare_text
end
if not text then
return
end
local run_diff = require('gitsigns.diff')
local hunks = run_diff(text, buftext, false)
if not self:schedule() then
return
end
return hunks
end
if staged then
return vim.deepcopy(self.hunks_staged)
end
return vim.deepcopy(self.hunks)
end
--- @param hunks? Gitsigns.Hunk.Hunk[]?
--- @return Gitsigns.Hunk.Hunk? hunk
--- @return integer? index
function CacheEntry:get_cursor_hunk(hunks)
if not hunks then
hunks = {}
vim.list_extend(hunks, self.hunks or {})
vim.list_extend(hunks, self.hunks_staged or {})
end
local lnum = api.nvim_win_get_cursor(0)[1]
local Hunks = require('gitsigns.hunks')
return Hunks.find_hunk(lnum, hunks)
end
--- @async
--- @param range? [integer,integer]
--- @param greedy? boolean
--- @param staged? boolean
--- @return Gitsigns.Hunk.Hunk?
function CacheEntry:get_hunk(range, greedy, staged)
local Hunks = require('gitsigns.hunks')
local hunks = self:get_hunks(greedy, staged)
if not range then
return self:get_cursor_hunk(hunks)
end
table.sort(range)
local top, bot = range[1], range[2]
local hunk = Hunks.create_partial_hunk(hunks or {}, top, bot)
if not hunk then
return
end
if staged then
local staged_top, staged_bot = top, bot
for _, h in ipairs(assert(self.hunks)) do
if top > h.vend then
staged_top = staged_top - (h.added.count - h.removed.count)
end
if bot > h.vend then
staged_bot = staged_bot - (h.added.count - h.removed.count)
end
end
hunk.added.lines = vim.list_slice(self.compare_text, staged_top, staged_bot)
hunk.removed.lines = vim.list_slice(
self.compare_text_head,
hunk.removed.start,
hunk.removed.start + hunk.removed.count - 1
)
else
hunk.added.lines = api.nvim_buf_get_lines(self.bufnr, top - 1, bot, false)
hunk.removed.lines = vim.list_slice(
self.compare_text,
hunk.removed.start,
hunk.removed.start + hunk.removed.count - 1
)
end
return hunk
end
function CacheEntry:destroy()
local w = self.gitdir_watcher
if w and not w:is_closing() then
w:close()
end
self.git_obj.repo:unref()
end
---@type table
M.cache = {}
--- @param bufnr integer
function M.destroy(bufnr)
assert(M.cache[bufnr]):destroy()
M.cache[bufnr] = nil
end
return M
lewis6991-gitsigns.nvim-7010000/lua/gitsigns/cli.lua 0000664 0000000 0000000 00000005100 14765572704 0022033 0 ustar 00root root 0000000 0000000 local actions = require('gitsigns.actions')
local argparse = require('gitsigns.cli.argparse')
local async = require('gitsigns.async')
local attach = require('gitsigns.attach')
local Debug = require('gitsigns.debug')
local log = require('gitsigns.debug.log')
local message = require('gitsigns.message')
--- @type table,boolean>
local sources = {
[actions] = true,
[attach] = false,
[Debug] = false,
}
-- try to parse each argument as a lua boolean, nil or number, if fails then
-- keep argument as a string:
--
-- 'false' -> false
-- 'nil' -> nil
-- '100' -> 100
-- 'HEAD~300' -> 'HEAD~300'
local function parse_to_lua(a)
if tonumber(a) then
return tonumber(a)
elseif a == 'false' or a == 'true' then
return a == 'true'
elseif a == 'nil' then
return nil
end
return a
end
local M = {}
function M.complete(arglead, line)
local words = vim.split(line, '%s+')
local n = #words
local matches = {}
if n == 2 then
for m, _ in pairs(sources) do
for func, _ in pairs(m) do
if not func:match('^[a-z]') then
-- exclude
elseif vim.startswith(func, arglead) then
table.insert(matches, func)
end
end
end
elseif n > 2 then
-- Subcommand completion
local cmp_func = actions._get_cmp_func(words[2])
if cmp_func then
return cmp_func(arglead)
end
end
return matches
end
M.run = async.create(1, function(params)
local __FUNC__ = 'cli.run'
local pos_args_raw, named_args_raw = argparse.parse_args(params.args)
local func = pos_args_raw[1]
if not func then
func = async.await(3, vim.ui.select, M.complete('', 'Gitsigns '), {}) --[[@as string]]
if not func then
return
end
end
local pos_args = vim.tbl_map(parse_to_lua, vim.list_slice(pos_args_raw, 2))
local named_args = vim.tbl_map(parse_to_lua, named_args_raw)
local args = vim.tbl_extend('error', pos_args, named_args)
log.dprintf(
"Running action '%s' with arguments %s",
func,
vim.inspect(args, { newline = ' ', indent = '' })
)
local cmd_func = actions._get_cmd_func(func)
if cmd_func then
-- Action has a specialised mapping function from command form to lua
-- function
cmd_func(args, params)
return
end
for m, has_named in pairs(sources) do
local f = m[func]
if type(f) == 'function' then
-- Note functions here do not have named arguments
f(unpack(pos_args), has_named and named_args or nil)
return
end
end
message.error('%s is not a valid function or action', func)
end)
return M
lewis6991-gitsigns.nvim-7010000/lua/gitsigns/cli/ 0000775 0000000 0000000 00000000000 14765572704 0021334 5 ustar 00root root 0000000 0000000 lewis6991-gitsigns.nvim-7010000/lua/gitsigns/cli/argparse.lua 0000664 0000000 0000000 00000005112 14765572704 0023642 0 ustar 00root root 0000000 0000000 local M = {}
local function is_char(x)
return x:match('[^=\'"%s]') ~= nil
end
-- Return positional arguments and named arguments
--- @param x string
--- @return string[], table
function M.parse_args(x)
--- @type string[], table
local pos_args, named_args = {}, {}
local state = 'in_arg'
local cur_arg = ''
local cur_val = ''
local cur_quote = ''
local function peek(idx)
return x:sub(idx + 1, idx + 1)
end
local i = 1
while i <= #x do
local ch = x:sub(i, i)
-- dprintf('L(%d)(%s): cur_arg="%s" ch="%s"', i, state, cur_arg, ch)
if state == 'in_arg' then
if is_char(ch) then
if ch == '-' and peek(i) == '-' then
state = 'in_flag'
cur_arg = ''
i = i + 1
else
cur_arg = cur_arg .. ch
end
elseif ch:match('%s') then
pos_args[#pos_args + 1] = cur_arg
state = 'in_ws'
elseif ch == '=' then
cur_val = ''
local next_ch = peek(i)
if next_ch == "'" or next_ch == '"' then
cur_quote = next_ch
i = i + 1
state = 'in_quote'
else
state = 'in_value'
end
end
elseif state == 'in_flag' then
if ch:match('%s') then
named_args[cur_arg] = true
state = 'in_ws'
else
cur_arg = cur_arg .. ch
end
elseif state == 'in_ws' then
if is_char(ch) then
if ch == '-' and peek(i) == '-' then
state = 'in_flag'
cur_arg = ''
i = i + 1
else
state = 'in_arg'
cur_arg = ch
end
end
elseif state == 'in_value' then
if is_char(ch) then
cur_val = cur_val .. ch
elseif ch:match('%s') then
named_args[cur_arg] = cur_val
cur_arg = ''
state = 'in_ws'
end
elseif state == 'in_quote' then
local next_ch = peek(i)
if ch == '\\' and next_ch == cur_quote then
cur_val = cur_val .. next_ch
i = i + 1
elseif ch == cur_quote then
named_args[cur_arg] = cur_val
state = 'in_ws'
if next_ch ~= '' and not next_ch:match('%s') then
error('malformed argument: ' .. next_ch)
end
else
cur_val = cur_val .. ch
end
end
i = i + 1
end
if #cur_arg > 0 then
if state == 'in_arg' then
pos_args[#pos_args + 1] = cur_arg
elseif state == 'in_flag' then
named_args[cur_arg] = true
elseif state == 'in_value' then
named_args[cur_arg] = cur_val
end
end
return pos_args, named_args
end
return M
lewis6991-gitsigns.nvim-7010000/lua/gitsigns/config.lua 0000664 0000000 0000000 00000066113 14765572704 0022544 0 ustar 00root root 0000000 0000000 --- @class (exact) Gitsigns.SchemaElem
--- @field type string|string[]|fun(x:any): boolean
--- @field type_help? string
--- @field refresh? fun(cb: fun()) Function to refresh the config value
--- @field deep_extend? boolean
--- @field default any
--- @field deprecated? boolean
--- @field default_help? string
--- @field description string
--- @class (exact) Gitsigns.DiffOpts
--- @field algorithm string
--- @field internal boolean
--- @field indent_heuristic boolean
--- @field vertical boolean
--- @field linematch? integer
--- @field ignore_whitespace_change? true
--- @field ignore_whitespace? true
--- @field ignore_whitespace_change_at_eol? true
--- @field ignore_blank_lines? true
--- @class (exact) Gitsigns.SignConfig
--- @field show_count boolean
--- @field hl string
--- @field text string
--- @field numhl string
--- @field linehl string
--- @field culhl string
--- @alias Gitsigns.SignType
--- | 'add'
--- | 'change'
--- | 'delete'
--- | 'topdelete'
--- | 'changedelete'
--- | 'untracked'
--- @alias Gitsigns.CurrentLineBlameFmtFun fun(user: string, info: table): [string,string][]
--- @class (exact) Gitsigns.CurrentLineBlameOpts : Gitsigns.BlameOpts
--- @field virt_text? boolean
--- @field virt_text_pos? 'eol'|'overlay'|'right_align'
--- @field delay? integer
--- @field virt_text_priority? integer
--- @field use_focus? boolean
--- @class (exact) Gitsigns.BlameOpts
--- @field ignore_whitespace? boolean
--- @field extra_opts? string[]
--- @class (exact) Gitsigns.LineBlameOpts : Gitsigns.BlameOpts
--- @field full? boolean
--- @class (exact) Gitsigns.Config
--- @field debug_mode boolean
--- @field diff_opts Gitsigns.DiffOpts
--- @field base? string
--- @field signs table
--- @field signs_staged table
--- @field signs_staged_enable boolean
--- @field count_chars table
--- @field signcolumn boolean
--- @field numhl boolean
--- @field linehl boolean
--- @field culhl boolean
--- @field show_deleted boolean
--- @field sign_priority integer
--- @field _on_attach_pre fun(bufnr: integer, callback: fun(_: table))
--- @field on_attach fun(bufnr: integer)
--- @field watch_gitdir { enable: boolean, follow_files: boolean }
--- @field max_file_length integer
--- @field update_debounce integer
--- @field status_formatter fun(_: table): string
--- @field current_line_blame boolean
--- @field current_line_blame_formatter string|Gitsigns.CurrentLineBlameFmtFun
--- @field current_line_blame_formatter_nc string|Gitsigns.CurrentLineBlameFmtFun
--- @field current_line_blame_opts Gitsigns.CurrentLineBlameOpts
--- @field preview_config table
--- @field auto_attach boolean
--- @field attach_to_untracked boolean
--- @field worktrees {toplevel: string, gitdir: string}[]
--- @field word_diff boolean
--- @field trouble boolean
--- -- Undocumented
--- @field _refresh_staged_on_update boolean
--- @field _threaded_diff boolean
--- @field _git_version string
--- @field _verbose boolean
--- @field _test_mode boolean
--- @field _new_sign_calc boolean
local M = {
Config = {
DiffOpts = {},
SignConfig = {},
watch_gitdir = {},
current_line_blame_opts = {},
Worktree = {},
},
}
--- @param v Gitsigns.SchemaElem
--- @return any
local function resolve_default(v)
if type(v.default) == 'function' and v.type ~= 'function' then
return v.default()
else
return v.default
end
end
--- @return Gitsigns.DiffOpts
local function parse_diffopt()
--- @type Gitsigns.DiffOpts
local r = {
algorithm = 'myers',
internal = false,
indent_heuristic = false,
vertical = true,
}
local optmap = {
['indent-heuristic'] = 'indent_heuristic',
internal = 'internal',
iwhite = 'ignore_whitespace_change',
iblank = 'ignore_blank_lines',
iwhiteeol = 'ignore_whitespace_change_at_eol',
iwhiteall = 'ignore_whitespace',
}
local diffopt = vim.opt.diffopt:get() --[[@as string[] ]]
for _, o in ipairs(diffopt) do
if optmap[o] then
r[optmap[o]] = true
elseif o == 'horizontal' then
r.vertical = false
elseif vim.startswith(o, 'algorithm:') then
r.algorithm = string.sub(o, ('algorithm:'):len() + 1)
elseif vim.startswith(o, 'linematch:') then
r.linematch = tonumber(string.sub(o, ('linematch:'):len() + 1))
end
end
return r
end
--- @type Gitsigns.Config
M.config = setmetatable({}, {
__index = function(t, k)
if rawget(t, k) == nil then
local field = M.schema[k]
if not field then
return
end
rawset(t, k, resolve_default(field))
if field.refresh then
field.refresh(function()
rawset(t, k, resolve_default(field))
end)
end
end
return rawget(t, k)
end,
})
local function warn(s, ...)
vim.notify_once(s:format(...), vim.log.levels.WARN, { title = 'gitsigns' })
end
--- @param x Gitsigns.SignConfig
--- @return boolean
local function validate_signs(x)
if type(x) ~= 'table' then
return false
end
local warnings --- @type table?
--- @diagnostic disable-next-line:no-unknown
for kind, s in pairs(M.schema.signs.default) do
--- @diagnostic disable-next-line:no-unknown
for ty, v in pairs(s) do
if x[kind] and x[kind][ty] and vim.endswith(ty, 'hl') then
warnings = warnings or {}
local w = string.format(
"'signs.%s.%s' is now deprecated, please define highlight '%s' e.g:\n"
.. " vim.api.nvim_set_hl(0, '%s', { link = '%s' })",
kind,
ty,
v,
v,
x[kind][ty]
)
warnings[w] = true
end
end
end
if warnings then
for w in vim.spairs(warnings) do
warn(w)
end
end
return true
end
--- @type table
M.schema = {
signs = {
type_help = 'table',
type = validate_signs,
deep_extend = true,
default = {
add = {
hl = 'GitSignsAdd',
text = 'โ',
numhl = 'GitSignsAddNr',
linehl = 'GitSignsAddLn',
culhl = 'GitSignsAddCul',
},
change = {
hl = 'GitSignsChange',
text = 'โ',
numhl = 'GitSignsChangeNr',
linehl = 'GitSignsChangeLn',
culhl = 'GitSignsChangeCul',
},
delete = {
hl = 'GitSignsDelete',
text = 'โ',
numhl = 'GitSignsDeleteNr',
linehl = 'GitSignsDeleteLn',
culhl = 'GitSignsDeleteCul',
},
topdelete = {
hl = 'GitSignsTopdelete',
text = 'โ',
numhl = 'GitSignsTopdeleteNr',
linehl = 'GitSignsTopdeleteLn',
culhl = 'GitSignsTopdeleteCul',
},
changedelete = {
hl = 'GitSignsChangedelete',
text = '~',
numhl = 'GitSignsChangedeleteNr',
linehl = 'GitSignsChangedeleteLn',
culhl = 'GitSignsChangedeleteCul',
},
untracked = {
hl = 'GitSignsUntracked',
text = 'โ',
numhl = 'GitSignsUntrackedNr',
linehl = 'GitSignsUntrackedLn',
culhl = 'GitSignsUntrackedCul',
},
},
default_help = [[{
add = { text = 'โ' },
change = { text = 'โ' },
delete = { text = 'โ' },
topdelete = { text = 'โ' },
changedelete = { text = '~' },
untracked = { text = 'โ' },
}]],
description = [[
Configuration for signs:
โข `text` specifies the character to use for the sign.
โข `show_count` to enable showing count of hunk, e.g. number of deleted
lines.
The highlights `GitSigns[kind][type]` is used for each kind of sign. E.g.
'add' signs uses the highlights:
โข `GitSignsAdd` (for normal text signs)
โข `GitSignsAddNr` (for signs when `config.numhl == true`)
โข `GitSignsAddLn `(for signs when `config.linehl == true`)
โข `GitSignsAddCul `(for signs when `config.culhl == true`)
See |gitsigns-highlight-groups|.
]],
},
signs_staged = {
type = 'table',
deep_extend = true,
default = {
add = {
hl = 'GitSignsStagedAdd',
text = 'โ',
numhl = 'GitSignsStagedAddNr',
linehl = 'GitSignsStagedAddLn',
culhl = 'GitSignsStagedAddCul',
},
change = {
hl = 'GitSignsStagedChange',
text = 'โ',
numhl = 'GitSignsStagedChangeNr',
linehl = 'GitSignsStagedChangeLn',
culhl = 'GitSignsStagedChangeCul',
},
delete = {
hl = 'GitSignsStagedDelete',
text = 'โ',
numhl = 'GitSignsStagedDeleteNr',
linehl = 'GitSignsStagedDeleteLn',
culhl = 'GitSignsStagedDeleteCul',
},
topdelete = {
hl = 'GitSignsStagedTopdelete',
text = 'โ',
numhl = 'GitSignsStagedTopdeleteNr',
linehl = 'GitSignsStagedTopdeleteLn',
culhl = 'GitSignsStagedTopdeleteCul',
},
changedelete = {
hl = 'GitSignsStagedChangedelete',
text = '~',
numhl = 'GitSignsStagedChangedeleteNr',
linehl = 'GitSignsStagedChangedeleteLn',
culhl = 'GitSignsStagedChangedeleteCul',
},
},
default_help = [[{
add = { text = 'โ' },
change = { text = 'โ' },
delete = { text = 'โ' },
topdelete = { text = 'โ' },
changedelete = { text = '~' },
}]],
description = [[
Configuration for signs of staged hunks.
See |gitsigns-config-signs|.
]],
},
signs_staged_enable = {
type = 'boolean',
default = true,
description = [[
Show signs for staged hunks.
When enabled the signs defined in |git-config-signs_staged|` are used.
]],
},
worktrees = {
type = 'table',
default = nil,
description = [[
Detached working trees.
Array of tables with the keys `gitdir` and `toplevel`.
If normal attaching fails, then each entry in the table is attempted
with the work tree details set.
Example: >lua
worktrees = {
{
toplevel = vim.env.HOME,
gitdir = vim.env.HOME .. '/projects/dotfiles/.git'
}
}
]],
},
_on_attach_pre = {
type = 'function',
default = nil,
description = [[
Asynchronous hook called before attaching to a buffer. Mainly used to
configure detached worktrees.
This callback must call its callback argument. The callback argument can
accept an optional table argument with the keys: 'gitdir' and 'toplevel'.
Example: >lua
on_attach_pre = function(bufnr, callback)
...
callback {
gitdir = ...,
toplevel = ...
}
end
<
]],
},
on_attach = {
type = 'function',
default = nil,
description = [[
Callback called when attaching to a buffer. Mainly used to setup keymaps.
The buffer number is passed as the first argument.
This callback can return `false` to prevent attaching to the buffer.
Example: >lua
on_attach = function(bufnr)
if vim.api.nvim_buf_get_name(bufnr):match() then
-- Don't attach to specific buffers whose name matches a pattern
return false
end
-- Setup keymaps
vim.api.nvim_buf_set_keymap(bufnr, 'n', 'hs', 'lua require"gitsigns".stage_hunk()', {})
... -- More keymaps
end
<
]],
},
watch_gitdir = {
type = 'table',
deep_extend = true,
default = {
enable = true,
follow_files = true,
},
description = [[
When opening a file, a libuv watcher is placed on the respective
`.git` directory to detect when changes happen to use as a trigger to
update signs.
Fields: ~
โข `enable`:
Whether the watcher is enabled.
โข `follow_files`:
If a file is moved with `git mv`, switch the buffer to the new location.
]],
},
sign_priority = {
type = 'number',
default = 6,
description = [[
Priority to use for signs.
]],
},
signcolumn = {
type = 'boolean',
default = true,
description = [[
Enable/disable symbols in the sign column.
When enabled the highlights defined in `signs.*.hl` and symbols defined
in `signs.*.text` are used.
]],
},
numhl = {
type = 'boolean',
default = false,
description = [[
Enable/disable line number highlights.
When enabled the highlights defined in `signs.*.numhl` are used. If
the highlight group does not exist, then it is automatically defined
and linked to the corresponding highlight group in `signs.*.hl`.
]],
},
linehl = {
type = 'boolean',
default = false,
description = [[
Enable/disable line highlights.
When enabled the highlights defined in `signs.*.linehl` are used. If
the highlight group does not exist, then it is automatically defined
and linked to the corresponding highlight group in `signs.*.hl`.
]],
},
culhl = {
type = 'boolean',
default = false,
description = [[
Enable/disable highlights for the sign column when the cursor is on
the same line.
When enabled the highlights defined in `signs.*.culhl` are used. If
the highlight group does not exist, then it is automatically defined
and linked to the corresponding highlight group in `signs.*.hl`.
]],
},
show_deleted = {
type = 'boolean',
deprecated = true,
default = false,
description = [[
Show the old version of hunks inline in the buffer (via virtual lines).
Note: Virtual lines currently use the highlight `GitSignsDeleteVirtLn`.
]],
},
diff_opts = {
type = 'table',
deep_extend = true,
refresh = function(callback)
vim.api.nvim_create_autocmd('OptionSet', {
group = vim.api.nvim_create_augroup('gitsigns.config.diff_opts', {}),
pattern = 'diffopt',
callback = callback,
})
end,
default = parse_diffopt,
default_help = "derived from 'diffopt'",
description = [[
Diff options. If the default value is used, then changes to 'diffopt' are
automatically applied.
Fields: ~
โข algorithm: string
Diff algorithm to use. Values:
โข "myers" the default algorithm
โข "minimal" spend extra time to generate the
smallest possible diff
โข "patience" patience diff algorithm
โข "histogram" histogram diff algorithm
โข internal: boolean
Use Neovim's built in xdiff library for running diffs.
โข indent_heuristic: boolean
Use the indent heuristic for the internal
diff library.
โข vertical: boolean
Start diff mode with vertical splits.
โข linematch: integer
Enable second-stage diff on hunks to align lines.
Requires `internal=true`.
โข ignore_blank_lines: boolean
Ignore changes where lines are blank.
โข ignore_whitespace_change: boolean
Ignore changes in amount of white space.
It should ignore adding trailing white space,
but not leading white space.
โข ignore_whitespace: boolean
Ignore all white space changes.
โข ignore_whitespace_change_at_eol: boolean
Ignore white space changes at end of line.
]],
},
base = {
type = 'string',
default = nil,
default_help = 'index',
description = [[
The object/revision to diff against.
See |gitsigns-revision|.
]],
},
count_chars = {
type = 'table',
default = {
[1] = '1', -- 'โ',
[2] = '2', -- 'โ',
[3] = '3', -- 'โ',
[4] = '4', -- 'โ',
[5] = '5', -- 'โ
',
[6] = '6', -- 'โ',
[7] = '7', -- 'โ',
[8] = '8', -- 'โ',
[9] = '9', -- 'โ',
['+'] = '>', -- 'โ',
},
description = [[
The count characters used when `signs.*.show_count` is enabled. The
`+` entry is used as a fallback. With the default, any count outside
of 1-9 uses the `>` character in the sign.
Possible use cases for this field:
โข to specify unicode characters for the counts instead of 1-9.
โข to define characters to be used for counts greater than 9.
]],
},
status_formatter = {
type = 'function',
--- @param status Gitsigns.StatusObj
--- @return string
default = function(status)
local added, changed, removed = status.added, status.changed, status.removed
local status_txt = {}
if added and added > 0 then
table.insert(status_txt, '+' .. added)
end
if changed and changed > 0 then
table.insert(status_txt, '~' .. changed)
end
if removed and removed > 0 then
table.insert(status_txt, '-' .. removed)
end
return table.concat(status_txt, ' ')
end,
default_help = [[function(status)
local added, changed, removed = status.added, status.changed, status.removed
local status_txt = {}
if added and added > 0 then table.insert(status_txt, '+'..added ) end
if changed and changed > 0 then table.insert(status_txt, '~'..changed) end
if removed and removed > 0 then table.insert(status_txt, '-'..removed) end
return table.concat(status_txt, ' ')
end]],
description = [[
Function used to format `b:gitsigns_status`.
]],
},
max_file_length = {
type = 'number',
default = 40000,
description = [[
Max file length (in lines) to attach to.
]],
},
preview_config = {
type = 'table',
deep_extend = true,
default = {
border = 'single',
style = 'minimal',
relative = 'cursor',
row = 0,
col = 1,
},
description = [[
Option overrides for the Gitsigns preview window. Table is passed directly
to `nvim_open_win`.
]],
},
auto_attach = {
type = 'boolean',
default = true,
description = [[
Automatically attach to files.
]],
},
attach_to_untracked = {
type = 'boolean',
default = false,
description = [[
Attach to untracked files.
]],
},
update_debounce = {
type = 'number',
default = 100,
description = [[
Debounce time for updates (in milliseconds).
]],
},
current_line_blame = {
type = 'boolean',
default = false,
description = [[
Adds an unobtrusive and customisable blame annotation at the end of
the current line.
The highlight group used for the text is `GitSignsCurrentLineBlame`.
]],
},
current_line_blame_opts = {
type = 'table',
deep_extend = true,
default = {
virt_text = true,
virt_text_pos = 'eol',
virt_text_priority = 100,
delay = 1000,
use_focus = true,
},
description = [[
Options for the current line blame annotation.
Fields: ~
โข virt_text: boolean
Whether to show a virtual text blame annotation.
โข virt_text_pos: string
Blame annotation position. Available values:
`eol` Right after eol character.
`overlay` Display over the specified column, without
shifting the underlying text.
`right_align` Display right aligned in the window.
โข delay: integer
Sets the delay (in milliseconds) before blame virtual text is
displayed.
โข ignore_whitespace: boolean
Ignore whitespace when running blame.
โข virt_text_priority: integer
Priority of virtual text.
โข use_focus: boolean
Enable only when buffer is in focus
โข extra_opts: string[]
Extra options passed to `git-blame`.
]],
},
current_line_blame_formatter = {
type = { 'string', 'function' },
default = ' , - ',
description = [[
String or function used to format the virtual text of
|gitsigns-config-current_line_blame|.
When a string, accepts the following format specifiers:
โข ``
โข ``
โข ``
โข ``
โข ``
โข `` or ``
โข ``
โข ``
โข ``
โข `` or ``
โข ``
โข ``
โข ``
โข ``
For `` and ``, `FORMAT` can
be any valid date format that is accepted by `os.date()` with the
addition of `%R` (defaults to `%Y-%m-%d`):
โข `%a` abbreviated weekday name (e.g., Wed)
โข `%A` full weekday name (e.g., Wednesday)
โข `%b` abbreviated month name (e.g., Sep)
โข `%B` full month name (e.g., September)
โข `%c` date and time (e.g., 09/16/98 23:48:10)
โข `%d` day of the month (16) [01-31]
โข `%H` hour, using a 24-hour clock (23) [00-23]
โข `%I` hour, using a 12-hour clock (11) [01-12]
โข `%M` minute (48) [00-59]
โข `%m` month (09) [01-12]
โข `%p` either "am" or "pm" (pm)
โข `%S` second (10) [00-61]
โข `%w` weekday (3) [0-6 = Sunday-Saturday]
โข `%x` date (e.g., 09/16/98)
โข `%X` time (e.g., 23:48:10)
โข `%Y` full year (1998)
โข `%y` two-digit year (98) [00-99]
โข `%%` the character `%ยด
โข `%R` relative (e.g., 4 months ago)
When a function:
Parameters: ~
{name} Git user name returned from `git config user.name` .
{blame_info} Table with the following keys:
โข `abbrev_sha`: string
โข `orig_lnum`: integer
โข `final_lnum`: integer
โข `author`: string
โข `author_mail`: string
โข `author_time`: integer
โข `author_tz`: string
โข `committer`: string
โข `committer_mail`: string
โข `committer_time`: integer
โข `committer_tz`: string
โข `summary`: string
โข `previous`: string
โข `filename`: string
โข `boundary`: true?
Note that the keys map onto the output of:
`git blame --line-porcelain`
Return: ~
The result of this function is passed directly to the `opts.virt_text`
field of |nvim_buf_set_extmark| and thus must be a list of
[text, highlight] tuples.
]],
},
current_line_blame_formatter_nc = {
type = { 'string', 'function' },
default = ' ',
description = [[
String or function used to format the virtual text of
|gitsigns-config-current_line_blame| for lines that aren't committed.
See |gitsigns-config-current_line_blame_formatter| for more information.
]],
},
trouble = {
type = 'boolean',
default = function()
local has_trouble = pcall(require, 'trouble')
return has_trouble
end,
default_help = 'true if installed',
description = [[
When using setqflist() or setloclist(), open Trouble instead of the
quickfix/location list window.
]],
},
_git_version = {
type = 'string',
default = 'auto',
description = [[
Version of git available. Set to 'auto' to automatically detect.
]],
},
_verbose = {
type = 'boolean',
default = false,
description = [[
More verbose debug message. Requires debug_mode=true.
]],
},
_test_mode = {
description = 'Enable test mode',
type = 'boolean',
default = false,
},
word_diff = {
type = 'boolean',
default = false,
description = [[
Highlight intra-line word differences in the buffer.
Requires `config.diff_opts.internal = true` .
Uses the highlights:
โข For word diff in previews:
โข `GitSignsAddInline`
โข `GitSignsChangeInline`
โข `GitSignsDeleteInline`
โข For word diff in buffer:
โข `GitSignsAddLnInline`
โข `GitSignsChangeLnInline`
โข `GitSignsDeleteLnInline`
โข For word diff in virtual lines (e.g. show_deleted):
โข `GitSignsAddVirtLnInline`
โข `GitSignsChangeVirtLnInline`
โข `GitSignsDeleteVirtLnInline`
]],
},
_refresh_staged_on_update = {
type = 'boolean',
default = false,
description = [[
Always refresh the staged file on each update. Disabling this will cause
the staged file to only be refreshed when an update to the index is
detected.
]],
},
_threaded_diff = {
type = 'boolean',
default = true,
description = [[
Run diffs on a separate thread
]],
},
_new_sign_calc = {
type = 'boolean',
default = false,
description = [[
Use new sign calculation method
]],
},
debug_mode = {
type = 'boolean',
default = false,
description = [[
Enables debug logging and makes the following functions
available: `dump_cache`, `debug_messages`, `clear_debug`.
]],
},
}
local function validate(k, v, ty)
if vim.fn.has('nvim-0.11') == 1 then
--- @diagnostic disable-next-line: redundant-parameter,param-type-mismatch
vim.validate(k, v, ty)
else
vim.validate({ [k] = { v, ty } })
end
end
--- @param config Gitsigns.Config
local function validate_config(config)
for k, v in
pairs(config --[[@as table]])
do
local kschema = M.schema[k]
if kschema == nil then
warn("gitsigns: Ignoring invalid configuration field '%s'", k)
else
local ty = kschema.type
if type(ty) == 'string' or type(ty) == 'function' then
validate(k, v, ty)
end
end
end
end
--- @param cfg table
local function handle_deprecated(cfg)
for k, v in pairs(M.schema) do
local dep = v.deprecated
if dep and cfg[k] ~= nil then
if type(dep) == 'table' then
if dep.hard then
if dep.message then
warn(dep.message)
else
warn('%s is now deprecated; ignoring', k)
end
end
else
warn('%s is now deprecated; ignoring', k)
end
end
end
end
--- @param k string
--- @param v Gitsigns.SchemaElem
--- @param user_val any
local function build_field(k, v, user_val)
local config = M.config --[[@as table]]
if v.deep_extend then
local d = resolve_default(v)
config[k] = vim.tbl_deep_extend('force', d, user_val)
else
config[k] = user_val
end
end
--- @param user_config Gitsigns.Config|nil
function M.build(user_config)
user_config = user_config or {}
handle_deprecated(user_config)
validate_config(user_config)
for k, v in pairs(M.schema) do
if user_config[k] ~= nil then
build_field(k, v, user_config[k])
if v.refresh then
v.refresh(function()
build_field(k, v, user_config[k])
end)
end
end
end
end
return M
lewis6991-gitsigns.nvim-7010000/lua/gitsigns/current_line_blame.lua 0000664 0000000 0000000 00000014742 14765572704 0025131 0 ustar 00root root 0000000 0000000 local async = require('gitsigns.async')
local debounce = require('gitsigns.debounce')
local util = require('gitsigns.util')
local cache = require('gitsigns.cache').cache
local config = require('gitsigns.config').config
local schema = require('gitsigns.config').schema
local error_once = require('gitsigns.message').error_once
local api = vim.api
local namespace = api.nvim_create_namespace('gitsigns_blame')
local M = {}
--- @param bufnr integer
local function reset(bufnr)
if not api.nvim_buf_is_valid(bufnr) then
return
end
api.nvim_buf_del_extmark(bufnr, namespace, 1)
vim.b[bufnr].gitsigns_blame_line_dict = nil
end
--- @param fmt string
--- @param name string
--- @param info Gitsigns.BlameInfoPublic
--- @return string
local function expand_blame_format(fmt, name, info)
if info.author == name then
info.author = 'You'
end
return util.expand_format(fmt, info)
end
--- @param virt_text [string, string][]
--- @return string
local function flatten_virt_text(virt_text)
local res = {} ---@type string[]
for _, part in ipairs(virt_text) do
res[#res + 1] = part[1]
end
return table.concat(res)
end
--- @return integer
local function win_width()
local winid = api.nvim_get_current_win()
local wininfo = vim.fn.getwininfo(winid)[1]
local textoff = wininfo and wininfo.textoff or 0
return api.nvim_win_get_width(winid) - textoff
end
--- @param bufnr integer
--- @param lnum integer
--- @return integer
local function line_len(bufnr, lnum)
local line = api.nvim_buf_get_lines(bufnr, lnum - 1, lnum, true)[1]
return api.nvim_strwidth(line)
end
--- @param fmt string
--- @return Gitsigns.CurrentLineBlameFmtFun
local function default_formatter(fmt)
return function(username, blame_info)
return {
{
expand_blame_format(fmt, username, blame_info),
'GitSignsCurrentLineBlame',
},
}
end
end
---@param bufnr integer
---@param blame_info Gitsigns.BlameInfoPublic
---@return [string, string][]
local function get_blame_virt_text(bufnr, blame_info)
local git_obj = assert(cache[bufnr]).git_obj
local use_nc = blame_info.author == 'Not Committed Yet'
local clb_formatter = use_nc and config.current_line_blame_formatter_nc
or config.current_line_blame_formatter
if type(clb_formatter) == 'function' then
local ok, res = pcall(clb_formatter, git_obj.repo.username, blame_info)
if ok then
return res
end
local nc_sfx = use_nc and '_nc' or ''
error_once(
'Failed running config.current_line_blame_formatter%s, using default:\n %s',
nc_sfx,
res
)
--- @type string
clb_formatter = schema.current_line_blame_formatter.default
end
return default_formatter(clb_formatter)(git_obj.repo.username, blame_info)
end
--- @param bufnr integer
--- @param lnum integer
--- @param blame_info Gitsigns.BlameInfo
--- @param opts Gitsigns.CurrentLineBlameOpts
local function handle_blame_info(bufnr, lnum, blame_info, opts)
blame_info = util.convert_blame_info(blame_info)
local virt_text = get_blame_virt_text(bufnr, blame_info)
local virt_text_str = flatten_virt_text(virt_text)
vim.b[bufnr].gitsigns_blame_line_dict = blame_info
vim.b[bufnr].gitsigns_blame_line = virt_text_str
if opts.virt_text then
local virt_text_pos = opts.virt_text_pos
if virt_text_pos == 'right_align' then
if api.nvim_strwidth(virt_text_str) > (win_width() - line_len(bufnr, lnum)) then
virt_text_pos = 'eol'
end
end
api.nvim_buf_set_extmark(bufnr, namespace, lnum - 1, 0, {
id = 1,
virt_text = virt_text,
virt_text_pos = virt_text_pos,
priority = opts.virt_text_priority,
hl_mode = 'combine',
})
end
end
--- @param winid integer
--- @return integer lnum
local function get_lnum(winid)
return api.nvim_win_get_cursor(winid)[1]
end
--- @param winid integer
--- @param lnum integer
--- @return boolean
local function foldclosed(winid, lnum)
---@return boolean
return api.nvim_win_call(winid, function()
return vim.fn.foldclosed(lnum) ~= -1
end)
end
---@return boolean
local function insert_mode()
return api.nvim_get_mode().mode == 'i'
end
--- Update function, must be called in async context
--- @param bufnr integer
local function update0(bufnr)
async.schedule()
if not api.nvim_buf_is_valid(bufnr) then
return
end
if insert_mode() then
return
end
local winid = api.nvim_get_current_win()
if bufnr ~= api.nvim_win_get_buf(winid) then
return
end
local lnum = get_lnum(winid)
-- Can't show extmarks on folded lines so skip
if foldclosed(winid, lnum) then
return
end
local bcache = cache[bufnr]
if not bcache or not bcache.git_obj.object_name then
return
end
local opts = config.current_line_blame_opts
local blame_info = bcache:get_blame(lnum, opts)
if not api.nvim_win_is_valid(winid) or bufnr ~= api.nvim_win_get_buf(winid) then
return
end
if not blame_info then
return
end
if lnum ~= get_lnum(winid) then
-- Cursor has moved during events; abort and tr-trigger another update
update0(bufnr)
return
end
handle_blame_info(bufnr, lnum, blame_info, opts)
end
local update = async.create(1, debounce.throttle_by_id(update0))
--- @type fun(bufnr: integer)
M.update = nil
function M.setup()
for k in pairs(cache) do
reset(k)
end
local group = api.nvim_create_augroup('gitsigns_blame', {})
if not config.current_line_blame then
return
end
local opts = config.current_line_blame_opts
-- TODO(lewis6991): opts.delay is always defined as the schema set
-- deep_extend=true
M.update = debounce.debounce_trailing(assert(opts.delay), update)
-- show current buffer line blame immediately
M.update(api.nvim_get_current_buf())
local update_events = { 'BufEnter', 'CursorMoved', 'CursorMovedI' }
local reset_events = { 'InsertEnter', 'BufLeave' }
if vim.fn.exists('#WinResized') == 1 then
-- For nvim 0.9+
update_events[#update_events + 1] = 'WinResized'
end
if opts.use_focus then
update_events[#update_events + 1] = 'FocusGained'
reset_events[#reset_events + 1] = 'FocusLost'
end
api.nvim_create_autocmd(update_events, {
group = group,
callback = function(args)
reset(args.buf)
M.update(args.buf)
end,
})
api.nvim_create_autocmd(reset_events, {
group = group,
callback = function(args)
reset(args.buf)
end,
})
api.nvim_create_autocmd('OptionSet', {
group = group,
pattern = { 'fileformat', 'bomb', 'eol' },
callback = function(args)
reset(args.buf)
end,
})
end
return M
lewis6991-gitsigns.nvim-7010000/lua/gitsigns/debounce.lua 0000664 0000000 0000000 00000004060 14765572704 0023054 0 ustar 00root root 0000000 0000000 local uv = vim.uv or vim.loop
local M = {}
--- Debounces a function on the trailing edge.
---
--- @generic F: function
--- @param ms number Timeout in ms
--- @param fn F Function to debounce
--- @param hash? integer|fun(...): any Function that determines id from arguments to fn
--- @return F Debounced function.
function M.debounce_trailing(ms, fn, hash)
local running = {} --- @type table
if type(hash) == 'number' then
local hash_i = hash
hash = function(...)
return select(hash_i, ...)
end
end
return function(...)
local id = hash and hash(...) or true
if running[id] == nil then
running[id] = assert(uv.new_timer())
end
local timer = running[id]
local argv = { ... }
timer:start(ms, 0, function()
timer:stop()
running[id] = nil
fn(unpack(argv, 1, table.maxn(argv)))
end)
end
end
--- Throttles a function using the first argument as an ID
---
--- If function is already running then the function will be scheduled to run
--- again once the running call has finished.
---
--- fn#1 _/โพ\__/โพ\_/โพ\_____________________________
--- throttled#1 _/โพโพโพโพโพโพโพโพโพโพโพโพโพโพโพ\/โพโพโพโพโพโพโพโพโพโพ\____________
--
--- fn#2 ______/โพ\___________/โพ\___________________
--- throttled#2 ______/โพโพโพโพโพโพโพโพโพโพ\__/โพโพโพโพโพโพโพโพโพโพ\__________
---
---
--- @generic F: function
--- @param fn F Function to throttle
--- @param schedule? boolean
--- @return F throttled function.
function M.throttle_by_id(fn, schedule)
local scheduled = {} --- @type table
local running = {} --- @type table
return function(id, ...)
if scheduled[id] then
-- If fn is already scheduled, then drop
return
end
if not running[id] or schedule then
scheduled[id] = true
end
if running[id] then
return
end
while scheduled[id] do
scheduled[id] = nil
running[id] = true
fn(id, ...)
running[id] = nil
end
end
end
return M
lewis6991-gitsigns.nvim-7010000/lua/gitsigns/debug.lua 0000664 0000000 0000000 00000002243 14765572704 0022357 0 ustar 00root root 0000000 0000000 local log = require('gitsigns.debug.log')
--- @class gitsigns.debug
local M = {}
--- @param raw_item any
--- @param path string[]
--- @return any
local function process(raw_item, path)
--- @diagnostic disable-next-line:undefined-field
if path[#path] == vim.inspect.METATABLE then
return
elseif type(raw_item) == 'function' then
return
elseif type(raw_item) ~= 'table' then
return raw_item
end
--- @cast raw_item table
local key = path[#path]
if
vim.tbl_contains({
'compare_text',
'compare_text_head',
'hunks',
'hunks_staged',
'staged_diffs',
}, key)
then
return { '...', length = #vim.tbl_keys(raw_item), head = raw_item[next(raw_item)] }
elseif key == 'blame' then
return { '...', length = #vim.tbl_keys(raw_item) }
end
return raw_item
end
--- @return any
function M.dump_cache()
-- TODO(lewis6991): hack: use package.loaded to avoid circular deps
local cache = (require('gitsigns.cache')).cache
--- @type string
local text = vim.inspect(cache, { process = process })
vim.api.nvim_echo({ { text } }, false, {})
end
M.debug_messages = log.show
M.clear_debug = log.clear
return M
lewis6991-gitsigns.nvim-7010000/lua/gitsigns/debug/ 0000775 0000000 0000000 00000000000 14765572704 0021653 5 ustar 00root root 0000000 0000000 lewis6991-gitsigns.nvim-7010000/lua/gitsigns/debug/log.lua 0000664 0000000 0000000 00000010347 14765572704 0023144 0 ustar 00root root 0000000 0000000 local uv = vim.uv or vim.loop
local start_time = uv.hrtime()
--- @class Gitsigns.log
--- @field private messages [number, string, string, string][]
local M = {
debug_mode = false,
verbose = false,
messages = {},
}
--- @param name string
--- @param lvl integer
--- @return any
local function getvarvalue(name, lvl)
lvl = lvl + 1
local value --- @type any?
local found --- @type boolean?
-- try local variables
local i = 1
while true do
local n, v = debug.getlocal(lvl, i)
if not n then
break
end
if n == name then
value = v
found = true
end
i = i + 1
end
if found then
return value
end
-- try upvalues
local func = debug.getinfo(lvl).func
i = 1
while true do
local n, v = debug.getupvalue(func, i)
if not n then
break
end
if n == name then
return v
end
i = i + 1
end
-- not found; get global
return getfenv(func)[name]
end
--- @param lvl integer
--- @return {name:string, bufnr: integer}
local function get_context(lvl)
lvl = lvl + 1
local name = getvarvalue('__FUNC__', lvl)
if not name then
local name0 = debug.getinfo(lvl, 'n').name or ''
name = name0:gsub('(.*)%d+$', '%1')
end
local bufnr = getvarvalue('bufnr', lvl)
or getvarvalue('_bufnr', lvl)
or getvarvalue('cbuf', lvl)
or getvarvalue('buf', lvl)
return { name = name, bufnr = bufnr }
end
local function tostring(obj)
return type(obj) == 'string' and obj or vim.inspect(obj)
end
-- If called in a callback then make sure the callback defines a __FUNC__
-- variable which can be used to identify the name of the function.
--- @param kind string
--- @param lvl integer
--- @param ... any
local function cprint(kind, lvl, ...)
lvl = lvl + 1
local msgs = {} --- @type string[]
for i = 1, select('#', ...) do
msgs[i] = tostring(select(i, ...))
end
local msg = table.concat(msgs, ' ')
local ctx = get_context(lvl)
local time = (uv.hrtime() - start_time) / 1e6
local ctx1 = ctx.name
if ctx.bufnr then
ctx1 = string.format('%s(%s)', ctx1, ctx.bufnr)
end
table.insert(M.messages, { time, kind, ctx1, msg })
end
function M.dprint(...)
if not M.debug_mode then
return
end
cprint('debug', 2, ...)
end
function M.dprintf(obj, ...)
if not M.debug_mode then
return
end
cprint('debug', 2, obj:format(...))
end
function M.vprint(...)
if not (M.debug_mode and M.verbose) then
return
end
cprint('info', 2, ...)
end
function M.vprintf(obj, ...)
if not (M.debug_mode and M.verbose) then
return
end
cprint('info', 2, obj:format(...))
end
--- @param msg string
--- @param level integer
local function eprint(msg, level)
local info = debug.getinfo(level + 2, 'Sl')
local ctx = info and string.format('%s<%d>', info.short_src, info.currentline) or '???'
local time = (uv.hrtime() - start_time) / 1e6
table.insert(M.messages, { time, 'error', ctx, debug.traceback(msg) })
if M.debug_mode then
error(msg, 3)
end
end
function M.eprint(msg)
eprint(msg, 1)
end
function M.eprintf(fmt, ...)
eprint(fmt:format(...), 1)
end
--- @param cond boolean
--- @param msg string
--- @return boolean
function M.assert(cond, msg)
if not cond then
eprint(msg, 1)
end
return not cond
end
local sev_to_hl = {
debug = 'Title',
info = 'MoreMsg',
warn = 'WarningMsg',
error = 'ErrorMsg',
}
function M.clear()
M.messages = {}
end
--- @param m [number, string, string, string]
--- @return [string,string][]
local function build_msg(m)
local time, kind, ctx, msg = m[1], m[2], m[3], m[4]
local hl = sev_to_hl[kind]
return {
{ string.format('%.2f ', time), 'Comment' },
{ kind:upper():sub(1, 1), hl },
{ string.format(' %s:', ctx), 'Tag' },
{ ' ' },
{ msg },
}
end
function M.show()
for _, m in ipairs(M.messages) do
vim.api.nvim_echo(build_msg(m), false, {})
end
end
--- @return string[]?
function M.get()
local r = {} --- @type string[]
for _, m in ipairs(M.messages) do
local e = build_msg(m)
local e1 = {} --- @type string[]
for _, x in ipairs(e) do
e1[#e1 + 1] = x[1]
end
r[#r + 1] = table.concat(e1)
end
return r
end
--- @param config Gitsigns.Config
function M.setup(config)
M.debug_mode = config.debug_mode
M.verbose = config._verbose
end
return M
lewis6991-gitsigns.nvim-7010000/lua/gitsigns/diff.lua 0000664 0000000 0000000 00000001135 14765572704 0022200 0 ustar 00root root 0000000 0000000 local config = require('gitsigns.config').config
--- @async
--- @param a string[]
--- @param b string[]
--- @param linematch? boolean
--- @return Gitsigns.Hunk.Hunk[] hunks
return function(a, b, linematch)
-- -- Short circuit optimization
-- if not a or #a == 0 then
-- local Hunks = require('gitsigns.hunks')
-- local hunk = Hunks.create_hunk(0, 0, 1, #b)
-- hunk.added.lines = b
-- return { hunk }
-- end
if config.diff_opts.internal then
return require('gitsigns.diff_int').run_diff(a, b, linematch)
else
return require('gitsigns.diff_ext').run_diff(a, b)
end
end
lewis6991-gitsigns.nvim-7010000/lua/gitsigns/diff_ext.lua 0000664 0000000 0000000 00000004763 14765572704 0023072 0 ustar 00root root 0000000 0000000 local Hunks = require('gitsigns.hunks')
local util = require('gitsigns.util')
local scheduler = require('gitsigns.async').schedule
local config = require('gitsigns.config').config
local git_command = require('gitsigns.git.cmd')
local M = {}
--- @param path string
--- @param text string[]
local function write_to_file(path, text)
local f, err = io.open(path, 'wb')
if f == nil then
error(err)
end
for _, l in ipairs(text) do
f:write(l)
f:write('\n')
end
f:close()
end
--- @async
--- @param text_cmp string[]
--- @param text_buf string[]
--- @return Gitsigns.Hunk.Hunk[]
function M.run_diff(text_cmp, text_buf)
local results = {} --- @type Gitsigns.Hunk.Hunk[]
-- tmpname must not be called in a callback
if vim.in_fast_event() then
scheduler()
end
local file_buf = util.tmpname()
local file_cmp = util.tmpname()
write_to_file(file_buf, text_buf)
write_to_file(file_cmp, text_cmp)
-- Taken from gitgutter, diff.vim:
--
-- If a file has CRLF line endings and git's core.autocrlf is true, the file
-- in git's object store will have LF line endings. Writing it out via
-- git-show will produce a file with LF line endings.
--
-- If this last file is one of the files passed to git-diff, git-diff will
-- convert its line endings to CRLF before diffing -- which is what we want
-- but also by default outputs a warning on stderr.
--
-- warning: LF will be replace by CRLF in .
-- The file will have its original line endings in your working directory.
--
-- We can safely ignore the warning, we turn it off by passing the '-c
-- "core.safecrlf=false"' argument to git-diff.
local opts = config.diff_opts
local out = git_command({
'-c',
'core.safecrlf=false',
'diff',
'--color=never',
'--' .. (opts.indent_heuristic and '' or 'no-') .. 'indent-heuristic',
'--diff-algorithm=' .. opts.algorithm,
'--patch-with-raw',
'--unified=0',
file_cmp,
file_buf,
}, {
-- git-diff implies --exit-code
ignore_error = true,
})
for _, line in ipairs(out) do
if vim.startswith(line, '@@') then
results[#results + 1] = Hunks.parse_diff_line(line)
elseif #results > 0 then
local r = results[#results]
if line:sub(1, 1) == '-' then
r.removed.lines[#r.removed.lines + 1] = line:sub(2)
elseif line:sub(1, 1) == '+' then
r.added.lines[#r.added.lines + 1] = line:sub(2)
end
end
end
os.remove(file_buf)
os.remove(file_cmp)
return results
end
return M
lewis6991-gitsigns.nvim-7010000/lua/gitsigns/diff_int.lua 0000664 0000000 0000000 00000012037 14765572704 0023055 0 ustar 00root root 0000000 0000000 local async = require('gitsigns.async')
local uv = vim.uv or vim.loop
local create_hunk = require('gitsigns.hunks').create_hunk
local config = require('gitsigns.config').config
local function getencdec()
local m = jit and package.preload['string.buffer'] and require('string.buffer') or vim.mpack
return m.encode, m.decode
end
--- @param f function
--- @param args any[]
--- @param cb function(...)
local new_thread = async.awrap(3, function(f, args, cb)
local encode, decode = getencdec()
uv.new_work(function(getencdec_bc, fd, argse)
local getencdec0 = getencdec or assert(loadstring(getencdec_bc))
local encode0, decode0 = getencdec0()
local args0 = decode0(argse) --[[@as any[] ]]
local f0 = assert(loadstring(fd))
return encode0(f0(unpack(args0)))
end, function(r)
cb(decode(r))
end):queue(string.dump(getencdec), string.dump(f), encode(args))
end)
local M = {}
--- @alias Gitsigns.Region [integer, string, integer, integer]
--- @alias Gitsigns.RawHunk [integer, integer, integer, integer]
---@param a string
---@param b string
---@param opts Gitsigns.DiffOpts
---@param linematch? boolean
---@return Gitsigns.RawHunk[]
local function run_diff(a, b, opts, linematch)
local linematch0 --- @type integer?
if linematch ~= false then
linematch0 = opts.linematch
end
return vim.diff(a, b, {
result_type = 'indices',
algorithm = opts.algorithm,
indent_heuristic = opts.indent_heuristic,
ignore_whitespace = opts.ignore_whitespace,
ignore_whitespace_change = opts.ignore_whitespace_change,
ignore_whitespace_change_at_eol = opts.ignore_whitespace_change_at_eol,
ignore_blank_lines = opts.ignore_blank_lines,
linematch = linematch0,
}) --[[@as Gitsigns.RawHunk[] ]]
end
--- @async
--- @param a string
--- @param b string
--- @param opts Gitsigns.DiffOpts
--- @param linematch? boolean
--- @return Gitsigns.RawHunk[]
local function run_diff_async(a, b, opts, linematch)
return new_thread(run_diff, { a, b, opts, linematch })
end
--- @param fa string[]
--- @param fb string[]
--- @param rawhunks Gitsigns.RawHunk[]
--- @return Gitsigns.Hunk.Hunk[]
local function tohunks(fa, fb, rawhunks)
local hunks = {} --- @type Gitsigns.Hunk.Hunk[]
for _, r in ipairs(rawhunks) do
local rs, rc, as, ac = r[1], r[2], r[3], r[4]
local hunk = create_hunk(rs, rc, as, ac)
if rc > 0 then
for i = rs, rs + rc - 1 do
hunk.removed.lines[#hunk.removed.lines + 1] = fa[i] or ''
end
if rs + rc >= #fa and fa[#fa] ~= '' then
hunk.removed.no_nl_at_eof = true
end
end
if ac > 0 then
for i = as, as + ac - 1 do
hunk.added.lines[#hunk.added.lines + 1] = fb[i] or ''
end
if as + ac >= #fb and fb[#fb] ~= '' then
hunk.added.no_nl_at_eof = true
end
end
hunks[#hunks + 1] = hunk
end
return hunks
end
--- @async
--- @param fa string[]
--- @param fb string[]
--- @param linematch? boolean
--- @return Gitsigns.Hunk.Hunk[]
function M.run_diff(fa, fb, linematch)
local run_diff0 = config._threaded_diff and vim.is_thread and run_diff_async or run_diff
local a = table.concat(fa, '\n')
local b = table.concat(fb, '\n')
return tohunks(fa, fb, run_diff0(a, b, config.diff_opts, linematch))
end
local gaps_between_regions = 5
--- @param hunks Gitsigns.Hunk.Hunk[]
--- @return Gitsigns.Hunk.Hunk[]
local function denoise_hunks(hunks)
-- Denoise the hunks
local ret = { hunks[1] } --- @type Gitsigns.Hunk.Hunk[]
for j = 2, #hunks do
local h, n = ret[#ret], hunks[j]
if not h or not n then
break
end
if n.added.start - h.added.start - h.added.count < gaps_between_regions then
h.added.count = n.added.start + n.added.count - h.added.start
h.removed.count = n.removed.start + n.removed.count - h.removed.start
if h.added.count > 0 or h.removed.count > 0 then
h.type = 'change'
end
else
ret[#ret + 1] = n
end
end
return ret
end
--- @param removed string[]
--- @param added string[]
--- @return Gitsigns.Region[] removed
--- @return Gitsigns.Region[] added
function M.run_word_diff(removed, added)
local adds = {} --- @type Gitsigns.Region[]
local rems = {} --- @type Gitsigns.Region[]
if #removed ~= #added then
return rems, adds
end
for i = 1, #removed do
-- pair lines by position
local a = table.concat(vim.split(removed[i], ''), '\n')
local b = table.concat(vim.split(added[i], ''), '\n')
local hunks = {} --- @type Gitsigns.Hunk.Hunk[]
for _, r in ipairs(run_diff(a, b, config.diff_opts)) do
local rs, rc, as, ac = r[1], r[2], r[3], r[4]
-- Balance of the unknown offset done in hunk_func
if rc == 0 then
rs = rs + 1
end
if ac == 0 then
as = as + 1
end
hunks[#hunks + 1] = create_hunk(rs, rc, as, ac)
end
hunks = denoise_hunks(hunks)
for _, h in ipairs(hunks) do
adds[#adds + 1] = { i, h.type, h.added.start, h.added.start + h.added.count }
rems[#rems + 1] = { i, h.type, h.removed.start, h.removed.start + h.removed.count }
end
end
return rems, adds
end
return M
lewis6991-gitsigns.nvim-7010000/lua/gitsigns/diffthis.lua 0000664 0000000 0000000 00000016135 14765572704 0023076 0 ustar 00root root 0000000 0000000 local async = require('gitsigns.async')
local manager = require('gitsigns.manager')
local message = require('gitsigns.message')
local util = require('gitsigns.util')
local Status = require('gitsigns.status')
local cache = require('gitsigns.cache').cache
local log = require('gitsigns.debug.log')
local throttle_by_id = require('gitsigns.debounce').throttle_by_id
local api = vim.api
local M = {}
--- @async
--- @param bufnr integer
--- @param dbufnr integer
--- @param base string?
local function bufread(bufnr, dbufnr, base)
local bcache = assert(cache[bufnr])
base = util.norm_base(base)
local text --- @type string[]
if base == bcache.git_obj.revision then
text = assert(bcache.compare_text)
else
local err
text, err = bcache.git_obj:get_show_text(base)
if err then
error(err, 2)
end
async.schedule()
if not api.nvim_buf_is_valid(bufnr) then
return
end
end
vim.bo[dbufnr].fileformat = vim.bo[bufnr].fileformat
vim.bo[dbufnr].filetype = vim.bo[bufnr].filetype
vim.bo[dbufnr].bufhidden = 'wipe'
local modifiable = vim.bo[dbufnr].modifiable
vim.bo[dbufnr].modifiable = true
Status:update(dbufnr, { head = base })
util.set_lines(dbufnr, 0, -1, text)
vim.bo[dbufnr].modifiable = modifiable
vim.bo[dbufnr].modified = false
require('gitsigns.attach').attach(dbufnr, nil, 'BufReadCmd')
end
--- @async
--- @param bufnr integer
--- @param dbufnr integer
--- @param base string?
local function bufwrite(bufnr, dbufnr, base)
local bcache = assert(cache[bufnr])
local buftext = util.buf_lines(dbufnr)
base = util.norm_base(base)
bcache.git_obj:stage_lines(buftext)
async.schedule()
if not api.nvim_buf_is_valid(bufnr) then
return
end
vim.bo[dbufnr].modified = false
-- If diff buffer base matches the git_obj revision then also update the
-- signs.
if base == bcache.git_obj.revision then
bcache.compare_text = buftext
manager.update(bufnr)
end
end
--- @async
--- Create a gitsigns buffer for a certain revision of a file
--- @param bufnr integer
--- @param base string?
--- @return string? buf Buffer name
--- @return integer? bufnr Buffer number
local function create_revision_buf(bufnr, base)
local bcache = assert(cache[bufnr])
base = util.norm_base(base)
local bufname = bcache:get_rev_bufname(base)
if util.bufexists(bufname) then
return bufname, vim.fn.bufnr(bufname)
end
local dbuf = api.nvim_create_buf(false, true)
api.nvim_buf_set_name(dbuf, bufname)
local ok, err = pcall(bufread, bufnr, dbuf, base)
if not ok then
message.error(err --[[@as string]])
async.schedule()
api.nvim_buf_delete(dbuf, { force = true })
return
end
-- allow editing the index revision
if not base then
vim.bo[dbuf].buftype = 'acwrite'
api.nvim_create_autocmd('BufReadCmd', {
group = 'gitsigns',
buffer = dbuf,
callback = function()
async.arun(bufread, bufnr, dbuf, base)
end,
})
api.nvim_create_autocmd('BufWriteCmd', {
group = 'gitsigns',
buffer = dbuf,
callback = function()
async.arun(bufwrite, bufnr, dbuf, base)
end,
})
else
vim.bo[dbuf].buftype = 'nowrite'
vim.bo[dbuf].modifiable = false
end
return bufname, dbuf
end
--- @class Gitsigns.DiffthisOpts
--- @field vertical? boolean
--- @field split? string
--- @async
--- @param base string?
--- @param opts? Gitsigns.DiffthisOpts
local function diffthis_rev(base, opts)
local bufnr = api.nvim_get_current_buf()
local bufname, dbuf = create_revision_buf(bufnr, base)
if not bufname then
return
end
opts = opts or {}
local cwin = api.nvim_get_current_win()
vim.cmd.diffsplit({
bufname,
mods = {
vertical = opts.vertical,
split = opts.split or 'aboveleft',
keepalt = true,
},
})
api.nvim_set_current_win(cwin)
-- Reset 'diff' option for the current window if the diff buffer is hidden
api.nvim_create_autocmd('BufHidden', {
buffer = assert(dbuf),
callback = function()
local tabpage = api.nvim_win_get_tabpage(cwin)
local disable_cwin_diff = true
for _, w in ipairs(api.nvim_tabpage_list_wins(tabpage)) do
if w ~= cwin and vim.wo[w].diff then
-- If there is another diff window open, don't disable diff
disable_cwin_diff = false
break
end
end
if disable_cwin_diff then
vim.wo[cwin].diff = false
end
end,
})
end
--- @param base string?
--- @param opts Gitsigns.DiffthisOpts
--- @param _callback? fun()
M.diffthis = async.create(2, function(base, opts, _callback)
if vim.wo.diff then
log.dprint('diff is disabled')
return
end
local bufnr = api.nvim_get_current_buf()
local bcache = cache[bufnr]
if not bcache then
log.dprintf('buffer %d is not attached', bufnr)
return
end
if not base and bcache.git_obj.has_conflicts then
diffthis_rev(':2', opts)
opts.split = 'belowright'
diffthis_rev(':3', opts)
else
diffthis_rev(base, opts)
end
end)
--- @param bufnr integer
--- @param base string
--- @param _callback? fun()
M.show = async.create(2, function(bufnr, base, _callback)
__FUNC__ = 'show'
local bufname = create_revision_buf(bufnr, base)
if not bufname then
log.dprint('No bufname for revision ' .. base)
return
end
log.dprint('bufname ' .. bufname)
vim.cmd.edit(bufname)
-- Wait for the buffer to attach in case the user passes a callback that
-- requires the buffer to be attached.
local sbufnr = api.nvim_get_current_buf()
local attached = vim.wait(2000, function()
return cache[sbufnr] ~= nil
end)
if not attached then
log.eprintf("Show buffer '%s' did not attach", bufname)
end
end)
--- @param bufnr integer
--- @return boolean
local function should_reload(bufnr)
if not vim.bo[bufnr].modified then
return true
end
local response --- @type string?
while not vim.tbl_contains({ 'O', 'L' }, response) do
response = async.await(2, vim.ui.input, {
prompt = 'Warning: The git index has changed and the buffer was changed as well. [O]K, (L)oad File:',
})
end
return response == 'L'
end
--- @param name string
--- @return boolean
local function is_fugitive_diff_window(name)
return vim.startswith(name, 'fugitive://')
and vim.fn.exists('*FugitiveParse')
and vim.fn.FugitiveParse(name)[1] ~= ':'
end
--- This function needs to be throttled as there is a call to vim.ui.input
--- @async
--- @param bufnr integer
--- @param _callback? fun()
M.update = throttle_by_id(function(bufnr, _callback)
if not vim.wo.diff then
return
end
-- Note this will be the bufname for the currently set base
-- which are the only ones we want to update
local bufname = assert(cache[bufnr]):get_rev_bufname()
for _, w in ipairs(api.nvim_list_wins()) do
if api.nvim_win_is_valid(w) then
local b = api.nvim_win_get_buf(w)
local bname = api.nvim_buf_get_name(b)
if bname == bufname or is_fugitive_diff_window(bname) then
if should_reload(b) then
api.nvim_buf_call(b, function()
vim.cmd.doautocmd('BufReadCmd')
vim.cmd.diffthis()
end)
end
end
end
end
end)
return M
lewis6991-gitsigns.nvim-7010000/lua/gitsigns/git.lua 0000664 0000000 0000000 00000014312 14765572704 0022054 0 ustar 00root root 0000000 0000000 local log = require('gitsigns.debug.log')
local async = require('gitsigns.async')
local util = require('gitsigns.util')
local Repo = require('gitsigns.git.repo')
local M = {}
M.Repo = Repo
--- @class Gitsigns.GitObj
--- @field file string
--- @field encoding string
--- @field i_crlf? boolean Object has crlf
--- @field w_crlf? boolean Working copy has crlf
--- @field mode_bits string
---
--- Revision the object is tracking against. Nil for index
--- @field revision? string
---
--- The fixed object name to use. Nil for untracked.
--- @field object_name? string
---
--- The path of the file relative to toplevel. Used to
--- perform git operations. Nil if file does not exist
--- @field relpath? string
---
--- Used for tracking moved files
--- @field orig_relpath? string
---
--- @field repo Gitsigns.Repo
--- @field has_conflicts? boolean
---
--- @field lock? true
local Obj = {}
Obj.__index = Obj
M.Obj = Obj
--- @param file string
--- @return boolean
local function in_git_dir(file)
for _, p in ipairs(vim.split(file, util.path_sep)) do
if p == '.git' then
return true
end
end
return false
end
--- @async
--- @param revision? string
--- @return string? err
function Obj:change_revision(revision)
self.revision = util.norm_base(revision)
return self:refresh()
end
--- @async
--- @return string? err
function Obj:refresh()
local info, err = self.repo:file_info(self.file, self.revision)
if err then
log.eprint(err)
end
if not info then
return err
end
self.relpath = info.relpath
self.object_name = info.object_name
self.mode_bits = info.mode_bits
self.has_conflicts = info.has_conflicts
self.i_crlf = info.i_crlf
self.w_crlf = info.w_crlf
end
function Obj:from_tree()
return Repo.from_tree(self.revision)
end
--- @async
--- @param revision? string
--- @return string[] stdout, string? stderr
function Obj:get_show_text(revision)
if revision and not self.relpath then
log.dprint('no relpath')
return {}
end
local object = revision and (revision .. ':' .. self.relpath) or self.object_name
if not object then
log.dprint('no revision or object_name')
return { '' }
end
local stdout, stderr = self.repo:get_show_text(object, self.encoding)
if not self.i_crlf and self.w_crlf then
-- Add cr
-- Do not add cr to the newline at the end of file
for i = 1, #stdout - 1 do
stdout[i] = stdout[i] .. '\r'
end
end
return stdout, stderr
end
--- @param file string
local function autocmd_changed(file)
vim.schedule(function()
vim.api.nvim_exec_autocmds('User', {
pattern = 'GitSignsChanged',
modeline = false,
data = { file = file },
})
end)
end
--- @async
function Obj:unstage_file()
self.lock = true
self.repo:command({ 'reset', self.file })
self.lock = nil
autocmd_changed(self.file)
end
--- @async
--- @param contents? string[]
--- @param lnum? integer
--- @param revision? string
--- @param opts? Gitsigns.BlameOpts
--- @return table
function Obj:run_blame(contents, lnum, revision, opts)
return require('gitsigns.git.blame').run_blame(self, contents, lnum, revision, opts)
end
--- @async
--- @private
function Obj:ensure_file_in_index()
self.lock = true
if self.object_name and not self.has_conflicts then
return
end
if not self.object_name then
-- If there is no object_name then it is not yet in the index so add it
self.repo:command({ 'add', '--intent-to-add', self.file })
else
-- Update the index with the common ancestor (stage 1) which is what bcache
-- stores
self.repo:update_index(self.mode_bits, self.object_name, self.relpath, true)
end
self:refresh()
self.lock = nil
end
--- @async
--- Stage 'lines' as the entire contents of the file
--- @param lines string[]
function Obj:stage_lines(lines)
self.lock = true
local new_object = self.repo:hash_object(self.relpath, lines)
self.repo:update_index(self.mode_bits, new_object, self.relpath)
self.lock = nil
autocmd_changed(self.file)
end
local sleep = async.awrap(2, function(duration, cb)
vim.defer_fn(cb, duration)
end)
--- @async
--- @param hunks Gitsigns.Hunk.Hunk[]
--- @param invert? boolean
--- @return string? err
function Obj:stage_hunks(hunks, invert)
self.lock = true
self:ensure_file_in_index()
local patch = require('gitsigns.hunks').create_patch(self.relpath, hunks, self.mode_bits, invert)
if not self.i_crlf and self.w_crlf then
-- Remove cr
for i = 1, #patch do
patch[i] = patch[i]:gsub('\r$', '')
end
end
local stat, err = pcall(function()
self.repo:command({
'apply',
'--whitespace=nowarn',
'--cached',
'--unidiff-zero',
'-',
}, {
stdin = patch,
})
end)
if not stat then
self.lock = nil
return err
end
-- Staging operations cause IO of the git directory so wait some time
-- for the changes to settle.
sleep(100)
self.lock = nil
autocmd_changed(self.file)
end
--- @async
--- @param file string
--- @param revision string?
--- @param encoding string
--- @param gitdir string?
--- @param toplevel string?
--- @return Gitsigns.GitObj?
function Obj.new(file, revision, encoding, gitdir, toplevel)
-- TODO(lewis6991): this check is flawed as any directory can be a git-dir
-- Can use: git rev-parse --is-inside-git-dir
if in_git_dir(file) then
log.dprint('In git dir')
return
end
if not vim.startswith(file, '/') and toplevel then
file = toplevel .. util.path_sep .. file
end
local repo = Repo.get(util.dirname(file), gitdir, toplevel)
if not repo then
log.dprint('Not in git repo')
return
end
-- When passing gitdir and toplevel, suppress stderr when resolving the file
local silent = gitdir ~= nil and toplevel ~= nil
revision = util.norm_base(revision)
local info, err = repo:file_info(file, revision)
if err and not silent then
log.eprint(err)
end
if not info then
return
end
local self = setmetatable({}, Obj)
self.repo = repo
self.file = file
self.revision = revision
self.encoding = encoding
self.relpath = info.relpath
self.object_name = info.object_name
self.mode_bits = info.mode_bits
self.has_conflicts = info.has_conflicts
self.i_crlf = info.i_crlf
self.w_crlf = info.w_crlf
return self
end
return M
lewis6991-gitsigns.nvim-7010000/lua/gitsigns/git/ 0000775 0000000 0000000 00000000000 14765572704 0021350 5 ustar 00root root 0000000 0000000 lewis6991-gitsigns.nvim-7010000/lua/gitsigns/git/blame.lua 0000664 0000000 0000000 00000015143 14765572704 0023137 0 ustar 00root root 0000000 0000000 local uv = vim.uv or vim.loop
local error_once = require('gitsigns.message').error_once
local log = require('gitsigns.debug.log')
--- @class Gitsigns.CommitInfo
--- @field author string
--- @field author_mail string
--- @field author_time integer
--- @field author_tz string
--- @field committer string
--- @field committer_mail string
--- @field committer_time integer
--- @field committer_tz string
--- @field summary string
--- @field sha string
--- @field abbrev_sha string
--- @field boundary? true
--- @class Gitsigns.BlameInfoPublic: Gitsigns.BlameInfo, Gitsigns.CommitInfo
--- @field body? string[]
--- @field hunk_no? integer
--- @field num_hunks? integer
--- @field hunk? string[]
--- @field hunk_head? string
--- @class Gitsigns.BlameInfo
--- @field orig_lnum integer
--- @field final_lnum integer
--- @field commit Gitsigns.CommitInfo
--- @field filename string
--- @field previous_filename? string
--- @field previous_sha? string
local NOT_COMMITTED = {
author = 'Not Committed Yet',
author_mail = '',
committer = 'Not Committed Yet',
committer_mail = '',
}
local M = {}
--- @param file string
--- @return Gitsigns.CommitInfo
local function not_committed(file)
local time = os.time()
return {
sha = string.rep('0', 40),
abbrev_sha = string.rep('0', 8),
author = 'Not Committed Yet',
author_mail = '',
author_tz = '+0000',
author_time = time,
committer = 'Not Committed Yet',
committer_time = time,
committer_mail = '',
committer_tz = '+0000',
summary = 'Version of ' .. file,
}
end
--- @param file string
--- @param lnum integer
--- @return Gitsigns.BlameInfo
function M.get_blame_nc(file, lnum)
return {
orig_lnum = 0,
final_lnum = lnum,
commit = not_committed(file),
filename = file,
}
end
---@param x any
---@return integer
local function asinteger(x)
return assert(tonumber(x))
end
--- @param readline fun(): string?
--- @param commits table
--- @param result table
local function incremental_iter(readline, commits, result)
local line = assert(readline())
--- @type string, string, string, string
local sha, orig_lnum_str, final_lnum_str, size_str = line:match('(%x+) (%d+) (%d+) (%d+)')
assert(sha)
local orig_lnum = asinteger(orig_lnum_str)
local final_lnum = asinteger(final_lnum_str)
local size = asinteger(size_str)
--- @type table
local commit = commits[sha] or {
sha = sha,
abbrev_sha = sha:sub(1, 8),
}
--- @type string, string
local previous_sha, previous_filename
line = assert(readline())
-- filename terminates the entry
while not line:match('^filename ') do
local key, value = line:match('^([^%s]+) (.*)')
if key == 'previous' then
previous_sha, previous_filename = line:match('^previous (%x+) (.*)')
elseif key then
key = key:gsub('%-', '_') --- @type string
if vim.endswith(key, '_time') then
value = tonumber(value)
end
commit[key] = value
else
commit[line] = true
if line ~= 'boundary' then
log.dprintf("Unknown tag: '%s'", line)
end
end
line = assert(readline())
end
local filename = assert(line:match('^filename (.*)'))
-- New in git 2.41:
-- The output given by "git blame" that attributes a line to contents
-- taken from the file specified by the "--contents" option shows it
-- differently from a line attributed to the working tree file.
if
commit.author_mail == ''
or commit.author_mail == 'External file (--contents)'
then
commit = vim.tbl_extend('force', commit, NOT_COMMITTED)
end
commits[sha] = commit
for j = 0, size - 1 do
result[final_lnum + j] = {
final_lnum = final_lnum + j,
orig_lnum = orig_lnum + j,
commit = commits[sha],
filename = filename,
previous_filename = previous_filename,
previous_sha = previous_sha,
}
end
end
--- @param data string
--- @return string[]
local function data_to_lines(data)
local lines = vim.split(data, '\n')
if lines[#lines] == '' then
lines[#lines] = nil
end
return lines
end
--- @param f fun(readline: fun(): string?))
--- @return fun(data: string?)
local function buffered_line_reader(f)
--- @param data string?
return coroutine.wrap(function(data)
if not data then
return
end
local data_lines = data_to_lines(data)
local i = 0
local function readline(peek)
if not data_lines[i + 1] then
data = coroutine.yield()
if not data then
return
end
data_lines = data_to_lines(data)
i = 0
end
if peek then
return data_lines[i + 1]
end
i = i + 1
return data_lines[i]
end
while readline(true) do
f(readline)
end
end)
end
--- @param obj Gitsigns.GitObj
--- @param contents? string[]
--- @param lnum? integer
--- @param revision? string
--- @param opts? Gitsigns.BlameOpts
--- @return table
function M.run_blame(obj, contents, lnum, revision, opts)
local ret = {} --- @type table
if not obj.object_name or obj.repo.abbrev_head == '' then
assert(contents, 'contents must be provided for untracked files')
-- As we support attaching to untracked files we need to return something if
-- the file isn't isn't tracked in git.
-- If abbrev_head is empty, then assume the repo has no commits
local commit = not_committed(obj.file)
for i in ipairs(contents) do
ret[i] = {
orig_lnum = 0,
final_lnum = i,
commit = commit,
filename = obj.file,
}
end
return ret
end
opts = opts or {}
local ignore_file = obj.repo.toplevel .. '/.git-blame-ignore-revs'
local commits = {} --- @type table
local reader = buffered_line_reader(function(readline)
incremental_iter(readline, commits, ret)
end)
--- @param data string?
local function on_stdout(_, data)
reader(data)
end
local contents_str = contents and table.concat(contents, '\n') or nil
local _, stderr = obj.repo:command({
'blame',
'--incremental',
contents and { '--contents', '-' },
opts.ignore_whitespace and '-w',
lnum and { '-L', lnum .. ',+1' },
opts.extra_opts,
uv.fs_stat(ignore_file) and { '--ignore-revs-file', ignore_file },
revision,
'--',
obj.file,
}, {
stdin = contents_str,
stdout = on_stdout,
ignore_error = true,
})
if stderr then
error_once('Error running git-blame: ' .. stderr)
return {}
end
return ret
end
return M
lewis6991-gitsigns.nvim-7010000/lua/gitsigns/git/cmd.lua 0000664 0000000 0000000 00000004002 14765572704 0022612 0 ustar 00root root 0000000 0000000 local async = require('gitsigns.async')
local log = require('gitsigns.debug.log')
local system = require('gitsigns.system').system
--- @type fun(cmd: string[], opts?: vim.SystemOpts): vim.SystemCompleted
local asystem = async.awrap(3, system)
--- @class Gitsigns.Git.JobSpec : vim.SystemOpts
--- @field ignore_error? boolean
--- @param x table
--- @return string[]
local function flatten(x)
local ret = {} --- @type string[]
for k, v in pairs(x) do
if type(k) == 'number' then
if type(v) == 'table' then
vim.list_extend(ret, flatten(v))
elseif type(v) == 'string' then
ret[#ret + 1] = v
end
end
end
return ret
end
--- @async
--- @param args table
--- @param spec? Gitsigns.Git.JobSpec
--- @return string[] stdout, string? stderr, integer code
local function git_command(args, spec)
spec = spec or {}
local cmd = flatten({
'git',
'--no-pager',
'--no-optional-locks',
'--literal-pathspecs',
'-c',
'gc.auto=0', -- Disable auto-packing which emits messages to stderr
args,
})
if spec.text == nil then
spec.text = true
end
-- Fix #895. Only needed for Nvim 0.9 and older
spec.clear_env = true
--- @type vim.SystemCompleted
local obj = asystem(cmd, spec)
if not spec.ignore_error and obj.code > 0 then
log.eprintf(
"Received exit code %d when running command\n'%s':\n%s",
obj.code,
table.concat(cmd, ' '),
obj.stderr
)
end
local stdout_lines = vim.split(obj.stdout or '', '\n')
if spec.text then
-- If stdout ends with a newline, then remove the final empty string after
-- the split
if stdout_lines[#stdout_lines] == '' then
stdout_lines[#stdout_lines] = nil
end
end
if log.verbose then
log.vprintf('%d lines:', #stdout_lines)
for i = 1, math.min(10, #stdout_lines) do
log.vprintf('\t%s', stdout_lines[i])
end
end
if obj.stderr == '' then
obj.stderr = nil
end
return stdout_lines, obj.stderr, obj.code
end
return git_command
lewis6991-gitsigns.nvim-7010000/lua/gitsigns/git/repo.lua 0000664 0000000 0000000 00000027541 14765572704 0023031 0 ustar 00root root 0000000 0000000 local async = require('gitsigns.async')
local git_command = require('gitsigns.git.cmd')
local log = require('gitsigns.debug.log')
local util = require('gitsigns.util')
local system = require('gitsigns.system').system
local check_version = require('gitsigns.git.version').check
local uv = vim.uv or vim.loop
--- @class Gitsigns.RepoInfo
--- @field gitdir string
--- @field toplevel string
--- @field detached boolean
--- @field abbrev_head string
--- @class Gitsigns.Repo : Gitsigns.RepoInfo
---
--- Username configured for the repo.
--- Needed for to determine "You" in current line blame.
--- @field username string
local M = {}
--- Run git command the with the objects gitdir and toplevel
--- @async
--- @param args table
--- @param spec? Gitsigns.Git.JobSpec
--- @return string[] stdout
--- @return string? stderr
--- @return integer code
function M:command(args, spec)
spec = spec or {}
spec.cwd = self.toplevel
return git_command({
'--git-dir',
self.gitdir,
self.detached and { '--work-tree', self.toplevel },
args,
}, spec)
end
--- @param base string?
--- @return string[]
function M:files_changed(base)
if base and base ~= ':0' then
local results = self:command({ 'diff', '--name-status', base })
for i, result in ipairs(results) do
results[i] = vim.split(result:gsub('\t', ' '), ' ', { plain = true })[2]
end
return results
end
local results = self:command({ 'status', '--porcelain', '--ignore-submodules' })
local ret = {} --- @type string[]
for _, line in ipairs(results) do
if line:sub(1, 2):match('^.M') then
ret[#ret + 1] = line:sub(4, -1)
end
end
return ret
end
--- @param encoding string
--- @return boolean
local function iconv_supported(encoding)
-- TODO(lewis6991): needs https://github.com/neovim/neovim/pull/21924
if vim.startswith(encoding, 'utf-16') or vim.startswith(encoding, 'utf-32') then
return false
end
return true
end
--- @async
--- Get version of file in the index, return array lines
--- @param object string
--- @param encoding? string
--- @return string[] stdout, string? stderr
function M:get_show_text(object, encoding)
local stdout, stderr = self:command({ 'show', object }, { text = false, ignore_error = true })
if encoding and encoding ~= 'utf-8' and iconv_supported(encoding) then
for i, l in ipairs(stdout) do
stdout[i] = vim.iconv(l, encoding, 'utf-8')
end
end
return stdout, stderr
end
--- @async
function M:update_abbrev_head()
local info, err = M.get_info(self.toplevel)
if not info then
log.eprintf('Could not get info for repo at %s: %s', self.gitdir, err or '')
return
end
self.abbrev_head = info.abbrev_head
end
--- @async
--- @private
--- @param info Gitsigns.RepoInfo
--- @return Gitsigns.Repo
local function new(info)
local self = setmetatable({}, { __index = M })
for k, v in
pairs(info --[[@as table]])
do
---@diagnostic disable-next-line:no-unknown
self[k] = v
end
self.username = self:command({ 'config', 'user.name' }, { ignore_error = true })[1]
return self
end
--- @type table
local repo_cache = setmetatable({}, { __mode = 'v' })
--- @async
--- @param dir string
--- @param gitdir? string
--- @param toplevel? string
--- @return Gitsigns.Repo?
function M.get(dir, gitdir, toplevel)
local info = M.get_info(dir, gitdir, toplevel)
if not info then
return
end
gitdir = info.gitdir
if not repo_cache[gitdir] then
repo_cache[gitdir] = { 1, new(info) }
else
repo_cache[gitdir][1] = repo_cache[gitdir][1] + 1
end
return repo_cache[gitdir][2]
end
function M:unref()
local gitdir = self.gitdir
local repo = repo_cache[gitdir]
if not repo then
-- Already reclaimed by GC
return
end
local refcount = repo[1]
if refcount <= 1 then
repo_cache[gitdir] = nil
else
repo_cache[gitdir][1] = refcount - 1
end
end
local has_cygpath = jit and jit.os == 'Windows' and vim.fn.executable('cygpath') == 1
--- @async
--- @generic S
--- @param path S
--- @return S
local function normalize_path(path)
if path and has_cygpath and not uv.fs_stat(path) then
-- If on windows and path isn't recognizable as a file, try passing it
-- through cygpath
--- @type string
path = async.await(3, system, { 'cygpath', '-aw', path }).stdout
end
return path
end
--- @async
--- @param gitdir? string
--- @param head_str string
--- @param cwd string
--- @return string
local function process_abbrev_head(gitdir, head_str, cwd)
if not gitdir or head_str ~= 'HEAD' then
return head_str
end
local short_sha = git_command({ 'rev-parse', '--short', 'HEAD' }, {
ignore_error = true,
cwd = cwd,
})[1] or ''
if log.debug_mode and short_sha ~= '' then
short_sha = 'HEAD'
end
if util.path_exists(gitdir .. '/rebase-merge') or util.path_exists(gitdir .. '/rebase-apply') then
return short_sha .. '(rebasing)'
end
return short_sha
end
--- @async
--- @param cwd string
--- @param gitdir? string
--- @param worktree? string
--- @return Gitsigns.RepoInfo? info, string? err
function M.get_info(cwd, gitdir, worktree)
-- Does git rev-parse have --absolute-git-dir, added in 2.13:
-- https://public-inbox.org/git/20170203024829.8071-16-szeder.dev@gmail.com/
local has_abs_gd = check_version(2, 13)
-- Wait for internal scheduler to settle before running command (#215)
async.schedule()
-- gitdir and worktree must be provided together from `man git`:
-- > Specifying the location of the ".git" directory using this option (or GIT_DIR environment
-- > variable) turns off the repository discovery that tries to find a directory with ".git"
-- > subdirectory (which is how the repository and the top-level of the working tree are
-- > discovered), and tells Git that you are at the top level of the working tree. If you are
-- > not at the top-level directory of the working tree, you should tell Git where the
-- > top-level of the working tree is, with the --work-tree= option (or GIT_WORK_TREE
-- > environment variable)
local stdout, stderr, code = git_command({
gitdir and worktree and {
'--git-dir',
gitdir,
'--work-tree',
worktree,
},
'rev-parse',
'--show-toplevel',
has_abs_gd and '--absolute-git-dir' or '--git-dir',
'--abbrev-ref',
'HEAD',
}, {
ignore_error = true,
cwd = worktree or cwd,
})
-- If the repo has no commits yet, rev-parse will fail. Ignore this error.
if code > 0 and stderr and stderr:match("fatal: ambiguous argument 'HEAD'") then
code = 0
end
if code > 0 then
return nil, string.format('got stderr: %s', stderr or '')
end
if #stdout < 3 then
return nil, string.format('incomplete stdout: %s', table.concat(stdout, '\n'))
end
local toplevel_r = assert(normalize_path(stdout[1]))
local gitdir_r = assert(normalize_path(stdout[2]))
if not has_abs_gd then
gitdir_r = assert(uv.fs_realpath(gitdir_r))
end
if gitdir and not worktree and gitdir ~= gitdir_r then
log.eprintf('expected gitdir to be %s, got %s', gitdir, gitdir_r)
end
return {
toplevel = toplevel_r,
gitdir = gitdir_r,
abbrev_head = process_abbrev_head(gitdir_r, assert(stdout[3]), cwd),
detached = toplevel_r and gitdir_r ~= toplevel_r .. '/.git',
}
end
--- @class (exact) Gitsigns.Repo.LsTree.Result
--- @field relpath string
--- @field mode_bits? string
--- @field object_name? string
--- @field object_type? 'blob'|'tree'|'commit'
--- @param path string
--- @param revision string
--- @return Gitsigns.Repo.LsTree.Result? info
--- @return string? err
function M:ls_tree(path, revision)
local results, stderr, code = self:command({
'-c',
'core.quotepath=off',
'ls-tree',
revision,
path,
}, { ignore_error = true })
if code > 0 then
return nil, stderr or tostring(code)
end
local info, relpath = unpack(vim.split(results[1], '\t'))
local mode_bits, object_type, object_name = unpack(vim.split(info, '%s+'))
return {
relpath = relpath,
mode_bits = mode_bits,
object_name = object_name,
object_type = object_type,
}
end
--- @class (exact) Gitsigns.Repo.LsFiles.Result
--- @field relpath? string nil if file is not in working tree
--- @field mode_bits? string
--- @field object_name? string nil if file is untracked
--- @field i_crlf? boolean (requires git version >= 2.9)
--- @field w_crlf? boolean (requires git version >= 2.9)
--- @field has_conflicts? true
--- @async
--- Get information about files in the index and the working tree
--- @param file string
--- @return Gitsigns.Repo.LsFiles.Result? info
--- @return string? err
function M:ls_files(file)
local has_eol = check_version(2, 9)
-- --others + --exclude-standard means ignored files won't return info, but
-- untracked files will. Unlike file_info_tree which won't return untracked
-- files.
local results, stderr, code = self:command({
'-c',
'core.quotepath=off',
'ls-files',
'--stage',
'--others',
'--exclude-standard',
has_eol and '--eol',
file,
}, { ignore_error = true })
-- ignore_error for the cases when we run:
-- git ls-files --others exists/nonexist
if
code > 0
and (
not stderr
or not stderr:match('^warning: could not open directory .*: No such file or directory')
)
then
return nil, stderr or tostring(code)
end
local relpath_idx = has_eol and 2 or 1
local result = {}
for _, line in ipairs(results) do
local parts = vim.split(line, '\t')
if #parts > relpath_idx then -- tracked file
local attrs = vim.split(parts[1], '%s+')
local stage = tonumber(attrs[3])
if stage <= 1 then
result.mode_bits = attrs[1]
result.object_name = attrs[2]
else
result.has_conflicts = true
end
if has_eol then
result.relpath = parts[3]
local eol = vim.split(parts[2], '%s+')
result.i_crlf = eol[1] == 'i/crlf'
result.w_crlf = eol[2] == 'w/crlf'
else
result.relpath = parts[2]
end
else -- untracked file
result.relpath = parts[relpath_idx]
end
end
return result
end
--- @param revision? string
--- @return boolean
function M.from_tree(revision)
return revision ~= nil and not vim.startswith(revision, ':')
end
--- @async
--- @param file string
--- @param revision? string
--- @return Gitsigns.Repo.LsFiles.Result? info
--- @return string? err
function M:file_info(file, revision)
if M.from_tree(revision) then
local info, err = self:ls_tree(file, assert(revision))
if err then
return nil, err
end
if info and info.object_type == 'blob' then
return {
relpath = info.relpath,
mode_bits = info.mode_bits,
object_name = info.object_name,
}
end
else
local info, err = self:ls_files(file)
if err then
return nil, err
end
return info
end
end
--- @param mode_bits string
--- @param object string
--- @param path string
--- @param add? boolean
function M:update_index(mode_bits, object, path, add)
self:command({
'update-index',
add and '--add',
'--cacheinfo',
('%s,%s,%s'):format(mode_bits, object, path),
})
end
--- @param path string
--- @param lines string[]
--- @return string
function M:hash_object(path, lines)
-- Concatenate the lines into a single string to ensure EOL
-- is respected
local text = table.concat(lines, '\n')
return self:command({ 'hash-object', '-w', '--path', path, '--stdin' }, { stdin = text })[1]
end
--- @async
--- @return string[]
function M:rename_status()
local out = self:command({
'diff',
'--name-status',
'--find-renames',
'--find-copies',
'--cached',
})
local ret = {} --- @type table
for _, l in ipairs(out) do
local parts = vim.split(l, '%s+')
if #parts == 3 then
local stat, orig_file, new_file = parts[1], parts[2], parts[3]
if vim.startswith(stat, 'R') then
ret[orig_file] = new_file
end
end
end
return ret
end
return M
lewis6991-gitsigns.nvim-7010000/lua/gitsigns/git/version.lua 0000664 0000000 0000000 00000004537 14765572704 0023551 0 ustar 00root root 0000000 0000000 local async = require('gitsigns.async')
local gs_config = require('gitsigns.config')
local log = require('gitsigns.debug.log')
local err = require('gitsigns.message').error
local system = require('gitsigns.system').system
local M = {}
--- @type fun(cmd: string[], opts?: vim.SystemOpts): vim.SystemCompleted
local asystem = async.awrap(3, system)
--- @class (exact) Gitsigns.Version
--- @field major integer
--- @field minor integer
--- @field patch integer
--- @param version string
--- @return Gitsigns.Version
local function parse_version(version)
assert(version:match('%d+%.%d+%.%w+'), 'Invalid git version: ' .. version)
local ret = {}
local parts = vim.split(version, '%.')
ret.major = assert(tonumber(parts[1]))
ret.minor = assert(tonumber(parts[2]))
if parts[3] == 'GIT' then
ret.patch = 0
else
local patch_ver = vim.split(parts[3], '-')
ret.patch = assert(tonumber(patch_ver[1]))
end
return ret
end
--- @async
local function set_version()
local version = gs_config.config._git_version
if version ~= 'auto' then
local ok, ret = pcall(parse_version, version)
if ok then
M.version = ret
else
err(ret --[[@as string]])
end
return
end
--- @type vim.SystemCompleted
local obj = asystem({ 'git', '--version' })
async.schedule()
local line = vim.split(obj.stdout or '', '\n')[1]
if not line then
err("Unable to detect git version as 'git --version' failed to return anything")
log.eprint(obj.stderr)
return
end
-- Sometime 'git --version' returns an empty string (#948)
if log.assert(type(line) == 'string', 'Unexpected output: ' .. line) then
return
end
if log.assert(vim.startswith(line, 'git version'), 'Unexpected output: ' .. line) then
return
end
local parts = vim.split(line, '%s+')
M.version = parse_version(parts[3])
end
--- @async
--- Usage: check_version{2,3}
--- @param major? integer
--- @param minor? integer
--- @param patch? integer
--- @return boolean
function M.check(major, minor, patch)
if not M.version then
set_version()
end
if not M.version then
return false
elseif not major or not minor then
return false
elseif M.version.major < major then
return false
elseif minor and M.version.minor < minor then
return false
elseif patch and M.version.patch < patch then
return false
end
return true
end
return M
lewis6991-gitsigns.nvim-7010000/lua/gitsigns/highlight.lua 0000664 0000000 0000000 00000016330 14765572704 0023242 0 ustar 00root root 0000000 0000000 local api = vim.api
--- @class Gitsigns.Hldef
--- @field [integer] string
--- @field desc string
--- @field hidden boolean
--- @field fg_factor number
local nvim10 = vim.fn.has('nvim-0.10') > 0
local M = {}
--- Use array of dict so we can iterate deterministically
--- Export for docgen
--- @type table[]
M.hls = {}
--- @param s string
--- @return string
local function capitalise(s)
return s:sub(1, 1):upper() .. s:sub(2)
end
---@param staged boolean
---@param kind ''|'Nr'|'Ln'|'Cul'
---@param ty 'add'|'change'|'delete'|'changedelete'|'topdelete'|'untracked'
---@return string? highlight
---@return Gitsigns.Hldef? spec
local function gen_hl(staged, kind, ty)
local cty = capitalise(ty)
local hl = ('GitSigns%s%s%s'):format(staged and 'Staged' or '', cty, kind)
if kind == 'Ln' and (ty == 'delete' or 'ty' == 'topdelete') then
return
end
local what --- @type string
if kind == 'Nr' then
what = 'number column (when `config.numhl == true`)'
elseif kind == 'Ln' then
what = 'buffer line (when `config.linehl == true`)'
elseif kind == 'Cul' then
what = 'the text (when the cursor is on the same line as the sign)'
else
what = 'the text'
end
local fallbacks --- @type string[]
if staged then
fallbacks = { ('GitSigns%s%s'):format(cty, kind) }
elseif ty == 'changedelete' then
fallbacks = { 'GitSignsChange' .. kind }
elseif ty == 'topdelete' then
fallbacks = { 'GitSignsDelete' .. kind }
elseif ty == 'untracked' then
fallbacks = { 'GitSignsAdd' .. kind }
elseif kind == 'Nr' then
fallbacks = {
('GitGutter%sLineNr'):format(cty),
('GitSigns%s'):format(cty),
}
elseif kind == 'Ln' then
fallbacks = {
('GitGutter%sLine'):format(cty),
('SignifyLine%s'):format(cty),
('Diff%s'):format(cty),
}
elseif kind == 'Cul' then
fallbacks = { ('GitSigns%s'):format(cty) }
else
fallbacks = {
('GitGutter%s'):format(cty),
('SignifySign%s'):format(cty),
ty == 'add' and 'DiffAddedGutter'
or ty == 'delete' and 'DiffRemovedGutter'
or ty == 'change' and 'DiffModifiedGutter'
or '???',
ty == 'add' and (nvim10 and 'Added' or 'diffAdded')
or ty == 'delete' and (nvim10 and 'Removed' or 'diffRemoved')
or ty == 'change' and (nvim10 and 'Changed' or 'diffChanged')
or '???',
('Diff%s'):format(cty),
}
end
local sty = (staged and 'staged ' or '')
return hl,
{
desc = ("Used for %s of '%s' %ssigns."):format(what, ty, sty),
fg_factor = staged and 0.5 or nil,
unpack(fallbacks),
}
end
for _, staged in ipairs({ false, true }) do
for _, kind in ipairs({ '', 'Nr', 'Ln', 'Cul' }) do
for _, ty in ipairs({ 'add', 'change', 'delete', 'changedelete', 'topdelete', 'untracked' }) do
local hl, spec = gen_hl(staged, kind, ty)
if hl then
table.insert(M.hls, { [hl] = spec })
end
end
end
end
vim.list_extend(M.hls, {
{
GitSignsAddPreview = {
'GitGutterAddLine',
'SignifyLineAdd',
'DiffAdd',
desc = 'Used for added lines in previews.',
},
},
{
GitSignsDeletePreview = {
'GitGutterDeleteLine',
'SignifyLineDelete',
'DiffDelete',
desc = 'Used for deleted lines in previews.',
},
},
{ GitSignsCurrentLineBlame = { 'NonText', desc = 'Used for current line blame.' } },
{
GitSignsAddInline = {
'TermCursor',
desc = 'Used for added word diff regions in inline previews.',
},
},
{
GitSignsDeleteInline = {
'TermCursor',
desc = 'Used for deleted word diff regions in inline previews.',
},
},
{
GitSignsChangeInline = {
'TermCursor',
desc = 'Used for changed word diff regions in inline previews.',
},
},
{
GitSignsAddLnInline = {
'GitSignsAddInline',
desc = 'Used for added word diff regions when `config.word_diff == true`.',
},
},
{
GitSignsChangeLnInline = {
'GitSignsChangeInline',
desc = 'Used for changed word diff regions when `config.word_diff == true`.',
},
},
{
GitSignsDeleteLnInline = {
'GitSignsDeleteInline',
desc = 'Used for deleted word diff regions when `config.word_diff == true`.',
},
},
-- Currently unused
-- {GitSignsAddLnVirtLn = {'GitSignsAddLn'}},
-- {GitSignsChangeVirtLn = {'GitSignsChangeLn'}},
-- {GitSignsAddLnVirtLnInLine = {'GitSignsAddLnInline', }},
-- {GitSignsChangeVirtLnInLine = {'GitSignsChangeLnInline', }},
{
GitSignsDeleteVirtLn = {
'GitGutterDeleteLine',
'SignifyLineDelete',
'DiffDelete',
desc = 'Used for deleted lines shown by inline `preview_hunk_inline()` or `show_deleted()`.',
},
},
{
GitSignsDeleteVirtLnInLine = {
'GitSignsDeleteLnInline',
desc = 'Used for word diff regions in lines shown by inline `preview_hunk_inline()` or `show_deleted()`.',
},
},
{
GitSignsVirtLnum = {
'GitSignsDeleteVirtLn',
desc = 'Used for line numbers in inline hunks previews.',
},
},
})
---@param name string
---@return table
local function get_hl(name)
return api.nvim_get_hl(0, { name = name, link = false })
end
--- @param hl_name string
--- @return boolean
local function is_hl_set(hl_name)
local hl = get_hl(hl_name)
local color = hl.fg
or hl.bg
or hl.reverse
or hl.ctermfg
or hl.ctermbg
or hl.cterm and hl.cterm.reverse
return color ~= nil
end
--- @param x? number
--- @param factor number
--- @return number?
local function cmul(x, factor)
if not x or factor == 1 then
return x
end
local r = math.floor(x / 2 ^ 16)
local x1 = x - (r * 2 ^ 16)
local g = math.floor(x1 / 2 ^ 8)
local b = math.floor(x1 - (g * 2 ^ 8))
return math.floor(
math.floor(r * factor) * 2 ^ 16 + math.floor(g * factor) * 2 ^ 8 + math.floor(b * factor)
)
end
local function dprintf(fmt, ...)
dprintf = require('gitsigns.debug.log').dprintf
dprintf(fmt, ...)
end
--- @param hl string
--- @param hldef Gitsigns.Hldef
local function derive(hl, hldef)
for _, d in ipairs(hldef) do
if is_hl_set(d) then
dprintf('Deriving %s from %s', hl, d)
if hldef.fg_factor then
local dh = get_hl(d)
api.nvim_set_hl(0, hl, {
default = true,
fg = cmul(dh.fg, hldef.fg_factor),
bg = dh.bg,
})
else
api.nvim_set_hl(0, hl, { default = true, link = d })
end
return
end
end
if hldef[1] and not hldef.fg_factor then
-- No fallback found which is set. Just link to the first fallback
-- if there are no modifiers
dprintf('Deriving %s from %s', hl, hldef[1])
api.nvim_set_hl(0, hl, { default = true, link = hldef[1] })
else
dprintf('Could not derive %s', hl)
end
end
--- Setup a GitSign* highlight by deriving it from other potentially present
--- highlights.
function M.setup_highlights()
for _, hlg in ipairs(M.hls) do
for hl, hldef in pairs(hlg) do
if is_hl_set(hl) then
-- Already defined
dprintf('Highlight %s is already defined', hl)
else
derive(hl, hldef)
end
end
end
end
function M.setup()
M.setup_highlights()
api.nvim_create_autocmd('ColorScheme', {
group = 'gitsigns',
callback = M.setup_highlights,
})
end
return M
lewis6991-gitsigns.nvim-7010000/lua/gitsigns/hunks.lua 0000664 0000000 0000000 00000037456 14765572704 0022437 0 ustar 00root root 0000000 0000000 local log = require('gitsigns.debug.log')
local util = require('gitsigns.util')
local config = require('gitsigns.config').config
local min, max = math.min, math.max
--- @alias Gitsigns.Hunk.Type
--- | "add"
--- | "change"
--- | "delete"
--- @class (exact) Gitsigns.Hunk.Node
--- @field start integer
--- @field count integer
--- @field lines string[]
--- @field no_nl_at_eof? true
--- @class (exact) Gitsigns.Hunk.Hunk
--- @field type Gitsigns.Hunk.Type
--- @field head string
--- @field added Gitsigns.Hunk.Node
--- @field removed Gitsigns.Hunk.Node
--- @field vend integer
--- @class (exact) Gitsigns.Hunk.Hunk_Public
--- @field type Gitsigns.Hunk.Type
--- @field head string
--- @field lines string[]
--- @field added Gitsigns.Hunk.Node
--- @field removed Gitsigns.Hunk.Node
--- @class gitsigns.hunks
local M = {}
--- @param old_start integer
--- @param old_count integer
--- @param new_start integer
--- @param new_count integer
--- @return Gitsigns.Hunk.Hunk
function M.create_hunk(old_start, old_count, new_start, new_count)
return {
removed = { start = old_start, count = old_count, lines = {} },
added = { start = new_start, count = new_count, lines = {} },
head = ('@@ -%d%s +%d%s @@'):format(
old_start,
old_count > 0 and ',' .. old_count or '',
new_start,
new_count > 0 and ',' .. new_count or ''
),
vend = new_start + math.max(new_count - 1, 0),
type = new_count == 0 and 'delete' or old_count == 0 and 'add' or 'change',
}
end
--- @param hunks Gitsigns.Hunk.Hunk[]
--- @param top integer
--- @param bot integer
--- @return Gitsigns.Hunk.Hunk?
function M.create_partial_hunk(hunks, top, bot)
local pretop, precount = top, bot - top + 1
local unused = 0
for _, h in ipairs(hunks) do
local added_in_hunk = h.added.count - h.removed.count
local added_in_range = 0
if h.added.start >= top and h.vend <= bot then
-- Range contains hunk
added_in_range = added_in_hunk
else
local added_above_bot = max(0, bot + 1 - (h.added.start + h.removed.count))
local added_above_top = max(0, top - (h.added.start + h.removed.count))
if h.added.start >= top and h.added.start <= bot then
-- Range top intersects hunk
added_in_range = added_above_bot
elseif h.vend >= top and h.vend <= bot then
-- Range bottom intersects hunk
added_in_range = added_in_hunk - added_above_top
pretop = pretop - added_above_top
elseif h.added.start <= top and h.vend >= bot then
-- Range within hunk
added_in_range = added_above_bot - added_above_top
pretop = pretop - added_above_top
else
-- No intersection
unused = unused + 1
end
if top > h.vend then
pretop = pretop - added_in_hunk
end
end
precount = precount - added_in_range
end
if unused == #hunks then
-- top and bot are not in any hunk
return
end
if precount == 0 then
pretop = pretop - 1
end
return M.create_hunk(pretop, precount, top, bot - top + 1)
end
--- @param hunk Gitsigns.Hunk.Hunk
--- @param fileformat string
--- @return string[]
function M.patch_lines(hunk, fileformat)
local lines = {} --- @type string[]
for _, l in ipairs(hunk.removed.lines) do
lines[#lines + 1] = '-' .. l
end
for _, l in ipairs(hunk.added.lines) do
lines[#lines + 1] = '+' .. l
end
if fileformat == 'dos' then
lines = util.strip_cr(lines)
end
return lines
end
local function tointeger(x)
return tonumber(x) --[[@as integer]]
end
--- @param line string
--- @return Gitsigns.Hunk.Hunk
function M.parse_diff_line(line)
local diffkey = vim.trim(vim.split(line, '@@', { plain = true })[2])
-- diffKey: "-xx,n +yy"
-- pre: {xx, n}, now: {yy}
--- @type string[], string[]
local pre, now = unpack(vim.tbl_map(
--- @param s string
--- @return string[]
function(s)
return vim.split(string.sub(s, 2), ',')
end,
vim.split(diffkey, ' ')
))
local hunk = M.create_hunk(
tointeger(pre[1]),
(tointeger(pre[2]) or 1),
tointeger(now[1]),
(tointeger(now[2]) or 1)
)
hunk.head = line
return hunk
end
--- @param hunk Gitsigns.Hunk.Hunk
--- @return integer
local function change_end(hunk)
if hunk.added.count == 0 then
-- delete
return hunk.added.start
elseif hunk.removed.count == 0 then
-- add
return hunk.added.start + hunk.added.count - 1
else
-- change
return hunk.added.start + min(hunk.added.count, hunk.removed.count) - 1
end
end
--- Calculate signs needed to be applied from a hunk for a specified line range.
--- @param hunk Gitsigns.Hunk.Hunk
--- @param next Gitsigns.Hunk.Hunk?
--- @param min_lnum integer
--- @param max_lnum integer
--- @param untracked? boolean
--- @return Gitsigns.Sign[]
local function calc_signs(hunk, next, min_lnum, max_lnum, untracked)
local start, added, removed = hunk.added.start, hunk.added.count, hunk.removed.count
if hunk.type == 'delete' and start == 0 then
if min_lnum <= 1 then
-- topdelete signs get placed one row lower
return { { type = 'topdelete', count = removed, lnum = 1 } }
else
return {}
end
end
--- @type Gitsigns.Sign[]
local signs = {}
local cend = change_end(hunk)
-- if this is a change hunk, mark changedelete if lines were removed or if the
-- next hunk removes on this hunks last line
local changedelete = hunk.type == 'change'
and (
removed > added
or (
(next and next.type == 'delete')
and hunk.added.start + hunk.added.count - 1 == next.added.start
)
)
for lnum = max(start, min_lnum), min(cend, max_lnum) do
signs[#signs + 1] = {
type = (changedelete and lnum == cend) and 'changedelete'
or untracked and 'untracked'
or hunk.type,
count = lnum == start and (hunk.type == 'add' and added or removed) or nil,
lnum = lnum,
}
end
if hunk.type == 'change' and added > removed and hunk.vend >= min_lnum and cend <= max_lnum then
for lnum = max(cend, min_lnum), min(hunk.vend, max_lnum) do
signs[#signs + 1] = {
type = 'add',
count = lnum == hunk.vend and (added - removed) or nil,
lnum = lnum,
}
end
end
return signs
end
--- Calculate signs needed to be applied from a hunk for a specified line range.
--- @param prev_hunk Gitsigns.Hunk.Hunk?
--- @param hunk Gitsigns.Hunk.Hunk
--- @param next_hunk Gitsigns.Hunk.Hunk?
--- @param min_lnum? integer
--- @param max_lnum? integer
--- @param untracked? boolean
--- @return Gitsigns.Sign[]
function M.calc_signs(prev_hunk, hunk, next_hunk, min_lnum, max_lnum, untracked)
if not (not untracked or hunk.type == 'add') then
log.eprintf('Invalid hunk with untracked=%s hunk="%s"', untracked, hunk.head)
return {}
end
min_lnum = math.max(1, min_lnum or 1)
max_lnum = max_lnum or math.huge
if not config._new_sign_calc then
return calc_signs(hunk, next_hunk, min_lnum, max_lnum, untracked)
end
local start, added, removed = hunk.added.start, hunk.added.count, hunk.removed.count
local cend = change_end(hunk)
local topdelete = hunk.type == 'delete'
and (start == 0 or prev_hunk and change_end(prev_hunk) == start)
and (not next_hunk or next_hunk.added.start ~= start + 1)
if topdelete and min_lnum == 1 then
min_lnum = 0
end
--- @type Gitsigns.Sign[]
local signs = {}
for lnum = max(start, min_lnum), min(cend, max_lnum) do
local changedelete = hunk.type == 'change'
and (removed > added and lnum == cend or prev_hunk and prev_hunk.added.start == 0)
signs[#signs + 1] = {
type = topdelete and 'topdelete'
or changedelete and 'changedelete'
or untracked and 'untracked'
or hunk.type,
count = lnum == start and (hunk.type == 'add' and added or removed) or nil,
lnum = lnum + (topdelete and 1 or 0),
}
end
if hunk.type == 'change' and added > removed and hunk.vend >= min_lnum and cend <= max_lnum then
for lnum = max(cend, min_lnum), min(hunk.vend, max_lnum) do
signs[#signs + 1] = {
type = 'add',
count = lnum == hunk.vend and (added - removed) or nil,
lnum = lnum,
}
end
end
return signs
end
--- @param relpath string
--- @param hunks Gitsigns.Hunk.Hunk[]
--- @param mode_bits string
--- @param invert? boolean
--- @return string[]
function M.create_patch(relpath, hunks, mode_bits, invert)
invert = invert or false
local results = {
string.format('diff --git a/%s b/%s', relpath, relpath),
'index 000000..000000 ' .. mode_bits,
'--- a/' .. relpath,
'+++ b/' .. relpath,
}
local offset = 0
for _, process_hunk in ipairs(hunks) do
local start, pre_count, now_count =
process_hunk.removed.start, process_hunk.removed.count, process_hunk.added.count
if process_hunk.type == 'add' then
start = start + 1
end
local pre_lines = process_hunk.removed.lines
local now_lines = process_hunk.added.lines
if invert then
pre_count, now_count = now_count, pre_count --- @type integer, integer
pre_lines, now_lines = now_lines, pre_lines --- @type string[], string[]
end
table.insert(
results,
string.format('@@ -%s,%s +%s,%s @@', start, pre_count, start + offset, now_count)
)
for _, l in ipairs(pre_lines) do
results[#results + 1] = '-' .. l
end
if process_hunk.removed.no_nl_at_eof then
results[#results + 1] = '\\ No newline at end of file'
end
for _, l in ipairs(now_lines) do
results[#results + 1] = '+' .. l
end
if process_hunk.added.no_nl_at_eof then
results[#results + 1] = '\\ No newline at end of file'
end
process_hunk.removed.start = start + offset
offset = offset + (now_count - pre_count)
end
return results
end
--- @param hunks Gitsigns.Hunk.Hunk[]
--- @return Gitsigns.StatusObj
function M.get_summary(hunks)
--- @type Gitsigns.StatusObj
local status = { added = 0, changed = 0, removed = 0 }
for _, hunk in ipairs(hunks or {}) do
if hunk.type == 'add' then
status.added = status.added + hunk.added.count
elseif hunk.type == 'delete' then
status.removed = status.removed + hunk.removed.count
elseif hunk.type == 'change' then
local add, remove = hunk.added.count, hunk.removed.count
local delta = min(add, remove)
status.changed = status.changed + delta
status.added = status.added + add - delta
status.removed = status.removed + remove - delta
end
end
return status
end
--- @param lnum integer
--- @param hunks Gitsigns.Hunk.Hunk[]?
--- @return Gitsigns.Hunk.Hunk?, integer?
function M.find_hunk(lnum, hunks)
for i, hunk in ipairs(hunks or {}) do
if lnum == 1 and hunk.added.start == 0 and hunk.vend == 0 then
return hunk, i
end
if hunk.added.start <= lnum and hunk.vend >= lnum then
return hunk, i
end
end
end
--- @param lnum integer
--- @param hunks Gitsigns.Hunk.Hunk[]
--- @param direction 'first'|'last'|'next'|'prev'
--- @param wrap boolean
--- @return integer?
function M.find_nearest_hunk(lnum, hunks, direction, wrap)
if direction == 'first' then
return 1
elseif direction == 'last' then
return #hunks
elseif direction == 'next' then
if hunks[1].added.start > lnum then
return 1
end
for i = #hunks, 1, -1 do
if hunks[i].added.start <= lnum then
if i + 1 <= #hunks and hunks[i + 1].added.start > lnum then
return i + 1
elseif wrap then
return 1
end
end
end
elseif direction == 'prev' then
if math.max(hunks[#hunks].vend) < lnum then
return #hunks
end
for i = 1, #hunks do
if lnum <= math.max(hunks[i].vend, 1) then
if i > 1 and math.max(hunks[i - 1].vend, 1) < lnum then
return i - 1
elseif wrap then
return #hunks
end
end
end
end
end
--- @param a Gitsigns.Hunk.Hunk[]?
--- @param b Gitsigns.Hunk.Hunk[]?
--- @return boolean
function M.compare_heads(a, b)
if (a == nil) ~= (b == nil) then
return true
elseif a and #a ~= #b then
return true
end
for i, ah in ipairs(a or {}) do
--- @diagnostic disable-next-line:need-check-nil
if b[i].head ~= ah.head then
return true
end
end
return false
end
--- @param a Gitsigns.Hunk.Hunk
--- @param b Gitsigns.Hunk.Hunk
--- @return boolean
local function compare_new(a, b)
if a.added.start ~= b.added.start then
return false
end
if a.added.count ~= b.added.count then
return false
end
for i = 1, a.added.count do
if a.added.lines[i] ~= b.added.lines[i] then
return false
end
end
return true
end
--- Return hunks in a using b's hunks as a filter. Only compare the 'new' section
--- of the hunk.
---
--- Eg. Given:
---
--- a = {
--- 1 = '@@ -24 +25,1 @@',
--- 2 = '@@ -32 +34,1 @@',
--- 3 = '@@ -37 +40,1 @@'
--- }
---
--- b = {
--- 1 = '@@ -26 +25,1 @@'
--- }
---
--- Since a[1] and b[1] introduce the same changes to the buffer (both have
--- +25,1), we exclude this hunk in the output so we return:
---
--- {
--- 1 = '@@ -32 +34,1 @@',
--- 2 = '@@ -37 +40,1 @@'
--- }
---
--- @param a Gitsigns.Hunk.Hunk[]
--- @param b Gitsigns.Hunk.Hunk[]
--- @return Gitsigns.Hunk.Hunk[]?
function M.filter_common(a, b)
if not a and not b then
return
end
a, b = a or {}, b or {}
local a_i = 1
local b_i = 1
--- @type Gitsigns.Hunk.Hunk[]
local ret = {}
-- Need an offset of 1 in order to process when we hit the end of either
-- a or b
for _ = 1, math.max(#a, #b) + 1 do
local a_h, b_h = a[a_i], b[b_i]
if not a_h then
-- Reached the end of a
break
end
if not b_h then
-- Reached the end of b, add remainder of a
for i = a_i, #a do
ret[#ret + 1] = a[i]
end
break
end
if a_h.added.start > b_h.added.start then
-- a pointer is ahead of b; increment b pointer
b_i = b_i + 1
elseif a_h.added.start < b_h.added.start then
-- b pointer is ahead of a; add a_h to ret and increment a pointer
ret[#ret + 1] = a_h
a_i = a_i + 1
else -- a_h.start == b_h.start
-- a_h and b_h start on the same line, if hunks have the same changes then
-- skip (filtered) otherwise add a_h to ret. Increment both hunk
-- pointers
-- TODO(lewis6991): Be smarter; if bh intercepts then break down ah.
if not compare_new(a_h, b_h) then
ret[#ret + 1] = a_h
end
a_i = a_i + 1
b_i = b_i + 1
end
end
return ret
end
--- @param hunk Gitsigns.Hunk.Hunk
--- @param fileformat string
--- @return Gitsigns.LineSpec
function M.linespec_for_hunk(hunk, fileformat)
local hls = {} --- @type [string, Gitsigns.HlMark[]][][]
local removed, added = hunk.removed.lines, hunk.added.lines
for _, spec in ipairs({
{ sym = '-', lines = removed, hl = 'GitSignsDeletePreview' },
{ sym = '+', lines = added, hl = 'GitSignsAddPreview' },
}) do
for _, l in ipairs(spec.lines) do
if fileformat == 'dos' then
l = l:gsub('\r$', '') --[[@as string]]
end
hls[#hls + 1] = {
{
spec.sym .. l,
{
{
hl_group = spec.hl,
end_row = 1, -- Highlight whole line
},
},
},
}
end
end
if config.diff_opts.internal then
local removed_regions, added_regions =
require('gitsigns.diff_int').run_word_diff(removed, added)
for _, region in ipairs(removed_regions) do
local i = region[1]
table.insert(hls[i][1][2], {
hl_group = 'GitSignsDeleteInline',
start_col = region[3],
end_col = region[4],
})
end
for _, region in ipairs(added_regions) do
local i = hunk.removed.count + region[1]
table.insert(hls[i][1][2], {
hl_group = 'GitSignsAddInline',
start_col = region[3],
end_col = region[4],
})
end
end
return hls
end
return M
lewis6991-gitsigns.nvim-7010000/lua/gitsigns/manager.lua 0000664 0000000 0000000 00000030311 14765572704 0022700 0 ustar 00root root 0000000 0000000 local async = require('gitsigns.async')
local log = require('gitsigns.debug.log')
local util = require('gitsigns.util')
local run_diff = require('gitsigns.diff')
local Hunks = require('gitsigns.hunks')
local Signs = require('gitsigns.signs')
local Status = require('gitsigns.status')
local debounce_trailing = require('gitsigns.debounce').debounce_trailing
local throttle_by_id = require('gitsigns.debounce').throttle_by_id
local cache = require('gitsigns.cache').cache
local config = require('gitsigns.config').config
local api = vim.api
local signs_normal --- @type Gitsigns.Signs
local signs_staged --- @type Gitsigns.Signs
--- @class gitsigns.manager
local M = {}
--- @param bufnr integer
--- @param signs Gitsigns.Signs
--- @param hunks Gitsigns.Hunk.Hunk[]
--- @param top integer
--- @param bot integer
--- @param clear? boolean
--- @param untracked boolean
--- @param filter? fun(line: integer):boolean
local function apply_win_signs0(bufnr, signs, hunks, top, bot, clear, untracked, filter)
if clear then
signs:remove(bufnr) -- Remove all signs
end
for i, hunk in ipairs(hunks or {}) do
--- @type Gitsigns.Hunk.Hunk?, Gitsigns.Hunk.Hunk?
local prev_hunk, next_hunk = hunks[i - 1], hunks[i + 1]
-- To stop the sign column width changing too much, if there are signs to be
-- added but none of them are visible in the window, then make sure to add at
-- least one sign. Only do this on the first call after an update when we all
-- the signs have been cleared.
if clear and i == 1 then
signs:add(
bufnr,
Hunks.calc_signs(prev_hunk, hunk, next_hunk, hunk.added.start, hunk.added.start, untracked),
filter
)
end
signs:add(bufnr, Hunks.calc_signs(prev_hunk, hunk, next_hunk, top, bot, untracked), filter)
if hunk.added.start > bot then
break
end
end
end
--- @param bufnr integer
--- @param top integer
--- @param bot integer
--- @param clear? boolean
local function apply_win_signs(bufnr, top, bot, clear)
local bcache = assert(cache[bufnr])
local untracked = bcache.git_obj.object_name == nil
apply_win_signs0(bufnr, signs_normal, bcache.hunks, top, bot, clear, untracked)
if signs_staged then
apply_win_signs0(
bufnr,
signs_staged,
bcache.hunks_staged,
top,
bot,
clear,
false,
function(lnum)
return not signs_normal:contains(bufnr, lnum)
end
)
end
end
--- @param blame table?
--- @param first integer
--- @param last_orig integer
--- @param last_new integer
local function on_lines_blame(blame, first, last_orig, last_new)
if not blame then
return
end
if last_new < last_orig then
util.list_remove(blame, last_new + 1, last_orig)
elseif last_new > last_orig then
util.list_insert(blame, last_orig + 1, last_new)
end
for i = first + 1, last_new do
blame[i] = nil
end
end
--- @param buf integer
--- @param first integer
--- @param last_orig integer
--- @param last_new integer
--- @return true?
function M.on_lines(buf, first, last_orig, last_new)
local bcache = cache[buf]
if not bcache then
log.dprint('Cache for buffer was nil. Detaching')
return true
end
on_lines_blame(bcache.blame, first, last_orig, last_new)
signs_normal:on_lines(buf, first, last_orig, last_new)
if signs_staged then
signs_staged:on_lines(buf, first, last_orig, last_new)
end
-- Signs in changed regions get invalidated so we need to force a redraw if
-- any signs get removed.
if bcache.hunks and signs_normal:contains(buf, first, last_new) then
-- Force a sign redraw on the next update (fixes #521)
bcache.force_next_update = true
end
if signs_staged then
if bcache.hunks_staged and signs_staged:contains(buf, first, last_new) then
-- Force a sign redraw on the next update (fixes #521)
bcache.force_next_update = true
end
end
M.update_debounced(buf)
end
local ns = api.nvim_create_namespace('gitsigns')
--- @param bufnr integer
--- @param row integer
local function apply_word_diff(bufnr, row)
-- Don't run on folded lines
if vim.fn.foldclosed(row + 1) ~= -1 then
return
end
local bcache = cache[bufnr]
if not bcache or not bcache.hunks then
return
end
local line = api.nvim_buf_get_lines(bufnr, row, row + 1, false)[1]
if not line then
-- Invalid line
return
end
local lnum = row + 1
local hunk = Hunks.find_hunk(lnum, bcache.hunks)
if not hunk then
-- No hunk at line
return
end
if hunk.added.count ~= hunk.removed.count then
-- Only word diff if added count == removed
return
end
local pos = lnum - hunk.added.start + 1
local added_line = hunk.added.lines[pos]
local removed_line = hunk.removed.lines[pos]
local _, added_regions = require('gitsigns.diff_int').run_word_diff(
{ removed_line },
{ added_line }
)
local cols = #line
for _, region in ipairs(added_regions) do
local rtype, scol, ecol = region[2], region[3] - 1, region[4] - 1
if ecol == scol then
-- Make sure region is at least 1 column wide so deletes can be shown
ecol = scol + 1
end
local hl_group = rtype == 'add' and 'GitSignsAddLnInline'
or rtype == 'change' and 'GitSignsChangeLnInline'
or 'GitSignsDeleteLnInline'
local opts = {
ephemeral = true,
priority = 1000,
}
if ecol > cols and ecol == scol + 1 then
-- delete on last column, use virtual text instead
opts.virt_text = { { ' ', hl_group } }
opts.virt_text_pos = 'overlay'
else
opts.end_col = ecol
opts.hl_group = hl_group
end
api.nvim_buf_set_extmark(bufnr, ns, row, scol, opts)
util.redraw({ buf = bufnr, range = { row, row + 1 } })
end
end
local ns_rm = api.nvim_create_namespace('gitsigns_removed')
local VIRT_LINE_LEN = 300
--- @param bufnr integer
local function clear_deleted(bufnr)
local marks = api.nvim_buf_get_extmarks(bufnr, ns_rm, 0, -1, {})
for _, mark in ipairs(marks) do
api.nvim_buf_del_extmark(bufnr, ns_rm, mark[1])
end
end
--- @param bufnr integer
--- @param nsd integer
--- @param hunk Gitsigns.Hunk.Hunk
local function show_deleted(bufnr, nsd, hunk)
local virt_lines = {} --- @type [string, string][][]
for i, line in ipairs(hunk.removed.lines) do
local vline = {} --- @type [string, string][]
local last_ecol = 1
if config.word_diff then
local regions = require('gitsigns.diff_int').run_word_diff(
{ hunk.removed.lines[i] },
{ hunk.added.lines[i] }
)
for _, region in ipairs(regions) do
local rline, scol, ecol = region[1], region[3], region[4]
if rline > 1 then
break
end
vline[#vline + 1] = { line:sub(last_ecol, scol - 1), 'GitSignsDeleteVirtLn' }
vline[#vline + 1] = { line:sub(scol, ecol - 1), 'GitSignsDeleteVirtLnInline' }
last_ecol = ecol
end
end
if #line > 0 then
vline[#vline + 1] = { line:sub(last_ecol, -1), 'GitSignsDeleteVirtLn' }
end
-- Add extra padding so the entire line is highlighted
local padding = string.rep(' ', VIRT_LINE_LEN - #line)
vline[#vline + 1] = { padding, 'GitSignsDeleteVirtLn' }
virt_lines[i] = vline
end
local topdelete = hunk.added.start == 0 and hunk.type == 'delete'
local row = topdelete and 0 or hunk.added.start - 1
api.nvim_buf_set_extmark(bufnr, nsd, row, -1, {
virt_lines = virt_lines,
-- TODO(lewis6991): Note virt_lines_above doesn't work on row 0 neovim/neovim#16166
virt_lines_above = hunk.type ~= 'delete' or topdelete,
})
end
--- @param bufnr integer
--- @param hunks? Gitsigns.Hunk.Hunk[]
local function update_show_deleted(bufnr, hunks)
clear_deleted(bufnr)
if config.show_deleted then
for _, hunk in ipairs(hunks or {}) do
show_deleted(bufnr, ns_rm, hunk)
end
end
end
--- @async
--- Ensure updates cannot be interleaved.
--- Since updates are asynchronous we need to make sure an update isn't performed
--- whilst another one is in progress. If this happens then schedule another
--- update after the current one has completed.
--- @param bufnr integer
M.update = throttle_by_id(function(bufnr)
local bcache = cache[bufnr]
if not bcache or not bcache:schedule() then
return
end
bcache.update_lock = true
local old_hunks, old_hunks_staged = bcache.hunks, bcache.hunks_staged
bcache.hunks, bcache.hunks_staged = nil, nil
local git_obj = bcache.git_obj
local file_mode = bcache.file_mode
if not bcache.compare_text or config._refresh_staged_on_update or file_mode then
if file_mode then
bcache.compare_text = util.file_lines(git_obj.file)
else
bcache.compare_text = git_obj:get_show_text()
end
if not bcache:schedule(true) then
return
end
end
local buftext = util.buf_lines(bufnr)
bcache.hunks = run_diff(bcache.compare_text, buftext)
if not bcache:schedule() then
return
end
local bufname = api.nvim_buf_get_name(bufnr)
local rev_is_index = not git_obj:from_tree()
if
config.signs_staged_enable
and not file_mode
and (rev_is_index or bufname:match('^fugitive://') or bufname:match('^gitsigns://'))
then
if not bcache.compare_text_head or config._refresh_staged_on_update then
-- When the revision is from the index, we compare against HEAD to
-- show the staged changes.
--
-- When showing a revision buffer (a buffer that represents the revision
-- of a specific file and does not have a corresponding file on disk), we
-- utilize the staged signs to represent the changes introduced in that
-- revision. Therefore we compare against the previous commit. Note there
-- should not be any normal signs for these buffers.
local staged_rev = rev_is_index and 'HEAD' or git_obj.revision .. '^'
bcache.compare_text_head = git_obj:get_show_text(staged_rev)
if not bcache:schedule(true) then
return
end
end
local hunks_head = run_diff(bcache.compare_text_head, buftext)
if not bcache:schedule() then
return
end
bcache.hunks_staged = Hunks.filter_common(hunks_head, bcache.hunks)
end
-- Note the decoration provider may have invalidated bcache.hunks at this
-- point
if
bcache.force_next_update
or Hunks.compare_heads(bcache.hunks, old_hunks)
or Hunks.compare_heads(bcache.hunks_staged, old_hunks_staged)
then
-- Apply signs to the window. Other signs will be added by the decoration
-- provider as they are drawn.
apply_win_signs(bufnr, vim.fn.line('w0'), vim.fn.line('w$'), true)
update_show_deleted(bufnr, bcache.hunks)
bcache.force_next_update = false
local summary = Hunks.get_summary(bcache.hunks)
summary.head = git_obj.repo.abbrev_head
Status:update(bufnr, summary)
end
bcache.update_lock = nil
end, true)
--- @param bufnr integer
--- @param keep_signs? boolean
function M.detach(bufnr, keep_signs)
if not keep_signs then
-- Remove all signs
signs_normal:remove(bufnr)
if signs_staged then
signs_staged:remove(bufnr)
end
end
end
function M.reset_signs()
-- Remove all signs
if signs_normal then
signs_normal:reset()
end
if signs_staged then
signs_staged:reset()
end
end
--- @param _cb 'win'
--- @param _winid integer
--- @param bufnr integer
--- @param topline integer
--- @param botline_guess integer
--- @return false?
local function on_win(_cb, _winid, bufnr, topline, botline_guess)
local bcache = cache[bufnr]
if not bcache or not bcache.hunks then
return false
end
local botline = math.min(botline_guess, api.nvim_buf_line_count(bufnr))
apply_win_signs(bufnr, topline + 1, botline + 1)
if not (config.word_diff and config.diff_opts.internal) then
return false
end
end
--- @param _cb 'line'
--- @param _winid integer
--- @param bufnr integer
--- @param row integer
local function on_line(_cb, _winid, bufnr, row)
apply_word_diff(bufnr, row)
end
function M.setup()
-- Calling this before any await calls will stop nvim's intro messages being
-- displayed
api.nvim_set_decoration_provider(ns, {
on_win = on_win,
on_line = on_line,
})
signs_normal = Signs.new(config.signs)
if config.signs_staged_enable then
signs_staged = Signs.new(config.signs_staged, 'staged')
end
M.update_debounced = debounce_trailing(config.update_debounce, async.create(1, M.update))
end
return M
lewis6991-gitsigns.nvim-7010000/lua/gitsigns/message.lua 0000664 0000000 0000000 00000001061 14765572704 0022712 0 ustar 00root root 0000000 0000000 local levels = vim.log.levels
local M = {}
--- @type fun(fmt: string, ...: string)
M.warn = vim.schedule_wrap(function(fmt, ...)
vim.notify(fmt:format(...), levels.WARN, { title = 'gitsigns' })
end)
--- @type fun(fmt: string, ...: string)
M.error = vim.schedule_wrap(function(fmt, ...)
vim.notify(fmt:format(...), vim.log.levels.ERROR, { title = 'gitsigns' })
end)
--- @type fun(fmt: string, ...: string)
M.error_once = vim.schedule_wrap(function(fmt, ...)
vim.notify_once(fmt:format(...), vim.log.levels.ERROR, { title = 'gitsigns' })
end)
return M
lewis6991-gitsigns.nvim-7010000/lua/gitsigns/nav.lua 0000664 0000000 0000000 00000007755 14765572704 0022072 0 ustar 00root root 0000000 0000000 local async = require('gitsigns.async')
local Hunks = require('gitsigns.hunks')
local cache = require('gitsigns.cache').cache
local Popup = require('gitsigns.popup')
local api = vim.api
--- @class Gitsigns.NavOpts
--- @field wrap boolean
--- @field foldopen boolean
--- @field navigation_message boolean
--- @field greedy boolean
--- @field preview boolean
--- @field count integer
--- @field target 'unstaged'|'staged'|'all'
--- @class gitsigns.nav
local M = {}
--- @param x string
--- @param word string
--- @return boolean
local function findword(x, word)
return string.find(x, '%f[%w_]' .. word .. '%f[^%w_]') ~= nil
end
--- @param opts? Gitsigns.NavOpts
--- @return Gitsigns.NavOpts
local function process_nav_opts(opts)
opts = opts or {}
-- show navigation message
if opts.navigation_message == nil then
opts.navigation_message = vim.o.shortmess:find('S') == nil
end
-- wrap around
if opts.wrap == nil then
opts.wrap = vim.o.wrapscan
end
if opts.foldopen == nil then
opts.foldopen = findword(vim.o.foldopen, 'search')
end
if opts.greedy == nil then
opts.greedy = true
end
if opts.count == nil then
opts.count = vim.v.count1
end
if opts.target == nil then
opts.target = 'unstaged'
end
return opts
end
--- @async
--- @param bufnr integer
--- @param target 'unstaged'|'staged'|'all'
--- @param greedy boolean
--- @return Gitsigns.Hunk.Hunk[]
local function get_nav_hunks(bufnr, target, greedy)
local bcache = assert(cache[bufnr])
local hunks_main = bcache:get_hunks(greedy, false) or {}
local hunks --- @type Gitsigns.Hunk.Hunk[]
if target == 'unstaged' then
hunks = hunks_main
else
local hunks_head = bcache:get_hunks(greedy, true) or {}
hunks_head = Hunks.filter_common(hunks_head, hunks_main) or {}
if target == 'all' then
hunks = hunks_main
vim.list_extend(hunks, hunks_head)
table.sort(hunks, function(h1, h2)
return h1.added.start < h2.added.start
end)
elseif target == 'staged' then
hunks = hunks_head
end
end
return hunks
end
--- @async
--- @param direction 'first'|'last'|'next'|'prev'
--- @param opts? Gitsigns.NavOpts
function M.nav_hunk(direction, opts)
opts = process_nav_opts(opts)
local bufnr = api.nvim_get_current_buf()
local bcache = cache[bufnr]
if not bcache then
return
end
local hunks = get_nav_hunks(bufnr, opts.target, opts.greedy)
if not hunks or vim.tbl_isempty(hunks) then
if opts.navigation_message then
api.nvim_echo({ { 'No hunks', 'WarningMsg' } }, false, {})
end
return
end
local line = api.nvim_win_get_cursor(0)[1]
local index --- @type integer?
local forwards = direction == 'next' or direction == 'last'
for _ = 1, opts.count do
index = Hunks.find_nearest_hunk(line, hunks, direction, opts.wrap)
if not index then
if opts.navigation_message then
api.nvim_echo({ { 'No more hunks', 'WarningMsg' } }, false, {})
end
local _, col = vim.fn.getline(line):find('^%s*')
api.nvim_win_set_cursor(0, { line, col })
return
end
line = forwards and hunks[index].added.start or hunks[index].vend
end
-- Handle topdelete
line = math.max(line, 1)
vim.cmd([[ normal! m' ]]) -- add current cursor position to the jump list
local _, col = vim.fn.getline(line):find('^%s*')
api.nvim_win_set_cursor(0, { line, col })
if opts.foldopen then
vim.cmd('silent! foldopen!')
end
-- schedule so the cursor change can settle, otherwise the popup might
-- appear in the old position
async.schedule()
local Preview = require('gitsigns.preview')
if opts.preview or Popup.is_open('hunk') ~= nil then
-- Close the popup in case one is open which will cause it to focus the
-- popup
Popup.close('hunk')
Preview.preview_hunk()
elseif Preview.has_preview_inline(bufnr) then
Preview.preview_hunk_inline()
end
if index and opts.navigation_message then
api.nvim_echo({ { ('Hunk %d of %d'):format(index, #hunks), 'None' } }, false, {})
end
end
return M
lewis6991-gitsigns.nvim-7010000/lua/gitsigns/popup.lua 0000664 0000000 0000000 00000017246 14765572704 0022445 0 ustar 00root root 0000000 0000000 local util = require('gitsigns.util')
local api = vim.api
local M = {}
--- @param bufnr integer
--- @param lines string[]
--- @return integer
local function bufnr_calc_width(bufnr, lines)
return api.nvim_buf_call(bufnr, function()
local width = 0
for _, l in ipairs(lines) do
if vim.fn.type(l) == vim.v.t_string then
local len = vim.fn.strdisplaywidth(l)
if len > width then
width = len
end
end
end
return width + 1 -- Add 1 for some miinor padding
end)
end
-- Expand height until all lines are visible to account for wrapped lines.
--- @param winid integer
--- @param nlines integer
--- @param border string
local function expand_height(winid, nlines, border)
local newheight = 0
local maxheight = vim.o.lines - vim.o.cmdheight - (border ~= '' and 2 or 0)
for _ = 0, 50 do
local winheight = api.nvim_win_get_height(winid)
if newheight > winheight then
-- Window must be max height
break
end
--- @type integer
local wd = api.nvim_win_call(winid, function()
return vim.fn.line('w$')
end)
if wd >= nlines then
break
end
newheight = winheight + nlines - wd
if newheight > maxheight then
api.nvim_win_set_height(winid, maxheight)
break
end
api.nvim_win_set_height(winid, newheight)
end
end
--- @class (exact) Gitsigns.HlMark
--- @field hl_group string
--- @field start_row? integer
--- @field start_col? integer
--- @field end_row? integer
--- @field end_col? integer
--- Each element represents a multi-line segment
--- @alias Gitsigns.LineSpec [string, string|Gitsigns.HlMark[]][][]
--- @param hlmarks Gitsigns.HlMark[]
--- @param row_offset integer
local function offset_hlmarks(hlmarks, row_offset)
for _, h in ipairs(hlmarks) do
h.start_row = (h.start_row or 0) + row_offset
if h.end_row then
h.end_row = h.end_row + row_offset
end
end
end
--- Partition the text and Gitsigns.HlMarks from a Gitsigns.LineSpec
--- @param fmt Gitsigns.LineSpec
--- @return string[]
--- @return Gitsigns.HlMark[]
local function partition_linesspec(fmt)
local lines = {} --- @type string[]
local ret = {} --- @type Gitsigns.HlMark[]
local row = 0
for _, section in ipairs(fmt) do
local section_text = {} --- @type string[]
local col = 0
for _, part in ipairs(section) do
local text, hls = part[1], part[2]
section_text[#section_text + 1] = text
local _, no_lines = text:gsub('\n', '')
local end_row = row + no_lines --- @type integer
local end_col = no_lines > 0 and 0 or col + #text --- @type integer
if type(hls) == 'string' then
ret[#ret + 1] = {
hl_group = hls,
start_row = row,
end_row = end_row,
start_col = col,
end_col = end_col,
}
else -- hl is Gitsigns.HlMark[]
offset_hlmarks(hls, row)
vim.list_extend(ret, hls)
end
row = end_row
col = end_col
end
local section_lines = vim.split(table.concat(section_text), '\n', { plain = true })
vim.list_extend(lines, section_lines)
row = row + 1
end
return lines, ret
end
--- @param id string|true
local function close_all_but(id)
for _, winid in ipairs(api.nvim_list_wins()) do
if vim.w[winid].gitsigns_preview ~= nil and vim.w[winid].gitsigns_preview ~= id then
pcall(api.nvim_win_close, winid, true)
end
end
end
--- @param id string
function M.close(id)
for _, winid in ipairs(api.nvim_list_wins()) do
if vim.w[winid].gitsigns_preview == id then
pcall(api.nvim_win_close, winid, true)
end
end
end
local ns = api.nvim_create_namespace('gitsigns_popup')
--- @param lines string[]
--- @param highlights Gitsigns.HlMark[]
--- @return integer bufnr
local function create_buf(lines, highlights)
local ts = vim.bo.tabstop
local bufnr = api.nvim_create_buf(false, true)
assert(bufnr, 'Failed to create buffer')
vim.bo[bufnr].bufhidden = 'wipe'
-- In case nvim was opened with '-M'
vim.bo[bufnr].modifiable = true
api.nvim_buf_set_lines(bufnr, 0, -1, true, lines)
vim.bo[bufnr].modifiable = false
-- Set tabstop before calculating the buffer width so that the correct width
-- is calculated
vim.bo[bufnr].tabstop = ts
for _, hl in ipairs(highlights) do
local ok, err = pcall(api.nvim_buf_set_extmark, bufnr, ns, hl.start_row, hl.start_col or 0, {
hl_group = hl.hl_group,
end_row = hl.end_row,
end_col = hl.end_col,
hl_eol = true,
})
if not ok then
error(vim.inspect(hl) .. '\n' .. err)
end
end
return bufnr
end
--- @param bufnr integer
--- @param opts table
--- @param id? string|true
--- @return integer winid
local function create_win(bufnr, opts, id)
id = id or true
-- Close any popups not matching id
close_all_but(id)
local lines = api.nvim_buf_get_lines(bufnr, 0, -1, true)
local opts1 = vim.deepcopy(opts or {})
opts1.height = opts1.height or #lines -- Guess, adjust later
opts1.width = opts1.width or bufnr_calc_width(bufnr, lines)
local winid = api.nvim_open_win(bufnr, false, opts1)
vim.w[winid].gitsigns_preview = id
if not opts.height then
expand_height(winid, #lines, opts.border)
end
if opts1.style == 'minimal' then
-- If 'signcolumn' = auto:1-2, then a empty signcolumn will appear and cause
-- line wrapping.
vim.wo[winid].signcolumn = 'no'
end
-- Close the popup when navigating to any window which is not the preview
-- itself.
local group = 'gitsigns_popup'
local group_id = api.nvim_create_augroup(group, {})
local old_cursor = api.nvim_win_get_cursor(0)
vim.keymap.set('n', 'q', 'quit!', { silent = true, buffer = bufnr })
api.nvim_create_autocmd({ 'CursorMoved', 'CursorMovedI' }, {
group = group_id,
callback = function()
local cursor = api.nvim_win_get_cursor(0)
-- Did the cursor REALLY change (neovim/neovim#12923)
if
(old_cursor[1] ~= cursor[1] or old_cursor[2] ~= cursor[2])
and api.nvim_get_current_win() ~= winid
then
-- Clear the augroup
api.nvim_create_augroup(group, {})
pcall(api.nvim_win_close, winid, true)
return
end
old_cursor = cursor
end,
})
api.nvim_create_autocmd('WinClosed', {
pattern = tostring(winid),
group = group_id,
callback = function()
-- Clear the augroup
api.nvim_create_augroup(group, {})
end,
})
-- update window position to follow the cursor when scrolling
api.nvim_create_autocmd('WinScrolled', {
buffer = api.nvim_get_current_buf(),
group = group_id,
callback = function()
if api.nvim_win_is_valid(winid) then
api.nvim_win_set_config(winid, opts1)
end
end,
})
return winid
end
--- @param lines_spec Gitsigns.LineSpec
--- @param opts table
--- @param id? string
--- @return integer winid, integer bufnr
function M.create(lines_spec, opts, id)
local lines, highlights = partition_linesspec(lines_spec)
local bufnr = create_buf(lines, highlights)
local winid = create_win(bufnr, opts, id)
return winid, bufnr
end
--- @param id string
--- @return integer? winid
function M.is_open(id)
for _, winid in ipairs(api.nvim_list_wins()) do
if vim.w[winid].gitsigns_preview == id then
return winid
end
end
end
--- @param id string
--- @return integer? winid
function M.focus_open(id)
local winid = M.is_open(id)
if winid then
api.nvim_set_current_win(winid)
end
return winid
end
--- @param fmt Gitsigns.LineSpec
--- @param info table
--- @return Gitsigns.LineSpec
function M.lines_format(fmt, info)
local ret = vim.deepcopy(fmt)
for _, line in ipairs(ret) do
for _, s in ipairs(line) do
s[1] = util.expand_format(s[1], info)
end
end
return ret
end
return M
lewis6991-gitsigns.nvim-7010000/lua/gitsigns/preview.lua 0000664 0000000 0000000 00000017150 14765572704 0022755 0 ustar 00root root 0000000 0000000 local cache = require('gitsigns.cache').cache
local config = require('gitsigns.config').config
local popup = require('gitsigns.popup')
local Hunks = require('gitsigns.hunks')
local api = vim.api
local current_buf = api.nvim_get_current_buf
--- @class gitsigns.preview
local M = {}
local ns_inline = api.nvim_create_namespace('gitsigns_preview_inline')
--- @async
--- @param bufnr integer
--- @param greedy? boolean
--- @return Gitsigns.Hunk.Hunk? hunk
--- @return boolean? staged
local function get_hunk_with_staged(bufnr, greedy)
local bcache = cache[bufnr]
if not bcache then
return
end
local hunk = bcache:get_hunk(nil, greedy, false)
if hunk then
return hunk, false
end
hunk = bcache:get_hunk(nil, greedy, true)
if hunk then
return hunk, true
end
end
local function clear_preview_inline(bufnr)
api.nvim_buf_clear_namespace(bufnr, ns_inline, 0, -1)
end
--- @param keys string
local function feedkeys(keys)
local cy = api.nvim_replace_termcodes(keys, true, false, true)
api.nvim_feedkeys(cy, 'n', false)
end
--- @param win integer
--- @param lnum integer
--- @param width integer
--- @return string str
--- @return {group:string, start:integer}[]? highlights
local function build_lno_str(win, lnum, width)
local has_col, statuscol =
pcall(api.nvim_get_option_value, 'statuscolumn', { win = win, scope = 'local' })
if has_col and statuscol and statuscol ~= '' then
local ok, data = pcall(api.nvim_eval_statusline, statuscol, {
winid = win,
use_statuscol_lnum = lnum,
highlights = true,
})
if ok then
return data.str, data.highlights
end
end
return string.format('%' .. width .. 'd', lnum)
end
--- @param bufnr integer
--- @param nsw integer
--- @param hunk Gitsigns.Hunk.Hunk
local function show_added(bufnr, nsw, hunk)
local start_row = hunk.added.start - 1
for offset = 0, hunk.added.count - 1 do
local row = start_row + offset
api.nvim_buf_set_extmark(bufnr, nsw, row, 0, {
end_row = row + 1,
hl_group = 'GitSignsAddPreview',
hl_eol = true,
priority = 1000,
})
end
local _, added_regions =
require('gitsigns.diff_int').run_word_diff(hunk.removed.lines, hunk.added.lines)
for _, region in ipairs(added_regions) do
local offset, rtype, scol, ecol = region[1] - 1, region[2], region[3] - 1, region[4] - 1
-- Special case to handle cr at eol in buffer but not in show text
local cr_at_eol_change = rtype == 'change' and vim.endswith(hunk.added.lines[offset + 1], '\r')
api.nvim_buf_set_extmark(bufnr, nsw, start_row + offset, scol, {
end_col = ecol,
strict = not cr_at_eol_change,
hl_group = rtype == 'add' and 'GitSignsAddInline'
or rtype == 'change' and 'GitSignsChangeInline'
or 'GitSignsDeleteInline',
priority = 1001,
})
end
end
--- @param bufnr integer
--- @param nsd integer
--- @param hunk Gitsigns.Hunk.Hunk
--- @param staged boolean?
--- @return integer winid
local function show_deleted_in_float(bufnr, nsd, hunk, staged)
local cwin = api.nvim_get_current_win()
local virt_lines = {} --- @type [string, string][][]
local textoff = vim.fn.getwininfo(cwin)[1].textoff --[[@as integer]]
for i = 1, hunk.removed.count do
local sc = build_lno_str(cwin, hunk.removed.start + i, textoff - 1)
virt_lines[i] = { { sc, 'LineNr' } }
end
local topdelete = hunk.added.start == 0 and hunk.type == 'delete'
local virt_lines_above = hunk.type ~= 'delete' or topdelete
local row = topdelete and 0 or hunk.added.start - 1
api.nvim_buf_set_extmark(bufnr, nsd, row, -1, {
virt_lines = virt_lines,
-- TODO(lewis6991): Note virt_lines_above doesn't work on row 0 neovim/neovim#16166
virt_lines_above = virt_lines_above,
virt_lines_leftcol = true,
})
local bcache = assert(cache[bufnr])
local pbufnr = api.nvim_create_buf(false, true)
local text = staged and bcache.compare_text_head or bcache.compare_text
api.nvim_buf_set_lines(pbufnr, 0, -1, false, assert(text))
local width = api.nvim_win_get_width(0)
local bufpos_offset = virt_lines_above and not topdelete and 1 or 0
local pwinid = api.nvim_open_win(pbufnr, false, {
relative = 'win',
win = cwin,
width = width - textoff,
height = hunk.removed.count,
anchor = 'SW',
bufpos = { hunk.added.start - bufpos_offset, 0 },
style = 'minimal',
})
vim.bo[pbufnr].filetype = vim.bo[bufnr].filetype
vim.bo[pbufnr].bufhidden = 'wipe'
vim.wo[pwinid].scrolloff = 0
api.nvim_win_call(pwinid, function()
-- Expand folds
vim.cmd('normal! ' .. 'zR')
-- Navigate to hunk
vim.cmd('normal! ' .. tostring(hunk.removed.start) .. 'gg')
vim.cmd('normal! ' .. vim.api.nvim_replace_termcodes('z', true, false, true))
end)
local last_lnum = api.nvim_buf_line_count(bufnr)
-- Apply highlights
for i = hunk.removed.start, hunk.removed.start + hunk.removed.count do
api.nvim_buf_set_extmark(pbufnr, nsd, i - 1, 0, {
hl_group = 'GitSignsDeleteVirtLn',
hl_eol = true,
end_row = i,
strict = i == last_lnum,
priority = 1000,
})
end
local removed_regions =
require('gitsigns.diff_int').run_word_diff(hunk.removed.lines, hunk.added.lines)
for _, region in ipairs(removed_regions) do
local start_row = (hunk.removed.start - 1) + (region[1] - 1)
local start_col = region[3] - 1
local end_col = region[4] - 1
api.nvim_buf_set_extmark(pbufnr, nsd, start_row, start_col, {
hl_group = 'GitSignsDeleteVirtLnInline',
end_col = end_col,
end_row = start_row,
priority = 1001,
})
end
return pwinid
end
local function noautocmd(f)
return function()
local ei = vim.o.eventignore
vim.o.eventignore = 'all'
f()
vim.o.eventignore = ei
end
end
--- Preview the hunk at the cursor position in a floating
--- window. If the preview is already open, calling this
--- will cause the window to get focus.
M.preview_hunk = noautocmd(function()
-- Wrap in noautocmd so vim-repeat continues to work
if popup.focus_open('hunk') then
return
end
local bufnr = current_buf()
local bcache = cache[bufnr]
if not bcache then
return
end
local hunk, index = bcache:get_cursor_hunk()
if not hunk then
return
end
--- @type Gitsigns.LineSpec
local preview_linespec = {
{ { 'Hunk of ', 'Title' } },
unpack(Hunks.linespec_for_hunk(hunk, vim.bo[bufnr].fileformat)),
}
local lines_spec = popup.lines_format(preview_linespec, {
hunk_no = index,
num_hunks = #cache[bufnr].hunks,
})
popup.create(lines_spec, config.preview_config, 'hunk')
end)
--- Preview the hunk at the cursor position inline in the buffer.
--- @async
function M.preview_hunk_inline()
local bufnr = current_buf()
local hunk, staged = get_hunk_with_staged(bufnr, true)
if not hunk then
return
end
clear_preview_inline(bufnr)
local winid --- @type integer
show_added(bufnr, ns_inline, hunk)
if hunk.removed.count > 0 then
winid = show_deleted_in_float(bufnr, ns_inline, hunk, staged)
end
api.nvim_create_autocmd({ 'CursorMoved', 'InsertEnter' }, {
buffer = bufnr,
desc = 'Clear gitsigns inline preview',
callback = function()
if winid then
pcall(api.nvim_win_close, winid, true)
end
clear_preview_inline(bufnr)
end,
once = true,
})
-- Virtual lines will be hidden if they are placed on the top row, so
-- automatically scroll the viewport.
if hunk.added.start <= 1 then
feedkeys(hunk.removed.count .. '')
end
end
--- @param bufnr integer
--- @return boolean
function M.has_preview_inline(bufnr)
return #api.nvim_buf_get_extmarks(bufnr, ns_inline, 0, -1, { limit = 1 }) > 0
end
return M
lewis6991-gitsigns.nvim-7010000/lua/gitsigns/qflist.lua 0000664 0000000 0000000 00000006351 14765572704 0022577 0 ustar 00root root 0000000 0000000 local async = require('gitsigns.async')
local cache = require('gitsigns.cache').cache
local git = require('gitsigns.git')
local run_diff = require('gitsigns.diff')
local config = require('gitsigns.config').config
local util = require('gitsigns.util')
local current_buf = vim.api.nvim_get_current_buf
--- @class gitsigns.qflist
local M = {}
--- @param buf_or_filename string|integer
--- @param hunks Gitsigns.Hunk.Hunk[]
--- @param qflist table[]
local function hunks_to_qflist(buf_or_filename, hunks, qflist)
for i, hunk in ipairs(hunks) do
qflist[#qflist + 1] = {
bufnr = type(buf_or_filename) == 'number' and buf_or_filename or nil,
filename = type(buf_or_filename) == 'string' and buf_or_filename or nil,
lnum = hunk.added.start,
text = string.format('Lines %d-%d (%d/%d)', hunk.added.start, hunk.vend, i, #hunks),
}
end
end
--- @async
--- @param target 'all'|'attached'|integer|nil
--- @return table[]?
local function buildqflist(target)
target = target or current_buf()
if target == 0 then
target = current_buf()
end
local qflist = {} --- @type table[]
if type(target) == 'number' then
local bufnr = target
if not cache[bufnr] then
return
end
hunks_to_qflist(bufnr, cache[bufnr].hunks, qflist)
elseif target == 'attached' then
for bufnr, bcache in pairs(cache) do
hunks_to_qflist(bufnr, assert(bcache.hunks), qflist)
end
elseif target == 'all' then
local repos = {} --- @type table
for _, bcache in pairs(cache) do
local repo = bcache.git_obj.repo
if not repos[repo.gitdir] then
repos[repo.gitdir] = repo
end
end
local repo = git.Repo.get(assert(vim.loop.cwd()))
if repo and not repos[repo.gitdir] then
repos[repo.gitdir] = repo
end
for _, r in pairs(repos) do
for _, f in ipairs(r:files_changed(config.base)) do
local f_abs = r.toplevel .. '/' .. f
local stat = vim.loop.fs_stat(f_abs)
if stat and stat.type == 'file' then
---@type string
local obj
if config.base and config.base ~= ':0' then
obj = config.base .. ':' .. f
else
obj = ':0:' .. f
end
local a = r:get_show_text(obj)
async.schedule()
local hunks = run_diff(a, util.file_lines(f_abs))
hunks_to_qflist(f_abs, hunks, qflist)
end
end
end
end
return qflist
end
--- Populate the quickfix list with hunks. Automatically opens the
--- quickfix window.
--- @async
--- @param target integer|'attached'|'all'
--- @param opts table?
function M.setqflist(target, opts)
opts = opts or {}
if opts.open == nil then
opts.open = true
end
local qfopts = {
items = buildqflist(target),
title = 'Hunks',
}
async.schedule()
if opts.use_location_list then
local nr = opts.nr or 0
vim.fn.setloclist(nr, {}, ' ', qfopts)
if opts.open then
if config.trouble then
require('trouble').open('loclist')
else
vim.cmd.lopen()
end
end
else
vim.fn.setqflist({}, ' ', qfopts)
if opts.open then
if config.trouble then
require('trouble').open('quickfix')
else
vim.cmd.copen()
end
end
end
end
return M
lewis6991-gitsigns.nvim-7010000/lua/gitsigns/repeat.lua 0000664 0000000 0000000 00000001131 14765572704 0022544 0 ustar 00root root 0000000 0000000 local M = {}
function M.mk_repeatable(fn)
return function(...)
local args = { ... }
local nargs = select('#', ...)
vim.go.operatorfunc = "v:lua.require'gitsigns.repeat'.repeat_action"
M.repeat_action = function()
fn(unpack(args, 1, nargs))
if vim.fn.exists('*repeat#set') == 1 then
local action = vim.api.nvim_replace_termcodes(
string.format('call %s()', vim.go.operatorfunc),
true,
true,
true
)
vim.fn['repeat#set'](action, -1)
end
end
vim.cmd('normal! g@l')
end
end
return M
lewis6991-gitsigns.nvim-7010000/lua/gitsigns/signs.lua 0000664 0000000 0000000 00000007132 14765572704 0022416 0 ustar 00root root 0000000 0000000 local api = vim.api
local config = require('gitsigns.config').config
--- @class Gitsigns.Sign
--- @field type Gitsigns.SignType
--- @field count? integer
--- @field lnum integer
--- @class Gitsigns.Signs
--- @field hls table
--- @field name string
--- @field group string
--- @field config table
--- @field ns integer
local M = {}
--- @param buf integer
--- @param last_orig? integer
--- @param last_new? integer
function M:on_lines(buf, _, last_orig, last_new)
-- Remove extmarks on line deletions to mimic
-- the behaviour of vim signs.
if last_orig > last_new then
self:remove(buf, last_new + 1, last_orig)
end
end
--- @param bufnr integer
--- @param start_lnum? integer
--- @param end_lnum? integer
function M:remove(bufnr, start_lnum, end_lnum)
if start_lnum then
api.nvim_buf_clear_namespace(bufnr, self.ns, start_lnum - 1, end_lnum or start_lnum)
else
api.nvim_buf_clear_namespace(bufnr, self.ns, 0, -1)
end
end
---@param bufnr integer
---@param signs Gitsigns.Sign[]
--- @param filter? fun(line: integer):boolean
function M:add(bufnr, signs, filter)
if not config.signcolumn and not config.numhl and not config.linehl then
-- Don't place signs if it won't show anything
return
end
for _, s in ipairs(signs) do
if (not filter or filter(s.lnum)) and not self:contains(bufnr, s.lnum) then
local cs = self.config[s.type]
local text = cs.text
if config.signcolumn and cs.show_count and s.count then
local count = s.count
local cc = config.count_chars
local count_char = cc[count] or cc['+'] or ''
text = cs.text .. count_char
end
local hls = self.hls[s.type]
local ok, err = pcall(api.nvim_buf_set_extmark, bufnr, self.ns, s.lnum - 1, 0, {
id = s.lnum,
sign_text = config.signcolumn and text or '',
priority = config.sign_priority,
sign_hl_group = hls.hl,
number_hl_group = config.numhl and hls.numhl or nil,
line_hl_group = config.linehl and hls.linehl or nil,
cursorline_hl_group = config.culhl and hls.culhl or nil,
})
if not ok and config.debug_mode then
vim.schedule(function()
error(table.concat({
string.format('Error placing extmark on line %d', s.lnum),
err,
}, '\n'))
end)
end
end
end
end
---@param bufnr integer
---@param start integer
---@param last? integer
---@return boolean
function M:contains(bufnr, start, last)
local marks = api.nvim_buf_get_extmarks(
bufnr,
self.ns,
{ start - 1, 0 },
{ last or start - 1, 0 },
{ limit = 1 }
)
return #marks > 0
end
function M:reset()
for _, buf in ipairs(api.nvim_list_bufs()) do
self:remove(buf)
end
end
-- local function capitalise_word(x: string): string
-- return x:sub(1, 1):upper()..x:sub(2)
-- end
function M.new(cfg, name)
local __FUNC__ = 'signs.init'
-- Add when config.signs.*.[hl,numhl,linehl] are removed
-- for _, t in ipairs {
-- 'add',
-- 'change',
-- 'delete',
-- 'topdelete',
-- 'changedelete',
-- 'untracked',
-- } do
-- local hl = string.format('GitSigns%s%s', name, capitalise_word(t))
-- obj.hls[t] = {
-- hl = hl,
-- numhl = hl..'Nr',
-- linehl = hl..'Ln',
-- }
-- end
local self = setmetatable({}, { __index = M })
self.config = cfg
self.hls = name == 'staged' and config.signs_staged or config.signs
self.group = 'gitsigns_signs_' .. (name or '')
self.ns = api.nvim_create_namespace(self.group)
return self
end
return M
lewis6991-gitsigns.nvim-7010000/lua/gitsigns/status.lua 0000664 0000000 0000000 00000002243 14765572704 0022614 0 ustar 00root root 0000000 0000000 local api = vim.api
--- @class (exact) Gitsigns.StatusObj
--- @field added? integer
--- @field removed? integer
--- @field changed? integer
--- @field head? string
--- @field root? string
--- @field gitdir? string
local M = {}
--- @param bufnr integer
local function autocmd_update(bufnr)
api.nvim_exec_autocmds('User', {
pattern = 'GitSignsUpdate',
modeline = false,
data = { buffer = bufnr },
})
end
--- @param bufnr integer
--- @param status Gitsigns.StatusObj
function M:update(bufnr, status)
if not api.nvim_buf_is_loaded(bufnr) then
return
end
local bstatus = vim.b[bufnr].gitsigns_status_dict
if bstatus then
status = vim.tbl_extend('force', bstatus, status)
end
vim.b[bufnr].gitsigns_head = status.head or ''
vim.b[bufnr].gitsigns_status_dict = status
local config = require('gitsigns.config').config
vim.b[bufnr].gitsigns_status = config.status_formatter(status)
autocmd_update(bufnr)
end
function M:clear(bufnr)
if not api.nvim_buf_is_loaded(bufnr) then
return
end
vim.b[bufnr].gitsigns_head = nil
vim.b[bufnr].gitsigns_status_dict = nil
vim.b[bufnr].gitsigns_status = nil
autocmd_update(bufnr)
end
return M
lewis6991-gitsigns.nvim-7010000/lua/gitsigns/system.lua 0000664 0000000 0000000 00000000721 14765572704 0022614 0 ustar 00root root 0000000 0000000 local log = require('gitsigns.debug.log')
local M = {}
-- compat module contains 0.11 fixes.
local system = vim.fn.has('nvim-0.11') == 1 and vim.system or require('gitsigns.system.compat')
--- @param cmd string[]
--- @param opts vim.SystemOpts
--- @param on_exit fun(obj: vim.SystemCompleted)
--- @return vim.SystemObj
function M.system(cmd, opts, on_exit)
local __FUNC__ = 'run_job'
log.dprint(unpack(cmd))
return system(cmd, opts, on_exit)
end
return M
lewis6991-gitsigns.nvim-7010000/lua/gitsigns/system/ 0000775 0000000 0000000 00000000000 14765572704 0022111 5 ustar 00root root 0000000 0000000 lewis6991-gitsigns.nvim-7010000/lua/gitsigns/system/compat.lua 0000664 0000000 0000000 00000020252 14765572704 0024100 0 ustar 00root root 0000000 0000000 local uv = vim.uv or vim.loop
--- @type vim.SystemSig
local SIG = {
HUP = 1, -- Hangup
INT = 2, -- Interrupt from keyboard
KILL = 9, -- Kill signal
TERM = 15, -- Termination signal
-- STOP = 17,19,23 -- Stop the process
}
--- @param handle uv.uv_handle_t?
local function close_handle(handle)
if handle and not handle:is_closing() then
handle:close()
end
end
--- @class Gitsigns.SystemObj : vim.SystemObj
--- @field private _state vim.SystemState
local SystemObj = {}
--- @param state vim.SystemState
--- @return vim.SystemObj
local function new_systemobj(state)
return setmetatable({
pid = state.pid,
_state = state,
}, { __index = SystemObj })
end
--- @param signal integer|string
function SystemObj:kill(signal)
self._state.handle:kill(signal)
end
--- @package
--- @param signal? vim.SystemSig
function SystemObj:_timeout(signal)
self._state.done = 'timeout'
self:kill(signal or SIG.TERM)
end
-- Use max 32-bit signed int value to avoid overflow on 32-bit systems. #31633
local MAX_TIMEOUT = 2 ^ 31 - 1
--- @param timeout? integer
--- @return vim.SystemCompleted
function SystemObj:wait(timeout)
local state = self._state
local done = vim.wait(timeout or state.timeout or MAX_TIMEOUT, function()
return state.result ~= nil
end, nil, true)
if not done then
-- Send sigkill since this cannot be caught
self:_timeout(SIG.KILL)
vim.wait(timeout or state.timeout or MAX_TIMEOUT, function()
return state.result ~= nil
end, nil, true)
end
return state.result
end
--- @param data string[]|string|nil
function SystemObj:write(data)
local stdin = self._state.stdin
if not stdin then
error('stdin has not been opened on this object')
end
if type(data) == 'table' then
for _, v in ipairs(data) do
stdin:write(v)
stdin:write('\n')
end
elseif type(data) == 'string' then
stdin:write(data)
elseif data == nil then
-- Shutdown the write side of the duplex stream and then close the pipe.
-- Note shutdown will wait for all the pending write requests to complete
-- TODO(lewis6991): apparently shutdown doesn't behave this way.
-- (https://github.com/neovim/neovim/pull/17620#discussion_r820775616)
stdin:write('', function()
stdin:shutdown(function()
close_handle(stdin)
end)
end)
end
end
--- @return boolean
function SystemObj:is_closing()
local handle = self._state.handle
return handle == nil or handle:is_closing() or false
end
--- @param output? uv.read_start.callback|false
--- @param text? boolean
--- @return uv.uv_stream_t? pipe
--- @return uv.read_start.callback? handler
--- @return string[]? data
local function setup_output(output, text)
if output == false then
return
end
local bucket --- @type string[]?
local handler --- @type uv.read_start.callback
if type(output) == 'function' then
handler = output
else
bucket = {}
handler = function(err, data)
if err then
error(err)
end
if text and data then
bucket[#bucket + 1] = data:gsub('\r\n', '\n')
else
bucket[#bucket + 1] = data
end
end
end
local pipe = assert(uv.new_pipe(false))
--- @type uv.read_start.callback
local function handler_with_close(err, data)
handler(err, data)
if data == nil then
pipe:read_stop()
pipe:close()
end
end
return pipe, handler_with_close, bucket
end
--- @param input? string|string[]|boolean
--- @return uv.uv_stream_t?
--- @return string|string[]?
local function setup_input(input)
if not input then
return
end
local towrite --- @type string|string[]?
if type(input) == 'string' or type(input) == 'table' then
towrite = input
end
return assert(uv.new_pipe(false)), towrite
end
--- @return table
local function base_env()
local env = vim.fn.environ() --- @type table
env['NVIM'] = vim.v.servername
env['NVIM_LISTEN_ADDRESS'] = nil
return env
end
--- uv.spawn will completely overwrite the environment
--- when we just want to modify the existing one, so
--- make sure to prepopulate it with the current env.
--- @param env? table
--- @param clear_env? boolean
--- @return string[]?
local function setup_env(env, clear_env)
if clear_env then
return env
end
--- @type table
env = vim.tbl_extend('force', base_env(), env or {})
local renv = {} --- @type string[]
for k, v in pairs(env) do
renv[#renv + 1] = string.format('%s=%s', k, tostring(v))
end
return renv
end
--- @param cmd string
--- @param opts uv.spawn.options
--- @param on_exit fun(code: integer, signal: integer)
--- @param on_error fun()
--- @return uv.uv_process_t, integer
local function spawn(cmd, opts, on_exit, on_error)
local handle, pid_or_err = uv.spawn(cmd, opts, on_exit)
if not handle then
on_error()
error(pid_or_err)
end
return handle, pid_or_err --[[@as integer]]
end
--- @param timeout integer
--- @param cb fun()
--- @return uv.uv_timer_t
local function timer_oneshot(timeout, cb)
local timer = assert(uv.new_timer())
timer:start(timeout, 0, function()
timer:stop()
timer:close()
cb()
end)
return timer
end
--- @param state vim.SystemState
--- @param code integer
--- @param signal integer
--- @param on_exit fun(result: vim.SystemCompleted)?
local function _on_exit(state, code, signal, on_exit)
close_handle(state.handle)
close_handle(state.stdin)
close_handle(state.timer)
local check = assert(uv.new_check())
check:start(function()
for _, pipe in pairs({ state.stdin, state.stdout, state.stderr }) do
if not pipe:is_closing() then
return
end
end
check:stop()
check:close()
if state.done == nil then
state.done = true
end
if (code == 0 or code == 1) and state.done == 'timeout' then
-- Unix: code == 0
-- Windows: code == 1
code = 124
end
local stdout_data = state.stdout_data
local stderr_data = state.stderr_data
state.result = {
code = code,
signal = signal,
stdout = stdout_data and table.concat(stdout_data) or nil,
stderr = stderr_data and table.concat(stderr_data) or nil,
}
if on_exit then
on_exit(state.result)
end
end)
end
--- @param state vim.SystemState
local function _on_error(state)
close_handle(state.handle)
close_handle(state.stdin)
close_handle(state.stdout)
close_handle(state.stderr)
close_handle(state.timer)
end
--- Run a system command
---
--- @param cmd string[]
--- @param opts? vim.SystemOpts
--- @param on_exit? fun(out: vim.SystemCompleted)
--- @return vim.SystemObj
local function system(cmd, opts, on_exit)
local __FUNC__ = 'run_job'
vim.validate({
cmd = { cmd, 'table' },
opts = { opts, 'table', true },
on_exit = { on_exit, 'function', true },
})
opts = opts or {}
local stdout, stdout_handler, stdout_data = setup_output(opts.stdout)
local stderr, stderr_handler, stderr_data = setup_output(opts.stderr)
local stdin, towrite = setup_input(opts.stdin)
--- @type vim.SystemState
local state = {
done = false,
cmd = cmd,
timeout = opts.timeout,
stdin = stdin,
stdout = stdout,
stdout_data = stdout_data,
stderr = stderr,
stderr_data = stderr_data,
}
--- @diagnostic disable-next-line:missing-fields
state.handle, state.pid = spawn(cmd[1], {
args = vim.list_slice(cmd, 2),
stdio = { stdin, stdout, stderr },
cwd = opts.cwd,
--- @diagnostic disable-next-line:assign-type-mismatch
env = setup_env(opts.env, opts.clear_env),
detached = opts.detach,
hide = true,
}, function(code, signal)
_on_exit(state, code, signal, on_exit)
end, function()
_on_error(state)
end)
if stdout and stdout_handler then
stdout:read_start(stdout_handler)
end
if stderr and stderr_handler then
stderr:read_start(stderr_handler)
end
local obj = new_systemobj(state)
if towrite then
obj:write(towrite)
obj:write(nil) -- close the stream
end
if opts.timeout then
state.timer = timer_oneshot(opts.timeout, function()
if state.handle and state.handle:is_active() then
--- @diagnostic disable-next-line: invisible
obj:_timeout()
end
end)
end
return obj
end
return system
lewis6991-gitsigns.nvim-7010000/lua/gitsigns/test.lua 0000664 0000000 0000000 00000001434 14765572704 0022251 0 ustar 00root root 0000000 0000000 local M = {}
local function eq(act, exp)
assert(act == exp, string.format('%s != %s', act, exp))
end
M._tests = {}
M._tests.expand_format = function()
local util = require('gitsigns.util')
assert('hello % world % 2021' == util.expand_format(' % % ', {
var1 = 'hello',
var2 = 'world',
var_time = 1616838297,
}))
end
M._tests.test_args = function()
local parse_args = require('gitsigns.cli.argparse').parse_args
local pos_args, named_args = parse_args('hello there key=value, key1="a b c"')
eq(pos_args[1], 'hello')
eq(pos_args[2], 'there')
eq(named_args.key, 'value,')
eq(named_args.key1, 'a b c')
pos_args, named_args = parse_args('base=HEAD~1 posarg')
eq(named_args.base, 'HEAD~1')
eq(pos_args[1], 'posarg')
end
return M
lewis6991-gitsigns.nvim-7010000/lua/gitsigns/util.lua 0000664 0000000 0000000 00000021350 14765572704 0022246 0 ustar 00root root 0000000 0000000 local M = {}
function M.path_exists(path)
return vim.loop.fs_stat(path) and true or false
end
local jit_os --- @type string
if jit then
jit_os = jit.os:lower()
end
local is_unix = false
if jit_os then
is_unix = jit_os == 'linux' or jit_os == 'osx' or jit_os == 'bsd'
else
local binfmt = package.cpath:match('%p[\\|/]?%p(%a+)')
is_unix = binfmt ~= 'dll'
end
--- @param file string
--- @return string
function M.dirname(file)
return file:match(string.format('^(.+)%s[^%s]+', M.path_sep, M.path_sep))
end
--- @param path string
--- @return string[]
function M.file_lines(path)
local file = assert(io.open(path, 'rb'))
local contents = file:read('*a')
file:close()
return vim.split(contents, '\n')
end
M.path_sep = package.config:sub(1, 1)
--- @param ... integer
--- @return string
local function make_bom(...)
local r = {}
---@diagnostic disable-next-line:no-unknown
for i, a in ipairs({ ... }) do
---@diagnostic disable-next-line:no-unknown
r[i] = string.char(a)
end
return table.concat(r)
end
local BOM_TABLE = {
['utf-8'] = make_bom(0xef, 0xbb, 0xbf),
['utf-16le'] = make_bom(0xff, 0xfe),
['utf-16'] = make_bom(0xfe, 0xff),
['utf-16be'] = make_bom(0xfe, 0xff),
['utf-32le'] = make_bom(0xff, 0xfe, 0x00, 0x00),
['utf-32'] = make_bom(0xff, 0xfe, 0x00, 0x00),
['utf-32be'] = make_bom(0x00, 0x00, 0xfe, 0xff),
['utf-7'] = make_bom(0x2b, 0x2f, 0x76),
['utf-1'] = make_bom(0xf7, 0x54, 0x4c),
}
---@param x string
---@param encoding string
---@return string
local function add_bom(x, encoding)
local bom = BOM_TABLE[encoding]
if bom then
return bom .. x
end
return x
end
--- @param bufnr integer
--- @return string[]
function M.buf_lines(bufnr)
-- nvim_buf_get_lines strips carriage returns if fileformat==dos
local buftext = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
local dos = vim.bo[bufnr].fileformat == 'dos'
if dos then
for i = 1, #buftext - 1 do
buftext[i] = buftext[i] .. '\r'
end
end
if vim.bo[bufnr].endofline then
-- Add CR to the last line
if dos then
buftext[#buftext] = buftext[#buftext] .. '\r'
end
buftext[#buftext + 1] = ''
end
if vim.bo[bufnr].bomb then
buftext[1] = add_bom(buftext[1], vim.bo[bufnr].fileencoding)
end
return buftext
end
--- @param buf integer
local function delete_alt(buf)
local alt = vim.api.nvim_buf_call(buf, function()
return vim.fn.bufnr('#')
end)
if alt ~= buf and alt ~= -1 then
pcall(vim.api.nvim_buf_delete, alt, { force = true })
end
end
--- @param bufnr integer
--- @param name string
function M.buf_rename(bufnr, name)
vim.api.nvim_buf_set_name(bufnr, name)
delete_alt(bufnr)
end
--- @param events string[]
--- @param f fun()
function M.noautocmd(events, f)
local ei = vim.o.eventignore
vim.o.eventignore = table.concat(events, ',')
f()
vim.o.eventignore = ei
end
--- @param bufnr integer
--- @param start_row integer
--- @param end_row integer
--- @param lines string[]
function M.set_lines(bufnr, start_row, end_row, lines)
if vim.bo[bufnr].fileformat == 'dos' then
lines = M.strip_cr(lines)
end
if start_row == 0 and end_row == -1 then
if lines[#lines] == '' then
lines = vim.deepcopy(lines)
lines[#lines] = nil
else
vim.bo[bufnr].eol = false
end
end
vim.api.nvim_buf_set_lines(bufnr, start_row, end_row, false, lines)
end
--- @return string
function M.tmpname()
if is_unix then
return os.tmpname()
end
return vim.fn.tempname()
end
--- @param time number
--- @param divisor integer
--- @param time_word string
--- @return string
local function to_relative_string(time, divisor, time_word)
local num = math.floor(time / divisor)
if num > 1 then
time_word = time_word .. 's'
end
return num .. ' ' .. time_word .. ' ago'
end
--- @param timestamp number
--- @return string
function M.get_relative_time(timestamp)
local current_timestamp = os.time()
local elapsed = current_timestamp - timestamp
if elapsed == 0 then
return 'a while ago'
end
local minute_seconds = 60
local hour_seconds = minute_seconds * 60
local day_seconds = hour_seconds * 24
local month_seconds = day_seconds * 30
local year_seconds = month_seconds * 12
if elapsed < minute_seconds then
return to_relative_string(elapsed, 1, 'second')
elseif elapsed < hour_seconds then
return to_relative_string(elapsed, minute_seconds, 'minute')
elseif elapsed < day_seconds then
return to_relative_string(elapsed, hour_seconds, 'hour')
elseif elapsed < month_seconds then
return to_relative_string(elapsed, day_seconds, 'day')
elseif elapsed < year_seconds then
return to_relative_string(elapsed, month_seconds, 'month')
else
return to_relative_string(elapsed, year_seconds, 'year')
end
end
--- @param opts vim.api.keyset.redraw
function M.redraw(opts)
if vim.fn.has('nvim-0.10') == 1 then
vim.api.nvim__redraw(opts)
else
vim.api.nvim__buf_redraw_range(opts.buf, opts.range[1], opts.range[2])
end
end
--- @param xs string[]
--- @return boolean
local function is_dos(xs)
-- Do not check CR at EOF
for i = 1, #xs - 1 do
if xs[i]:sub(-1) ~= '\r' then
return false
end
end
return true
end
--- Strip '\r' from the EOL of each line only if all lines end with '\r'
--- @param xs0 string[]
--- @return string[]
function M.strip_cr(xs0)
if not is_dos(xs0) then
-- don't strip, return early
return xs0
end
-- all lines end with '\r', need to strip
local xs = vim.deepcopy(xs0)
for i = 1, #xs do
xs[i] = xs[i]:sub(1, -2)
end
return xs
end
--- @param base? string
--- @return string?
function M.norm_base(base)
if base == ':0' then
return
end
if base and base:sub(1, 1):match('[~\\^]') then
base = 'HEAD' .. base
end
return base
end
function M.emptytable()
return setmetatable({}, {
---@param t table
---@param k any
---@return any
__index = function(t, k)
t[k] = {}
return t[k]
end,
})
end
local function expand_date(fmt, time)
if fmt == '%R' then
return M.get_relative_time(time)
end
return os.date(fmt, time)
end
---@param fmt string
---@param info table
---@return string
function M.expand_format(fmt, info)
local ret = {} --- @type string[]
for _ = 1, 20 do -- loop protection
-- Capture or
local scol, ecol, match, key, time_fmt = fmt:find('(<([^:>]+):?([^>]*)>)')
if not match then
break
end
--- @cast key string
ret[#ret + 1], fmt = fmt:sub(1, scol - 1), fmt:sub(ecol + 1)
local v = info[key]
if v then
if type(v) == 'table' then
v = table.concat(v, '\n')
end
if vim.endswith(key, '_time') then
if time_fmt == '' then
time_fmt = '%Y-%m-%d'
end
v = expand_date(time_fmt, v)
end
match = tostring(v)
end
ret[#ret + 1] = match
end
ret[#ret + 1] = fmt
return table.concat(ret, '')
end
--- @param buf string
--- @return boolean
function M.bufexists(buf)
--- @diagnostic disable-next-line:param-type-mismatch
return vim.fn.bufexists(buf) == 1
end
--- @param x Gitsigns.BlameInfo
--- @return Gitsigns.BlameInfoPublic
function M.convert_blame_info(x)
--- @type Gitsigns.BlameInfoPublic
local ret = vim.tbl_extend('error', x, x.commit)
ret.commit = nil
return ret
end
--- Efficiently remove items from middle of a list a list.
---
--- Calling table.remove() in a loop will re-index the tail of the table on
--- every iteration, instead this function will re-index the table exactly
--- once.
---
--- Based on https://stackoverflow.com/questions/12394841/safely-remove-items-from-an-array-table-while-iterating/53038524#53038524
---
---@param t any[]
---@param first integer
---@param last integer
function M.list_remove(t, first, last)
local n = table.maxn(t)
for i = 0, n - first do
t[first + i] = t[last + 1 + i]
t[last + 1 + i] = nil
end
end
--- Efficiently insert items into the middle of a list.
---
--- Calling table.insert() in a loop will re-index the tail of the table on
--- every iteration, instead this function will re-index the table exactly
--- once.
---
--- Based on https://stackoverflow.com/questions/12394841/safely-remove-items-from-an-array-table-while-iterating/53038524#53038524
---
---@param t any[]
---@param first integer
---@param last integer
---@param v any
function M.list_insert(t, first, last, v)
local n = table.maxn(t)
-- Shift table forward
for i = n - first, 0, -1 do
t[last + 1 + i] = t[first + i]
end
-- Fill in new values
for i = first, last do
t[i] = v
end
end
--- Run a function once and ignore subsequent calls
--- @generic F: function
--- @param fn F
--- @return F
function M.once(fn)
local called = false
return function(...)
if called then
return
end
called = true
return fn(...)
end
end
return M
lewis6991-gitsigns.nvim-7010000/lua/gitsigns/watcher.lua 0000664 0000000 0000000 00000010712 14765572704 0022726 0 ustar 00root root 0000000 0000000 local api = vim.api
local uv = vim.loop
local async = require('gitsigns.async')
local log = require('gitsigns.debug.log')
local util = require('gitsigns.util')
local Status = require('gitsigns.status')
local cache = require('gitsigns.cache').cache
local config = require('gitsigns.config').config
local throttle_by_id = require('gitsigns.debounce').throttle_by_id
local debounce_trailing = require('gitsigns.debounce').debounce_trailing
local dprint = log.dprint
local dprintf = log.dprintf
--- @param bufnr integer
--- @param old_relpath? string
local function handle_moved(bufnr, old_relpath)
local bcache = assert(cache[bufnr])
local git_obj = bcache.git_obj
git_obj.orig_relpath = assert(git_obj.orig_relpath or old_relpath)
local new_name = git_obj.repo:rename_status()[git_obj.orig_relpath]
if new_name then
dprintf('File moved to %s', new_name)
git_obj.relpath = new_name
git_obj.file = git_obj.repo.toplevel .. '/' .. new_name
elseif git_obj.orig_relpath then
local orig_file = git_obj.repo.toplevel .. util.path_sep .. git_obj.orig_relpath
if not git_obj.repo:file_info(orig_file, git_obj.revision) then
return
end
--- File was moved in the index, but then reset
dprintf('Moved file reset')
git_obj.relpath = git_obj.orig_relpath
git_obj.orig_relpath = nil
else
-- File removed from index, do nothing
return
end
git_obj.file = git_obj.repo.toplevel .. util.path_sep .. git_obj.relpath
bcache.file = git_obj.file
git_obj:refresh()
if not bcache:schedule() then
return
end
local bufexists = util.bufexists(bcache.file)
local old_name = api.nvim_buf_get_name(bufnr)
if not bufexists then
-- Do not trigger BufFilePre/Post
-- TODO(lewis6991): figure out how to avoid reattaching without
-- disabling all autocommands.
util.noautocmd({ 'BufFilePre', 'BufFilePost' }, function()
util.buf_rename(bufnr, bcache.file)
end)
end
local msg = bufexists and 'Cannot rename' or 'Renamed'
dprintf('%s buffer %d from %s to %s', msg, bufnr, old_name, bcache.file)
end
--- @async
--- @param bufnr integer
local function watcher_handler0(bufnr)
local __FUNC__ = 'watcher_handler'
local bcache = cache[bufnr]
if not bcache then
return
end
-- Avoid cache hit for detached buffer
-- ref: https://github.com/lewis6991/gitsigns.nvim/issues/956
if not bcache:schedule() then
dprint('buffer invalid (1)')
return
end
local git_obj = bcache.git_obj
git_obj.repo:update_abbrev_head()
if not bcache:schedule() then
dprint('buffer invalid (2)')
return
end
Status:update(bufnr, { head = git_obj.repo.abbrev_head })
local was_tracked = git_obj.object_name ~= nil
local old_relpath = git_obj.relpath
git_obj:refresh()
if not bcache:schedule() then
dprint('buffer invalid (3)')
return
end
if config.watch_gitdir.follow_files and was_tracked and not git_obj.object_name then
-- File was tracked but is no longer tracked. Must of been removed or
-- moved. Check if it was moved and switch to it.
handle_moved(bufnr, old_relpath)
if not bcache:schedule() then
dprint('buffer invalid (4)')
return
end
end
cache[bufnr]:invalidate(true)
require('gitsigns.manager').update(bufnr)
end
--- Debounce to:
--- - wait for all changes to the gitdir to complete.
--- Throttle to:
--- - ensure handler is only triggered once per git operation.
--- - prevent updates to the same buffer from interleaving as the handler is
--- async.
local watcher_handler =
debounce_trailing(200, async.create(1, throttle_by_id(watcher_handler0, true)), 1)
--- vim.inspect but on one line
--- @param x any
--- @return string
local function inspect(x)
return vim.inspect(x, { indent = '', newline = ' ' })
end
local M = {}
--- @param bufnr integer
--- @param gitdir string
--- @return uv.uv_fs_event_t
function M.watch_gitdir(bufnr, gitdir)
dprintf('Watching git dir')
local w = assert(uv.new_fs_event())
w:start(gitdir, {}, function(err, filename, events)
local __FUNC__ = 'watcher_cb'
if err then
dprintf('Git dir update error: %s', err)
return
end
-- The luv docs say filename is passed as a string but it has been observed
-- to sometimes be nil.
-- https://github.com/lewis6991/gitsigns.nvim/issues/848
if not filename then
log.eprint('No filename')
return
end
dprintf("Git dir update: '%s' %s", filename, inspect(events))
watcher_handler(bufnr)
end)
return w
end
return M
lewis6991-gitsigns.nvim-7010000/release-please-config.json 0000664 0000000 0000000 00000000445 14765572704 0023205 0 ustar 00root root 0000000 0000000 {
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
"release-type": "simple",
"include-component-in-tag": false,
"bump-minor-pre-major": true,
"packages": {
".": {
"extra-files": [
"gen_help.lua"
]
}
}
}
lewis6991-gitsigns.nvim-7010000/test/ 0000775 0000000 0000000 00000000000 14765572704 0017134 5 ustar 00root root 0000000 0000000 lewis6991-gitsigns.nvim-7010000/test/actions_spec.lua 0000664 0000000 0000000 00000022015 14765572704 0022311 0 ustar 00root root 0000000 0000000 local helpers = require('test.gs_helpers')
local setup_gitsigns = helpers.setup_gitsigns
local feed = helpers.feed
local test_file = helpers.test_file
local edit = helpers.edit
local check = helpers.check
local exec_lua = helpers.exec_lua
local fn = helpers.fn
local system = fn.system
local test_config = helpers.test_config
local clear = helpers.clear
local setup_test_repo = helpers.setup_test_repo
local eq = helpers.eq
local expectf = helpers.expectf
helpers.env()
--- @param exp_hunks string[]
local function expect_hunks(exp_hunks)
expectf(function()
--- @type table[]
local hunks = exec_lua("return require('gitsigns').get_hunks()")
if #exp_hunks ~= #hunks then
local msg = {} --- @type string[]
msg[#msg + 1] = ''
msg[#msg + 1] = string.format(
'Number of hunks do not match. Expected: %d, passed in: %d',
#exp_hunks,
#hunks
)
msg[#msg + 1] = '\nExpected hunks:'
for _, h in ipairs(exp_hunks) do
msg[#msg + 1] = h
end
msg[#msg + 1] = '\nPassed in hunks:'
for _, h in ipairs(hunks) do
msg[#msg + 1] = h.head
end
error(table.concat(msg, '\n'))
end
for i, hunk in ipairs(hunks) do
eq(exp_hunks[i], hunk.head)
end
end)
end
local delay = 10
local function command(cmd)
helpers.sleep(delay)
helpers.api.nvim_command(cmd)
-- Flaky tests, add a large delay between commands.
-- Flakiness is due to actions being async and problems occur when an action
-- is run while another action or update is running.
-- Must wait for actions and updates to finish.
helpers.sleep(delay)
end
local function retry(f)
local orig_delay = delay
local ok, err --- @type boolean, string?
for _ = 1, 20 do
--- @type boolean, string?
ok, err = pcall(f)
if ok then
return
end
delay = delay * 1.6
print('failed, retrying with delay', delay)
end
if err then
delay = orig_delay
error(err)
end
end
describe('actions', function()
local orig_it = it
local function it(desc, f)
orig_it(desc, function()
retry(f)
end)
end
before_each(function()
clear()
command('cd ' .. system({ 'dirname', os.tmpname() }))
setup_gitsigns(test_config)
end)
it('works with commands', function()
setup_test_repo()
edit(test_file)
feed('jjjccEDIT')
check({
status = { head = 'main', added = 0, changed = 1, removed = 0 },
signs = { changed = 1 },
})
command('Gitsigns stage_hunk')
check({
status = { head = 'main', added = 0, changed = 0, removed = 0 },
signs = {},
})
command('Gitsigns undo_stage_hunk')
check({
status = { head = 'main', added = 0, changed = 1, removed = 0 },
signs = { changed = 1 },
})
command('Gitsigns stage_hunk')
check({
status = { head = 'main', added = 0, changed = 0, removed = 0 },
signs = {},
})
command('Gitsigns stage_hunk')
check({
status = { head = 'main', added = 0, changed = 1, removed = 0 },
signs = { changed = 1 },
})
-- Add multiple edits
feed('ggccThat')
check({
status = { head = 'main', added = 0, changed = 2, removed = 0 },
signs = { changed = 2 },
})
command('Gitsigns stage_buffer')
check({
status = { head = 'main', added = 0, changed = 0, removed = 0 },
signs = {},
})
command('Gitsigns reset_buffer_index')
check({
status = { head = 'main', added = 0, changed = 2, removed = 0 },
signs = { changed = 2 },
})
command('Gitsigns reset_hunk')
check({
status = { head = 'main', added = 0, changed = 1, removed = 0 },
signs = { changed = 1 },
})
end)
describe('staging partial hunks', function()
setup(function()
clear()
setup_test_repo({ test_file_text = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H' } })
end)
before_each(function()
helpers.git('reset', '--hard')
edit(test_file)
end)
local function set_lines(start, dend, lines)
helpers.api.nvim_buf_set_lines(0, start, dend, false, lines)
end
describe('can stage add hunks', function()
before_each(function()
set_lines(2, 2, { 'c1', 'c2', 'c3', 'c4' })
expect_hunks({ '@@ -2 +3,4 @@' })
end)
it('contained in range', function()
command([[1,7 Gitsigns stage_hunk]])
expect_hunks({})
end)
it('containing range', function()
command([[4,5 Gitsigns stage_hunk]])
expect_hunks({
'@@ -2 +3,1 @@',
'@@ -4 +6,1 @@',
})
end)
it('from top range', function()
command([[1,4 Gitsigns stage_hunk]])
expect_hunks({ '@@ -4 +5,2 @@' })
end)
it('from bottom range', function()
command([[4,7 Gitsigns stage_hunk]])
expect_hunks({ '@@ -2 +3,1 @@' })
command([[Gitsigns reset_buffer_index]])
expect_hunks({ '@@ -2 +3,4 @@' })
command([[4,10 Gitsigns stage_hunk]])
expect_hunks({ '@@ -2 +3,1 @@' })
end)
end)
describe('can stage modified-add hunks', function()
before_each(function()
set_lines(2, 4, { 'c1', 'c2', 'c3', 'c4', 'c5' })
expect_hunks({ '@@ -3,2 +3,5 @@' })
end)
it('from top range containing mod', function()
command([[2,3 Gitsigns stage_hunk]])
expect_hunks({ '@@ -4,1 +4,4 @@' })
end)
it('from top range containing mod-add', function()
command([[2,5 Gitsigns stage_hunk]])
expect_hunks({ '@@ -5 +6,2 @@' })
end)
it('from bottom range containing add', function()
command([[6,8 Gitsigns stage_hunk]])
expect_hunks({ '@@ -3,2 +3,3 @@' })
end)
it('containing range containing add', function()
command('write')
command([[5,6 Gitsigns stage_hunk]])
expect_hunks({
'@@ -3,2 +3,2 @@',
'@@ -6 +7,1 @@',
})
end)
end)
describe('can stage modified-remove hunks', function()
before_each(function()
set_lines(2, 7, { 'c1', 'c2', 'c3' })
command('write')
expect_hunks({ '@@ -3,5 +3,3 @@' })
end)
it('from top range', function()
expect_hunks({ '@@ -3,5 +3,3 @@' })
command([[2,3 Gitsigns stage_hunk]])
expect_hunks({ '@@ -4,4 +4,2 @@' })
command([[2,3 Gitsigns reset_buffer_index]])
expect_hunks({ '@@ -3,5 +3,3 @@' })
command([[2,4 Gitsigns stage_hunk]])
expect_hunks({ '@@ -5,3 +5,1 @@' })
end)
it('from bottom range', function()
expect_hunks({ '@@ -3,5 +3,3 @@' })
command([[4,6 Gitsigns stage_hunk]])
expect_hunks({ '@@ -3,1 +3,1 @@' })
command([[2,3 Gitsigns reset_buffer_index]])
expect_hunks({ '@@ -3,5 +3,3 @@' })
command([[5,6 Gitsigns stage_hunk]])
expect_hunks({ '@@ -3,2 +3,2 @@' })
end)
end)
it('can stage remove hunks', function()
set_lines(2, 5, {})
expect_hunks({ '@@ -3,3 +2 @@' })
command([[2 Gitsigns stage_hunk]])
expect_hunks({})
end)
end)
local function check_cursor(pos)
eq(pos, helpers.api.nvim_win_get_cursor(0))
end
it('can navigate hunks', function()
setup_test_repo()
edit(test_file)
feed('dd')
feed('4Gx')
feed('6Gx')
expect_hunks({
'@@ -1,1 +0 @@',
'@@ -5,1 +4,1 @@',
'@@ -7,1 +6,1 @@',
})
check_cursor({ 6, 0 })
command('Gitsigns next_hunk') -- Wrap
check_cursor({ 1, 0 })
command('Gitsigns next_hunk')
check_cursor({ 4, 0 })
command('Gitsigns next_hunk')
check_cursor({ 6, 0 })
command('Gitsigns prev_hunk')
check_cursor({ 4, 0 })
command('Gitsigns prev_hunk')
check_cursor({ 1, 0 })
command('Gitsigns prev_hunk') -- Wrap
check_cursor({ 6, 0 })
end)
it('can navigate hunks (nowrap)', function()
setup_test_repo()
edit(test_file)
feed('4Gx')
feed('6Gx')
feed('gg')
expect_hunks({
'@@ -4,1 +4,1 @@',
'@@ -6,1 +6,1 @@',
})
command('set nowrapscan')
check_cursor({ 1, 0 })
command('Gitsigns next_hunk')
check_cursor({ 4, 0 })
command('Gitsigns next_hunk')
check_cursor({ 6, 0 })
command('Gitsigns next_hunk')
check_cursor({ 6, 0 })
feed('G')
check_cursor({ 18, 0 })
command('Gitsigns prev_hunk')
check_cursor({ 6, 0 })
command('Gitsigns prev_hunk')
check_cursor({ 4, 0 })
command('Gitsigns prev_hunk')
check_cursor({ 4, 0 })
end)
it('can stage hunks with no NL at EOF', function()
setup_test_repo()
local newfile = helpers.newfile
exec_lua([[vim.g.editorconfig = false]])
system("printf 'This is a file with no nl at eof' > " .. newfile)
helpers.git('add', newfile)
helpers.git('commit', '-m', 'commit on main')
edit(newfile)
check({ status = { head = 'main', added = 0, changed = 0, removed = 0 } })
feed('x')
check({ status = { head = 'main', added = 0, changed = 1, removed = 0 } })
command('Gitsigns stage_hunk')
check({ status = { head = 'main', added = 0, changed = 0, removed = 0 } })
end)
end)
lewis6991-gitsigns.nvim-7010000/test/gitdir_watcher_spec.lua 0000664 0000000 0000000 00000012206 14765572704 0023651 0 ustar 00root root 0000000 0000000 local helpers = require('test.gs_helpers')
local clear = helpers.clear
local system = helpers.fn.system
local edit = helpers.edit
local eq = helpers.eq
local setup_test_repo = helpers.setup_test_repo
local cleanup = helpers.cleanup
local command = helpers.api.nvim_command
local test_config = helpers.test_config
local match_debug_messages = helpers.match_debug_messages
local match_dag = helpers.match_dag
local n, p, np = helpers.n, helpers.p, helpers.np
local setup_gitsigns = helpers.setup_gitsigns
local test_file = helpers.test_file
local git = helpers.git
helpers.env()
local function get_bufs()
local bufs = {} --- @type table
for _, b in ipairs(helpers.api.nvim_list_bufs()) do
bufs[b] = helpers.api.nvim_buf_get_name(b)
end
return bufs
end
describe('gitdir_watcher', function()
before_each(function()
clear()
command('cd ' .. system({ 'dirname', os.tmpname() }))
end)
after_each(function()
cleanup()
end)
it('can follow moved files', function()
setup_test_repo()
setup_gitsigns(test_config)
command('Gitsigns clear_debug')
edit(test_file)
match_debug_messages({
'attach(1): Attaching (trigger=BufReadPost)',
np(
'run_job: git .* rev%-parse %-%-show%-toplevel %-%-absolute%-git%-dir %-%-abbrev%-ref HEAD'
),
np('run_job: git .* config user.name'),
np('run_job: git .* ls%-files .* ' .. vim.pesc(test_file)),
n('watch_gitdir(1): Watching git dir'),
np('run_job: git .* show .*'),
})
eq({ [1] = test_file }, get_bufs())
command('Gitsigns clear_debug')
local test_file2 = test_file .. '2'
git('mv', test_file, test_file2)
match_dag({
"watcher_cb(1): Git dir update: 'index.lock' { rename = true }",
"watcher_cb(1): Git dir update: 'index' { rename = true }",
"watcher_cb(1): Git dir update: 'index' { rename = true }",
})
match_debug_messages({
np(
'run_job: git .* rev%-parse %-%-show%-toplevel %-%-absolute%-git%-dir %-%-abbrev%-ref HEAD'
),
np('run_job: git .* ls%-files .* ' .. vim.pesc(test_file)),
np('run_job: git .* diff %-%-name%-status .* %-%-cached'),
n('handle_moved(1): File moved to dummy.txt2'),
np('run_job: git .* ls%-files .* ' .. vim.pesc(test_file2)),
np('handle_moved%(1%): Renamed buffer 1 from .*/dummy.txt to .*/dummy.txt2'),
np('run_job: git .* show .*'),
})
eq({ [1] = test_file2 }, get_bufs())
command('Gitsigns clear_debug')
local test_file3 = test_file .. '3'
git('mv', test_file2, test_file3)
match_dag({
"watcher_cb(1): Git dir update: 'index.lock' { rename = true }",
"watcher_cb(1): Git dir update: 'index' { rename = true }",
"watcher_cb(1): Git dir update: 'index' { rename = true }",
})
match_debug_messages({
p(
'run_job: git .* rev%-parse %-%-show%-toplevel %-%-absolute%-git%-dir %-%-abbrev%-ref HEAD'
),
np('run_job: git .* ls%-files .* ' .. vim.pesc(test_file2)),
np('run_job: git .* diff %-%-name%-status .* %-%-cached'),
n('handle_moved(1): File moved to dummy.txt3'),
np('run_job: git .* ls%-files .* ' .. vim.pesc(test_file3)),
np('handle_moved%(1%): Renamed buffer 1 from .*/dummy.txt2 to .*/dummy.txt3'),
np('run_job: git .* show .*'),
})
eq({ [1] = test_file3 }, get_bufs())
command('Gitsigns clear_debug')
git('mv', test_file3, test_file)
match_dag({
"watcher_cb(1): Git dir update: 'index.lock' { rename = true }",
"watcher_cb(1): Git dir update: 'index' { rename = true }",
"watcher_cb(1): Git dir update: 'index' { rename = true }",
})
match_debug_messages({
p(
'run_job: git .* rev%-parse %-%-show%-toplevel %-%-absolute%-git%-dir %-%-abbrev%-ref HEAD'
),
np('run_job: git .* ls%-files .* ' .. vim.pesc(test_file3)),
np('run_job: git .* diff %-%-name%-status .* %-%-cached'),
np('run_job: git .* ls%-files .* ' .. vim.pesc(test_file)),
n('handle_moved(1): Moved file reset'),
np('run_job: git .* ls%-files .* ' .. vim.pesc(test_file)),
np('handle_moved%(1%): Renamed buffer 1 from .*/dummy.txt3 to .*/dummy.txt'),
np('run_job: git .* show .*'),
})
eq({ [1] = test_file }, get_bufs())
end)
it('can debounce and throttle updates per buffer', function()
helpers.cleanup()
system({ 'mkdir', helpers.scratch })
helpers.git_init()
local f1 = vim.fs.joinpath(helpers.scratch, 'file1')
local f2 = vim.fs.joinpath(helpers.scratch, 'file2')
helpers.write_to_file(f1, { '1', '2', '3' })
helpers.write_to_file(f2, { '1', '2', '3' })
git('add', f1, f2)
git('commit', '-m', 'init commit')
setup_gitsigns(test_config)
command('edit ' .. f1)
helpers.feed('Aa')
command('write')
local b1 = helpers.api.nvim_get_current_buf()
command('split ' .. f2)
helpers.feed('Ab')
command('write')
local b2 = helpers.api.nvim_get_current_buf()
helpers.check({ signs = { changed = 1 } }, b1)
helpers.check({ signs = { changed = 1 } }, b2)
git('add', f1, f2)
helpers.check({ signs = {} }, b1)
helpers.check({ signs = {} }, b2)
end)
end)
lewis6991-gitsigns.nvim-7010000/test/gitsigns_spec.lua 0000664 0000000 0000000 00000055135 14765572704 0022511 0 ustar 00root root 0000000 0000000 local Screen = require('nvim-test.screen')
local helpers = require('test.gs_helpers')
local clear = helpers.clear
local command = helpers.api.nvim_command
local feed = helpers.feed
local insert = helpers.insert
local exec_lua = helpers.exec_lua
local split = vim.split
local get_buf_var = helpers.api.nvim_buf_get_var
local fn = helpers.fn
local system = fn.system
local expectf = helpers.expectf
local write_to_file = helpers.write_to_file
local edit = helpers.edit
local cleanup = helpers.cleanup
local test_file = helpers.test_file
local git = helpers.git
local scratch = helpers.scratch
local newfile = helpers.newfile
local match_dag = helpers.match_dag
local match_lines = helpers.match_lines
local n, p, np = helpers.n, helpers.p, helpers.np
local match_debug_messages = helpers.match_debug_messages
local setup_gitsigns = helpers.setup_gitsigns
local setup_test_repo = helpers.setup_test_repo
local test_config = helpers.test_config
local check = helpers.check
local eq = helpers.eq
helpers.env()
describe('gitsigns (with screen)', function()
local screen --- @type test.screen
local config --- @type table
before_each(function()
clear()
screen = Screen.new(20, 17)
screen:attach({ ext_messages = true })
local default_attrs = {
[1] = { foreground = Screen.colors.DarkBlue, background = Screen.colors.WebGray },
[2] = { foreground = Screen.colors.NvimDarkCyan },
[3] = { foreground = Screen.colors.NvimDarkGreen },
[4] = { foreground = Screen.colors.NvimDarkRed },
[5] = { foreground = Screen.colors.Brown },
[6] = { foreground = Screen.colors.Blue1, bold = true },
[7] = { bold = true },
[8] = { foreground = Screen.colors.White, background = Screen.colors.Red },
[9] = { foreground = Screen.colors.SeaGreen, bold = true },
[10] = { foreground = Screen.colors.Red },
[11] = { foreground = Screen.colors.NvimDarkRed, background = Screen.colors.WebGray },
[12] = { foreground = Screen.colors.NvimDarkCyan, background = Screen.colors.WebGray },
}
-- Use the classic vim colorscheme, not the new defaults in nvim >= 0.10
if fn.has('nvim-0.10') > 0 then
command('colorscheme vim')
else
default_attrs[2] = { background = Screen.colors.LightMagenta }
default_attrs[3] = { background = Screen.colors.LightBlue }
default_attrs[4] =
{ background = Screen.colors.LightCyan1, bold = true, foreground = Screen.colors.Blue1 }
end
screen:set_default_attr_ids(default_attrs)
config = vim.deepcopy(test_config)
command('cd ' .. system({ 'dirname', os.tmpname() }))
end)
after_each(function()
cleanup()
screen:detach()
end)
it('can run basic setup', function()
setup_gitsigns()
check({ status = {}, signs = {} })
end)
it('gitdir watcher works on a fresh repo', function()
--- @type integer
local nvim_ver = exec_lua('return vim.version().minor')
screen:try_resize(20, 6)
setup_test_repo({ no_add = true })
-- Don't set this too low, or else the test will lock up
config.watch_gitdir = { interval = 100 }
setup_gitsigns(config)
edit(test_file)
match_dag({
'attach(1): Attaching (trigger=BufReadPost)',
p('run_job: git .* config user.name'),
p(
'run_job: git .* rev%-parse %-%-show%-toplevel %-%-absolute%-git%-dir %-%-abbrev%-ref HEAD'
),
p(
'run_job: git .* ls%-files %-%-stage %-%-others %-%-exclude%-standard %-%-eol '
.. vim.pesc(test_file)
),
'watch_gitdir(1): Watching git dir',
})
check({
status = { head = '', added = 18, changed = 0, removed = 0 },
signs = { untracked = nvim_ver == 9 and 8 or 7 },
})
git('add', test_file)
check({
status = { head = '', added = 0, changed = 0, removed = 0 },
signs = {},
})
end)
it('can open files not in a git repo', function()
setup_gitsigns(config)
local tmpfile = os.tmpname()
edit(tmpfile)
match_debug_messages({
'attach(1): Attaching (trigger=BufReadPost)',
np(
'run_job: git .* rev%-parse %-%-show%-toplevel %-%-absolute%-git%-dir %-%-abbrev%-ref HEAD'
),
n('new: Not in git repo'),
n('attach(1): Empty git obj'),
})
command('Gitsigns clear_debug')
insert('line')
command('write')
match_debug_messages({
n('attach(1): Attaching (trigger=BufWritePost)'),
np(
'run_job: git .* rev%-parse %-%-show%-toplevel %-%-absolute%-git%-dir %-%-abbrev%-ref HEAD'
),
n('new: Not in git repo'),
n('attach(1): Empty git obj'),
})
end)
describe('when attaching', function()
before_each(function()
setup_test_repo()
setup_gitsigns(config)
end)
it('can setup mappings', function()
edit(test_file)
expectf(function()
local res = split(helpers.api.nvim_exec2('nmap ', { output = true }).output, '\n')
table.sort(res)
-- Check all keymaps get set
match_lines(res, {
n('n mhS *@lua require"gitsigns".stage_buffer()'),
n('n mhU *@lua require"gitsigns".reset_buffer_index()'),
n('n mhp *@lua require"gitsigns".preview_hunk()'),
n('n mhr *@lua require"gitsigns".reset_hunk()'),
n('n mhs *@lua require"gitsigns".stage_hunk()'),
n('n mhu *@lua require"gitsigns".undo_stage_hunk()'),
})
end)
end)
it('does not attach inside .git', function()
edit(scratch .. '/.git/index')
match_debug_messages({
'attach(1): Attaching (trigger=BufReadPost)',
n('new: In git dir'),
n('attach(1): Empty git obj'),
})
end)
it("doesn't attach to ignored files", function()
write_to_file(scratch .. '/.gitignore', { 'dummy_ignored.txt' })
local ignored_file = scratch .. '/dummy_ignored.txt'
system({ 'touch', ignored_file })
edit(ignored_file)
match_debug_messages({
'attach(1): Attaching (trigger=BufReadPost)',
np(
'run_job: git .* rev%-parse %-%-show%-toplevel %-%-absolute%-git%-dir %-%-abbrev%-ref HEAD'
),
np('run_job: git .* config user.name'),
np('run_job: git .* ls%-files .*/dummy_ignored.txt'),
n('attach(1): Cannot resolve file in repo'),
})
check({ status = { head = 'main' } })
end)
it("doesn't attach to non-existent files", function()
edit(newfile)
match_debug_messages({
'attach(1): Attaching (trigger=BufNewFile)',
np(
'run_job: git .* rev%-parse %-%-show%-toplevel %-%-absolute%-git%-dir %-%-abbrev%-ref HEAD'
),
np('run_job: git .* config user.name'),
np(
'run_job: git .* ls%-files %-%-stage %-%-others %-%-exclude%-standard %-%-eol '
.. vim.pesc(newfile)
),
n('attach(1): Not a file'),
})
check({ status = { head = 'main' } })
end)
it("doesn't attach to non-existent files with non-existent sub-dirs", function()
edit(scratch .. '/does/not/exist')
match_debug_messages({
'attach(1): Attaching (trigger=BufNewFile)',
n('attach(1): Not a path'),
})
helpers.pcall_err(get_buf_var, 0, 'gitsigns_head')
helpers.pcall_err(get_buf_var, 0, 'gitsigns_status_dict')
end)
it('can run copen', function()
command('copen')
match_debug_messages({
'attach(2): Attaching (trigger=BufReadPost)',
n('attach(2): Non-normal buffer'),
})
end)
it('can run get_hunks()', function()
edit(test_file)
insert('line1')
feed('oline2')
expectf(function()
eq({
{
head = '@@ -1,1 +1,2 @@',
type = 'change',
lines = { '-This', '+line1This', '+line2' },
added = { count = 2, start = 1, lines = { 'line1This', 'line2' } },
removed = { count = 1, start = 1, lines = { 'This' } },
},
}, exec_lua([[return require'gitsigns'.get_hunks()]]))
end)
end)
end)
describe('current line blame', function()
before_each(function()
config.current_line_blame = true
config.current_line_blame_formatter = ' , - '
setup_gitsigns(config)
end)
local function blame_line_ui_test(autocrlf, file_ending)
setup_test_repo()
exec_lua([[vim.g.editorconfig = false]])
git('config', 'core.autocrlf', autocrlf)
if file_ending == 'dos' then
system("printf 'This\r\nis\r\na\r\nwindows\r\nfile\r\n' > " .. newfile)
else
system("printf 'This\nis\na\nwindows\nfile\n' > " .. newfile)
end
git('add', newfile)
git('commit', '-m', 'commit on main')
edit(newfile)
feed('gg')
check({ signs = {} })
screen:expect({
grid = [[
^{MATCH:This {6: You, %d second.}}|
is |
a |
windows |
file |
{6:~ }|
{6:~ }|
{6:~ }|
{6:~ }|
{6:~ }|
{6:~ }|
{6:~ }|
{6:~ }|
{6:~ }|
{6:~ }|
{6:~ }|
{6:~ }|
]],
})
end
it('does handle dos fileformats', function()
-- Add a file with windows line ending into the repo
-- Disable autocrlf, so that the file keeps the \r\n file endings.
blame_line_ui_test('false', 'dos')
end)
it('does handle autocrlf', function()
blame_line_ui_test('true', 'dos')
end)
it('does handle unix', function()
blame_line_ui_test('false', 'unix')
end)
end)
-- TODO(lewis6991): All deprecated fields removed. Re-add when we have another deprecated field
-- describe('configuration', function()
-- it('handled deprecated fields', function()
-- pending()
-- -- config.current_line_blame_delay = 100
-- -- setup_gitsigns(config)
-- -- eq(100, exec_lua([[return package.loaded['gitsigns.config'].config.current_line_blame_opts.delay]]))
-- end)
-- end)
describe('on_attach()', function()
it('can prevent attaching to a buffer', function()
setup_test_repo({ no_add = true })
setup_gitsigns(config, true)
edit(test_file)
match_debug_messages({
'attach(1): Attaching (trigger=BufReadPost)',
np(
'run_job: git .* rev%-parse %-%-show%-toplevel %-%-absolute%-git%-dir %-%-abbrev%-ref HEAD'
),
np('run_job: git .* rev%-parse %-%-short HEAD'),
np('run_job: git .* config user.name'),
np('run_job: git .* %-%-git%-dir .* %-%-stage %-%-others %-%-exclude%-standard %-%-eol.*'),
n('attach(1): User on_attach() returned false'),
})
end)
end)
describe('change_base()', function()
it('works', function()
setup_test_repo()
edit(test_file)
feed('oEDIT')
command('write')
git('add', test_file)
git('commit', '-m', 'commit on main')
-- Don't setup gitsigns until the repo has two commits
setup_gitsigns(config)
check({
status = { head = 'main', added = 0, changed = 0, removed = 0 },
signs = {},
})
command('Gitsigns change_base ~')
check({
status = { head = 'main', added = 1, changed = 0, removed = 0 },
signs = { added = 1 },
})
end)
end)
local function testsuite(internal_diff)
return function()
before_each(function()
config.diff_opts = {
internal = internal_diff,
}
setup_test_repo()
end)
it('apply basic signs', function()
setup_gitsigns(config)
edit(test_file)
command('set signcolumn=yes')
feed('dd') -- Top delete
feed('j')
feed('o') -- Add
feed('2j')
feed('x') -- Change
feed('3j')
feed('dd') -- Delete
feed('j')
feed('ddx') -- Change delete
check({
status = { head = 'main', added = 1, changed = 2, removed = 3 },
signs = { topdelete = 1, changedelete = 1, added = 1, delete = 1, changed = 1 },
})
end)
it('can enable numhl', function()
config.numhl = true
setup_gitsigns(config)
edit(test_file)
command('set signcolumn=no')
command('set number')
feed('dd') -- Top delete
feed('j')
feed('o') -- Add
feed('2j')
feed('x') -- Change
feed('3j')
feed('dd') -- Delete
feed('j')
feed('ddx') -- Change delete
-- screen:snapshot_util()
screen:expect({
grid = [[
{4: 1 }is |
{5: 2 }a |
{3: 3 } |
{5: 4 }file |
{2: 5 }sed |
{5: 6 }for |
{4: 7 }testing |
{5: 8 }The |
{2: 9 }^oesn't |
{5: 10 }matter, |
{5: 11 }it |
{5: 12 }just |
{5: 13 }needs |
{5: 14 }to |
{5: 15 }be |
{5: 16 }static. |
{6:~ }|
]],
})
end)
it('attaches to newly created files', function()
setup_gitsigns(config)
edit(newfile)
match_debug_messages({
'attach(1): Attaching (trigger=BufNewFile)',
np(
'run_job: git .* rev%-parse %-%-show%-toplevel %-%-absolute%-git%-dir %-%-abbrev%-ref HEAD'
),
np('run_job: git .* config user.name'),
np('run_job: git .* ls%-files .*'),
n('attach(1): Not a file'),
})
command('write')
local messages = {
'attach(1): Attaching (trigger=BufWritePost)',
np(
'run_job: git .* rev%-parse %-%-show%-toplevel %-%-absolute%-git%-dir %-%-abbrev%-ref HEAD'
),
np('run_job: git .* ls%-files .*'),
n('watch_gitdir(1): Watching git dir'),
}
if not internal_diff then
table.insert(messages, np('run_job: git .* diff .* /tmp/lua_.* /tmp/lua_.*'))
end
match_debug_messages(messages)
check({
status = { head = 'main', added = 1, changed = 0, removed = 0 },
signs = { untracked = 1 },
})
end)
it('can add untracked files to the index', function()
setup_gitsigns(config)
edit(newfile)
feed('iline')
check({ status = { head = 'main' } })
command('write')
check({
status = { head = 'main', added = 1, changed = 0, removed = 0 },
signs = { untracked = 1 },
})
feed('mhs') -- Stage the file (add file to index)
check({
status = { head = 'main', added = 0, changed = 0, removed = 0 },
signs = {},
})
end)
it('tracks files in new repos', function()
setup_gitsigns(config)
system({ 'touch', newfile })
edit(newfile)
feed('iEDIT')
command('write')
check({
status = { head = 'main', added = 1, changed = 0, removed = 0 },
signs = { untracked = 1 },
})
git('add', newfile)
check({
status = { head = 'main', added = 0, changed = 0, removed = 0 },
signs = {},
})
git('reset')
check({
status = { head = 'main', added = 1, changed = 0, removed = 0 },
signs = { untracked = 1 },
})
end)
it('can detach from buffers', function()
setup_gitsigns(config)
edit(test_file)
command('set signcolumn=yes')
feed('dd') -- Top delete
feed('j')
feed('o') -- Add
feed('2j')
feed('x') -- Change
feed('3j')
feed('dd') -- Delete
feed('j')
feed('ddx') -- Change delete
check({
status = { head = 'main', added = 1, changed = 2, removed = 3 },
signs = { topdelete = 1, added = 1, changed = 1, delete = 1, changedelete = 1 },
})
command('Gitsigns detach')
check({ status = {}, signs = {} })
end)
it('can stages file with merge conflicts', function()
setup_gitsigns(config)
command('set signcolumn=yes')
-- Edit a file and commit it on main branch
edit(test_file)
check({ status = { head = 'main', added = 0, changed = 0, removed = 0 } })
feed('iedit')
check({ status = { head = 'main', added = 0, changed = 1, removed = 0 } })
command('write')
command('bwipe')
git('add', test_file)
git('commit', '-m', 'commit on main')
-- Create a branch, remove last commit, edit file again
git('checkout', '-B', 'abranch')
git('reset', '--hard', 'HEAD~1')
edit(test_file)
check({ status = { head = 'abranch', added = 0, changed = 0, removed = 0 } })
feed('idiff')
check({ status = { head = 'abranch', added = 0, changed = 1, removed = 0 } })
command('write')
command('bwipe')
git('add', test_file)
git('commit', '-m', 'commit on branch')
git('rebase', 'main')
-- test_file should have a conflict
edit(test_file)
check({
status = { head = 'HEAD(rebasing)', added = 4, changed = 1, removed = 0 },
signs = { changed = 1, added = 4 },
})
exec_lua('require("gitsigns.actions").stage_hunk()')
check({
status = { head = 'HEAD(rebasing)', added = 0, changed = 0, removed = 0 },
signs = {},
})
end)
it('handle files with spaces', function()
setup_gitsigns(config)
command('set signcolumn=yes')
local spacefile = scratch .. '/a b c d'
write_to_file(spacefile, { 'spaces', 'in', 'file' })
edit(spacefile)
check({
status = { head = 'main', added = 3, removed = 0, changed = 0 },
signs = { untracked = 3 },
})
git('add', spacefile)
edit(spacefile)
check({
status = { head = 'main', added = 0, removed = 0, changed = 0 },
signs = {},
})
end)
end
end
-- Run regular config
describe('diff-ext', testsuite(false))
-- Run with:
-- - internal diff (ffi)
-- - decoration provider
describe('diff-int', testsuite(true))
it('can handle vimgrep', function()
setup_test_repo()
write_to_file(scratch .. '/t1.txt', { 'hello ben' })
write_to_file(scratch .. '/t2.txt', { 'hello ben' })
write_to_file(scratch .. '/t3.txt', { 'hello lewis' })
setup_gitsigns(config)
helpers.exc_exec('vimgrep ben ' .. scratch .. '/*')
screen:expect({
messages = {
{
kind = 'quickfix',
content = { { '(1 of 2): hello ben' } },
},
},
})
match_debug_messages({
'attach_autocmd(2): Attaching is disabled',
n('attach_autocmd(3): Attaching is disabled'),
n('attach_autocmd(4): Attaching is disabled'),
n('attach_autocmd(5): Attaching is disabled'),
})
end)
it('show short SHA when detached head', function()
setup_test_repo()
git('checkout', '--detach')
-- Disable debug_mode so the sha is calculated
config.debug_mode = false
setup_gitsigns(config)
edit(test_file)
-- SHA is not deterministic so just check it can be cast as a hex value
expectf(function()
helpers.neq(nil, tonumber('0x' .. get_buf_var(0, 'gitsigns_head')))
end)
end)
it('handles a quick undo', function()
setup_test_repo()
setup_gitsigns(config)
edit(test_file)
-- This test isn't deterministic so run it a few times
for _ = 1, 3 do
feed('x')
check({ signs = { changed = 1 } })
feed('u')
check({ signs = {} })
end
end)
it('handles filenames with unicode characters', function()
screen:try_resize(20, 2)
setup_test_repo()
setup_gitsigns(config)
local uni_filename = scratch .. '/fรถobรฆr'
write_to_file(uni_filename, { 'Lorem ipsum' })
git('add', uni_filename)
git('commit', '-m', 'another commit')
edit(uni_filename)
screen:expect({ grid = [[
^Lorem ipsum |
{6:~ }|
]] })
feed('x')
if fn.has('nvim-0.11') > 0 then
screen:expect({
grid = [[
{12:~ }^orem ipsum |
{6:~ }|
]],
})
else
screen:expect({
grid = [[
{2:~ }^orem ipsum |
{6:~ }|
]],
})
end
end)
it('handle #521', function()
screen:detach()
screen:attach()
screen:try_resize(20, 4)
setup_test_repo()
setup_gitsigns(config)
edit(test_file)
feed('dd')
local function check_screen(unchanged)
if fn.has('nvim-0.11') > 0 then
-- TODO(lewis6991): ???
screen:expect({
grid = [[
{11:^ }^is |
{1: }a |
{1: }file |
|
]],
unchanged = unchanged,
})
else
screen:expect({
grid = [[
{4:^ }^is |
{1: }a |
{1: }file |
{1: }used |
]],
unchanged = unchanged,
})
end
end
check_screen()
-- Write over the text with itself. This will remove all the signs but the
-- calculated hunks won't change.
exec_lua(function()
local text = vim.api.nvim_buf_get_lines(0, 0, -1, false)
vim.api.nvim_buf_set_lines(0, 0, -1, true, text)
end)
check_screen(true)
end)
end)
describe('gitsigns', function()
local config --- @type table
before_each(function()
clear()
config = vim.deepcopy(test_config)
command('cd ' .. system({ 'dirname', os.tmpname() }))
end)
after_each(function()
cleanup()
end)
it('handle #888', function()
setup_test_repo()
local path1 = scratch .. '/cargo.toml'
local subdir = scratch .. '/subdir'
local path2 = subdir .. '/cargo.toml'
write_to_file(path1, { 'some text' })
git('add', path1)
git('commit', '-m', 'add cargo')
-- move file and stage move
system({ 'mkdir', subdir })
system({ 'mv', path1, path2 })
git('add', path1, path2)
config.base = 'HEAD'
setup_gitsigns(config)
edit(path1)
command('write')
helpers.sleep(100)
end)
end)
lewis6991-gitsigns.nvim-7010000/test/gs_helpers.lua 0000664 0000000 0000000 00000021002 14765572704 0021765 0 ustar 00root root 0000000 0000000 local helpers = require('nvim-test.helpers')
local timeout = 2000
local M = helpers
local exec_lua = helpers.exec_lua
local matches = helpers.matches
local eq = helpers.eq
local buf_get_var = helpers.api.nvim_buf_get_var
local system = helpers.fn.system
M.scratch = os.getenv('PJ_ROOT') .. '/scratch'
M.test_file = M.scratch .. '/dummy.txt'
M.newfile = M.scratch .. '/newfile.txt'
M.test_config = {
debug_mode = true,
_test_mode = true,
signs = {
add = { text = '+' },
delete = { text = '_' },
change = { text = '~' },
topdelete = { text = '^' },
changedelete = { text = '%' },
untracked = { text = '#' },
},
on_attach = {
{ 'n', 'mhs', 'lua require"gitsigns".stage_hunk()' },
{ 'n', 'mhu', 'lua require"gitsigns".undo_stage_hunk()' },
{ 'n', 'mhr', 'lua require"gitsigns".reset_hunk()' },
{ 'n', 'mhp', 'lua require"gitsigns".preview_hunk()' },
{ 'n', 'mhS', 'lua require"gitsigns".stage_buffer()' },
{ 'n', 'mhU', 'lua require"gitsigns".reset_buffer_index()' },
},
attach_to_untracked = true,
update_debounce = 5,
}
local test_file_text = {
'This',
'is',
'a',
'file',
'used',
'for',
'testing',
'gitsigns.',
'The',
'content',
"doesn't",
'matter,',
'it',
'just',
'needs',
'to',
'be',
'static.',
}
--- Run a git command
--- @param ... string
function M.git(...)
system({ 'git', '-C', M.scratch, ... })
end
function M.cleanup()
system({ 'rm', '-rf', M.scratch })
end
function M.git_init()
M.git('init', '-b', 'main')
-- Always force color to test settings don't interfere with gitsigns systems
-- commands (addresses #23)
M.git('config', 'color.branch', 'always')
M.git('config', 'color.ui', 'always')
M.git('config', 'color.diff', 'always')
M.git('config', 'color.interactive', 'always')
M.git('config', 'color.status', 'always')
M.git('config', 'color.grep', 'always')
M.git('config', 'color.pager', 'true')
M.git('config', 'color.decorate', 'always')
M.git('config', 'color.showbranch', 'always')
M.git('config', 'merge.conflictStyle', 'merge')
M.git('config', 'user.email', 'tester@com.com')
M.git('config', 'user.name', 'tester')
M.git('config', 'init.defaultBranch', 'main')
end
--- Setup a basic git repository in directory `helpers.scratch` with a single file
--- `helpers.test_file` committed.
--- @param opts? {test_file_text?: string[], no_add?: boolean}
function M.setup_test_repo(opts)
local text = opts and opts.test_file_text or test_file_text
M.cleanup()
system({ 'mkdir', M.scratch })
M.git_init()
system({ 'touch', M.test_file })
M.write_to_file(M.test_file, text)
if not (opts and opts.no_add) then
M.git('add', M.test_file)
M.git('commit', '-m', 'init commit')
end
end
--- @param cond fun()
--- @param interval? integer
function M.expectf(cond, interval)
local duration = 0
interval = interval or 1
while duration < timeout do
--- @type boolean, boolean?
local ok, ret = pcall(cond)
if ok and (ret == nil or ret == true) then
return
end
duration = duration + interval
helpers.sleep(interval)
interval = interval * 2
end
cond()
end
--- @param path string
function M.edit(path)
helpers.api.nvim_command('edit ' .. path)
end
--- @param path string
--- @param text string[]
function M.write_to_file(path, text)
local f = assert(io.open(path, 'wb'))
for _, l in ipairs(text) do
f:write(l)
f:write('\n')
end
f:close()
end
--- @param line string
--- @param spec string|{next:boolean, pattern:boolean, text:string}
--- @return boolean
local function match_spec_elem(line, spec)
if spec.pattern then
if line:match(spec.text) then
return true
end
elseif spec.next then
-- local matcher = spec.pattern and matches or eq
-- matcher(spec.text, line)
if spec.pattern then
matches(spec.text, line)
else
eq(spec.text, line)
end
return true
end
return spec == line
end
--- Match lines in spec. Not all lines have to match
--- @param lines string[]
--- @param spec table
function M.match_lines(lines, spec)
local i = 1
for _, line in ipairs(lines) do
local s = spec[i]
if line ~= '' and s and match_spec_elem(line, s) then
i = i + 1
end
end
if i < #spec + 1 then
local unmatched_msg = table.concat(
--- @param v any
--- @return string
vim.tbl_map(function(v)
return (' - %s'):format(v.text or v)
end, spec),
'\n'
)
local lines_msg = table.concat(
--- @param v any
--- @return string
vim.tbl_map(function(v)
return string.format(' - %s', v)
end, lines),
'\n'
)
error(('Did not match patterns:\n%s\nwith:\n%s'):format(unmatched_msg, lines_msg))
end
end
function M.p(str)
return { text = str, pattern = true }
end
function M.n(str)
return { text = str, next = true }
end
function M.np(str)
return { text = str, pattern = true, next = true }
end
--- @return string[]
function M.debug_messages()
--- @type string[]
local r = exec_lua("return require'gitsigns.debug.log'.get()")
for i, line in ipairs(r) do
-- Remove leading timestamp
r[i] = line:gsub('^[0-9.]+ D ', '')
end
return r
end
--- Like match_debug_messages but elements in spec are unordered
--- @param spec table
function M.match_dag(spec)
M.expectf(function()
local messages = M.debug_messages()
for _, s in ipairs(spec) do
M.match_lines(messages, { s })
end
end)
end
--- @param spec table
function M.match_debug_messages(spec)
M.expectf(function()
M.match_lines(M.debug_messages(), spec)
end)
end
--- @param config? table
--- @param on_attach? boolean
function M.setup_gitsigns(config, on_attach)
exec_lua(function(path, config0, on_attach0)
package.path = path
if config0 and config0.on_attach then
local maps = config0.on_attach --[[@as [string,string,string][] ]]
config0.on_attach = function(bufnr)
for _, map in ipairs(maps) do
vim.keymap.set(map[1], map[2], map[3], { buffer = bufnr })
end
end
end
if on_attach0 then
config0.on_attach = function()
return false
end
end
require('gitsigns').setup(config0)
vim.o.diffopt = 'internal,filler,closeoff'
end, package.path, config, on_attach)
end
--- @param status table
--- @param bufnr integer
local function check_status(status, bufnr)
if next(status) == nil then
eq(false, pcall(buf_get_var, bufnr, 'gitsigns_head'), 'b:gitsigns_head is unexpectedly set')
eq(
false,
pcall(buf_get_var, bufnr, 'gitsigns_status_dict'),
'b:gitsigns_status_dict is unexpectedly set'
)
return
end
eq(status.head, buf_get_var(bufnr, 'gitsigns_head'), 'b:gitsigns_head does not match')
--- @type table
local bstatus = buf_get_var(bufnr, 'gitsigns_status_dict')
for _, i in ipairs({ 'added', 'changed', 'removed', 'head' }) do
eq(status[i], bstatus[i], string.format("status['%s'] did not match gitsigns_status_dict", i))
end
-- Catch any extra keys
for i, v in pairs(status) do
eq(v, bstatus[i], string.format("status['%s'] did not match gitsigns_status_dict", i))
end
end
--- @param signs table
--- @param bufnr integer
local function check_signs(signs, bufnr)
local buf_signs = {} --- @type string[]
local buf_marks = helpers.api.nvim_buf_get_extmarks(bufnr, -1, 0, -1, { details = true })
for _, s in ipairs(buf_marks) do
buf_signs[#buf_signs + 1] = s[4].sign_hl_group
end
--- @type table
local act = {}
for _, name in ipairs(buf_signs) do
for t, hl in pairs({
added = 'GitSignsAdd',
changed = 'GitSignsChange',
delete = 'GitSignsDelete',
changedelete = 'GitSignsChangedelete',
topdelete = 'GitSignsTopdelete',
untracked = 'GitSignsUntracked',
}) do
if name == hl then
act[t] = (act[t] or 0) + 1
end
end
end
eq(signs, act, vim.inspect(buf_signs))
end
--- @param attrs {signs:table,status:table}
--- @param bufnr? integer
function M.check(attrs, bufnr)
bufnr = bufnr or 0
if not attrs then
return
end
M.expectf(function()
if attrs.status then
check_status(attrs.status, bufnr)
end
if attrs.signs then
check_signs(attrs.signs, bufnr)
end
end)
end
return M
lewis6991-gitsigns.nvim-7010000/test/highlights_spec.lua 0000664 0000000 0000000 00000005615 14765572704 0023012 0 ustar 00root root 0000000 0000000 local Screen = require('nvim-test.screen')
local helpers = require('test.gs_helpers')
local clear = helpers.clear
local exec_lua = helpers.exec_lua
local command = helpers.api.nvim_command
local cleanup = helpers.cleanup
local test_config = helpers.test_config
local expectf = helpers.expectf
local match_dag = helpers.match_dag
local p = helpers.p
local setup_gitsigns = helpers.setup_gitsigns
helpers.env()
describe('highlights', function()
local screen --- @type test.screen
local config --- @type Gitsigns.Config
before_each(function()
clear()
screen = Screen.new(20, 17)
screen:attach()
local default_attrs = {
[1] = { foreground = Screen.colors.DarkBlue, background = Screen.colors.WebGray },
[2] = { foreground = Screen.colors.NvimDarkCyan },
[3] = { background = Screen.colors.LightBlue },
[4] = { foreground = Screen.colors.NvimDarkRed },
[5] = { foreground = Screen.colors.Brown },
[6] = { foreground = Screen.colors.Blue1, bold = true },
[7] = { bold = true },
[8] = { foreground = Screen.colors.White, background = Screen.colors.Red },
[9] = { foreground = Screen.colors.SeaGreen, bold = true },
}
-- Use the classic vim colorscheme, not the new defaults in nvim >= 0.10
if helpers.fn.has('nvim-0.10') > 0 then
command('colorscheme vim')
else
default_attrs[2] = { background = Screen.colors.LightMagenta }
default_attrs[4] =
{ background = Screen.colors.LightCyan1, bold = true, foreground = Screen.colors.Blue1 }
end
screen:set_default_attr_ids(default_attrs)
config = vim.deepcopy(test_config)
end)
after_each(function()
cleanup()
screen:detach()
end)
it('get set up correctly', function()
command('set termguicolors')
config.signs.add.hl = nil
config.signs.change.hl = nil
config.signs.delete.hl = nil
config.signs.changedelete.hl = nil
config.signs.topdelete.hl = nil
config.numhl = true
config.linehl = true
config._test_mode = true
setup_gitsigns(config)
local nvim10 = helpers.fn.has('nvim-0.10') > 0
expectf(function()
match_dag({
p('Deriving GitSignsAdd from ' .. (nvim10 and 'Added' or 'DiffAdd')),
p('Deriving GitSignsAddLn from DiffAdd'),
p('Deriving GitSignsAddNr from GitSignsAdd'),
p('Deriving GitSignsChangeLn from DiffChange'),
p('Deriving GitSignsChangeNr from GitSignsChange'),
p('Deriving GitSignsDelete from ' .. (nvim10 and 'Removed' or 'DiffDelete')),
p('Deriving GitSignsDeleteNr from GitSignsDelete'),
})
end)
end)
it('update when colorscheme changes', function()
command('set termguicolors')
config.signs.add.hl = nil
config.signs.change.hl = nil
config.signs.delete.hl = nil
config.signs.changedelete.hl = nil
config.signs.topdelete.hl = nil
config.linehl = true
setup_gitsigns(config)
end)
end)
lewis6991-gitsigns.nvim-7010000/test/hunk_spec.lua 0000664 0000000 0000000 00000005306 14765572704 0021622 0 ustar 00root root 0000000 0000000 local helpers = require('test.gs_helpers')
local exec_lua = helpers.exec_lua
local eq = helpers.eq
helpers.env()
--- @param hunks [string,integer,integer,integer,integer][]
--- @return [string,integer,integer][]
local function calc_signs(hunks)
for i, hunk in ipairs(hunks) do
if hunk[1] then
hunks[i] = {
added = { count = hunk[4], start = hunk[5] },
removed = { count = hunk[2], start = hunk[3] },
type = hunk[1],
}
end
end
--- @type Gitsigns.Sign[]
local signs = exec_lua(
--- @param hunks0 Gitsigns.Hunk.Hunk[]
--- @return Gitsigns.Sign[]
function(hunks0)
local Hunks = require('gitsigns.hunks')
local signs = {}
for i, hunk in ipairs(hunks0) do
local prev_hunk, next_hunk = hunks0[i - 1], hunks0[i + 1]
vim.list_extend(signs, Hunks.calc_signs(prev_hunk, hunk, next_hunk))
end
return signs
end,
hunks
)
local r = {} --- @type [string,integer,integer][]
for i, s in ipairs(signs) do
r[i] = { s.type, s.lnum, s.count }
end
return r
end
describe('hunksigns', function()
before_each(function()
exec_lua(function(path)
package.path = path
require('gitsigns').setup({ _new_sign_calc = true })
end, package.path)
end)
it('calculate topdelete signs', function()
eq({ { 'topdelete', 1, 1 } }, calc_signs({ { 'delete', 1, 1, 0, 0 } }))
end)
it('calculate topdelete signs with changedelete', function()
eq(
{ { 'changedelete', 1, 1 } },
calc_signs({
{ 'delete', 1, 1, 0, 0 },
{ 'change', 1, 2, 1, 1 },
})
)
end)
it('delete, change, topdelete', function()
eq(
{
{ 'delete', 1, 1 },
{ 'change', 2, 1 },
{ 'topdelete', 3, 1 },
},
calc_signs({
{ 'delete', 1, 2, 0, 1 },
{ 'change', 1, 3, 1, 2 },
{ 'delete', 1, 4, 0, 2 },
})
)
end)
it('delete, change, change, topdelete', function()
eq(
{
{ 'delete', 1, 1 },
{ 'change', 2, 2 },
{ 'change', 3 },
{ 'topdelete', 4, 1 },
},
calc_signs({
{ 'delete', 1, 2, 0, 1 },
{ 'change', 2, 3, 2, 2 },
{ 'delete', 1, 5, 0, 3 },
})
)
end)
it('delete, change, changedelete', function()
local r = calc_signs({
{ 'delete', 1, 2, 0, 1 },
{ 'change', 1, 3, 1, 2 },
{ 'delete', 1, 4, 0, 2 },
{ 'change', 1, 5, 1, 3 },
})
-- TODO(lewis6991): not perfect. Better signs would be
-- { 'delete', 1, 1 },
-- { 'changedelete', 2, 1 },
-- { 'change', 3, 1 },
eq({
{ 'delete', 1, 1 },
{ 'change', 2, 1 },
{ 'delete', 2, 1 },
{ 'change', 3, 1 },
}, r)
end)
end)