mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-29 18:46:55 -04:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d1a8b05401 | ||
|
|
de22238589 | ||
|
|
52a2e4c1dc | ||
|
|
e953659ebf | ||
|
|
b33ec5a050 | ||
|
|
fbfec75a99 | ||
|
|
3e73dacce3 | ||
|
|
684d1e955e | ||
|
|
f67824b349 | ||
|
|
c53752a5f9 | ||
|
|
dc163bfc4d | ||
|
|
3c436b3457 | ||
|
|
526d02de1c | ||
|
|
a905a28e41 | ||
|
|
5964472ec1 | ||
|
|
c77c02c879 | ||
|
|
d26350502c | ||
|
|
2ddd3ddfc0 | ||
|
|
df3cd941d7 | ||
|
|
ea28529cbb | ||
|
|
db592e1dc6 | ||
|
|
863a65cf94 | ||
|
|
5eeaebde98 | ||
|
|
e059641f3f | ||
|
|
fbac05fff7 | ||
|
|
99e05ee138 | ||
|
|
7144691ae5 | ||
|
|
fb170b55a2 | ||
|
|
903e65ae8d | ||
|
|
e56b05cbac | ||
|
|
9e91aa8b43 | ||
|
|
7977c2deb5 | ||
|
|
d7fb441a3c | ||
|
|
f03e1e4e42 | ||
|
|
91f83c6be7 | ||
|
|
fd581ef70c | ||
|
|
f98b2edae5 | ||
|
|
97ced9e14f | ||
|
|
ea09686770 | ||
|
|
c769e5828c | ||
|
|
33410213f1 | ||
|
|
af57dbd225 | ||
|
|
66a253916e | ||
|
|
188890fc49 | ||
|
|
3e15315339 | ||
|
|
4fa8586191 | ||
|
|
3a295f34df | ||
|
|
1ac3ae1ad1 | ||
|
|
34404162e6 | ||
|
|
e6e95343af | ||
|
|
52316e628e | ||
|
|
e8b97c9fe2 | ||
|
|
ab03cf8bcf | ||
|
|
cc01c0de6d | ||
|
|
4f471f352f | ||
|
|
a63d5c5644 | ||
|
|
a47e72892a | ||
|
|
c4e71356bb | ||
|
|
b0f7cf092f | ||
|
|
808b35d8f2 | ||
|
|
4aa9147dfa | ||
|
|
14ec0cd92e | ||
|
|
36c6462a53 | ||
|
|
2ec0b671e6 | ||
|
|
3103398e31 | ||
|
|
e78e829e3a | ||
|
|
eceb3a5aa6 | ||
|
|
8a9eefa4d2 | ||
|
|
11316b7a48 | ||
|
|
8f3ccd07db | ||
|
|
79334b84f0 | ||
|
|
39e84e13f4 | ||
|
|
0c3eab4059 | ||
|
|
05089b9a9a | ||
|
|
d90fedfbf8 | ||
|
|
6af0afdb1c | ||
|
|
6cb5e628b3 | ||
|
|
a4239c7a37 | ||
|
|
fc0b14edef | ||
|
|
6b9f43c73b | ||
|
|
1358662a21 | ||
|
|
ffe5e16688 | ||
|
|
575afa1377 | ||
|
|
d22f6d73ff | ||
|
|
867975ad31 | ||
|
|
24a07ae85a | ||
|
|
c370a5e728 | ||
|
|
dba0edb998 | ||
|
|
69040f094d | ||
|
|
8c46a0110d | ||
|
|
a30d924afe | ||
|
|
f4c188fa5b | ||
|
|
7b4b42abd6 | ||
|
|
2f76ef58f1 | ||
|
|
e436922eb6 | ||
|
|
056cc14821 | ||
|
|
c686c7f9ed | ||
|
|
b27a2ce39a | ||
|
|
e3f21f0588 | ||
|
|
3375cc1b3d | ||
|
|
ed98b60078 | ||
|
|
e76fa5a6cb | ||
|
|
a77aa4d75a | ||
|
|
68a2a4c6b5 | ||
|
|
8858f03606 | ||
|
|
5670c0a301 | ||
|
|
c953871ec7 | ||
|
|
b0c19b291f | ||
|
|
8a022ddf0e | ||
|
|
e16f1ba84f | ||
|
|
19df238b77 | ||
|
|
092363a986 | ||
|
|
23d39c79b2 | ||
|
|
0141bbde0e | ||
|
|
4c7d363959 | ||
|
|
41f86f6535 | ||
|
|
90feb4bae6 | ||
|
|
1d40bd1707 | ||
|
|
17df42e517 | ||
|
|
fd66499c8f | ||
|
|
e001183768 | ||
|
|
268d8f99b8 | ||
|
|
212e19f598 | ||
|
|
1c82924307 | ||
|
|
c8066c54b5 | ||
|
|
438038a4f1 | ||
|
|
f23547580a | ||
|
|
3d79c6a3d3 | ||
|
|
bc8050d8ac | ||
|
|
b8900baf1a | ||
|
|
dcbebef897 | ||
|
|
7e9dffe1b9 | ||
|
|
c81333fefe | ||
|
|
5639af9918 | ||
|
|
ee348751a0 | ||
|
|
a92f0f7c89 | ||
|
|
6dca3e1766 | ||
|
|
850a82784a | ||
|
|
74acb3e86a | ||
|
|
9351af3829 | ||
|
|
7af7af27c6 | ||
|
|
db9373662a | ||
|
|
e189546acb | ||
|
|
c7733aa5e5 | ||
|
|
a0fd47348b | ||
|
|
6feb301c2a | ||
|
|
e66b162726 | ||
|
|
4f8816ebf2 | ||
|
|
50cf7ac5bc | ||
|
|
088445b7be | ||
|
|
c7fd218308 | ||
|
|
7ddb80a13e | ||
|
|
fecf2ab810 | ||
|
|
3232f46a8b | ||
|
|
7b757d1cfe | ||
|
|
843488bff4 | ||
|
|
a9d4ff2aee | ||
|
|
5d5471c017 | ||
|
|
8cdfc6758f | ||
|
|
010f430d1f | ||
|
|
79620fc6cf | ||
|
|
47f84b6aea | ||
|
|
de6d278e57 | ||
|
|
46b08e7d70 | ||
|
|
eb9fbb0a33 | ||
|
|
448ff80d7d | ||
|
|
74f9b8a3b4 | ||
|
|
25c4aa532e | ||
|
|
e7aacafc2e | ||
|
|
d78d694959 | ||
|
|
a67a08eeac | ||
|
|
f6d6b5dec0 | ||
|
|
ce98279896 | ||
|
|
e0e4138396 | ||
|
|
014daf7bc0 | ||
|
|
67d6b69115 | ||
|
|
956372cf8a | ||
|
|
290e865584 | ||
|
|
6248091e9c | ||
|
|
3ada4dde12 | ||
|
|
ef82f39431 | ||
|
|
21a4522a51 | ||
|
|
154bd975a6 | ||
|
|
fbe54512a8 | ||
|
|
7fd0e55b7f | ||
|
|
13b04e9e8c | ||
|
|
8a1937787d | ||
|
|
a94c703afb | ||
|
|
6d7a7afbbc | ||
|
|
6abcfd5cba | ||
|
|
a95d3e26b3 | ||
|
|
af45692e24 | ||
|
|
fed2aaf37f | ||
|
|
b2ae651686 | ||
|
|
e875d6b448 | ||
|
|
53979f7cec | ||
|
|
9c0d860980 | ||
|
|
4aea005f26 | ||
|
|
39e9266089 | ||
|
|
f999c90a7b | ||
|
|
502723421b | ||
|
|
5173d7f5e1 | ||
|
|
349a7d2453 | ||
|
|
f3837000dd | ||
|
|
d1b093e703 | ||
|
|
c66a2c3465 | ||
|
|
ba7d639940 | ||
|
|
cad84c9e74 | ||
|
|
5e475355bf | ||
|
|
304d74370e | ||
|
|
2bdefe2049 | ||
|
|
faed509bfd | ||
|
|
b8d9443999 | ||
|
|
de84b6c4b9 | ||
|
|
610eddfe05 | ||
|
|
1ef6f4c0e1 | ||
|
|
ae798157d2 | ||
|
|
a45b22e12f | ||
|
|
ba96baee32 | ||
|
|
d890238c7b | ||
|
|
46ae55f91e | ||
|
|
0ff5b74d80 | ||
|
|
225c563c63 | ||
|
|
77881a9c7d | ||
|
|
c7f9d5801b | ||
|
|
77622e5638 | ||
|
|
6422b399e7 | ||
|
|
3016469708 | ||
|
|
8b371ada73 | ||
|
|
fc49068a6f | ||
|
|
95297f72a3 | ||
|
|
121872ac27 | ||
|
|
d072b0c75e | ||
|
|
70a46028e1 | ||
|
|
e4e04cfa8f | ||
|
|
2e7b935bfb | ||
|
|
71a9389ca4 | ||
|
|
b77f3160f1 | ||
|
|
92bde6dda4 | ||
|
|
d73a236d7c | ||
|
|
fcf20f3b93 | ||
|
|
9578329cd9 | ||
|
|
4c5fa83566 | ||
|
|
755ae0ef84 | ||
|
|
7410acd1aa | ||
|
|
4db22aec0e | ||
|
|
a91d977f89 | ||
|
|
445a625b56 | ||
|
|
cd2c01d1cf | ||
|
|
17316ca061 | ||
|
|
54fb73080a | ||
|
|
524bf83a4a | ||
|
|
7fcd1d2a6e | ||
|
|
fdf6a9cc2b | ||
|
|
57f86b8f90 | ||
|
|
6160d85d4f | ||
|
|
64224e7caa | ||
|
|
d56bb0b8ef | ||
|
|
d238d8952b | ||
|
|
86d5654d20 | ||
|
|
6dfef08efc | ||
|
|
c1c6e2fac2 | ||
|
|
0b4f9f8c76 | ||
|
|
a658cd4076 | ||
|
|
69718fb557 | ||
|
|
b9b2f58bc8 | ||
|
|
bdb0b9ae6e | ||
|
|
2866827ca8 | ||
|
|
932b0b593e | ||
|
|
3ee2ad511e | ||
|
|
debc58b6c5 | ||
|
|
fab2e14b55 | ||
|
|
81f80ce968 | ||
|
|
95426c5e46 | ||
|
|
4ffc9a5395 | ||
|
|
86ac309e08 | ||
|
|
9987e65c35 | ||
|
|
69f4292fe3 | ||
|
|
682be18507 | ||
|
|
2ca2acaca5 | ||
|
|
5f4c29d7b5 | ||
|
|
44de380c2b | ||
|
|
37bdbd9a21 | ||
|
|
767ef31661 | ||
|
|
bb64fe4309 | ||
|
|
c81c046615 | ||
|
|
a905165046 | ||
|
|
7077ea428f | ||
|
|
f2880ecb46 | ||
|
|
966733240c | ||
|
|
25ddac0d8f | ||
|
|
7c09ce3871 | ||
|
|
e6e4513f45 | ||
|
|
9c6239df3d | ||
|
|
c26650258d | ||
|
|
c943cdd8d2 | ||
|
|
ab5a8091f5 | ||
|
|
95678b1b43 | ||
|
|
f947214ce0 | ||
|
|
8c65f63e76 | ||
|
|
085904d209 | ||
|
|
cc377b5c49 | ||
|
|
1f97ddd9b1 | ||
|
|
5cef0f2921 | ||
|
|
61e14c46b2 | ||
|
|
1f482e585e | ||
|
|
41b54d742f | ||
|
|
83dfc3b28b | ||
|
|
017d44ff17 | ||
|
|
168fbe0a6c | ||
|
|
e1f41b653c |
@@ -0,0 +1,13 @@
|
||||
# adapted from https://github.com/devcontainers/images/blob/main/src/go/.devcontainer/Dockerfile
|
||||
|
||||
# [Choice] Go version (use -bullseye variants on local arm64/Apple Silicon): 1, 1.19, 1.18, 1-bullseye, 1.19-bullseye, 1.18-bullseye, 1-buster, 1.19-buster, 1.18-buster
|
||||
ARG VARIANT=1-bullseye
|
||||
FROM golang:${VARIANT}
|
||||
|
||||
RUN go install mvdan.cc/gofumpt@latest
|
||||
RUN curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.50.0
|
||||
RUN golangci-lint --version
|
||||
|
||||
# [Optional] Uncomment this section to install additional OS packages.
|
||||
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
||||
# && apt-get -y install --no-install-recommends <your-package-list-here>
|
||||
@@ -0,0 +1,69 @@
|
||||
// adapted from https://github.com/devcontainers/images/blob/main/src/go/.devcontainer/devcontainer.json
|
||||
{
|
||||
"build": {
|
||||
"dockerfile": "./Dockerfile",
|
||||
"context": "."
|
||||
},
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/common-utils:1": {
|
||||
"installZsh": "true",
|
||||
"username": "vscode",
|
||||
"uid": "1000",
|
||||
"gid": "1000",
|
||||
"upgradePackages": "true"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/go:1": {
|
||||
"version": "none"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/git:1": {
|
||||
"version": "latest",
|
||||
"ppa": "false"
|
||||
}
|
||||
},
|
||||
"overrideFeatureInstallOrder": [
|
||||
"ghcr.io/devcontainers/features/common-utils"
|
||||
],
|
||||
// not sure if we actually need these
|
||||
"runArgs": [
|
||||
"--cap-add=SYS_PTRACE",
|
||||
"--security-opt",
|
||||
"seccomp=unconfined"
|
||||
],
|
||||
// Configure tool-specific properties.
|
||||
"customizations": {
|
||||
// Configure properties specific to VS Code.
|
||||
"vscode": {
|
||||
// Set *default* container specific settings.json values on container create.
|
||||
"settings": {
|
||||
"go.toolsManagement.checkForUpdates": "local",
|
||||
"go.useLanguageServer": true,
|
||||
"go.gopath": "/go",
|
||||
"[go]": {
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": true
|
||||
}
|
||||
},
|
||||
"go.lintTool": "golangci-lint",
|
||||
"gopls": {
|
||||
"formatting.gofumpt": true,
|
||||
"usePlaceholders": false // add parameter placeholders when completing a function
|
||||
},
|
||||
"files.eol": "\n"
|
||||
},
|
||||
// Add the IDs of extensions you want installed when the container is created.
|
||||
"extensions": [
|
||||
"golang.Go"
|
||||
]
|
||||
}
|
||||
},
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
// "postCreateCommand": "go version",
|
||||
|
||||
// See https://www.kenmuse.com/blog/avoiding-dubious-ownership-in-dev-containers/ for the safe.directory part
|
||||
// The defaultBranch part is required for our deprecated integration tests.
|
||||
"postStartCommand": "git config --global --add safe.directory ${containerWorkspaceFolder} && git config --global init.defaultBranch master",
|
||||
|
||||
// Set `remoteUser` to `root` to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
|
||||
"remoteUser": "vscode"
|
||||
}
|
||||
@@ -4,7 +4,6 @@ about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
@@ -12,6 +11,7 @@ A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
@@ -23,10 +23,15 @@ A clear and concise description of what you expected to happen.
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. Windows]
|
||||
- Lazygit Version [e.g. v0.1.45]
|
||||
- The last commit id if you built project from sources (run : ```git rev-parse HEAD```)
|
||||
**Version info:**
|
||||
_Run `lazygit --version` and paste the result here_
|
||||
_Run `git --version` and paste the result here_
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
||||
**Note:** please try updating to the latest version or [manually building](https://github.com/jesseduffield/lazygit/#manual) the latest `master` to see if the issue still occurs.
|
||||
|
||||
<!--
|
||||
If you want to try and debug this issue yourself, you can run `lazygit --debug` in one terminal panel and `lazygit --logs` in another to view the logs.
|
||||
-->
|
||||
|
||||
@@ -4,7 +4,6 @@ about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
@@ -18,3 +17,13 @@ A clear and concise description of any alternative solutions or features you've
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
|
||||
<!--
|
||||
You may be able to add your desired feature with a custom command. Check out the examples here: https://github.com/jesseduffield/lazygit/wiki/Custom-Commands-Compendium
|
||||
|
||||
If a custom command does what you want but you still want to see the feature built-in to lazygit, feel free to paste the custom command into the issue to help us better understand the functionality you want.
|
||||
|
||||
We also encourage you to put up a PR yourself! Who cares if you've never written Go before, neither did any of the existing contributors before their first lazygit PR! Check out the PR tutorial here: https://www.youtube.com/watch?v=kNavnhzZHtk&ab_channel=JesseDuffield
|
||||
|
||||
Also check out the contributing guide here: https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md
|
||||
-->
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
- **PR Description**
|
||||
|
||||
- **Please check if the PR fulfills these requirements**
|
||||
|
||||
* [ ] Cheatsheets are up-to-date (run `go run scripts/cheatsheet/main.go generate`)
|
||||
* [ ] Code has been formatted (see [here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#code-formatting))
|
||||
* [ ] Tests have been added/updated (see [here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md) for the integration test guide)
|
||||
* [ ] Text is internationalised (see [here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#internationalisation))
|
||||
* [ ] Docs (specifically `docs/Config.md`) have been updated if necessary
|
||||
* [ ] You've read through your own file changes for silly mistakes etc
|
||||
@@ -1,28 +0,0 @@
|
||||
name: automerge
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- labeled
|
||||
- unlabeled
|
||||
- synchronize
|
||||
- opened
|
||||
- edited
|
||||
- ready_for_review
|
||||
- reopened
|
||||
- unlocked
|
||||
pull_request_review:
|
||||
types:
|
||||
- submitted
|
||||
check_suite:
|
||||
types:
|
||||
- completed
|
||||
status: {}
|
||||
jobs:
|
||||
automerge:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: automerge
|
||||
uses: "pascalgn/automerge-action@135f0bdb927d9807b5446f7ca9ecc2c51de03c4a"
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
MERGE_METHOD: rebase
|
||||
+39
-10
@@ -46,14 +46,39 @@ jobs:
|
||||
# we're passing -short so that we skip the integration tests, which will be run in parallel below
|
||||
run: |
|
||||
go test ./... -short
|
||||
integration-tests:
|
||||
integration-tests-old:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
parallelism: [5]
|
||||
index: [0,1,2,3,4]
|
||||
name: "Integration Tests (${{ matrix.index }}/${{ matrix.parallelism }})"
|
||||
name: "Integration Tests (Old pattern) (${{ matrix.index }}/${{ matrix.parallelism }})"
|
||||
env:
|
||||
GOFLAGS: -mod=vendor
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: 1.18.x
|
||||
- name: Cache build
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: |
|
||||
~/.cache/go-build
|
||||
~/go/pkg/mod
|
||||
key: ${{runner.os}}-go-${{hashFiles('**/go.sum')}}-test
|
||||
restore-keys: |
|
||||
${{runner.os}}-go-
|
||||
- name: Test code
|
||||
# for file.allow thing see https://vielmetti.typepad.com/logbook/2022/10/git-security-fixes-lead-to-fatal-transport-file-not-allowed-error-in-ci-systems-cve-2022-39253.html
|
||||
run: |
|
||||
git config --global protocol.file.allow always && PARALLEL_TOTAL=${{ matrix.parallelism }} PARALLEL_INDEX=${{ matrix.index }} go test pkg/integration/deprecated/*.go
|
||||
integration-tests:
|
||||
runs-on: ubuntu-latest
|
||||
name: "Integration Tests"
|
||||
env:
|
||||
GOFLAGS: -mod=vendor
|
||||
steps:
|
||||
@@ -74,7 +99,7 @@ jobs:
|
||||
${{runner.os}}-go-
|
||||
- name: Test code
|
||||
run: |
|
||||
PARALLEL_TOTAL=${{ matrix.parallelism }} PARALLEL_INDEX=${{ matrix.index }} go test pkg/gui/gui_test.go
|
||||
go test pkg/integration/clients/*.go
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
@@ -105,7 +130,13 @@ jobs:
|
||||
- name: Build darwin binary
|
||||
run: |
|
||||
GOOS=darwin go build
|
||||
check-cheatsheet:
|
||||
- name: Build integration test binary
|
||||
run: |
|
||||
GOOS=linux go build cmd/integration_test/main.go
|
||||
- name: Build integration test injector
|
||||
run: |
|
||||
GOOS=linux go build pkg/integration/clients/injector/main.go
|
||||
check-codebase:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GOFLAGS: -mod=vendor
|
||||
@@ -129,6 +160,10 @@ jobs:
|
||||
- name: Check Cheatsheet
|
||||
run: |
|
||||
go run scripts/cheatsheet/main.go check
|
||||
- name: Check Vendor Directory
|
||||
# ensure our vendor directory matches up with our go modules
|
||||
run: |
|
||||
go mod vendor && git diff --exit-code || (echo "Unexpected change to vendor directory. Run 'go mod vendor' locally and commit the changes" && exit 1)
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
@@ -153,12 +188,6 @@ jobs:
|
||||
uses: golangci/golangci-lint-action@v3.1.0
|
||||
with:
|
||||
version: latest
|
||||
- name: Format code
|
||||
run: |
|
||||
if [ $(find . ! -path "./vendor/*" -name "*.go" -exec gofmt -s -d {} \;|wc -l) -gt 0 ]; then
|
||||
find . ! -path "./vendor/*" -name "*.go" -exec gofmt -s -d {} \;
|
||||
exit 1
|
||||
fi
|
||||
- name: errors
|
||||
run: golangci-lint run
|
||||
if: ${{ failure() }}
|
||||
|
||||
+10
-1
@@ -27,17 +27,26 @@ lazygit.exe
|
||||
!.circleci/
|
||||
!.github/
|
||||
!.vscode/
|
||||
!.devcontainer/
|
||||
|
||||
# these are for our integration tests
|
||||
!.git_keep
|
||||
!.gitmodules_keep
|
||||
|
||||
test/git_server/data
|
||||
|
||||
# we'll scrap these lines once we've fully moved over to the new integration test approach
|
||||
test/integration/*/actual/
|
||||
test/integration/*/used_config/
|
||||
# these sample hooks waste too much space
|
||||
test/integration/*/expected/**/hooks/
|
||||
test/integration/*/expected_remote/**/hooks/
|
||||
|
||||
test/integration_new/**/actual/
|
||||
test/integration_new/**/used_config/
|
||||
# these sample hooks waste too much space
|
||||
test/integration_new/**/expected/**/hooks/
|
||||
test/integration_new/**/expected_remote/**/hooks/
|
||||
|
||||
oryxBuildBinary
|
||||
__debug_bin
|
||||
__debug_bin
|
||||
|
||||
+1
-5
@@ -32,12 +32,8 @@ checksum:
|
||||
snapshot:
|
||||
name_template: '{{ .Tag }}-next'
|
||||
changelog:
|
||||
use: github-native
|
||||
sort: asc
|
||||
filters:
|
||||
exclude:
|
||||
- '^docs:'
|
||||
- '^test:'
|
||||
- '^bump'
|
||||
brews:
|
||||
-
|
||||
# Repository to push the tap to.
|
||||
|
||||
Vendored
+53
@@ -0,0 +1,53 @@
|
||||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "Generate cheatsheet",
|
||||
"type": "process",
|
||||
"command": "go run scripts/cheatsheet/main.go ",
|
||||
"problemMatcher": [],
|
||||
},
|
||||
{
|
||||
"label": "Bump gocui",
|
||||
"type": "shell",
|
||||
"command": "./scripts/bump_gocui.sh",
|
||||
"problemMatcher": [],
|
||||
},
|
||||
{
|
||||
"label": "Bump lazycore",
|
||||
"type": "shell",
|
||||
"command": "./scripts/bump_lazycore.sh",
|
||||
"problemMatcher": [],
|
||||
},
|
||||
{
|
||||
"label": "Run current file integration test",
|
||||
"type": "shell",
|
||||
"command": "go run cmd/integration_test/main.go cli ${relativeFile}",
|
||||
"problemMatcher": [],
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Run current file integration test (slow)",
|
||||
"type": "shell",
|
||||
"command": "go run cmd/integration_test/main.go cli --slow ${relativeFile}",
|
||||
"problemMatcher": [],
|
||||
"group": {
|
||||
"kind": "test",
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Run current file integration test (sandbox)",
|
||||
"type": "shell",
|
||||
"command": "go run cmd/integration_test/main.go cli --sandbox ${relativeFile}",
|
||||
"problemMatcher": [],
|
||||
"group": {
|
||||
"kind": "test",
|
||||
}
|
||||
}
|
||||
],
|
||||
}
|
||||
+79
-9
@@ -23,6 +23,26 @@ welcome your pull requests:
|
||||
|
||||
If you've never written Go in your life, then join the club! Lazygit was the maintainer's first Go program, and most contributors have never used Go before. Go is widely considered an easy-to-learn language, so if you're looking for an open source project to gain dev experience, you've come to the right place.
|
||||
|
||||
## Running in a VSCode dev container
|
||||
|
||||
If you want to spare yourself the hassle of setting up your dev environment yourself (i.e. installing Go, extensions, and extra tools), you can run the Lazygit code in a VSCode dev container like so:
|
||||
|
||||

|
||||
|
||||
This requires that:
|
||||
* you have docker installed
|
||||
* you have the dev containers extension installed in VSCode
|
||||
|
||||
See [here](https://code.visualstudio.com/docs/devcontainers/containers) for more info about dev containers.
|
||||
|
||||
## Running in a Github Codespace
|
||||
|
||||
If you want to start contributing to Lazygit with the click of a button, you can open the lazygit codebase in a Codespace:
|
||||
|
||||

|
||||
|
||||
This allows you to contribute to Lazygit without needing to install anything on your local machine. The Codespace has all the necessary tools and extensions pre-installed.
|
||||
|
||||
## Code of conduct
|
||||
|
||||
Please note by participating in this project, you agree to abide by the [code of conduct].
|
||||
@@ -63,15 +83,21 @@ by setting [`formatting.gofumpt`](https://github.com/golang/tools/blob/master/go
|
||||
```jsonc
|
||||
// .vscode/settings.json
|
||||
{
|
||||
"gopls": {
|
||||
"formatting.gofumpt": true
|
||||
}
|
||||
"gopls": {
|
||||
"formatting.gofumpt": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To run gofumpt from your terminal go:
|
||||
|
||||
```
|
||||
go install mvdan.cc/gofumpt@latest && gofumpt -l -w .
|
||||
```
|
||||
|
||||
## Internationalisation
|
||||
|
||||
Boy that's a hard word to spell. Anyway, lazygit is translated into several languages within the pkg/i18n package. If you need to render text to the user, you should add a new field to the TranslationSet struct in `pkg/i18n/english.go` and add the actual content within the `EnglishTranslationSet()` method in the same file. Although it is appreciated if you translate the text into other languages, it's not expected of you (google translate will likely do a bad job anyway!).
|
||||
Boy that's a hard word to spell. Anyway, lazygit is translated into several languages within the pkg/i18n package. If you need to render text to the user, you should add a new field to the TranslationSet struct in `pkg/i18n/english.go` and add the actual content within the `EnglishTranslationSet()` method in the same file. Then you can access via `gui.Tr.YourNewText` (or `app.Tr.YourNewText`, etc). Although it is appreciated if you translate the text into other languages, it's not expected of you (google translate will likely do a bad job anyway!).
|
||||
|
||||
## Debugging
|
||||
|
||||
@@ -82,6 +108,7 @@ From most places in the codebase you have access to a logger e.g. `gui.Log.Warn(
|
||||
If you find that the existing logs are too noisy, you can set the log level with e.g. `LOG_LEVEL=warn go run main.go -debug` and then only use `Warn` logs yourself.
|
||||
|
||||
If you need to log from code in the vendor directory (e.g. the `gocui` package), you won't have access to the logger, but you can easily add logging support by adding the following:
|
||||
|
||||
```go
|
||||
func newLogger() *logrus.Entry {
|
||||
// REPLACE THE BELOW PATH WITH YOUR ACTUAL LOG PATH (YOU'LL SEE THIS PRINTED WHEN YOU RUN `lazygit --logs`
|
||||
@@ -118,24 +145,50 @@ If you want to trigger a debug session from VSCode, you can use the following sn
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
"program": "main.go",
|
||||
"args": [
|
||||
"--debug"
|
||||
],
|
||||
"args": ["--debug"],
|
||||
"console": "externalTerminal" // <-- you need this to actually see the lazygit UI in a window while debugging
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Profiling
|
||||
|
||||
If you want to investigate what's contributing to CPU usage you can add the following to the top of the `main()` function in `main.go`
|
||||
|
||||
```go
|
||||
import "runtime/pprof"
|
||||
|
||||
func main() {
|
||||
f, err := os.Create("cpu.prof")
|
||||
if err != nil {
|
||||
log.Fatal("could not create CPU profile: ", err)
|
||||
}
|
||||
defer f.Close()
|
||||
if err := pprof.StartCPUProfile(f); err != nil {
|
||||
log.Fatal("could not start CPU profile: ", err)
|
||||
}
|
||||
defer pprof.StopCPUProfile()
|
||||
...
|
||||
```
|
||||
|
||||
Then run lazygit, and afterwards, from your terminal, run:
|
||||
|
||||
```sh
|
||||
go tool pprof --web cpu.prof
|
||||
```
|
||||
|
||||
That should open an application which allows you to view the breakdown of CPU usage.
|
||||
|
||||
## Testing
|
||||
|
||||
Lazygit has two kinds of tests: unit tests and integration tests. Unit tests go in files that end in `_test.go`, and are written in Go. Lazygit has its own integration test system where you can build a sandbox repo with a shell script, record yourself doing something, and commit the resulting repo snapshot. It's pretty damn cool! To learn more see [here](https://github.com/jesseduffield/lazygit/blob/master/docs/Integration_Tests.md)
|
||||
Lazygit has two kinds of tests: unit tests and integration tests. Unit tests go in files that end in `_test.go`, and are written in Go. For integration tests, see [here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md)
|
||||
|
||||
## Updating Gocui
|
||||
|
||||
Sometimes you will need to make a change in the gocui fork (https://github.com/jesseduffield/gocui). Gocui is the package responsible for rendering windows and handling user input. Here's the typical process to follow:
|
||||
|
||||
1. Make the changes in gocui inside the vendor directory so it's easy to test against lazygit
|
||||
1. Make the changes in gocui inside lazygit's vendor directory so it's easy to test against lazygit
|
||||
2. Copy the changes over to the actual gocui repo (clone it if you haven't already, and use the `awesome` branch, not `master`)
|
||||
3. Raise a PR on the gocui repo with your changes
|
||||
4. After that PR is merged, make a PR in lazygit bumping the gocui version. You can bump the version by running the following at the lazygit repo root:
|
||||
@@ -146,6 +199,23 @@ Sometimes you will need to make a change in the gocui fork (https://github.com/j
|
||||
|
||||
5. Raise a PR in lazygit with those changes
|
||||
|
||||
## Updating Lazycore
|
||||
|
||||
[Lazycore](https://github.com/jesseduffield/lazycore) is a repo containing shared functionality between lazygit and lazydocker. Sometimes you will need to make a change to that repo and import the changes into lazygit. Similar to updating Gocui, here's what you do:
|
||||
|
||||
1. Make the changes in lazycore inside lazygit's vendor directory so it's easy to test against lazygit
|
||||
2. Copy the changes over to the actual lazycore repo (clone it if you haven't already, and use the `master` branch)
|
||||
3. Raise a PR on the lazycore repo with your changes
|
||||
4. After that PR is merged, make a PR in lazygit bumping the lazycore version. You can bump the version by running the following at the lazygit repo root:
|
||||
|
||||
```sh
|
||||
./scripts/bump_lazycore.sh
|
||||
```
|
||||
|
||||
Or if you're using VSCode, there is a bump lazycore task you can find by going `cmd+shift+p` and typing 'Run task'
|
||||
|
||||
5. Raise a PR in lazygit with those changes
|
||||
|
||||
## Improvements
|
||||
|
||||
If you can think of any way to improve these docs let us know.
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/integration/clients"
|
||||
)
|
||||
|
||||
var usage = `
|
||||
Usage:
|
||||
See https://github.com/jesseduffield/lazygit/tree/master/pkg/integration/README.md
|
||||
|
||||
CLI mode:
|
||||
> go run cmd/integration_test/main.go cli [--slow] [--sandbox] <test1> <test2> ...
|
||||
If you pass no test names, it runs all tests
|
||||
Accepted environment variables:
|
||||
KEY_PRESS_DELAY (e.g. 200): the number of milliseconds to wait between keypresses
|
||||
MODE:
|
||||
* ask (default): if a snapshot test fails, asks if you want to update the snapshot
|
||||
* check: if a snapshot test fails, exits with an error
|
||||
* update: if a snapshot test fails, updates the snapshot
|
||||
* sandbox: uses the test's setup step to run the test in a sandbox where you can do whatever you want
|
||||
|
||||
TUI mode:
|
||||
> go run cmd/integration_test/main.go tui
|
||||
This will open up a terminal UI where you can run tests
|
||||
|
||||
Help:
|
||||
> go run cmd/integration_test/main.go help
|
||||
`
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
log.Fatal(usage)
|
||||
}
|
||||
|
||||
switch os.Args[1] {
|
||||
case "help":
|
||||
fmt.Println(usage)
|
||||
case "cli":
|
||||
testNames := os.Args[2:]
|
||||
slow := false
|
||||
sandbox := false
|
||||
// get the next arg if it's --slow
|
||||
if len(os.Args) > 2 {
|
||||
if os.Args[2] == "--slow" || os.Args[2] == "-slow" {
|
||||
testNames = os.Args[3:]
|
||||
slow = true
|
||||
} else if os.Args[2] == "--sandbox" || os.Args[2] == "-sandbox" {
|
||||
testNames = os.Args[3:]
|
||||
sandbox = true
|
||||
}
|
||||
}
|
||||
|
||||
clients.RunCLI(testNames, slow, sandbox)
|
||||
case "tui":
|
||||
clients.RunTUI()
|
||||
default:
|
||||
log.Fatal(usage)
|
||||
}
|
||||
}
|
||||
+20
-20
@@ -12,6 +12,10 @@ For old installations (slightly embarrassing: I didn't realise at the time that
|
||||
- MacOS: `~/Library/Application Support/jesseduffield/lazygit/config.yml`
|
||||
- Windows: `%APPDATA%\jesseduffield\lazygit\config.yml`
|
||||
|
||||
If you want to change the config directory:
|
||||
|
||||
- MacOS: `export XDG_CONFIG_HOME="$HOME/.config"`
|
||||
|
||||
## Default
|
||||
|
||||
```yaml
|
||||
@@ -25,7 +29,6 @@ gui:
|
||||
language: 'auto' # one of 'auto' | 'en' | 'zh' | 'pl' | 'nl' | 'ja' | 'ko'
|
||||
timeFormat: '02 Jan 06 15:04 MST' # https://pkg.go.dev/time#Time.Format
|
||||
theme:
|
||||
lightTheme: false # For terminals with a light background
|
||||
activeBorderColor:
|
||||
- green
|
||||
- bold
|
||||
@@ -101,7 +104,7 @@ confirmOnQuit: false
|
||||
# determines whether hitting 'esc' will quit the application when there is nothing to cancel/close
|
||||
quitOnTopLevelReturn: false
|
||||
disableStartupPopups: false
|
||||
notARepository: 'prompt' # one of: 'prompt' | 'create' | 'skip'
|
||||
notARepository: 'prompt' # one of: 'prompt' | 'create' | 'skip' | 'quit'
|
||||
promptToReturnFromSubprocess: true # display confirmation when subprocess terminates
|
||||
keybinding:
|
||||
universal:
|
||||
@@ -219,6 +222,7 @@ keybinding:
|
||||
viewBisectOptions: 'b'
|
||||
stash:
|
||||
popStash: 'g'
|
||||
renameStash: 'r'
|
||||
commitFiles:
|
||||
checkoutCommitFile: 'c'
|
||||
main:
|
||||
@@ -274,7 +278,7 @@ os:
|
||||
|
||||
Lazygit will log an error if none of these options are set.
|
||||
|
||||
You can specify a line number you are currently at when in the line-by-line mode.
|
||||
You can specify the current line number when you're in the patch explorer.
|
||||
|
||||
```yaml
|
||||
os:
|
||||
@@ -339,23 +343,6 @@ The available attributes are:
|
||||
- reverse # useful for high-contrast
|
||||
- underline
|
||||
|
||||
## Light terminal theme
|
||||
|
||||
If you have issues with a light terminal theme where you can't read / see the text add these settings
|
||||
|
||||
```yaml
|
||||
gui:
|
||||
theme:
|
||||
lightTheme: true
|
||||
activeBorderColor:
|
||||
- black
|
||||
- bold
|
||||
inactiveBorderColor:
|
||||
- black
|
||||
selectedLineBgColor:
|
||||
- default
|
||||
```
|
||||
|
||||
## Highlighting the selected line
|
||||
|
||||
If you don't like the default behaviour of highlighting the selected line with a blue background, you can use the `selectedLineBgColor` and `selectedRangeBgColor` keys to customise the behaviour. If you just want to embolden the selected line (this was the original default), you can do the following:
|
||||
@@ -431,6 +418,14 @@ gui:
|
||||
|
||||
For all possible keybinding options, check [Custom_Keybindings.md](https://github.com/jesseduffield/lazygit/blob/master/docs/keybindings/Custom_Keybindings.md)
|
||||
|
||||
You can disable certain key bindings by specifying `null`.
|
||||
|
||||
```yaml
|
||||
keybinding:
|
||||
universal:
|
||||
edit: null # disable 'edit file'
|
||||
```
|
||||
|
||||
### Example Keybindings For Colemak Users
|
||||
|
||||
```yaml
|
||||
@@ -531,3 +526,8 @@ notARepository: 'create'
|
||||
# to skip without creating a new repo
|
||||
notARepository: 'skip'
|
||||
```
|
||||
|
||||
```yaml
|
||||
# to exit immediately if run outside of the Git repository
|
||||
notARepository: 'quit'
|
||||
```
|
||||
|
||||
@@ -8,7 +8,7 @@ customCommands:
|
||||
command: 'hub browse -- "commit/{{.SelectedLocalCommit.Sha}}"'
|
||||
context: 'commits'
|
||||
- key: 'a'
|
||||
command: "git {{if .SelectedFile.HasUnstagedChanges}} add {{else}} reset {{end}} {{.SelectedFile.Name}}"
|
||||
command: "git {{if .SelectedFile.HasUnstagedChanges}} add {{else}} reset {{end}} {{.SelectedFile.Name | quote}}"
|
||||
context: 'files'
|
||||
description: 'toggle file staged'
|
||||
- key: 'C'
|
||||
@@ -77,6 +77,7 @@ For a given custom command, here are the allowed fields:
|
||||
| loadingText | text to display while waiting for command to finish | no |
|
||||
| description | text to display in the keybindings menu that appears when you press 'x' | no |
|
||||
| stream | whether you want to stream the command's output to the Command Log panel | no |
|
||||
| showOutput | whether you want to show the command's output in a gui prompt | no |
|
||||
|
||||
### Contexts
|
||||
|
||||
@@ -135,13 +136,14 @@ If an option has no name the value will be displayed to the user in place of the
|
||||
|
||||
### Placeholder values
|
||||
|
||||
Your commands can contain placeholder strings using Go's [template syntax](https://jan.newmarch.name/go/template/chapter-template.html). The template syntax is pretty powerful, letting you do things like conditionals if you want, but for the most part you'll simply want to be accessing the fields on the following objects:
|
||||
Your commands can contain placeholder strings using Go's [template syntax](https://jan.newmarch.name/golang/template/chapter-template.html). The template syntax is pretty powerful, letting you do things like conditionals if you want, but for the most part you'll simply want to be accessing the fields on the following objects:
|
||||
|
||||
```
|
||||
SelectedLocalCommit
|
||||
SelectedReflogCommit
|
||||
SelectedSubCommit
|
||||
SelectedFile
|
||||
SelectedPath
|
||||
SelectedLocalBranch
|
||||
SelectedRemoteBranch
|
||||
SelectedRemote
|
||||
@@ -159,7 +161,7 @@ If your custom keybinding collides with an inbuilt keybinding that is defined fo
|
||||
|
||||
### Debugging
|
||||
|
||||
If you want to verify that your command actually does what you expect, you can wrap it in an 'echo' call and set `subprocess: true` so that it doesn't actually execute the command but you can see how the placeholders were resolved. Alternatively you can run lazygit in debug mode with `lazygit --debug` and in another terminal window run `lazygit --logs` to see which commands are actually run
|
||||
If you want to verify that your command actually does what you expect, you can wrap it in an 'echo' call and set `showOutput: true` so that it doesn't actually execute the command but you can see how the placeholders were resolved. Alternatively you can run lazygit in debug mode with `lazygit --debug` and in another terminal window run `lazygit --logs` to see which commands are actually run
|
||||
|
||||
### More Examples
|
||||
|
||||
|
||||
+1
-122
@@ -1,122 +1 @@
|
||||
# How To Make And Run Integration Tests For lazygit
|
||||
|
||||
Integration tests are located in `test/integration`. Each test will run a bash script to prepare a test repo, then replay a recorded lazygit session from within that repo, and then the resultant repo will be compared to an expected repo that was created upon the initial recording. Each integration test lives in its own directory, and the name of the directory becomes the name of the test. Within the directory must be the following files:
|
||||
|
||||
### `test.json`
|
||||
|
||||
An example of a `test.json` is:
|
||||
|
||||
```
|
||||
{ "description": "stage a file and commit the change", "speed": 20 }
|
||||
```
|
||||
|
||||
The `speed` key refers to the playback speed as a multiple of the original recording speed. So 20 means the test will run 20 times faster than the original recording speed. If a test fails for a given speed, it will drop the speed and re-test, until finally attempting the test at the original speed. If you omit the speed, it will default to 10.
|
||||
|
||||
### `setup.sh`
|
||||
|
||||
This is a bash script containing the instructions for creating the test repo from scratch. For example:
|
||||
|
||||
```
|
||||
#!/bin/sh
|
||||
|
||||
cd $1
|
||||
|
||||
git init
|
||||
|
||||
git config user.email "CI@example.com"
|
||||
git config user.name "CI"
|
||||
|
||||
echo test1 > myfile1
|
||||
git add .
|
||||
git commit -am "myfile1"
|
||||
```
|
||||
|
||||
Be sure to:
|
||||
|
||||
- ensure that by the end of the test you've got at least one commit in the repo, as we've had issues in the past when that wasn't the case.
|
||||
- set the git user email and name as above so that your own user details aren't included in the snapshot.
|
||||
|
||||
## Running tests
|
||||
|
||||
### From a TUI
|
||||
|
||||
You can run/record/sandbox tests via a TUI with the following command:
|
||||
|
||||
```
|
||||
go run test/lazyintegration/main.go
|
||||
```
|
||||
|
||||
This TUI makes much of the following documentation redundant, but feel free to read through anyway!
|
||||
|
||||
### From command line
|
||||
|
||||
To run all tests - assuming you're at the project root:
|
||||
|
||||
```
|
||||
go test ./pkg/gui/
|
||||
```
|
||||
|
||||
To run them in parallel
|
||||
|
||||
```
|
||||
PARALLEL=true go test ./pkg/gui
|
||||
```
|
||||
|
||||
To run a single test
|
||||
|
||||
```
|
||||
go test ./pkg/gui -run /<test name>
|
||||
# For example, to run the `tags` test:
|
||||
go test ./pkg/gui -run /tags
|
||||
```
|
||||
|
||||
To run a test at a certain speed
|
||||
|
||||
```
|
||||
SPEED=2 go test ./pkg/gui -run /<test name>
|
||||
```
|
||||
|
||||
To update a snapshot
|
||||
|
||||
```
|
||||
MODE=updateSnapshot go test ./pkg/gui -run /<test name>
|
||||
```
|
||||
|
||||
## Creating a new test
|
||||
|
||||
To create a new test:
|
||||
|
||||
1. Copy and paste an existing test directory and rename the new directory to whatever you want the test name to be. Update the test.json file's description to describe your test.
|
||||
2. Update the `setup.sh` any way you like
|
||||
3. If you want to have a config folder for just that test, create a `config` directory to contain a `config.yml` and optionally a `state.yml` file. Otherwise, the `test/default_test_config` directory will be used.
|
||||
4. From the lazygit root directory, run:
|
||||
|
||||
```
|
||||
MODE=record go test ./pkg/gui -run /<test name>
|
||||
```
|
||||
|
||||
5. Feel free to re-attempt recording as many times as you like. In the absence of a proper testing framework, the more deliberate your keypresses, the better!
|
||||
6. Once satisfied with the recording, stage all the newly created files: `test.json`, `setup.sh`, `recording.json` and the `expected` directory that contains a copy of the repo you created.
|
||||
|
||||
The resulting directory will look like:
|
||||
|
||||
```
|
||||
actual/ (the resulting repo(s) after running the test, ignored by git)
|
||||
expected/ (the 'snapshot' repo(s))
|
||||
config/ (need not be present)
|
||||
test.json
|
||||
setup.sh
|
||||
recording.json
|
||||
```
|
||||
|
||||
## Sandboxing
|
||||
|
||||
The integration tests serve a secondary purpose of providing a setup for easy sandboxing. If you want to run a test in sandbox mode (meaning the session won't be recorded and we won't create/update snapshots), go:
|
||||
|
||||
```
|
||||
MODE=sandbox go test ./pkg/gui -run /<test name>
|
||||
```
|
||||
|
||||
## Feedback
|
||||
|
||||
If you think this process can be improved, let me know! It shouldn't be too hard to change things.
|
||||
see new docs [here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md)
|
||||
|
||||
@@ -30,11 +30,11 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
## List Panel Navigation
|
||||
|
||||
<pre>
|
||||
<kbd>.</kbd>: next page
|
||||
<kbd>,</kbd>: previous page
|
||||
<kbd>.</kbd>: next page
|
||||
<kbd><</kbd>: scroll to top
|
||||
<kbd>></kbd>: scroll to bottom
|
||||
<kbd>/</kbd>: start search
|
||||
<kbd>></kbd>: scroll to bottom
|
||||
<kbd>H</kbd>: scroll left
|
||||
<kbd>L</kbd>: scroll right
|
||||
<kbd>]</kbd>: next tab
|
||||
@@ -102,7 +102,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>C</kbd>: commit changes using git editor
|
||||
<kbd>e</kbd>: edit file
|
||||
<kbd>o</kbd>: open file
|
||||
<kbd>i</kbd>: Ignore or Exclude file
|
||||
<kbd>i</kbd>: ignore or exclude file
|
||||
<kbd>r</kbd>: refresh files
|
||||
<kbd>s</kbd>: stash all changes
|
||||
<kbd>S</kbd>: view stash options
|
||||
@@ -140,59 +140,56 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
## Main Panel (Merging)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: return to files panel
|
||||
<kbd>M</kbd>: open external merge tool (git mergetool)
|
||||
<kbd>space</kbd>: pick hunk
|
||||
<kbd>b</kbd>: pick all hunks
|
||||
<kbd>e</kbd>: edit file
|
||||
<kbd>o</kbd>: open file
|
||||
<kbd>◄</kbd>: select previous conflict
|
||||
<kbd>►</kbd>: select next conflict
|
||||
<kbd>▲</kbd>: select previous hunk
|
||||
<kbd>▼</kbd>: select next hunk
|
||||
<kbd>e</kbd>: edit file
|
||||
<kbd>o</kbd>: open file
|
||||
<kbd>z</kbd>: undo
|
||||
<kbd>M</kbd>: open external merge tool (git mergetool)
|
||||
<kbd>space</kbd>: pick hunk
|
||||
<kbd>b</kbd>: pick all hunks
|
||||
<kbd>esc</kbd>: return to files panel
|
||||
</pre>
|
||||
|
||||
## Main Panel (Normal)
|
||||
|
||||
<pre>
|
||||
<kbd>mouse wheel down</kbd>: scroll down (fn+up)
|
||||
<kbd>mouse wheel up</kbd>: scroll up (fn+down)
|
||||
<kbd>mouse wheel ▼</kbd>: scroll down (fn+up)
|
||||
<kbd>mouse wheel ▲</kbd>: scroll up (fn+down)
|
||||
</pre>
|
||||
|
||||
## Main Panel (Patch Building)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: exit line-by-line mode
|
||||
<kbd>o</kbd>: open file
|
||||
<kbd>▲</kbd>: select previous line
|
||||
<kbd>▼</kbd>: select next line
|
||||
<kbd>◄</kbd>: select previous hunk
|
||||
<kbd>►</kbd>: select next hunk
|
||||
<kbd>ctrl+o</kbd>: copy the selected text to the clipboard
|
||||
<kbd>space</kbd>: add/remove line(s) to patch
|
||||
<kbd>v</kbd>: toggle drag select
|
||||
<kbd>V</kbd>: toggle drag select
|
||||
<kbd>a</kbd>: toggle select hunk
|
||||
<kbd>ctrl+o</kbd>: copy the selected text to the clipboard
|
||||
<kbd>o</kbd>: open file
|
||||
<kbd>e</kbd>: edit file
|
||||
<kbd>space</kbd>: add/remove line(s) to patch
|
||||
<kbd>esc</kbd>: exit custom patch builder
|
||||
</pre>
|
||||
|
||||
## Main Panel (Staging)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: return to files panel
|
||||
<kbd>space</kbd>: toggle line staged / unstaged
|
||||
<kbd>d</kbd>: delete change (git reset)
|
||||
<kbd>tab</kbd>: switch to other panel
|
||||
<kbd>o</kbd>: open file
|
||||
<kbd>▲</kbd>: select previous line
|
||||
<kbd>▼</kbd>: select next line
|
||||
<kbd>◄</kbd>: select previous hunk
|
||||
<kbd>►</kbd>: select next hunk
|
||||
<kbd>ctrl+o</kbd>: copy the selected text to the clipboard
|
||||
<kbd>e</kbd>: edit file
|
||||
<kbd>v</kbd>: toggle drag select
|
||||
<kbd>V</kbd>: toggle drag select
|
||||
<kbd>a</kbd>: toggle select hunk
|
||||
<kbd>ctrl+o</kbd>: copy the selected text to the clipboard
|
||||
<kbd>o</kbd>: open file
|
||||
<kbd>e</kbd>: edit file
|
||||
<kbd>esc</kbd>: return to files panel
|
||||
<kbd>tab</kbd>: switch to other panel (staged/unstaged changes)
|
||||
<kbd>space</kbd>: toggle line staged / unstaged
|
||||
<kbd>d</kbd>: delete change (git reset)
|
||||
<kbd>E</kbd>: edit hunk
|
||||
</pre>
|
||||
|
||||
@@ -241,6 +238,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>g</kbd>: pop
|
||||
<kbd>d</kbd>: drop
|
||||
<kbd>n</kbd>: new branch
|
||||
<kbd>r</kbd>: rename stash
|
||||
<kbd>enter</kbd>: view selected item's files
|
||||
</pre>
|
||||
|
||||
|
||||
@@ -30,11 +30,11 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
## 一覧パネルの操作
|
||||
|
||||
<pre>
|
||||
<kbd>.</kbd>: 次のページ
|
||||
<kbd>,</kbd>: 前のページ
|
||||
<kbd>.</kbd>: 次のページ
|
||||
<kbd><</kbd>: 最上部までスクロール
|
||||
<kbd>></kbd>: 最下部までスクロール
|
||||
<kbd>/</kbd>: 検索を開始
|
||||
<kbd>></kbd>: 最下部までスクロール
|
||||
<kbd>H</kbd>: 左スクロール
|
||||
<kbd>L</kbd>: 右スクロール
|
||||
<kbd>]</kbd>: 次のタブ
|
||||
@@ -48,6 +48,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>g</kbd>: pop
|
||||
<kbd>d</kbd>: drop
|
||||
<kbd>n</kbd>: 新しいブランチを作成
|
||||
<kbd>r</kbd>: Stashを変更
|
||||
<kbd>enter</kbd>: view selected item's files
|
||||
</pre>
|
||||
|
||||
@@ -199,59 +200,56 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
## メインパネル (Merging)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: ファイル一覧に戻る
|
||||
<kbd>M</kbd>: git mergetoolを開く
|
||||
<kbd>space</kbd>: pick hunk
|
||||
<kbd>b</kbd>: pick all hunks
|
||||
<kbd>e</kbd>: ファイルを編集
|
||||
<kbd>o</kbd>: ファイルを開く
|
||||
<kbd>◄</kbd>: 前のコンフリクトを選択
|
||||
<kbd>►</kbd>: 次のコンフリクトを選択
|
||||
<kbd>▲</kbd>: 前のhunkを選択
|
||||
<kbd>▼</kbd>: 次のhunkを選択
|
||||
<kbd>e</kbd>: ファイルを編集
|
||||
<kbd>o</kbd>: ファイルを開く
|
||||
<kbd>z</kbd>: アンドゥ
|
||||
<kbd>M</kbd>: git mergetoolを開く
|
||||
<kbd>space</kbd>: pick hunk
|
||||
<kbd>b</kbd>: pick all hunks
|
||||
<kbd>esc</kbd>: ファイル一覧に戻る
|
||||
</pre>
|
||||
|
||||
## メインパネル (Normal)
|
||||
|
||||
<pre>
|
||||
<kbd>mouse wheel down</kbd>: 下にスクロール (fn+up)
|
||||
<kbd>mouse wheel up</kbd>: 上にスクロール (fn+down)
|
||||
<kbd>mouse wheel ▼</kbd>: 下にスクロール (fn+up)
|
||||
<kbd>mouse wheel ▲</kbd>: 上にスクロール (fn+down)
|
||||
</pre>
|
||||
|
||||
## メインパネル (Patch Building)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: line-by-lineモードを終了
|
||||
<kbd>o</kbd>: ファイルを開く
|
||||
<kbd>▲</kbd>: 前の行を選択
|
||||
<kbd>▼</kbd>: 次の行を選択
|
||||
<kbd>◄</kbd>: 前のhunkを選択
|
||||
<kbd>►</kbd>: 次のhunkを選択
|
||||
<kbd>ctrl+o</kbd>: 選択されたテキストをクリップボードにコピー
|
||||
<kbd>space</kbd>: 行をパッチに追加/削除
|
||||
<kbd>v</kbd>: 範囲選択を切り替え
|
||||
<kbd>V</kbd>: 範囲選択を切り替え
|
||||
<kbd>a</kbd>: hunk選択を切り替え
|
||||
<kbd>ctrl+o</kbd>: 選択されたテキストをクリップボードにコピー
|
||||
<kbd>o</kbd>: ファイルを開く
|
||||
<kbd>e</kbd>: ファイルを編集
|
||||
<kbd>space</kbd>: 行をパッチに追加/削除
|
||||
<kbd>esc</kbd>: exit custom patch builder
|
||||
</pre>
|
||||
|
||||
## メインパネル (Staging)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: ファイル一覧に戻る
|
||||
<kbd>space</kbd>: 選択行をステージ/アンステージ
|
||||
<kbd>d</kbd>: 変更を削除 (git reset)
|
||||
<kbd>tab</kbd>: パネルを切り替え
|
||||
<kbd>o</kbd>: ファイルを開く
|
||||
<kbd>▲</kbd>: 前の行を選択
|
||||
<kbd>▼</kbd>: 次の行を選択
|
||||
<kbd>◄</kbd>: 前のhunkを選択
|
||||
<kbd>►</kbd>: 次のhunkを選択
|
||||
<kbd>ctrl+o</kbd>: 選択されたテキストをクリップボードにコピー
|
||||
<kbd>e</kbd>: ファイルを編集
|
||||
<kbd>v</kbd>: 範囲選択を切り替え
|
||||
<kbd>V</kbd>: 範囲選択を切り替え
|
||||
<kbd>a</kbd>: hunk選択を切り替え
|
||||
<kbd>ctrl+o</kbd>: 選択されたテキストをクリップボードにコピー
|
||||
<kbd>o</kbd>: ファイルを開く
|
||||
<kbd>e</kbd>: ファイルを編集
|
||||
<kbd>esc</kbd>: ファイル一覧に戻る
|
||||
<kbd>tab</kbd>: パネルを切り替え
|
||||
<kbd>space</kbd>: 選択行をステージ/アンステージ
|
||||
<kbd>d</kbd>: 変更を削除 (git reset)
|
||||
<kbd>E</kbd>: edit hunk
|
||||
</pre>
|
||||
|
||||
|
||||
@@ -30,11 +30,11 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
## List Panel Navigation
|
||||
|
||||
<pre>
|
||||
<kbd>.</kbd>: 다음 페이지
|
||||
<kbd>,</kbd>: 이전 페이지
|
||||
<kbd>.</kbd>: 다음 페이지
|
||||
<kbd><</kbd>: 맨 위로 스크롤
|
||||
<kbd>></kbd>: 맨 아래로 스크롤
|
||||
<kbd>/</kbd>: 검색 시작
|
||||
<kbd>></kbd>: 맨 아래로 스크롤
|
||||
<kbd>H</kbd>: 우 스크롤
|
||||
<kbd>L</kbd>: 좌 스크롤
|
||||
<kbd>]</kbd>: 이전 탭
|
||||
@@ -63,6 +63,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>g</kbd>: pop
|
||||
<kbd>d</kbd>: drop
|
||||
<kbd>n</kbd>: 새 브랜치 생성
|
||||
<kbd>r</kbd>: rename stash
|
||||
<kbd>enter</kbd>: view selected item's files
|
||||
</pre>
|
||||
|
||||
@@ -84,59 +85,56 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
## 메인 패널 (Merging)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: 파일 목록으로 돌아가기
|
||||
<kbd>M</kbd>: git mergetool를 열기
|
||||
<kbd>space</kbd>: pick hunk
|
||||
<kbd>b</kbd>: pick all hunks
|
||||
<kbd>e</kbd>: 파일 편집
|
||||
<kbd>o</kbd>: 파일 닫기
|
||||
<kbd>◄</kbd>: 이전 충돌을 선택
|
||||
<kbd>►</kbd>: 다음 충돌을 선택
|
||||
<kbd>▲</kbd>: 이전 hunk를 선택
|
||||
<kbd>▼</kbd>: 다음 hunk를 선택
|
||||
<kbd>e</kbd>: 파일 편집
|
||||
<kbd>o</kbd>: 파일 닫기
|
||||
<kbd>z</kbd>: 되돌리기
|
||||
<kbd>M</kbd>: git mergetool를 열기
|
||||
<kbd>space</kbd>: pick hunk
|
||||
<kbd>b</kbd>: pick all hunks
|
||||
<kbd>esc</kbd>: 파일 목록으로 돌아가기
|
||||
</pre>
|
||||
|
||||
## 메인 패널 (Normal)
|
||||
|
||||
<pre>
|
||||
<kbd>mouse wheel down</kbd>: 아래로 스크롤 (fn+up)
|
||||
<kbd>mouse wheel up</kbd>: 위로 스크롤 (fn+down)
|
||||
<kbd>mouse wheel ▼</kbd>: 아래로 스크롤 (fn+up)
|
||||
<kbd>mouse wheel ▲</kbd>: 위로 스크롤 (fn+down)
|
||||
</pre>
|
||||
|
||||
## 메인 패널 (Patch Building)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: line-by-line 모드 종료
|
||||
<kbd>o</kbd>: 파일 닫기
|
||||
<kbd>▲</kbd>: 이전 줄 선택
|
||||
<kbd>▼</kbd>: 다음 줄 선택
|
||||
<kbd>◄</kbd>: 이전 hunk를 선택
|
||||
<kbd>►</kbd>: 다음 hunk를 선택
|
||||
<kbd>ctrl+o</kbd>: 선택한 텍스트를 클립보드에 복사
|
||||
<kbd>space</kbd>: line(s)을 패치에 추가/삭제
|
||||
<kbd>v</kbd>: 드래그 선택 전환
|
||||
<kbd>V</kbd>: 드래그 선택 전환
|
||||
<kbd>a</kbd>: toggle select hunk
|
||||
<kbd>ctrl+o</kbd>: 선택한 텍스트를 클립보드에 복사
|
||||
<kbd>o</kbd>: 파일 닫기
|
||||
<kbd>e</kbd>: 파일 편집
|
||||
<kbd>space</kbd>: line(s)을 패치에 추가/삭제
|
||||
<kbd>esc</kbd>: exit custom patch builder
|
||||
</pre>
|
||||
|
||||
## 메인 패널 (Staging)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: 파일 목록으로 돌아가기
|
||||
<kbd>space</kbd>: 선택한 행을 staged / unstaged
|
||||
<kbd>d</kbd>: 변경을 삭제 (git reset)
|
||||
<kbd>tab</kbd>: 패널 전환
|
||||
<kbd>o</kbd>: 파일 닫기
|
||||
<kbd>▲</kbd>: 이전 줄 선택
|
||||
<kbd>▼</kbd>: 다음 줄 선택
|
||||
<kbd>◄</kbd>: 이전 hunk를 선택
|
||||
<kbd>►</kbd>: 다음 hunk를 선택
|
||||
<kbd>ctrl+o</kbd>: 선택한 텍스트를 클립보드에 복사
|
||||
<kbd>e</kbd>: 파일 편집
|
||||
<kbd>v</kbd>: 드래그 선택 전환
|
||||
<kbd>V</kbd>: 드래그 선택 전환
|
||||
<kbd>a</kbd>: toggle select hunk
|
||||
<kbd>ctrl+o</kbd>: 선택한 텍스트를 클립보드에 복사
|
||||
<kbd>o</kbd>: 파일 닫기
|
||||
<kbd>e</kbd>: 파일 편집
|
||||
<kbd>esc</kbd>: 파일 목록으로 돌아가기
|
||||
<kbd>tab</kbd>: 패널 전환
|
||||
<kbd>space</kbd>: 선택한 행을 staged / unstaged
|
||||
<kbd>d</kbd>: 변경을 삭제 (git reset)
|
||||
<kbd>E</kbd>: edit hunk
|
||||
</pre>
|
||||
|
||||
@@ -280,7 +278,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>C</kbd>: Git 편집기를 사용하여 변경 내용을 커밋합니다.
|
||||
<kbd>e</kbd>: 파일 편집
|
||||
<kbd>o</kbd>: 파일 닫기
|
||||
<kbd>i</kbd>: Ignore file
|
||||
<kbd>i</kbd>: ignore file
|
||||
<kbd>r</kbd>: 파일 새로고침
|
||||
<kbd>s</kbd>: 변경사항을 Stash
|
||||
<kbd>S</kbd>: Stash 옵션 보기
|
||||
|
||||
@@ -30,11 +30,11 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
## Lijstpaneel Navigatie
|
||||
|
||||
<pre>
|
||||
<kbd>.</kbd>: volgende pagina
|
||||
<kbd>,</kbd>: vorige pagina
|
||||
<kbd>.</kbd>: volgende pagina
|
||||
<kbd><</kbd>: scroll naar boven
|
||||
<kbd>></kbd>: scroll naar beneden
|
||||
<kbd>/</kbd>: start met zoeken
|
||||
<kbd>></kbd>: scroll naar beneden
|
||||
<kbd>H</kbd>: scroll left
|
||||
<kbd>L</kbd>: scroll right
|
||||
<kbd>]</kbd>: volgende tabblad
|
||||
@@ -55,7 +55,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>C</kbd>: commit veranderingen met de git editor
|
||||
<kbd>e</kbd>: verander bestand
|
||||
<kbd>o</kbd>: open bestand
|
||||
<kbd>i</kbd>: Ignore or Exclude file
|
||||
<kbd>i</kbd>: ignore or exclude file
|
||||
<kbd>r</kbd>: refresh bestanden
|
||||
<kbd>s</kbd>: stash-bestanden
|
||||
<kbd>S</kbd>: bekijk stash opties
|
||||
@@ -140,40 +140,39 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
## Mergen
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: ga terug naar het bestanden paneel
|
||||
<kbd>M</kbd>: open external merge tool (git mergetool)
|
||||
<kbd>space</kbd>: kies hunk
|
||||
<kbd>b</kbd>: kies bijde hunks
|
||||
<kbd>e</kbd>: verander bestand
|
||||
<kbd>o</kbd>: open bestand
|
||||
<kbd>◄</kbd>: selecteer voorgaand conflict
|
||||
<kbd>►</kbd>: selecteer volgende conflict
|
||||
<kbd>▲</kbd>: selecteer bovenste hunk
|
||||
<kbd>▼</kbd>: selecteer onderste hunk
|
||||
<kbd>e</kbd>: verander bestand
|
||||
<kbd>o</kbd>: open bestand
|
||||
<kbd>z</kbd>: ongedaan maken
|
||||
<kbd>M</kbd>: open external merge tool (git mergetool)
|
||||
<kbd>space</kbd>: kies hunk
|
||||
<kbd>b</kbd>: kies bijde hunks
|
||||
<kbd>esc</kbd>: ga terug naar het bestanden paneel
|
||||
</pre>
|
||||
|
||||
## Normaal
|
||||
|
||||
<pre>
|
||||
<kbd>mouse wheel down</kbd>: scroll omlaag (fn+up)
|
||||
<kbd>mouse wheel up</kbd>: scroll omhoog (fn+down)
|
||||
<kbd>mouse wheel ▼</kbd>: scroll omlaag (fn+up)
|
||||
<kbd>mouse wheel ▲</kbd>: scroll omhoog (fn+down)
|
||||
</pre>
|
||||
|
||||
## Patch Bouwen
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: sluit lijn-bij-lijn modus
|
||||
<kbd>o</kbd>: open bestand
|
||||
<kbd>▲</kbd>: selecteer de vorige lijn
|
||||
<kbd>▼</kbd>: selecteer de volgende lijn
|
||||
<kbd>◄</kbd>: selecteer de vorige hunk
|
||||
<kbd>►</kbd>: selecteer de volgende hunk
|
||||
<kbd>ctrl+o</kbd>: copy the selected text to the clipboard
|
||||
<kbd>space</kbd>: voeg toe/verwijder lijn(en) in patch
|
||||
<kbd>v</kbd>: toggle drag selecteer
|
||||
<kbd>V</kbd>: toggle drag selecteer
|
||||
<kbd>a</kbd>: toggle selecteer hunk
|
||||
<kbd>ctrl+o</kbd>: copy the selected text to the clipboard
|
||||
<kbd>o</kbd>: open bestand
|
||||
<kbd>e</kbd>: verander bestand
|
||||
<kbd>space</kbd>: voeg toe/verwijder lijn(en) in patch
|
||||
<kbd>esc</kbd>: sluit lijn-bij-lijn modus
|
||||
</pre>
|
||||
|
||||
## Reflog
|
||||
@@ -217,20 +216,18 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
## Staging
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: ga terug naar het bestanden paneel
|
||||
<kbd>space</kbd>: toggle lijnen staged / unstaged
|
||||
<kbd>d</kbd>: verwijdert change (git reset)
|
||||
<kbd>tab</kbd>: ga naar een ander paneel
|
||||
<kbd>o</kbd>: open bestand
|
||||
<kbd>▲</kbd>: selecteer de vorige lijn
|
||||
<kbd>▼</kbd>: selecteer de volgende lijn
|
||||
<kbd>◄</kbd>: selecteer de vorige hunk
|
||||
<kbd>►</kbd>: selecteer de volgende hunk
|
||||
<kbd>ctrl+o</kbd>: copy the selected text to the clipboard
|
||||
<kbd>e</kbd>: verander bestand
|
||||
<kbd>v</kbd>: toggle drag selecteer
|
||||
<kbd>V</kbd>: toggle drag selecteer
|
||||
<kbd>a</kbd>: toggle selecteer hunk
|
||||
<kbd>ctrl+o</kbd>: copy the selected text to the clipboard
|
||||
<kbd>o</kbd>: open bestand
|
||||
<kbd>e</kbd>: verander bestand
|
||||
<kbd>esc</kbd>: ga terug naar het bestanden paneel
|
||||
<kbd>tab</kbd>: ga naar een ander paneel
|
||||
<kbd>space</kbd>: toggle lijnen staged / unstaged
|
||||
<kbd>d</kbd>: verwijdert change (git reset)
|
||||
<kbd>E</kbd>: edit hunk
|
||||
</pre>
|
||||
|
||||
@@ -241,6 +238,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>g</kbd>: pop
|
||||
<kbd>d</kbd>: laten vallen
|
||||
<kbd>n</kbd>: nieuwe branch
|
||||
<kbd>r</kbd>: rename stash
|
||||
<kbd>enter</kbd>: bekijk gecommite bestanden
|
||||
</pre>
|
||||
|
||||
|
||||
@@ -30,11 +30,11 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
## List Panel Navigation
|
||||
|
||||
<pre>
|
||||
<kbd>.</kbd>: next page
|
||||
<kbd>,</kbd>: previous page
|
||||
<kbd>.</kbd>: next page
|
||||
<kbd><</kbd>: scroll to top
|
||||
<kbd>></kbd>: scroll to bottom
|
||||
<kbd>/</kbd>: start search
|
||||
<kbd>></kbd>: scroll to bottom
|
||||
<kbd>H</kbd>: scroll left
|
||||
<kbd>L</kbd>: scroll right
|
||||
<kbd>]</kbd>: next tab
|
||||
@@ -99,17 +99,16 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
## Main Panel (Patch Building)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: wyście z trybu "linia po linii"
|
||||
<kbd>o</kbd>: otwórz plik
|
||||
<kbd>▲</kbd>: poprzednia linia
|
||||
<kbd>▼</kbd>: następna linia
|
||||
<kbd>◄</kbd>: poprzedni kawałek
|
||||
<kbd>►</kbd>: następny kawałek
|
||||
<kbd>ctrl+o</kbd>: copy the selected text to the clipboard
|
||||
<kbd>space</kbd>: add/remove line(s) to patch
|
||||
<kbd>v</kbd>: toggle drag select
|
||||
<kbd>V</kbd>: toggle drag select
|
||||
<kbd>a</kbd>: toggle select hunk
|
||||
<kbd>ctrl+o</kbd>: copy the selected text to the clipboard
|
||||
<kbd>o</kbd>: otwórz plik
|
||||
<kbd>e</kbd>: edytuj plik
|
||||
<kbd>space</kbd>: add/remove line(s) to patch
|
||||
<kbd>esc</kbd>: wyście z trybu "linia po linii"
|
||||
</pre>
|
||||
|
||||
## Pliki
|
||||
@@ -126,7 +125,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>C</kbd>: Zatwierdź zmiany używając edytora
|
||||
<kbd>e</kbd>: edytuj plik
|
||||
<kbd>o</kbd>: otwórz plik
|
||||
<kbd>i</kbd>: Ignore or Exclude file
|
||||
<kbd>i</kbd>: ignore or exclude file
|
||||
<kbd>r</kbd>: odśwież pliki
|
||||
<kbd>s</kbd>: przechowaj zmiany
|
||||
<kbd>S</kbd>: wyświetl opcje schowka
|
||||
@@ -156,20 +155,18 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
## Poczekalnia
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: wróć do panelu plików
|
||||
<kbd>space</kbd>: toggle line staged / unstaged
|
||||
<kbd>d</kbd>: delete change (git reset)
|
||||
<kbd>tab</kbd>: switch to other panel
|
||||
<kbd>o</kbd>: otwórz plik
|
||||
<kbd>▲</kbd>: poprzednia linia
|
||||
<kbd>▼</kbd>: następna linia
|
||||
<kbd>◄</kbd>: poprzedni kawałek
|
||||
<kbd>►</kbd>: następny kawałek
|
||||
<kbd>ctrl+o</kbd>: copy the selected text to the clipboard
|
||||
<kbd>e</kbd>: edytuj plik
|
||||
<kbd>v</kbd>: toggle drag select
|
||||
<kbd>V</kbd>: toggle drag select
|
||||
<kbd>a</kbd>: toggle select hunk
|
||||
<kbd>ctrl+o</kbd>: copy the selected text to the clipboard
|
||||
<kbd>o</kbd>: otwórz plik
|
||||
<kbd>e</kbd>: edytuj plik
|
||||
<kbd>esc</kbd>: wróć do panelu plików
|
||||
<kbd>tab</kbd>: switch to other panel (staged/unstaged changes)
|
||||
<kbd>space</kbd>: toggle line staged / unstaged
|
||||
<kbd>d</kbd>: delete change (git reset)
|
||||
<kbd>E</kbd>: edit hunk
|
||||
</pre>
|
||||
|
||||
@@ -214,17 +211,17 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
## Scalanie
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: wróć do panelu plików
|
||||
<kbd>M</kbd>: open external merge tool (git mergetool)
|
||||
<kbd>space</kbd>: wybierz kawałek
|
||||
<kbd>b</kbd>: wybierz wszystkie kawałki
|
||||
<kbd>e</kbd>: edytuj plik
|
||||
<kbd>o</kbd>: otwórz plik
|
||||
<kbd>◄</kbd>: poprzedni konflikt
|
||||
<kbd>►</kbd>: następny konflikt
|
||||
<kbd>▲</kbd>: wybierz poprzedni kawałek
|
||||
<kbd>▼</kbd>: wybierz następny kawałek
|
||||
<kbd>e</kbd>: edytuj plik
|
||||
<kbd>o</kbd>: otwórz plik
|
||||
<kbd>z</kbd>: cofnij
|
||||
<kbd>M</kbd>: open external merge tool (git mergetool)
|
||||
<kbd>space</kbd>: wybierz kawałek
|
||||
<kbd>b</kbd>: wybierz wszystkie kawałki
|
||||
<kbd>esc</kbd>: wróć do panelu plików
|
||||
</pre>
|
||||
|
||||
## Schowek
|
||||
@@ -234,6 +231,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>g</kbd>: wyciągnij
|
||||
<kbd>d</kbd>: porzuć
|
||||
<kbd>n</kbd>: nowa gałąź
|
||||
<kbd>r</kbd>: rename stash
|
||||
<kbd>enter</kbd>: przeglądaj pliki commita
|
||||
</pre>
|
||||
|
||||
@@ -289,6 +287,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
## Zwykłe
|
||||
|
||||
<pre>
|
||||
<kbd>mouse wheel down</kbd>: przewiń w dół (fn+up)
|
||||
<kbd>mouse wheel up</kbd>: przewiń w górę (fn+down)
|
||||
<kbd>mouse wheel ▼</kbd>: przewiń w dół (fn+up)
|
||||
<kbd>mouse wheel ▲</kbd>: przewiń w górę (fn+down)
|
||||
</pre>
|
||||
|
||||
@@ -30,11 +30,11 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
## 列表面板导航
|
||||
|
||||
<pre>
|
||||
<kbd>.</kbd>: 下一页
|
||||
<kbd>,</kbd>: 上一页
|
||||
<kbd>.</kbd>: 下一页
|
||||
<kbd><</kbd>: 滚动到顶部
|
||||
<kbd>></kbd>: 滚动到底部
|
||||
<kbd>/</kbd>: 开始搜索
|
||||
<kbd>></kbd>: 滚动到底部
|
||||
<kbd>H</kbd>: 向左滚动
|
||||
<kbd>L</kbd>: 向右滚动
|
||||
<kbd>]</kbd>: 下一个标签
|
||||
@@ -183,17 +183,16 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
## 构建补丁中
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: 退出逐行模式
|
||||
<kbd>o</kbd>: 打开文件
|
||||
<kbd>▲</kbd>: 选择上一行
|
||||
<kbd>▼</kbd>: 选择下一行
|
||||
<kbd>◄</kbd>: 选择上一个区块
|
||||
<kbd>►</kbd>: 选择下一个区块
|
||||
<kbd>ctrl+o</kbd>: 将选中文本复制到剪贴板
|
||||
<kbd>space</kbd>: 添加/移除 行到补丁
|
||||
<kbd>v</kbd>: 切换拖动选择
|
||||
<kbd>V</kbd>: 切换拖动选择
|
||||
<kbd>a</kbd>: 切换选择区块
|
||||
<kbd>ctrl+o</kbd>: 将选中文本复制到剪贴板
|
||||
<kbd>o</kbd>: 打开文件
|
||||
<kbd>e</kbd>: 编辑文件
|
||||
<kbd>space</kbd>: 添加/移除 行到补丁
|
||||
<kbd>esc</kbd>: 退出逐行模式
|
||||
</pre>
|
||||
|
||||
## 标签页面
|
||||
@@ -210,44 +209,42 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
## 正在合并
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: 返回文件面板
|
||||
<kbd>M</kbd>: 打开外部合并工具 (git mergetool)
|
||||
<kbd>space</kbd>: 选中区块
|
||||
<kbd>b</kbd>: 选中所有区块
|
||||
<kbd>e</kbd>: 编辑文件
|
||||
<kbd>o</kbd>: 打开文件
|
||||
<kbd>◄</kbd>: 选择上一个冲突
|
||||
<kbd>►</kbd>: 选择下一个冲突
|
||||
<kbd>▲</kbd>: 选择顶部块
|
||||
<kbd>▼</kbd>: 选择底部块
|
||||
<kbd>e</kbd>: 编辑文件
|
||||
<kbd>o</kbd>: 打开文件
|
||||
<kbd>z</kbd>: 撤销
|
||||
<kbd>M</kbd>: 打开外部合并工具 (git mergetool)
|
||||
<kbd>space</kbd>: 选中区块
|
||||
<kbd>b</kbd>: 选中所有区块
|
||||
<kbd>esc</kbd>: 返回文件面板
|
||||
</pre>
|
||||
|
||||
## 正在暂存
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: 返回文件面板
|
||||
<kbd>space</kbd>: 切换行暂存状态
|
||||
<kbd>d</kbd>: 取消变更 (git reset)
|
||||
<kbd>tab</kbd>: 切换到其他面板
|
||||
<kbd>o</kbd>: 打开文件
|
||||
<kbd>▲</kbd>: 选择上一行
|
||||
<kbd>▼</kbd>: 选择下一行
|
||||
<kbd>◄</kbd>: 选择上一个区块
|
||||
<kbd>►</kbd>: 选择下一个区块
|
||||
<kbd>ctrl+o</kbd>: 将选中文本复制到剪贴板
|
||||
<kbd>e</kbd>: 编辑文件
|
||||
<kbd>v</kbd>: 切换拖动选择
|
||||
<kbd>V</kbd>: 切换拖动选择
|
||||
<kbd>a</kbd>: 切换选择区块
|
||||
<kbd>ctrl+o</kbd>: 将选中文本复制到剪贴板
|
||||
<kbd>o</kbd>: 打开文件
|
||||
<kbd>e</kbd>: 编辑文件
|
||||
<kbd>esc</kbd>: 返回文件面板
|
||||
<kbd>tab</kbd>: 切换到其他面板
|
||||
<kbd>space</kbd>: 切换行暂存状态
|
||||
<kbd>d</kbd>: 取消变更 (git reset)
|
||||
<kbd>E</kbd>: edit hunk
|
||||
</pre>
|
||||
|
||||
## 正常
|
||||
|
||||
<pre>
|
||||
<kbd>mouse wheel down</kbd>: 向下滚动 (fn+up)
|
||||
<kbd>mouse wheel up</kbd>: 向上滚动 (fn+down)
|
||||
<kbd>mouse wheel ▼</kbd>: 向下滚动 (fn+up)
|
||||
<kbd>mouse wheel ▲</kbd>: 向上滚动 (fn+down)
|
||||
</pre>
|
||||
|
||||
## 状态
|
||||
@@ -267,6 +264,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>g</kbd>: 应用并删除
|
||||
<kbd>d</kbd>: 删除
|
||||
<kbd>n</kbd>: 新分支
|
||||
<kbd>r</kbd>: rename stash
|
||||
<kbd>enter</kbd>: 查看提交的文件
|
||||
</pre>
|
||||
|
||||
|
||||
@@ -11,30 +11,34 @@ require (
|
||||
github.com/creack/pty v1.1.11
|
||||
github.com/fsmiamoto/git-todo-parser v0.0.2
|
||||
github.com/fsnotify/fsnotify v1.4.7
|
||||
github.com/gdamore/tcell/v2 v2.5.3
|
||||
github.com/go-errors/errors v1.4.2
|
||||
github.com/gookit/color v1.4.2
|
||||
github.com/imdario/mergo v0.3.11
|
||||
github.com/integrii/flaggy v1.4.0
|
||||
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68
|
||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20201006095850-341962be15a4
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20220417002912-bce22fd599f6
|
||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20221016041636-16c24668f71c
|
||||
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10
|
||||
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5
|
||||
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e
|
||||
github.com/jesseduffield/yaml v2.1.0+incompatible
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
|
||||
github.com/kyokomi/emoji/v2 v2.2.8
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0
|
||||
github.com/mattn/go-runewidth v0.0.13
|
||||
github.com/mattn/go-runewidth v0.0.14
|
||||
github.com/mgutz/str v1.2.0
|
||||
github.com/pmezard/go-difflib v1.0.0
|
||||
github.com/sahilm/fuzzy v0.1.0
|
||||
github.com/samber/lo v1.10.1
|
||||
github.com/samber/lo v1.31.0
|
||||
github.com/sanity-io/litter v1.5.2
|
||||
github.com/sasha-s/go-deadlock v0.3.1
|
||||
github.com/sirupsen/logrus v1.4.2
|
||||
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778
|
||||
gopkg.in/ozeidan/fuzzy-patricia.v3 v3.0.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -42,7 +46,6 @@ require (
|
||||
github.com/emirpasic/gods v1.12.0 // indirect
|
||||
github.com/fatih/color v1.9.0 // indirect
|
||||
github.com/gdamore/encoding v1.0.0 // indirect
|
||||
github.com/gdamore/tcell/v2 v2.5.1 // indirect
|
||||
github.com/go-git/gcfg v1.5.0 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.0.0 // indirect
|
||||
github.com/go-logfmt/logfmt v0.5.0 // indirect
|
||||
@@ -58,15 +61,15 @@ require (
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/onsi/ginkgo v1.10.3 // indirect
|
||||
github.com/onsi/gomega v1.7.1 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
|
||||
github.com/rivo/uniseg v0.4.2 // indirect
|
||||
github.com/sergi/go-diff v1.1.0 // indirect
|
||||
github.com/xanzy/ssh-agent v0.2.1 // indirect
|
||||
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 // indirect
|
||||
golang.org/x/exp v0.0.0-20220318154914-8dddf5d87bd8 // indirect
|
||||
golang.org/x/net v0.0.0-20201002202402-0a1ea396d57c // indirect
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect
|
||||
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 // indirect
|
||||
golang.org/x/term v0.0.0-20220919170432-7a66f970e087 // indirect
|
||||
golang.org/x/text v0.3.8 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
)
|
||||
|
||||
@@ -35,8 +35,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
|
||||
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||
github.com/gdamore/tcell/v2 v2.4.0/go.mod h1:cTTuF84Dlj/RqmaCIV5p4w8uG1zWdk0SF6oBpwHp4fU=
|
||||
github.com/gdamore/tcell/v2 v2.5.1 h1:zc3LPdpK184lBW7syF2a5C6MV827KmErk9jGVnmsl/I=
|
||||
github.com/gdamore/tcell/v2 v2.5.1/go.mod h1:wSkrPaXoiIWZqW/g7Px4xc79di6FTcpB8tvaKJ6uGBo=
|
||||
github.com/gdamore/tcell/v2 v2.5.3 h1:b9XQrT6QGbgI7JvZOJXFNczOQeIYbo8BfeSMzt2sAV0=
|
||||
github.com/gdamore/tcell/v2 v2.5.3/go.mod h1:wSkrPaXoiIWZqW/g7Px4xc79di6FTcpB8tvaKJ6uGBo=
|
||||
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
|
||||
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
|
||||
@@ -70,12 +70,14 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 h1:EQP2Tv8TIcC6Y4RI+1ZbJDOHfGJ570tPeYVCqo7/tws=
|
||||
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68/go.mod h1:+LLj9/WUPAP8LqCchs7P+7X0R98HiFujVFANdNaxhGk=
|
||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20201006095850-341962be15a4 h1:GOQrmaE8i+KEdB8NzAegKYd4tPn/inM0I1uo0NXFerg=
|
||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20201006095850-341962be15a4/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20220417002912-bce22fd599f6 h1:Fmay0Lz21taUpXiIbFkjjIIcn0E5GKwp5UFRuXaOiGQ=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20220417002912-bce22fd599f6/go.mod h1:znJuCDnF2Ph40YZSlBwdX/4GEofnIoWLGdT4mK5zRAU=
|
||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d h1:bO+OmbreIv91rCe8NmscRwhFSqkDJtzWCPV4Y+SQuXE=
|
||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20221016041636-16c24668f71c h1:ttqCvM86hyp4V3DtRPxLo9imD1s+n6ry/zshPx0tt98=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20221016041636-16c24668f71c/go.mod h1:znJuCDnF2Ph40YZSlBwdX/4GEofnIoWLGdT4mK5zRAU=
|
||||
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 h1:jmpr7KpX2+2GRiE91zTgfq49QvgiqB0nbmlwZ8UnOx0=
|
||||
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10/go.mod h1:aA97kHeNA+sj2Hbki0pvLslmE4CbDyhBeSSTUUnOuVo=
|
||||
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 h1:CDuQmfOjAtb1Gms6a1p5L2P8RhbLUq5t8aL7PiQd2uY=
|
||||
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5/go.mod h1:qxN4mHOAyeIDLP7IK7defgPClM/z1Kze8VVQiaEjzsQ=
|
||||
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e h1:uw/oo+kg7t/oeMs6sqlAwr85ND/9cpO3up3VxphxY0U=
|
||||
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e/go.mod h1:u60qdFGXRd36jyEXxetz0vQceQIxzI13lIo3EFUDf4I=
|
||||
github.com/jesseduffield/yaml v2.1.0+incompatible h1:HWQJ1gIv2zHKbDYNp0Jwjlj24K8aqpFHnMCynY1EpmE=
|
||||
@@ -112,8 +114,9 @@ github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOA
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mgutz/str v1.2.0 h1:4IzWSdIz9qPQWLfKZ0rJcV0jcUDpxvP4JVZ4GXQyvSw=
|
||||
github.com/mgutz/str v1.2.0/go.mod h1:w1v0ofgLaJdoD0HpQ3fycxKD1WtxpjSo151pK/31q6w=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
@@ -125,20 +128,25 @@ github.com/onsi/ginkgo v1.10.3 h1:OoxbjfXVZyod1fmWYhI7SEyaD8B00ynP3T+D5GiyHOY=
|
||||
github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ=
|
||||
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8=
|
||||
github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI=
|
||||
github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
|
||||
github.com/samber/lo v1.10.1 h1:0D3h7i0U3hRAbaCeQ82DLe67n0A7Bbl0/cEoWqFGp+U=
|
||||
github.com/samber/lo v1.10.1/go.mod h1:2I7tgIv8Q1SG2xEIkRq0F2i2zgxVpnyPOP0d3Gj2r+A=
|
||||
github.com/samber/lo v1.31.0 h1:Sfa+/064Tdo4SvlohQUQzBhgSer9v/coGvKQI/XLWAM=
|
||||
github.com/samber/lo v1.31.0/go.mod h1:HLeWcJRRyLKp3+/XBJvOrerCQn9mhdKMHyd7IRlgeQ8=
|
||||
github.com/sanity-io/litter v1.5.2 h1:AnC8s9BMORWH5a4atZ4D6FPVvKGzHcnc5/IVTa87myw=
|
||||
github.com/sanity-io/litter v1.5.2/go.mod h1:5Z71SvaYy5kcGtyglXOC9rrUi3c1E8CamFWjQsazTh0=
|
||||
github.com/sasha-s/go-deadlock v0.3.1 h1:sqv7fDNShgjcaxkO0JNcOAlr8B9+cV5Ey/OB71efZx0=
|
||||
github.com/sasha-s/go-deadlock v0.3.1/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM=
|
||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||
@@ -146,14 +154,17 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd
|
||||
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad h1:fiWzISvDn0Csy5H0iwgAuJGQTUpVfEMJJd4nRFXogbc=
|
||||
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M=
|
||||
github.com/urfave/cli v1.20.1-0.20180226030253-8e01ec4cd3e2/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70=
|
||||
@@ -189,15 +200,16 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220318055525-2edf467146b5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 h1:OK7RB6t2WQX54srQQYSXMW8dF5C6/8+oA/s5QBmmto4=
|
||||
golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 h1:EH1Deb8WZJ0xc0WK//leUHXcX9aLE5SymusoTmMZye8=
|
||||
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.0.0-20220919170432-7a66f970e087 h1:tPwmk4vmvVCMdr98VgL4JH+qZxPL8fqlUOHnyOM8N3w=
|
||||
golang.org/x/term v0.0.0-20220919170432-7a66f970e087/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -217,5 +229,5 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
@@ -1,179 +1,24 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/integrii/flaggy"
|
||||
"github.com/jesseduffield/lazygit/pkg/app"
|
||||
"github.com/jesseduffield/lazygit/pkg/app/daemon"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/jesseduffield/lazygit/pkg/env"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/logs"
|
||||
yaml "github.com/jesseduffield/yaml"
|
||||
)
|
||||
|
||||
// These values may be set by the build script via the LDFLAGS argument
|
||||
var (
|
||||
commit string
|
||||
version = "unversioned"
|
||||
date string
|
||||
version string
|
||||
buildSource = "unknown"
|
||||
)
|
||||
|
||||
func main() {
|
||||
flaggy.DefaultParser.ShowVersionWithVersionFlag = false
|
||||
|
||||
repoPath := ""
|
||||
flaggy.String(&repoPath, "p", "path", "Path of git repo. (equivalent to --work-tree=<path> --git-dir=<path>/.git/)")
|
||||
|
||||
filterPath := ""
|
||||
flaggy.String(&filterPath, "f", "filter", "Path to filter on in `git log -- <path>`. When in filter mode, the commits, reflog, and stash are filtered based on the given path, and some operations are restricted")
|
||||
|
||||
gitArg := ""
|
||||
flaggy.AddPositionalValue(&gitArg, "git-arg", 1, false, "Panel to focus upon opening lazygit. Accepted values (based on git terminology): status, branch, log, stash. Ignored if --filter arg is passed.")
|
||||
|
||||
versionFlag := false
|
||||
flaggy.Bool(&versionFlag, "v", "version", "Print the current version")
|
||||
|
||||
debuggingFlag := false
|
||||
flaggy.Bool(&debuggingFlag, "d", "debug", "Run in debug mode with logging (see --logs flag below). Use the LOG_LEVEL env var to set the log level (debug/info/warn/error)")
|
||||
|
||||
logFlag := false
|
||||
flaggy.Bool(&logFlag, "l", "logs", "Tail lazygit logs (intended to be used when `lazygit --debug` is called in a separate terminal tab)")
|
||||
|
||||
configFlag := false
|
||||
flaggy.Bool(&configFlag, "c", "config", "Print the default config")
|
||||
|
||||
configDirFlag := false
|
||||
flaggy.Bool(&configDirFlag, "cd", "print-config-dir", "Print the config directory")
|
||||
|
||||
useConfigDir := ""
|
||||
flaggy.String(&useConfigDir, "ucd", "use-config-dir", "override default config directory with provided directory")
|
||||
|
||||
workTree := ""
|
||||
flaggy.String(&workTree, "w", "work-tree", "equivalent of the --work-tree git argument")
|
||||
|
||||
gitDir := ""
|
||||
flaggy.String(&gitDir, "g", "git-dir", "equivalent of the --git-dir git argument")
|
||||
|
||||
customConfig := ""
|
||||
flaggy.String(&customConfig, "ucf", "use-config-file", "Comma separated list to custom config file(s)")
|
||||
|
||||
flaggy.Parse()
|
||||
|
||||
if repoPath != "" {
|
||||
if workTree != "" || gitDir != "" {
|
||||
log.Fatal("--path option is incompatible with the --work-tree and --git-dir options")
|
||||
}
|
||||
|
||||
absRepoPath, err := filepath.Abs(repoPath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
workTree = absRepoPath
|
||||
gitDir = filepath.Join(absRepoPath, ".git")
|
||||
ldFlagsBuildInfo := &app.BuildInfo{
|
||||
Commit: commit,
|
||||
Date: date,
|
||||
Version: version,
|
||||
BuildSource: buildSource,
|
||||
}
|
||||
|
||||
if customConfig != "" {
|
||||
os.Setenv("LG_CONFIG_FILE", customConfig)
|
||||
}
|
||||
|
||||
if useConfigDir != "" {
|
||||
os.Setenv("CONFIG_DIR", useConfigDir)
|
||||
}
|
||||
|
||||
if workTree != "" {
|
||||
env.SetGitWorkTreeEnv(workTree)
|
||||
}
|
||||
|
||||
if gitDir != "" {
|
||||
env.SetGitDirEnv(gitDir)
|
||||
}
|
||||
|
||||
if versionFlag {
|
||||
fmt.Printf("commit=%s, build date=%s, build source=%s, version=%s, os=%s, arch=%s\n", commit, date, buildSource, version, runtime.GOOS, runtime.GOARCH)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if configFlag {
|
||||
var buf bytes.Buffer
|
||||
encoder := yaml.NewEncoder(&buf)
|
||||
err := encoder.Encode(config.GetDefaultConfig())
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
fmt.Printf("%s\n", buf.String())
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if configDirFlag {
|
||||
fmt.Printf("%s\n", config.ConfigDir())
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if logFlag {
|
||||
logs.TailLogs()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if workTree != "" {
|
||||
if err := os.Chdir(workTree); err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
tempDir, err := os.MkdirTemp("", "lazygit-*")
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
appConfig, err := config.NewAppConfig("lazygit", version, commit, date, buildSource, debuggingFlag, tempDir)
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
|
||||
common, err := app.NewCommon(appConfig)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if daemon.InDaemonMode() {
|
||||
daemon.Handle(common)
|
||||
return
|
||||
}
|
||||
|
||||
parsedGitArg := parseGitArg(gitArg)
|
||||
|
||||
app.Run(appConfig, common, types.NewStartArgs(filterPath, parsedGitArg))
|
||||
}
|
||||
|
||||
func parseGitArg(gitArg string) types.GitArg {
|
||||
typedArg := types.GitArg(gitArg)
|
||||
|
||||
// using switch so that linter catches when a new git arg value is defined but not handled here
|
||||
switch typedArg {
|
||||
case types.GitArgNone, types.GitArgStatus, types.GitArgBranch, types.GitArgLog, types.GitArgStash:
|
||||
return typedArg
|
||||
}
|
||||
|
||||
permittedValues := []string{
|
||||
string(types.GitArgStatus),
|
||||
string(types.GitArgBranch),
|
||||
string(types.GitArgLog),
|
||||
string(types.GitArgStash),
|
||||
}
|
||||
|
||||
log.Fatalf("Invalid git arg value: '%s'. Must be one of the following values: %s. e.g. 'lazygit status'. See 'lazygit --help'.",
|
||||
gitArg,
|
||||
strings.Join(permittedValues, ", "),
|
||||
)
|
||||
|
||||
panic("unreachable")
|
||||
app.Start(ldFlagsBuildInfo, nil)
|
||||
}
|
||||
|
||||
+76
-27
@@ -14,7 +14,9 @@ import (
|
||||
"github.com/go-errors/errors"
|
||||
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
appTypes "github.com/jesseduffield/lazygit/pkg/app/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_config"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
@@ -22,7 +24,6 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/constants"
|
||||
"github.com/jesseduffield/lazygit/pkg/env"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/i18n"
|
||||
"github.com/jesseduffield/lazygit/pkg/updates"
|
||||
)
|
||||
@@ -38,7 +39,11 @@ type App struct {
|
||||
Updater *updates.Updater // may only need this on the Gui
|
||||
}
|
||||
|
||||
func Run(config config.AppConfigurer, common *common.Common, startArgs types.StartArgs) {
|
||||
func Run(
|
||||
config config.AppConfigurer,
|
||||
common *common.Common,
|
||||
startArgs appTypes.StartArgs,
|
||||
) {
|
||||
app, err := NewApp(config, common)
|
||||
|
||||
if err == nil {
|
||||
@@ -148,7 +153,19 @@ func isGitVersionValid(versionStr string) bool {
|
||||
|
||||
func isDirectoryAGitRepository(dir string) (bool, error) {
|
||||
info, err := os.Stat(filepath.Join(dir, ".git"))
|
||||
return info != nil && info.IsDir(), err
|
||||
return info != nil, err
|
||||
}
|
||||
|
||||
func openRecentRepo(app *App) bool {
|
||||
for _, repoDir := range app.Config.GetAppState().RecentRepos {
|
||||
if isRepo, _ := isDirectoryAGitRepository(repoDir); isRepo {
|
||||
if err := os.Chdir(repoDir); err == nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (app *App) setupRepo() (bool, error) {
|
||||
@@ -167,53 +184,85 @@ func (app *App) setupRepo() (bool, error) {
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if isRepo, err := isDirectoryAGitRepository(cwd); isRepo {
|
||||
return false, err
|
||||
}
|
||||
|
||||
shouldInitRepo := true
|
||||
notARepository := app.UserConfig.NotARepository
|
||||
initialBranch := ""
|
||||
if notARepository == "prompt" {
|
||||
var shouldInitRepo bool
|
||||
initialBranchArg := ""
|
||||
switch app.UserConfig.NotARepository {
|
||||
case "prompt":
|
||||
// Offer to initialize a new repository in current directory.
|
||||
fmt.Print(app.Tr.CreateRepo)
|
||||
response, _ := bufio.NewReader(os.Stdin).ReadString('\n')
|
||||
if strings.Trim(response, " \r\n") != "y" {
|
||||
shouldInitRepo = false
|
||||
} else {
|
||||
shouldInitRepo = (strings.Trim(response, " \r\n") == "y")
|
||||
if shouldInitRepo {
|
||||
// Ask for the initial branch name
|
||||
fmt.Print(app.Tr.InitialBranch)
|
||||
response, _ := bufio.NewReader(os.Stdin).ReadString('\n')
|
||||
if trimmedResponse := strings.Trim(response, " \r\n"); len(trimmedResponse) > 0 {
|
||||
initialBranch += "--initial-branch=" + trimmedResponse
|
||||
initialBranchArg += "--initial-branch=" + app.OSCommand.Quote(trimmedResponse)
|
||||
}
|
||||
}
|
||||
} else if notARepository == "skip" {
|
||||
case "create":
|
||||
shouldInitRepo = true
|
||||
case "skip":
|
||||
shouldInitRepo = false
|
||||
}
|
||||
|
||||
if !shouldInitRepo {
|
||||
// check if we have a recent repo we can open
|
||||
for _, repoDir := range app.Config.GetAppState().RecentRepos {
|
||||
if isRepo, _ := isDirectoryAGitRepository(repoDir); isRepo {
|
||||
if err := os.Chdir(repoDir); err == nil {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println(app.Tr.NoRecentRepositories)
|
||||
case "quit":
|
||||
fmt.Fprintln(os.Stderr, app.Tr.NotARepository)
|
||||
os.Exit(1)
|
||||
default:
|
||||
fmt.Fprintln(os.Stderr, app.Tr.IncorrectNotARepository)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := app.OSCommand.Cmd.New("git init " + initialBranch).Run(); err != nil {
|
||||
|
||||
if shouldInitRepo {
|
||||
if err := app.OSCommand.Cmd.New("git init " + initialBranchArg).Run(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// check if we have a recent repo we can open
|
||||
for _, repoDir := range app.Config.GetAppState().RecentRepos {
|
||||
if isRepo, _ := isDirectoryAGitRepository(repoDir); isRepo {
|
||||
if err := os.Chdir(repoDir); err == nil {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintln(os.Stderr, app.Tr.NoRecentRepositories)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Run this afterward so that the previous repo creation steps can run without this interfering
|
||||
if isBare, err := git_commands.IsBareRepo(app.OSCommand); isBare {
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
fmt.Print(app.Tr.BareRepo)
|
||||
|
||||
response, _ := bufio.NewReader(os.Stdin).ReadString('\n')
|
||||
|
||||
if shouldOpenRecent := strings.Trim(response, " \r\n") == "y"; !shouldOpenRecent {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if didOpenRepo := openRecentRepo(app); didOpenRepo {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
fmt.Println(app.Tr.NoRecentRepositories)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (app *App) Run(startArgs types.StartArgs) error {
|
||||
func (app *App) Run(startArgs appTypes.StartArgs) error {
|
||||
err := app.Gui.RunAndHandleError(startArgs)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -77,7 +76,7 @@ func (self *rebaseDaemon) Run() error {
|
||||
self.c.Log.Info("args: ", os.Args)
|
||||
|
||||
if strings.HasSuffix(os.Args[1], "git-rebase-todo") {
|
||||
if err := ioutil.WriteFile(os.Args[1], []byte(os.Getenv(RebaseTODOEnvKey)), 0o644); err != nil {
|
||||
if err := os.WriteFile(os.Args[1], []byte(os.Getenv(RebaseTODOEnvKey)), 0o644); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if strings.HasSuffix(os.Args[1], filepath.Join(gitDir(), "COMMIT_EDITMSG")) { // TODO: test
|
||||
|
||||
@@ -0,0 +1,279 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
|
||||
"github.com/integrii/flaggy"
|
||||
"github.com/jesseduffield/lazygit/pkg/app/daemon"
|
||||
appTypes "github.com/jesseduffield/lazygit/pkg/app/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/jesseduffield/lazygit/pkg/env"
|
||||
integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/logs"
|
||||
"github.com/jesseduffield/lazygit/pkg/secureexec"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type cliArgs struct {
|
||||
RepoPath string
|
||||
FilterPath string
|
||||
GitArg string
|
||||
PrintVersionInfo bool
|
||||
Debug bool
|
||||
TailLogs bool
|
||||
PrintDefaultConfig bool
|
||||
PrintConfigDir bool
|
||||
UseConfigDir string
|
||||
WorkTree string
|
||||
GitDir string
|
||||
CustomConfigFile string
|
||||
}
|
||||
|
||||
type BuildInfo struct {
|
||||
Commit string
|
||||
Date string
|
||||
Version string
|
||||
BuildSource string
|
||||
}
|
||||
|
||||
func Start(buildInfo *BuildInfo, integrationTest integrationTypes.IntegrationTest) {
|
||||
cliArgs := parseCliArgsAndEnvVars()
|
||||
mergeBuildInfo(buildInfo)
|
||||
|
||||
if cliArgs.RepoPath != "" {
|
||||
if cliArgs.WorkTree != "" || cliArgs.GitDir != "" {
|
||||
log.Fatal("--path option is incompatible with the --work-tree and --git-dir options")
|
||||
}
|
||||
|
||||
absRepoPath, err := filepath.Abs(cliArgs.RepoPath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if isRepo, err := isDirectoryAGitRepository(absRepoPath); err != nil || !isRepo {
|
||||
log.Fatal(absRepoPath + " is not a valid git repository.")
|
||||
}
|
||||
|
||||
cliArgs.WorkTree = absRepoPath
|
||||
cliArgs.GitDir = filepath.Join(absRepoPath, ".git")
|
||||
}
|
||||
|
||||
if cliArgs.CustomConfigFile != "" {
|
||||
os.Setenv("LG_CONFIG_FILE", cliArgs.CustomConfigFile)
|
||||
}
|
||||
|
||||
if cliArgs.UseConfigDir != "" {
|
||||
os.Setenv("CONFIG_DIR", cliArgs.UseConfigDir)
|
||||
}
|
||||
|
||||
if cliArgs.WorkTree != "" {
|
||||
env.SetGitWorkTreeEnv(cliArgs.WorkTree)
|
||||
}
|
||||
|
||||
if cliArgs.GitDir != "" {
|
||||
env.SetGitDirEnv(cliArgs.GitDir)
|
||||
}
|
||||
|
||||
if cliArgs.PrintVersionInfo {
|
||||
gitVersion := getGitVersionInfo()
|
||||
fmt.Printf("commit=%s, build date=%s, build source=%s, version=%s, os=%s, arch=%s, git version=%s\n", buildInfo.Commit, buildInfo.Date, buildInfo.BuildSource, buildInfo.Version, runtime.GOOS, runtime.GOARCH, gitVersion)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if cliArgs.PrintDefaultConfig {
|
||||
var buf bytes.Buffer
|
||||
encoder := yaml.NewEncoder(&buf)
|
||||
err := encoder.Encode(config.GetDefaultConfig())
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
fmt.Printf("%s\n", buf.String())
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if cliArgs.PrintConfigDir {
|
||||
fmt.Printf("%s\n", config.ConfigDir())
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if cliArgs.TailLogs {
|
||||
logs.TailLogs()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if cliArgs.WorkTree != "" {
|
||||
if err := os.Chdir(cliArgs.WorkTree); err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
tempDir, err := os.MkdirTemp("", "lazygit-*")
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
appConfig, err := config.NewAppConfig("lazygit", buildInfo.Version, buildInfo.Commit, buildInfo.Date, buildInfo.BuildSource, cliArgs.Debug, tempDir)
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
|
||||
if integrationTest != nil {
|
||||
integrationTest.SetupConfig(appConfig)
|
||||
}
|
||||
|
||||
common, err := NewCommon(appConfig)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if daemon.InDaemonMode() {
|
||||
daemon.Handle(common)
|
||||
return
|
||||
}
|
||||
|
||||
parsedGitArg := parseGitArg(cliArgs.GitArg)
|
||||
|
||||
Run(appConfig, common, appTypes.NewStartArgs(cliArgs.FilterPath, parsedGitArg, integrationTest))
|
||||
}
|
||||
|
||||
func parseCliArgsAndEnvVars() *cliArgs {
|
||||
flaggy.DefaultParser.ShowVersionWithVersionFlag = false
|
||||
|
||||
repoPath := ""
|
||||
flaggy.String(&repoPath, "p", "path", "Path of git repo. (equivalent to --work-tree=<path> --git-dir=<path>/.git/)")
|
||||
|
||||
filterPath := ""
|
||||
flaggy.String(&filterPath, "f", "filter", "Path to filter on in `git log -- <path>`. When in filter mode, the commits, reflog, and stash are filtered based on the given path, and some operations are restricted")
|
||||
|
||||
gitArg := ""
|
||||
flaggy.AddPositionalValue(&gitArg, "git-arg", 1, false, "Panel to focus upon opening lazygit. Accepted values (based on git terminology): status, branch, log, stash. Ignored if --filter arg is passed.")
|
||||
|
||||
printVersionInfo := false
|
||||
flaggy.Bool(&printVersionInfo, "v", "version", "Print the current version")
|
||||
|
||||
debug := false
|
||||
flaggy.Bool(&debug, "d", "debug", "Run in debug mode with logging (see --logs flag below). Use the LOG_LEVEL env var to set the log level (debug/info/warn/error)")
|
||||
|
||||
tailLogs := false
|
||||
flaggy.Bool(&tailLogs, "l", "logs", "Tail lazygit logs (intended to be used when `lazygit --debug` is called in a separate terminal tab)")
|
||||
|
||||
printDefaultConfig := false
|
||||
flaggy.Bool(&printDefaultConfig, "c", "config", "Print the default config")
|
||||
|
||||
printConfigDir := false
|
||||
flaggy.Bool(&printConfigDir, "cd", "print-config-dir", "Print the config directory")
|
||||
|
||||
useConfigDir := ""
|
||||
flaggy.String(&useConfigDir, "ucd", "use-config-dir", "override default config directory with provided directory")
|
||||
|
||||
workTree := ""
|
||||
flaggy.String(&workTree, "w", "work-tree", "equivalent of the --work-tree git argument")
|
||||
|
||||
gitDir := ""
|
||||
flaggy.String(&gitDir, "g", "git-dir", "equivalent of the --git-dir git argument")
|
||||
|
||||
customConfigFile := ""
|
||||
flaggy.String(&customConfigFile, "ucf", "use-config-file", "Comma separated list to custom config file(s)")
|
||||
|
||||
flaggy.Parse()
|
||||
|
||||
if os.Getenv("DEBUG") == "TRUE" {
|
||||
debug = true
|
||||
}
|
||||
|
||||
return &cliArgs{
|
||||
RepoPath: repoPath,
|
||||
FilterPath: filterPath,
|
||||
GitArg: gitArg,
|
||||
PrintVersionInfo: printVersionInfo,
|
||||
Debug: debug,
|
||||
TailLogs: tailLogs,
|
||||
PrintDefaultConfig: printDefaultConfig,
|
||||
PrintConfigDir: printConfigDir,
|
||||
UseConfigDir: useConfigDir,
|
||||
WorkTree: workTree,
|
||||
GitDir: gitDir,
|
||||
CustomConfigFile: customConfigFile,
|
||||
}
|
||||
}
|
||||
|
||||
func parseGitArg(gitArg string) appTypes.GitArg {
|
||||
typedArg := appTypes.GitArg(gitArg)
|
||||
|
||||
// using switch so that linter catches when a new git arg value is defined but not handled here
|
||||
switch typedArg {
|
||||
case appTypes.GitArgNone, appTypes.GitArgStatus, appTypes.GitArgBranch, appTypes.GitArgLog, appTypes.GitArgStash:
|
||||
return typedArg
|
||||
}
|
||||
|
||||
permittedValues := []string{
|
||||
string(appTypes.GitArgStatus),
|
||||
string(appTypes.GitArgBranch),
|
||||
string(appTypes.GitArgLog),
|
||||
string(appTypes.GitArgStash),
|
||||
}
|
||||
|
||||
log.Fatalf("Invalid git arg value: '%s'. Must be one of the following values: %s. e.g. 'lazygit status'. See 'lazygit --help'.",
|
||||
gitArg,
|
||||
strings.Join(permittedValues, ", "),
|
||||
)
|
||||
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// the buildInfo struct we get passed in is based on what's baked into the lazygit
|
||||
// binary via the LDFLAGS argument. Some lazygit distributions will make use of these
|
||||
// arguments and some will not. Go recently started baking in build info
|
||||
// into the binary by default e.g. the git commit hash. So in this function
|
||||
// we merge the two together, giving priority to the stuff set by LDFLAGS.
|
||||
// Note: this mutates the argument passed in
|
||||
func mergeBuildInfo(buildInfo *BuildInfo) {
|
||||
// if the version has already been set by build flags then we'll honour that.
|
||||
// chances are it's something like v0.31.0 which is more informative than a
|
||||
// commit hash.
|
||||
if buildInfo.Version != "" {
|
||||
return
|
||||
}
|
||||
|
||||
buildInfo.Version = "unversioned"
|
||||
|
||||
goBuildInfo, ok := debug.ReadBuildInfo()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
revision, ok := lo.Find(goBuildInfo.Settings, func(setting debug.BuildSetting) bool {
|
||||
return setting.Key == "vcs.revision"
|
||||
})
|
||||
if ok {
|
||||
buildInfo.Commit = revision.Value
|
||||
// if lazygit was built from source we'll show the version as the
|
||||
// abbreviated commit hash
|
||||
buildInfo.Version = utils.ShortSha(revision.Value)
|
||||
}
|
||||
|
||||
// if version hasn't been set we assume that neither has the date
|
||||
time, ok := lo.Find(goBuildInfo.Settings, func(setting debug.BuildSetting) bool {
|
||||
return setting.Key == "vcs.time"
|
||||
})
|
||||
if ok {
|
||||
buildInfo.Date = time.Value
|
||||
}
|
||||
}
|
||||
|
||||
func getGitVersionInfo() string {
|
||||
cmd := secureexec.Command("git", "--version")
|
||||
stdout, _ := cmd.Output()
|
||||
gitVersion := strings.Trim(strings.TrimPrefix(string(stdout), "git version "), " \r\n")
|
||||
return gitVersion
|
||||
}
|
||||
+4
-9
@@ -1,7 +1,7 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
func newLogger(config config.AppConfigurer) *logrus.Entry {
|
||||
var log *logrus.Logger
|
||||
if config.GetDebug() || os.Getenv("DEBUG") == "TRUE" {
|
||||
if config.GetDebug() {
|
||||
log = newDevelopmentLogger()
|
||||
} else {
|
||||
log = newProductionLogger()
|
||||
@@ -21,17 +21,12 @@ func newLogger(config config.AppConfigurer) *logrus.Entry {
|
||||
// https://github.com/aybabtme/humanlog
|
||||
log.Formatter = &logrus.JSONFormatter{}
|
||||
|
||||
return log.WithFields(logrus.Fields{
|
||||
"debug": config.GetDebug(),
|
||||
"version": config.GetVersion(),
|
||||
"commit": config.GetCommit(),
|
||||
"buildDate": config.GetBuildDate(),
|
||||
})
|
||||
return log.WithFields(logrus.Fields{})
|
||||
}
|
||||
|
||||
func newProductionLogger() *logrus.Logger {
|
||||
log := logrus.New()
|
||||
log.Out = ioutil.Discard
|
||||
log.Out = io.Discard
|
||||
log.SetLevel(logrus.ErrorLevel)
|
||||
return log
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
package types
|
||||
package app
|
||||
|
||||
import (
|
||||
integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types"
|
||||
)
|
||||
|
||||
// StartArgs is the struct that represents some things we want to do on program start
|
||||
type StartArgs struct {
|
||||
@@ -6,6 +10,8 @@ type StartArgs struct {
|
||||
FilterPath string
|
||||
// GitArg determines what context we open in
|
||||
GitArg GitArg
|
||||
// integration test (only relevant when invoking lazygit in the context of an integration test)
|
||||
IntegrationTest integrationTypes.IntegrationTest
|
||||
}
|
||||
|
||||
type GitArg string
|
||||
@@ -18,9 +24,10 @@ const (
|
||||
GitArgStash GitArg = "stash"
|
||||
)
|
||||
|
||||
func NewStartArgs(filterPath string, gitArg GitArg) StartArgs {
|
||||
func NewStartArgs(filterPath string, gitArg GitArg, test integrationTypes.IntegrationTest) StartArgs {
|
||||
return StartArgs{
|
||||
FilterPath: filterPath,
|
||||
GitArg: gitArg,
|
||||
FilterPath: filterPath,
|
||||
GitArg: gitArg,
|
||||
IntegrationTest: test,
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package cheatsheet
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -13,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
func Check() {
|
||||
dir := GetDir()
|
||||
dir := GetKeybindingsDir()
|
||||
tmpDir := filepath.Join(os.TempDir(), "lazygit_cheatsheet")
|
||||
err := os.RemoveAll(tmpDir)
|
||||
if err != nil {
|
||||
@@ -60,7 +59,7 @@ func obtainContent(dir string) string {
|
||||
content := ""
|
||||
err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
|
||||
if re.MatchString(path) {
|
||||
bytes, err := ioutil.ReadFile(path)
|
||||
bytes, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
log.Fatalf("Error occurred while checking if cheatsheets are up to date: %v", err)
|
||||
}
|
||||
|
||||
+21
-26
@@ -15,13 +15,12 @@ import (
|
||||
|
||||
"github.com/jesseduffield/generics/maps"
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/lazycore/pkg/utils"
|
||||
"github.com/jesseduffield/lazygit/pkg/app"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/i18n"
|
||||
"github.com/jesseduffield/lazygit/pkg/integration"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
@@ -45,8 +44,8 @@ func CommandToRun() string {
|
||||
return "go run scripts/cheatsheet/main.go generate"
|
||||
}
|
||||
|
||||
func GetDir() string {
|
||||
return integration.GetRootDirectory() + "/docs/keybindings"
|
||||
func GetKeybindingsDir() string {
|
||||
return utils.GetLazyRootDirectory() + "/docs/keybindings"
|
||||
}
|
||||
|
||||
func generateAtDir(cheatsheetDir string) {
|
||||
@@ -76,7 +75,7 @@ func generateAtDir(cheatsheetDir string) {
|
||||
}
|
||||
|
||||
func Generate() {
|
||||
generateAtDir(GetDir())
|
||||
generateAtDir(GetKeybindingsDir())
|
||||
}
|
||||
|
||||
func writeString(file *os.File, str string) {
|
||||
@@ -105,10 +104,9 @@ func localisedTitle(tr *i18n.TranslationSet, str string) string {
|
||||
"commits": tr.CommitsTitle,
|
||||
"confirmation": tr.ConfirmationTitle,
|
||||
"information": tr.InformationTitle,
|
||||
"main": tr.MainTitle,
|
||||
"main": tr.NormalTitle,
|
||||
"patchBuilding": tr.PatchBuildingTitle,
|
||||
"merging": tr.MergingTitle,
|
||||
"normal": tr.NormalTitle,
|
||||
"mergeConflicts": tr.MergingTitle,
|
||||
"staging": tr.StagingTitle,
|
||||
"menu": tr.MenuTitle,
|
||||
"search": tr.SearchTitle,
|
||||
@@ -127,19 +125,24 @@ func localisedTitle(tr *i18n.TranslationSet, str string) string {
|
||||
}
|
||||
|
||||
func getBindingSections(bindings []*types.Binding, tr *i18n.TranslationSet) []*bindingSection {
|
||||
excludedViews := []string{"stagingSecondary", "patchBuildingSecondary"}
|
||||
bindingsToDisplay := slices.Filter(bindings, func(binding *types.Binding) bool {
|
||||
return binding.Description != "" || binding.Alternative != ""
|
||||
if lo.Contains(excludedViews, binding.ViewName) {
|
||||
return false
|
||||
}
|
||||
|
||||
return (binding.Description != "" || binding.Alternative != "")
|
||||
})
|
||||
|
||||
bindingsByHeader := utils.MuiltiGroupBy(bindingsToDisplay, func(binding *types.Binding) []header {
|
||||
return getHeaders(binding, tr)
|
||||
bindingsByHeader := lo.GroupBy(bindingsToDisplay, func(binding *types.Binding) header {
|
||||
return getHeader(binding, tr)
|
||||
})
|
||||
|
||||
bindingGroups := maps.MapToSlice(
|
||||
bindingsByHeader,
|
||||
func(header header, hBindings []*types.Binding) headerWithBindings {
|
||||
uniqBindings := lo.UniqBy(hBindings, func(binding *types.Binding) string {
|
||||
return binding.Description + keybindings.GetKeyDisplay(binding.Key)
|
||||
return binding.Description + keybindings.LabelFromKey(binding.Key)
|
||||
})
|
||||
|
||||
return headerWithBindings{
|
||||
@@ -164,24 +167,16 @@ func getBindingSections(bindings []*types.Binding, tr *i18n.TranslationSet) []*b
|
||||
})
|
||||
}
|
||||
|
||||
// a binding may belong to multiple headers if it is applicable to multiple contexts,
|
||||
// for example the copy-to-clipboard binding.
|
||||
func getHeaders(binding *types.Binding, tr *i18n.TranslationSet) []header {
|
||||
func getHeader(binding *types.Binding, tr *i18n.TranslationSet) header {
|
||||
if binding.Tag == "navigation" {
|
||||
return []header{{priority: 2, title: localisedTitle(tr, "navigation")}}
|
||||
return header{priority: 2, title: localisedTitle(tr, "navigation")}
|
||||
}
|
||||
|
||||
if binding.ViewName == "" {
|
||||
return []header{{priority: 3, title: localisedTitle(tr, "global")}}
|
||||
return header{priority: 3, title: localisedTitle(tr, "global")}
|
||||
}
|
||||
|
||||
if len(binding.Contexts) == 0 {
|
||||
return []header{}
|
||||
}
|
||||
|
||||
return slices.Map(binding.Contexts, func(context string) header {
|
||||
return header{priority: 1, title: localisedTitle(tr, context)}
|
||||
})
|
||||
return header{priority: 1, title: localisedTitle(tr, binding.ViewName)}
|
||||
}
|
||||
|
||||
func formatSections(tr *i18n.TranslationSet, bindingSections []*bindingSection) string {
|
||||
@@ -207,10 +202,10 @@ func formatBinding(binding *types.Binding) string {
|
||||
if binding.Alternative != "" {
|
||||
return fmt.Sprintf(
|
||||
" <kbd>%s</kbd>: %s (%s)\n",
|
||||
keybindings.GetKeyDisplay(binding.Key),
|
||||
keybindings.LabelFromKey(binding.Key),
|
||||
binding.Description,
|
||||
binding.Alternative,
|
||||
)
|
||||
}
|
||||
return fmt.Sprintf(" <kbd>%s</kbd>: %s\n", keybindings.GetKeyDisplay(binding.Key), binding.Description)
|
||||
return fmt.Sprintf(" <kbd>%s</kbd>: %s\n", keybindings.LabelFromKey(binding.Key), binding.Description)
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ func TestGetBindingSections(t *testing.T) {
|
||||
bindings: []*types.Binding{
|
||||
{
|
||||
ViewName: "files",
|
||||
Contexts: []string{"files"},
|
||||
Description: "stage file",
|
||||
},
|
||||
},
|
||||
@@ -36,7 +35,6 @@ func TestGetBindingSections(t *testing.T) {
|
||||
bindings: []*types.Binding{
|
||||
{
|
||||
ViewName: "files",
|
||||
Contexts: []string{"files"},
|
||||
Description: "stage file",
|
||||
},
|
||||
},
|
||||
@@ -69,17 +67,14 @@ func TestGetBindingSections(t *testing.T) {
|
||||
{
|
||||
ViewName: "files",
|
||||
Description: "stage file",
|
||||
Contexts: []string{"files"},
|
||||
},
|
||||
{
|
||||
ViewName: "files",
|
||||
Description: "unstage file",
|
||||
Contexts: []string{"files"},
|
||||
},
|
||||
{
|
||||
ViewName: "files",
|
||||
ViewName: "submodules",
|
||||
Description: "drop submodule",
|
||||
Contexts: []string{"submodules"},
|
||||
},
|
||||
},
|
||||
expected: []*bindingSection{
|
||||
@@ -89,12 +84,10 @@ func TestGetBindingSections(t *testing.T) {
|
||||
{
|
||||
ViewName: "files",
|
||||
Description: "stage file",
|
||||
Contexts: []string{"files"},
|
||||
},
|
||||
{
|
||||
ViewName: "files",
|
||||
Description: "unstage file",
|
||||
Contexts: []string{"files"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -102,9 +95,8 @@ func TestGetBindingSections(t *testing.T) {
|
||||
title: "Submodules",
|
||||
bindings: []*types.Binding{
|
||||
{
|
||||
ViewName: "files",
|
||||
ViewName: "submodules",
|
||||
Description: "drop submodule",
|
||||
Contexts: []string{"submodules"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -115,23 +107,19 @@ func TestGetBindingSections(t *testing.T) {
|
||||
bindings: []*types.Binding{
|
||||
{
|
||||
ViewName: "files",
|
||||
Contexts: []string{"files"},
|
||||
Description: "stage file",
|
||||
},
|
||||
{
|
||||
ViewName: "files",
|
||||
Contexts: []string{"files"},
|
||||
Description: "unstage file",
|
||||
},
|
||||
{
|
||||
ViewName: "files",
|
||||
Contexts: []string{"files"},
|
||||
Description: "scroll",
|
||||
Tag: "navigation",
|
||||
},
|
||||
{
|
||||
ViewName: "commits",
|
||||
Contexts: []string{"commits"},
|
||||
Description: "revert commit",
|
||||
},
|
||||
},
|
||||
@@ -141,7 +129,6 @@ func TestGetBindingSections(t *testing.T) {
|
||||
bindings: []*types.Binding{
|
||||
{
|
||||
ViewName: "files",
|
||||
Contexts: []string{"files"},
|
||||
Description: "scroll",
|
||||
Tag: "navigation",
|
||||
},
|
||||
@@ -152,7 +139,6 @@ func TestGetBindingSections(t *testing.T) {
|
||||
bindings: []*types.Binding{
|
||||
{
|
||||
ViewName: "commits",
|
||||
Contexts: []string{"commits"},
|
||||
Description: "revert commit",
|
||||
},
|
||||
},
|
||||
@@ -162,12 +148,10 @@ func TestGetBindingSections(t *testing.T) {
|
||||
bindings: []*types.Binding{
|
||||
{
|
||||
ViewName: "files",
|
||||
Contexts: []string{"files"},
|
||||
Description: "stage file",
|
||||
},
|
||||
{
|
||||
ViewName: "files",
|
||||
Contexts: []string{"files"},
|
||||
Description: "unstage file",
|
||||
},
|
||||
},
|
||||
@@ -179,34 +163,28 @@ func TestGetBindingSections(t *testing.T) {
|
||||
bindings: []*types.Binding{
|
||||
{
|
||||
ViewName: "files",
|
||||
Contexts: []string{"files"},
|
||||
Description: "stage file",
|
||||
},
|
||||
{
|
||||
ViewName: "files",
|
||||
Contexts: []string{"files"},
|
||||
Description: "unstage file",
|
||||
},
|
||||
{
|
||||
ViewName: "files",
|
||||
Contexts: []string{"files"},
|
||||
Description: "scroll",
|
||||
Tag: "navigation",
|
||||
},
|
||||
{
|
||||
ViewName: "commits",
|
||||
Contexts: []string{"commits"},
|
||||
Description: "revert commit",
|
||||
},
|
||||
{
|
||||
ViewName: "commits",
|
||||
Contexts: []string{"commits"},
|
||||
Description: "scroll",
|
||||
Tag: "navigation",
|
||||
},
|
||||
{
|
||||
ViewName: "commits",
|
||||
Contexts: []string{"commits"},
|
||||
Description: "page up",
|
||||
Tag: "navigation",
|
||||
},
|
||||
@@ -217,13 +195,11 @@ func TestGetBindingSections(t *testing.T) {
|
||||
bindings: []*types.Binding{
|
||||
{
|
||||
ViewName: "files",
|
||||
Contexts: []string{"files"},
|
||||
Description: "scroll",
|
||||
Tag: "navigation",
|
||||
},
|
||||
{
|
||||
ViewName: "commits",
|
||||
Contexts: []string{"commits"},
|
||||
Description: "page up",
|
||||
Tag: "navigation",
|
||||
},
|
||||
@@ -234,7 +210,6 @@ func TestGetBindingSections(t *testing.T) {
|
||||
bindings: []*types.Binding{
|
||||
{
|
||||
ViewName: "commits",
|
||||
Contexts: []string{"commits"},
|
||||
Description: "revert commit",
|
||||
},
|
||||
},
|
||||
@@ -244,12 +219,10 @@ func TestGetBindingSections(t *testing.T) {
|
||||
bindings: []*types.Binding{
|
||||
{
|
||||
ViewName: "files",
|
||||
Contexts: []string{"files"},
|
||||
Description: "stage file",
|
||||
},
|
||||
{
|
||||
ViewName: "files",
|
||||
Contexts: []string{"files"},
|
||||
Description: "unstage file",
|
||||
},
|
||||
},
|
||||
|
||||
+31
-24
@@ -1,18 +1,16 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
|
||||
gogit "github.com/jesseduffield/go-git/v5"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_config"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/loaders"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/patch"
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
@@ -43,21 +41,21 @@ type GitCommand struct {
|
||||
}
|
||||
|
||||
type Loaders struct {
|
||||
Branches *loaders.BranchLoader
|
||||
CommitFiles *loaders.CommitFileLoader
|
||||
Commits *loaders.CommitLoader
|
||||
Files *loaders.FileLoader
|
||||
ReflogCommits *loaders.ReflogCommitLoader
|
||||
Remotes *loaders.RemoteLoader
|
||||
Stash *loaders.StashLoader
|
||||
Tags *loaders.TagLoader
|
||||
BranchLoader *git_commands.BranchLoader
|
||||
CommitFileLoader *git_commands.CommitFileLoader
|
||||
CommitLoader *git_commands.CommitLoader
|
||||
FileLoader *git_commands.FileLoader
|
||||
ReflogCommitLoader *git_commands.ReflogCommitLoader
|
||||
RemoteLoader *git_commands.RemoteLoader
|
||||
StashLoader *git_commands.StashLoader
|
||||
TagLoader *git_commands.TagLoader
|
||||
}
|
||||
|
||||
func NewGitCommand(
|
||||
cmn *common.Common,
|
||||
osCommand *oscommands.OSCommand,
|
||||
gitConfig git_config.IGitConfig,
|
||||
syncMutex *sync.Mutex,
|
||||
syncMutex *deadlock.Mutex,
|
||||
) (*GitCommand, error) {
|
||||
if err := navigateToRepoRootDirectory(os.Stat, os.Chdir); err != nil {
|
||||
return nil, err
|
||||
@@ -68,7 +66,7 @@ func NewGitCommand(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dotGitDir, err := findDotGitDir(os.Stat, ioutil.ReadFile)
|
||||
dotGitDir, err := findDotGitDir(os.Stat, os.ReadFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -89,7 +87,7 @@ func NewGitCommandAux(
|
||||
gitConfig git_config.IGitConfig,
|
||||
dotGitDir string,
|
||||
repo *gogit.Repository,
|
||||
syncMutex *sync.Mutex,
|
||||
syncMutex *deadlock.Mutex,
|
||||
) *GitCommand {
|
||||
cmd := NewGitCmdObjBuilder(cmn.Log, osCommand.Cmd)
|
||||
|
||||
@@ -99,10 +97,11 @@ func NewGitCommandAux(
|
||||
// on the one struct.
|
||||
// common ones are: cmn, osCommand, dotGitDir, configCommands
|
||||
configCommands := git_commands.NewConfigCommands(cmn, gitConfig, repo)
|
||||
gitCommon := git_commands.NewGitCommon(cmn, cmd, osCommand, dotGitDir, repo, configCommands, syncMutex)
|
||||
|
||||
fileLoader := git_commands.NewFileLoader(cmn, cmd, configCommands)
|
||||
|
||||
gitCommon := git_commands.NewGitCommon(cmn, cmd, osCommand, dotGitDir, repo, configCommands, syncMutex)
|
||||
statusCommands := git_commands.NewStatusCommands(gitCommon)
|
||||
fileLoader := loaders.NewFileLoader(cmn, cmd, configCommands)
|
||||
flowCommands := git_commands.NewFlowCommands(gitCommon)
|
||||
remoteCommands := git_commands.NewRemoteCommands(gitCommon)
|
||||
branchCommands := git_commands.NewBranchCommands(gitCommon)
|
||||
@@ -120,6 +119,14 @@ func NewGitCommandAux(
|
||||
patchCommands := git_commands.NewPatchCommands(gitCommon, rebaseCommands, commitCommands, statusCommands, stashCommands, patchManager)
|
||||
bisectCommands := git_commands.NewBisectCommands(gitCommon)
|
||||
|
||||
branchLoader := git_commands.NewBranchLoader(cmn, branchCommands.GetRawBranches, branchCommands.CurrentBranchInfo, configCommands)
|
||||
commitFileLoader := git_commands.NewCommitFileLoader(cmn, cmd)
|
||||
commitLoader := git_commands.NewCommitLoader(cmn, cmd, dotGitDir, branchCommands.CurrentBranchInfo, statusCommands.RebaseMode)
|
||||
reflogCommitLoader := git_commands.NewReflogCommitLoader(cmn, cmd)
|
||||
remoteLoader := git_commands.NewRemoteLoader(cmn, cmd, repo.Remotes)
|
||||
stashLoader := git_commands.NewStashLoader(cmn, cmd)
|
||||
tagLoader := git_commands.NewTagLoader(cmn, cmd)
|
||||
|
||||
return &GitCommand{
|
||||
Branch: branchCommands,
|
||||
Commit: commitCommands,
|
||||
@@ -138,14 +145,14 @@ func NewGitCommandAux(
|
||||
Bisect: bisectCommands,
|
||||
WorkingTree: workingTreeCommands,
|
||||
Loaders: Loaders{
|
||||
Branches: loaders.NewBranchLoader(cmn, branchCommands.GetRawBranches, branchCommands.CurrentBranchName, configCommands),
|
||||
CommitFiles: loaders.NewCommitFileLoader(cmn, cmd),
|
||||
Commits: loaders.NewCommitLoader(cmn, cmd, dotGitDir, branchCommands.CurrentBranchName, statusCommands.RebaseMode),
|
||||
Files: fileLoader,
|
||||
ReflogCommits: loaders.NewReflogCommitLoader(cmn, cmd),
|
||||
Remotes: loaders.NewRemoteLoader(cmn, cmd, repo.Remotes),
|
||||
Stash: loaders.NewStashLoader(cmn, cmd),
|
||||
Tags: loaders.NewTagLoader(cmn, cmd),
|
||||
BranchLoader: branchLoader,
|
||||
CommitFileLoader: commitFileLoader,
|
||||
CommitLoader: commitLoader,
|
||||
FileLoader: fileLoader,
|
||||
ReflogCommitLoader: reflogCommitLoader,
|
||||
RemoteLoader: remoteLoader,
|
||||
StashLoader: stashLoader,
|
||||
TagLoader: tagLoader,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,10 @@ func (self *gitCmdObjRunner) RunWithOutput(cmdObj oscommands.ICmdObj) (string, e
|
||||
return self.innerRunner.RunWithOutput(cmdObj)
|
||||
}
|
||||
|
||||
func (self *gitCmdObjRunner) RunWithOutputs(cmdObj oscommands.ICmdObj) (string, string, error) {
|
||||
return self.innerRunner.RunWithOutputs(cmdObj)
|
||||
}
|
||||
|
||||
func (self *gitCmdObjRunner) RunAndProcessLines(cmdObj oscommands.ICmdObj, onLine func(line string) (bool, error)) error {
|
||||
return self.innerRunner.RunAndProcessLines(cmdObj, onLine)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
// this takes something like:
|
||||
// * (HEAD detached at 264fc6f5)
|
||||
// remotes
|
||||
// remotes
|
||||
// and returns '264fc6f5' as the second match
|
||||
const CurrentBranchNameRegex = `(?m)^\*.*?([^ ]*?)\)?$`
|
||||
|
||||
@@ -30,18 +30,20 @@ func (self *BranchCommands) New(name string, base string) error {
|
||||
return self.cmd.New(fmt.Sprintf("git checkout -b %s %s", self.cmd.Quote(name), self.cmd.Quote(base))).Run()
|
||||
}
|
||||
|
||||
// CurrentBranchName get the current branch name and displayname.
|
||||
// the first returned string is the name and the second is the displayname
|
||||
// e.g. name is 123asdf and displayname is '(HEAD detached at 123asdf)'
|
||||
func (self *BranchCommands) CurrentBranchName() (string, string, error) {
|
||||
// CurrentBranchInfo get the current branch information.
|
||||
func (self *BranchCommands) CurrentBranchInfo() (BranchInfo, error) {
|
||||
branchName, err := self.cmd.New("git symbolic-ref --short HEAD").DontLog().RunWithOutput()
|
||||
if err == nil && branchName != "HEAD\n" {
|
||||
trimmedBranchName := strings.TrimSpace(branchName)
|
||||
return trimmedBranchName, trimmedBranchName, nil
|
||||
return BranchInfo{
|
||||
RefName: trimmedBranchName,
|
||||
DisplayName: trimmedBranchName,
|
||||
DetachedHead: false,
|
||||
}, nil
|
||||
}
|
||||
output, err := self.cmd.New("git branch --contains").DontLog().RunWithOutput()
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
return BranchInfo{}, err
|
||||
}
|
||||
for _, line := range utils.SplitLines(output) {
|
||||
re := regexp.MustCompile(CurrentBranchNameRegex)
|
||||
@@ -49,10 +51,18 @@ func (self *BranchCommands) CurrentBranchName() (string, string, error) {
|
||||
if len(match) > 0 {
|
||||
branchName = match[1]
|
||||
displayBranchName := match[0][2:]
|
||||
return branchName, displayBranchName, nil
|
||||
return BranchInfo{
|
||||
RefName: branchName,
|
||||
DisplayName: displayBranchName,
|
||||
DetachedHead: true,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
return "HEAD", "HEAD", nil
|
||||
return BranchInfo{
|
||||
RefName: "HEAD",
|
||||
DisplayName: "HEAD",
|
||||
DetachedHead: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Delete delete branch
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package loaders
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
@@ -27,24 +27,30 @@ type BranchLoaderConfigCommands interface {
|
||||
Branches() (map[string]*config.Branch, error)
|
||||
}
|
||||
|
||||
type BranchInfo struct {
|
||||
RefName string
|
||||
DisplayName string // e.g. '(HEAD detached at 123asdf)'
|
||||
DetachedHead bool
|
||||
}
|
||||
|
||||
// BranchLoader returns a list of Branch objects for the current repo
|
||||
type BranchLoader struct {
|
||||
*common.Common
|
||||
getRawBranches func() (string, error)
|
||||
getCurrentBranchName func() (string, string, error)
|
||||
getCurrentBranchInfo func() (BranchInfo, error)
|
||||
config BranchLoaderConfigCommands
|
||||
}
|
||||
|
||||
func NewBranchLoader(
|
||||
cmn *common.Common,
|
||||
getRawBranches func() (string, error),
|
||||
getCurrentBranchName func() (string, string, error),
|
||||
getCurrentBranchInfo func() (BranchInfo, error),
|
||||
config BranchLoaderConfigCommands,
|
||||
) *BranchLoader {
|
||||
return &BranchLoader{
|
||||
Common: cmn,
|
||||
getRawBranches: getRawBranches,
|
||||
getCurrentBranchName: getCurrentBranchName,
|
||||
getCurrentBranchInfo: getCurrentBranchInfo,
|
||||
config: config,
|
||||
}
|
||||
}
|
||||
@@ -84,11 +90,11 @@ outer:
|
||||
}
|
||||
}
|
||||
if !foundHead {
|
||||
currentBranchName, currentBranchDisplayName, err := self.getCurrentBranchName()
|
||||
info, err := self.getCurrentBranchInfo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
branches = slices.Prepend(branches, &models.Branch{Name: currentBranchName, DisplayName: currentBranchDisplayName, Head: true, Recency: " *"})
|
||||
branches = slices.Prepend(branches, &models.Branch{Name: info.RefName, DisplayName: info.DisplayName, Head: true, DetachedHead: info.DetachedHead, Recency: " *"})
|
||||
}
|
||||
|
||||
configBranches, err := self.config.Branches()
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package loaders
|
||||
package git_commands
|
||||
|
||||
// "*|feat/detect-purge|origin/feat/detect-purge|[ahead 1]"
|
||||
import (
|
||||
@@ -53,10 +53,10 @@ func TestBranchGetCommitDifferences(t *testing.T) {
|
||||
|
||||
func TestBranchNewBranch(t *testing.T) {
|
||||
runner := oscommands.NewFakeRunner(t).
|
||||
Expect(`git checkout -b "test" "master"`, "", nil)
|
||||
Expect(`git checkout -b "test" "refs/heads/master"`, "", nil)
|
||||
instance := buildBranchCommands(commonDeps{runner: runner})
|
||||
|
||||
assert.NoError(t, instance.New("test", "master"))
|
||||
assert.NoError(t, instance.New("test", "refs/heads/master"))
|
||||
runner.CheckForMissingCalls()
|
||||
}
|
||||
|
||||
@@ -162,32 +162,34 @@ func TestBranchGetAllBranchGraph(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestBranchCurrentBranchName(t *testing.T) {
|
||||
func TestBranchCurrentBranchInfo(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
runner *oscommands.FakeCmdObjRunner
|
||||
test func(string, string, error)
|
||||
test func(BranchInfo, error)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
"says we are on the master branch if we are",
|
||||
oscommands.NewFakeRunner(t).Expect(`git symbolic-ref --short HEAD`, "master", nil),
|
||||
func(name string, displayname string, err error) {
|
||||
func(info BranchInfo, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, "master", name)
|
||||
assert.EqualValues(t, "master", displayname)
|
||||
assert.EqualValues(t, "master", info.RefName)
|
||||
assert.EqualValues(t, "master", info.DisplayName)
|
||||
assert.False(t, info.DetachedHead)
|
||||
},
|
||||
},
|
||||
{
|
||||
"falls back to git `git branch --contains` if symbolic-ref fails",
|
||||
oscommands.NewFakeRunner(t).
|
||||
Expect(`git symbolic-ref --short HEAD`, "", errors.New("error")).
|
||||
Expect(`git branch --contains`, "* master", nil),
|
||||
func(name string, displayname string, err error) {
|
||||
Expect(`git branch --contains`, "* (HEAD detached at 8982166a)", nil),
|
||||
func(info BranchInfo, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, "master", name)
|
||||
assert.EqualValues(t, "master", displayname)
|
||||
assert.EqualValues(t, "8982166a", info.RefName)
|
||||
assert.EqualValues(t, "(HEAD detached at 8982166a)", info.DisplayName)
|
||||
assert.True(t, info.DetachedHead)
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -195,10 +197,11 @@ func TestBranchCurrentBranchName(t *testing.T) {
|
||||
oscommands.NewFakeRunner(t).
|
||||
Expect(`git symbolic-ref --short HEAD`, "", errors.New("error")).
|
||||
Expect(`git branch --contains`, "* (HEAD detached at 123abcd)", nil),
|
||||
func(name string, displayname string, err error) {
|
||||
func(info BranchInfo, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, "123abcd", name)
|
||||
assert.EqualValues(t, "(HEAD detached at 123abcd)", displayname)
|
||||
assert.EqualValues(t, "123abcd", info.RefName)
|
||||
assert.EqualValues(t, "(HEAD detached at 123abcd)", info.DisplayName)
|
||||
assert.True(t, info.DetachedHead)
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -206,10 +209,11 @@ func TestBranchCurrentBranchName(t *testing.T) {
|
||||
oscommands.NewFakeRunner(t).
|
||||
Expect(`git symbolic-ref --short HEAD`, "", errors.New("error")).
|
||||
Expect(`git branch --contains`, "", errors.New("error")),
|
||||
func(name string, displayname string, err error) {
|
||||
func(info BranchInfo, err error) {
|
||||
assert.Error(t, err)
|
||||
assert.EqualValues(t, "", name)
|
||||
assert.EqualValues(t, "", displayname)
|
||||
assert.EqualValues(t, "", info.RefName)
|
||||
assert.EqualValues(t, "", info.DisplayName)
|
||||
assert.False(t, info.DetachedHead)
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -218,7 +222,7 @@ func TestBranchCurrentBranchName(t *testing.T) {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildBranchCommands(commonDeps{runner: s.runner})
|
||||
s.test(instance.CurrentBranchName())
|
||||
s.test(instance.CurrentBranchInfo())
|
||||
s.runner.CheckForMissingCalls()
|
||||
})
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package loaders
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package loaders
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@@ -1,9 +1,8 @@
|
||||
package loaders
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
@@ -30,7 +29,7 @@ type CommitLoader struct {
|
||||
*common.Common
|
||||
cmd oscommands.ICmdObjBuilder
|
||||
|
||||
getCurrentBranchName func() (string, string, error)
|
||||
getCurrentBranchInfo func() (BranchInfo, error)
|
||||
getRebaseMode func() (enums.RebaseMode, error)
|
||||
readFile func(filename string) ([]byte, error)
|
||||
walkFiles func(root string, fn filepath.WalkFunc) error
|
||||
@@ -42,15 +41,15 @@ func NewCommitLoader(
|
||||
cmn *common.Common,
|
||||
cmd oscommands.ICmdObjBuilder,
|
||||
dotGitDir string,
|
||||
getCurrentBranchName func() (string, string, error),
|
||||
getCurrentBranchInfo func() (BranchInfo, error),
|
||||
getRebaseMode func() (enums.RebaseMode, error),
|
||||
) *CommitLoader {
|
||||
return &CommitLoader{
|
||||
Common: cmn,
|
||||
cmd: cmd,
|
||||
getCurrentBranchName: getCurrentBranchName,
|
||||
getCurrentBranchInfo: getCurrentBranchInfo,
|
||||
getRebaseMode: getRebaseMode,
|
||||
readFile: ioutil.ReadFile,
|
||||
readFile: os.ReadFile,
|
||||
walkFiles: filepath.Walk,
|
||||
dotGitDir: dotGitDir,
|
||||
}
|
||||
@@ -372,13 +371,13 @@ func (self *CommitLoader) setCommitMergedStatuses(refName string, commits []*mod
|
||||
}
|
||||
|
||||
func (self *CommitLoader) getMergeBase(refName string) (string, error) {
|
||||
currentBranch, _, err := self.getCurrentBranchName()
|
||||
info, err := self.getCurrentBranchInfo()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
baseBranch := "master"
|
||||
if strings.HasPrefix(currentBranch, "feature/") {
|
||||
if strings.HasPrefix(info.RefName, "feature/") {
|
||||
baseBranch = "develop"
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
package loaders
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
@@ -182,8 +182,8 @@ func TestGetCommits(t *testing.T) {
|
||||
builder := &CommitLoader{
|
||||
Common: utils.NewDummyCommon(),
|
||||
cmd: oscommands.NewDummyCmdObjBuilder(scenario.runner),
|
||||
getCurrentBranchName: func() (string, string, error) {
|
||||
return scenario.currentBranchName, scenario.currentBranchName, nil
|
||||
getCurrentBranchInfo: func() (BranchInfo, error) {
|
||||
return BranchInfo{RefName: scenario.currentBranchName, DisplayName: scenario.currentBranchName, DetachedHead: false}, nil
|
||||
},
|
||||
getRebaseMode: func() (enums.RebaseMode, error) { return scenario.rebaseMode, nil },
|
||||
dotGitDir: ".git",
|
||||
@@ -1,11 +1,10 @@
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
gogit "github.com/jesseduffield/go-git/v5"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
)
|
||||
|
||||
type GitCommon struct {
|
||||
@@ -16,7 +15,7 @@ type GitCommon struct {
|
||||
repo *gogit.Repository
|
||||
config *ConfigCommands
|
||||
// mutex for doing things like push/pull/fetch
|
||||
syncMutex *sync.Mutex
|
||||
syncMutex *deadlock.Mutex
|
||||
}
|
||||
|
||||
func NewGitCommon(
|
||||
@@ -26,7 +25,7 @@ func NewGitCommon(
|
||||
dotGitDir string,
|
||||
repo *gogit.Repository,
|
||||
config *ConfigCommands,
|
||||
syncMutex *sync.Mutex,
|
||||
syncMutex *deadlock.Mutex,
|
||||
) *GitCommon {
|
||||
return &GitCommon{
|
||||
Common: cmn,
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
gogit "github.com/jesseduffield/go-git/v5"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_config"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/loaders"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
@@ -70,6 +71,7 @@ func buildGitCommon(deps commonDeps) *GitCommon {
|
||||
GetenvFn: getenv,
|
||||
Cmd: cmd,
|
||||
RemoveFileFn: removeFile,
|
||||
TempDir: os.TempDir(),
|
||||
})
|
||||
|
||||
gitCommon.dotGitDir = deps.dotGitDir
|
||||
@@ -86,8 +88,8 @@ func buildRepo() *gogit.Repository {
|
||||
return repo
|
||||
}
|
||||
|
||||
func buildFileLoader(gitCommon *GitCommon) *loaders.FileLoader {
|
||||
return loaders.NewFileLoader(gitCommon.Common, gitCommon.cmd, gitCommon.config)
|
||||
func buildFileLoader(gitCommon *GitCommon) *FileLoader {
|
||||
return NewFileLoader(gitCommon.Common, gitCommon.cmd, gitCommon.config)
|
||||
}
|
||||
|
||||
func buildSubmoduleCommands(deps commonDeps) *SubmoduleCommands {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
@@ -20,7 +20,7 @@ func NewFileCommands(gitCommon *GitCommon) *FileCommands {
|
||||
|
||||
// Cat obtains the content of a file
|
||||
func (self *FileCommands) Cat(fileName string) (string, error) {
|
||||
buf, err := ioutil.ReadFile(fileName)
|
||||
buf, err := os.ReadFile(fileName)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package loaders
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type FileLoaderConfig interface {
|
||||
@@ -54,28 +53,15 @@ func (self *FileLoader) GetStatusFiles(opts GetStatusFileOptions) []*models.File
|
||||
self.Log.Warningf("warning when calling git status: %s", status.StatusString)
|
||||
continue
|
||||
}
|
||||
change := status.Change
|
||||
stagedChange := change[0:1]
|
||||
unstagedChange := change[1:2]
|
||||
untracked := lo.Contains([]string{"??", "A ", "AM"}, change)
|
||||
hasNoStagedChanges := lo.Contains([]string{" ", "U", "?"}, stagedChange)
|
||||
hasInlineMergeConflicts := lo.Contains([]string{"UU", "AA"}, change)
|
||||
hasMergeConflicts := hasInlineMergeConflicts || lo.Contains([]string{"DD", "AU", "UA", "UD", "DU"}, change)
|
||||
|
||||
file := &models.File{
|
||||
Name: status.Name,
|
||||
PreviousName: status.PreviousName,
|
||||
DisplayString: status.StatusString,
|
||||
HasStagedChanges: !hasNoStagedChanges,
|
||||
HasUnstagedChanges: unstagedChange != " ",
|
||||
Tracked: !untracked,
|
||||
Deleted: unstagedChange == "D" || stagedChange == "D",
|
||||
Added: unstagedChange == "A" || untracked,
|
||||
HasMergeConflicts: hasMergeConflicts,
|
||||
HasInlineMergeConflicts: hasInlineMergeConflicts,
|
||||
Type: self.getFileType(status.Name),
|
||||
ShortStatus: change,
|
||||
Name: status.Name,
|
||||
PreviousName: status.PreviousName,
|
||||
DisplayString: status.StatusString,
|
||||
Type: self.getFileType(status.Name),
|
||||
}
|
||||
|
||||
models.SetStatusFields(file, status.Change)
|
||||
files = append(files, file)
|
||||
}
|
||||
|
||||
@@ -101,7 +87,7 @@ func (c *FileLoader) GitStatus(opts GitStatusOptions) ([]FileStatus, error) {
|
||||
noRenamesFlag = " --no-renames"
|
||||
}
|
||||
|
||||
statusLines, err := c.cmd.New(fmt.Sprintf("git status %s --porcelain -z%s", opts.UntrackedFilesArg, noRenamesFlag)).DontLog().RunWithOutput()
|
||||
statusLines, _, err := c.cmd.New(fmt.Sprintf("git status %s --porcelain -z%s", opts.UntrackedFilesArg, noRenamesFlag)).DontLog().RunWithOutputs()
|
||||
if err != nil {
|
||||
return []FileStatus{}, err
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package loaders
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@@ -2,7 +2,7 @@ package git_commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
@@ -130,7 +130,7 @@ func (self *RebaseCommands) PrepareInteractiveRebaseCommand(baseSha string, todo
|
||||
}
|
||||
|
||||
cmdStr := fmt.Sprintf("git rebase --interactive --autostash --keep-empty %s", baseSha)
|
||||
self.Log.WithField("command", cmdStr).Info("RunCommand")
|
||||
self.Log.WithField("command", cmdStr).Debug("RunCommand")
|
||||
|
||||
cmdObj := self.cmd.New(cmdStr)
|
||||
|
||||
@@ -202,7 +202,7 @@ func (self *RebaseCommands) AmendTo(sha string) error {
|
||||
// EditRebaseTodo sets the action at a given index in the git-rebase-todo file
|
||||
func (self *RebaseCommands) EditRebaseTodo(index int, action string) error {
|
||||
fileName := filepath.Join(self.dotGitDir, "rebase-merge/git-rebase-todo")
|
||||
bytes, err := ioutil.ReadFile(fileName)
|
||||
bytes, err := os.ReadFile(fileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -217,7 +217,7 @@ func (self *RebaseCommands) EditRebaseTodo(index int, action string) error {
|
||||
content[contentIndex] = action + " " + strings.Join(splitLine[1:], " ")
|
||||
result := strings.Join(content, "\n")
|
||||
|
||||
return ioutil.WriteFile(fileName, []byte(result), 0o644)
|
||||
return os.WriteFile(fileName, []byte(result), 0o644)
|
||||
}
|
||||
|
||||
func (self *RebaseCommands) getTodoCommitCount(content []string) int {
|
||||
@@ -234,7 +234,7 @@ func (self *RebaseCommands) getTodoCommitCount(content []string) int {
|
||||
// MoveTodoDown moves a rebase todo item down by one position
|
||||
func (self *RebaseCommands) MoveTodoDown(index int) error {
|
||||
fileName := filepath.Join(self.dotGitDir, "rebase-merge/git-rebase-todo")
|
||||
bytes, err := ioutil.ReadFile(fileName)
|
||||
bytes, err := os.ReadFile(fileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -247,7 +247,7 @@ func (self *RebaseCommands) MoveTodoDown(index int) error {
|
||||
rearrangedContent = append(rearrangedContent, content[contentIndex+1:]...)
|
||||
result := strings.Join(rearrangedContent, "\n")
|
||||
|
||||
return ioutil.WriteFile(fileName, []byte(result), 0o644)
|
||||
return os.WriteFile(fileName, []byte(result), 0o644)
|
||||
}
|
||||
|
||||
// SquashAllAboveFixupCommits squashes all fixup! commits above the given one
|
||||
@@ -255,7 +255,7 @@ func (self *RebaseCommands) SquashAllAboveFixupCommits(sha string) error {
|
||||
return self.runSkipEditorCommand(
|
||||
self.cmd.New(
|
||||
fmt.Sprintf(
|
||||
"git rebase --interactive --autostash --autosquash %s^",
|
||||
"git rebase --interactive --rebase-merges --autostash --autosquash %s^",
|
||||
sha,
|
||||
),
|
||||
),
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package loaders
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package loaders
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@@ -1,4 +1,4 @@
|
||||
package loaders
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -2,20 +2,20 @@ package git_commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/loaders"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
)
|
||||
|
||||
type StashCommands struct {
|
||||
*GitCommon
|
||||
fileLoader *loaders.FileLoader
|
||||
fileLoader *FileLoader
|
||||
workingTree *WorkingTreeCommands
|
||||
}
|
||||
|
||||
func NewStashCommands(
|
||||
gitCommon *GitCommon,
|
||||
fileLoader *loaders.FileLoader,
|
||||
fileLoader *FileLoader,
|
||||
workingTree *WorkingTreeCommands,
|
||||
) *StashCommands {
|
||||
return &StashCommands{
|
||||
@@ -46,6 +46,19 @@ func (self *StashCommands) Save(message string) error {
|
||||
return self.cmd.New("git stash save " + self.cmd.Quote(message)).Run()
|
||||
}
|
||||
|
||||
func (self *StashCommands) Store(sha string, message string) error {
|
||||
trimmedMessage := strings.Trim(message, " \t")
|
||||
if len(trimmedMessage) > 0 {
|
||||
return self.cmd.New(fmt.Sprintf("git stash store %s -m %s", self.cmd.Quote(sha), self.cmd.Quote(trimmedMessage))).Run()
|
||||
}
|
||||
return self.cmd.New(fmt.Sprintf("git stash store %s", self.cmd.Quote(sha))).Run()
|
||||
}
|
||||
|
||||
func (self *StashCommands) Sha(index int) (string, error) {
|
||||
sha, _, err := self.cmd.New(fmt.Sprintf("git rev-parse refs/stash@{%d}", index)).DontLog().RunWithOutputs()
|
||||
return strings.Trim(sha, "\r\n"), err
|
||||
}
|
||||
|
||||
func (self *StashCommands) ShowStashEntryCmdObj(index int) oscommands.ICmdObj {
|
||||
cmdStr := fmt.Sprintf("git stash show -p --stat --color=%s --unified=%d stash@{%d}", self.UserConfig.Git.Paging.ColorArg, self.UserConfig.Git.DiffContextSize, index)
|
||||
|
||||
@@ -97,7 +110,7 @@ func (self *StashCommands) SaveStagedChanges(message string) error {
|
||||
// meaning it's deleted in your working tree but added in your index. Given that it's
|
||||
// now safely stashed, we need to remove it.
|
||||
files := self.fileLoader.
|
||||
GetStatusFiles(loaders.GetStatusFileOptions{})
|
||||
GetStatusFiles(GetStatusFileOptions{})
|
||||
|
||||
for _, file := range files {
|
||||
if file.ShortStatus == "AD" {
|
||||
@@ -109,3 +122,25 @@ func (self *StashCommands) SaveStagedChanges(message string) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *StashCommands) StashIncludeUntrackedChanges(message string) error {
|
||||
return self.cmd.New(fmt.Sprintf("git stash save %s --include-untracked", self.cmd.Quote(message))).Run()
|
||||
}
|
||||
|
||||
func (self *StashCommands) Rename(index int, message string) error {
|
||||
sha, err := self.Sha(index)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.Drop(index); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = self.Store(sha, message)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package loaders
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
@@ -65,8 +65,8 @@ outer:
|
||||
}
|
||||
|
||||
func (self *StashLoader) getUnfilteredStashEntries() []*models.StashEntry {
|
||||
rawString, _ := self.cmd.New("git stash list --pretty='%gs'").DontLog().RunWithOutput()
|
||||
return slices.MapWithIndex(utils.SplitLines(rawString), func(line string, index int) *models.StashEntry {
|
||||
rawString, _ := self.cmd.New("git stash list -z --pretty='%gs'").DontLog().RunWithOutput()
|
||||
return slices.MapWithIndex(utils.SplitNul(rawString), func(line string, index int) *models.StashEntry {
|
||||
return self.stashEntryFromLine(line, index)
|
||||
})
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package loaders
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@@ -22,7 +22,7 @@ func TestGetStashEntries(t *testing.T) {
|
||||
"No stash entries found",
|
||||
"",
|
||||
oscommands.NewFakeRunner(t).
|
||||
Expect(`git stash list --pretty='%gs'`, "", nil),
|
||||
Expect(`git stash list -z --pretty='%gs'`, "", nil),
|
||||
[]*models.StashEntry{},
|
||||
},
|
||||
{
|
||||
@@ -30,8 +30,8 @@ func TestGetStashEntries(t *testing.T) {
|
||||
"",
|
||||
oscommands.NewFakeRunner(t).
|
||||
Expect(
|
||||
`git stash list --pretty='%gs'`,
|
||||
"WIP on add-pkg-commands-test: 55c6af2 increase parallel build\nWIP on master: bb86a3f update github template",
|
||||
`git stash list -z --pretty='%gs'`,
|
||||
"WIP on add-pkg-commands-test: 55c6af2 increase parallel build\x00WIP on master: bb86a3f update github template\x00",
|
||||
nil,
|
||||
),
|
||||
[]*models.StashEntry{
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
func TestStashDrop(t *testing.T) {
|
||||
runner := oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"stash", "drop", "stash@{1}"}, "", nil)
|
||||
ExpectGitArgs([]string{"stash", "drop", "stash@{1}"}, "Dropped refs/stash@{1} (98e9cca532c37c766107093010c72e26f2c24c04)\n", nil)
|
||||
instance := buildStashCommands(commonDeps{runner: runner})
|
||||
|
||||
assert.NoError(t, instance.Drop(1))
|
||||
@@ -44,6 +44,59 @@ func TestStashSave(t *testing.T) {
|
||||
runner.CheckForMissingCalls()
|
||||
}
|
||||
|
||||
func TestStashStore(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
sha string
|
||||
message string
|
||||
expected []string
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
testName: "Non-empty message",
|
||||
sha: "0123456789abcdef",
|
||||
message: "New stash name",
|
||||
expected: []string{"stash", "store", "0123456789abcdef", "-m", "New stash name"},
|
||||
},
|
||||
{
|
||||
testName: "Empty message",
|
||||
sha: "0123456789abcdef",
|
||||
message: "",
|
||||
expected: []string{"stash", "store", "0123456789abcdef"},
|
||||
},
|
||||
{
|
||||
testName: "Space message",
|
||||
sha: "0123456789abcdef",
|
||||
message: " ",
|
||||
expected: []string{"stash", "store", "0123456789abcdef"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
runner := oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs(s.expected, "", nil)
|
||||
instance := buildStashCommands(commonDeps{runner: runner})
|
||||
|
||||
assert.NoError(t, instance.Store(s.sha, s.message))
|
||||
runner.CheckForMissingCalls()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStashSha(t *testing.T) {
|
||||
runner := oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"rev-parse", "refs/stash@{5}"}, "14d94495194651adfd5f070590df566c11d28243\n", nil)
|
||||
instance := buildStashCommands(commonDeps{runner: runner})
|
||||
|
||||
sha, err := instance.Sha(5)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "14d94495194651adfd5f070590df566c11d28243", sha)
|
||||
runner.CheckForMissingCalls()
|
||||
}
|
||||
|
||||
func TestStashStashEntryCmdObj(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
@@ -79,3 +132,50 @@ func TestStashStashEntryCmdObj(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStashRename(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
index int
|
||||
message string
|
||||
expectedShaCmd []string
|
||||
shaResult string
|
||||
expectedDropCmd []string
|
||||
expectedStoreCmd []string
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
testName: "Default case",
|
||||
index: 3,
|
||||
message: "New message",
|
||||
expectedShaCmd: []string{"rev-parse", "refs/stash@{3}"},
|
||||
shaResult: "f0d0f20f2f61ffd6d6bfe0752deffa38845a3edd\n",
|
||||
expectedDropCmd: []string{"stash", "drop", "stash@{3}"},
|
||||
expectedStoreCmd: []string{"stash", "store", "f0d0f20f2f61ffd6d6bfe0752deffa38845a3edd", "-m", "New message"},
|
||||
},
|
||||
{
|
||||
testName: "Empty message",
|
||||
index: 4,
|
||||
message: "",
|
||||
expectedShaCmd: []string{"rev-parse", "refs/stash@{4}"},
|
||||
shaResult: "f0d0f20f2f61ffd6d6bfe0752deffa38845a3edd\n",
|
||||
expectedDropCmd: []string{"stash", "drop", "stash@{4}"},
|
||||
expectedStoreCmd: []string{"stash", "store", "f0d0f20f2f61ffd6d6bfe0752deffa38845a3edd"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
runner := oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs(s.expectedShaCmd, s.shaResult, nil).
|
||||
ExpectGitArgs(s.expectedDropCmd, "", nil).
|
||||
ExpectGitArgs(s.expectedStoreCmd, "", nil)
|
||||
instance := buildStashCommands(commonDeps{runner: runner})
|
||||
|
||||
err := instance.Rename(s.index, s.message)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,10 @@ package git_commands
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
gogit "github.com/jesseduffield/go-git/v5"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
|
||||
)
|
||||
|
||||
@@ -49,13 +51,21 @@ func (self *StatusCommands) WorkingTreeState() enums.RebaseMode {
|
||||
return enums.REBASE_MODE_NONE
|
||||
}
|
||||
|
||||
func (self *StatusCommands) IsBareRepo() (bool, error) {
|
||||
return IsBareRepo(self.os)
|
||||
}
|
||||
|
||||
func IsBareRepo(osCommand *oscommands.OSCommand) (bool, error) {
|
||||
res, err := osCommand.Cmd.New("git rev-parse --is-bare-repository").DontLog().RunWithOutput()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// The command returns output with a newline, so we need to strip
|
||||
return strconv.ParseBool(strings.TrimSpace(res))
|
||||
}
|
||||
|
||||
// IsInMergeState states whether we are still mid-merge
|
||||
func (self *StatusCommands) IsInMergeState() (bool, error) {
|
||||
return self.os.FileExists(filepath.Join(self.dotGitDir, "MERGE_HEAD"))
|
||||
}
|
||||
|
||||
func (self *StatusCommands) IsBareRepo() bool {
|
||||
// note: could use `git rev-parse --is-bare-repository` if we wanna drop go-git
|
||||
_, err := self.repo.Worktree()
|
||||
return err == gogit.ErrIsBareRepository
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package loaders
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
@@ -1,4 +1,4 @@
|
||||
package loaders
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/loaders"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
@@ -18,13 +17,13 @@ import (
|
||||
type WorkingTreeCommands struct {
|
||||
*GitCommon
|
||||
submodule *SubmoduleCommands
|
||||
fileLoader *loaders.FileLoader
|
||||
fileLoader *FileLoader
|
||||
}
|
||||
|
||||
func NewWorkingTreeCommands(
|
||||
gitCommon *GitCommon,
|
||||
submodule *SubmoduleCommands,
|
||||
fileLoader *loaders.FileLoader,
|
||||
fileLoader *FileLoader,
|
||||
) *WorkingTreeCommands {
|
||||
return &WorkingTreeCommands{
|
||||
GitCommon: gitCommon,
|
||||
@@ -90,7 +89,7 @@ func (self *WorkingTreeCommands) BeforeAndAfterFileForRename(file *models.File)
|
||||
// all files, passing the --no-renames flag and then recursively call the function
|
||||
// again for the before file and after file.
|
||||
|
||||
filesWithoutRenames := self.fileLoader.GetStatusFiles(loaders.GetStatusFileOptions{NoRenames: true})
|
||||
filesWithoutRenames := self.fileLoader.GetStatusFiles(GetStatusFileOptions{NoRenames: true})
|
||||
|
||||
var beforeFile *models.File
|
||||
var afterFile *models.File
|
||||
@@ -241,7 +240,7 @@ func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain
|
||||
if cached {
|
||||
cachedArg = " --cached"
|
||||
}
|
||||
if !node.GetIsTracked() && !node.GetHasStagedChanges() && !cached {
|
||||
if !node.GetIsTracked() && !node.GetHasStagedChanges() && !cached && node.GetIsFile() {
|
||||
trackedArg = "--no-index -- /dev/null"
|
||||
}
|
||||
if plain {
|
||||
|
||||
@@ -2,7 +2,7 @@ package git_commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
@@ -432,7 +432,7 @@ func TestWorkingTreeApplyPatch(t *testing.T) {
|
||||
|
||||
filename := matches[1]
|
||||
|
||||
content, err := ioutil.ReadFile(filename)
|
||||
content, err := os.ReadFile(filename)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "test", string(content))
|
||||
|
||||
@@ -3,7 +3,7 @@ package git_config
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"syscall"
|
||||
@@ -38,7 +38,7 @@ import (
|
||||
func runGitConfigCmd(cmd *exec.Cmd) (string, error) {
|
||||
var stdout bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = ioutil.Discard
|
||||
cmd.Stderr = io.Discard
|
||||
|
||||
err := cmd.Run()
|
||||
if exitError, ok := err.(*exec.ExitError); ok {
|
||||
|
||||
@@ -3,7 +3,6 @@ package commands
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -12,6 +11,7 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_config"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -220,7 +220,7 @@ func TestNewGitCommand(t *testing.T) {
|
||||
NewGitCommand(utils.NewDummyCommon(),
|
||||
oscommands.NewDummyOSCommand(),
|
||||
git_config.NewFakeGitConfig(nil),
|
||||
&sync.Mutex{},
|
||||
&deadlock.Mutex{},
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package hosting_service
|
||||
// if you want to make a custom regex for a given service feel free to test it out
|
||||
// at regoio.herokuapp.com
|
||||
var defaultUrlRegexStrings = []string{
|
||||
`^(?:https?|ssh)://.*/(?P<owner>.*)/(?P<repo>.*?)(?:\.git)?$`,
|
||||
`^(?:https?|ssh)://[^/]+/(?P<owner>.*)/(?P<repo>.*?)(?:\.git)?$`,
|
||||
`^git@.*:(?P<owner>.*)/(?P<repo>.*?)(?:\.git)?$`,
|
||||
}
|
||||
var defaultRepoURLTemplate = "https://{{.webDomain}}/{{.owner}}/{{.repo}}"
|
||||
@@ -24,8 +24,11 @@ var bitbucketServiceDef = ServiceDefinition{
|
||||
pullRequestURLIntoDefaultBranch: "/pull-requests/new?source={{.From}}&t=1",
|
||||
pullRequestURLIntoTargetBranch: "/pull-requests/new?source={{.From}}&dest={{.To}}&t=1",
|
||||
commitURL: "/commits/{{.CommitSha}}",
|
||||
regexStrings: defaultUrlRegexStrings,
|
||||
repoURLTemplate: defaultRepoURLTemplate,
|
||||
regexStrings: []string{
|
||||
`^(?:https?|ssh)://.*/(?P<owner>.*)/(?P<repo>.*?)(?:\.git)?$`,
|
||||
`^.*@.*:(?P<owner>.*)/(?P<repo>.*?)(?:\.git)?$`,
|
||||
},
|
||||
repoURLTemplate: defaultRepoURLTemplate,
|
||||
}
|
||||
|
||||
var gitLabServiceDef = ServiceDefinition{
|
||||
|
||||
@@ -47,6 +47,15 @@ func TestGetPullRequestURL(t *testing.T) {
|
||||
assert.Equal(t, "https://github.com/peter/calculator/compare/feature%2Fsum-operation?expand=1", url)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Opens a link to new pull request on github with https remote url",
|
||||
from: "feature/sum-operation",
|
||||
remoteUrl: "https://github.com/peter/calculator.git",
|
||||
test: func(url string, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "https://github.com/peter/calculator/compare/feature%2Fsum-operation?expand=1", url)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Opens a link to new pull request on bitbucket with specific target branch",
|
||||
from: "feature/profile-page/avatar",
|
||||
@@ -77,6 +86,16 @@ func TestGetPullRequestURL(t *testing.T) {
|
||||
assert.Equal(t, "https://github.com/peter/calculator/compare/feature%2Foperations...feature%2Fsum-operation?expand=1", url)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Opens a link to new pull request on github with https remote url with specific target branch",
|
||||
from: "feature/sum-operation",
|
||||
to: "feature/operations",
|
||||
remoteUrl: "https://github.com/peter/calculator.git",
|
||||
test: func(url string, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "https://github.com/peter/calculator/compare/feature%2Foperations...feature%2Fsum-operation?expand=1", url)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Opens a link to new pull request on gitlab",
|
||||
from: "feature/ui",
|
||||
@@ -95,6 +114,15 @@ func TestGetPullRequestURL(t *testing.T) {
|
||||
assert.Equal(t, "https://gitlab.com/peter/public/calculator/merge_requests/new?merge_request[source_branch]=feature%2Fui", url)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Opens a link to new pull request on gitlab with https remote url in nested groups",
|
||||
from: "feature/ui",
|
||||
remoteUrl: "https://gitlab.com/peter/public/calculator.git",
|
||||
test: func(url string, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "https://gitlab.com/peter/public/calculator/merge_requests/new?merge_request[source_branch]=feature%2Fui", url)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Opens a link to new pull request on gitlab with specific target branch",
|
||||
from: "feature/commit-ui",
|
||||
@@ -115,6 +143,25 @@ func TestGetPullRequestURL(t *testing.T) {
|
||||
assert.Equal(t, "https://gitlab.com/peter/public/calculator/merge_requests/new?merge_request[source_branch]=feature%2Fcommit-ui&merge_request[target_branch]=epic%2Fui", url)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Opens a link to new pull request on gitlab with https remote url with specific target branch in nested groups",
|
||||
from: "feature/commit-ui",
|
||||
to: "epic/ui",
|
||||
remoteUrl: "https://gitlab.com/peter/public/calculator.git",
|
||||
test: func(url string, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "https://gitlab.com/peter/public/calculator/merge_requests/new?merge_request[source_branch]=feature%2Fcommit-ui&merge_request[target_branch]=epic%2Fui", url)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Opens a link to new pull request on bitbucket with a custom SSH username",
|
||||
from: "feature/profile-page",
|
||||
remoteUrl: "john@bitbucket.org:johndoe/social_network.git",
|
||||
test: func(url string, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature%2Fprofile-page&t=1", url)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Opens a link to new pull request on Azure DevOps (SSH)",
|
||||
from: "feature/new",
|
||||
|
||||
@@ -11,6 +11,7 @@ type Branch struct {
|
||||
Pullables string
|
||||
UpstreamGone bool
|
||||
Head bool
|
||||
DetachedHead bool
|
||||
// if we have a named remote locally this will be the name of that remote e.g.
|
||||
// 'origin' or 'tiwood'. If we don't have the remote locally it'll look like
|
||||
// 'git@github.com:tiwood/lazygit.git'
|
||||
@@ -19,6 +20,9 @@ type Branch struct {
|
||||
}
|
||||
|
||||
func (b *Branch) FullRefName() string {
|
||||
if b.DetachedHead {
|
||||
return b.Name
|
||||
}
|
||||
return "refs/heads/" + b.Name
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package models
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
// File : A file from git status
|
||||
@@ -28,6 +29,7 @@ type IFile interface {
|
||||
GetIsTracked() bool
|
||||
GetPath() string
|
||||
GetPreviousPath() string
|
||||
GetIsFile() bool
|
||||
}
|
||||
|
||||
func (f *File) IsRename() bool {
|
||||
@@ -43,7 +45,7 @@ func (f *File) Names() []string {
|
||||
return result
|
||||
}
|
||||
|
||||
// returns true if the file names are the same or if a a file rename includes the filename of the other
|
||||
// returns true if the file names are the same or if a file rename includes the filename of the other
|
||||
func (f *File) Matches(f2 *File) bool {
|
||||
return utils.StringArraysOverlap(f.Names(), f2.Names())
|
||||
}
|
||||
@@ -90,3 +92,52 @@ func (f *File) GetPath() string {
|
||||
func (f *File) GetPreviousPath() string {
|
||||
return f.PreviousName
|
||||
}
|
||||
|
||||
func (f *File) GetIsFile() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type StatusFields struct {
|
||||
HasStagedChanges bool
|
||||
HasUnstagedChanges bool
|
||||
Tracked bool
|
||||
Deleted bool
|
||||
Added bool
|
||||
HasMergeConflicts bool
|
||||
HasInlineMergeConflicts bool
|
||||
ShortStatus string
|
||||
}
|
||||
|
||||
func SetStatusFields(file *File, shortStatus string) {
|
||||
derived := deriveStatusFields(shortStatus)
|
||||
|
||||
file.HasStagedChanges = derived.HasStagedChanges
|
||||
file.HasUnstagedChanges = derived.HasUnstagedChanges
|
||||
file.Tracked = derived.Tracked
|
||||
file.Deleted = derived.Deleted
|
||||
file.Added = derived.Added
|
||||
file.HasMergeConflicts = derived.HasMergeConflicts
|
||||
file.HasInlineMergeConflicts = derived.HasInlineMergeConflicts
|
||||
file.ShortStatus = derived.ShortStatus
|
||||
}
|
||||
|
||||
// shortStatus is something like '??' or 'A '
|
||||
func deriveStatusFields(shortStatus string) StatusFields {
|
||||
stagedChange := shortStatus[0:1]
|
||||
unstagedChange := shortStatus[1:2]
|
||||
tracked := !lo.Contains([]string{"??", "A ", "AM"}, shortStatus)
|
||||
hasStagedChanges := !lo.Contains([]string{" ", "U", "?"}, stagedChange)
|
||||
hasInlineMergeConflicts := lo.Contains([]string{"UU", "AA"}, shortStatus)
|
||||
hasMergeConflicts := hasInlineMergeConflicts || lo.Contains([]string{"DD", "AU", "UA", "UD", "DU"}, shortStatus)
|
||||
|
||||
return StatusFields{
|
||||
HasStagedChanges: hasStagedChanges,
|
||||
HasUnstagedChanges: unstagedChange != " ",
|
||||
Tracked: tracked,
|
||||
Deleted: unstagedChange == "D" || stagedChange == "D",
|
||||
Added: unstagedChange == "A" || !tracked,
|
||||
HasMergeConflicts: hasMergeConflicts,
|
||||
HasInlineMergeConflicts: hasInlineMergeConflicts,
|
||||
ShortStatus: shortStatus,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@ package oscommands
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"sync"
|
||||
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
)
|
||||
|
||||
// A command object is a general way to represent a command to be run on the
|
||||
@@ -21,6 +22,8 @@ type ICmdObj interface {
|
||||
Run() error
|
||||
// runs the command and returns the output as a string, and an error if any
|
||||
RunWithOutput() (string, error)
|
||||
// runs the command and returns stdout and stderr as a string, and an error if any
|
||||
RunWithOutputs() (string, string, error)
|
||||
// runs the command and runs a callback function on each line of the output. If the callback returns true for the boolean value, we kill the process and return.
|
||||
RunAndProcessLines(onLine func(line string) (bool, error)) error
|
||||
|
||||
@@ -51,8 +54,8 @@ type ICmdObj interface {
|
||||
PromptOnCredentialRequest() ICmdObj
|
||||
FailOnCredentialRequest() ICmdObj
|
||||
|
||||
WithMutex(mutex *sync.Mutex) ICmdObj
|
||||
Mutex() *sync.Mutex
|
||||
WithMutex(mutex *deadlock.Mutex) ICmdObj
|
||||
Mutex() *deadlock.Mutex
|
||||
|
||||
GetCredentialStrategy() CredentialStrategy
|
||||
}
|
||||
@@ -76,7 +79,7 @@ type CmdObj struct {
|
||||
credentialStrategy CredentialStrategy
|
||||
|
||||
// can be set so that we don't run certain commands simultaneously
|
||||
mutex *sync.Mutex
|
||||
mutex *deadlock.Mutex
|
||||
}
|
||||
|
||||
type CredentialStrategy int
|
||||
@@ -139,11 +142,11 @@ func (self *CmdObj) IgnoreEmptyError() ICmdObj {
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *CmdObj) Mutex() *sync.Mutex {
|
||||
func (self *CmdObj) Mutex() *deadlock.Mutex {
|
||||
return self.mutex
|
||||
}
|
||||
|
||||
func (self *CmdObj) WithMutex(mutex *sync.Mutex) ICmdObj {
|
||||
func (self *CmdObj) WithMutex(mutex *deadlock.Mutex) ICmdObj {
|
||||
self.mutex = mutex
|
||||
|
||||
return self
|
||||
@@ -161,6 +164,10 @@ func (self *CmdObj) RunWithOutput() (string, error) {
|
||||
return self.runner.RunWithOutput(self)
|
||||
}
|
||||
|
||||
func (self *CmdObj) RunWithOutputs() (string, string, error) {
|
||||
return self.runner.RunWithOutputs(self)
|
||||
}
|
||||
|
||||
func (self *CmdObj) RunAndProcessLines(onLine func(line string) (bool, error)) error {
|
||||
return self.runner.RunAndProcessLines(self, onLine)
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
type ICmdObjRunner interface {
|
||||
Run(cmdObj ICmdObj) error
|
||||
RunWithOutput(cmdObj ICmdObj) (string, error)
|
||||
RunWithOutputs(cmdObj ICmdObj) (string, string, error)
|
||||
RunAndProcessLines(cmdObj ICmdObj, onLine func(line string) (bool, error)) error
|
||||
}
|
||||
|
||||
@@ -76,6 +77,31 @@ func (self *cmdObjRunner) RunWithOutput(cmdObj ICmdObj) (string, error) {
|
||||
return self.RunWithOutputAux(cmdObj)
|
||||
}
|
||||
|
||||
func (self *cmdObjRunner) RunWithOutputs(cmdObj ICmdObj) (string, string, error) {
|
||||
if cmdObj.Mutex() != nil {
|
||||
cmdObj.Mutex().Lock()
|
||||
defer cmdObj.Mutex().Unlock()
|
||||
}
|
||||
|
||||
if cmdObj.GetCredentialStrategy() != NONE {
|
||||
err := self.runWithCredentialHandling(cmdObj)
|
||||
// for now we're not capturing output, just because it would take a little more
|
||||
// effort and there's currently no use case for it. Some commands call RunWithOutputs
|
||||
// but ignore the output, hence why we've got this check here.
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
if cmdObj.ShouldStreamOutput() {
|
||||
err := self.runAndStream(cmdObj)
|
||||
// for now we're not capturing output, just because it would take a little more
|
||||
// effort and there's currently no use case for it. Some commands call RunWithOutputs
|
||||
// but ignore the output, hence why we've got this check here.
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
return self.RunWithOutputsAux(cmdObj)
|
||||
}
|
||||
|
||||
func (self *cmdObjRunner) RunWithOutputAux(cmdObj ICmdObj) (string, error) {
|
||||
self.log.WithField("command", cmdObj.ToString()).Debug("RunCommand")
|
||||
|
||||
@@ -90,6 +116,28 @@ func (self *cmdObjRunner) RunWithOutputAux(cmdObj ICmdObj) (string, error) {
|
||||
return output, err
|
||||
}
|
||||
|
||||
func (self *cmdObjRunner) RunWithOutputsAux(cmdObj ICmdObj) (string, string, error) {
|
||||
self.log.WithField("command", cmdObj.ToString()).Debug("RunCommand")
|
||||
|
||||
if cmdObj.ShouldLog() {
|
||||
self.logCmdObj(cmdObj)
|
||||
}
|
||||
|
||||
var outBuffer, errBuffer bytes.Buffer
|
||||
cmd := cmdObj.GetCmd()
|
||||
cmd.Stdout = &outBuffer
|
||||
cmd.Stderr = &errBuffer
|
||||
err := cmd.Run()
|
||||
|
||||
stdout := outBuffer.String()
|
||||
stderr, err := sanitisedCommandOutput(errBuffer.Bytes(), err)
|
||||
if err != nil {
|
||||
self.log.WithField("command", cmdObj.ToString()).Error(stderr)
|
||||
}
|
||||
|
||||
return stdout, stderr, err
|
||||
}
|
||||
|
||||
func (self *cmdObjRunner) RunAndProcessLines(cmdObj ICmdObj, onLine func(line string) (bool, error)) error {
|
||||
if cmdObj.Mutex() != nil {
|
||||
cmdObj.Mutex().Lock()
|
||||
@@ -147,7 +195,7 @@ func (self *cmdObjRunner) runWithCredentialHandling(cmdObj ICmdObj) error {
|
||||
promptFn = failPromptFn
|
||||
case NONE:
|
||||
// we should never land here
|
||||
return errors.New("runWithCredentialHandling called but cmdObj does not have a a credential strategy")
|
||||
return errors.New("runWithCredentialHandling called but cmdObj does not have a credential strategy")
|
||||
}
|
||||
|
||||
return self.runAndDetectCredentialRequest(cmdObj, promptFn)
|
||||
@@ -215,7 +263,7 @@ func (self *cmdObjRunner) runAndStreamAux(
|
||||
if cmdObj.ShouldLog() {
|
||||
self.logCmdObj(cmdObj)
|
||||
}
|
||||
self.log.WithField("command", cmdObj.ToString()).Info("RunCommand")
|
||||
self.log.WithField("command", cmdObj.ToString()).Debug("RunCommand")
|
||||
cmd := cmdObj.GetCmd()
|
||||
|
||||
var stderr bytes.Buffer
|
||||
|
||||
@@ -7,12 +7,13 @@ import (
|
||||
"bytes"
|
||||
"io"
|
||||
"os/exec"
|
||||
"sync"
|
||||
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
)
|
||||
|
||||
type Buffer struct {
|
||||
b bytes.Buffer
|
||||
m sync.Mutex
|
||||
m deadlock.Mutex
|
||||
}
|
||||
|
||||
func (b *Buffer) Read(p []byte) (n int, err error) {
|
||||
|
||||
@@ -19,6 +19,7 @@ type OSCommandDeps struct {
|
||||
GetenvFn func(string) string
|
||||
RemoveFileFn func(string) error
|
||||
Cmd *CmdObjBuilder
|
||||
TempDir string
|
||||
}
|
||||
|
||||
func NewDummyOSCommandWithDeps(deps OSCommandDeps) *OSCommand {
|
||||
@@ -38,6 +39,7 @@ func NewDummyOSCommandWithDeps(deps OSCommandDeps) *OSCommand {
|
||||
getenvFn: deps.GetenvFn,
|
||||
removeFileFn: deps.RemoveFileFn,
|
||||
guiIO: NewNullGuiIO(utils.NewDummyLog()),
|
||||
tempDir: deps.TempDir,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,11 @@ func (self *FakeCmdObjRunner) RunWithOutput(cmdObj ICmdObj) (string, error) {
|
||||
return output, err
|
||||
}
|
||||
|
||||
func (self *FakeCmdObjRunner) RunWithOutputs(cmdObj ICmdObj) (string, string, error) {
|
||||
output, err := self.RunWithOutput(cmdObj)
|
||||
return output, "", err
|
||||
}
|
||||
|
||||
func (self *FakeCmdObjRunner) RunAndProcessLines(cmdObj ICmdObj, onLine func(line string) (bool, error)) error {
|
||||
output, err := self.RunWithOutput(cmdObj)
|
||||
if err != nil {
|
||||
|
||||
@@ -2,7 +2,6 @@ package oscommands
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
@@ -45,7 +44,7 @@ func NewNullGuiIO(log *logrus.Entry) *guiIO {
|
||||
return &guiIO{
|
||||
log: log,
|
||||
logCommandFn: func(string, bool) {},
|
||||
newCmdWriterFn: func() io.Writer { return ioutil.Discard },
|
||||
newCmdWriterFn: func() io.Writer { return io.Discard },
|
||||
promptForCredentialFn: failPromptFn,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package oscommands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
@@ -109,13 +109,34 @@ func (c *OSCommand) Quote(message string) string {
|
||||
// AppendLineToFile adds a new line in file
|
||||
func (c *OSCommand) AppendLineToFile(filename, line string) error {
|
||||
c.LogCommand(fmt.Sprintf("Appending '%s' to file '%s'", line, filename), false)
|
||||
f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0o600)
|
||||
f, err := os.OpenFile(filename, os.O_APPEND|os.O_RDWR|os.O_CREATE, 0o600)
|
||||
if err != nil {
|
||||
return utils.WrapError(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = f.WriteString("\n" + line)
|
||||
info, err := os.Stat(filename)
|
||||
if err != nil {
|
||||
return utils.WrapError(err)
|
||||
}
|
||||
|
||||
if info.Size() > 0 {
|
||||
// read last char
|
||||
buf := make([]byte, 1)
|
||||
if _, err := f.ReadAt(buf, info.Size()-1); err != nil {
|
||||
return utils.WrapError(err)
|
||||
}
|
||||
|
||||
// if the last byte of the file is not a newline, add it
|
||||
if []byte("\n")[0] != buf[0] {
|
||||
_, err = f.WriteString("\n")
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
_, err = f.WriteString(line + "\n")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return utils.WrapError(err)
|
||||
}
|
||||
@@ -130,7 +151,7 @@ func (c *OSCommand) CreateFileWithContent(path string, content string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(path, []byte(content), 0o644); err != nil {
|
||||
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
|
||||
c.Log.Error(err)
|
||||
return utils.WrapError(err)
|
||||
}
|
||||
@@ -194,7 +215,7 @@ func (c *OSCommand) PipeCommands(commandStrings ...string) error {
|
||||
c.Log.Error(err)
|
||||
}
|
||||
|
||||
if b, err := ioutil.ReadAll(stderr); err == nil {
|
||||
if b, err := io.ReadAll(stderr); err == nil {
|
||||
if len(b) > 0 {
|
||||
finalErrors = append(finalErrors, string(b))
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package oscommands
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -135,3 +136,61 @@ func TestOSCommandFileType(t *testing.T) {
|
||||
_ = os.RemoveAll(s.path)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOSCommandAppendLineToFile(t *testing.T) {
|
||||
type scenario struct {
|
||||
path string
|
||||
setup func(string)
|
||||
test func(string)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
filepath.Join(os.TempDir(), "testFile"),
|
||||
func(path string) {
|
||||
if err := os.WriteFile(path, []byte("hello"), 0o600); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
},
|
||||
func(output string) {
|
||||
assert.EqualValues(t, "hello\nworld\n", output)
|
||||
},
|
||||
},
|
||||
{
|
||||
filepath.Join(os.TempDir(), "emptyTestFile"),
|
||||
func(path string) {
|
||||
if err := os.WriteFile(path, []byte(""), 0o600); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
},
|
||||
func(output string) {
|
||||
assert.EqualValues(t, "world\n", output)
|
||||
},
|
||||
},
|
||||
{
|
||||
filepath.Join(os.TempDir(), "testFileWithNewline"),
|
||||
func(path string) {
|
||||
if err := os.WriteFile(path, []byte("hello\n"), 0o600); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
},
|
||||
func(output string) {
|
||||
assert.EqualValues(t, "hello\nworld\n", output)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s.setup(s.path)
|
||||
osCommand := NewDummyOSCommand()
|
||||
if err := osCommand.AppendLineToFile(s.path, "world"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
f, err := os.ReadFile(s.path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
s.test(string(f))
|
||||
_ = os.RemoveAll(s.path)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,7 +190,7 @@ func (p *PatchManager) RenderPatchForFile(filename string, plain bool, reverse b
|
||||
parser := NewPatchParser(p.Log, patch)
|
||||
|
||||
// not passing included lines because we don't want to see them in the secondary panel
|
||||
return parser.Render(-1, -1, nil)
|
||||
return parser.Render(false, -1, -1, nil)
|
||||
}
|
||||
|
||||
func (p *PatchManager) renderEachFilePatch(plain bool) []string {
|
||||
|
||||
@@ -183,7 +183,7 @@ func parsePatch(patch string) ([]int, []int, []*PatchLine) {
|
||||
}
|
||||
|
||||
// Render returns the coloured string of the diff with any selected lines highlighted
|
||||
func (p *PatchParser) Render(firstLineIndex int, lastLineIndex int, incLineIndices []int) string {
|
||||
func (p *PatchParser) Render(isFocused bool, firstLineIndex int, lastLineIndex int, incLineIndices []int) string {
|
||||
contentToDisplay := slices.Some(p.PatchLines, func(line *PatchLine) bool {
|
||||
return line.Content != ""
|
||||
})
|
||||
@@ -192,7 +192,7 @@ func (p *PatchParser) Render(firstLineIndex int, lastLineIndex int, incLineIndic
|
||||
}
|
||||
|
||||
renderedLines := slices.MapWithIndex(p.PatchLines, func(patchLine *PatchLine, index int) string {
|
||||
selected := index >= firstLineIndex && index <= lastLineIndex
|
||||
selected := isFocused && index >= firstLineIndex && index <= lastLineIndex
|
||||
included := lo.Contains(incLineIndices, index)
|
||||
return patchLine.render(selected, included)
|
||||
})
|
||||
@@ -202,12 +202,18 @@ func (p *PatchParser) Render(firstLineIndex int, lastLineIndex int, incLineIndic
|
||||
return result
|
||||
}
|
||||
|
||||
// PlainRenderLines returns the non-coloured string of diff part from firstLineIndex to
|
||||
// lastLineIndex
|
||||
func (p *PatchParser) PlainRenderLines(firstLineIndex, lastLineIndex int) string {
|
||||
linesToCopy := p.PatchLines[firstLineIndex : lastLineIndex+1]
|
||||
func (p *PatchParser) RenderPlain() string {
|
||||
return renderLinesPlain(p.PatchLines)
|
||||
}
|
||||
|
||||
renderedLines := slices.Map(linesToCopy, func(line *PatchLine) string {
|
||||
// RenderLinesPlain returns the non-coloured string of diff part from firstLineIndex to
|
||||
// lastLineIndex
|
||||
func (p *PatchParser) RenderLinesPlain(firstLineIndex, lastLineIndex int) string {
|
||||
return renderLinesPlain(p.PatchLines[firstLineIndex : lastLineIndex+1])
|
||||
}
|
||||
|
||||
func renderLinesPlain(lines []*PatchLine) string {
|
||||
renderedLines := slices.Map(lines, func(line *PatchLine) string {
|
||||
return line.Content
|
||||
})
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -15,7 +14,6 @@ import (
|
||||
type AppConfig struct {
|
||||
Debug bool `long:"debug" env:"DEBUG" default:"false"`
|
||||
Version string `long:"version" env:"VERSION" default:"unversioned"`
|
||||
Commit string `long:"commit" env:"COMMIT"`
|
||||
BuildDate string `long:"build-date" env:"BUILD_DATE"`
|
||||
Name string `long:"name" env:"NAME" default:"lazygit"`
|
||||
BuildSource string `long:"build-source" env:"BUILD_SOURCE" default:""`
|
||||
@@ -28,15 +26,11 @@ type AppConfig struct {
|
||||
IsNewRepo bool
|
||||
}
|
||||
|
||||
// AppConfigurer interface allows individual app config structs to inherit Fields
|
||||
// from AppConfig and still be used by lazygit.
|
||||
type AppConfigurer interface {
|
||||
GetDebug() bool
|
||||
|
||||
// build info
|
||||
GetVersion() string
|
||||
GetCommit() string
|
||||
GetBuildDate() string
|
||||
GetName() string
|
||||
GetBuildSource() string
|
||||
|
||||
@@ -80,10 +74,6 @@ func NewAppConfig(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if os.Getenv("DEBUG") == "TRUE" {
|
||||
debuggingFlag = true
|
||||
}
|
||||
|
||||
appState, err := loadAppState()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -92,7 +82,6 @@ func NewAppConfig(
|
||||
appConfig := &AppConfig{
|
||||
Name: name,
|
||||
Version: version,
|
||||
Commit: commit,
|
||||
BuildDate: date,
|
||||
Debug: debuggingFlag,
|
||||
BuildSource: buildSource,
|
||||
@@ -162,7 +151,7 @@ func loadUserConfig(configFiles []string, base *UserConfig) (*UserConfig, error)
|
||||
file.Close()
|
||||
}
|
||||
|
||||
content, err := ioutil.ReadFile(path)
|
||||
content, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -183,14 +172,6 @@ func (c *AppConfig) GetVersion() string {
|
||||
return c.Version
|
||||
}
|
||||
|
||||
func (c *AppConfig) GetCommit() string {
|
||||
return c.Commit
|
||||
}
|
||||
|
||||
func (c *AppConfig) GetBuildDate() string {
|
||||
return c.BuildDate
|
||||
}
|
||||
|
||||
func (c *AppConfig) GetName() string {
|
||||
return c.Name
|
||||
}
|
||||
@@ -261,7 +242,7 @@ func (c *AppConfig) SaveAppState() error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(filepath, marshalledAppState, 0o644)
|
||||
err = os.WriteFile(filepath, marshalledAppState, 0o644)
|
||||
if err != nil && os.IsPermission(err) {
|
||||
// apparently when people have read-only permissions they prefer us to fail silently
|
||||
return nil
|
||||
@@ -281,7 +262,7 @@ func loadAppState() (*AppState, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
appStateBytes, err := ioutil.ReadFile(filepath)
|
||||
appStateBytes, err := os.ReadFile(filepath)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -7,14 +7,11 @@ import (
|
||||
// NewDummyAppConfig creates a new dummy AppConfig for testing
|
||||
func NewDummyAppConfig() *AppConfig {
|
||||
appConfig := &AppConfig{
|
||||
Name: "lazygit",
|
||||
Version: "unversioned",
|
||||
Commit: "",
|
||||
BuildDate: "",
|
||||
Debug: false,
|
||||
BuildSource: "",
|
||||
UserConfig: GetDefaultConfig(),
|
||||
AppState: &AppState{},
|
||||
Name: "lazygit",
|
||||
Version: "unversioned",
|
||||
Debug: false,
|
||||
UserConfig: GetDefaultConfig(),
|
||||
AppState: &AppState{},
|
||||
}
|
||||
_ = yaml.Unmarshal([]byte{}, appConfig.AppState)
|
||||
return appConfig
|
||||
|
||||
@@ -55,7 +55,6 @@ type GuiConfig struct {
|
||||
}
|
||||
|
||||
type ThemeConfig struct {
|
||||
LightTheme bool `yaml:"lightTheme"`
|
||||
ActiveBorderColor []string `yaml:"activeBorderColor"`
|
||||
InactiveBorderColor []string `yaml:"inactiveBorderColor"`
|
||||
OptionsTextColor []string `yaml:"optionsTextColor"`
|
||||
@@ -265,7 +264,8 @@ type KeybindingCommitsConfig struct {
|
||||
}
|
||||
|
||||
type KeybindingStashConfig struct {
|
||||
PopStash string `yaml:"popStash"`
|
||||
PopStash string `yaml:"popStash"`
|
||||
RenameStash string `yaml:"renameStash"`
|
||||
}
|
||||
|
||||
type KeybindingCommitFilesConfig struct {
|
||||
@@ -310,10 +310,15 @@ type CustomCommand struct {
|
||||
LoadingText string `yaml:"loadingText"`
|
||||
Description string `yaml:"description"`
|
||||
Stream bool `yaml:"stream"`
|
||||
ShowOutput bool `yaml:"showOutput"`
|
||||
}
|
||||
|
||||
type CustomCommandPrompt struct {
|
||||
Type string `yaml:"type"` // one of 'input', 'menu', or 'confirm'
|
||||
Key string `yaml:"key"`
|
||||
|
||||
// one of 'input', 'menu', 'confirm', or 'menuFromCommand'
|
||||
Type string `yaml:"type"`
|
||||
|
||||
Title string `yaml:"title"`
|
||||
|
||||
// this only apply to input prompts
|
||||
@@ -352,9 +357,8 @@ func GetDefaultConfig() *UserConfig {
|
||||
Language: "auto",
|
||||
TimeFormat: time.RFC822,
|
||||
Theme: ThemeConfig{
|
||||
LightTheme: false,
|
||||
ActiveBorderColor: []string{"green", "bold"},
|
||||
InactiveBorderColor: []string{"white"},
|
||||
InactiveBorderColor: []string{"default"},
|
||||
OptionsTextColor: []string{"blue"},
|
||||
SelectedLineBgColor: []string{"blue"},
|
||||
SelectedRangeBgColor: []string{"blue"},
|
||||
@@ -542,7 +546,8 @@ func GetDefaultConfig() *UserConfig {
|
||||
ViewBisectOptions: "b",
|
||||
},
|
||||
Stash: KeybindingStashConfig{
|
||||
PopStash: "g",
|
||||
PopStash: "g",
|
||||
RenameStash: "r",
|
||||
},
|
||||
CommitFiles: KeybindingCommitFilesConfig{
|
||||
CheckoutCommitFile: "c",
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
)
|
||||
|
||||
// statusManager's job is to handle rendering of loading states and toast notifications
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
type statusManager struct {
|
||||
statuses []appStatus
|
||||
nextId int
|
||||
mutex sync.Mutex
|
||||
mutex deadlock.Mutex
|
||||
}
|
||||
|
||||
type appStatus struct {
|
||||
@@ -82,6 +82,10 @@ func (m *statusManager) getStatusString() string {
|
||||
return topStatus.message
|
||||
}
|
||||
|
||||
func (m *statusManager) showStatus() bool {
|
||||
return len(m.statuses) > 0
|
||||
}
|
||||
|
||||
func (gui *Gui) toast(message string) {
|
||||
gui.statusManager.addToastStatus(message)
|
||||
|
||||
@@ -94,7 +98,7 @@ func (gui *Gui) renderAppStatus() {
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
appStatus := gui.statusManager.getStatusString()
|
||||
gui.OnUIThread(func() error {
|
||||
gui.c.OnUIThread(func() error {
|
||||
return gui.renderString(gui.Views.AppStatus, appStatus)
|
||||
})
|
||||
|
||||
@@ -117,7 +121,7 @@ func (gui *Gui) withWaitingStatus(message string, f func() error) error {
|
||||
gui.renderAppStatus()
|
||||
|
||||
if err := f(); err != nil {
|
||||
gui.OnUIThread(func() error {
|
||||
gui.c.OnUIThread(func() error {
|
||||
return gui.c.Error(err)
|
||||
})
|
||||
}
|
||||
|
||||
+21
-32
@@ -1,7 +1,7 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/boxlayout"
|
||||
"github.com/jesseduffield/lazycore/pkg/boxlayout"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
@@ -31,7 +31,7 @@ func (gui *Gui) getWindowDimensions(informationStr string, appStatus string) map
|
||||
|
||||
extrasWindowSize := gui.getExtrasWindowSize(height)
|
||||
|
||||
showInfoSection := gui.c.UserConfig.Gui.ShowBottomLine || (gui.State.Searching.isSearching || gui.isAnyModeActive())
|
||||
showInfoSection := gui.c.UserConfig.Gui.ShowBottomLine || gui.State.Searching.isSearching || gui.isAnyModeActive() || gui.statusManager.showStatus()
|
||||
infoSectionSize := 0
|
||||
if showInfoSection {
|
||||
infoSectionSize = 1
|
||||
@@ -105,20 +105,13 @@ func (gui *Gui) mainSectionChildren() []*boxlayout.Box {
|
||||
}
|
||||
}
|
||||
|
||||
main := "main"
|
||||
secondary := "secondary"
|
||||
if gui.secondaryViewFocused() {
|
||||
// when you think you've focused the secondary view, we've actually just swapped them around in the layout
|
||||
main, secondary = secondary, main
|
||||
}
|
||||
|
||||
return []*boxlayout.Box{
|
||||
{
|
||||
Window: main,
|
||||
Window: "main",
|
||||
Weight: 1,
|
||||
},
|
||||
{
|
||||
Window: secondary,
|
||||
Window: "secondary",
|
||||
Weight: 1,
|
||||
},
|
||||
}
|
||||
@@ -166,30 +159,26 @@ func (gui *Gui) infoSectionChildren(informationStr string, appStatus string) []*
|
||||
}
|
||||
}
|
||||
|
||||
result := []*boxlayout.Box{}
|
||||
appStatusBox := &boxlayout.Box{Window: "appStatus"}
|
||||
optionsBox := &boxlayout.Box{Window: "options"}
|
||||
|
||||
if len(appStatus) > 0 {
|
||||
result = append(result,
|
||||
&boxlayout.Box{
|
||||
Window: "appStatus",
|
||||
Size: runewidth.StringWidth(appStatus) + runewidth.StringWidth(INFO_SECTION_PADDING),
|
||||
},
|
||||
)
|
||||
if !gui.c.UserConfig.Gui.ShowBottomLine {
|
||||
optionsBox.Weight = 0
|
||||
appStatusBox.Weight = 1
|
||||
} else {
|
||||
optionsBox.Weight = 1
|
||||
appStatusBox.Size = runewidth.StringWidth(INFO_SECTION_PADDING) + runewidth.StringWidth(appStatus)
|
||||
}
|
||||
|
||||
result = append(result,
|
||||
[]*boxlayout.Box{
|
||||
{
|
||||
Window: "options",
|
||||
Weight: 1,
|
||||
},
|
||||
{
|
||||
Window: "information",
|
||||
// unlike appStatus, informationStr has various colors so we need to decolorise before taking the length
|
||||
Size: runewidth.StringWidth(INFO_SECTION_PADDING) + runewidth.StringWidth(utils.Decolorise(informationStr)),
|
||||
},
|
||||
}...,
|
||||
)
|
||||
result := []*boxlayout.Box{appStatusBox, optionsBox}
|
||||
|
||||
if gui.c.UserConfig.Gui.ShowBottomLine || gui.isAnyModeActive() {
|
||||
result = append(result, &boxlayout.Box{
|
||||
Window: "information",
|
||||
// unlike appStatus, informationStr has various colors so we need to decolorise before taking the length
|
||||
Size: runewidth.StringWidth(INFO_SECTION_PADDING) + runewidth.StringWidth(utils.Decolorise(informationStr)),
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -1,380 +0,0 @@
|
||||
package boxlayout
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestArrangeWindows(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
root *Box
|
||||
x0 int
|
||||
y0 int
|
||||
width int
|
||||
height int
|
||||
test func(result map[string]Dimensions)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
testName: "Empty box",
|
||||
root: &Box{},
|
||||
x0: 0,
|
||||
y0: 0,
|
||||
width: 10,
|
||||
height: 10,
|
||||
test: func(result map[string]Dimensions) {
|
||||
assert.EqualValues(t, result, map[string]Dimensions{})
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Box with static and dynamic panel",
|
||||
root: &Box{Children: []*Box{{Size: 1, Window: "static"}, {Weight: 1, Window: "dynamic"}}},
|
||||
x0: 0,
|
||||
y0: 0,
|
||||
width: 10,
|
||||
height: 10,
|
||||
test: func(result map[string]Dimensions) {
|
||||
assert.EqualValues(
|
||||
t,
|
||||
result,
|
||||
map[string]Dimensions{
|
||||
"dynamic": {X0: 0, X1: 9, Y0: 1, Y1: 9},
|
||||
"static": {X0: 0, X1: 9, Y0: 0, Y1: 0},
|
||||
},
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Box with static and two dynamic panels",
|
||||
root: &Box{Children: []*Box{{Size: 1, Window: "static"}, {Weight: 1, Window: "dynamic1"}, {Weight: 2, Window: "dynamic2"}}},
|
||||
x0: 0,
|
||||
y0: 0,
|
||||
width: 10,
|
||||
height: 10,
|
||||
test: func(result map[string]Dimensions) {
|
||||
assert.EqualValues(
|
||||
t,
|
||||
result,
|
||||
map[string]Dimensions{
|
||||
"static": {X0: 0, X1: 9, Y0: 0, Y1: 0},
|
||||
"dynamic1": {X0: 0, X1: 9, Y0: 1, Y1: 3},
|
||||
"dynamic2": {X0: 0, X1: 9, Y0: 4, Y1: 9},
|
||||
},
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Box with COLUMN direction",
|
||||
root: &Box{Direction: COLUMN, Children: []*Box{{Size: 1, Window: "static"}, {Weight: 1, Window: "dynamic1"}, {Weight: 2, Window: "dynamic2"}}},
|
||||
x0: 0,
|
||||
y0: 0,
|
||||
width: 10,
|
||||
height: 10,
|
||||
test: func(result map[string]Dimensions) {
|
||||
assert.EqualValues(
|
||||
t,
|
||||
result,
|
||||
map[string]Dimensions{
|
||||
"static": {X0: 0, X1: 0, Y0: 0, Y1: 9},
|
||||
"dynamic1": {X0: 1, X1: 3, Y0: 0, Y1: 9},
|
||||
"dynamic2": {X0: 4, X1: 9, Y0: 0, Y1: 9},
|
||||
},
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Box with COLUMN direction only on wide boxes with narrow box",
|
||||
root: &Box{ConditionalDirection: func(width int, height int) Direction {
|
||||
if width > 4 {
|
||||
return COLUMN
|
||||
} else {
|
||||
return ROW
|
||||
}
|
||||
}, Children: []*Box{{Weight: 1, Window: "dynamic1"}, {Weight: 1, Window: "dynamic2"}}},
|
||||
x0: 0,
|
||||
y0: 0,
|
||||
width: 4,
|
||||
height: 4,
|
||||
test: func(result map[string]Dimensions) {
|
||||
assert.EqualValues(
|
||||
t,
|
||||
result,
|
||||
map[string]Dimensions{
|
||||
"dynamic1": {X0: 0, X1: 3, Y0: 0, Y1: 1},
|
||||
"dynamic2": {X0: 0, X1: 3, Y0: 2, Y1: 3},
|
||||
},
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Box with COLUMN direction only on wide boxes with wide box",
|
||||
root: &Box{ConditionalDirection: func(width int, height int) Direction {
|
||||
if width > 4 {
|
||||
return COLUMN
|
||||
} else {
|
||||
return ROW
|
||||
}
|
||||
}, Children: []*Box{{Weight: 1, Window: "dynamic1"}, {Weight: 1, Window: "dynamic2"}}},
|
||||
// 5 / 2 = 2 remainder 1. That remainder goes to the first box.
|
||||
x0: 0,
|
||||
y0: 0,
|
||||
width: 5,
|
||||
height: 5,
|
||||
test: func(result map[string]Dimensions) {
|
||||
assert.EqualValues(
|
||||
t,
|
||||
result,
|
||||
map[string]Dimensions{
|
||||
"dynamic1": {X0: 0, X1: 2, Y0: 0, Y1: 4},
|
||||
"dynamic2": {X0: 3, X1: 4, Y0: 0, Y1: 4},
|
||||
},
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Box with conditional children where box is wide",
|
||||
root: &Box{ConditionalChildren: func(width int, height int) []*Box {
|
||||
if width > 4 {
|
||||
return []*Box{{Window: "wide", Weight: 1}}
|
||||
} else {
|
||||
return []*Box{{Window: "narrow", Weight: 1}}
|
||||
}
|
||||
}},
|
||||
x0: 0,
|
||||
y0: 0,
|
||||
width: 5,
|
||||
height: 5,
|
||||
test: func(result map[string]Dimensions) {
|
||||
assert.EqualValues(
|
||||
t,
|
||||
result,
|
||||
map[string]Dimensions{
|
||||
"wide": {X0: 0, X1: 4, Y0: 0, Y1: 4},
|
||||
},
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Box with conditional children where box is narrow",
|
||||
root: &Box{ConditionalChildren: func(width int, height int) []*Box {
|
||||
if width > 4 {
|
||||
return []*Box{{Window: "wide", Weight: 1}}
|
||||
} else {
|
||||
return []*Box{{Window: "narrow", Weight: 1}}
|
||||
}
|
||||
}},
|
||||
x0: 0,
|
||||
y0: 0,
|
||||
width: 4,
|
||||
height: 4,
|
||||
test: func(result map[string]Dimensions) {
|
||||
assert.EqualValues(
|
||||
t,
|
||||
result,
|
||||
map[string]Dimensions{
|
||||
"narrow": {X0: 0, X1: 3, Y0: 0, Y1: 3},
|
||||
},
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Box with static child with size too large",
|
||||
root: &Box{Direction: COLUMN, Children: []*Box{{Size: 11, Window: "static"}, {Weight: 1, Window: "dynamic1"}, {Weight: 2, Window: "dynamic2"}}},
|
||||
x0: 0,
|
||||
y0: 0,
|
||||
width: 10,
|
||||
height: 10,
|
||||
test: func(result map[string]Dimensions) {
|
||||
assert.EqualValues(
|
||||
t,
|
||||
result,
|
||||
map[string]Dimensions{
|
||||
"static": {X0: 0, X1: 9, Y0: 0, Y1: 9},
|
||||
// not sure if X0: 10, X1: 9 makes any sense, but testing this in the
|
||||
// actual GUI it seems harmless
|
||||
"dynamic1": {X0: 10, X1: 9, Y0: 0, Y1: 9},
|
||||
"dynamic2": {X0: 10, X1: 9, Y0: 0, Y1: 9},
|
||||
},
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
// 10 total space minus 2 from the status box leaves us with 8.
|
||||
// Total weight is 3, 8 / 3 = 2 with 2 remainder.
|
||||
// We want to end up with 2, 3, 5 (one unit from remainder to each dynamic box)
|
||||
testName: "Distributing remainder across weighted boxes",
|
||||
root: &Box{Direction: COLUMN, Children: []*Box{{Size: 2, Window: "static"}, {Weight: 1, Window: "dynamic1"}, {Weight: 2, Window: "dynamic2"}}},
|
||||
x0: 0,
|
||||
y0: 0,
|
||||
width: 10,
|
||||
height: 10,
|
||||
test: func(result map[string]Dimensions) {
|
||||
assert.EqualValues(
|
||||
t,
|
||||
result,
|
||||
map[string]Dimensions{
|
||||
"static": {X0: 0, X1: 1, Y0: 0, Y1: 9}, // 2
|
||||
"dynamic1": {X0: 2, X1: 4, Y0: 0, Y1: 9}, // 3
|
||||
"dynamic2": {X0: 5, X1: 9, Y0: 0, Y1: 9}, // 5
|
||||
},
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
// 9 total space.
|
||||
// total weight is 5, 9 / 5 = 1 with 4 remainder
|
||||
// we want to give 2 of that remainder to the first, 1 to the second, and 1 to the last.
|
||||
// Reason being that we just give units to each box evenly and consider weight in subsequent passes.
|
||||
testName: "Distributing remainder across weighted boxes 2",
|
||||
root: &Box{Direction: COLUMN, Children: []*Box{{Weight: 2, Window: "dynamic1"}, {Weight: 2, Window: "dynamic2"}, {Weight: 1, Window: "dynamic3"}}},
|
||||
x0: 0,
|
||||
y0: 0,
|
||||
width: 9,
|
||||
height: 10,
|
||||
test: func(result map[string]Dimensions) {
|
||||
assert.EqualValues(
|
||||
t,
|
||||
result,
|
||||
map[string]Dimensions{
|
||||
"dynamic1": {X0: 0, X1: 3, Y0: 0, Y1: 9}, // 4
|
||||
"dynamic2": {X0: 4, X1: 6, Y0: 0, Y1: 9}, // 3
|
||||
"dynamic3": {X0: 7, X1: 8, Y0: 0, Y1: 9}, // 2
|
||||
},
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
// 9 total space.
|
||||
// total weight is 5, 9 / 5 = 1 with 4 remainder
|
||||
// we want to give 2 of that remainder to the first, 1 to the second, and 1 to the last.
|
||||
// Reason being that we just give units to each box evenly and consider weight in subsequent passes.
|
||||
testName: "Distributing remainder across weighted boxes with unnormalized weights",
|
||||
root: &Box{Direction: COLUMN, Children: []*Box{{Weight: 4, Window: "dynamic1"}, {Weight: 4, Window: "dynamic2"}, {Weight: 2, Window: "dynamic3"}}},
|
||||
x0: 0,
|
||||
y0: 0,
|
||||
width: 9,
|
||||
height: 10,
|
||||
test: func(result map[string]Dimensions) {
|
||||
assert.EqualValues(
|
||||
t,
|
||||
result,
|
||||
map[string]Dimensions{
|
||||
"dynamic1": {X0: 0, X1: 3, Y0: 0, Y1: 9}, // 4
|
||||
"dynamic2": {X0: 4, X1: 6, Y0: 0, Y1: 9}, // 3
|
||||
"dynamic3": {X0: 7, X1: 8, Y0: 0, Y1: 9}, // 2
|
||||
},
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Another distribution test",
|
||||
root: &Box{Direction: COLUMN, Children: []*Box{
|
||||
{Weight: 3, Window: "dynamic1"},
|
||||
{Weight: 1, Window: "dynamic2"},
|
||||
{Weight: 1, Window: "dynamic3"},
|
||||
}},
|
||||
x0: 0,
|
||||
y0: 0,
|
||||
width: 9,
|
||||
height: 10,
|
||||
test: func(result map[string]Dimensions) {
|
||||
assert.EqualValues(
|
||||
t,
|
||||
result,
|
||||
map[string]Dimensions{
|
||||
"dynamic1": {X0: 0, X1: 4, Y0: 0, Y1: 9}, // 5
|
||||
"dynamic2": {X0: 5, X1: 6, Y0: 0, Y1: 9}, // 2
|
||||
"dynamic3": {X0: 7, X1: 8, Y0: 0, Y1: 9}, // 2
|
||||
},
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Box with zero weight",
|
||||
root: &Box{Direction: COLUMN, Children: []*Box{
|
||||
{Weight: 1, Window: "dynamic1"},
|
||||
{Weight: 0, Window: "dynamic2"},
|
||||
}},
|
||||
x0: 0,
|
||||
y0: 0,
|
||||
width: 10,
|
||||
height: 10,
|
||||
test: func(result map[string]Dimensions) {
|
||||
assert.EqualValues(
|
||||
t,
|
||||
result,
|
||||
map[string]Dimensions{
|
||||
"dynamic1": {X0: 0, X1: 9, Y0: 0, Y1: 9},
|
||||
"dynamic2": {X0: 10, X1: 9, Y0: 0, Y1: 9}, // when X0 > X1, we will hide the window
|
||||
},
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
s.test(ArrangeWindows(s.root, s.x0, s.y0, s.width, s.height))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeWeights(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
testName string
|
||||
input []int
|
||||
expected []int
|
||||
}{
|
||||
{
|
||||
testName: "empty",
|
||||
input: []int{},
|
||||
expected: []int{},
|
||||
},
|
||||
{
|
||||
testName: "one item of value 1",
|
||||
input: []int{1},
|
||||
expected: []int{1},
|
||||
},
|
||||
{
|
||||
testName: "one item of value greater than 1",
|
||||
input: []int{2},
|
||||
expected: []int{1},
|
||||
},
|
||||
{
|
||||
testName: "slice contains 1",
|
||||
input: []int{2, 1},
|
||||
expected: []int{2, 1},
|
||||
},
|
||||
{
|
||||
testName: "slice contains 2 and 2",
|
||||
input: []int{2, 2},
|
||||
expected: []int{1, 1},
|
||||
},
|
||||
{
|
||||
testName: "no common multiple",
|
||||
input: []int{2, 3},
|
||||
expected: []int{2, 3},
|
||||
},
|
||||
{
|
||||
testName: "complex case",
|
||||
input: []int{10, 10, 20},
|
||||
expected: []int{1, 1, 2},
|
||||
},
|
||||
{
|
||||
testName: "when a zero weight is included it is ignored",
|
||||
input: []int{10, 10, 20, 0},
|
||||
expected: []int{1, 1, 2, 0},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
assert.EqualValues(t, s.expected, normalizeWeights(s.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,23 @@
|
||||
package gui
|
||||
|
||||
import "github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
|
||||
func (gui *Gui) branchesRenderToMain() error {
|
||||
var task updateTask
|
||||
var task types.UpdateTask
|
||||
branch := gui.State.Contexts.Branches.GetSelected()
|
||||
if branch == nil {
|
||||
task = NewRenderStringTask(gui.c.Tr.NoBranchesThisRepo)
|
||||
task = types.NewRenderStringTask(gui.c.Tr.NoBranchesThisRepo)
|
||||
} else {
|
||||
cmdObj := gui.git.Branch.GetGraphCmdObj(branch.FullRefName())
|
||||
|
||||
task = NewRunPtyTask(cmdObj.GetCmd())
|
||||
task = types.NewRunPtyTask(cmdObj.GetCmd())
|
||||
}
|
||||
|
||||
return gui.refreshMainViews(refreshMainOpts{
|
||||
main: &viewUpdateOpts{
|
||||
title: gui.c.Tr.LogTitle,
|
||||
task: task,
|
||||
return gui.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: gui.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: gui.c.Tr.LogTitle,
|
||||
Task: task,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -7,15 +7,16 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/constants"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
)
|
||||
|
||||
// our UI command log looks like this:
|
||||
// Stage File
|
||||
// git add -- 'filename'
|
||||
// Unstage File
|
||||
// git reset HEAD 'filename'
|
||||
// Stage File:
|
||||
// git add -- 'filename'
|
||||
// Unstage File:
|
||||
// git reset HEAD 'filename'
|
||||
//
|
||||
// The 'Stage File' and 'Unstage File' lines are actions i.e they group up a set
|
||||
// of command logs (typically there's only one command under an action but there may be more).
|
||||
@@ -53,7 +54,7 @@ func (gui *Gui) LogCommand(cmdStr string, commandLine bool) {
|
||||
func (gui *Gui) printCommandLogHeader() {
|
||||
introStr := fmt.Sprintf(
|
||||
gui.c.Tr.CommandLogHeader,
|
||||
gui.getKeyDisplay(gui.c.UserConfig.Keybinding.Universal.ExtrasMenu),
|
||||
keybindings.Label(gui.c.UserConfig.Keybinding.Universal.ExtrasMenu),
|
||||
)
|
||||
fmt.Fprintln(gui.Views.Extras, style.FgCyan.Sprint(introStr))
|
||||
|
||||
@@ -71,7 +72,7 @@ func (gui *Gui) getRandomTip() string {
|
||||
config := gui.c.UserConfig.Keybinding
|
||||
|
||||
formattedKey := func(key string) string {
|
||||
return gui.getKeyDisplay(key)
|
||||
return keybindings.Label(key)
|
||||
}
|
||||
|
||||
tips := []string{
|
||||
|
||||
@@ -2,14 +2,9 @@ package gui
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/controllers"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
// TODO: do we need this?
|
||||
func (gui *Gui) onCommitFileFocus() error {
|
||||
gui.escapeLineByLinePanel()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) commitFilesRenderToMain() error {
|
||||
node := gui.State.Contexts.CommitFiles.GetSelected()
|
||||
if node == nil {
|
||||
@@ -21,20 +16,20 @@ func (gui *Gui) commitFilesRenderToMain() error {
|
||||
from, reverse := gui.State.Modes.Diffing.GetFromAndReverseArgsForDiff(ref.ParentRefName())
|
||||
|
||||
cmdObj := gui.git.WorkingTree.ShowFileDiffCmdObj(from, to, reverse, node.GetPath(), false)
|
||||
task := NewRunPtyTask(cmdObj.GetCmd())
|
||||
task := types.NewRunPtyTask(cmdObj.GetCmd())
|
||||
|
||||
mainContext := gui.State.Contexts.Normal
|
||||
pair := gui.c.MainViewPairs().Normal
|
||||
if node.File != nil {
|
||||
mainContext = gui.State.Contexts.PatchBuilding
|
||||
pair = gui.c.MainViewPairs().PatchBuilding
|
||||
}
|
||||
|
||||
return gui.refreshMainViews(refreshMainOpts{
|
||||
main: &viewUpdateOpts{
|
||||
title: "Patch",
|
||||
task: task,
|
||||
context: mainContext,
|
||||
return gui.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: pair,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: gui.Tr.Patch,
|
||||
Task: task,
|
||||
},
|
||||
secondary: gui.secondaryPatchPanelUpdateOpts(),
|
||||
Secondary: gui.secondaryPatchPanelUpdateOpts(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -46,33 +41,11 @@ func (gui *Gui) SwitchToCommitFilesContext(opts controllers.SwitchToCommitFilesC
|
||||
gui.State.Contexts.CommitFiles.SetParentContext(opts.Context)
|
||||
gui.State.Contexts.CommitFiles.SetWindowName(opts.Context.GetWindowName())
|
||||
|
||||
if err := gui.refreshCommitFilesContext(); err != nil {
|
||||
if err := gui.c.Refresh(types.RefreshOptions{
|
||||
Scope: []types.RefreshableView{types.COMMIT_FILES},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.c.PushContext(gui.State.Contexts.CommitFiles)
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshCommitFilesContext() error {
|
||||
ref := gui.State.Contexts.CommitFiles.GetRef()
|
||||
to := ref.RefName()
|
||||
from, reverse := gui.State.Modes.Diffing.GetFromAndReverseArgsForDiff(ref.ParentRefName())
|
||||
|
||||
files, err := gui.git.Loaders.CommitFiles.GetFilesInDiff(from, to, reverse)
|
||||
if err != nil {
|
||||
return gui.c.Error(err)
|
||||
}
|
||||
gui.State.Model.CommitFiles = files
|
||||
gui.State.Contexts.CommitFiles.CommitFileTreeViewModel.SetTree()
|
||||
|
||||
return gui.c.PostRefreshUpdate(gui.State.Contexts.CommitFiles)
|
||||
}
|
||||
|
||||
func (gui *Gui) getSelectedCommitFileName() string {
|
||||
node := gui.State.Contexts.CommitFiles.GetSelected()
|
||||
if node == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return node.Path
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
@@ -12,9 +13,9 @@ func (gui *Gui) handleCommitMessageFocused() error {
|
||||
message := utils.ResolvePlaceholderString(
|
||||
gui.c.Tr.CommitMessageConfirm,
|
||||
map[string]string{
|
||||
"keyBindClose": gui.getKeyDisplay(gui.c.UserConfig.Keybinding.Universal.Return),
|
||||
"keyBindConfirm": gui.getKeyDisplay(gui.c.UserConfig.Keybinding.Universal.Confirm),
|
||||
"keyBindNewLine": gui.getKeyDisplay(gui.c.UserConfig.Keybinding.Universal.AppendNewline),
|
||||
"keyBindClose": keybindings.Label(gui.c.UserConfig.Keybinding.Universal.Return),
|
||||
"keyBindConfirm": keybindings.Label(gui.c.UserConfig.Keybinding.Universal.Confirm),
|
||||
"keyBindNewLine": keybindings.Label(gui.c.UserConfig.Keybinding.Universal.AppendNewline),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
+23
-10
@@ -2,6 +2,7 @@ package gui
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
@@ -25,30 +26,42 @@ func (gui *Gui) onCommitFocus() error {
|
||||
})
|
||||
}
|
||||
|
||||
gui.escapeLineByLinePanel()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) branchCommitsRenderToMain() error {
|
||||
var task updateTask
|
||||
var task types.UpdateTask
|
||||
commit := gui.State.Contexts.LocalCommits.GetSelected()
|
||||
if commit == nil {
|
||||
task = NewRenderStringTask(gui.c.Tr.NoCommitsThisBranch)
|
||||
task = types.NewRenderStringTask(gui.c.Tr.NoCommitsThisBranch)
|
||||
} else {
|
||||
cmdObj := gui.git.Commit.ShowCmdObj(commit.Sha, gui.State.Modes.Filtering.GetPath())
|
||||
task = NewRunPtyTask(cmdObj.GetCmd())
|
||||
task = types.NewRunPtyTask(cmdObj.GetCmd())
|
||||
}
|
||||
|
||||
return gui.refreshMainViews(refreshMainOpts{
|
||||
main: &viewUpdateOpts{
|
||||
title: "Patch",
|
||||
task: task,
|
||||
return gui.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: gui.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: "Patch",
|
||||
Task: task,
|
||||
},
|
||||
secondary: gui.secondaryPatchPanelUpdateOpts(),
|
||||
Secondary: gui.secondaryPatchPanelUpdateOpts(),
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) secondaryPatchPanelUpdateOpts() *types.ViewUpdateOpts {
|
||||
if gui.git.Patch.PatchManager.Active() {
|
||||
patch := gui.git.Patch.PatchManager.RenderAggregatedPatchColored(false)
|
||||
|
||||
return &types.ViewUpdateOpts{
|
||||
Task: types.NewRenderStringWithoutScrollTask(patch),
|
||||
Title: gui.Tr.CustomPatch,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) refForLog() string {
|
||||
bisectInfo := gui.git.Bisect.GetInfo()
|
||||
gui.State.Model.BisectInfo = bisectInfo
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
@@ -16,9 +16,9 @@ import (
|
||||
// This file is for the rendering of confirmation panels along with setting and handling associated
|
||||
// keybindings.
|
||||
|
||||
func (gui *Gui) wrappedConfirmationFunction(handlersManageFocus bool, function func() error) func() error {
|
||||
func (gui *Gui) wrappedConfirmationFunction(function func() error) func() error {
|
||||
return func() error {
|
||||
if err := gui.closeConfirmationPrompt(handlersManageFocus); err != nil {
|
||||
if err := gui.c.PopContext(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -32,9 +32,9 @@ func (gui *Gui) wrappedConfirmationFunction(handlersManageFocus bool, function f
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) wrappedPromptConfirmationFunction(handlersManageFocus bool, function func(string) error, getResponse func() string) func() error {
|
||||
func (gui *Gui) wrappedPromptConfirmationFunction(function func(string) error, getResponse func() string) func() error {
|
||||
return func() error {
|
||||
if err := gui.closeConfirmationPrompt(handlersManageFocus); err != nil {
|
||||
if err := gui.c.PopContext(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -48,27 +48,15 @@ func (gui *Gui) wrappedPromptConfirmationFunction(handlersManageFocus bool, func
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) closeConfirmationPrompt(handlersManageFocus bool) error {
|
||||
func (gui *Gui) deactivateConfirmationPrompt() {
|
||||
gui.Mutexes.PopupMutex.Lock()
|
||||
gui.State.CurrentPopupOpts = nil
|
||||
gui.Mutexes.PopupMutex.Unlock()
|
||||
|
||||
// we've already closed it so we can just return
|
||||
if !gui.Views.Confirmation.Visible {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !handlersManageFocus {
|
||||
if err := gui.c.PopContext(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
gui.clearConfirmationViewKeyBindings()
|
||||
gui.Views.Confirmation.Visible = false
|
||||
gui.Views.Suggestions.Visible = false
|
||||
|
||||
return nil
|
||||
gui.clearConfirmationViewKeyBindings()
|
||||
}
|
||||
|
||||
func (gui *Gui) getMessageHeight(wrap bool, message string, width int) int {
|
||||
@@ -123,33 +111,30 @@ func (gui *Gui) getConfirmationPanelWidth() int {
|
||||
}
|
||||
|
||||
func (gui *Gui) prepareConfirmationPanel(
|
||||
title,
|
||||
prompt string,
|
||||
hasLoader bool,
|
||||
findSuggestionsFunc func(string) []*types.Suggestion,
|
||||
editable bool,
|
||||
mask bool,
|
||||
opts types.ConfirmOpts,
|
||||
) error {
|
||||
gui.Views.Confirmation.HasLoader = hasLoader
|
||||
if hasLoader {
|
||||
gui.Views.Confirmation.HasLoader = opts.HasLoader
|
||||
if opts.HasLoader {
|
||||
gui.g.StartTicking()
|
||||
}
|
||||
gui.Views.Confirmation.Title = title
|
||||
gui.Views.Confirmation.Title = opts.Title
|
||||
// for now we do not support wrapping in our editor
|
||||
gui.Views.Confirmation.Wrap = !editable
|
||||
gui.Views.Confirmation.Wrap = !opts.Editable
|
||||
gui.Views.Confirmation.FgColor = theme.GocuiDefaultTextColor
|
||||
gui.Views.Confirmation.Mask = runeForMask(mask)
|
||||
gui.Views.Confirmation.Mask = runeForMask(opts.Mask)
|
||||
_ = gui.Views.Confirmation.SetOrigin(0, 0)
|
||||
|
||||
gui.findSuggestions = findSuggestionsFunc
|
||||
if findSuggestionsFunc != nil {
|
||||
gui.findSuggestions = opts.FindSuggestionsFunc
|
||||
if opts.FindSuggestionsFunc != nil {
|
||||
suggestionsView := gui.Views.Suggestions
|
||||
suggestionsView.Wrap = false
|
||||
suggestionsView.FgColor = theme.GocuiDefaultTextColor
|
||||
gui.setSuggestions(findSuggestionsFunc(""))
|
||||
gui.setSuggestions(opts.FindSuggestionsFunc(""))
|
||||
suggestionsView.Visible = true
|
||||
suggestionsView.Title = fmt.Sprintf(gui.c.Tr.SuggestionsTitle, gui.c.UserConfig.Keybinding.Universal.TogglePanel)
|
||||
}
|
||||
|
||||
gui.resizeConfirmationPanel()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -177,13 +162,14 @@ func (gui *Gui) createPopupPanel(opts types.CreatePopupPanelOpts) error {
|
||||
gui.clearConfirmationViewKeyBindings()
|
||||
|
||||
err := gui.prepareConfirmationPanel(
|
||||
opts.Title,
|
||||
opts.Prompt,
|
||||
opts.HasLoader,
|
||||
opts.FindSuggestionsFunc,
|
||||
opts.Editable,
|
||||
opts.Mask,
|
||||
)
|
||||
types.ConfirmOpts{
|
||||
Title: opts.Title,
|
||||
Prompt: opts.Prompt,
|
||||
HasLoader: opts.HasLoader,
|
||||
FindSuggestionsFunc: opts.FindSuggestionsFunc,
|
||||
Editable: opts.Editable,
|
||||
Mask: opts.Mask,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -195,6 +181,7 @@ func (gui *Gui) createPopupPanel(opts types.CreatePopupPanelOpts) error {
|
||||
textArea := confirmationView.TextArea
|
||||
textArea.Clear()
|
||||
textArea.TypeString(opts.Prompt)
|
||||
gui.resizeConfirmationPanel()
|
||||
confirmationView.RenderTextArea()
|
||||
} else {
|
||||
if err := gui.renderString(confirmationView, style.AttrBold.Sprint(opts.Prompt)); err != nil {
|
||||
@@ -223,14 +210,13 @@ func (gui *Gui) setKeyBindings(opts types.CreatePopupPanelOpts) error {
|
||||
_ = gui.renderString(gui.Views.Options, actions)
|
||||
var onConfirm func() error
|
||||
if opts.HandleConfirmPrompt != nil {
|
||||
onConfirm = gui.wrappedPromptConfirmationFunction(opts.HandlersManageFocus, opts.HandleConfirmPrompt, func() string { return gui.Views.Confirmation.TextArea.GetContent() })
|
||||
onConfirm = gui.wrappedPromptConfirmationFunction(opts.HandleConfirmPrompt, func() string { return gui.Views.Confirmation.TextArea.GetContent() })
|
||||
} else {
|
||||
onConfirm = gui.wrappedConfirmationFunction(opts.HandlersManageFocus, opts.HandleConfirm)
|
||||
onConfirm = gui.wrappedConfirmationFunction(opts.HandleConfirm)
|
||||
}
|
||||
|
||||
keybindingConfig := gui.c.UserConfig.Keybinding
|
||||
onSuggestionConfirm := gui.wrappedPromptConfirmationFunction(
|
||||
opts.HandlersManageFocus,
|
||||
opts.HandleConfirmPrompt,
|
||||
gui.getSelectedSuggestionValue,
|
||||
)
|
||||
@@ -238,26 +224,22 @@ func (gui *Gui) setKeyBindings(opts types.CreatePopupPanelOpts) error {
|
||||
bindings := []*types.Binding{
|
||||
{
|
||||
ViewName: "confirmation",
|
||||
Contexts: []string{string(context.CONFIRMATION_CONTEXT_KEY)},
|
||||
Key: gui.getKey(keybindingConfig.Universal.Confirm),
|
||||
Key: keybindings.GetKey(keybindingConfig.Universal.Confirm),
|
||||
Handler: onConfirm,
|
||||
},
|
||||
{
|
||||
ViewName: "confirmation",
|
||||
Contexts: []string{string(context.CONFIRMATION_CONTEXT_KEY)},
|
||||
Key: gui.getKey(keybindingConfig.Universal.ConfirmAlt1),
|
||||
Key: keybindings.GetKey(keybindingConfig.Universal.ConfirmAlt1),
|
||||
Handler: onConfirm,
|
||||
},
|
||||
{
|
||||
ViewName: "confirmation",
|
||||
Contexts: []string{string(context.CONFIRMATION_CONTEXT_KEY)},
|
||||
Key: gui.getKey(keybindingConfig.Universal.Return),
|
||||
Handler: gui.wrappedConfirmationFunction(opts.HandlersManageFocus, opts.HandleClose),
|
||||
Key: keybindings.GetKey(keybindingConfig.Universal.Return),
|
||||
Handler: gui.wrappedConfirmationFunction(opts.HandleClose),
|
||||
},
|
||||
{
|
||||
ViewName: "confirmation",
|
||||
Contexts: []string{string(context.CONFIRMATION_CONTEXT_KEY)},
|
||||
Key: gui.getKey(keybindingConfig.Universal.TogglePanel),
|
||||
Key: keybindings.GetKey(keybindingConfig.Universal.TogglePanel),
|
||||
Handler: func() error {
|
||||
if len(gui.State.Suggestions) > 0 {
|
||||
return gui.replaceContext(gui.State.Contexts.Suggestions)
|
||||
@@ -267,26 +249,22 @@ func (gui *Gui) setKeyBindings(opts types.CreatePopupPanelOpts) error {
|
||||
},
|
||||
{
|
||||
ViewName: "suggestions",
|
||||
Contexts: []string{string(context.SUGGESTIONS_CONTEXT_KEY)},
|
||||
Key: gui.getKey(keybindingConfig.Universal.Confirm),
|
||||
Key: keybindings.GetKey(keybindingConfig.Universal.Confirm),
|
||||
Handler: onSuggestionConfirm,
|
||||
},
|
||||
{
|
||||
ViewName: "suggestions",
|
||||
Contexts: []string{string(context.SUGGESTIONS_CONTEXT_KEY)},
|
||||
Key: gui.getKey(keybindingConfig.Universal.ConfirmAlt1),
|
||||
Key: keybindings.GetKey(keybindingConfig.Universal.ConfirmAlt1),
|
||||
Handler: onSuggestionConfirm,
|
||||
},
|
||||
{
|
||||
ViewName: "suggestions",
|
||||
Contexts: []string{string(context.SUGGESTIONS_CONTEXT_KEY)},
|
||||
Key: gui.getKey(keybindingConfig.Universal.Return),
|
||||
Handler: gui.wrappedConfirmationFunction(opts.HandlersManageFocus, opts.HandleClose),
|
||||
Key: keybindings.GetKey(keybindingConfig.Universal.Return),
|
||||
Handler: gui.wrappedConfirmationFunction(opts.HandleClose),
|
||||
},
|
||||
{
|
||||
ViewName: "suggestions",
|
||||
Contexts: []string{string(context.SUGGESTIONS_CONTEXT_KEY)},
|
||||
Key: gui.getKey(keybindingConfig.Universal.TogglePanel),
|
||||
Key: keybindings.GetKey(keybindingConfig.Universal.TogglePanel),
|
||||
Handler: func() error { return gui.replaceContext(gui.State.Contexts.Confirmation) },
|
||||
},
|
||||
}
|
||||
@@ -302,12 +280,12 @@ func (gui *Gui) setKeyBindings(opts types.CreatePopupPanelOpts) error {
|
||||
|
||||
func (gui *Gui) clearConfirmationViewKeyBindings() {
|
||||
keybindingConfig := gui.c.UserConfig.Keybinding
|
||||
_ = gui.g.DeleteKeybinding("confirmation", gui.getKey(keybindingConfig.Universal.Confirm), gocui.ModNone)
|
||||
_ = gui.g.DeleteKeybinding("confirmation", gui.getKey(keybindingConfig.Universal.ConfirmAlt1), gocui.ModNone)
|
||||
_ = gui.g.DeleteKeybinding("confirmation", gui.getKey(keybindingConfig.Universal.Return), gocui.ModNone)
|
||||
_ = gui.g.DeleteKeybinding("suggestions", gui.getKey(keybindingConfig.Universal.Confirm), gocui.ModNone)
|
||||
_ = gui.g.DeleteKeybinding("suggestions", gui.getKey(keybindingConfig.Universal.ConfirmAlt1), gocui.ModNone)
|
||||
_ = gui.g.DeleteKeybinding("suggestions", gui.getKey(keybindingConfig.Universal.Return), gocui.ModNone)
|
||||
_ = gui.g.DeleteKeybinding("confirmation", keybindings.GetKey(keybindingConfig.Universal.Confirm), gocui.ModNone)
|
||||
_ = gui.g.DeleteKeybinding("confirmation", keybindings.GetKey(keybindingConfig.Universal.ConfirmAlt1), gocui.ModNone)
|
||||
_ = gui.g.DeleteKeybinding("confirmation", keybindings.GetKey(keybindingConfig.Universal.Return), gocui.ModNone)
|
||||
_ = gui.g.DeleteKeybinding("suggestions", keybindings.GetKey(keybindingConfig.Universal.Confirm), gocui.ModNone)
|
||||
_ = gui.g.DeleteKeybinding("suggestions", keybindings.GetKey(keybindingConfig.Universal.ConfirmAlt1), gocui.ModNone)
|
||||
_ = gui.g.DeleteKeybinding("suggestions", keybindings.GetKey(keybindingConfig.Universal.Return), gocui.ModNone)
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshSuggestions() {
|
||||
@@ -323,8 +301,8 @@ func (gui *Gui) handleAskFocused() error {
|
||||
message := utils.ResolvePlaceholderString(
|
||||
gui.c.Tr.CloseConfirm,
|
||||
map[string]string{
|
||||
"keyBindClose": gui.getKeyDisplay(keybindingConfig.Universal.Return),
|
||||
"keyBindConfirm": gui.getKeyDisplay(keybindingConfig.Universal.Confirm),
|
||||
"keyBindClose": keybindings.Label(keybindingConfig.Universal.Return),
|
||||
"keyBindConfirm": keybindings.Label(keybindingConfig.Universal.Confirm),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
+73
-160
@@ -1,8 +1,6 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
@@ -28,24 +26,6 @@ func (gui *Gui) popupViewNames() []string {
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) currentContextKeyIgnoringPopups() types.ContextKey {
|
||||
gui.State.ContextManager.RLock()
|
||||
defer gui.State.ContextManager.RUnlock()
|
||||
|
||||
stack := gui.State.ContextManager.ContextStack
|
||||
|
||||
for i := range stack {
|
||||
reversedIndex := len(stack) - 1 - i
|
||||
context := stack[reversedIndex]
|
||||
kind := stack[reversedIndex].GetKind()
|
||||
if kind != types.TEMPORARY_POPUP && kind != types.PERSISTENT_POPUP {
|
||||
return context.GetKey()
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// use replaceContext when you don't want to return to the original context upon
|
||||
// hitting escape: you want to go that context's parent instead.
|
||||
func (gui *Gui) replaceContext(c types.Context) error {
|
||||
@@ -64,54 +44,71 @@ func (gui *Gui) replaceContext(c types.Context) error {
|
||||
|
||||
defer gui.State.ContextManager.Unlock()
|
||||
|
||||
return gui.activateContext(c)
|
||||
return gui.activateContext(c, types.OnFocusOpts{})
|
||||
}
|
||||
|
||||
func (gui *Gui) pushContext(c types.Context, opts ...types.OnFocusOpts) error {
|
||||
// using triple dot but you should only ever pass one of these opt structs
|
||||
if len(opts) > 1 {
|
||||
return errors.New("cannot pass multiple opts to pushContext")
|
||||
}
|
||||
|
||||
func (gui *Gui) pushContext(c types.Context, opts types.OnFocusOpts) error {
|
||||
if !c.IsFocusable() {
|
||||
return nil
|
||||
}
|
||||
|
||||
gui.State.ContextManager.Lock()
|
||||
contextsToDeactivate := gui.pushToContextStack(c)
|
||||
|
||||
// push onto stack
|
||||
// if we are switching to a side context, remove all other contexts in the stack
|
||||
if c.GetKind() == types.SIDE_CONTEXT {
|
||||
for _, contextToDeactivate := range contextsToDeactivate {
|
||||
if err := gui.deactivateContext(contextToDeactivate, types.OnFocusLostOpts{NewContextKey: c.GetKey()}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return gui.activateContext(c, opts)
|
||||
}
|
||||
|
||||
// Adjusts the context stack based on the context that's being pushed and returns contexts to deactivate
|
||||
func (gui *Gui) pushToContextStack(c types.Context) []types.Context {
|
||||
contextsToDeactivate := []types.Context{}
|
||||
|
||||
gui.State.ContextManager.Lock()
|
||||
defer gui.State.ContextManager.Unlock()
|
||||
|
||||
if len(gui.State.ContextManager.ContextStack) == 0 {
|
||||
gui.State.ContextManager.ContextStack = append(gui.State.ContextManager.ContextStack, c)
|
||||
} else if c.GetKind() == types.SIDE_CONTEXT {
|
||||
// if we are switching to a side context, remove all other contexts in the stack
|
||||
contextsToDeactivate = gui.State.ContextManager.ContextStack
|
||||
gui.State.ContextManager.ContextStack = []types.Context{c}
|
||||
} else if c.GetKind() == types.MAIN_CONTEXT {
|
||||
// if we're switching to a main context, remove all other main contexts in the stack
|
||||
for _, stackContext := range gui.State.ContextManager.ContextStack {
|
||||
if stackContext.GetKey() != c.GetKey() {
|
||||
if err := gui.deactivateContext(stackContext); err != nil {
|
||||
gui.State.ContextManager.Unlock()
|
||||
return err
|
||||
}
|
||||
if stackContext.GetKind() == types.MAIN_CONTEXT {
|
||||
contextsToDeactivate = append(contextsToDeactivate, stackContext)
|
||||
}
|
||||
}
|
||||
gui.State.ContextManager.ContextStack = []types.Context{c}
|
||||
} else if len(gui.State.ContextManager.ContextStack) == 0 || gui.currentContextWithoutLock().GetKey() != c.GetKey() {
|
||||
// Do not append if the one at the end is the same context (e.g. opening a menu from a menu)
|
||||
// In that case we'll just close the menu entirely when the user hits escape.
|
||||
} else {
|
||||
topContext := gui.currentContextWithoutLock()
|
||||
|
||||
// TODO: think about other exceptional cases
|
||||
gui.State.ContextManager.ContextStack = append(gui.State.ContextManager.ContextStack, c)
|
||||
// if we're pushing the same context on, we do nothing.
|
||||
if topContext.GetKey() != c.GetKey() {
|
||||
// if top one is a temporary popup, we remove it. Ideally you'd be able to
|
||||
// escape back to previous temporary popups, but because we're currently reusing
|
||||
// views for this, you might not be able to get back to where you previously were.
|
||||
// The exception is when going to the search context e.g. for searching a menu.
|
||||
if (topContext.GetKind() == types.TEMPORARY_POPUP && c.GetKey() != context.SEARCH_CONTEXT_KEY) ||
|
||||
// we only ever want one main context on the stack at a time.
|
||||
(topContext.GetKind() == types.MAIN_CONTEXT && c.GetKind() == types.MAIN_CONTEXT) {
|
||||
|
||||
contextsToDeactivate = append(contextsToDeactivate, topContext)
|
||||
_, gui.State.ContextManager.ContextStack = slices.Pop(gui.State.ContextManager.ContextStack)
|
||||
}
|
||||
|
||||
gui.State.ContextManager.ContextStack = append(gui.State.ContextManager.ContextStack, c)
|
||||
}
|
||||
}
|
||||
|
||||
gui.State.ContextManager.Unlock()
|
||||
|
||||
return gui.activateContext(c, opts...)
|
||||
return contextsToDeactivate
|
||||
}
|
||||
|
||||
// pushContextWithView is to be used when you don't know which context you
|
||||
// want to switch to: you only know the view that you want to switch to. It will
|
||||
// look up the context currently active for that view and switch to that context
|
||||
func (gui *Gui) pushContextWithView(viewName string) error {
|
||||
return gui.c.PushContext(gui.State.ViewContextMap.Get(viewName))
|
||||
}
|
||||
|
||||
func (gui *Gui) returnFromContext() error {
|
||||
func (gui *Gui) popContext() error {
|
||||
gui.State.ContextManager.Lock()
|
||||
|
||||
if len(gui.State.ContextManager.ContextStack) == 1 {
|
||||
@@ -120,25 +117,21 @@ func (gui *Gui) returnFromContext() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
n := len(gui.State.ContextManager.ContextStack) - 1
|
||||
var currentContext types.Context
|
||||
currentContext, gui.State.ContextManager.ContextStack = slices.Pop(gui.State.ContextManager.ContextStack)
|
||||
|
||||
currentContext := gui.State.ContextManager.ContextStack[n]
|
||||
newContext := gui.State.ContextManager.ContextStack[n-1]
|
||||
|
||||
gui.State.ContextManager.ContextStack = gui.State.ContextManager.ContextStack[:n]
|
||||
|
||||
gui.g.SetCurrentContext(string(newContext.GetKey()))
|
||||
newContext := gui.State.ContextManager.ContextStack[len(gui.State.ContextManager.ContextStack)-1]
|
||||
|
||||
gui.State.ContextManager.Unlock()
|
||||
|
||||
if err := gui.deactivateContext(currentContext); err != nil {
|
||||
if err := gui.deactivateContext(currentContext, types.OnFocusLostOpts{NewContextKey: newContext.GetKey()}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.activateContext(newContext)
|
||||
return gui.activateContext(newContext, types.OnFocusOpts{})
|
||||
}
|
||||
|
||||
func (gui *Gui) deactivateContext(c types.Context) error {
|
||||
func (gui *Gui) deactivateContext(c types.Context, opts types.OnFocusLostOpts) error {
|
||||
view, _ := gui.g.View(c.GetViewName())
|
||||
|
||||
if view != nil && view.IsSearching() {
|
||||
@@ -154,7 +147,7 @@ func (gui *Gui) deactivateContext(c types.Context) error {
|
||||
view.Visible = false
|
||||
}
|
||||
|
||||
if err := c.HandleFocusLost(); err != nil {
|
||||
if err := c.HandleFocusLost(opts); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -165,16 +158,12 @@ func (gui *Gui) deactivateContext(c types.Context) error {
|
||||
// if the context's view is set to another context we do nothing.
|
||||
// if the context's view is the current view we trigger a focus; re-selecting the current item.
|
||||
func (gui *Gui) postRefreshUpdate(c types.Context) error {
|
||||
if gui.State.ViewContextMap.Get(c.GetViewName()).GetKey() != c.GetKey() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := c.HandleRender(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if gui.currentViewName() == c.GetViewName() {
|
||||
if err := c.HandleFocus(); err != nil {
|
||||
if err := c.HandleFocus(types.OnFocusOpts{}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -182,29 +171,16 @@ func (gui *Gui) postRefreshUpdate(c types.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) activateContext(c types.Context, opts ...types.OnFocusOpts) error {
|
||||
func (gui *Gui) activateContext(c types.Context, opts types.OnFocusOpts) error {
|
||||
viewName := c.GetViewName()
|
||||
v, err := gui.g.View(viewName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
originalViewContext := gui.State.ViewContextMap.Get(viewName)
|
||||
var originalViewContextKey types.ContextKey = ""
|
||||
if originalViewContext != nil {
|
||||
originalViewContextKey = originalViewContext.GetKey()
|
||||
}
|
||||
|
||||
gui.setWindowContext(c)
|
||||
gui.setViewTabForContext(c)
|
||||
|
||||
if viewName == "main" {
|
||||
gui.changeMainViewsContext(c)
|
||||
} else {
|
||||
gui.changeMainViewsContext(gui.State.Contexts.Normal)
|
||||
}
|
||||
|
||||
gui.g.SetCurrentContext(string(c.GetKey()))
|
||||
gui.moveToTopOfWindow(c)
|
||||
if _, err := gui.g.SetCurrentView(viewName); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -216,15 +192,6 @@ func (gui *Gui) activateContext(c types.Context, opts ...types.OnFocusOpts) erro
|
||||
|
||||
v.Visible = true
|
||||
|
||||
// if the new context's view was previously displaying another context, render the new context
|
||||
if originalViewContextKey != c.GetKey() {
|
||||
if err := c.HandleRender(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
gui.ViewContextMapSet(viewName, c)
|
||||
|
||||
gui.g.Cursor = v.Editable
|
||||
|
||||
// render the options available for the current context at the bottom of the screen
|
||||
@@ -234,7 +201,7 @@ func (gui *Gui) activateContext(c types.Context, opts ...types.OnFocusOpts) erro
|
||||
}
|
||||
gui.renderOptionsMap(optionsMap)
|
||||
|
||||
if err := c.HandleFocus(opts...); err != nil {
|
||||
if err := c.HandleFocus(opts); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -253,16 +220,6 @@ func (gui *Gui) renderOptionsMap(optionsMap map[string]string) {
|
||||
_ = gui.renderString(gui.Views.Options, gui.optionsMapToString(optionsMap))
|
||||
}
|
||||
|
||||
// also setting context on view for now. We'll need to pick one of these two approaches to stick with.
|
||||
func (gui *Gui) ViewContextMapSet(viewName string, c types.Context) {
|
||||
gui.State.ViewContextMap.Set(viewName, c)
|
||||
view, err := gui.g.View(viewName)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
view.Context = string(c.GetKey())
|
||||
}
|
||||
|
||||
// // currently unused
|
||||
// func (gui *Gui) renderContextStack() string {
|
||||
// result := ""
|
||||
@@ -326,6 +283,10 @@ func (gui *Gui) currentStaticContext() types.Context {
|
||||
gui.State.ContextManager.RLock()
|
||||
defer gui.State.ContextManager.RUnlock()
|
||||
|
||||
return gui.currentStaticContextWithoutLock()
|
||||
}
|
||||
|
||||
func (gui *Gui) currentStaticContextWithoutLock() types.Context {
|
||||
stack := gui.State.ContextManager.ContextStack
|
||||
|
||||
if len(stack) == 0 {
|
||||
@@ -359,7 +320,7 @@ func (gui *Gui) getFocusLayout() func(g *gocui.Gui) error {
|
||||
newView := gui.g.CurrentView()
|
||||
// for now we don't consider losing focus to a popup panel as actually losing focus
|
||||
if newView != previousView && !gui.isPopupPanel(newView.Name()) {
|
||||
if err := gui.onViewFocusLost(previousView, newView); err != nil {
|
||||
if err := gui.onViewFocusLost(previousView); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -369,7 +330,7 @@ func (gui *Gui) getFocusLayout() func(g *gocui.Gui) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) onViewFocusLost(oldView *gocui.View, newView *gocui.View) error {
|
||||
func (gui *Gui) onViewFocusLost(oldView *gocui.View) error {
|
||||
if oldView == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -387,57 +348,14 @@ func (gui *Gui) TransientContexts() []types.Context {
|
||||
})
|
||||
}
|
||||
|
||||
// changeContext is a helper function for when we want to change a 'main' context
|
||||
// which currently just means a context that affects both the main and secondary views
|
||||
// other views can have their context changed directly but this function helps
|
||||
// keep the main and secondary views in sync
|
||||
func (gui *Gui) changeMainViewsContext(c types.Context) {
|
||||
if gui.State.MainContext == c.GetKey() {
|
||||
return
|
||||
}
|
||||
|
||||
switch c.GetKey() {
|
||||
case context.MAIN_NORMAL_CONTEXT_KEY, context.MAIN_PATCH_BUILDING_CONTEXT_KEY, context.MAIN_STAGING_CONTEXT_KEY, context.MAIN_MERGING_CONTEXT_KEY:
|
||||
gui.ViewContextMapSet(gui.Views.Main.Name(), c)
|
||||
gui.ViewContextMapSet(gui.Views.Secondary.Name(), c)
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown context for main: %s", c.GetKey()))
|
||||
}
|
||||
|
||||
gui.State.MainContext = c.GetKey()
|
||||
}
|
||||
|
||||
func (gui *Gui) viewTabNames(viewName string) []string {
|
||||
tabContexts := gui.State.ViewTabContextMap[viewName]
|
||||
|
||||
return slices.Map(tabContexts, func(tabContext context.TabContext) string {
|
||||
return tabContext.Tab
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) setViewTabForContext(c types.Context) {
|
||||
// search for the context in our map and if we find it, set the tab for the corresponding view
|
||||
tabContexts, ok := gui.State.ViewTabContextMap[c.GetViewName()]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
for tabIndex, tabContext := range tabContexts {
|
||||
if tabContext.Context.GetKey() == c.GetKey() {
|
||||
// get the view, set the tab
|
||||
v, err := gui.g.View(c.GetViewName())
|
||||
if err != nil {
|
||||
gui.c.Log.Error(err)
|
||||
return
|
||||
}
|
||||
v.TabIndex = tabIndex
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) rerenderView(view *gocui.View) error {
|
||||
return gui.State.ViewContextMap.Get(view.Name()).HandleRender()
|
||||
context, ok := gui.contextForView(view.Name())
|
||||
if !ok {
|
||||
gui.Log.Errorf("no context found for view %s", view.Name())
|
||||
return nil
|
||||
}
|
||||
|
||||
return context.HandleRender()
|
||||
}
|
||||
|
||||
func (gui *Gui) getSideContextSelectedItemId() string {
|
||||
@@ -449,11 +367,6 @@ func (gui *Gui) getSideContextSelectedItemId() string {
|
||||
return currentSideContext.GetSelectedItemId()
|
||||
}
|
||||
|
||||
func (gui *Gui) isContextVisible(c types.Context) bool {
|
||||
return gui.State.WindowViewNameMap[c.GetWindowName()] == c.GetViewName() &&
|
||||
gui.State.ViewContextMap.Get(c.GetViewName()).GetKey() == c.GetKey()
|
||||
}
|
||||
|
||||
// currently unused
|
||||
// func (gui *Gui) getCurrentSideView() *gocui.View {
|
||||
// currentSideContext := gui.currentSideContext()
|
||||
|
||||
@@ -8,7 +8,8 @@ import (
|
||||
type BaseContext struct {
|
||||
kind types.ContextKind
|
||||
key types.ContextKey
|
||||
ViewName string
|
||||
view *gocui.View
|
||||
viewTrait types.IViewTrait
|
||||
windowName string
|
||||
onGetOptionsMap func() map[string]string
|
||||
|
||||
@@ -16,8 +17,9 @@ type BaseContext struct {
|
||||
mouseKeybindingsFns []types.MouseKeybindingsFn
|
||||
onClickFn func() error
|
||||
|
||||
focusable bool
|
||||
transient bool
|
||||
focusable bool
|
||||
transient bool
|
||||
hasControlledBounds bool
|
||||
|
||||
*ParentContextMgr
|
||||
}
|
||||
@@ -25,26 +27,33 @@ type BaseContext struct {
|
||||
var _ types.IBaseContext = &BaseContext{}
|
||||
|
||||
type NewBaseContextOpts struct {
|
||||
Kind types.ContextKind
|
||||
Key types.ContextKey
|
||||
ViewName string
|
||||
WindowName string
|
||||
Focusable bool
|
||||
Transient bool
|
||||
Kind types.ContextKind
|
||||
Key types.ContextKey
|
||||
View *gocui.View
|
||||
WindowName string
|
||||
Focusable bool
|
||||
Transient bool
|
||||
HasUncontrolledBounds bool // negating for the sake of making false the default
|
||||
|
||||
OnGetOptionsMap func() map[string]string
|
||||
}
|
||||
|
||||
func NewBaseContext(opts NewBaseContextOpts) *BaseContext {
|
||||
viewTrait := NewViewTrait(opts.View)
|
||||
|
||||
hasControlledBounds := !opts.HasUncontrolledBounds
|
||||
|
||||
return &BaseContext{
|
||||
kind: opts.Kind,
|
||||
key: opts.Key,
|
||||
ViewName: opts.ViewName,
|
||||
windowName: opts.WindowName,
|
||||
onGetOptionsMap: opts.OnGetOptionsMap,
|
||||
focusable: opts.Focusable,
|
||||
transient: opts.Transient,
|
||||
ParentContextMgr: &ParentContextMgr{},
|
||||
kind: opts.Kind,
|
||||
key: opts.Key,
|
||||
view: opts.View,
|
||||
windowName: opts.WindowName,
|
||||
onGetOptionsMap: opts.OnGetOptionsMap,
|
||||
focusable: opts.Focusable,
|
||||
transient: opts.Transient,
|
||||
hasControlledBounds: hasControlledBounds,
|
||||
ParentContextMgr: &ParentContextMgr{},
|
||||
viewTrait: viewTrait,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +73,20 @@ func (self *BaseContext) GetWindowName() string {
|
||||
}
|
||||
|
||||
func (self *BaseContext) GetViewName() string {
|
||||
return self.ViewName
|
||||
// for the sake of the global context which has no view
|
||||
if self.view == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return self.view.Name()
|
||||
}
|
||||
|
||||
func (self *BaseContext) GetView() *gocui.View {
|
||||
return self.view
|
||||
}
|
||||
|
||||
func (self *BaseContext) GetViewTrait() types.IViewTrait {
|
||||
return self.viewTrait
|
||||
}
|
||||
|
||||
func (self *BaseContext) GetKind() types.ContextKind {
|
||||
@@ -123,6 +145,10 @@ func (self *BaseContext) IsTransient() bool {
|
||||
return self.transient
|
||||
}
|
||||
|
||||
func (self *BaseContext) HasControlledBounds() bool {
|
||||
return self.hasControlledBounds
|
||||
}
|
||||
|
||||
func (self *BaseContext) Title() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -18,9 +18,9 @@ func NewBranchesContext(
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
|
||||
onFocus func(...types.OnFocusOpts) error,
|
||||
onRenderToMain func(...types.OnFocusOpts) error,
|
||||
onFocusLost func() error,
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
|
||||
c *types.HelperCommon,
|
||||
) *BranchesContext {
|
||||
@@ -30,7 +30,7 @@ func NewBranchesContext(
|
||||
BasicViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
ViewName: "branches",
|
||||
View: view,
|
||||
WindowName: "branches",
|
||||
Key: LOCAL_BRANCHES_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
@@ -41,7 +41,6 @@ func NewBranchesContext(
|
||||
OnRenderToMain: onRenderToMain,
|
||||
}),
|
||||
list: viewModel,
|
||||
viewTrait: NewViewTrait(view),
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
},
|
||||
|
||||
@@ -20,9 +20,9 @@ func NewCommitFilesContext(
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
|
||||
onFocus func(...types.OnFocusOpts) error,
|
||||
onRenderToMain func(...types.OnFocusOpts) error,
|
||||
onFocusLost func() error,
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
|
||||
c *types.HelperCommon,
|
||||
) *CommitFilesContext {
|
||||
@@ -34,7 +34,7 @@ func NewCommitFilesContext(
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(
|
||||
NewBaseContext(NewBaseContextOpts{
|
||||
ViewName: "commitFiles",
|
||||
View: view,
|
||||
WindowName: "commits",
|
||||
Key: COMMIT_FILES_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
@@ -47,7 +47,6 @@ func NewCommitFilesContext(
|
||||
OnRenderToMain: onRenderToMain,
|
||||
}),
|
||||
list: viewModel,
|
||||
viewTrait: NewViewTrait(view),
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
},
|
||||
|
||||
+98
-91
@@ -1,39 +1,48 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
const (
|
||||
GLOBAL_CONTEXT_KEY types.ContextKey = "global"
|
||||
STATUS_CONTEXT_KEY types.ContextKey = "status"
|
||||
FILES_CONTEXT_KEY types.ContextKey = "files"
|
||||
LOCAL_BRANCHES_CONTEXT_KEY types.ContextKey = "localBranches"
|
||||
REMOTES_CONTEXT_KEY types.ContextKey = "remotes"
|
||||
REMOTE_BRANCHES_CONTEXT_KEY types.ContextKey = "remoteBranches"
|
||||
TAGS_CONTEXT_KEY types.ContextKey = "tags"
|
||||
LOCAL_COMMITS_CONTEXT_KEY types.ContextKey = "commits"
|
||||
REFLOG_COMMITS_CONTEXT_KEY types.ContextKey = "reflogCommits"
|
||||
SUB_COMMITS_CONTEXT_KEY types.ContextKey = "subCommits"
|
||||
COMMIT_FILES_CONTEXT_KEY types.ContextKey = "commitFiles"
|
||||
STASH_CONTEXT_KEY types.ContextKey = "stash"
|
||||
MAIN_NORMAL_CONTEXT_KEY types.ContextKey = "normal"
|
||||
MAIN_MERGING_CONTEXT_KEY types.ContextKey = "merging"
|
||||
MAIN_PATCH_BUILDING_CONTEXT_KEY types.ContextKey = "patchBuilding"
|
||||
MAIN_STAGING_CONTEXT_KEY types.ContextKey = "staging"
|
||||
MENU_CONTEXT_KEY types.ContextKey = "menu"
|
||||
CONFIRMATION_CONTEXT_KEY types.ContextKey = "confirmation"
|
||||
SEARCH_CONTEXT_KEY types.ContextKey = "search"
|
||||
COMMIT_MESSAGE_CONTEXT_KEY types.ContextKey = "commitMessage"
|
||||
SUBMODULES_CONTEXT_KEY types.ContextKey = "submodules"
|
||||
SUGGESTIONS_CONTEXT_KEY types.ContextKey = "suggestions"
|
||||
COMMAND_LOG_CONTEXT_KEY types.ContextKey = "cmdLog"
|
||||
GLOBAL_CONTEXT_KEY types.ContextKey = "global"
|
||||
STATUS_CONTEXT_KEY types.ContextKey = "status"
|
||||
FILES_CONTEXT_KEY types.ContextKey = "files"
|
||||
LOCAL_BRANCHES_CONTEXT_KEY types.ContextKey = "localBranches"
|
||||
REMOTES_CONTEXT_KEY types.ContextKey = "remotes"
|
||||
REMOTE_BRANCHES_CONTEXT_KEY types.ContextKey = "remoteBranches"
|
||||
TAGS_CONTEXT_KEY types.ContextKey = "tags"
|
||||
LOCAL_COMMITS_CONTEXT_KEY types.ContextKey = "commits"
|
||||
REFLOG_COMMITS_CONTEXT_KEY types.ContextKey = "reflogCommits"
|
||||
SUB_COMMITS_CONTEXT_KEY types.ContextKey = "subCommits"
|
||||
COMMIT_FILES_CONTEXT_KEY types.ContextKey = "commitFiles"
|
||||
STASH_CONTEXT_KEY types.ContextKey = "stash"
|
||||
NORMAL_MAIN_CONTEXT_KEY types.ContextKey = "normal"
|
||||
NORMAL_SECONDARY_CONTEXT_KEY types.ContextKey = "normalSecondary"
|
||||
STAGING_MAIN_CONTEXT_KEY types.ContextKey = "staging"
|
||||
STAGING_SECONDARY_CONTEXT_KEY types.ContextKey = "stagingSecondary"
|
||||
PATCH_BUILDING_MAIN_CONTEXT_KEY types.ContextKey = "patchBuilding"
|
||||
PATCH_BUILDING_SECONDARY_CONTEXT_KEY types.ContextKey = "patchBuildingSecondary"
|
||||
MERGE_CONFLICTS_CONTEXT_KEY types.ContextKey = "mergeConflicts"
|
||||
|
||||
// these shouldn't really be needed for anything but I'm giving them unique keys nonetheless
|
||||
OPTIONS_CONTEXT_KEY types.ContextKey = "options"
|
||||
APP_STATUS_CONTEXT_KEY types.ContextKey = "appStatus"
|
||||
SEARCH_PREFIX_CONTEXT_KEY types.ContextKey = "searchPrefix"
|
||||
INFORMATION_CONTEXT_KEY types.ContextKey = "information"
|
||||
LIMIT_CONTEXT_KEY types.ContextKey = "limit"
|
||||
|
||||
MENU_CONTEXT_KEY types.ContextKey = "menu"
|
||||
CONFIRMATION_CONTEXT_KEY types.ContextKey = "confirmation"
|
||||
SEARCH_CONTEXT_KEY types.ContextKey = "search"
|
||||
COMMIT_MESSAGE_CONTEXT_KEY types.ContextKey = "commitMessage"
|
||||
SUBMODULES_CONTEXT_KEY types.ContextKey = "submodules"
|
||||
SUGGESTIONS_CONTEXT_KEY types.ContextKey = "suggestions"
|
||||
COMMAND_LOG_CONTEXT_KEY types.ContextKey = "cmdLog"
|
||||
)
|
||||
|
||||
var AllContextKeys = []types.ContextKey{
|
||||
GLOBAL_CONTEXT_KEY, // not focusable
|
||||
GLOBAL_CONTEXT_KEY,
|
||||
STATUS_CONTEXT_KEY,
|
||||
FILES_CONTEXT_KEY,
|
||||
LOCAL_BRANCHES_CONTEXT_KEY,
|
||||
@@ -45,10 +54,14 @@ var AllContextKeys = []types.ContextKey{
|
||||
SUB_COMMITS_CONTEXT_KEY,
|
||||
COMMIT_FILES_CONTEXT_KEY,
|
||||
STASH_CONTEXT_KEY,
|
||||
MAIN_NORMAL_CONTEXT_KEY, // not focusable
|
||||
MAIN_MERGING_CONTEXT_KEY,
|
||||
MAIN_PATCH_BUILDING_CONTEXT_KEY,
|
||||
MAIN_STAGING_CONTEXT_KEY, // not focusable for secondary view
|
||||
NORMAL_MAIN_CONTEXT_KEY,
|
||||
NORMAL_SECONDARY_CONTEXT_KEY,
|
||||
STAGING_MAIN_CONTEXT_KEY,
|
||||
STAGING_SECONDARY_CONTEXT_KEY,
|
||||
PATCH_BUILDING_MAIN_CONTEXT_KEY,
|
||||
PATCH_BUILDING_SECONDARY_CONTEXT_KEY,
|
||||
MERGE_CONFLICTS_CONTEXT_KEY,
|
||||
|
||||
MENU_CONTEXT_KEY,
|
||||
CONFIRMATION_CONTEXT_KEY,
|
||||
SEARCH_CONTEXT_KEY,
|
||||
@@ -59,87 +72,81 @@ var AllContextKeys = []types.ContextKey{
|
||||
}
|
||||
|
||||
type ContextTree struct {
|
||||
Global types.Context
|
||||
Status types.Context
|
||||
Files *WorkingTreeContext
|
||||
Menu *MenuContext
|
||||
Branches *BranchesContext
|
||||
Tags *TagsContext
|
||||
LocalCommits *LocalCommitsContext
|
||||
CommitFiles *CommitFilesContext
|
||||
Remotes *RemotesContext
|
||||
Submodules *SubmodulesContext
|
||||
RemoteBranches *RemoteBranchesContext
|
||||
ReflogCommits *ReflogCommitsContext
|
||||
SubCommits *SubCommitsContext
|
||||
Stash *StashContext
|
||||
Suggestions *SuggestionsContext
|
||||
Normal types.Context
|
||||
Staging types.Context
|
||||
PatchBuilding types.Context
|
||||
Merging types.Context
|
||||
Confirmation types.Context
|
||||
CommitMessage types.Context
|
||||
Search types.Context
|
||||
CommandLog types.Context
|
||||
Global types.Context
|
||||
Status types.Context
|
||||
Files *WorkingTreeContext
|
||||
Menu *MenuContext
|
||||
Branches *BranchesContext
|
||||
Tags *TagsContext
|
||||
LocalCommits *LocalCommitsContext
|
||||
CommitFiles *CommitFilesContext
|
||||
Remotes *RemotesContext
|
||||
Submodules *SubmodulesContext
|
||||
RemoteBranches *RemoteBranchesContext
|
||||
ReflogCommits *ReflogCommitsContext
|
||||
SubCommits *SubCommitsContext
|
||||
Stash *StashContext
|
||||
Suggestions *SuggestionsContext
|
||||
Normal types.Context
|
||||
NormalSecondary types.Context
|
||||
Staging *PatchExplorerContext
|
||||
StagingSecondary *PatchExplorerContext
|
||||
CustomPatchBuilder *PatchExplorerContext
|
||||
CustomPatchBuilderSecondary types.Context
|
||||
MergeConflicts *MergeConflictsContext
|
||||
Confirmation types.Context
|
||||
CommitMessage types.Context
|
||||
CommandLog types.Context
|
||||
|
||||
// display contexts
|
||||
AppStatus types.Context
|
||||
Options types.Context
|
||||
SearchPrefix types.Context
|
||||
Search types.Context
|
||||
Information types.Context
|
||||
Limit types.Context
|
||||
}
|
||||
|
||||
// the order of this decides which context is initially at the top of its window
|
||||
func (self *ContextTree) Flatten() []types.Context {
|
||||
return []types.Context{
|
||||
self.Global,
|
||||
self.Status,
|
||||
self.Files,
|
||||
self.Submodules,
|
||||
self.Branches,
|
||||
self.Files,
|
||||
self.SubCommits,
|
||||
self.Remotes,
|
||||
self.RemoteBranches,
|
||||
self.Tags,
|
||||
self.LocalCommits,
|
||||
self.Branches,
|
||||
self.CommitFiles,
|
||||
self.ReflogCommits,
|
||||
self.LocalCommits,
|
||||
self.Stash,
|
||||
self.Menu,
|
||||
self.Confirmation,
|
||||
self.CommitMessage,
|
||||
self.Normal,
|
||||
|
||||
self.MergeConflicts,
|
||||
self.StagingSecondary,
|
||||
self.Staging,
|
||||
self.Merging,
|
||||
self.PatchBuilding,
|
||||
self.SubCommits,
|
||||
self.CustomPatchBuilderSecondary,
|
||||
self.CustomPatchBuilder,
|
||||
self.NormalSecondary,
|
||||
self.Normal,
|
||||
|
||||
self.Suggestions,
|
||||
self.CommandLog,
|
||||
self.AppStatus,
|
||||
self.Options,
|
||||
self.SearchPrefix,
|
||||
self.Search,
|
||||
self.Information,
|
||||
self.Limit,
|
||||
}
|
||||
}
|
||||
|
||||
type ViewContextMap struct {
|
||||
content map[string]types.Context
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
func NewViewContextMap() *ViewContextMap {
|
||||
return &ViewContextMap{content: map[string]types.Context{}}
|
||||
}
|
||||
|
||||
func (self *ViewContextMap) Get(viewName string) types.Context {
|
||||
self.RLock()
|
||||
defer self.RUnlock()
|
||||
|
||||
return self.content[viewName]
|
||||
}
|
||||
|
||||
func (self *ViewContextMap) Set(viewName string, context types.Context) {
|
||||
self.Lock()
|
||||
defer self.Unlock()
|
||||
self.content[viewName] = context
|
||||
}
|
||||
|
||||
func (self *ViewContextMap) Entries() map[string]types.Context {
|
||||
self.Lock()
|
||||
defer self.Unlock()
|
||||
return self.content
|
||||
}
|
||||
|
||||
type TabContext struct {
|
||||
Tab string
|
||||
Context types.Context
|
||||
type TabView struct {
|
||||
Tab string
|
||||
ViewName string
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ type ListContextTrait struct {
|
||||
|
||||
c *types.HelperCommon
|
||||
list types.IList
|
||||
viewTrait *ViewTrait
|
||||
getDisplayStrings func(startIdx int, length int) [][]string
|
||||
}
|
||||
|
||||
@@ -20,43 +19,39 @@ func (self *ListContextTrait) GetList() types.IList {
|
||||
return self.list
|
||||
}
|
||||
|
||||
func (self *ListContextTrait) GetViewTrait() types.IViewTrait {
|
||||
return self.viewTrait
|
||||
}
|
||||
|
||||
func (self *ListContextTrait) FocusLine() {
|
||||
// we need a way of knowing whether we've rendered to the view yet.
|
||||
self.viewTrait.FocusPoint(self.list.GetSelectedLineIdx())
|
||||
self.GetViewTrait().FocusPoint(self.list.GetSelectedLineIdx())
|
||||
self.setFooter()
|
||||
}
|
||||
|
||||
func (self *ListContextTrait) setFooter() {
|
||||
self.viewTrait.SetFooter(formatListFooter(self.list.GetSelectedLineIdx(), self.list.Len()))
|
||||
self.GetViewTrait().SetFooter(formatListFooter(self.list.GetSelectedLineIdx(), self.list.Len()))
|
||||
}
|
||||
|
||||
func formatListFooter(selectedLineIdx int, length int) string {
|
||||
return fmt.Sprintf("%d of %d", selectedLineIdx+1, length)
|
||||
}
|
||||
|
||||
func (self *ListContextTrait) HandleFocus(opts ...types.OnFocusOpts) error {
|
||||
func (self *ListContextTrait) HandleFocus(opts types.OnFocusOpts) error {
|
||||
self.FocusLine()
|
||||
|
||||
self.viewTrait.SetHighlight(self.list.Len() > 0)
|
||||
self.GetViewTrait().SetHighlight(self.list.Len() > 0)
|
||||
|
||||
return self.Context.HandleFocus(opts...)
|
||||
return self.Context.HandleFocus(opts)
|
||||
}
|
||||
|
||||
func (self *ListContextTrait) HandleFocusLost() error {
|
||||
self.viewTrait.SetOriginX(0)
|
||||
func (self *ListContextTrait) HandleFocusLost(opts types.OnFocusLostOpts) error {
|
||||
self.GetViewTrait().SetOriginX(0)
|
||||
|
||||
return self.Context.HandleFocusLost()
|
||||
return self.Context.HandleFocusLost(opts)
|
||||
}
|
||||
|
||||
// OnFocus assumes that the content of the context has already been rendered to the view. OnRender is the function which actually renders the content to the view
|
||||
func (self *ListContextTrait) HandleRender() error {
|
||||
self.list.RefreshSelectedIdx()
|
||||
content := utils.RenderDisplayStrings(self.getDisplayStrings(0, self.list.Len()))
|
||||
self.viewTrait.SetContent(content)
|
||||
self.GetViewTrait().SetContent(content)
|
||||
self.c.Render()
|
||||
self.setFooter()
|
||||
|
||||
@@ -65,5 +60,5 @@ func (self *ListContextTrait) HandleRender() error {
|
||||
|
||||
func (self *ListContextTrait) OnSearchSelect(selectedLineIdx int) error {
|
||||
self.GetList().SetSelectedLineIdx(selectedLineIdx)
|
||||
return self.HandleFocus()
|
||||
return self.HandleFocus(types.OnFocusOpts{})
|
||||
}
|
||||
|
||||
@@ -18,9 +18,9 @@ func NewLocalCommitsContext(
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
|
||||
onFocus func(...types.OnFocusOpts) error,
|
||||
onRenderToMain func(...types.OnFocusOpts) error,
|
||||
onFocusLost func() error,
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
|
||||
c *types.HelperCommon,
|
||||
) *LocalCommitsContext {
|
||||
@@ -31,7 +31,7 @@ func NewLocalCommitsContext(
|
||||
ViewportListContextTrait: &ViewportListContextTrait{
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
ViewName: "commits",
|
||||
View: view,
|
||||
WindowName: "commits",
|
||||
Key: LOCAL_COMMITS_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
@@ -42,7 +42,6 @@ func NewLocalCommitsContext(
|
||||
OnRenderToMain: onRenderToMain,
|
||||
}),
|
||||
list: viewModel,
|
||||
viewTrait: NewViewTrait(view),
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
},
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
@@ -25,7 +24,7 @@ func NewMenuContext(
|
||||
) *MenuContext {
|
||||
viewModel := NewMenuViewModel()
|
||||
|
||||
onFocus := func(...types.OnFocusOpts) error {
|
||||
onFocus := func(types.OnFocusOpts) error {
|
||||
selectedMenuItem := viewModel.GetSelected()
|
||||
renderToDescriptionView(selectedMenuItem.Tooltip)
|
||||
return nil
|
||||
@@ -35,17 +34,18 @@ func NewMenuContext(
|
||||
MenuViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
ViewName: "menu",
|
||||
Key: "menu",
|
||||
Kind: types.PERSISTENT_POPUP,
|
||||
OnGetOptionsMap: getOptionsMap,
|
||||
Focusable: true,
|
||||
View: view,
|
||||
WindowName: "menu",
|
||||
Key: "menu",
|
||||
Kind: types.TEMPORARY_POPUP,
|
||||
OnGetOptionsMap: getOptionsMap,
|
||||
Focusable: true,
|
||||
HasUncontrolledBounds: true,
|
||||
}), ContextCallbackOpts{
|
||||
OnFocus: onFocus,
|
||||
}),
|
||||
getDisplayStrings: viewModel.GetDisplayStrings,
|
||||
list: viewModel,
|
||||
viewTrait: NewViewTrait(view),
|
||||
c: c,
|
||||
},
|
||||
}
|
||||
@@ -87,26 +87,14 @@ func (self *MenuViewModel) GetDisplayStrings(_startIdx int, _length int) [][]str
|
||||
})
|
||||
|
||||
return slices.Map(self.menuItems, func(item *types.MenuItem) []string {
|
||||
displayStrings := getItemDisplayStrings(item)
|
||||
displayStrings := item.LabelColumns
|
||||
if showKeys {
|
||||
displayStrings = slices.Prepend(displayStrings, style.FgCyan.Sprint(keybindings.GetKeyDisplay(item.Key)))
|
||||
displayStrings = slices.Prepend(displayStrings, style.FgCyan.Sprint(keybindings.LabelFromKey(item.Key)))
|
||||
}
|
||||
return displayStrings
|
||||
})
|
||||
}
|
||||
|
||||
func getItemDisplayStrings(item *types.MenuItem) []string {
|
||||
if item.LabelColumns != nil {
|
||||
return item.LabelColumns
|
||||
}
|
||||
|
||||
styledStr := item.Label
|
||||
if item.OpensMenu {
|
||||
styledStr = presentation.OpensMenuStyle(styledStr)
|
||||
}
|
||||
return []string{styledStr}
|
||||
}
|
||||
|
||||
func (self *MenuContext) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
|
||||
basicBindings := self.ListContextTrait.GetKeybindings(opts)
|
||||
menuItemsWithKeys := slices.Filter(self.menuItems, func(item *types.MenuItem) bool {
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
)
|
||||
|
||||
type MergeConflictsContext struct {
|
||||
types.Context
|
||||
viewModel *ConflictsViewModel
|
||||
c *types.HelperCommon
|
||||
mutex *deadlock.Mutex
|
||||
}
|
||||
|
||||
type ConflictsViewModel struct {
|
||||
state *mergeconflicts.State
|
||||
|
||||
// userVerticalScrolling tells us if the user has started scrolling through the file themselves
|
||||
// in which case we won't auto-scroll to a conflict.
|
||||
userVerticalScrolling bool
|
||||
}
|
||||
|
||||
func NewMergeConflictsContext(
|
||||
view *gocui.View,
|
||||
|
||||
opts ContextCallbackOpts,
|
||||
|
||||
c *types.HelperCommon,
|
||||
getOptionsMap func() map[string]string,
|
||||
) *MergeConflictsContext {
|
||||
viewModel := &ConflictsViewModel{
|
||||
state: mergeconflicts.NewState(),
|
||||
userVerticalScrolling: false,
|
||||
}
|
||||
|
||||
return &MergeConflictsContext{
|
||||
viewModel: viewModel,
|
||||
mutex: &deadlock.Mutex{},
|
||||
Context: NewSimpleContext(
|
||||
NewBaseContext(NewBaseContextOpts{
|
||||
Kind: types.MAIN_CONTEXT,
|
||||
View: view,
|
||||
WindowName: "main",
|
||||
Key: MERGE_CONFLICTS_CONTEXT_KEY,
|
||||
OnGetOptionsMap: getOptionsMap,
|
||||
Focusable: true,
|
||||
}),
|
||||
opts,
|
||||
),
|
||||
c: c,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *MergeConflictsContext) GetState() *mergeconflicts.State {
|
||||
return self.viewModel.state
|
||||
}
|
||||
|
||||
func (self *MergeConflictsContext) SetState(state *mergeconflicts.State) {
|
||||
self.viewModel.state = state
|
||||
}
|
||||
|
||||
func (self *MergeConflictsContext) GetMutex() *deadlock.Mutex {
|
||||
return self.mutex
|
||||
}
|
||||
|
||||
func (self *MergeConflictsContext) SetUserScrolling(isScrolling bool) {
|
||||
self.viewModel.userVerticalScrolling = isScrolling
|
||||
}
|
||||
|
||||
func (self *MergeConflictsContext) IsUserScrolling() bool {
|
||||
return self.viewModel.userVerticalScrolling
|
||||
}
|
||||
|
||||
func (self *MergeConflictsContext) RenderAndFocus(isFocused bool) error {
|
||||
self.setContent(isFocused)
|
||||
self.focusSelection()
|
||||
|
||||
self.c.Render()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *MergeConflictsContext) Render(isFocused bool) error {
|
||||
self.setContent(isFocused)
|
||||
|
||||
self.c.Render()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *MergeConflictsContext) GetContentToRender(isFocused bool) string {
|
||||
if self.GetState() == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return mergeconflicts.ColoredConflictFile(self.GetState(), isFocused)
|
||||
}
|
||||
|
||||
func (self *MergeConflictsContext) setContent(isFocused bool) {
|
||||
self.GetView().SetContent(self.GetContentToRender(isFocused))
|
||||
}
|
||||
|
||||
func (self *MergeConflictsContext) focusSelection() {
|
||||
if !self.IsUserScrolling() {
|
||||
_ = self.GetView().SetOrigin(self.GetView().OriginX(), self.GetOriginY())
|
||||
}
|
||||
}
|
||||
|
||||
func (self *MergeConflictsContext) GetOriginY() int {
|
||||
view := self.GetView()
|
||||
conflictMiddle := self.GetState().GetConflictMiddle()
|
||||
return int(math.Max(0, float64(conflictMiddle-(view.Height()/2))))
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/patch_exploring"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
deadlock "github.com/sasha-s/go-deadlock"
|
||||
)
|
||||
|
||||
type PatchExplorerContext struct {
|
||||
*SimpleContext
|
||||
|
||||
state *patch_exploring.State
|
||||
viewTrait *ViewTrait
|
||||
getIncludedLineIndices func() []int
|
||||
c *types.HelperCommon
|
||||
mutex *deadlock.Mutex
|
||||
}
|
||||
|
||||
var _ types.IPatchExplorerContext = (*PatchExplorerContext)(nil)
|
||||
|
||||
func NewPatchExplorerContext(
|
||||
view *gocui.View,
|
||||
windowName string,
|
||||
key types.ContextKey,
|
||||
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
getIncludedLineIndices func() []int,
|
||||
|
||||
c *types.HelperCommon,
|
||||
) *PatchExplorerContext {
|
||||
return &PatchExplorerContext{
|
||||
state: nil,
|
||||
viewTrait: NewViewTrait(view),
|
||||
c: c,
|
||||
mutex: &deadlock.Mutex{},
|
||||
getIncludedLineIndices: getIncludedLineIndices,
|
||||
SimpleContext: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: view,
|
||||
WindowName: windowName,
|
||||
Key: key,
|
||||
Kind: types.MAIN_CONTEXT,
|
||||
Focusable: true,
|
||||
}), ContextCallbackOpts{
|
||||
OnFocus: onFocus,
|
||||
OnFocusLost: onFocusLost,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
func (self *PatchExplorerContext) GetState() *patch_exploring.State {
|
||||
return self.state
|
||||
}
|
||||
|
||||
func (self *PatchExplorerContext) SetState(state *patch_exploring.State) {
|
||||
self.state = state
|
||||
}
|
||||
|
||||
func (self *PatchExplorerContext) GetViewTrait() types.IViewTrait {
|
||||
return self.viewTrait
|
||||
}
|
||||
|
||||
func (self *PatchExplorerContext) GetIncludedLineIndices() []int {
|
||||
return self.getIncludedLineIndices()
|
||||
}
|
||||
|
||||
func (self *PatchExplorerContext) RenderAndFocus(isFocused bool) error {
|
||||
self.setContent(isFocused)
|
||||
|
||||
self.focusSelection()
|
||||
self.c.Render()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *PatchExplorerContext) Render(isFocused bool) error {
|
||||
self.setContent(isFocused)
|
||||
|
||||
self.c.Render()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *PatchExplorerContext) Focus() error {
|
||||
self.focusSelection()
|
||||
self.c.Render()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *PatchExplorerContext) setContent(isFocused bool) {
|
||||
self.GetView().SetContent(self.GetContentToRender(isFocused))
|
||||
}
|
||||
|
||||
func (self *PatchExplorerContext) focusSelection() {
|
||||
view := self.GetView()
|
||||
state := self.GetState()
|
||||
_, viewHeight := view.Size()
|
||||
bufferHeight := viewHeight - 1
|
||||
_, origin := view.Origin()
|
||||
|
||||
selectedLineIdx := state.GetSelectedLineIdx()
|
||||
|
||||
newOrigin := state.CalculateOrigin(origin, bufferHeight)
|
||||
|
||||
_ = view.SetOriginY(newOrigin)
|
||||
_ = view.SetCursor(0, selectedLineIdx-newOrigin)
|
||||
}
|
||||
|
||||
func (self *PatchExplorerContext) GetContentToRender(isFocused bool) string {
|
||||
if self.GetState() == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return self.GetState().RenderForLineIndices(isFocused, self.GetIncludedLineIndices())
|
||||
}
|
||||
|
||||
func (self *PatchExplorerContext) NavigateTo(isFocused bool, selectedLineIdx int) error {
|
||||
self.GetState().SetLineSelectMode()
|
||||
self.GetState().SelectLine(selectedLineIdx)
|
||||
|
||||
return self.RenderAndFocus(isFocused)
|
||||
}
|
||||
|
||||
func (self *PatchExplorerContext) GetMutex() *deadlock.Mutex {
|
||||
return self.mutex
|
||||
}
|
||||
@@ -18,9 +18,9 @@ func NewReflogCommitsContext(
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
|
||||
onFocus func(...types.OnFocusOpts) error,
|
||||
onRenderToMain func(...types.OnFocusOpts) error,
|
||||
onFocusLost func() error,
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
|
||||
c *types.HelperCommon,
|
||||
) *ReflogCommitsContext {
|
||||
@@ -30,7 +30,7 @@ func NewReflogCommitsContext(
|
||||
BasicViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
ViewName: "commits",
|
||||
View: view,
|
||||
WindowName: "commits",
|
||||
Key: REFLOG_COMMITS_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
@@ -41,7 +41,6 @@ func NewReflogCommitsContext(
|
||||
OnRenderToMain: onRenderToMain,
|
||||
}),
|
||||
list: viewModel,
|
||||
viewTrait: NewViewTrait(view),
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
},
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user