mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-10-05 05:26:53 -04:00
Compare commits
66
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8193731a82 | ||
|
|
1a6d062192 | ||
|
|
1ff13cdfc6 | ||
|
|
19e8cafe41 | ||
|
|
cbb5fe6007 | ||
|
|
c4fc30c243 | ||
|
|
117aa1dcc6 | ||
|
|
c550737a4f | ||
|
|
9e37ae3f5d | ||
|
|
c89ef8b84a | ||
|
|
23befdd13a | ||
|
|
58a83b0862 | ||
|
|
d5b4f7bb3e | ||
|
|
3cee4de39c | ||
|
|
f609a04671 | ||
|
|
7ffb6ffb0f | ||
|
|
3691856021 | ||
|
|
b5ca6a3add | ||
|
|
ecaa4846f1 | ||
|
|
787f9966ec | ||
|
|
013cfc77a1 | ||
|
|
16f7b01fec | ||
|
|
421c6565f9 | ||
|
|
d02deeefd8 | ||
|
|
235f5bb221 | ||
|
|
be3b4bd791 | ||
|
|
67d6447e12 | ||
|
|
fd9d7cb7bb | ||
|
|
3d6965ccbb | ||
|
|
707fa37160 | ||
|
|
7075b66bc6 | ||
|
|
9d55d71fdd | ||
|
|
cc9a20c4ab | ||
|
|
c39fafe6ec | ||
|
|
2a11725749 | ||
|
|
deed9eb18e | ||
|
|
a3759c65e4 | ||
|
|
519760077b | ||
|
|
8390622f70 | ||
|
|
a04fbafcfd | ||
|
|
4fe8dee40f | ||
|
|
6fd80565c7 | ||
|
|
3cda1d03d9 | ||
|
|
d4eb02fac0 | ||
|
|
9c72d8a2b0 | ||
|
|
4f3127ccb8 | ||
|
|
877c27722f | ||
|
|
0dda8b58d1 | ||
|
|
e997d1ae5d | ||
|
|
3e2ef84d56 | ||
|
|
b2eccd6fe8 | ||
|
|
cc25a3b8c5 | ||
|
|
e93d945dba | ||
|
|
246e08096f | ||
|
|
860a52a736 | ||
|
|
18d8b29461 | ||
|
|
7af371701d | ||
|
|
5ccc95b76f | ||
|
|
cdb1d76484 | ||
|
|
9e423e949c | ||
|
|
e1ceb6892a | ||
|
|
40b8557608 | ||
|
|
92e107f52d | ||
|
|
2f6a87df98 | ||
|
|
2c29577e3c | ||
|
|
e72559bb27 |
@@ -2,7 +2,7 @@
|
||||
|
||||
- **Please check if the PR fulfills these requirements**
|
||||
|
||||
* [ ] Cheatsheets are up-to-date (run `go run scripts/cheatsheet/main.go generate`)
|
||||
* [ ] Cheatsheets are up-to-date (run `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))
|
||||
|
||||
@@ -127,17 +127,14 @@ jobs:
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 1.20.x
|
||||
- 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)
|
||||
- name: Check Integration Test List
|
||||
# ensure our integration test list is up to date
|
||||
- name: Check All Auto-Generated Files
|
||||
# ensure all our auto-generated files are up to date
|
||||
run: |
|
||||
go generate pkg/integration/tests/tests.go && git diff --exit-code || (echo "Integration test list not up to date. Run 'go generate pkg/integration/tests/tests.go' locally and commit the changes" && exit 1)
|
||||
go generate ./... && git diff --quiet || (git status -s; echo "Auto-generated files not up to date. Run 'go generate ./...' locally and commit the changes" && exit 1)
|
||||
shell: bash # needed so that we get "-o pipefail"
|
||||
- name: Check Filenames
|
||||
run: scripts/check_filenames.sh
|
||||
|
||||
+5
-1
@@ -20,7 +20,11 @@ linters:
|
||||
linters-settings:
|
||||
exhaustive:
|
||||
default-signifies-exhaustive: true
|
||||
|
||||
staticcheck:
|
||||
# SA1019 is for checking that we're not using fields marked as deprecated
|
||||
# in a comment. It decides this in a loose way so I'm silencing it. Also because
|
||||
# it's tripping on our own structs.
|
||||
checks: ["all", "-SA1019"]
|
||||
nakedret:
|
||||
# the gods will judge me but I just don't like naked returns at all
|
||||
max-func-lines: 0
|
||||
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"gopls": {
|
||||
"formatting.gofumpt": true,
|
||||
},
|
||||
}
|
||||
@@ -21,6 +21,8 @@ welcome your pull requests:
|
||||
4. Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
|
||||
5. Issue that pull request!
|
||||
|
||||
Please do not raise pull request from your fork's master branch: make a feature branch instead. Lazygit maintainers will sometimes push changes to your branch when reviewing a PR and we often can't do this if you use your master branch.
|
||||
|
||||
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
|
||||
@@ -97,6 +99,10 @@ To run gofumpt from your terminal go:
|
||||
go install mvdan.cc/gofumpt@latest && gofumpt -l -w .
|
||||
```
|
||||
|
||||
## Programming Font
|
||||
|
||||
Lazygit supports [Nerd Fonts](https://www.nerdfonts.com) to render certain icons. Sometimes we use some of these icons verbatim in string literals in the code (mainly in tests), so you need to set your development environment to use a nerd font to see these.
|
||||
|
||||
## 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. Then you can access via `gui.Tr.YourNewText` (or `self.c.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!).
|
||||
|
||||
@@ -3,7 +3,7 @@ all: build
|
||||
|
||||
.PHONY: build
|
||||
build:
|
||||
go build
|
||||
go build -gcflags='all=-N -l'
|
||||
|
||||
.PHONY: install
|
||||
install:
|
||||
@@ -29,6 +29,7 @@ unit-test:
|
||||
.PHONY: test
|
||||
test: unit-test integration-test-all
|
||||
|
||||
# Generate all our auto-generated files (test list, cheatsheets, maybe other things in the future)
|
||||
.PHONY: generate
|
||||
generate:
|
||||
go generate ./...
|
||||
@@ -37,10 +38,6 @@ generate:
|
||||
format:
|
||||
gofumpt -l -w .
|
||||
|
||||
.PHONY: update-cheatsheet
|
||||
update-cheatsheet:
|
||||
go run scripts/cheatsheet/main.go generate
|
||||
|
||||
# For more details about integration test, see https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md.
|
||||
.PHONY: integration-test-tui
|
||||
integration-test-tui:
|
||||
|
||||
+5
-4
@@ -85,6 +85,7 @@ gui:
|
||||
skipRewordInEditorWarning: false # for skipping the confirmation before launching the reword editor
|
||||
border: 'rounded' # one of 'single' | 'double' | 'rounded' | 'hidden'
|
||||
animateExplosion: true # shows an explosion animation when nuking the working tree
|
||||
portraitMode: 'auto' # one of 'auto' | 'never' | 'always'
|
||||
git:
|
||||
paging:
|
||||
colorArg: always
|
||||
@@ -162,7 +163,7 @@ keybinding:
|
||||
jumpToBlock: ['1', '2', '3', '4', '5'] # goto the Nth block / panel
|
||||
nextMatch: 'n'
|
||||
prevMatch: 'N'
|
||||
optionMenu: null # show help menu
|
||||
optionMenu: <disabled> # show help menu
|
||||
optionMenu-alt1: '?' # show help menu
|
||||
select: '<space>'
|
||||
goInto: '<enter>'
|
||||
@@ -318,7 +319,7 @@ os:
|
||||
editPreset: 'vscode'
|
||||
```
|
||||
|
||||
Supported presets are `vim`, `nvim`, `nvim-remote`, `emacs`, `nano`, `vscode`, `sublime`, `bbedit`, `kakoune`, `helix`, and `xcode`. In many cases lazygit will be able to guess the right preset from your $(git config core.editor), or an environment variable such as $VISUAL or $EDITOR.
|
||||
Supported presets are `vim`, `nvim`, `nvim-remote`, `lvim`, `emacs`, `nano`, `micro`, `vscode`, `sublime`, `bbedit`, `kakoune`, `helix`, and `xcode`. In many cases lazygit will be able to guess the right preset from your $(git config core.editor), or an environment variable such as $VISUAL or $EDITOR.
|
||||
|
||||
`nvim-remote` is an experimental preset for when you have invoked lazygit from within a neovim process, allowing lazygit to open the file from within the parent process rather than spawning a new one.
|
||||
|
||||
@@ -461,12 +462,12 @@ Supported versions are "2" and "3". The deprecated config `showIcons` sets the v
|
||||
|
||||
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`.
|
||||
You can disable certain key bindings by specifying `<disabled>`.
|
||||
|
||||
```yaml
|
||||
keybinding:
|
||||
universal:
|
||||
edit: null # disable 'edit file'
|
||||
edit: <disabled> # disable 'edit file'
|
||||
```
|
||||
|
||||
### Example Keybindings For Colemak Users
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go run scripts/cheatsheet/main.go generate` from the project root._
|
||||
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._
|
||||
|
||||
# Lazygit Keybindings
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go run scripts/cheatsheet/main.go generate` from the project root._
|
||||
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._
|
||||
|
||||
# Lazygit キーバインド
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go run scripts/cheatsheet/main.go generate` from the project root._
|
||||
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._
|
||||
|
||||
# Lazygit 키 바인딩
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go run scripts/cheatsheet/main.go generate` from the project root._
|
||||
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._
|
||||
|
||||
# Lazygit Sneltoetsen
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go run scripts/cheatsheet/main.go generate` from the project root._
|
||||
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._
|
||||
|
||||
# Lazygit Keybindings
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go run scripts/cheatsheet/main.go generate` from the project root._
|
||||
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._
|
||||
|
||||
# Lazygit Связки клавиш
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go run scripts/cheatsheet/main.go generate` from the project root._
|
||||
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._
|
||||
|
||||
# Lazygit 按键绑定
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go run scripts/cheatsheet/main.go generate` from the project root._
|
||||
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._
|
||||
|
||||
# Lazygit 鍵盤快捷鍵
|
||||
|
||||
|
||||
@@ -180,10 +180,10 @@ func parseCliArgsAndEnvVars() *cliArgs {
|
||||
useConfigDir := ""
|
||||
flaggy.String(&useConfigDir, "ucd", "use-config-dir", "override default config directory with provided directory")
|
||||
|
||||
workTree := ""
|
||||
workTree := os.Getenv("GIT_WORK_TREE")
|
||||
flaggy.String(&workTree, "w", "work-tree", "equivalent of the --work-tree git argument")
|
||||
|
||||
gitDir := ""
|
||||
gitDir := os.Getenv("GIT_DIR")
|
||||
flaggy.String(&gitDir, "g", "git-dir", "equivalent of the --git-dir git argument")
|
||||
|
||||
customConfigFile := ""
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
package cheatsheet
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
|
||||
"github.com/pmezard/go-difflib/difflib"
|
||||
)
|
||||
|
||||
func Check() {
|
||||
dir := GetKeybindingsDir()
|
||||
tmpDir := filepath.Join(os.TempDir(), "lazygit_cheatsheet")
|
||||
err := os.RemoveAll(tmpDir)
|
||||
if err != nil {
|
||||
log.Fatalf("Error occurred while checking if cheatsheets are up to date: %v", err)
|
||||
}
|
||||
err = os.Mkdir(tmpDir, 0o700)
|
||||
if err != nil {
|
||||
log.Fatalf("Error occurred while checking if cheatsheets are up to date: %v", err)
|
||||
}
|
||||
|
||||
generateAtDir(tmpDir)
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
actualContent := obtainContent(dir)
|
||||
expectedContent := obtainContent(tmpDir)
|
||||
|
||||
if expectedContent == "" {
|
||||
log.Fatal("empty expected content")
|
||||
}
|
||||
|
||||
if actualContent != expectedContent {
|
||||
err := difflib.WriteUnifiedDiff(os.Stdout, difflib.UnifiedDiff{
|
||||
A: difflib.SplitLines(expectedContent),
|
||||
B: difflib.SplitLines(actualContent),
|
||||
FromFile: "Expected",
|
||||
FromDate: "",
|
||||
ToFile: "Actual",
|
||||
ToDate: "",
|
||||
Context: 1,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("Error occurred while checking if cheatsheets are up to date: %v", err)
|
||||
}
|
||||
fmt.Printf("\nCheatsheets are out of date. Please run `%s` at the project root and commit the changes. If you run the script and no keybindings files are updated as a result, try rebasing onto master and trying again.\n", CommandToRun())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Println("\nCheatsheets are up to date")
|
||||
}
|
||||
|
||||
func obtainContent(dir string) string {
|
||||
re := regexp.MustCompile(`Keybindings_\w+\.md$`)
|
||||
|
||||
content := ""
|
||||
err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
|
||||
if re.MatchString(path) {
|
||||
bytes, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
log.Fatalf("Error occurred while checking if cheatsheets are up to date: %v", err)
|
||||
}
|
||||
content += fmt.Sprintf("\n%s\n\n", filepath.Base(path))
|
||||
content += string(bytes)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("Error occurred while checking if cheatsheets are up to date: %v", err)
|
||||
}
|
||||
|
||||
return content
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
// This "script" generates a file called Keybindings_{{.LANG}}.md
|
||||
// in current working directory.
|
||||
//go:generate go run generator.go
|
||||
|
||||
// This "script" generates files called Keybindings_{{.LANG}}.md
|
||||
// in the docs/keybindings directory.
|
||||
//
|
||||
// The content of this generated file is a keybindings cheatsheet.
|
||||
// The content of these generated files is a keybindings cheatsheet.
|
||||
//
|
||||
// To generate cheatsheet in english run:
|
||||
// go run scripts/generate_cheatsheet.go
|
||||
// To generate the cheatsheets, run:
|
||||
// go generate pkg/cheatsheet/generate.go
|
||||
|
||||
package cheatsheet
|
||||
|
||||
@@ -42,7 +44,7 @@ type headerWithBindings struct {
|
||||
}
|
||||
|
||||
func CommandToRun() string {
|
||||
return "go run scripts/cheatsheet/main.go generate"
|
||||
return "go generate ./..."
|
||||
}
|
||||
|
||||
func GetKeybindingsDir() string {
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
//go:build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/cheatsheet"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Printf("Generating cheatsheets in %s...\n", cheatsheet.GetKeybindingsDir())
|
||||
cheatsheet.Generate()
|
||||
}
|
||||
+1
-5
@@ -7,7 +7,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
"github.com/spf13/afero"
|
||||
|
||||
gogit "github.com/jesseduffield/go-git/v5"
|
||||
@@ -63,7 +62,6 @@ func NewGitCommand(
|
||||
version *git_commands.GitVersion,
|
||||
osCommand *oscommands.OSCommand,
|
||||
gitConfig git_config.IGitConfig,
|
||||
syncMutex *deadlock.Mutex,
|
||||
) (*GitCommand, error) {
|
||||
currentPath, err := os.Getwd()
|
||||
if err != nil {
|
||||
@@ -118,7 +116,6 @@ func NewGitCommand(
|
||||
gitConfig,
|
||||
repoPaths,
|
||||
repository,
|
||||
syncMutex,
|
||||
), nil
|
||||
}
|
||||
|
||||
@@ -129,7 +126,6 @@ func NewGitCommandAux(
|
||||
gitConfig git_config.IGitConfig,
|
||||
repoPaths *git_commands.RepoPaths,
|
||||
repo *gogit.Repository,
|
||||
syncMutex *deadlock.Mutex,
|
||||
) *GitCommand {
|
||||
cmd := NewGitCmdObjBuilder(cmn.Log, osCommand.Cmd)
|
||||
|
||||
@@ -140,7 +136,7 @@ func NewGitCommandAux(
|
||||
// common ones are: cmn, osCommand, dotGitDir, configCommands
|
||||
configCommands := git_commands.NewConfigCommands(cmn, gitConfig, repo)
|
||||
|
||||
gitCommon := git_commands.NewGitCommon(cmn, version, cmd, osCommand, repoPaths, repo, configCommands, syncMutex)
|
||||
gitCommon := git_commands.NewGitCommon(cmn, version, cmd, osCommand, repoPaths, repo, configCommands)
|
||||
|
||||
fileLoader := git_commands.NewFileLoader(gitCommon, cmd, configCommands)
|
||||
statusCommands := git_commands.NewStatusCommands(gitCommon)
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
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 {
|
||||
@@ -15,8 +14,6 @@ type GitCommon struct {
|
||||
repoPaths *RepoPaths
|
||||
repo *gogit.Repository
|
||||
config *ConfigCommands
|
||||
// mutex for doing things like push/pull/fetch
|
||||
syncMutex *deadlock.Mutex
|
||||
}
|
||||
|
||||
func NewGitCommon(
|
||||
@@ -27,7 +24,6 @@ func NewGitCommon(
|
||||
repoPaths *RepoPaths,
|
||||
repo *gogit.Repository,
|
||||
config *ConfigCommands,
|
||||
syncMutex *deadlock.Mutex,
|
||||
) *GitCommon {
|
||||
return &GitCommon{
|
||||
Common: cmn,
|
||||
@@ -37,6 +33,5 @@ func NewGitCommon(
|
||||
repoPaths: repoPaths,
|
||||
repo: repo,
|
||||
config: config,
|
||||
syncMutex: syncMutex,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ func (self *RemoteCommands) DeleteRemoteBranch(task gocui.Task, remoteName strin
|
||||
Arg(remoteName, "--delete", branchName).
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
|
||||
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run()
|
||||
}
|
||||
|
||||
func (self *RemoteCommands) DeleteRemoteTag(task gocui.Task, remoteName string, tagName string) error {
|
||||
@@ -61,7 +61,7 @@ func (self *RemoteCommands) DeleteRemoteTag(task gocui.Task, remoteName string,
|
||||
Arg(remoteName, "--delete", tagName).
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
|
||||
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run()
|
||||
}
|
||||
|
||||
// CheckRemoteBranchExists Returns remote branch
|
||||
|
||||
@@ -36,7 +36,7 @@ func (self *SyncCommands) PushCmdObj(task gocui.Task, opts PushOpts) (oscommands
|
||||
ArgIf(opts.UpstreamBranch != "", opts.UpstreamBranch).
|
||||
ToArgv()
|
||||
|
||||
cmdObj := self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex)
|
||||
cmdObj := self.cmd.New(cmdArgs).PromptOnCredentialRequest(task)
|
||||
return cmdObj, nil
|
||||
}
|
||||
|
||||
@@ -70,7 +70,6 @@ func (self *SyncCommands) FetchBackgroundCmdObj() oscommands.ICmdObj {
|
||||
|
||||
cmdObj := self.cmd.New(cmdArgs)
|
||||
cmdObj.DontLog().FailOnCredentialRequest()
|
||||
cmdObj.WithMutex(self.syncMutex)
|
||||
return cmdObj
|
||||
}
|
||||
|
||||
@@ -96,7 +95,7 @@ func (self *SyncCommands) Pull(task gocui.Task, opts PullOptions) error {
|
||||
|
||||
// setting GIT_SEQUENCE_EDITOR to ':' as a way of skipping it, in case the user
|
||||
// has 'pull.rebase = interactive' configured.
|
||||
return self.cmd.New(cmdArgs).AddEnvVars("GIT_SEQUENCE_EDITOR=:").PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
|
||||
return self.cmd.New(cmdArgs).AddEnvVars("GIT_SEQUENCE_EDITOR=:").PromptOnCredentialRequest(task).Run()
|
||||
}
|
||||
|
||||
func (self *SyncCommands) FastForward(
|
||||
@@ -110,7 +109,7 @@ func (self *SyncCommands) FastForward(
|
||||
Arg(remoteBranchName + ":" + branchName).
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
|
||||
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run()
|
||||
}
|
||||
|
||||
func (self *SyncCommands) FetchRemote(task gocui.Task, remoteName string) error {
|
||||
@@ -118,5 +117,5 @@ func (self *SyncCommands) FetchRemote(task gocui.Task, remoteName string) error
|
||||
Arg(remoteName).
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
|
||||
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run()
|
||||
}
|
||||
|
||||
@@ -52,5 +52,5 @@ func (self *TagCommands) Push(task gocui.Task, remoteName string, tagName string
|
||||
cmdArgs := NewGitCmd("push").Arg(remoteName, "tag", tagName).
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
|
||||
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run()
|
||||
}
|
||||
|
||||
@@ -99,32 +99,30 @@ func (self *HostingServiceMgr) getCandidateServiceDomains() []ServiceDomain {
|
||||
|
||||
serviceDomains := slices.Clone(defaultServiceDomains)
|
||||
|
||||
if len(self.configServiceDomains) > 0 {
|
||||
for gitDomain, typeAndDomain := range self.configServiceDomains {
|
||||
provider, webDomain, success := strings.Cut(typeAndDomain, ":")
|
||||
for gitDomain, typeAndDomain := range self.configServiceDomains {
|
||||
provider, webDomain, success := strings.Cut(typeAndDomain, ":")
|
||||
|
||||
// we allow for one ':' for specifying the TCP port
|
||||
if !success || strings.Count(webDomain, ":") > 1 {
|
||||
self.log.Errorf("Unexpected format for git service: '%s'. Expected something like 'github.com:github.com'", typeAndDomain)
|
||||
continue
|
||||
}
|
||||
|
||||
serviceDefinition, ok := serviceDefinitionByProvider[provider]
|
||||
if !ok {
|
||||
providerNames := lo.Map(serviceDefinitions, func(serviceDefinition ServiceDefinition, _ int) string {
|
||||
return serviceDefinition.provider
|
||||
})
|
||||
|
||||
self.log.Errorf("Unknown git service type: '%s'. Expected one of %s", provider, strings.Join(providerNames, ", "))
|
||||
continue
|
||||
}
|
||||
|
||||
serviceDomains = append(serviceDomains, ServiceDomain{
|
||||
gitDomain: gitDomain,
|
||||
webDomain: webDomain,
|
||||
serviceDefinition: serviceDefinition,
|
||||
})
|
||||
// we allow for one ':' for specifying the TCP port
|
||||
if !success || strings.Count(webDomain, ":") > 1 {
|
||||
self.log.Errorf("Unexpected format for git service: '%s'. Expected something like 'github.com:github.com'", typeAndDomain)
|
||||
continue
|
||||
}
|
||||
|
||||
serviceDefinition, ok := serviceDefinitionByProvider[provider]
|
||||
if !ok {
|
||||
providerNames := lo.Map(serviceDefinitions, func(serviceDefinition ServiceDefinition, _ int) string {
|
||||
return serviceDefinition.provider
|
||||
})
|
||||
|
||||
self.log.Errorf("Unknown git service type: '%s'. Expected one of %s", provider, strings.Join(providerNames, ", "))
|
||||
continue
|
||||
}
|
||||
|
||||
serviceDomains = append(serviceDomains, ServiceDomain{
|
||||
gitDomain: gitDomain,
|
||||
webDomain: webDomain,
|
||||
serviceDefinition: serviceDefinition,
|
||||
})
|
||||
}
|
||||
|
||||
return serviceDomains
|
||||
|
||||
@@ -65,6 +65,10 @@ func (b *Branch) ID() string {
|
||||
return b.RefName()
|
||||
}
|
||||
|
||||
func (b *Branch) URN() string {
|
||||
return "branch-" + b.ID()
|
||||
}
|
||||
|
||||
func (b *Branch) Description() string {
|
||||
return b.RefName()
|
||||
}
|
||||
|
||||
@@ -24,6 +24,10 @@ func (t *Tag) ID() string {
|
||||
return t.RefName()
|
||||
}
|
||||
|
||||
func (t *Tag) URN() string {
|
||||
return "tag-" + t.ID()
|
||||
}
|
||||
|
||||
func (t *Tag) Description() string {
|
||||
return t.Message
|
||||
}
|
||||
|
||||
@@ -59,7 +59,9 @@ func getPreset(osConfig *OSConfig, guessDefaultEditor func() string) *editPreset
|
||||
openDirInEditorTemplate: `nvim --server "$NVIM" --remote-tab {{dir}}`,
|
||||
suspend: false,
|
||||
},
|
||||
"lvim": standardTerminalEditorPreset("lvim"),
|
||||
"emacs": standardTerminalEditorPreset("emacs"),
|
||||
"micro": standardTerminalEditorPreset("micro"),
|
||||
"nano": standardTerminalEditorPreset("nano"),
|
||||
"kakoune": standardTerminalEditorPreset("kak"),
|
||||
"helix": {
|
||||
|
||||
+265
-110
@@ -5,131 +5,255 @@ import (
|
||||
)
|
||||
|
||||
type UserConfig struct {
|
||||
Gui GuiConfig `yaml:"gui"`
|
||||
Git GitConfig `yaml:"git"`
|
||||
Update UpdateConfig `yaml:"update"`
|
||||
Refresher RefresherConfig `yaml:"refresher"`
|
||||
ConfirmOnQuit bool `yaml:"confirmOnQuit"`
|
||||
QuitOnTopLevelReturn bool `yaml:"quitOnTopLevelReturn"`
|
||||
Keybinding KeybindingConfig `yaml:"keybinding"`
|
||||
// OS determines what defaults are set for opening files and links
|
||||
OS OSConfig `yaml:"os,omitempty"`
|
||||
DisableStartupPopups bool `yaml:"disableStartupPopups"`
|
||||
CustomCommands []CustomCommand `yaml:"customCommands"`
|
||||
Services map[string]string `yaml:"services"`
|
||||
NotARepository string `yaml:"notARepository"`
|
||||
PromptToReturnFromSubprocess bool `yaml:"promptToReturnFromSubprocess"`
|
||||
// Config relating to the Lazygit UI
|
||||
Gui GuiConfig `yaml:"gui"`
|
||||
// Config relating to git
|
||||
Git GitConfig `yaml:"git"`
|
||||
// Periodic update checks
|
||||
Update UpdateConfig `yaml:"update"`
|
||||
// Background refreshes
|
||||
Refresher RefresherConfig `yaml:"refresher"`
|
||||
// If true, show a confirmation popup before quitting Lazygit
|
||||
ConfirmOnQuit bool `yaml:"confirmOnQuit"`
|
||||
// If true, exit Lazygit when the user presses escape in a context where there is nothing to cancel/close
|
||||
QuitOnTopLevelReturn bool `yaml:"quitOnTopLevelReturn"`
|
||||
// Keybindings
|
||||
Keybinding KeybindingConfig `yaml:"keybinding"`
|
||||
// Config relating to things outside of Lazygit like how files are opened, copying to clipboard, etc
|
||||
OS OSConfig `yaml:"os,omitempty"`
|
||||
// If true, don't display introductory popups upon opening Lazygit.
|
||||
// Lazygit sets this to true upon first runninng the program so that you don't see introductory popups every time you open the program.
|
||||
DisableStartupPopups bool `yaml:"disableStartupPopups"`
|
||||
// User-configured commands that can be invoked from within Lazygit
|
||||
CustomCommands []CustomCommand `yaml:"customCommands"`
|
||||
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-pull-request-urls
|
||||
Services map[string]string `yaml:"services"`
|
||||
// What to do when opening Lazygit outside of a git repo.
|
||||
// - 'prompt': (default) ask whether to initialize a new repo or open in the most recent repo
|
||||
// - 'create': initialize a new repo
|
||||
// - 'skip': open most recent repo
|
||||
// - 'quit': exit Lazygit
|
||||
NotARepository string `yaml:"notARepository"`
|
||||
// If true, display a confirmation when subprocess terminates. This allows you to view the output of the subprocess before returning to Lazygit.
|
||||
PromptToReturnFromSubprocess bool `yaml:"promptToReturnFromSubprocess"`
|
||||
}
|
||||
|
||||
type RefresherConfig struct {
|
||||
// File/submodule refresh interval in seconds.
|
||||
// Auto-refresh can be disabled via option 'git.autoRefresh'.
|
||||
RefreshInterval int `yaml:"refreshInterval"`
|
||||
FetchInterval int `yaml:"fetchInterval"`
|
||||
// Re-fetch interval in seconds.
|
||||
// Auto-fetch can be disabled via option 'git.autoFetch'.
|
||||
FetchInterval int `yaml:"fetchInterval"`
|
||||
}
|
||||
|
||||
type GuiConfig struct {
|
||||
AuthorColors map[string]string `yaml:"authorColors"`
|
||||
BranchColors map[string]string `yaml:"branchColors"`
|
||||
ScrollHeight int `yaml:"scrollHeight"`
|
||||
ScrollPastBottom bool `yaml:"scrollPastBottom"`
|
||||
ScrollOffMargin int `yaml:"scrollOffMargin"`
|
||||
ScrollOffBehavior string `yaml:"scrollOffBehavior"`
|
||||
MouseEvents bool `yaml:"mouseEvents"`
|
||||
SkipDiscardChangeWarning bool `yaml:"skipDiscardChangeWarning"`
|
||||
SkipStashWarning bool `yaml:"skipStashWarning"`
|
||||
SidePanelWidth float64 `yaml:"sidePanelWidth"`
|
||||
ExpandFocusedSidePanel bool `yaml:"expandFocusedSidePanel"`
|
||||
MainPanelSplitMode string `yaml:"mainPanelSplitMode"`
|
||||
Language string `yaml:"language"`
|
||||
TimeFormat string `yaml:"timeFormat"`
|
||||
ShortTimeFormat string `yaml:"shortTimeFormat"`
|
||||
Theme ThemeConfig `yaml:"theme"`
|
||||
CommitLength CommitLengthConfig `yaml:"commitLength"`
|
||||
SkipNoStagedFilesWarning bool `yaml:"skipNoStagedFilesWarning"`
|
||||
ShowListFooter bool `yaml:"showListFooter"`
|
||||
ShowFileTree bool `yaml:"showFileTree"`
|
||||
ShowRandomTip bool `yaml:"showRandomTip"`
|
||||
ShowCommandLog bool `yaml:"showCommandLog"`
|
||||
ShowBottomLine bool `yaml:"showBottomLine"`
|
||||
ShowPanelJumps bool `yaml:"showPanelJumps"`
|
||||
ShowIcons bool `yaml:"showIcons"`
|
||||
NerdFontsVersion string `yaml:"nerdFontsVersion"`
|
||||
ShowBranchCommitHash bool `yaml:"showBranchCommitHash"`
|
||||
CommandLogSize int `yaml:"commandLogSize"`
|
||||
SplitDiff string `yaml:"splitDiff"`
|
||||
SkipRewordInEditorWarning bool `yaml:"skipRewordInEditorWarning"`
|
||||
WindowSize string `yaml:"windowSize"`
|
||||
Border string `yaml:"border"`
|
||||
AnimateExplosion bool `yaml:"animateExplosion"`
|
||||
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-author-color
|
||||
AuthorColors map[string]string `yaml:"authorColors"`
|
||||
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-branch-color
|
||||
BranchColors map[string]string `yaml:"branchColors"`
|
||||
// The number of lines you scroll by when scrolling the main window
|
||||
ScrollHeight int `yaml:"scrollHeight"`
|
||||
// If true, allow scrolling past the bottom of the content in the main window
|
||||
ScrollPastBottom bool `yaml:"scrollPastBottom"`
|
||||
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#scroll-off-margin
|
||||
ScrollOffMargin int `yaml:"scrollOffMargin"`
|
||||
// One of: 'margin' (default) | 'jump'
|
||||
ScrollOffBehavior string `yaml:"scrollOffBehavior"`
|
||||
// If true, capture mouse events.
|
||||
// When mouse events are captured, it's a little harder to select text: e.g. requiring you to hold the option key when on macOS.
|
||||
MouseEvents bool `yaml:"mouseEvents"`
|
||||
// If true, do not show a warning when discarding changes in the staging view.
|
||||
SkipDiscardChangeWarning bool `yaml:"skipDiscardChangeWarning"`
|
||||
// If true, do not show warning when applying/popping the stash
|
||||
SkipStashWarning bool `yaml:"skipStashWarning"`
|
||||
// If true, do not show a warning when attempting to commit without any staged files; instead stage all unstaged files.
|
||||
SkipNoStagedFilesWarning bool `yaml:"skipNoStagedFilesWarning"`
|
||||
// If true, do not show a warning when rewording a commit via an external editor
|
||||
SkipRewordInEditorWarning bool `yaml:"skipRewordInEditorWarning"`
|
||||
// Fraction of the total screen width to use for the left side section. You may want to pick a small number (e.g. 0.2) if you're using a narrow screen, so that you can see more of the main section.
|
||||
// Number from 0 to 1.0.
|
||||
SidePanelWidth float64 `yaml:"sidePanelWidth"`
|
||||
// If true, increase the height of the focused side window; creating an accordion effect.
|
||||
ExpandFocusedSidePanel bool `yaml:"expandFocusedSidePanel"`
|
||||
// Sometimes the main window is split in two (e.g. when the selected file has both staged and unstaged changes). This setting controls how the two sections are split.
|
||||
// Options are:
|
||||
// - 'horizontal': split the window horizontally
|
||||
// - 'vertical': split the window vertically
|
||||
// - 'flexible': (default) split the window horizontally if the window is wide enough, otherwise split vertically
|
||||
MainPanelSplitMode string `yaml:"mainPanelSplitMode"`
|
||||
// One of 'auto' (default) | 'en' | 'zh-CN' | 'zh-TW' | 'pl' | 'nl' | 'ja' | 'ko' | 'ru'
|
||||
Language string `yaml:"language"`
|
||||
// Format used when displaying time e.g. commit time.
|
||||
// Uses Go's time format syntax: https://pkg.go.dev/time#Time.Format
|
||||
TimeFormat string `yaml:"timeFormat"`
|
||||
// Format used when displaying time if the time is less than 24 hours ago.
|
||||
// Uses Go's time format syntax: https://pkg.go.dev/time#Time.Format
|
||||
ShortTimeFormat string `yaml:"shortTimeFormat"`
|
||||
// Config relating to colors and styles.
|
||||
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#color-attributes
|
||||
Theme ThemeConfig `yaml:"theme"`
|
||||
// Config relating to the commit length indicator
|
||||
CommitLength CommitLengthConfig `yaml:"commitLength"`
|
||||
// If true, show the '5 of 20' footer at the bottom of list views
|
||||
ShowListFooter bool `yaml:"showListFooter"`
|
||||
// If true, display the files in the file views as a tree. If false, display the files as a flat list.
|
||||
// This can be toggled from within Lazygit with the '~' key, but that will not change the default.
|
||||
ShowFileTree bool `yaml:"showFileTree"`
|
||||
// If true, show a random tip in the command log when Lazygit starts
|
||||
ShowRandomTip bool `yaml:"showRandomTip"`
|
||||
// If true, show the command log
|
||||
ShowCommandLog bool `yaml:"showCommandLog"`
|
||||
// If true, show the bottom line that contains keybinding info and useful buttons. If false, this line will be hidden except to display a loader for an in-progress action.
|
||||
ShowBottomLine bool `yaml:"showBottomLine"`
|
||||
// If true, show jump-to-window keybindings in window titles.
|
||||
ShowPanelJumps bool `yaml:"showPanelJumps"`
|
||||
// Deprecated: use nerdFontsVersion instead
|
||||
ShowIcons bool `yaml:"showIcons"`
|
||||
// Nerd fonts version to use.
|
||||
// One of: '2' | '3' | empty string (default)
|
||||
// If empty, do not show icons.
|
||||
NerdFontsVersion string `yaml:"nerdFontsVersion"`
|
||||
// If true, show commit hashes alongside branch names in the branches view.
|
||||
ShowBranchCommitHash bool `yaml:"showBranchCommitHash"`
|
||||
// Height of the command log view
|
||||
CommandLogSize int `yaml:"commandLogSize"`
|
||||
// Whether to split the main window when viewing file changes.
|
||||
// One of: 'auto' | 'always'
|
||||
// If 'auto', only split the main window when a file has both staged and unstaged changes
|
||||
SplitDiff string `yaml:"splitDiff"`
|
||||
// Default size for focused window. Window size can be changed from within Lazygit with '+' and '_' (but this won't change the default).
|
||||
// One of: 'normal' (default) | 'half' | 'full'
|
||||
WindowSize string `yaml:"windowSize"`
|
||||
// Window border style.
|
||||
// One of 'rounded' (default) | 'single' | 'double' | 'hidden'
|
||||
Border string `yaml:"border"`
|
||||
// If true, show a seriously epic explosion animation when nuking the working tree.
|
||||
AnimateExplosion bool `yaml:"animateExplosion"`
|
||||
// Whether to stack UI components on top of each other.
|
||||
// One of 'auto' (default) | 'always' | 'never'
|
||||
PortraitMode string `yaml:"portraitMode"`
|
||||
}
|
||||
|
||||
type ThemeConfig struct {
|
||||
ActiveBorderColor []string `yaml:"activeBorderColor"`
|
||||
InactiveBorderColor []string `yaml:"inactiveBorderColor"`
|
||||
// Border color of focused window
|
||||
ActiveBorderColor []string `yaml:"activeBorderColor"`
|
||||
// Border color of non-focused windows
|
||||
InactiveBorderColor []string `yaml:"inactiveBorderColor"`
|
||||
// Border color of focused window when searching in that window
|
||||
SearchingActiveBorderColor []string `yaml:"searchingActiveBorderColor"`
|
||||
OptionsTextColor []string `yaml:"optionsTextColor"`
|
||||
SelectedLineBgColor []string `yaml:"selectedLineBgColor"`
|
||||
SelectedRangeBgColor []string `yaml:"selectedRangeBgColor"`
|
||||
CherryPickedCommitBgColor []string `yaml:"cherryPickedCommitBgColor"`
|
||||
CherryPickedCommitFgColor []string `yaml:"cherryPickedCommitFgColor"`
|
||||
MarkedBaseCommitBgColor []string `yaml:"markedBaseCommitBgColor"`
|
||||
MarkedBaseCommitFgColor []string `yaml:"markedBaseCommitFgColor"`
|
||||
UnstagedChangesColor []string `yaml:"unstagedChangesColor"`
|
||||
DefaultFgColor []string `yaml:"defaultFgColor"`
|
||||
// Color of keybindings help text in the bottom line
|
||||
OptionsTextColor []string `yaml:"optionsTextColor"`
|
||||
// Background color of selected line.
|
||||
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#highlighting-the-selected-line
|
||||
SelectedLineBgColor []string `yaml:"selectedLineBgColor"`
|
||||
// Background color of selected range
|
||||
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#highlighting-the-selected-line
|
||||
SelectedRangeBgColor []string `yaml:"selectedRangeBgColor"`
|
||||
// Foreground color of copied commit
|
||||
CherryPickedCommitFgColor []string `yaml:"cherryPickedCommitFgColor"`
|
||||
// Background color of copied commit
|
||||
CherryPickedCommitBgColor []string `yaml:"cherryPickedCommitBgColor"`
|
||||
// Foreground color of marked base commit (for rebase)
|
||||
MarkedBaseCommitFgColor []string `yaml:"markedBaseCommitFgColor"`
|
||||
// Background color of marked base commit (for rebase)
|
||||
MarkedBaseCommitBgColor []string `yaml:"markedBaseCommitBgColor"`
|
||||
// Color for file with unstaged changes
|
||||
UnstagedChangesColor []string `yaml:"unstagedChangesColor"`
|
||||
// Default text color
|
||||
DefaultFgColor []string `yaml:"defaultFgColor"`
|
||||
}
|
||||
|
||||
type CommitLengthConfig struct {
|
||||
// If true, show an indicator of commit message length
|
||||
Show bool `yaml:"show"`
|
||||
}
|
||||
|
||||
type GitConfig struct {
|
||||
Paging PagingConfig `yaml:"paging"`
|
||||
Commit CommitConfig `yaml:"commit"`
|
||||
Merging MergingConfig `yaml:"merging"`
|
||||
MainBranches []string `yaml:"mainBranches"`
|
||||
SkipHookPrefix string `yaml:"skipHookPrefix"`
|
||||
AutoFetch bool `yaml:"autoFetch"`
|
||||
AutoRefresh bool `yaml:"autoRefresh"`
|
||||
FetchAll bool `yaml:"fetchAll"`
|
||||
BranchLogCmd string `yaml:"branchLogCmd"`
|
||||
AllBranchesLogCmd string `yaml:"allBranchesLogCmd"`
|
||||
OverrideGpg bool `yaml:"overrideGpg"`
|
||||
DisableForcePushing bool `yaml:"disableForcePushing"`
|
||||
CommitPrefixes map[string]CommitPrefixConfig `yaml:"commitPrefixes"`
|
||||
// this should really be under 'gui', not 'git'
|
||||
ParseEmoji bool `yaml:"parseEmoji"`
|
||||
Log LogConfig `yaml:"log"`
|
||||
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Custom_Pagers.md
|
||||
Paging PagingConfig `yaml:"paging"`
|
||||
// Config relating to committing
|
||||
Commit CommitConfig `yaml:"commit"`
|
||||
// Config relating to merging
|
||||
Merging MergingConfig `yaml:"merging"`
|
||||
// list of branches that are considered 'main' branches, used when displaying commits
|
||||
MainBranches []string `yaml:"mainBranches"`
|
||||
// Prefix to use when skipping hooks. E.g. if set to 'WIP', then pre-commit hooks will be skipped when the commit message starts with 'WIP'
|
||||
SkipHookPrefix string `yaml:"skipHookPrefix"`
|
||||
// If true, periodically fetch from remote
|
||||
AutoFetch bool `yaml:"autoFetch"`
|
||||
// If true, periodically refresh files and submodules
|
||||
AutoRefresh bool `yaml:"autoRefresh"`
|
||||
// If true, pass the --all arg to git fetch
|
||||
FetchAll bool `yaml:"fetchAll"`
|
||||
// Command used when displaying the current branch git log in the main window
|
||||
BranchLogCmd string `yaml:"branchLogCmd"`
|
||||
// Command used to display git log of all branches in the main window
|
||||
AllBranchesLogCmd string `yaml:"allBranchesLogCmd"`
|
||||
// If true, do not spawn a separate process when using GPG
|
||||
OverrideGpg bool `yaml:"overrideGpg"`
|
||||
// If true, do not allow force pushes
|
||||
DisableForcePushing bool `yaml:"disableForcePushing"`
|
||||
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#predefined-commit-message-prefix
|
||||
CommitPrefixes map[string]CommitPrefixConfig `yaml:"commitPrefixes"`
|
||||
// If true, parse emoji strings in commit messages e.g. render :rocket: as 🚀
|
||||
// (This should really be under 'gui', not 'git')
|
||||
ParseEmoji bool `yaml:"parseEmoji"`
|
||||
// Config for showing the log in the commits view
|
||||
Log LogConfig `yaml:"log"`
|
||||
}
|
||||
|
||||
type PagingConfig struct {
|
||||
ColorArg string `yaml:"colorArg"`
|
||||
Pager string `yaml:"pager"`
|
||||
UseConfig bool `yaml:"useConfig"`
|
||||
// Value of the --color arg in the git diff command. Some pagers want this to be set to 'always' and some want it set to 'never'
|
||||
ColorArg string `yaml:"colorArg"`
|
||||
// e.g.
|
||||
// diff-so-fancy
|
||||
// delta --dark --paging=never
|
||||
// ydiff -p cat -s --wrap --width={{columnWidth}}
|
||||
Pager string `yaml:"pager"`
|
||||
// If true, Lazygit will use whatever pager is specified in `$GIT_PAGER`, `$PAGER`, or your *git config*. If the pager ends with something like ` | less` we will strip that part out, because less doesn't play nice with our rendering approach. If the custom pager uses less under the hood, that will also break rendering (hence the `--paging=never` flag for the `delta` pager).
|
||||
UseConfig bool `yaml:"useConfig"`
|
||||
// e.g. 'difft --color=always'
|
||||
ExternalDiffCommand string `yaml:"externalDiffCommand"`
|
||||
}
|
||||
|
||||
type CommitConfig struct {
|
||||
// If true, pass '--signoff' flag when committing
|
||||
SignOff bool `yaml:"signOff"`
|
||||
}
|
||||
|
||||
type MergingConfig struct {
|
||||
ManualCommit bool `yaml:"manualCommit"`
|
||||
Args string `yaml:"args"`
|
||||
// If true, run merges in a subprocess so that if a commit message is required, Lazygit will not hang
|
||||
// Only applicable to unix users.
|
||||
ManualCommit bool `yaml:"manualCommit"`
|
||||
// Extra args passed to `git merge`, e.g. --no-ff
|
||||
Args string `yaml:"args"`
|
||||
}
|
||||
|
||||
type LogConfig struct {
|
||||
Order string `yaml:"order"` // one of date-order, author-date-order, topo-order
|
||||
ShowGraph string `yaml:"showGraph"` // one of always, never, when-maximised
|
||||
ShowWholeGraph bool `yaml:"showWholeGraph"`
|
||||
// One of: 'date-order' | 'author-date-order' | 'topo-order'
|
||||
// 'topo-order' makes it easier to read the git log graph, but commits may not
|
||||
// appear chronologically. See https://git-scm.com/docs/
|
||||
Order string `yaml:"order"`
|
||||
// This determines whether the git graph is rendered in the commits panel
|
||||
// One of 'always' | 'never' 'when-maximised'
|
||||
ShowGraph string `yaml:"showGraph"`
|
||||
// displays the whole git graph by default in the commits view (equivalent to passing the `--all` argument to `git log`)
|
||||
ShowWholeGraph bool `yaml:"showWholeGraph"`
|
||||
}
|
||||
|
||||
type CommitPrefixConfig struct {
|
||||
// pattern to match on. E.g. for 'feature/AB-123' to match on the AB-123 use "^\\w+\\/(\\w+-\\w+).*"
|
||||
Pattern string `yaml:"pattern"`
|
||||
// Replace directive. E.g. for 'feature/AB-123' to start the commit message with 'AB-123 ' use "[$1] "
|
||||
Replace string `yaml:"replace"`
|
||||
}
|
||||
|
||||
type UpdateConfig struct {
|
||||
// One of: 'prompt' (default) | 'background' | 'never'
|
||||
Method string `yaml:"method"`
|
||||
Days int64 `yaml:"days"`
|
||||
// Period in days between update checks
|
||||
Days int64 `yaml:"days"`
|
||||
}
|
||||
|
||||
type KeybindingConfig struct {
|
||||
@@ -367,7 +491,8 @@ type OSConfig struct {
|
||||
// Deprecated: use OpenLink instead.
|
||||
OpenLinkCommand string `yaml:"openLinkCommand,omitempty"`
|
||||
|
||||
// CopyToClipboardCmd is the command for copying to clipboard
|
||||
// CopyToClipboardCmd is the command for copying to clipboard.
|
||||
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-command-for-copying-to-clipboard
|
||||
CopyToClipboardCmd string `yaml:"copyToClipboardCmd,omitempty"`
|
||||
}
|
||||
|
||||
@@ -376,50 +501,79 @@ type CustomCommandAfterHook struct {
|
||||
}
|
||||
|
||||
type CustomCommand struct {
|
||||
Key string `yaml:"key"`
|
||||
Context string `yaml:"context"`
|
||||
Command string `yaml:"command"`
|
||||
Subprocess bool `yaml:"subprocess"`
|
||||
Prompts []CustomCommandPrompt `yaml:"prompts"`
|
||||
LoadingText string `yaml:"loadingText"`
|
||||
Description string `yaml:"description"`
|
||||
Stream bool `yaml:"stream"`
|
||||
ShowOutput bool `yaml:"showOutput"`
|
||||
After CustomCommandAfterHook `yaml:"after"`
|
||||
// The key to trigger the command. Use a single letter or one of the values from https://github.com/jesseduffield/lazygit/blob/master/docs/keybindings/Custom_Keybindings.md
|
||||
Key string `yaml:"key"`
|
||||
// The context in which to listen for the key
|
||||
Context string `yaml:"context"`
|
||||
// The command to run (using Go template syntax for placeholder values)
|
||||
Command string `yaml:"command"`
|
||||
// If true, run the command in a subprocess (e.g. if the command requires user input)
|
||||
Subprocess bool `yaml:"subprocess"`
|
||||
// A list of prompts that will request user input before running the final command
|
||||
Prompts []CustomCommandPrompt `yaml:"prompts"`
|
||||
// Text to display while waiting for command to finish
|
||||
LoadingText string `yaml:"loadingText"`
|
||||
// Label for the custom command when displayed in the keybindings menu
|
||||
Description string `yaml:"description"`
|
||||
// If true, stream the command's output to the Command Log panel
|
||||
Stream bool `yaml:"stream"`
|
||||
// If true, show the command's output in a popup within Lazygit
|
||||
ShowOutput bool `yaml:"showOutput"`
|
||||
// Actions to take after the command has completed
|
||||
After CustomCommandAfterHook `yaml:"after"`
|
||||
}
|
||||
|
||||
type CustomCommandPrompt struct {
|
||||
// one of 'input', 'menu', 'confirm', or 'menuFromCommand'
|
||||
Type string `yaml:"type"`
|
||||
Key string `yaml:"key"`
|
||||
// One of: 'input' | 'menu' | 'confirm' | 'menuFromCommand'
|
||||
Type string `yaml:"type"`
|
||||
// Used to reference the entered value from within the custom command. E.g. a prompt with `key: 'Branch'` can be referred to as `{{.Form.Branch}}` in the command
|
||||
Key string `yaml:"key"`
|
||||
// The title to display in the popup panel
|
||||
Title string `yaml:"title"`
|
||||
|
||||
// these only apply to input prompts
|
||||
InitialValue string `yaml:"initialValue"`
|
||||
Suggestions CustomCommandSuggestions `yaml:"suggestions"`
|
||||
// The initial value to appear in the text box.
|
||||
// Only for input prompts.
|
||||
InitialValue string `yaml:"initialValue"`
|
||||
// Shows suggestions as the input is entered
|
||||
// Only for input prompts.
|
||||
Suggestions CustomCommandSuggestions `yaml:"suggestions"`
|
||||
|
||||
// this only applies to confirm prompts
|
||||
// The message of the confirmation prompt.
|
||||
// Only for confirm prompts.
|
||||
Body string `yaml:"body"`
|
||||
|
||||
// this only applies to menus
|
||||
// Menu options.
|
||||
// Only for menu prompts.
|
||||
Options []CustomCommandMenuOption
|
||||
|
||||
// this only applies to menuFromCommand
|
||||
Command string `yaml:"command"`
|
||||
Filter string `yaml:"filter"`
|
||||
// The command to run to generate menu options
|
||||
// Only for menuFromCommand prompts.
|
||||
Command string `yaml:"command"`
|
||||
// The regexp to run specifying groups which are going to be kept from the command's output.
|
||||
// Only for menuFromCommand prompts.
|
||||
Filter string `yaml:"filter"`
|
||||
// How to format matched groups from the filter to construct a menu item's value.
|
||||
// Only for menuFromCommand prompts.
|
||||
ValueFormat string `yaml:"valueFormat"`
|
||||
// Like valueFormat but for the labels. If `labelFormat` is not specified, `valueFormat` is shown instead.
|
||||
// Only for menuFromCommand prompts.
|
||||
LabelFormat string `yaml:"labelFormat"`
|
||||
}
|
||||
|
||||
type CustomCommandSuggestions struct {
|
||||
Preset string `yaml:"preset"`
|
||||
// Uses built-in logic to obtain the suggestions. One of 'authors' | 'branches' | 'files' | 'refs' | 'remotes' | 'remoteBranches' | 'tags'
|
||||
Preset string `yaml:"preset"`
|
||||
// Command to run such that each line in the output becomes a suggestion. Mutually exclusive with 'preset' field.
|
||||
Command string `yaml:"command"`
|
||||
}
|
||||
|
||||
type CustomCommandMenuOption struct {
|
||||
Name string `yaml:"name"`
|
||||
// The first part of the label
|
||||
Name string `yaml:"name"`
|
||||
// The second part of the label
|
||||
Description string `yaml:"description"`
|
||||
Value string `yaml:"value"`
|
||||
// The value that will be used in the command
|
||||
Value string `yaml:"value"`
|
||||
}
|
||||
|
||||
func GetDefaultConfig() *UserConfig {
|
||||
@@ -468,6 +622,7 @@ func GetDefaultConfig() *UserConfig {
|
||||
SkipRewordInEditorWarning: false,
|
||||
Border: "rounded",
|
||||
AnimateExplosion: true,
|
||||
PortraitMode: "auto",
|
||||
},
|
||||
Git: GitConfig{
|
||||
Paging: PagingConfig{
|
||||
@@ -536,7 +691,7 @@ func GetDefaultConfig() *UserConfig {
|
||||
NextMatch: "n",
|
||||
PrevMatch: "N",
|
||||
StartSearch: "/",
|
||||
OptionMenu: "",
|
||||
OptionMenu: "<disabled>",
|
||||
OptionMenuAlt1: "?",
|
||||
Select: "<space>",
|
||||
GoInto: "<enter>",
|
||||
|
||||
@@ -20,10 +20,11 @@ type BaseContext struct {
|
||||
onFocusFn onFocusFn
|
||||
onFocusLostFn onFocusLostFn
|
||||
|
||||
focusable bool
|
||||
transient bool
|
||||
hasControlledBounds bool
|
||||
highlightOnFocus bool
|
||||
focusable bool
|
||||
transient bool
|
||||
hasControlledBounds bool
|
||||
needsRerenderOnWidthChange bool
|
||||
highlightOnFocus bool
|
||||
|
||||
*ParentContextMgr
|
||||
}
|
||||
@@ -36,14 +37,15 @@ type (
|
||||
var _ types.IBaseContext = &BaseContext{}
|
||||
|
||||
type NewBaseContextOpts struct {
|
||||
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
|
||||
HighlightOnFocus 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
|
||||
HighlightOnFocus bool
|
||||
NeedsRerenderOnWidthChange bool
|
||||
|
||||
OnGetOptionsMap func() map[string]string
|
||||
}
|
||||
@@ -54,17 +56,18 @@ func NewBaseContext(opts NewBaseContextOpts) *BaseContext {
|
||||
hasControlledBounds := !opts.HasUncontrolledBounds
|
||||
|
||||
return &BaseContext{
|
||||
kind: opts.Kind,
|
||||
key: opts.Key,
|
||||
view: opts.View,
|
||||
windowName: opts.WindowName,
|
||||
onGetOptionsMap: opts.OnGetOptionsMap,
|
||||
focusable: opts.Focusable,
|
||||
transient: opts.Transient,
|
||||
hasControlledBounds: hasControlledBounds,
|
||||
highlightOnFocus: opts.HighlightOnFocus,
|
||||
ParentContextMgr: &ParentContextMgr{},
|
||||
viewTrait: viewTrait,
|
||||
kind: opts.Kind,
|
||||
key: opts.Key,
|
||||
view: opts.View,
|
||||
windowName: opts.WindowName,
|
||||
onGetOptionsMap: opts.OnGetOptionsMap,
|
||||
focusable: opts.Focusable,
|
||||
transient: opts.Transient,
|
||||
hasControlledBounds: hasControlledBounds,
|
||||
highlightOnFocus: opts.HighlightOnFocus,
|
||||
needsRerenderOnWidthChange: opts.NeedsRerenderOnWidthChange,
|
||||
ParentContextMgr: &ParentContextMgr{},
|
||||
viewTrait: viewTrait,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,6 +193,10 @@ func (self *BaseContext) HasControlledBounds() bool {
|
||||
return self.hasControlledBounds
|
||||
}
|
||||
|
||||
func (self *BaseContext) NeedsRerenderOnWidthChange() bool {
|
||||
return self.needsRerenderOnWidthChange
|
||||
}
|
||||
|
||||
func (self *BaseContext) Title() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -27,8 +27,10 @@ func NewBranchesContext(c *ContextCommon) *BranchesContext {
|
||||
getDisplayStrings := func(_ int, _ int) [][]string {
|
||||
return presentation.GetBranchListDisplayStrings(
|
||||
viewModel.GetItems(),
|
||||
c.State().GetItemOperation,
|
||||
c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL,
|
||||
c.Modes().Diffing.Ref,
|
||||
c.Views().Branches.Width(),
|
||||
c.Tr,
|
||||
c.UserConfig,
|
||||
c.Model().Worktrees,
|
||||
@@ -39,11 +41,12 @@ func NewBranchesContext(c *ContextCommon) *BranchesContext {
|
||||
FilteredListViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: c.Views().Branches,
|
||||
WindowName: "branches",
|
||||
Key: LOCAL_BRANCHES_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
View: c.Views().Branches,
|
||||
WindowName: "branches",
|
||||
Key: LOCAL_BRANCHES_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
NeedsRerenderOnWidthChange: true,
|
||||
})),
|
||||
ListRenderer: ListRenderer{
|
||||
list: viewModel,
|
||||
|
||||
@@ -41,6 +41,10 @@ func (self *FilteredList[T]) ClearFilter() {
|
||||
self.SetFilter("")
|
||||
}
|
||||
|
||||
func (self *FilteredList[T]) ReApplyFilter() {
|
||||
self.applyFilter()
|
||||
}
|
||||
|
||||
func (self *FilteredList[T]) IsFiltering() bool {
|
||||
return self.filter != ""
|
||||
}
|
||||
|
||||
@@ -68,11 +68,12 @@ func NewLocalCommitsContext(c *ContextCommon) *LocalCommitsContext {
|
||||
SearchTrait: NewSearchTrait(c),
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: c.Views().Commits,
|
||||
WindowName: "commits",
|
||||
Key: LOCAL_COMMITS_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
View: c.Views().Commits,
|
||||
WindowName: "commits",
|
||||
Key: LOCAL_COMMITS_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
NeedsRerenderOnWidthChange: true,
|
||||
})),
|
||||
ListRenderer: ListRenderer{
|
||||
list: viewModel,
|
||||
|
||||
@@ -43,11 +43,12 @@ func NewReflogCommitsContext(c *ContextCommon) *ReflogCommitsContext {
|
||||
FilteredListViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: c.Views().ReflogCommits,
|
||||
WindowName: "commits",
|
||||
Key: REFLOG_COMMITS_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
View: c.Views().ReflogCommits,
|
||||
WindowName: "commits",
|
||||
Key: REFLOG_COMMITS_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
NeedsRerenderOnWidthChange: true,
|
||||
})),
|
||||
ListRenderer: ListRenderer{
|
||||
list: viewModel,
|
||||
|
||||
@@ -36,12 +36,13 @@ func NewRemoteBranchesContext(
|
||||
DynamicTitleBuilder: NewDynamicTitleBuilder(c.Tr.RemoteBranchesDynamicTitle),
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: c.Views().RemoteBranches,
|
||||
WindowName: "branches",
|
||||
Key: REMOTE_BRANCHES_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
Transient: true,
|
||||
View: c.Views().RemoteBranches,
|
||||
WindowName: "branches",
|
||||
Key: REMOTE_BRANCHES_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
Transient: true,
|
||||
NeedsRerenderOnWidthChange: true,
|
||||
})),
|
||||
ListRenderer: ListRenderer{
|
||||
list: viewModel,
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
type RemotesContext struct {
|
||||
*FilteredListViewModel[*models.Remote]
|
||||
*ListContextTrait
|
||||
*SearchHistory
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
@@ -115,12 +115,13 @@ func NewSubCommitsContext(
|
||||
DynamicTitleBuilder: NewDynamicTitleBuilder(c.Tr.SubCommitsDynamicTitle),
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: c.Views().SubCommits,
|
||||
WindowName: "branches",
|
||||
Key: SUB_COMMITS_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
Transient: true,
|
||||
View: c.Views().SubCommits,
|
||||
WindowName: "branches",
|
||||
Key: SUB_COMMITS_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
Transient: true,
|
||||
NeedsRerenderOnWidthChange: true,
|
||||
})),
|
||||
ListRenderer: ListRenderer{
|
||||
list: viewModel,
|
||||
|
||||
@@ -27,7 +27,10 @@ func NewTagsContext(
|
||||
)
|
||||
|
||||
getDisplayStrings := func(_ int, _ int) [][]string {
|
||||
return presentation.GetTagListDisplayStrings(viewModel.GetItems(), c.Modes().Diffing.Ref)
|
||||
return presentation.GetTagListDisplayStrings(
|
||||
viewModel.GetItems(),
|
||||
c.State().GetItemOperation,
|
||||
c.Modes().Diffing.Ref, c.Tr)
|
||||
}
|
||||
|
||||
return &TagsContext{
|
||||
|
||||
@@ -47,6 +47,7 @@ func (gui *Gui) resetHelpersAndControllers() {
|
||||
patchBuildingHelper := helpers.NewPatchBuildingHelper(helperCommon)
|
||||
stagingHelper := helpers.NewStagingHelper(helperCommon)
|
||||
mergeConflictsHelper := helpers.NewMergeConflictsHelper(helperCommon)
|
||||
searchHelper := helpers.NewSearchHelper(helperCommon)
|
||||
|
||||
refreshHelper := helpers.NewRefreshHelper(
|
||||
helperCommon,
|
||||
@@ -56,6 +57,7 @@ func (gui *Gui) resetHelpersAndControllers() {
|
||||
stagingHelper,
|
||||
mergeConflictsHelper,
|
||||
worktreeHelper,
|
||||
searchHelper,
|
||||
)
|
||||
diffHelper := helpers.NewDiffHelper(helperCommon)
|
||||
cherryPickHelper := helpers.NewCherryPickHelper(
|
||||
@@ -112,13 +114,14 @@ func (gui *Gui) resetHelpersAndControllers() {
|
||||
Confirmation: helpers.NewConfirmationHelper(helperCommon),
|
||||
Mode: modeHelper,
|
||||
AppStatus: appStatusHelper,
|
||||
InlineStatus: helpers.NewInlineStatusHelper(helperCommon),
|
||||
WindowArrangement: helpers.NewWindowArrangementHelper(
|
||||
gui.c,
|
||||
windowHelper,
|
||||
modeHelper,
|
||||
appStatusHelper,
|
||||
),
|
||||
Search: helpers.NewSearchHelper(helperCommon),
|
||||
Search: searchHelper,
|
||||
Worktree: worktreeHelper,
|
||||
SubCommits: helpers.NewSubCommitsHelper(helperCommon, refreshHelper, setSubCommits),
|
||||
}
|
||||
|
||||
@@ -34,9 +34,10 @@ func NewBranchesController(
|
||||
func (self *BranchesController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
|
||||
return []*types.Binding{
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Select),
|
||||
Handler: self.checkSelected(self.press),
|
||||
Description: self.c.Tr.Checkout,
|
||||
Key: opts.GetKey(opts.Config.Universal.Select),
|
||||
Handler: self.checkSelected(self.press),
|
||||
GetDisabledReason: self.getDisabledReasonForPress,
|
||||
Description: self.c.Tr.Checkout,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.New),
|
||||
@@ -299,6 +300,18 @@ func (self *BranchesController) press(selectedBranch *models.Branch) error {
|
||||
return self.c.Helpers().Refs.CheckoutRef(selectedBranch.Name, types.CheckoutRefOptions{})
|
||||
}
|
||||
|
||||
func (self *BranchesController) getDisabledReasonForPress() string {
|
||||
currentBranch := self.c.Helpers().Refs.GetCheckedOutRef()
|
||||
if currentBranch != nil {
|
||||
op := self.c.State().GetItemOperation(currentBranch)
|
||||
if op == types.ItemOperationFastForwarding || op == types.ItemOperationPulling {
|
||||
return self.c.Tr.CantCheckoutBranchWhilePulling
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (self *BranchesController) worktreeForBranch(branch *models.Branch) (*models.Worktree, bool) {
|
||||
return git_commands.WorktreeForBranch(branch, self.c.Model().Worktrees)
|
||||
}
|
||||
@@ -563,14 +576,7 @@ func (self *BranchesController) fastForward(branch *models.Branch) error {
|
||||
|
||||
action := self.c.Tr.Actions.FastForwardBranch
|
||||
|
||||
message := utils.ResolvePlaceholderString(
|
||||
self.c.Tr.FastForwarding,
|
||||
map[string]string{
|
||||
"branch": branch.Name,
|
||||
},
|
||||
)
|
||||
|
||||
return self.c.WithWaitingStatus(message, func(task gocui.Task) error {
|
||||
return self.c.WithInlineStatus(branch, types.ItemOperationFastForwarding, context.LOCAL_BRANCHES_CONTEXT_KEY, func(task gocui.Task) error {
|
||||
worktree, ok := self.worktreeForBranch(branch)
|
||||
if ok {
|
||||
self.c.LogAction(action)
|
||||
@@ -590,24 +596,17 @@ func (self *BranchesController) fastForward(branch *models.Branch) error {
|
||||
WorktreeGitDir: worktreeGitDir,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
_ = self.c.Error(err)
|
||||
}
|
||||
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
_ = self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
return err
|
||||
} else {
|
||||
self.c.LogAction(action)
|
||||
|
||||
err := self.c.Git().Sync.FastForward(
|
||||
task, branch.Name, branch.UpstreamRemote, branch.UpstreamBranch,
|
||||
)
|
||||
if err != nil {
|
||||
_ = self.c.Error(err)
|
||||
}
|
||||
_ = self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}})
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ type Helpers struct {
|
||||
Confirmation *ConfirmationHelper
|
||||
Mode *ModeHelper
|
||||
AppStatus *AppStatusHelper
|
||||
InlineStatus *InlineStatusHelper
|
||||
WindowArrangement *WindowArrangementHelper
|
||||
Search *SearchHelper
|
||||
Worktree *WorktreeHelper
|
||||
@@ -81,6 +82,7 @@ func NewStubHelpers() *Helpers {
|
||||
Confirmation: &ConfirmationHelper{},
|
||||
Mode: &ModeHelper{},
|
||||
AppStatus: &AppStatusHelper{},
|
||||
InlineStatus: &InlineStatusHelper{},
|
||||
WindowArrangement: &WindowArrangementHelper{},
|
||||
Search: &SearchHelper{},
|
||||
Worktree: &WorktreeHelper{},
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
)
|
||||
|
||||
type InlineStatusHelper struct {
|
||||
c *HelperCommon
|
||||
|
||||
contextsWithInlineStatus map[types.ContextKey]*inlineStatusInfo
|
||||
mutex *deadlock.Mutex
|
||||
}
|
||||
|
||||
func NewInlineStatusHelper(c *HelperCommon) *InlineStatusHelper {
|
||||
return &InlineStatusHelper{
|
||||
c: c,
|
||||
contextsWithInlineStatus: make(map[types.ContextKey]*inlineStatusInfo),
|
||||
mutex: &deadlock.Mutex{},
|
||||
}
|
||||
}
|
||||
|
||||
type InlineStatusOpts struct {
|
||||
Item types.HasUrn
|
||||
Operation types.ItemOperation
|
||||
ContextKey types.ContextKey
|
||||
}
|
||||
|
||||
type inlineStatusInfo struct {
|
||||
refCount int
|
||||
stop chan struct{}
|
||||
}
|
||||
|
||||
// A custom task for WithInlineStatus calls; it wraps the original one and
|
||||
// hides the status whenever the task is paused, and shows it again when
|
||||
// continued.
|
||||
type inlineStatusHelperTask struct {
|
||||
gocui.Task
|
||||
|
||||
inlineStatusHelper *InlineStatusHelper
|
||||
opts InlineStatusOpts
|
||||
}
|
||||
|
||||
// poor man's version of explicitly saying that struct X implements interface Y
|
||||
var _ gocui.Task = inlineStatusHelperTask{}
|
||||
|
||||
func (self inlineStatusHelperTask) Pause() {
|
||||
self.inlineStatusHelper.stop(self.opts)
|
||||
self.Task.Pause()
|
||||
|
||||
self.inlineStatusHelper.renderContext(self.opts.ContextKey)
|
||||
}
|
||||
|
||||
func (self inlineStatusHelperTask) Continue() {
|
||||
self.Task.Continue()
|
||||
self.inlineStatusHelper.start(self.opts)
|
||||
}
|
||||
|
||||
func (self *InlineStatusHelper) WithInlineStatus(opts InlineStatusOpts, f func(gocui.Task) error) {
|
||||
self.c.OnWorker(func(task gocui.Task) {
|
||||
self.start(opts)
|
||||
|
||||
err := f(inlineStatusHelperTask{task, self, opts})
|
||||
if err != nil {
|
||||
self.c.OnUIThread(func() error {
|
||||
return self.c.Error(err)
|
||||
})
|
||||
}
|
||||
|
||||
self.stop(opts)
|
||||
})
|
||||
}
|
||||
|
||||
func (self *InlineStatusHelper) start(opts InlineStatusOpts) {
|
||||
self.c.State().SetItemOperation(opts.Item, opts.Operation)
|
||||
|
||||
self.mutex.Lock()
|
||||
defer self.mutex.Unlock()
|
||||
|
||||
info := self.contextsWithInlineStatus[opts.ContextKey]
|
||||
if info == nil {
|
||||
info = &inlineStatusInfo{refCount: 0, stop: make(chan struct{})}
|
||||
self.contextsWithInlineStatus[opts.ContextKey] = info
|
||||
|
||||
go utils.Safe(func() {
|
||||
ticker := time.NewTicker(time.Millisecond * utils.LoaderAnimationInterval)
|
||||
defer ticker.Stop()
|
||||
outer:
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
self.renderContext(opts.ContextKey)
|
||||
case <-info.stop:
|
||||
break outer
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
info.refCount++
|
||||
}
|
||||
|
||||
func (self *InlineStatusHelper) stop(opts InlineStatusOpts) {
|
||||
self.mutex.Lock()
|
||||
|
||||
if info := self.contextsWithInlineStatus[opts.ContextKey]; info != nil {
|
||||
info.refCount--
|
||||
if info.refCount <= 0 {
|
||||
info.stop <- struct{}{}
|
||||
delete(self.contextsWithInlineStatus, opts.ContextKey)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
self.mutex.Unlock()
|
||||
|
||||
self.c.State().ClearItemOperation(opts.Item)
|
||||
}
|
||||
|
||||
func (self *InlineStatusHelper) renderContext(contextKey types.ContextKey) {
|
||||
self.c.OnUIThread(func() error {
|
||||
_ = self.c.ContextForKey(contextKey).HandleRender()
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -28,6 +28,7 @@ type RefreshHelper struct {
|
||||
stagingHelper *StagingHelper
|
||||
mergeConflictsHelper *MergeConflictsHelper
|
||||
worktreeHelper *WorktreeHelper
|
||||
searchHelper *SearchHelper
|
||||
}
|
||||
|
||||
func NewRefreshHelper(
|
||||
@@ -38,6 +39,7 @@ func NewRefreshHelper(
|
||||
stagingHelper *StagingHelper,
|
||||
mergeConflictsHelper *MergeConflictsHelper,
|
||||
worktreeHelper *WorktreeHelper,
|
||||
searchHelper *SearchHelper,
|
||||
) *RefreshHelper {
|
||||
return &RefreshHelper{
|
||||
c: c,
|
||||
@@ -47,6 +49,7 @@ func NewRefreshHelper(
|
||||
stagingHelper: stagingHelper,
|
||||
mergeConflictsHelper: mergeConflictsHelper,
|
||||
worktreeHelper: worktreeHelper,
|
||||
searchHelper: searchHelper,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,12 +118,15 @@ func (self *RefreshHelper) Refresh(options types.RefreshOptions) error {
|
||||
}
|
||||
}
|
||||
|
||||
includeWorktreesWithBranches := false
|
||||
if scopeSet.Includes(types.COMMITS) || scopeSet.Includes(types.BRANCHES) || scopeSet.Includes(types.REFLOG) || scopeSet.Includes(types.BISECT_INFO) {
|
||||
// whenever we change commits, we should update branches because the upstream/downstream
|
||||
// counts can change. Whenever we change branches we should also change commits
|
||||
// e.g. in the case of switching branches.
|
||||
refresh("commits and commit files", self.refreshCommitsAndCommitFiles)
|
||||
refresh("reflog and branches", self.refreshReflogAndBranches)
|
||||
|
||||
includeWorktreesWithBranches = scopeSet.Includes(types.WORKTREES)
|
||||
refresh("reflog and branches", func() { self.refreshReflogAndBranches(includeWorktreesWithBranches) })
|
||||
} else if scopeSet.Includes(types.REBASE_COMMITS) {
|
||||
// the above block handles rebase commits so we only need to call this one
|
||||
// if we've asked specifically for rebase commits and not those other things
|
||||
@@ -157,7 +163,7 @@ func (self *RefreshHelper) Refresh(options types.RefreshOptions) error {
|
||||
refresh("remotes", func() { _ = self.refreshRemotes() })
|
||||
}
|
||||
|
||||
if scopeSet.Includes(types.WORKTREES) {
|
||||
if scopeSet.Includes(types.WORKTREES) && !includeWorktreesWithBranches {
|
||||
refresh("worktrees", func() { _ = self.refreshWorktrees() })
|
||||
}
|
||||
|
||||
@@ -242,7 +248,7 @@ func (self *RefreshHelper) refreshReflogCommitsConsideringStartup() {
|
||||
case types.INITIAL:
|
||||
self.c.OnWorker(func(_ gocui.Task) {
|
||||
_ = self.refreshReflogCommits()
|
||||
self.refreshBranches()
|
||||
self.refreshBranches(false)
|
||||
self.c.State().GetRepoState().SetStartupStage(types.COMPLETE)
|
||||
})
|
||||
|
||||
@@ -251,10 +257,10 @@ func (self *RefreshHelper) refreshReflogCommitsConsideringStartup() {
|
||||
}
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) refreshReflogAndBranches() {
|
||||
func (self *RefreshHelper) refreshReflogAndBranches(refreshWorktrees bool) {
|
||||
self.refreshReflogCommitsConsideringStartup()
|
||||
|
||||
self.refreshBranches()
|
||||
self.refreshBranches(refreshWorktrees)
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) refreshCommitsAndCommitFiles() {
|
||||
@@ -325,7 +331,7 @@ func (self *RefreshHelper) refreshCommitsWithLimit() error {
|
||||
self.c.Model().WorkingTreeStateAtLastCommitRefresh = self.c.Git().Status.WorkingTreeState()
|
||||
self.c.Model().CheckedOutBranch = checkedOutBranchName
|
||||
|
||||
return self.c.PostRefreshUpdate(self.c.Contexts().LocalCommits)
|
||||
return self.refreshView(self.c.Contexts().LocalCommits)
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) refreshSubCommitsWithLimit() error {
|
||||
@@ -348,7 +354,7 @@ func (self *RefreshHelper) refreshSubCommitsWithLimit() error {
|
||||
self.c.Model().SubCommits = commits
|
||||
self.RefreshAuthors(commits)
|
||||
|
||||
return self.c.PostRefreshUpdate(self.c.Contexts().SubCommits)
|
||||
return self.refreshView(self.c.Contexts().SubCommits)
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) RefreshAuthors(commits []*models.Commit) {
|
||||
@@ -378,7 +384,7 @@ func (self *RefreshHelper) refreshCommitFilesContext() error {
|
||||
self.c.Model().CommitFiles = files
|
||||
self.c.Contexts().CommitFiles.CommitFileTreeViewModel.SetTree()
|
||||
|
||||
return self.c.PostRefreshUpdate(self.c.Contexts().CommitFiles)
|
||||
return self.refreshView(self.c.Contexts().CommitFiles)
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) refreshRebaseCommits() error {
|
||||
@@ -392,7 +398,7 @@ func (self *RefreshHelper) refreshRebaseCommits() error {
|
||||
self.c.Model().Commits = updatedCommits
|
||||
self.c.Model().WorkingTreeStateAtLastCommitRefresh = self.c.Git().Status.WorkingTreeState()
|
||||
|
||||
return self.c.PostRefreshUpdate(self.c.Contexts().LocalCommits)
|
||||
return self.refreshView(self.c.Contexts().LocalCommits)
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) refreshTags() error {
|
||||
@@ -403,7 +409,7 @@ func (self *RefreshHelper) refreshTags() error {
|
||||
|
||||
self.c.Model().Tags = tags
|
||||
|
||||
return self.c.PostRefreshUpdate(self.c.Contexts().Tags)
|
||||
return self.refreshView(self.c.Contexts().Tags)
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) refreshStateSubmoduleConfigs() error {
|
||||
@@ -419,7 +425,7 @@ func (self *RefreshHelper) refreshStateSubmoduleConfigs() error {
|
||||
|
||||
// self.refreshStatus is called at the end of this because that's when we can
|
||||
// be sure there is a State.Model.Branches array to pick the current branch from
|
||||
func (self *RefreshHelper) refreshBranches() {
|
||||
func (self *RefreshHelper) refreshBranches(refreshWorktrees bool) {
|
||||
self.c.Mutexes().RefreshingBranchesMutex.Lock()
|
||||
defer self.c.Mutexes().RefreshingBranchesMutex.Unlock()
|
||||
|
||||
@@ -443,15 +449,24 @@ func (self *RefreshHelper) refreshBranches() {
|
||||
|
||||
self.c.Model().Branches = branches
|
||||
|
||||
if err := self.c.PostRefreshUpdate(self.c.Contexts().Branches); err != nil {
|
||||
if refreshWorktrees {
|
||||
self.loadWorktrees()
|
||||
if err := self.refreshView(self.c.Contexts().Worktrees); err != nil {
|
||||
self.c.Log.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := self.refreshView(self.c.Contexts().Branches); err != nil {
|
||||
self.c.Log.Error(err)
|
||||
}
|
||||
|
||||
// Need to re-render the commits view because the visualization of local
|
||||
// branch heads might have changed
|
||||
self.c.Mutexes().LocalCommitsMutex.Lock()
|
||||
if err := self.c.Contexts().LocalCommits.HandleRender(); err != nil {
|
||||
self.c.Log.Error(err)
|
||||
}
|
||||
self.c.Mutexes().LocalCommitsMutex.Unlock()
|
||||
|
||||
self.refreshStatus()
|
||||
}
|
||||
@@ -473,11 +488,11 @@ func (self *RefreshHelper) refreshFilesAndSubmodules() error {
|
||||
}
|
||||
|
||||
self.c.OnUIThread(func() error {
|
||||
if err := self.c.PostRefreshUpdate(self.c.Contexts().Submodules); err != nil {
|
||||
if err := self.refreshView(self.c.Contexts().Submodules); err != nil {
|
||||
self.c.Log.Error(err)
|
||||
}
|
||||
|
||||
if err := self.c.PostRefreshUpdate(self.c.Contexts().Files); err != nil {
|
||||
if err := self.refreshView(self.c.Contexts().Files); err != nil {
|
||||
self.c.Log.Error(err)
|
||||
}
|
||||
|
||||
@@ -599,7 +614,7 @@ func (self *RefreshHelper) refreshReflogCommits() error {
|
||||
model.FilteredReflogCommits = model.ReflogCommits
|
||||
}
|
||||
|
||||
return self.c.PostRefreshUpdate(self.c.Contexts().ReflogCommits)
|
||||
return self.refreshView(self.c.Contexts().ReflogCommits)
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) refreshRemotes() error {
|
||||
@@ -623,41 +638,44 @@ func (self *RefreshHelper) refreshRemotes() error {
|
||||
}
|
||||
}
|
||||
|
||||
if err := self.c.PostRefreshUpdate(self.c.Contexts().Remotes); err != nil {
|
||||
if err := self.refreshView(self.c.Contexts().Remotes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.c.PostRefreshUpdate(self.c.Contexts().RemoteBranches); err != nil {
|
||||
if err := self.refreshView(self.c.Contexts().RemoteBranches); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) refreshWorktrees() error {
|
||||
func (self *RefreshHelper) loadWorktrees() {
|
||||
worktrees, err := self.c.Git().Loaders.Worktrees.GetWorktrees()
|
||||
if err != nil {
|
||||
self.c.Log.Error(err)
|
||||
self.c.Model().Worktrees = []*models.Worktree{}
|
||||
return nil
|
||||
}
|
||||
|
||||
self.c.Model().Worktrees = worktrees
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) refreshWorktrees() error {
|
||||
self.loadWorktrees()
|
||||
|
||||
// need to refresh branches because the branches view shows worktrees against
|
||||
// branches
|
||||
if err := self.c.PostRefreshUpdate(self.c.Contexts().Branches); err != nil {
|
||||
if err := self.refreshView(self.c.Contexts().Branches); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return self.c.PostRefreshUpdate(self.c.Contexts().Worktrees)
|
||||
return self.refreshView(self.c.Contexts().Worktrees)
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) refreshStashEntries() error {
|
||||
self.c.Model().StashEntries = self.c.Git().Loaders.StashLoader.
|
||||
GetStashEntries(self.c.Modes().Filtering.GetPath())
|
||||
|
||||
return self.c.PostRefreshUpdate(self.c.Contexts().Stash)
|
||||
return self.refreshView(self.c.Contexts().Stash)
|
||||
}
|
||||
|
||||
// never call this on its own, it should only be called from within refreshCommits()
|
||||
@@ -676,7 +694,7 @@ func (self *RefreshHelper) refreshStatus() {
|
||||
|
||||
repoName := self.c.Git().RepoPaths.RepoName()
|
||||
|
||||
status := presentation.FormatStatus(repoName, currentBranch, linkedWorktreeName, workingTreeState, self.c.Tr)
|
||||
status := presentation.FormatStatus(repoName, currentBranch, types.ItemOperationNone, linkedWorktreeName, workingTreeState, self.c.Tr)
|
||||
|
||||
self.c.SetViewContent(self.c.Views().Status, status)
|
||||
}
|
||||
@@ -696,3 +714,8 @@ func (self *RefreshHelper) refForLog() string {
|
||||
|
||||
return bisectInfo.GetStartSha()
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) refreshView(context types.Context) error {
|
||||
self.searchHelper.ReApplyFilter(context)
|
||||
return self.c.PostRefreshUpdate(context)
|
||||
}
|
||||
|
||||
@@ -173,11 +173,6 @@ func (self *ReposHelper) DispatchSwitchTo(path string, errMsg string, contextKey
|
||||
return err
|
||||
}
|
||||
|
||||
// these two mutexes are used by our background goroutines (triggered via `self.goEvery`. We don't want to
|
||||
// switch to a repo while one of these goroutines is in the process of updating something
|
||||
self.c.Mutexes().SyncMutex.Lock()
|
||||
defer self.c.Mutexes().SyncMutex.Unlock()
|
||||
|
||||
self.c.Mutexes().RefreshingFilesMutex.Lock()
|
||||
defer self.c.Mutexes().RefreshingFilesMutex.Unlock()
|
||||
|
||||
|
||||
@@ -227,6 +227,18 @@ func (self *SearchHelper) OnPromptContentChanged(searchString string) {
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SearchHelper) ReApplyFilter(context types.Context) {
|
||||
state := self.searchState()
|
||||
if context == state.Context {
|
||||
filterableContext, ok := context.(types.IFilterableContext)
|
||||
if ok {
|
||||
filterableContext.SetSelectedLineIdx(0)
|
||||
_ = filterableContext.GetView().SetOriginY(0)
|
||||
filterableContext.ReApplyFilter()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SearchHelper) RenderSearchStatus(c types.Context) {
|
||||
if c.GetKey() == context.SEARCH_CONTEXT_KEY {
|
||||
return
|
||||
|
||||
@@ -34,14 +34,24 @@ func NewWindowArrangementHelper(
|
||||
|
||||
const INFO_SECTION_PADDING = " "
|
||||
|
||||
func (self *WindowArrangementHelper) shouldUsePortraitMode(width, height int) bool {
|
||||
switch self.c.UserConfig.Gui.PortraitMode {
|
||||
case "never":
|
||||
return false
|
||||
case "always":
|
||||
return true
|
||||
default: // "auto" or any garbage values in PortraitMode value
|
||||
return width <= 84 && height > 45
|
||||
}
|
||||
}
|
||||
|
||||
func (self *WindowArrangementHelper) GetWindowDimensions(informationStr string, appStatus string) map[string]boxlayout.Dimensions {
|
||||
width, height := self.c.GocuiGui().Size()
|
||||
|
||||
sideSectionWeight, mainSectionWeight := self.getMidSectionWeights()
|
||||
|
||||
sidePanelsDirection := boxlayout.COLUMN
|
||||
portraitMode := width <= 84 && height > 45
|
||||
if portraitMode {
|
||||
if self.shouldUsePortraitMode(width, height) {
|
||||
sidePanelsDirection = boxlayout.ROW
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
@@ -17,7 +16,7 @@ func (self *ScreenModeActions) Next() error {
|
||||
),
|
||||
)
|
||||
|
||||
return self.rerenderViewsWithScreenModeDependentContent()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *ScreenModeActions) Prev() error {
|
||||
@@ -28,32 +27,9 @@ func (self *ScreenModeActions) Prev() error {
|
||||
),
|
||||
)
|
||||
|
||||
return self.rerenderViewsWithScreenModeDependentContent()
|
||||
}
|
||||
|
||||
// these views need to be re-rendered when the screen mode changes. The commits view,
|
||||
// for example, will show authorship information in half and full screen mode.
|
||||
func (self *ScreenModeActions) rerenderViewsWithScreenModeDependentContent() error {
|
||||
// for now we re-render all list views.
|
||||
for _, context := range self.c.Context().AllList() {
|
||||
if err := self.rerenderView(context.GetView()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *ScreenModeActions) rerenderView(view *gocui.View) error {
|
||||
context, ok := self.c.Helpers().View.ContextForView(view.Name())
|
||||
if !ok {
|
||||
self.c.Log.Errorf("no context found for view %s", view.Name())
|
||||
return nil
|
||||
}
|
||||
|
||||
return context.HandleRender()
|
||||
}
|
||||
|
||||
func nextIntInCycle(sl []types.WindowMaximisation, current types.WindowMaximisation) types.WindowMaximisation {
|
||||
for i, val := range sl {
|
||||
if val == current {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
|
||||
"github.com/jesseduffield/lazygit/pkg/constants"
|
||||
@@ -106,7 +107,7 @@ func (self *StatusController) onClick() error {
|
||||
}
|
||||
|
||||
cx, _ := self.c.Views().Status.Cursor()
|
||||
upstreamStatus := presentation.BranchStatus(currentBranch, self.c.Tr)
|
||||
upstreamStatus := presentation.BranchStatus(currentBranch, types.ItemOperationNone, self.c.Tr, time.Now())
|
||||
repoName := self.c.Git().RepoPaths.RepoName()
|
||||
workingTreeState := self.c.Git().Status.WorkingTreeState()
|
||||
switch workingTreeState {
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
@@ -30,14 +31,16 @@ func NewSyncController(
|
||||
func (self *SyncController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
|
||||
bindings := []*types.Binding{
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Push),
|
||||
Handler: opts.Guards.NoPopupPanel(self.HandlePush),
|
||||
Description: self.c.Tr.Push,
|
||||
Key: opts.GetKey(opts.Config.Universal.Push),
|
||||
Handler: opts.Guards.NoPopupPanel(self.HandlePush),
|
||||
GetDisabledReason: self.getDisabledReasonForPushOrPull,
|
||||
Description: self.c.Tr.Push,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Pull),
|
||||
Handler: opts.Guards.NoPopupPanel(self.HandlePull),
|
||||
Description: self.c.Tr.Pull,
|
||||
Key: opts.GetKey(opts.Config.Universal.Pull),
|
||||
Handler: opts.Guards.NoPopupPanel(self.HandlePull),
|
||||
GetDisabledReason: self.getDisabledReasonForPushOrPull,
|
||||
Description: self.c.Tr.Pull,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -56,6 +59,18 @@ func (self *SyncController) HandlePull() error {
|
||||
return self.branchCheckedOut(self.pull)()
|
||||
}
|
||||
|
||||
func (self *SyncController) getDisabledReasonForPushOrPull() string {
|
||||
currentBranch := self.c.Helpers().Refs.GetCheckedOutRef()
|
||||
if currentBranch != nil {
|
||||
op := self.c.State().GetItemOperation(currentBranch)
|
||||
if op != types.ItemOperationNone {
|
||||
return self.c.Tr.CantPullOrPushSameBranchTwice
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (self *SyncController) branchCheckedOut(f func(*models.Branch) error) func() error {
|
||||
return func() error {
|
||||
currentBranch := self.c.Helpers().Refs.GetCheckedOutRef()
|
||||
@@ -73,13 +88,13 @@ func (self *SyncController) push(currentBranch *models.Branch) error {
|
||||
if currentBranch.IsTrackingRemote() {
|
||||
opts := pushOpts{}
|
||||
if currentBranch.HasCommitsToPull() {
|
||||
return self.requestToForcePush(opts)
|
||||
return self.requestToForcePush(currentBranch, opts)
|
||||
} else {
|
||||
return self.pushAux(opts)
|
||||
return self.pushAux(currentBranch, opts)
|
||||
}
|
||||
} else {
|
||||
if self.c.Git().Config.GetPushToCurrent() {
|
||||
return self.pushAux(pushOpts{setUpstream: true})
|
||||
return self.pushAux(currentBranch, pushOpts{setUpstream: true})
|
||||
} else {
|
||||
return self.c.Helpers().Upstream.PromptForUpstreamWithInitialContent(currentBranch, func(upstream string) error {
|
||||
upstreamRemote, upstreamBranch, err := self.c.Helpers().Upstream.ParseUpstream(upstream)
|
||||
@@ -87,7 +102,7 @@ func (self *SyncController) push(currentBranch *models.Branch) error {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
return self.pushAux(pushOpts{
|
||||
return self.pushAux(currentBranch, pushOpts{
|
||||
setUpstream: true,
|
||||
upstreamRemote: upstreamRemote,
|
||||
upstreamBranch: upstreamBranch,
|
||||
@@ -107,11 +122,11 @@ func (self *SyncController) pull(currentBranch *models.Branch) error {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
return self.PullAux(PullFilesOptions{Action: action})
|
||||
return self.PullAux(currentBranch, PullFilesOptions{Action: action})
|
||||
})
|
||||
}
|
||||
|
||||
return self.PullAux(PullFilesOptions{Action: action})
|
||||
return self.PullAux(currentBranch, PullFilesOptions{Action: action})
|
||||
}
|
||||
|
||||
func (self *SyncController) setCurrentBranchUpstream(upstream string) error {
|
||||
@@ -139,8 +154,8 @@ type PullFilesOptions struct {
|
||||
Action string
|
||||
}
|
||||
|
||||
func (self *SyncController) PullAux(opts PullFilesOptions) error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.PullingStatus, func(task gocui.Task) error {
|
||||
func (self *SyncController) PullAux(currentBranch *models.Branch, opts PullFilesOptions) error {
|
||||
return self.c.WithInlineStatus(currentBranch, types.ItemOperationPulling, context.LOCAL_BRANCHES_CONTEXT_KEY, func(task gocui.Task) error {
|
||||
return self.pullWithLock(task, opts)
|
||||
})
|
||||
}
|
||||
@@ -167,8 +182,8 @@ type pushOpts struct {
|
||||
setUpstream bool
|
||||
}
|
||||
|
||||
func (self *SyncController) pushAux(opts pushOpts) error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.PushingStatus, func(task gocui.Task) error {
|
||||
func (self *SyncController) pushAux(currentBranch *models.Branch, opts pushOpts) error {
|
||||
return self.c.WithInlineStatus(currentBranch, types.ItemOperationPushing, context.LOCAL_BRANCHES_CONTEXT_KEY, func(task gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.Push)
|
||||
err := self.c.Git().Sync.Push(
|
||||
task,
|
||||
@@ -192,18 +207,18 @@ func (self *SyncController) pushAux(opts pushOpts) error {
|
||||
newOpts := opts
|
||||
newOpts.force = true
|
||||
|
||||
return self.pushAux(newOpts)
|
||||
return self.pushAux(currentBranch, newOpts)
|
||||
},
|
||||
})
|
||||
return nil
|
||||
}
|
||||
_ = self.c.Error(err)
|
||||
return err
|
||||
}
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
})
|
||||
}
|
||||
|
||||
func (self *SyncController) requestToForcePush(opts pushOpts) error {
|
||||
func (self *SyncController) requestToForcePush(currentBranch *models.Branch, opts pushOpts) error {
|
||||
forcePushDisabled := self.c.UserConfig.Git.DisableForcePushing
|
||||
if forcePushDisabled {
|
||||
return self.c.ErrorMsg(self.c.Tr.ForcePushDisabled)
|
||||
@@ -214,7 +229,7 @@ func (self *SyncController) requestToForcePush(opts pushOpts) error {
|
||||
Prompt: self.forcePushPrompt(),
|
||||
HandleConfirm: func() error {
|
||||
opts.force = true
|
||||
return self.pushAux(opts)
|
||||
return self.pushAux(currentBranch, opts)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -92,10 +92,9 @@ func (self *TagsController) checkout(tag *models.Tag) error {
|
||||
func (self *TagsController) localDelete(tag *models.Tag) error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.DeleteLocalTag)
|
||||
if err := self.c.Git().Tag.LocalDelete(tag.Name); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.COMMITS, types.TAGS}})
|
||||
err := self.c.Git().Tag.LocalDelete(tag.Name)
|
||||
_ = self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.COMMITS, types.TAGS}})
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
@@ -130,10 +129,10 @@ func (self *TagsController) remoteDelete(tag *models.Tag) error {
|
||||
Title: confirmTitle,
|
||||
Prompt: confirmPrompt,
|
||||
HandleConfirm: func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(t gocui.Task) error {
|
||||
return self.c.WithInlineStatus(tag, types.ItemOperationDeleting, context.TAGS_CONTEXT_KEY, func(task gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.DeleteRemoteTag)
|
||||
if err := self.c.Git().Remote.DeleteRemoteTag(t, upstream, tag.Name); err != nil {
|
||||
return self.c.Error(err)
|
||||
if err := self.c.Git().Remote.DeleteRemoteTag(task, upstream, tag.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
self.c.Toast(self.c.Tr.RemoteTagDeletedMessage)
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.COMMITS, types.TAGS}})
|
||||
@@ -189,14 +188,17 @@ func (self *TagsController) push(tag *models.Tag) error {
|
||||
InitialContent: "origin",
|
||||
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRemoteSuggestionsFunc(),
|
||||
HandleConfirm: func(response string) error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.PushingTagStatus, func(task gocui.Task) error {
|
||||
return self.c.WithInlineStatus(tag, types.ItemOperationPushing, context.TAGS_CONTEXT_KEY, func(task gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.PushTag)
|
||||
err := self.c.Git().Tag.Push(task, response, tag.Name)
|
||||
if err != nil {
|
||||
_ = self.c.Error(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
// Render again to remove the inline status:
|
||||
self.c.OnUIThread(func() error {
|
||||
_ = self.c.Contexts().Tags.HandleRender()
|
||||
return nil
|
||||
})
|
||||
|
||||
return err
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
+34
-3
@@ -110,6 +110,12 @@ type Gui struct {
|
||||
// lazygit was opened in, or if we'll retain the one we're currently in.
|
||||
RetainOriginalDir bool
|
||||
|
||||
// stores long-running operations associated with items (e.g. when a branch
|
||||
// is being pushed). At the moment the rule is to use an item operation when
|
||||
// we need to talk to the remote.
|
||||
itemOperations map[string]types.ItemOperation
|
||||
itemOperationsMutex *deadlock.Mutex
|
||||
|
||||
PrevLayout PrevLayout
|
||||
|
||||
// this is the initial dir we are in upon opening lazygit. We hold onto this
|
||||
@@ -180,6 +186,27 @@ func (self *StateAccessor) SetRetainOriginalDir(value bool) {
|
||||
self.gui.RetainOriginalDir = value
|
||||
}
|
||||
|
||||
func (self *StateAccessor) GetItemOperation(item types.HasUrn) types.ItemOperation {
|
||||
self.gui.itemOperationsMutex.Lock()
|
||||
defer self.gui.itemOperationsMutex.Unlock()
|
||||
|
||||
return self.gui.itemOperations[item.URN()]
|
||||
}
|
||||
|
||||
func (self *StateAccessor) SetItemOperation(item types.HasUrn, operation types.ItemOperation) {
|
||||
self.gui.itemOperationsMutex.Lock()
|
||||
defer self.gui.itemOperationsMutex.Unlock()
|
||||
|
||||
self.gui.itemOperations[item.URN()] = operation
|
||||
}
|
||||
|
||||
func (self *StateAccessor) ClearItemOperation(item types.HasUrn) {
|
||||
self.gui.itemOperationsMutex.Lock()
|
||||
defer self.gui.itemOperationsMutex.Unlock()
|
||||
|
||||
delete(self.gui.itemOperations, item.URN())
|
||||
}
|
||||
|
||||
// we keep track of some stuff from one render to the next to see if certain
|
||||
// things have changed
|
||||
type PrevLayout struct {
|
||||
@@ -273,7 +300,6 @@ func (gui *Gui) onNewRepo(startArgs appTypes.StartArgs, contextKey types.Context
|
||||
gui.gitVersion,
|
||||
gui.os,
|
||||
git_config.NewStdCachedGitConfig(gui.Log),
|
||||
gui.Mutexes.SyncMutex,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -463,7 +489,6 @@ func NewGui(
|
||||
RefreshingFilesMutex: &deadlock.Mutex{},
|
||||
RefreshingBranchesMutex: &deadlock.Mutex{},
|
||||
RefreshingStatusMutex: &deadlock.Mutex{},
|
||||
SyncMutex: &deadlock.Mutex{},
|
||||
LocalCommitsMutex: &deadlock.Mutex{},
|
||||
SubCommitsMutex: &deadlock.Mutex{},
|
||||
AuthorsMutex: &deadlock.Mutex{},
|
||||
@@ -473,6 +498,9 @@ func NewGui(
|
||||
},
|
||||
InitialDir: initialDir,
|
||||
afterLayoutFuncs: make(chan func() error, 1000),
|
||||
|
||||
itemOperations: make(map[string]types.ItemOperation),
|
||||
itemOperationsMutex: &deadlock.Mutex{},
|
||||
}
|
||||
|
||||
gui.PopupHandler = popup.NewPopupHandler(
|
||||
@@ -623,7 +651,10 @@ func (gui *Gui) Run(startArgs appTypes.StartArgs) error {
|
||||
deadlock.Opts.LogBuf = utils.NewOnceWriter(os.Stderr, func() {
|
||||
gui.g.Close()
|
||||
})
|
||||
deadlock.Opts.Disable = !gui.Debug
|
||||
// disable deadlock reporting if we're not running in debug mode, or if
|
||||
// we're debugging an integration test. In this latter case, stopping at
|
||||
// breakpoints and stepping through code can easily take more than 30s.
|
||||
deadlock.Opts.Disable = !gui.Debug || os.Getenv(components.WAIT_FOR_DEBUGGER_ENV_VAR) == ""
|
||||
|
||||
if err := gui.Config.ReloadUserConfig(); err != nil {
|
||||
return nil
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
@@ -199,3 +200,8 @@ func (self *guiCommon) RunningIntegrationTest() bool {
|
||||
func (self *guiCommon) InDemo() bool {
|
||||
return self.gui.integrationTest != nil && self.gui.integrationTest.IsDemo()
|
||||
}
|
||||
|
||||
func (self *guiCommon) WithInlineStatus(item types.HasUrn, operation types.ItemOperation, contextKey types.ContextKey, f func(gocui.Task) error) error {
|
||||
self.gui.helpers.InlineStatus.WithInlineStatus(helpers.InlineStatusOpts{Item: item, Operation: operation, ContextKey: contextKey}, f)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -100,7 +100,9 @@ func LabelFromKey(key types.Key) string {
|
||||
|
||||
func GetKey(key string) types.Key {
|
||||
runeCount := utf8.RuneCountInString(key)
|
||||
if runeCount > 1 {
|
||||
if key == "<disabled>" {
|
||||
return nil
|
||||
} else if runeCount > 1 {
|
||||
binding, ok := keyByLabel[strings.ToLower(key)]
|
||||
if !ok {
|
||||
log.Fatalf("Unrecognized key %s for keybinding. For permitted values see %s", strings.ToLower(key), constants.Links.Docs.CustomKeybindings)
|
||||
|
||||
+23
-2
@@ -44,8 +44,13 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
}
|
||||
}
|
||||
|
||||
contextsToRerender := []types.Context{}
|
||||
|
||||
// we assume that the view has already been created.
|
||||
setViewFromDimensions := func(viewName string, windowName string) (*gocui.View, error) {
|
||||
setViewFromDimensions := func(context types.Context) (*gocui.View, error) {
|
||||
viewName := context.GetViewName()
|
||||
windowName := context.GetWindowName()
|
||||
|
||||
dimensionsObj, ok := viewDimensions[windowName]
|
||||
|
||||
view, err := g.View(viewName)
|
||||
@@ -67,6 +72,16 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
if view.Frame {
|
||||
frameOffset = 0
|
||||
}
|
||||
|
||||
if context.NeedsRerenderOnWidthChange() {
|
||||
// view.Width() returns the width -1 for some reason
|
||||
oldWidth := view.Width() + 1
|
||||
newWidth := dimensionsObj.X1 - dimensionsObj.X0 + 2*frameOffset
|
||||
if oldWidth != newWidth {
|
||||
contextsToRerender = append(contextsToRerender, context)
|
||||
}
|
||||
}
|
||||
|
||||
_, err = g.SetView(
|
||||
viewName,
|
||||
dimensionsObj.X0-frameOffset,
|
||||
@@ -85,7 +100,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
continue
|
||||
}
|
||||
|
||||
_, err := setViewFromDimensions(context.GetViewName(), context.GetWindowName())
|
||||
_, err := setViewFromDimensions(context)
|
||||
if err != nil && !gocui.IsUnknownView(err) {
|
||||
return err
|
||||
}
|
||||
@@ -146,6 +161,12 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
}
|
||||
}
|
||||
|
||||
for _, context := range contextsToRerender {
|
||||
if err := context.HandleRender(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// here is a good place log some stuff
|
||||
// if you run `lazygit --logs`
|
||||
// this will let you see these branches as prettified json
|
||||
|
||||
@@ -3,15 +3,18 @@ package presentation
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation/icons"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/i18n"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/mattn/go-runewidth"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
@@ -19,27 +22,52 @@ var branchPrefixColorCache = make(map[string]style.TextStyle)
|
||||
|
||||
func GetBranchListDisplayStrings(
|
||||
branches []*models.Branch,
|
||||
getItemOperation func(item types.HasUrn) types.ItemOperation,
|
||||
fullDescription bool,
|
||||
diffName string,
|
||||
viewWidth int,
|
||||
tr *i18n.TranslationSet,
|
||||
userConfig *config.UserConfig,
|
||||
worktrees []*models.Worktree,
|
||||
) [][]string {
|
||||
return lo.Map(branches, func(branch *models.Branch, _ int) []string {
|
||||
diffed := branch.Name == diffName
|
||||
return getBranchDisplayStrings(branch, fullDescription, diffed, tr, userConfig, worktrees)
|
||||
return getBranchDisplayStrings(branch, getItemOperation(branch), fullDescription, diffed, viewWidth, tr, userConfig, worktrees, time.Now())
|
||||
})
|
||||
}
|
||||
|
||||
// getBranchDisplayStrings returns the display string of branch
|
||||
func getBranchDisplayStrings(
|
||||
b *models.Branch,
|
||||
itemOperation types.ItemOperation,
|
||||
fullDescription bool,
|
||||
diffed bool,
|
||||
viewWidth int,
|
||||
tr *i18n.TranslationSet,
|
||||
userConfig *config.UserConfig,
|
||||
worktrees []*models.Worktree,
|
||||
now time.Time,
|
||||
) []string {
|
||||
checkedOutByWorkTree := git_commands.CheckedOutByOtherWorktree(b, worktrees)
|
||||
showCommitHash := fullDescription || userConfig.Gui.ShowBranchCommitHash
|
||||
branchStatus := BranchStatus(b, itemOperation, tr, now)
|
||||
worktreeIcon := lo.Ternary(icons.IsIconEnabled(), icons.LINKED_WORKTREE_ICON, fmt.Sprintf("(%s)", tr.LcWorktree))
|
||||
|
||||
// Recency is always three characters, plus one for the space
|
||||
availableWidth := viewWidth - 4
|
||||
if len(branchStatus) > 0 {
|
||||
availableWidth -= runewidth.StringWidth(branchStatus) + 1
|
||||
}
|
||||
if icons.IsIconEnabled() {
|
||||
availableWidth -= 2 // one for the icon, one for the space
|
||||
}
|
||||
if showCommitHash {
|
||||
availableWidth -= utils.COMMIT_HASH_SHORT_SIZE + 1
|
||||
}
|
||||
if checkedOutByWorkTree {
|
||||
availableWidth -= runewidth.StringWidth(worktreeIcon) + 1
|
||||
}
|
||||
|
||||
displayName := b.Name
|
||||
if b.DisplayName != "" {
|
||||
displayName = b.DisplayName
|
||||
@@ -50,13 +78,19 @@ func getBranchDisplayStrings(
|
||||
nameTextStyle = theme.DiffTerminalColor
|
||||
}
|
||||
|
||||
if len(displayName) > availableWidth {
|
||||
// Never shorten the branch name to less then 3 characters
|
||||
len := utils.Max(availableWidth, 4)
|
||||
displayName = displayName[:len-1] + "…"
|
||||
}
|
||||
coloredName := nameTextStyle.Sprint(displayName)
|
||||
branchStatus := utils.WithPadding(ColoredBranchStatus(b, tr), 2, utils.AlignLeft)
|
||||
if git_commands.CheckedOutByOtherWorktree(b, worktrees) {
|
||||
worktreeIcon := lo.Ternary(icons.IsIconEnabled(), icons.LINKED_WORKTREE_ICON, fmt.Sprintf("(%s)", tr.LcWorktree))
|
||||
if checkedOutByWorkTree {
|
||||
coloredName = fmt.Sprintf("%s %s", coloredName, style.FgDefault.Sprint(worktreeIcon))
|
||||
}
|
||||
coloredName = fmt.Sprintf("%s %s", coloredName, branchStatus)
|
||||
if len(branchStatus) > 0 {
|
||||
coloredStatus := branchStatusColor(b, itemOperation).Sprint(branchStatus)
|
||||
coloredName = fmt.Sprintf("%s %s", coloredName, coloredStatus)
|
||||
}
|
||||
|
||||
recencyColor := style.FgCyan
|
||||
if b.Recency == " *" {
|
||||
@@ -70,7 +104,7 @@ func getBranchDisplayStrings(
|
||||
res = append(res, nameTextStyle.Sprint(icons.IconForBranch(b)))
|
||||
}
|
||||
|
||||
if fullDescription || userConfig.Gui.ShowBranchCommitHash {
|
||||
if showCommitHash {
|
||||
res = append(res, utils.ShortSha(b.CommitHash))
|
||||
}
|
||||
|
||||
@@ -109,9 +143,11 @@ func GetBranchTextStyle(name string) style.TextStyle {
|
||||
}
|
||||
}
|
||||
|
||||
func ColoredBranchStatus(branch *models.Branch, tr *i18n.TranslationSet) string {
|
||||
func branchStatusColor(branch *models.Branch, itemOperation types.ItemOperation) style.TextStyle {
|
||||
colour := style.FgYellow
|
||||
if branch.UpstreamGone {
|
||||
if itemOperation != types.ItemOperationNone {
|
||||
colour = style.FgCyan
|
||||
} else if branch.UpstreamGone {
|
||||
colour = style.FgRed
|
||||
} else if branch.MatchesUpstream() {
|
||||
colour = style.FgGreen
|
||||
@@ -119,10 +155,19 @@ func ColoredBranchStatus(branch *models.Branch, tr *i18n.TranslationSet) string
|
||||
colour = style.FgMagenta
|
||||
}
|
||||
|
||||
return colour.Sprint(BranchStatus(branch, tr))
|
||||
return colour
|
||||
}
|
||||
|
||||
func BranchStatus(branch *models.Branch, tr *i18n.TranslationSet) string {
|
||||
func ColoredBranchStatus(branch *models.Branch, itemOperation types.ItemOperation, tr *i18n.TranslationSet) string {
|
||||
return branchStatusColor(branch, itemOperation).Sprint(BranchStatus(branch, itemOperation, tr, time.Now()))
|
||||
}
|
||||
|
||||
func BranchStatus(branch *models.Branch, itemOperation types.ItemOperation, tr *i18n.TranslationSet, now time.Time) string {
|
||||
itemOperationStr := itemOperationToString(itemOperation, tr)
|
||||
if itemOperationStr != "" {
|
||||
return itemOperationStr + " " + utils.Loader(now)
|
||||
}
|
||||
|
||||
if !branch.IsTrackingRemote() {
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
package presentation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation/icons"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_getBranchDisplayStrings(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
branch *models.Branch
|
||||
itemOperation types.ItemOperation
|
||||
fullDescription bool
|
||||
viewWidth int
|
||||
useIcons bool
|
||||
checkedOutByWorktree bool
|
||||
expected []string
|
||||
}{
|
||||
// First some tests for when the view is wide enough so that everything fits:
|
||||
{
|
||||
branch: &models.Branch{Name: "branch_name", Recency: "1m"},
|
||||
itemOperation: types.ItemOperationNone,
|
||||
fullDescription: false,
|
||||
viewWidth: 100,
|
||||
useIcons: false,
|
||||
checkedOutByWorktree: false,
|
||||
expected: []string{"1m", "branch_name"},
|
||||
},
|
||||
{
|
||||
branch: &models.Branch{Name: "branch_name", Recency: "1m"},
|
||||
itemOperation: types.ItemOperationNone,
|
||||
fullDescription: false,
|
||||
viewWidth: 100,
|
||||
useIcons: false,
|
||||
checkedOutByWorktree: true,
|
||||
expected: []string{"1m", "branch_name (worktree)"},
|
||||
},
|
||||
{
|
||||
branch: &models.Branch{Name: "branch_name", Recency: "1m"},
|
||||
itemOperation: types.ItemOperationNone,
|
||||
fullDescription: false,
|
||||
viewWidth: 100,
|
||||
useIcons: true,
|
||||
checkedOutByWorktree: true,
|
||||
expected: []string{"1m", "", "branch_name "},
|
||||
},
|
||||
{
|
||||
branch: &models.Branch{
|
||||
Name: "branch_name",
|
||||
Recency: "1m",
|
||||
UpstreamRemote: "origin",
|
||||
Pushables: "0",
|
||||
Pullables: "0",
|
||||
},
|
||||
itemOperation: types.ItemOperationNone,
|
||||
fullDescription: false,
|
||||
viewWidth: 100,
|
||||
useIcons: false,
|
||||
checkedOutByWorktree: false,
|
||||
expected: []string{"1m", "branch_name ✓"},
|
||||
},
|
||||
{
|
||||
branch: &models.Branch{
|
||||
Name: "branch_name",
|
||||
Recency: "1m",
|
||||
UpstreamRemote: "origin",
|
||||
Pushables: "3",
|
||||
Pullables: "5",
|
||||
},
|
||||
itemOperation: types.ItemOperationNone,
|
||||
fullDescription: false,
|
||||
viewWidth: 100,
|
||||
useIcons: false,
|
||||
checkedOutByWorktree: true,
|
||||
expected: []string{"1m", "branch_name (worktree) ↑3↓5"},
|
||||
},
|
||||
{
|
||||
branch: &models.Branch{Name: "branch_name", Recency: "1m"},
|
||||
itemOperation: types.ItemOperationPushing,
|
||||
fullDescription: false,
|
||||
viewWidth: 100,
|
||||
useIcons: false,
|
||||
checkedOutByWorktree: false,
|
||||
expected: []string{"1m", "branch_name Pushing |"},
|
||||
},
|
||||
{
|
||||
branch: &models.Branch{
|
||||
Name: "branch_name",
|
||||
Recency: "1m",
|
||||
CommitHash: "1234567890",
|
||||
UpstreamRemote: "origin",
|
||||
UpstreamBranch: "branch_name",
|
||||
Pushables: "0",
|
||||
Pullables: "0",
|
||||
Subject: "commit title",
|
||||
},
|
||||
itemOperation: types.ItemOperationNone,
|
||||
fullDescription: true,
|
||||
viewWidth: 100,
|
||||
useIcons: false,
|
||||
checkedOutByWorktree: false,
|
||||
expected: []string{"1m", "12345678", "branch_name ✓", "origin branch_name", "commit title"},
|
||||
},
|
||||
|
||||
// Now tests for how we truncate the branch name when there's not enough room:
|
||||
{
|
||||
branch: &models.Branch{Name: "branch_name", Recency: "1m"},
|
||||
itemOperation: types.ItemOperationNone,
|
||||
fullDescription: false,
|
||||
viewWidth: 14,
|
||||
useIcons: false,
|
||||
checkedOutByWorktree: false,
|
||||
expected: []string{"1m", "branch_na…"},
|
||||
},
|
||||
{
|
||||
branch: &models.Branch{Name: "branch_name", Recency: "1m"},
|
||||
itemOperation: types.ItemOperationNone,
|
||||
fullDescription: false,
|
||||
viewWidth: 14,
|
||||
useIcons: false,
|
||||
checkedOutByWorktree: true,
|
||||
expected: []string{"1m", "bra… (worktree)"},
|
||||
},
|
||||
{
|
||||
branch: &models.Branch{Name: "branch_name", Recency: "1m"},
|
||||
itemOperation: types.ItemOperationNone,
|
||||
fullDescription: false,
|
||||
viewWidth: 14,
|
||||
useIcons: true,
|
||||
checkedOutByWorktree: true,
|
||||
expected: []string{"1m", "", "branc… "},
|
||||
},
|
||||
{
|
||||
branch: &models.Branch{
|
||||
Name: "branch_name",
|
||||
Recency: "1m",
|
||||
UpstreamRemote: "origin",
|
||||
Pushables: "0",
|
||||
Pullables: "0",
|
||||
},
|
||||
itemOperation: types.ItemOperationNone,
|
||||
fullDescription: false,
|
||||
viewWidth: 14,
|
||||
useIcons: false,
|
||||
checkedOutByWorktree: false,
|
||||
expected: []string{"1m", "branch_… ✓"},
|
||||
},
|
||||
{
|
||||
branch: &models.Branch{
|
||||
Name: "branch_name",
|
||||
Recency: "1m",
|
||||
UpstreamRemote: "origin",
|
||||
Pushables: "3",
|
||||
Pullables: "5",
|
||||
},
|
||||
itemOperation: types.ItemOperationNone,
|
||||
fullDescription: false,
|
||||
viewWidth: 30,
|
||||
useIcons: false,
|
||||
checkedOutByWorktree: true,
|
||||
expected: []string{"1m", "branch_na… (worktree) ↑3↓5"},
|
||||
},
|
||||
{
|
||||
branch: &models.Branch{Name: "branch_name", Recency: "1m"},
|
||||
itemOperation: types.ItemOperationPushing,
|
||||
fullDescription: false,
|
||||
viewWidth: 20,
|
||||
useIcons: false,
|
||||
checkedOutByWorktree: false,
|
||||
expected: []string{"1m", "branc… Pushing |"},
|
||||
},
|
||||
{
|
||||
branch: &models.Branch{
|
||||
Name: "branch_name",
|
||||
Recency: "1m",
|
||||
CommitHash: "1234567890",
|
||||
UpstreamRemote: "origin",
|
||||
UpstreamBranch: "branch_name",
|
||||
Pushables: "0",
|
||||
Pullables: "0",
|
||||
Subject: "commit title",
|
||||
},
|
||||
itemOperation: types.ItemOperationNone,
|
||||
fullDescription: true,
|
||||
viewWidth: 20,
|
||||
useIcons: false,
|
||||
checkedOutByWorktree: false,
|
||||
expected: []string{"1m", "12345678", "bran… ✓", "origin branch_name", "commit title"},
|
||||
},
|
||||
}
|
||||
|
||||
c := utils.NewDummyCommon()
|
||||
|
||||
for i, s := range scenarios {
|
||||
icons.SetNerdFontsVersion(lo.Ternary(s.useIcons, "3", ""))
|
||||
|
||||
worktrees := []*models.Worktree{}
|
||||
if s.checkedOutByWorktree {
|
||||
worktrees = append(worktrees, &models.Worktree{Branch: s.branch.Name})
|
||||
}
|
||||
|
||||
t.Run(fmt.Sprintf("getBranchDisplayStrings_%d", i), func(t *testing.T) {
|
||||
strings := getBranchDisplayStrings(s.branch, s.itemOperation, s.fullDescription, false, s.viewWidth, c.Tr, c.UserConfig, worktrees, time.Time{})
|
||||
assert.Equal(t, s.expected, strings)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package presentation
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gookit/color"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/patch"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
|
||||
@@ -159,7 +160,9 @@ func getFileLine(hasUnstagedChanges bool, hasStagedChanges bool, name string, di
|
||||
isDirectory := file == nil
|
||||
|
||||
if icons.IsIconEnabled() {
|
||||
output += restColor.Sprintf("%s ", icons.IconForFile(name, isSubmodule, isLinkedWorktree, isDirectory))
|
||||
icon := icons.IconForFile(name, isSubmodule, isLinkedWorktree, isDirectory)
|
||||
paint := color.C256(icon.Color, false)
|
||||
output += paint.Sprint(icon.Icon) + " "
|
||||
}
|
||||
|
||||
output += restColor.Sprint(utils.EscapeSpecialChars(name))
|
||||
@@ -198,7 +201,9 @@ func getCommitFileLine(name string, diffName string, commitFile *models.CommitFi
|
||||
isDirectory := commitFile == nil
|
||||
|
||||
if icons.IsIconEnabled() {
|
||||
output += colour.Sprintf("%s ", icons.IconForFile(name, isSubmodule, isLinkedWorktree, isDirectory))
|
||||
icon := icons.IconForFile(name, isSubmodule, isLinkedWorktree, isDirectory)
|
||||
paint := color.C256(icon.Color, false)
|
||||
output += paint.Sprint(icon.Icon) + " "
|
||||
}
|
||||
|
||||
output += colour.Sprint(name)
|
||||
|
||||
@@ -5,327 +5,328 @@ import (
|
||||
)
|
||||
|
||||
// https://github.com/ogham/exa/blob/master/src/output/icons.rs
|
||||
const (
|
||||
DEFAULT_FILE_ICON = "\uf15b" //
|
||||
DEFAULT_SUBMODULE_ICON = "\uf1d3" //
|
||||
DEFAULT_DIRECTORY_ICON = "\uf114" //
|
||||
var (
|
||||
DEFAULT_FILE_ICON = IconProperties{Icon: "\uf15b", Color: 241} //
|
||||
DEFAULT_SUBMODULE_ICON = IconProperties{Icon: "\uf1d3", Color: 202} //
|
||||
DEFAULT_DIRECTORY_ICON = IconProperties{Icon: "\uf07b", Color: 241} //
|
||||
)
|
||||
|
||||
var nameIconMap = map[string]string{
|
||||
".Trash": "\uf1f8", //
|
||||
".atom": "\ue764", //
|
||||
".bashprofile": "\ue615", //
|
||||
".bashrc": "\uf489", //
|
||||
".idea": "\ue7b5", //
|
||||
".git": "\uf1d3", //
|
||||
".gitattributes": "\uf1d3", //
|
||||
".gitconfig": "\uf1d3", //
|
||||
".github": "\uf408", //
|
||||
".gitignore": "\uf1d3", //
|
||||
".gitmodules": "\uf1d3", //
|
||||
".rvm": "\ue21e", //
|
||||
".vimrc": "\ue62b", //
|
||||
".vscode": "\ue70c", //
|
||||
".zshrc": "\uf489", //
|
||||
"Cargo.lock": "\ue7a8", //
|
||||
"Cargo.toml": "\ue7a8", //
|
||||
"bin": "\ue5fc", //
|
||||
"config": "\ue5fc", //
|
||||
"docker-compose.yml": "\uf308", //
|
||||
"Dockerfile": "\uf308", //
|
||||
"ds_store": "\uf179", //
|
||||
"gitignore_global": "\uf1d3", //
|
||||
"go.mod": "\ue626", //
|
||||
"go.sum": "\ue626", //
|
||||
"gradle": "\ue256", //
|
||||
"gruntfile.coffee": "\ue611", //
|
||||
"gruntfile.js": "\ue611", //
|
||||
"gruntfile.ls": "\ue611", //
|
||||
"gulpfile.coffee": "\ue610", //
|
||||
"gulpfile.js": "\ue610", //
|
||||
"gulpfile.ls": "\ue610", //
|
||||
"hidden": "\uf023", //
|
||||
"include": "\ue5fc", //
|
||||
"lib": "\uf121", //
|
||||
"localized": "\uf179", //
|
||||
"Makefile": "\uf489", //
|
||||
"node_modules": "\ue718", //
|
||||
"npmignore": "\ue71e", //
|
||||
"PKGBUILD": "\uf303", //
|
||||
"rubydoc": "\ue73b", //
|
||||
"yarn.lock": "\ue718", //
|
||||
// See https://github.com/nvim-tree/nvim-web-devicons/blob/master/lua/nvim-web-devicons/icons-default.lua
|
||||
var nameIconMap = map[string]IconProperties{
|
||||
".Trash": {Icon: "\uf1f8", Color: 241}, //
|
||||
".atom": {Icon: "\ue764", Color: 241}, //
|
||||
".bashprofile": {Icon: "\ue615", Color: 113}, //
|
||||
".bashrc": {Icon: "\ue795", Color: 113}, //
|
||||
".idea": {Icon: "\ue7b5", Color: 241}, //
|
||||
".git": {Icon: "\uf1d3", Color: 202}, //
|
||||
".gitattributes": {Icon: "\uf1d3", Color: 202}, //
|
||||
".gitconfig": {Icon: "\uf1d3", Color: 202}, //
|
||||
".github": {Icon: "\uf408", Color: 241}, //
|
||||
".gitignore": {Icon: "\uf1d3", Color: 202}, //
|
||||
".gitmodules": {Icon: "\uf1d3", Color: 202}, //
|
||||
".rvm": {Icon: "\ue21e", Color: 160}, //
|
||||
".vimrc": {Icon: "\ue62b", Color: 28}, //
|
||||
".vscode": {Icon: "\ue70c", Color: 39}, //
|
||||
".zshrc": {Icon: "\ue795", Color: 113}, //
|
||||
"Cargo.lock": {Icon: "\ue7a8", Color: 216}, //
|
||||
"Cargo.toml": {Icon: "\ue7a8", Color: 216}, //
|
||||
"bin": {Icon: "\ue5fc", Color: 241}, //
|
||||
"config": {Icon: "\ue5fc", Color: 241}, //
|
||||
"docker-compose.yml": {Icon: "\uf308", Color: 68}, //
|
||||
"Dockerfile": {Icon: "\uf308", Color: 68}, //
|
||||
"ds_store": {Icon: "\uf179", Color: 15}, //
|
||||
"gitignore_global": {Icon: "\uf1d3", Color: 202}, //
|
||||
"go.mod": {Icon: "\ue627", Color: 74}, //
|
||||
"go.sum": {Icon: "\ue627", Color: 74}, //
|
||||
"gradle": {Icon: "\ue256", Color: 168}, //
|
||||
"gruntfile.coffee": {Icon: "\ue611", Color: 166}, //
|
||||
"gruntfile.js": {Icon: "\ue611", Color: 166}, //
|
||||
"gruntfile.ls": {Icon: "\ue611", Color: 166}, //
|
||||
"gulpfile.coffee": {Icon: "\ue610", Color: 167}, //
|
||||
"gulpfile.js": {Icon: "\ue610", Color: 167}, //
|
||||
"gulpfile.ls": {Icon: "\ue610", Color: 168}, //
|
||||
"hidden": {Icon: "\uf023", Color: 241}, //
|
||||
"include": {Icon: "\ue5fc", Color: 241}, //
|
||||
"lib": {Icon: "\uf121", Color: 241}, //
|
||||
"localized": {Icon: "\uf179", Color: 15}, //
|
||||
"Makefile": {Icon: "\ue975", Color: 241}, //
|
||||
"node_modules": {Icon: "\ue718", Color: 197}, //
|
||||
"npmignore": {Icon: "\ue71e", Color: 197}, //
|
||||
"PKGBUILD": {Icon: "\uf303", Color: 38}, //
|
||||
"rubydoc": {Icon: "\ue73b", Color: 160}, //
|
||||
"yarn.lock": {Icon: "\ue6a7", Color: 74}, //
|
||||
}
|
||||
|
||||
var extIconMap = map[string]string{
|
||||
".ai": "\ue7b4", //
|
||||
".android": "\ue70e", //
|
||||
".apk": "\ue70e", //
|
||||
".apple": "\uf179", //
|
||||
".avi": "\uf03d", //
|
||||
".avif": "\uf1c5", //
|
||||
".avro": "\ue60b", //
|
||||
".awk": "\uf489", //
|
||||
".bash": "\uf489", //
|
||||
".bash_history": "\uf489", //
|
||||
".bash_profile": "\uf489", //
|
||||
".bashrc": "\uf489", //
|
||||
".bat": "\uf17a", //
|
||||
".bats": "\uf489", //
|
||||
".bmp": "\uf1c5", //
|
||||
".bz": "\uf410", //
|
||||
".bz2": "\uf410", //
|
||||
".c": "\ue61e", //
|
||||
".c++": "\ue61d", //
|
||||
".cab": "\ue70f", //
|
||||
".cc": "\ue61d", //
|
||||
".cfg": "\ue615", //
|
||||
".class": "\ue256", //
|
||||
".clj": "\ue768", //
|
||||
".cljs": "\ue76a", //
|
||||
".cls": "\uf034", //
|
||||
".cmd": "\ue70f", //
|
||||
".coffee": "\uf0f4", //
|
||||
".conf": "\ue615", //
|
||||
".cp": "\ue61d", //
|
||||
".cpio": "\uf410", //
|
||||
".cpp": "\ue61d", //
|
||||
".cs": "\U000f031b", //
|
||||
".csh": "\uf489", //
|
||||
".cshtml": "\uf1fa", //
|
||||
".csproj": "\U000f031b", //
|
||||
".css": "\ue749", //
|
||||
".csv": "\uf1c3", //
|
||||
".csx": "\U000f031b", //
|
||||
".cxx": "\ue61d", //
|
||||
".d": "\ue7af", //
|
||||
".dart": "\ue798", //
|
||||
".db": "\uf1c0", //
|
||||
".deb": "\ue77d", //
|
||||
".diff": "\uf440", //
|
||||
".djvu": "\uf02d", //
|
||||
".dll": "\ue70f", //
|
||||
".doc": "\uf1c2", //
|
||||
".docx": "\uf1c2", //
|
||||
".ds_store": "\uf179", //
|
||||
".DS_store": "\uf179", //
|
||||
".dump": "\uf1c0", //
|
||||
".ebook": "\ue28b", //
|
||||
".ebuild": "\uf30d", //
|
||||
".editorconfig": "\ue615", //
|
||||
".ejs": "\ue618", //
|
||||
".elm": "\ue62c", //
|
||||
".env": "\uf462", //
|
||||
".eot": "\uf031", //
|
||||
".epub": "\ue28a", //
|
||||
".erb": "\ue73b", //
|
||||
".erl": "\ue7b1", //
|
||||
".ex": "\ue62d", //
|
||||
".exe": "\uf17a", //
|
||||
".exs": "\ue62d", //
|
||||
".fish": "\uf489", //
|
||||
".flac": "\uf001", //
|
||||
".flv": "\uf03d", //
|
||||
".font": "\uf031", //
|
||||
".fs": "\ue7a7", //
|
||||
".fsi": "\ue7a7", //
|
||||
".fsx": "\ue7a7", //
|
||||
".gdoc": "\uf1c2", //
|
||||
".gem": "\ue21e", //
|
||||
".gemfile": "\ue21e", //
|
||||
".gemspec": "\ue21e", //
|
||||
".gform": "\uf298", //
|
||||
".gif": "\uf1c5", //
|
||||
".git": "\uf1d3", //
|
||||
".gitattributes": "\uf1d3", //
|
||||
".gitignore": "\uf1d3", //
|
||||
".gitmodules": "\uf1d3", //
|
||||
".go": "\ue626", //
|
||||
".gradle": "\ue256", //
|
||||
".groovy": "\ue775", //
|
||||
".gsheet": "\uf1c3", //
|
||||
".gslides": "\uf1c4", //
|
||||
".guardfile": "\ue21e", //
|
||||
".gz": "\uf410", //
|
||||
".h": "\uf0fd", //
|
||||
".hbs": "\ue60f", //
|
||||
".hpp": "\uf0fd", //
|
||||
".hs": "\ue777", //
|
||||
".htm": "\uf13b", //
|
||||
".html": "\uf13b", //
|
||||
".hxx": "\uf0fd", //
|
||||
".ico": "\uf1c5", //
|
||||
".image": "\uf1c5", //
|
||||
".iml": "\ue7b5", //
|
||||
".ini": "\uf17a", //
|
||||
".ipynb": "\ue606", //
|
||||
".iso": "\ue271", //
|
||||
".j2c": "\uf1c5", //
|
||||
".j2k": "\uf1c5", //
|
||||
".jad": "\ue256", //
|
||||
".jar": "\ue256", //
|
||||
".java": "\ue256", //
|
||||
".jfi": "\uf1c5", //
|
||||
".jfif": "\uf1c5", //
|
||||
".jif": "\uf1c5", //
|
||||
".jl": "\ue624", //
|
||||
".jmd": "\uf48a", //
|
||||
".jp2": "\uf1c5", //
|
||||
".jpe": "\uf1c5", //
|
||||
".jpeg": "\uf1c5", //
|
||||
".jpg": "\uf1c5", //
|
||||
".jpx": "\uf1c5", //
|
||||
".js": "\ue74e", //
|
||||
".json": "\ue60b", //
|
||||
".jsx": "\ue7ba", //
|
||||
".jxl": "\uf1c5", //
|
||||
".ksh": "\uf489", //
|
||||
".kt": "\ue634", //
|
||||
".kts": "\ue634", //
|
||||
".latex": "\uf034", //
|
||||
".less": "\ue758", //
|
||||
".lhs": "\ue777", //
|
||||
".license": "\U000f0219", //
|
||||
".localized": "\uf179", //
|
||||
".lock": "\uf023", //
|
||||
".log": "\uf18d", //
|
||||
".lua": "\ue620", //
|
||||
".lz": "\uf410", //
|
||||
".lz4": "\uf410", //
|
||||
".lzh": "\uf410", //
|
||||
".lzma": "\uf410", //
|
||||
".lzo": "\uf410", //
|
||||
".m": "\ue61e", //
|
||||
".mm": "\ue61d", //
|
||||
".m4a": "\uf001", //
|
||||
".markdown": "\uf48a", //
|
||||
".md": "\uf48a", //
|
||||
".mdx": "\uf48a", //
|
||||
".mjs": "\ue74e", //
|
||||
".mk": "\uf489", //
|
||||
".mkd": "\uf48a", //
|
||||
".mkv": "\uf03d", //
|
||||
".mobi": "\ue28b", //
|
||||
".mov": "\uf03d", //
|
||||
".mp3": "\uf001", //
|
||||
".mp4": "\uf03d", //
|
||||
".msi": "\ue70f", //
|
||||
".mustache": "\ue60f", //
|
||||
".nix": "\uf313", //
|
||||
".node": "\U000f0399", //
|
||||
".npmignore": "\ue71e", //
|
||||
".odp": "\uf1c4", //
|
||||
".ods": "\uf1c3", //
|
||||
".odt": "\uf1c2", //
|
||||
".ogg": "\uf001", //
|
||||
".ogv": "\uf03d", //
|
||||
".otf": "\uf031", //
|
||||
".part": "\uf43a", //
|
||||
".patch": "\uf440", //
|
||||
".pdf": "\uf1c1", //
|
||||
".php": "\ue73d", //
|
||||
".pl": "\ue769", //
|
||||
".png": "\uf1c5", //
|
||||
".ppt": "\uf1c4", //
|
||||
".pptx": "\uf1c4", //
|
||||
".procfile": "\ue21e", //
|
||||
".properties": "\ue60b", //
|
||||
".ps1": "\uf489", //
|
||||
".psd": "\ue7b8", //
|
||||
".pxm": "\uf1c5", //
|
||||
".py": "\ue606", //
|
||||
".pyc": "\ue606", //
|
||||
".r": "\uf25d", //
|
||||
".rakefile": "\ue21e", //
|
||||
".rar": "\uf410", //
|
||||
".razor": "\uf1fa", //
|
||||
".rb": "\ue21e", //
|
||||
".rdata": "\uf25d", //
|
||||
".rdb": "\ue76d", //
|
||||
".rdoc": "\uf48a", //
|
||||
".rds": "\uf25d", //
|
||||
".readme": "\uf48a", //
|
||||
".rlib": "\ue7a8", //
|
||||
".rmd": "\uf48a", //
|
||||
".rpm": "\ue7bb", //
|
||||
".rs": "\ue7a8", //
|
||||
".rspec": "\ue21e", //
|
||||
".rspec_parallel": "\ue21e", //
|
||||
".rspec_status": "\ue21e", //
|
||||
".rss": "\uf09e", //
|
||||
".rtf": "\U000f0219", //
|
||||
".ru": "\ue21e", //
|
||||
".rubydoc": "\ue73b", //
|
||||
".sass": "\ue603", //
|
||||
".scala": "\ue737", //
|
||||
".scss": "\ue749", //
|
||||
".sh": "\uf489", //
|
||||
".shell": "\uf489", //
|
||||
".slim": "\ue73b", //
|
||||
".sln": "\ue70c", //
|
||||
".so": "\uf17c", //
|
||||
".sql": "\uf1c0", //
|
||||
".sqlite3": "\ue7c4", //
|
||||
".sty": "\uf034", //
|
||||
".styl": "\ue600", //
|
||||
".stylus": "\ue600", //
|
||||
".svelte": "\ue697", //
|
||||
".svg": "\uf1c5", //
|
||||
".swift": "\ue755", //
|
||||
".tar": "\uf410", //
|
||||
".taz": "\uf410", //
|
||||
".tbz": "\uf410", //
|
||||
".tbz2": "\uf410", //
|
||||
".tex": "\uf034", //
|
||||
".tgz": "\uf410", //
|
||||
".tiff": "\uf1c5", //
|
||||
".tlz": "\uf410", //
|
||||
".toml": "\ue615", //
|
||||
".torrent": "\ue275", //
|
||||
".ts": "\ue628", //
|
||||
".tsv": "\uf1c3", //
|
||||
".tsx": "\ue7ba", //
|
||||
".ttf": "\uf031", //
|
||||
".twig": "\ue61c", //
|
||||
".txt": "\uf15c", //
|
||||
".txz": "\uf410", //
|
||||
".tz": "\uf410", //
|
||||
".tzo": "\uf410", //
|
||||
".video": "\uf03d", //
|
||||
".vim": "\ue62b", //
|
||||
".vue": "\U000f0844", //
|
||||
".war": "\ue256", //
|
||||
".wav": "\uf001", //
|
||||
".webm": "\uf03d", //
|
||||
".webp": "\uf1c5", //
|
||||
".windows": "\uf17a", //
|
||||
".woff": "\uf031", //
|
||||
".woff2": "\uf031", //
|
||||
".xhtml": "\uf13b", //
|
||||
".xls": "\uf1c3", //
|
||||
".xlsx": "\uf1c3", //
|
||||
".xml": "\uf121", //
|
||||
".xul": "\uf121", //
|
||||
".xz": "\uf410", //
|
||||
".yaml": "\uf481", //
|
||||
".yml": "\uf481", //
|
||||
".zip": "\uf410", //
|
||||
".zsh": "\uf489", //
|
||||
".zsh-theme": "\uf489", //
|
||||
".zshrc": "\uf489", //
|
||||
".zst": "\uf410", //
|
||||
var extIconMap = map[string]IconProperties{
|
||||
".ai": {Icon: "\ue7b4", Color: 185}, //
|
||||
".android": {Icon: "\ue70e", Color: 70}, //
|
||||
".apk": {Icon: "\ue70e", Color: 70}, //
|
||||
".apple": {Icon: "\uf179", Color: 15}, //
|
||||
".avi": {Icon: "\uf03d", Color: 140}, //
|
||||
".avif": {Icon: "\uf1c5", Color: 140}, //
|
||||
".avro": {Icon: "\ue60b", Color: 130}, //
|
||||
".awk": {Icon: "\ue795", Color: 140}, //
|
||||
".bash": {Icon: "\ue795", Color: 113}, //
|
||||
".bash_history": {Icon: "\ue795", Color: 113}, //
|
||||
".bash_profile": {Icon: "\ue795", Color: 113}, //
|
||||
".bashrc": {Icon: "\ue795", Color: 113}, //
|
||||
".bat": {Icon: "\uf17a", Color: 81}, //
|
||||
".bats": {Icon: "\ue795", Color: 241}, //
|
||||
".bmp": {Icon: "\uf1c5", Color: 149}, //
|
||||
".bz": {Icon: "\uf410", Color: 239}, //
|
||||
".bz2": {Icon: "\uf410", Color: 239}, //
|
||||
".c": {Icon: "\ue61e", Color: 111}, //
|
||||
".c++": {Icon: "\ue61d", Color: 204}, //
|
||||
".cab": {Icon: "\ue70f", Color: 241}, //
|
||||
".cc": {Icon: "\ue61d", Color: 204}, //
|
||||
".cfg": {Icon: "\ue615", Color: 255}, //
|
||||
".class": {Icon: "\ue256", Color: 168}, //
|
||||
".clj": {Icon: "\ue768", Color: 113}, //
|
||||
".cljs": {Icon: "\ue76a", Color: 74}, //
|
||||
".cls": {Icon: "\uf034", Color: 239}, //
|
||||
".cmd": {Icon: "\ue70f", Color: 239}, //
|
||||
".coffee": {Icon: "\uf0f4", Color: 185}, //
|
||||
".conf": {Icon: "\ue615", Color: 66}, //
|
||||
".cp": {Icon: "\ue61d", Color: 74}, //
|
||||
".cpio": {Icon: "\uf410", Color: 239}, //
|
||||
".cpp": {Icon: "\ue61d", Color: 74}, //
|
||||
".cs": {Icon: "\U000f031b", Color: 58}, //
|
||||
".csh": {Icon: "\ue795", Color: 240}, //
|
||||
".cshtml": {Icon: "\uf1fa", Color: 239}, //
|
||||
".csproj": {Icon: "\U000f031b", Color: 58}, //
|
||||
".css": {Icon: "\ue749", Color: 75}, //
|
||||
".csv": {Icon: "\uf1c3", Color: 113}, //
|
||||
".csx": {Icon: "\U000f031b", Color: 58}, //
|
||||
".cxx": {Icon: "\ue61d", Color: 74}, //
|
||||
".d": {Icon: "\ue7af", Color: 28}, //
|
||||
".dart": {Icon: "\ue798", Color: 25}, //
|
||||
".db": {Icon: "\uf1c0", Color: 188}, //
|
||||
".deb": {Icon: "\ue77d", Color: 88}, //
|
||||
".diff": {Icon: "\uf440", Color: 241}, //
|
||||
".djvu": {Icon: "\uf02d", Color: 241}, //
|
||||
".dll": {Icon: "\ue70f", Color: 241}, //
|
||||
".doc": {Icon: "\uf0219", Color: 26}, //
|
||||
".docx": {Icon: "\uf0219", Color: 26}, //
|
||||
".ds_store": {Icon: "\uf179", Color: 15}, //
|
||||
".DS_store": {Icon: "\uf179", Color: 15}, //
|
||||
".dump": {Icon: "\uf1c0", Color: 188}, //
|
||||
".ebook": {Icon: "\ue28b", Color: 241}, //
|
||||
".ebuild": {Icon: "\uf30d", Color: 56}, //
|
||||
".editorconfig": {Icon: "\ue615", Color: 241}, //
|
||||
".ejs": {Icon: "\ue618", Color: 185}, //
|
||||
".elm": {Icon: "\ue62c", Color: 74}, //
|
||||
".env": {Icon: "\uf462", Color: 227}, //
|
||||
".eot": {Icon: "\uf031", Color: 124}, //
|
||||
".epub": {Icon: "\ue28a", Color: 241}, //
|
||||
".erb": {Icon: "\ue73b", Color: 160}, //
|
||||
".erl": {Icon: "\ue7b1", Color: 163}, //
|
||||
".ex": {Icon: "\ue62d", Color: 140}, //
|
||||
".exe": {Icon: "\uf17a", Color: 81}, //
|
||||
".exs": {Icon: "\ue62d", Color: 140}, //
|
||||
".fish": {Icon: "\ue795", Color: 249}, //
|
||||
".flac": {Icon: "\uf001", Color: 241}, //
|
||||
".flv": {Icon: "\uf03d", Color: 241}, //
|
||||
".font": {Icon: "\uf031", Color: 241}, //
|
||||
".fs": {Icon: "\ue7a7", Color: 74}, //
|
||||
".fsi": {Icon: "\ue7a7", Color: 74}, //
|
||||
".fsx": {Icon: "\ue7a7", Color: 74}, //
|
||||
".gdoc": {Icon: "\uf1c2", Color: 40}, //
|
||||
".gem": {Icon: "\ue21e", Color: 160}, //
|
||||
".gemfile": {Icon: "\ue21e", Color: 160}, //
|
||||
".gemspec": {Icon: "\ue21e", Color: 160}, //
|
||||
".gform": {Icon: "\uf298", Color: 40}, //
|
||||
".gif": {Icon: "\uf1c5", Color: 140}, //
|
||||
".git": {Icon: "\uf1d3", Color: 202}, //
|
||||
".gitattributes": {Icon: "\uf1d3", Color: 202}, //
|
||||
".gitignore": {Icon: "\uf1d3", Color: 202}, //
|
||||
".gitmodules": {Icon: "\uf1d3", Color: 202}, //
|
||||
".go": {Icon: "\ue627", Color: 74}, //
|
||||
".gradle": {Icon: "\ue256", Color: 168}, //
|
||||
".groovy": {Icon: "\ue775", Color: 24}, //
|
||||
".gsheet": {Icon: "\uf1c3", Color: 10}, //
|
||||
".gslides": {Icon: "\uf1c4", Color: 226}, //
|
||||
".guardfile": {Icon: "\ue21e", Color: 241}, //
|
||||
".gz": {Icon: "\uf410", Color: 241}, //
|
||||
".h": {Icon: "\uf0fd", Color: 140}, //
|
||||
".hbs": {Icon: "\ue60f", Color: 202}, //
|
||||
".hpp": {Icon: "\uf0fd", Color: 140}, //
|
||||
".hs": {Icon: "\ue777", Color: 140}, //
|
||||
".htm": {Icon: "\uf13b", Color: 196}, //
|
||||
".html": {Icon: "\uf13b", Color: 196}, //
|
||||
".hxx": {Icon: "\uf0fd", Color: 140}, //
|
||||
".ico": {Icon: "\uf1c5", Color: 185}, //
|
||||
".image": {Icon: "\uf1c5", Color: 185}, //
|
||||
".iml": {Icon: "\ue7b5", Color: 239}, //
|
||||
".ini": {Icon: "\uf17a", Color: 81}, //
|
||||
".ipynb": {Icon: "\ue606", Color: 214}, //
|
||||
".iso": {Icon: "\ue271", Color: 239}, //
|
||||
".j2c": {Icon: "\uf1c5", Color: 239}, //
|
||||
".j2k": {Icon: "\uf1c5", Color: 239}, //
|
||||
".jad": {Icon: "\ue256", Color: 168}, //
|
||||
".jar": {Icon: "\ue256", Color: 168}, //
|
||||
".java": {Icon: "\ue256", Color: 168}, //
|
||||
".jfi": {Icon: "\uf1c5", Color: 241}, //
|
||||
".jfif": {Icon: "\uf1c5", Color: 241}, //
|
||||
".jif": {Icon: "\uf1c5", Color: 241}, //
|
||||
".jl": {Icon: "\ue624", Color: 241}, //
|
||||
".jmd": {Icon: "\uf48a", Color: 74}, //
|
||||
".jp2": {Icon: "\uf1c5", Color: 241}, //
|
||||
".jpe": {Icon: "\uf1c5", Color: 241}, //
|
||||
".jpeg": {Icon: "\uf1c5", Color: 241}, //
|
||||
".jpg": {Icon: "\uf1c5", Color: 241}, //
|
||||
".jpx": {Icon: "\uf1c5", Color: 241}, //
|
||||
".js": {Icon: "\ue74e", Color: 185}, //
|
||||
".json": {Icon: "\ue60b", Color: 185}, //
|
||||
".jsx": {Icon: "\ue7ba", Color: 45}, //
|
||||
".jxl": {Icon: "\uf1c5", Color: 241}, //
|
||||
".ksh": {Icon: "\ue795", Color: 241}, //
|
||||
".kt": {Icon: "\ue634", Color: 99}, //
|
||||
".kts": {Icon: "\ue634", Color: 99}, //
|
||||
".latex": {Icon: "\uf034", Color: 241}, //
|
||||
".less": {Icon: "\ue758", Color: 54}, //
|
||||
".lhs": {Icon: "\ue777", Color: 140}, //
|
||||
".license": {Icon: "\U000f0219", Color: 185}, //
|
||||
".localized": {Icon: "\uf179", Color: 15}, //
|
||||
".lock": {Icon: "\uf023", Color: 241}, //
|
||||
".log": {Icon: "\uf18d", Color: 188}, //
|
||||
".lua": {Icon: "\ue620", Color: 74}, //
|
||||
".lz": {Icon: "\uf410", Color: 241}, //
|
||||
".lz4": {Icon: "\uf410", Color: 241}, //
|
||||
".lzh": {Icon: "\uf410", Color: 241}, //
|
||||
".lzma": {Icon: "\uf410", Color: 241}, //
|
||||
".lzo": {Icon: "\uf410", Color: 241}, //
|
||||
".m": {Icon: "\ue61e", Color: 111}, //
|
||||
".mm": {Icon: "\ue61d", Color: 111}, //
|
||||
".m4a": {Icon: "\uf001", Color: 239}, //
|
||||
".markdown": {Icon: "\uf48a", Color: 74}, //
|
||||
".md": {Icon: "\uf48a", Color: 74}, //
|
||||
".mdx": {Icon: "\uf48a", Color: 74}, //
|
||||
".mjs": {Icon: "\ue74e", Color: 185}, //
|
||||
".mk": {Icon: "\ue795", Color: 241}, //
|
||||
".mkd": {Icon: "\uf48a", Color: 74}, //
|
||||
".mkv": {Icon: "\uf03d", Color: 241}, //
|
||||
".mobi": {Icon: "\ue28b", Color: 241}, //
|
||||
".mov": {Icon: "\uf03d", Color: 241}, //
|
||||
".mp3": {Icon: "\uf001", Color: 241}, //
|
||||
".mp4": {Icon: "\uf03d", Color: 241}, //
|
||||
".msi": {Icon: "\ue70f", Color: 241}, //
|
||||
".mustache": {Icon: "\ue60f", Color: 241}, //
|
||||
".nix": {Icon: "\uf313", Color: 111}, //
|
||||
".node": {Icon: "\U000f0399", Color: 197}, //
|
||||
".npmignore": {Icon: "\ue71e", Color: 197}, //
|
||||
".odp": {Icon: "\uf1c4", Color: 241}, //
|
||||
".ods": {Icon: "\uf1c3", Color: 241}, //
|
||||
".odt": {Icon: "\uf1c2", Color: 241}, //
|
||||
".ogg": {Icon: "\uf001", Color: 241}, //
|
||||
".ogv": {Icon: "\uf03d", Color: 241}, //
|
||||
".otf": {Icon: "\uf031", Color: 241}, //
|
||||
".part": {Icon: "\uf43a", Color: 241}, //
|
||||
".patch": {Icon: "\uf440", Color: 241}, //
|
||||
".pdf": {Icon: "\uf1c1", Color: 124}, //
|
||||
".php": {Icon: "\ue73d", Color: 61}, //
|
||||
".pl": {Icon: "\ue769", Color: 74}, //
|
||||
".png": {Icon: "\uf1c5", Color: 241}, //
|
||||
".ppt": {Icon: "\uf1c4", Color: 241}, //
|
||||
".pptx": {Icon: "\uf1c4", Color: 241}, //
|
||||
".procfile": {Icon: "\ue21e", Color: 241}, //
|
||||
".properties": {Icon: "\ue60b", Color: 185}, //
|
||||
".ps1": {Icon: "\ue795", Color: 241}, //
|
||||
".psd": {Icon: "\ue7b8", Color: 241}, //
|
||||
".pxm": {Icon: "\uf1c5", Color: 241}, //
|
||||
".py": {Icon: "\ue606", Color: 214}, //
|
||||
".pyc": {Icon: "\ue606", Color: 214}, //
|
||||
".r": {Icon: "\uf25d", Color: 68}, //
|
||||
".rakefile": {Icon: "\ue21e", Color: 160}, //
|
||||
".rar": {Icon: "\uf410", Color: 241}, //
|
||||
".razor": {Icon: "\uf1fa", Color: 81}, //
|
||||
".rb": {Icon: "\ue21e", Color: 160}, //
|
||||
".rdata": {Icon: "\uf25d", Color: 68}, //
|
||||
".rdb": {Icon: "\ue76d", Color: 160}, //
|
||||
".rdoc": {Icon: "\uf48a", Color: 74}, //
|
||||
".rds": {Icon: "\uf25d", Color: 68}, //
|
||||
".readme": {Icon: "\uf48a", Color: 74}, //
|
||||
".rlib": {Icon: "\ue7a8", Color: 216}, //
|
||||
".rmd": {Icon: "\uf48a", Color: 74}, //
|
||||
".rpm": {Icon: "\ue7bb", Color: 52}, //
|
||||
".rs": {Icon: "\ue7a8", Color: 216}, //
|
||||
".rspec": {Icon: "\ue21e", Color: 160}, //
|
||||
".rspec_parallel": {Icon: "\ue21e", Color: 160}, //
|
||||
".rspec_status": {Icon: "\ue21e", Color: 160}, //
|
||||
".rss": {Icon: "\uf09e", Color: 130}, //
|
||||
".rtf": {Icon: "\U000f0219", Color: 241}, //
|
||||
".ru": {Icon: "\ue21e", Color: 160}, //
|
||||
".rubydoc": {Icon: "\ue73b", Color: 160}, //
|
||||
".sass": {Icon: "\ue603", Color: 169}, //
|
||||
".scala": {Icon: "\ue737", Color: 74}, //
|
||||
".scss": {Icon: "\ue749", Color: 204}, //
|
||||
".sh": {Icon: "\ue795", Color: 239}, //
|
||||
".shell": {Icon: "\ue795", Color: 239}, //
|
||||
".slim": {Icon: "\ue73b", Color: 160}, //
|
||||
".sln": {Icon: "\ue70c", Color: 39}, //
|
||||
".so": {Icon: "\uf17c", Color: 241}, //
|
||||
".sql": {Icon: "\uf1c0", Color: 188}, //
|
||||
".sqlite3": {Icon: "\ue7c4", Color: 25}, //
|
||||
".sty": {Icon: "\uf034", Color: 239}, //
|
||||
".styl": {Icon: "\ue600", Color: 148}, //
|
||||
".stylus": {Icon: "\ue600", Color: 148}, //
|
||||
".svelte": {Icon: "\ue697", Color: 208}, //
|
||||
".svg": {Icon: "\uf1c5", Color: 241}, //
|
||||
".swift": {Icon: "\ue755", Color: 208}, //
|
||||
".tar": {Icon: "\uf410", Color: 241}, //
|
||||
".taz": {Icon: "\uf410", Color: 241}, //
|
||||
".tbz": {Icon: "\uf410", Color: 241}, //
|
||||
".tbz2": {Icon: "\uf410", Color: 241}, //
|
||||
".tex": {Icon: "\uf034", Color: 79}, //
|
||||
".tgz": {Icon: "\uf410", Color: 241}, //
|
||||
".tiff": {Icon: "\uf1c5", Color: 241}, //
|
||||
".tlz": {Icon: "\uf410", Color: 241}, //
|
||||
".toml": {Icon: "\ue615", Color: 241}, //
|
||||
".torrent": {Icon: "\ue275", Color: 76}, //
|
||||
".ts": {Icon: "\ue628", Color: 74}, //
|
||||
".tsv": {Icon: "\uf1c3", Color: 241}, //
|
||||
".tsx": {Icon: "\ue7ba", Color: 74}, //
|
||||
".ttf": {Icon: "\uf031", Color: 241}, //
|
||||
".twig": {Icon: "\ue61c", Color: 241}, //
|
||||
".txt": {Icon: "\uf15c", Color: 241}, //
|
||||
".txz": {Icon: "\uf410", Color: 241}, //
|
||||
".tz": {Icon: "\uf410", Color: 241}, //
|
||||
".tzo": {Icon: "\uf410", Color: 241}, //
|
||||
".video": {Icon: "\uf03d", Color: 241}, //
|
||||
".vim": {Icon: "\ue62b", Color: 28}, //
|
||||
".vue": {Icon: "\U000f0844", Color: 113}, //
|
||||
".war": {Icon: "\ue256", Color: 168}, //
|
||||
".wav": {Icon: "\uf001", Color: 241}, //
|
||||
".webm": {Icon: "\uf03d", Color: 241}, //
|
||||
".webp": {Icon: "\uf1c5", Color: 241}, //
|
||||
".windows": {Icon: "\uf17a", Color: 81}, //
|
||||
".woff": {Icon: "\uf031", Color: 241}, //
|
||||
".woff2": {Icon: "\uf031", Color: 241}, //
|
||||
".xhtml": {Icon: "\uf13b", Color: 196}, //
|
||||
".xls": {Icon: "\uf1c3", Color: 34}, //
|
||||
".xlsx": {Icon: "\uf1c3", Color: 34}, //
|
||||
".xml": {Icon: "\uf121", Color: 160}, //
|
||||
".xul": {Icon: "\uf121", Color: 166}, //
|
||||
".xz": {Icon: "\uf410", Color: 241}, //
|
||||
".yaml": {Icon: "\uf481", Color: 160}, //
|
||||
".yml": {Icon: "\uf481", Color: 160}, //
|
||||
".zip": {Icon: "\uf410", Color: 241}, //
|
||||
".zsh": {Icon: "\ue795", Color: 241}, //
|
||||
".zsh-theme": {Icon: "\ue795", Color: 241}, //
|
||||
".zshrc": {Icon: "\ue795", Color: 241}, //
|
||||
".zst": {Icon: "\uf410", Color: 241}, //
|
||||
}
|
||||
|
||||
func patchFileIconsForNerdFontsV2() {
|
||||
extIconMap[".cs"] = "\uf81a" //
|
||||
extIconMap[".csproj"] = "\uf81a" //
|
||||
extIconMap[".csx"] = "\uf81a" //
|
||||
extIconMap[".license"] = "\uf718" //
|
||||
extIconMap[".node"] = "\uf898" //
|
||||
extIconMap[".rtf"] = "\uf718" //
|
||||
extIconMap[".vue"] = "\ufd42" // ﵂
|
||||
extIconMap[".cs"] = IconProperties{Icon: "\uf81a", Color: 58} //
|
||||
extIconMap[".csproj"] = IconProperties{Icon: "\uf81a", Color: 58} //
|
||||
extIconMap[".csx"] = IconProperties{Icon: "\uf81a", Color: 58} //
|
||||
extIconMap[".license"] = IconProperties{Icon: "\uf718", Color: 241} //
|
||||
extIconMap[".node"] = IconProperties{Icon: "\uf898", Color: 197} //
|
||||
extIconMap[".rtf"] = IconProperties{Icon: "\uf718", Color: 241} //
|
||||
extIconMap[".vue"] = IconProperties{Icon: "\ufd42", Color: 113} // ﵂
|
||||
}
|
||||
|
||||
func IconForFile(name string, isSubmodule bool, isLinkedWorktree bool, isDirectory bool) string {
|
||||
func IconForFile(name string, isSubmodule bool, isLinkedWorktree bool, isDirectory bool) IconProperties {
|
||||
base := filepath.Base(name)
|
||||
if icon, ok := nameIconMap[base]; ok {
|
||||
return icon
|
||||
@@ -339,7 +340,7 @@ func IconForFile(name string, isSubmodule bool, isLinkedWorktree bool, isDirecto
|
||||
if isSubmodule {
|
||||
return DEFAULT_SUBMODULE_ICON
|
||||
} else if isLinkedWorktree {
|
||||
return LINKED_WORKTREE_ICON
|
||||
return IconProperties{LINKED_WORKTREE_ICON, 239}
|
||||
} else if isDirectory {
|
||||
return DEFAULT_DIRECTORY_ICON
|
||||
}
|
||||
|
||||
@@ -6,6 +6,11 @@ import (
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type IconProperties struct {
|
||||
Icon string
|
||||
Color uint8
|
||||
}
|
||||
|
||||
var isIconEnabled = false
|
||||
|
||||
func IsIconEnabled() bool {
|
||||
@@ -13,14 +18,18 @@ func IsIconEnabled() bool {
|
||||
}
|
||||
|
||||
func SetNerdFontsVersion(version string) {
|
||||
if !lo.Contains([]string{"2", "3"}, version) {
|
||||
log.Fatalf("Unsupported nerdFontVersion %s", version)
|
||||
}
|
||||
if version == "" {
|
||||
isIconEnabled = false
|
||||
} else {
|
||||
if !lo.Contains([]string{"2", "3"}, version) {
|
||||
log.Fatalf("Unsupported nerdFontVersion %s", version)
|
||||
}
|
||||
|
||||
if version == "2" {
|
||||
patchGitIconsForNerdFontsV2()
|
||||
patchFileIconsForNerdFontsV2()
|
||||
}
|
||||
if version == "2" {
|
||||
patchGitIconsForNerdFontsV2()
|
||||
patchFileIconsForNerdFontsV2()
|
||||
}
|
||||
|
||||
isIconEnabled = true
|
||||
isIconEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package presentation
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/i18n"
|
||||
)
|
||||
|
||||
func itemOperationToString(itemOperation types.ItemOperation, tr *i18n.TranslationSet) string {
|
||||
switch itemOperation {
|
||||
case types.ItemOperationNone:
|
||||
return ""
|
||||
case types.ItemOperationPushing:
|
||||
return tr.PushingStatus
|
||||
case types.ItemOperationPulling:
|
||||
return tr.PullingStatus
|
||||
case types.ItemOperationFastForwarding:
|
||||
return tr.FastForwarding
|
||||
case types.ItemOperationDeleting:
|
||||
return tr.DeletingStatus
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
@@ -7,14 +7,15 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation/icons"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/i18n"
|
||||
)
|
||||
|
||||
func FormatStatus(repoName string, currentBranch *models.Branch, linkedWorktreeName string, workingTreeState enums.RebaseMode, tr *i18n.TranslationSet) string {
|
||||
func FormatStatus(repoName string, currentBranch *models.Branch, itemOperation types.ItemOperation, linkedWorktreeName string, workingTreeState enums.RebaseMode, tr *i18n.TranslationSet) string {
|
||||
status := ""
|
||||
|
||||
if currentBranch.IsRealBranch() {
|
||||
status += ColoredBranchStatus(currentBranch, tr) + " "
|
||||
status += ColoredBranchStatus(currentBranch, itemOperation, tr) + " "
|
||||
}
|
||||
|
||||
if workingTreeState != enums.REBASE_MODE_NONE {
|
||||
|
||||
@@ -1,22 +1,32 @@
|
||||
package presentation
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation/icons"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/i18n"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
func GetTagListDisplayStrings(tags []*models.Tag, diffName string) [][]string {
|
||||
func GetTagListDisplayStrings(
|
||||
tags []*models.Tag,
|
||||
getItemOperation func(item types.HasUrn) types.ItemOperation,
|
||||
diffName string,
|
||||
tr *i18n.TranslationSet,
|
||||
) [][]string {
|
||||
return lo.Map(tags, func(tag *models.Tag, _ int) []string {
|
||||
diffed := tag.Name == diffName
|
||||
return getTagDisplayStrings(tag, diffed)
|
||||
return getTagDisplayStrings(tag, getItemOperation(tag), diffed, tr)
|
||||
})
|
||||
}
|
||||
|
||||
// getTagDisplayStrings returns the display string of branch
|
||||
func getTagDisplayStrings(t *models.Tag, diffed bool) []string {
|
||||
func getTagDisplayStrings(t *models.Tag, itemOperation types.ItemOperation, diffed bool, tr *i18n.TranslationSet) []string {
|
||||
textStyle := theme.DefaultTextColor
|
||||
if diffed {
|
||||
textStyle = theme.DiffTerminalColor
|
||||
@@ -26,6 +36,11 @@ func getTagDisplayStrings(t *models.Tag, diffed bool) []string {
|
||||
res = append(res, textStyle.Sprint(icons.IconForTag(t)))
|
||||
}
|
||||
descriptionColor := style.FgYellow
|
||||
res = append(res, textStyle.Sprint(t.Name), descriptionColor.Sprint(t.Description()))
|
||||
descriptionStr := descriptionColor.Sprint(t.Description())
|
||||
itemOperationStr := itemOperationToString(itemOperation, tr)
|
||||
if itemOperationStr != "" {
|
||||
descriptionStr = style.FgCyan.Sprint(itemOperationStr+" "+utils.Loader(time.Now())) + " " + descriptionStr
|
||||
}
|
||||
res = append(res, textStyle.Sprint(t.Name), descriptionStr)
|
||||
return res
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ func (self *StatusManager) GetStatusString() string {
|
||||
}
|
||||
topStatus := self.statuses[0]
|
||||
if topStatus.statusType == "waiting" {
|
||||
return topStatus.message + " " + utils.Loader()
|
||||
return topStatus.message + " " + utils.Loader(time.Now())
|
||||
}
|
||||
return topStatus.message
|
||||
}
|
||||
|
||||
@@ -45,10 +45,12 @@ func (gui *Gui) handleTestMode() {
|
||||
log.Fatal("gocui should have already exited")
|
||||
}()
|
||||
|
||||
go utils.Safe(func() {
|
||||
time.Sleep(time.Second * 40)
|
||||
log.Fatal("40 seconds is up, lazygit recording took too long to complete")
|
||||
})
|
||||
if os.Getenv(components.WAIT_FOR_DEBUGGER_ENV_VAR) == "" {
|
||||
go utils.Safe(func() {
|
||||
time.Sleep(time.Second * 40)
|
||||
log.Fatal("40 seconds is up, lazygit recording took too long to complete")
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+27
-1
@@ -87,6 +87,12 @@ type IGuiCommon interface {
|
||||
// resized, if in accordion mode.
|
||||
AfterLayout(f func() error)
|
||||
|
||||
// Wraps a function, attaching the given operation to the given item while
|
||||
// the function is executing, and also causes the given context to be
|
||||
// redrawn periodically. This allows the operation to be visualized with a
|
||||
// spinning loader animation (e.g. when a branch is being pushed).
|
||||
WithInlineStatus(item HasUrn, operation ItemOperation, contextKey ContextKey, f func(gocui.Task) error) error
|
||||
|
||||
// returns the gocui Gui struct. There is a good chance you don't actually want to use
|
||||
// this struct and instead want to use another method above
|
||||
GocuiGui() *gocui.Gui
|
||||
@@ -256,7 +262,6 @@ type Mutexes struct {
|
||||
RefreshingFilesMutex *deadlock.Mutex
|
||||
RefreshingBranchesMutex *deadlock.Mutex
|
||||
RefreshingStatusMutex *deadlock.Mutex
|
||||
SyncMutex *deadlock.Mutex
|
||||
LocalCommitsMutex *deadlock.Mutex
|
||||
SubCommitsMutex *deadlock.Mutex
|
||||
AuthorsMutex *deadlock.Mutex
|
||||
@@ -265,6 +270,24 @@ type Mutexes struct {
|
||||
PtyMutex *deadlock.Mutex
|
||||
}
|
||||
|
||||
// A long-running operation associated with an item. For example, we'll show
|
||||
// that a branch is being pushed from so that there's visual feedback about
|
||||
// what's happening and so that you can see multiple branches' concurrent
|
||||
// operations
|
||||
type ItemOperation int
|
||||
|
||||
const (
|
||||
ItemOperationNone ItemOperation = iota
|
||||
ItemOperationPushing
|
||||
ItemOperationPulling
|
||||
ItemOperationFastForwarding
|
||||
ItemOperationDeleting
|
||||
)
|
||||
|
||||
type HasUrn interface {
|
||||
URN() string
|
||||
}
|
||||
|
||||
type IStateAccessor interface {
|
||||
GetRepoPathStack() *utils.StringStack
|
||||
GetRepoState() IRepoStateAccessor
|
||||
@@ -277,6 +300,9 @@ type IStateAccessor interface {
|
||||
SetShowExtrasWindow(bool)
|
||||
GetRetainOriginalDir() bool
|
||||
SetRetainOriginalDir(bool)
|
||||
GetItemOperation(item HasUrn) ItemOperation
|
||||
SetItemOperation(item HasUrn, operation ItemOperation)
|
||||
ClearItemOperation(item HasUrn)
|
||||
}
|
||||
|
||||
type IRepoStateAccessor interface {
|
||||
|
||||
@@ -60,6 +60,9 @@ type IBaseContext interface {
|
||||
// determined independently.
|
||||
HasControlledBounds() bool
|
||||
|
||||
// true if the view needs to be rerendered when its width changes
|
||||
NeedsRerenderOnWidthChange() bool
|
||||
|
||||
// returns the desired title for the view upon activation. If there is no desired title (returns empty string), then
|
||||
// no title will be set
|
||||
Title() string
|
||||
@@ -102,6 +105,7 @@ type IFilterableContext interface {
|
||||
SetFilter(string)
|
||||
GetFilter() string
|
||||
ClearFilter()
|
||||
ReApplyFilter()
|
||||
IsFiltering() bool
|
||||
IsFilterableContext()
|
||||
}
|
||||
|
||||
+1
-1
@@ -169,7 +169,7 @@ func chineseTranslationSet() TranslationSet {
|
||||
ToggleStagingPanel: `切换到其他面板`,
|
||||
ReturnToFilesPanel: `返回文件面板`,
|
||||
FastForward: `从上游快进此分支`,
|
||||
FastForwarding: "抓取并快进 {{.branch}} ...",
|
||||
FastForwarding: "抓取并快进",
|
||||
FoundConflictsTitle: "自动合并失败",
|
||||
ViewMergeRebaseOptions: "查看 合并/变基 选项",
|
||||
NotMergingOrRebasing: "您目前既不进行变基也不进行合并",
|
||||
|
||||
+1
-1
@@ -134,7 +134,7 @@ func dutchTranslationSet() TranslationSet {
|
||||
ToggleStagingPanel: `Ga naar een ander paneel`,
|
||||
ReturnToFilesPanel: `Ga terug naar het bestanden paneel`,
|
||||
FastForward: `Fast-forward deze branch vanaf zijn upstream`,
|
||||
FastForwarding: "Fast-forwarding {{.branch}} ...",
|
||||
FastForwarding: "Fast-forwarding",
|
||||
FoundConflictsTitle: "Conflicten!",
|
||||
ViewMergeRebaseOptions: "Bekijk merge/rebase opties",
|
||||
NotMergingOrRebasing: "Je bent momenteel niet aan het rebasen of mergen",
|
||||
|
||||
+5
-1
@@ -57,6 +57,8 @@ type TranslationSet struct {
|
||||
ResetFilter string
|
||||
MergeConflictsTitle string
|
||||
Checkout string
|
||||
CantCheckoutBranchWhilePulling string
|
||||
CantPullOrPushSameBranchTwice string
|
||||
NoChangedFiles string
|
||||
SoftReset string
|
||||
AlreadyCheckedOutBranch string
|
||||
@@ -846,6 +848,8 @@ func EnglishTranslationSet() TranslationSet {
|
||||
Scroll: "Scroll",
|
||||
MergeConflictsTitle: "Merge conflicts",
|
||||
Checkout: "Checkout",
|
||||
CantCheckoutBranchWhilePulling: "You cannot checkout another branch while pulling the current branch",
|
||||
CantPullOrPushSameBranchTwice: "You cannot push or pull a branch while it is already being pushed or pulled",
|
||||
FileFilter: "Filter files by status",
|
||||
FilterStagedFiles: "Show only staged files",
|
||||
FilterUnstagedFiles: "Show only unstaged files",
|
||||
@@ -980,7 +984,7 @@ func EnglishTranslationSet() TranslationSet {
|
||||
ToggleStagingPanel: `Switch to other panel (staged/unstaged changes)`,
|
||||
ReturnToFilesPanel: `Return to files panel`,
|
||||
FastForward: `Fast-forward this branch from its upstream`,
|
||||
FastForwarding: "Fast-forwarding {{.branch}}",
|
||||
FastForwarding: "Fast-forwarding",
|
||||
FoundConflictsTitle: "Conflicts!",
|
||||
ViewConflictsMenuItem: "View conflicts",
|
||||
AbortMenuItem: "Abort the %s",
|
||||
|
||||
+1
-1
@@ -170,7 +170,7 @@ func koreanTranslationSet() TranslationSet {
|
||||
ToggleStagingPanel: `패널 전환`,
|
||||
ReturnToFilesPanel: `파일 목록으로 돌아가기`,
|
||||
FastForward: `Fast-forward this branch from its upstream`,
|
||||
FastForwarding: "Fast-forwarding {{.branch}} ...",
|
||||
FastForwarding: "Fast-forwarding",
|
||||
FoundConflictsTitle: "Auto-merge failed",
|
||||
ViewMergeRebaseOptions: "View merge/rebase options",
|
||||
NotMergingOrRebasing: "You are currently neither rebasing nor merging",
|
||||
|
||||
+1
-1
@@ -201,7 +201,7 @@ func RussianTranslationSet() TranslationSet {
|
||||
ToggleStagingPanel: `Переключиться на другую панель (проиндексированные/непроиндексированные изменения)`,
|
||||
ReturnToFilesPanel: `Вернуться к панели файлов`,
|
||||
FastForward: `Перемотать эту ветку вперёд из её upstream-ветки`,
|
||||
FastForwarding: "Получить изменения и перемотать вперёд {{.branch}} ...",
|
||||
FastForwarding: "Получить изменения и перемотать вперёд",
|
||||
FoundConflictsTitle: "Конфликты!",
|
||||
ViewConflictsMenuItem: "Просмотр конфликтов",
|
||||
AbortMenuItem: "Прервать %s",
|
||||
|
||||
@@ -234,7 +234,7 @@ func traditionalChineseTranslationSet() TranslationSet {
|
||||
ToggleStagingPanel: `切換至另一個面板 (已預存/未預存更改)`,
|
||||
ReturnToFilesPanel: `返回檔案面板`,
|
||||
FastForward: `從上游快進此分支`,
|
||||
FastForwarding: "{{.branch}} 的擷取和快進中...",
|
||||
FastForwarding: "的擷取和快進中",
|
||||
FoundConflictsTitle: "自動合併失敗",
|
||||
ViewMergeRebaseOptions: "查看合併/變基選項",
|
||||
NotMergingOrRebasing: "你當前既不在變基也不在合併中",
|
||||
|
||||
@@ -31,7 +31,7 @@ func main() {
|
||||
|
||||
integrationTest := getIntegrationTest()
|
||||
|
||||
if os.Getenv("WAIT_FOR_DEBUGGER") != "" {
|
||||
if os.Getenv(components.WAIT_FOR_DEBUGGER_ENV_VAR) != "" {
|
||||
println("Waiting for debugger to attach...")
|
||||
for !isDebuggerAttached() {
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
const (
|
||||
TEST_NAME_ENV_VAR = "TEST_NAME"
|
||||
SANDBOX_ENV_VAR = "SANDBOX"
|
||||
WAIT_FOR_DEBUGGER_ENV_VAR = "WAIT_FOR_DEBUGGER"
|
||||
GIT_CONFIG_GLOBAL_ENV_VAR = "GIT_CONFIG_GLOBAL"
|
||||
)
|
||||
|
||||
@@ -42,7 +43,7 @@ func RunTests(
|
||||
|
||||
testDir := filepath.Join(projectRootDir, "test", "_results")
|
||||
|
||||
if err := buildLazygit(raceDetector); err != nil {
|
||||
if err := buildLazygit(waitForDebugger, raceDetector); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -138,12 +139,17 @@ func prepareTestDir(
|
||||
return createFixture(test, paths, rootDir)
|
||||
}
|
||||
|
||||
func buildLazygit(raceDetector bool) error {
|
||||
func buildLazygit(debug bool, raceDetector bool) error {
|
||||
// // TODO: remove this line!
|
||||
// // skipping this because I'm not making changes to the app code atm.
|
||||
// return nil
|
||||
|
||||
args := []string{"go", "build"}
|
||||
if debug {
|
||||
// Disable compiler optimizations (-N) and inlining (-l) because this
|
||||
// makes debugging work better
|
||||
args = append(args, "-gcflags=all=-N -l")
|
||||
}
|
||||
if raceDetector {
|
||||
args = append(args, "-race")
|
||||
}
|
||||
@@ -210,7 +216,7 @@ func getLazygitCommand(test *IntegrationTest, paths Paths, rootDir string, sandb
|
||||
cmdObj.AddEnvVars(fmt.Sprintf("%s=%s", SANDBOX_ENV_VAR, "true"))
|
||||
}
|
||||
if waitForDebugger {
|
||||
cmdObj.AddEnvVars("WAIT_FOR_DEBUGGER=true")
|
||||
cmdObj.AddEnvVars(fmt.Sprintf("%s=true", WAIT_FOR_DEBUGGER_ENV_VAR))
|
||||
}
|
||||
// Set a race detector log path only to avoid spamming the terminal with the
|
||||
// logs. We are not showing this anywhere yet.
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
const unitTestDescription = "test test"
|
||||
|
||||
const (
|
||||
defaultWidth = 100
|
||||
defaultWidth = 150
|
||||
defaultHeight = 100
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package filter_and_search
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var FilterRemotes = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Filtering remotes",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.EmptyCommit("commit-one")
|
||||
shell.CloneIntoRemote("remote1")
|
||||
shell.CloneIntoRemote("remote2")
|
||||
shell.CloneIntoRemote("remote3")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Remotes().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("remote1").IsSelected(),
|
||||
Contains("remote2"),
|
||||
Contains("remote3"),
|
||||
).
|
||||
FilterOrSearch("2").
|
||||
Lines(
|
||||
Contains("remote2").IsSelected(),
|
||||
).
|
||||
// cancel the filter
|
||||
PressEscape().
|
||||
Tap(func() {
|
||||
t.Views().Search().IsInvisible()
|
||||
}).
|
||||
Lines(
|
||||
Contains("remote1"),
|
||||
Contains("remote2").IsSelected(),
|
||||
Contains("remote3"),
|
||||
)
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,52 @@
|
||||
package filter_and_search
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var FilterUpdatesWhenModelChanges = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Verify that after deleting a branch the filter is reapplied to show only the remaining branches",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.EmptyCommit("first commit")
|
||||
shell.NewBranch("branch-to-delete")
|
||||
shell.NewBranch("other")
|
||||
shell.NewBranch("checked-out-branch")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Branches().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("checked-out-branch").IsSelected(),
|
||||
Contains("other"),
|
||||
Contains("branch-to-delete"),
|
||||
Contains("master"),
|
||||
).
|
||||
FilterOrSearch("branch").
|
||||
Lines(
|
||||
Contains("branch-to-delete").IsSelected(),
|
||||
Contains("checked-out-branch"),
|
||||
).
|
||||
Press(keys.Universal.Remove).
|
||||
Tap(func() {
|
||||
t.ExpectPopup().
|
||||
Menu().
|
||||
Title(Equals("Delete branch 'branch-to-delete'?")).
|
||||
Select(Contains("Delete local branch")).
|
||||
Confirm()
|
||||
}).
|
||||
Lines(
|
||||
Contains("checked-out-branch").IsSelected(),
|
||||
).
|
||||
// cancel the filter
|
||||
PressEscape().
|
||||
Lines(
|
||||
Contains("checked-out-branch").IsSelected(),
|
||||
Contains("other"),
|
||||
Contains("master"),
|
||||
)
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,26 @@
|
||||
package misc
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var DisabledKeybindings = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Confirms you can disable keybindings by setting them to <disabled>",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {
|
||||
config.UserConfig.Keybinding.Universal.PrevItem = "<disabled>"
|
||||
config.UserConfig.Keybinding.Universal.NextItem = "<disabled>"
|
||||
config.UserConfig.Keybinding.Universal.NextTab = "<up>"
|
||||
config.UserConfig.Keybinding.Universal.PrevTab = "<down>"
|
||||
},
|
||||
SetupRepo: func(shell *Shell) {},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Files().
|
||||
IsFocused().
|
||||
Press("<up>")
|
||||
|
||||
t.Views().Worktrees().IsFocused()
|
||||
},
|
||||
})
|
||||
@@ -131,7 +131,9 @@ var tests = []*components.IntegrationTest{
|
||||
filter_and_search.FilterMenu,
|
||||
filter_and_search.FilterMenuCancelFilterWithEscape,
|
||||
filter_and_search.FilterRemoteBranches,
|
||||
filter_and_search.FilterRemotes,
|
||||
filter_and_search.FilterSearchHistory,
|
||||
filter_and_search.FilterUpdatesWhenModelChanges,
|
||||
filter_and_search.NestedFilter,
|
||||
filter_and_search.NestedFilterTransient,
|
||||
filter_and_search.NewSearch,
|
||||
@@ -170,6 +172,7 @@ var tests = []*components.IntegrationTest{
|
||||
interactive_rebase.SwapWithConflict,
|
||||
misc.ConfirmOnQuit,
|
||||
misc.CopyToClipboard,
|
||||
misc.DisabledKeybindings,
|
||||
misc.InitialOpen,
|
||||
misc.RecentReposOnLaunch,
|
||||
patch_building.Apply,
|
||||
|
||||
@@ -19,6 +19,8 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
println("Generating test_list.go...")
|
||||
|
||||
code := generateCode()
|
||||
|
||||
formattedCode, err := format.Source(code)
|
||||
|
||||
+1
-2
@@ -28,9 +28,8 @@ func GetProjectRoot() string {
|
||||
const LoaderAnimationInterval = 50
|
||||
|
||||
// Loader dumps a string to be displayed as a loader
|
||||
func Loader() string {
|
||||
func Loader(now time.Time) string {
|
||||
characters := "|/-\\"
|
||||
now := time.Now()
|
||||
milliseconds := now.UnixMilli()
|
||||
index := milliseconds / LoaderAnimationInterval % int64(len(characters))
|
||||
return characters[index : index+1]
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/cheatsheet"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
log.Fatal("Please provide a command: one of 'generate', 'check'")
|
||||
}
|
||||
|
||||
command := os.Args[1]
|
||||
|
||||
switch command {
|
||||
case "generate":
|
||||
cheatsheet.Generate()
|
||||
fmt.Printf("\nGenerated cheatsheets in %s\n", cheatsheet.GetKeybindingsDir())
|
||||
case "check":
|
||||
cheatsheet.Check()
|
||||
default:
|
||||
log.Fatal("\nUnknown command. Expected one of 'generate', 'check'")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user