Compare commits

..
Author SHA1 Message Date
Jesse Duffield 954a4d2fbb My commit summary
My commit description
2023-09-29 10:46:02 +10:00
84 changed files with 820 additions and 1616 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
- **Please check if the PR fulfills these requirements**
* [ ] Cheatsheets are up-to-date (run `go generate ./...`)
* [ ] Cheatsheets are up-to-date (run `go run scripts/cheatsheet/main.go generate`)
* [ ] Code has been formatted (see [here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#code-formatting))
* [ ] Tests have been added/updated (see [here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md) for the integration test guide)
* [ ] Text is internationalised (see [here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#internationalisation))
+6 -3
View File
@@ -127,14 +127,17 @@ 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 All Auto-Generated Files
# ensure all our auto-generated files are up to date
- name: Check Integration Test List
# ensure our integration test list is up to date
run: |
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)
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)
shell: bash # needed so that we get "-o pipefail"
- name: Check Filenames
run: scripts/check_filenames.sh
+1 -5
View File
@@ -20,11 +20,7 @@ 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
-5
View File
@@ -1,5 +0,0 @@
{
"gopls": {
"formatting.gofumpt": true,
},
}
-6
View File
@@ -21,8 +21,6 @@ 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
@@ -99,10 +97,6 @@ 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!).
+5 -2
View File
@@ -3,7 +3,7 @@ all: build
.PHONY: build
build:
go build -gcflags='all=-N -l'
go build
.PHONY: install
install:
@@ -29,7 +29,6 @@ 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 ./...
@@ -38,6 +37,10 @@ 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:
+1 -1
View File
File diff suppressed because one or more lines are too long
+4 -5
View File
@@ -85,7 +85,6 @@ 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
@@ -163,7 +162,7 @@ keybinding:
jumpToBlock: ['1', '2', '3', '4', '5'] # goto the Nth block / panel
nextMatch: 'n'
prevMatch: 'N'
optionMenu: <disabled> # show help menu
optionMenu: null # show help menu
optionMenu-alt1: '?' # show help menu
select: '<space>'
goInto: '<enter>'
@@ -319,7 +318,7 @@ os:
editPreset: 'vscode'
```
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.
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.
`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.
@@ -462,12 +461,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 `<disabled>`.
You can disable certain key bindings by specifying `null`.
```yaml
keybinding:
universal:
edit: <disabled> # disable 'edit file'
edit: null # disable 'edit file'
```
### Example Keybindings For Colemak Users
+1 -1
View File
@@ -1,4 +1,4 @@
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `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 run scripts/cheatsheet/main.go generate` from the project root._
# Lazygit Keybindings
+1 -1
View File
@@ -1,4 +1,4 @@
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `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 run scripts/cheatsheet/main.go generate` from the project root._
# Lazygit キーバインド
+1 -1
View File
@@ -1,4 +1,4 @@
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `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 run scripts/cheatsheet/main.go generate` from the project root._
# Lazygit 키 바인딩
+1 -1
View File
@@ -1,4 +1,4 @@
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `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 run scripts/cheatsheet/main.go generate` from the project root._
# Lazygit Sneltoetsen
+1 -1
View File
@@ -1,4 +1,4 @@
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `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 run scripts/cheatsheet/main.go generate` from the project root._
# Lazygit Keybindings
+1 -1
View File
@@ -1,4 +1,4 @@
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `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 run scripts/cheatsheet/main.go generate` from the project root._
# Lazygit Связки клавиш
+1 -1
View File
@@ -1,4 +1,4 @@
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `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 run scripts/cheatsheet/main.go generate` from the project root._
# Lazygit 按键绑定
+1 -1
View File
@@ -1,4 +1,4 @@
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `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 run scripts/cheatsheet/main.go generate` from the project root._
# Lazygit 鍵盤快捷鍵
+2 -2
View File
@@ -180,10 +180,10 @@ func parseCliArgsAndEnvVars() *cliArgs {
useConfigDir := ""
flaggy.String(&useConfigDir, "ucd", "use-config-dir", "override default config directory with provided directory")
workTree := os.Getenv("GIT_WORK_TREE")
workTree := ""
flaggy.String(&workTree, "w", "work-tree", "equivalent of the --work-tree git argument")
gitDir := os.Getenv("GIT_DIR")
gitDir := ""
flaggy.String(&gitDir, "g", "git-dir", "equivalent of the --git-dir git argument")
customConfigFile := ""
+77
View File
@@ -0,0 +1,77 @@
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
}
+6 -8
View File
@@ -1,12 +1,10 @@
//go:generate go run generator.go
// This "script" generates files called Keybindings_{{.LANG}}.md
// in the docs/keybindings directory.
// This "script" generates a file called Keybindings_{{.LANG}}.md
// in current working directory.
//
// The content of these generated files is a keybindings cheatsheet.
// The content of this generated file is a keybindings cheatsheet.
//
// To generate the cheatsheets, run:
// go generate pkg/cheatsheet/generate.go
// To generate cheatsheet in english run:
// go run scripts/generate_cheatsheet.go
package cheatsheet
@@ -44,7 +42,7 @@ type headerWithBindings struct {
}
func CommandToRun() string {
return "go generate ./..."
return "go run scripts/cheatsheet/main.go generate"
}
func GetKeybindingsDir() string {
-14
View File
@@ -1,14 +0,0 @@
//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()
}
+5 -1
View File
@@ -7,6 +7,7 @@ import (
"strings"
"github.com/go-errors/errors"
"github.com/sasha-s/go-deadlock"
"github.com/spf13/afero"
gogit "github.com/jesseduffield/go-git/v5"
@@ -62,6 +63,7 @@ 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 {
@@ -116,6 +118,7 @@ func NewGitCommand(
gitConfig,
repoPaths,
repository,
syncMutex,
), nil
}
@@ -126,6 +129,7 @@ func NewGitCommandAux(
gitConfig git_config.IGitConfig,
repoPaths *git_commands.RepoPaths,
repo *gogit.Repository,
syncMutex *deadlock.Mutex,
) *GitCommand {
cmd := NewGitCmdObjBuilder(cmn.Log, osCommand.Cmd)
@@ -136,7 +140,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)
gitCommon := git_commands.NewGitCommon(cmn, version, cmd, osCommand, repoPaths, repo, configCommands, syncMutex)
fileLoader := git_commands.NewFileLoader(gitCommon, cmd, configCommands)
statusCommands := git_commands.NewStatusCommands(gitCommon)
+5
View File
@@ -4,6 +4,7 @@ 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 {
@@ -14,6 +15,8 @@ type GitCommon struct {
repoPaths *RepoPaths
repo *gogit.Repository
config *ConfigCommands
// mutex for doing things like push/pull/fetch
syncMutex *deadlock.Mutex
}
func NewGitCommon(
@@ -24,6 +27,7 @@ func NewGitCommon(
repoPaths *RepoPaths,
repo *gogit.Repository,
config *ConfigCommands,
syncMutex *deadlock.Mutex,
) *GitCommon {
return &GitCommon{
Common: cmn,
@@ -33,5 +37,6 @@ func NewGitCommon(
repoPaths: repoPaths,
repo: repo,
config: config,
syncMutex: syncMutex,
}
}
+2 -2
View File
@@ -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).Run()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).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).Run()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
}
// CheckRemoteBranchExists Returns remote branch
+5 -4
View File
@@ -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)
cmdObj := self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex)
return cmdObj, nil
}
@@ -70,6 +70,7 @@ func (self *SyncCommands) FetchBackgroundCmdObj() oscommands.ICmdObj {
cmdObj := self.cmd.New(cmdArgs)
cmdObj.DontLog().FailOnCredentialRequest()
cmdObj.WithMutex(self.syncMutex)
return cmdObj
}
@@ -95,7 +96,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).Run()
return self.cmd.New(cmdArgs).AddEnvVars("GIT_SEQUENCE_EDITOR=:").PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
}
func (self *SyncCommands) FastForward(
@@ -109,7 +110,7 @@ func (self *SyncCommands) FastForward(
Arg(remoteBranchName + ":" + branchName).
ToArgv()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
}
func (self *SyncCommands) FetchRemote(task gocui.Task, remoteName string) error {
@@ -117,5 +118,5 @@ func (self *SyncCommands) FetchRemote(task gocui.Task, remoteName string) error
Arg(remoteName).
ToArgv()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
}
+1 -1
View File
@@ -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).Run()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
}
+22 -20
View File
@@ -99,30 +99,32 @@ func (self *HostingServiceMgr) getCandidateServiceDomains() []ServiceDomain {
serviceDomains := slices.Clone(defaultServiceDomains)
for gitDomain, typeAndDomain := range self.configServiceDomains {
provider, webDomain, success := strings.Cut(typeAndDomain, ":")
if len(self.configServiceDomains) > 0 {
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
}
// 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
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,
})
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
-4
View File
@@ -65,10 +65,6 @@ 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()
}
-4
View File
@@ -24,10 +24,6 @@ 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
}
+5
View File
@@ -74,6 +74,11 @@ func CopyFile(src, dst string) (err error) {
return //nolint: nakedret
}
// If I was less lazy
// I would actually think
// of a way to add some real code
// for the sake of an example
// CopyDir recursively copies a directory tree, attempting to preserve permissions.
// Source directory must exist. If destination already exists we'll clobber it.
// Symlinks are ignored and skipped.
-2
View File
@@ -59,9 +59,7 @@ 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": {
+110 -265
View File
@@ -5,255 +5,131 @@ import (
)
type UserConfig struct {
// 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"`
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"`
}
type RefresherConfig struct {
// File/submodule refresh interval in seconds.
// Auto-refresh can be disabled via option 'git.autoRefresh'.
RefreshInterval int `yaml:"refreshInterval"`
// Re-fetch interval in seconds.
// Auto-fetch can be disabled via option 'git.autoFetch'.
FetchInterval int `yaml:"fetchInterval"`
FetchInterval int `yaml:"fetchInterval"`
}
type GuiConfig struct {
// 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"`
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"`
}
type ThemeConfig struct {
// 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
ActiveBorderColor []string `yaml:"activeBorderColor"`
InactiveBorderColor []string `yaml:"inactiveBorderColor"`
SearchingActiveBorderColor []string `yaml:"searchingActiveBorderColor"`
// 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"`
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"`
}
type CommitLengthConfig struct {
// If true, show an indicator of commit message length
Show bool `yaml:"show"`
}
type GitConfig struct {
// 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"`
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"`
}
type PagingConfig struct {
// 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'
ColorArg string `yaml:"colorArg"`
Pager string `yaml:"pager"`
UseConfig bool `yaml:"useConfig"`
ExternalDiffCommand string `yaml:"externalDiffCommand"`
}
type CommitConfig struct {
// If true, pass '--signoff' flag when committing
SignOff bool `yaml:"signOff"`
}
type MergingConfig struct {
// 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"`
ManualCommit bool `yaml:"manualCommit"`
Args string `yaml:"args"`
}
type LogConfig struct {
// 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"`
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"`
}
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"`
// Period in days between update checks
Days int64 `yaml:"days"`
Days int64 `yaml:"days"`
}
type KeybindingConfig struct {
@@ -491,8 +367,7 @@ type OSConfig struct {
// Deprecated: use OpenLink instead.
OpenLinkCommand string `yaml:"openLinkCommand,omitempty"`
// 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 is the command for copying to clipboard
CopyToClipboardCmd string `yaml:"copyToClipboardCmd,omitempty"`
}
@@ -501,79 +376,50 @@ type CustomCommandAfterHook struct {
}
type CustomCommand struct {
// 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"`
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"`
}
type CustomCommandPrompt struct {
// 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
// one of 'input', 'menu', 'confirm', or 'menuFromCommand'
Type string `yaml:"type"`
Key string `yaml:"key"`
Title string `yaml:"title"`
// 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"`
// these only apply to input prompts
InitialValue string `yaml:"initialValue"`
Suggestions CustomCommandSuggestions `yaml:"suggestions"`
// The message of the confirmation prompt.
// Only for confirm prompts.
// this only applies to confirm prompts
Body string `yaml:"body"`
// Menu options.
// Only for menu prompts.
// this only applies to menus
Options []CustomCommandMenuOption
// 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.
// this only applies to menuFromCommand
Command string `yaml:"command"`
Filter string `yaml:"filter"`
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 {
// 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.
Preset string `yaml:"preset"`
Command string `yaml:"command"`
}
type CustomCommandMenuOption struct {
// The first part of the label
Name string `yaml:"name"`
// The second part of the label
Name string `yaml:"name"`
Description string `yaml:"description"`
// The value that will be used in the command
Value string `yaml:"value"`
Value string `yaml:"value"`
}
func GetDefaultConfig() *UserConfig {
@@ -622,7 +468,6 @@ func GetDefaultConfig() *UserConfig {
SkipRewordInEditorWarning: false,
Border: "rounded",
AnimateExplosion: true,
PortraitMode: "auto",
},
Git: GitConfig{
Paging: PagingConfig{
@@ -691,7 +536,7 @@ func GetDefaultConfig() *UserConfig {
NextMatch: "n",
PrevMatch: "N",
StartSearch: "/",
OptionMenu: "<disabled>",
OptionMenu: "",
OptionMenuAlt1: "?",
Select: "<space>",
GoInto: "<enter>",
+23 -30
View File
@@ -20,11 +20,10 @@ type BaseContext struct {
onFocusFn onFocusFn
onFocusLostFn onFocusLostFn
focusable bool
transient bool
hasControlledBounds bool
needsRerenderOnWidthChange bool
highlightOnFocus bool
focusable bool
transient bool
hasControlledBounds bool
highlightOnFocus bool
*ParentContextMgr
}
@@ -37,15 +36,14 @@ 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
NeedsRerenderOnWidthChange 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
OnGetOptionsMap func() map[string]string
}
@@ -56,18 +54,17 @@ 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,
needsRerenderOnWidthChange: opts.NeedsRerenderOnWidthChange,
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,
ParentContextMgr: &ParentContextMgr{},
viewTrait: viewTrait,
}
}
@@ -193,10 +190,6 @@ func (self *BaseContext) HasControlledBounds() bool {
return self.hasControlledBounds
}
func (self *BaseContext) NeedsRerenderOnWidthChange() bool {
return self.needsRerenderOnWidthChange
}
func (self *BaseContext) Title() string {
return ""
}
+5 -8
View File
@@ -27,10 +27,8 @@ 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,
@@ -41,12 +39,11 @@ 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,
NeedsRerenderOnWidthChange: true,
View: c.Views().Branches,
WindowName: "branches",
Key: LOCAL_BRANCHES_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
})),
ListRenderer: ListRenderer{
list: viewModel,
-4
View File
@@ -41,10 +41,6 @@ func (self *FilteredList[T]) ClearFilter() {
self.SetFilter("")
}
func (self *FilteredList[T]) ReApplyFilter() {
self.applyFilter()
}
func (self *FilteredList[T]) IsFiltering() bool {
return self.filter != ""
}
+5 -6
View File
@@ -68,12 +68,11 @@ 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,
NeedsRerenderOnWidthChange: true,
View: c.Views().Commits,
WindowName: "commits",
Key: LOCAL_COMMITS_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
})),
ListRenderer: ListRenderer{
list: viewModel,
+5 -6
View File
@@ -43,12 +43,11 @@ 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,
NeedsRerenderOnWidthChange: true,
View: c.Views().ReflogCommits,
WindowName: "commits",
Key: REFLOG_COMMITS_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
})),
ListRenderer: ListRenderer{
list: viewModel,
+6 -7
View File
@@ -36,13 +36,12 @@ 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,
NeedsRerenderOnWidthChange: true,
View: c.Views().RemoteBranches,
WindowName: "branches",
Key: REMOTE_BRANCHES_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
Transient: true,
})),
ListRenderer: ListRenderer{
list: viewModel,
+1
View File
@@ -9,6 +9,7 @@ import (
type RemotesContext struct {
*FilteredListViewModel[*models.Remote]
*ListContextTrait
*SearchHistory
}
var (
+6 -7
View File
@@ -115,13 +115,12 @@ 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,
NeedsRerenderOnWidthChange: true,
View: c.Views().SubCommits,
WindowName: "branches",
Key: SUB_COMMITS_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
Transient: true,
})),
ListRenderer: ListRenderer{
list: viewModel,
+1 -4
View File
@@ -27,10 +27,7 @@ func NewTagsContext(
)
getDisplayStrings := func(_ int, _ int) [][]string {
return presentation.GetTagListDisplayStrings(
viewModel.GetItems(),
c.State().GetItemOperation,
c.Modes().Diffing.Ref, c.Tr)
return presentation.GetTagListDisplayStrings(viewModel.GetItems(), c.Modes().Diffing.Ref)
}
return &TagsContext{
+1 -4
View File
@@ -47,7 +47,6 @@ func (gui *Gui) resetHelpersAndControllers() {
patchBuildingHelper := helpers.NewPatchBuildingHelper(helperCommon)
stagingHelper := helpers.NewStagingHelper(helperCommon)
mergeConflictsHelper := helpers.NewMergeConflictsHelper(helperCommon)
searchHelper := helpers.NewSearchHelper(helperCommon)
refreshHelper := helpers.NewRefreshHelper(
helperCommon,
@@ -57,7 +56,6 @@ func (gui *Gui) resetHelpersAndControllers() {
stagingHelper,
mergeConflictsHelper,
worktreeHelper,
searchHelper,
)
diffHelper := helpers.NewDiffHelper(helperCommon)
cherryPickHelper := helpers.NewCherryPickHelper(
@@ -114,14 +112,13 @@ 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: searchHelper,
Search: helpers.NewSearchHelper(helperCommon),
Worktree: worktreeHelper,
SubCommits: helpers.NewSubCommitsHelper(helperCommon, refreshHelper, setSubCommits),
}
+21 -20
View File
@@ -34,10 +34,9 @@ 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),
GetDisabledReason: self.getDisabledReasonForPress,
Description: self.c.Tr.Checkout,
Key: opts.GetKey(opts.Config.Universal.Select),
Handler: self.checkSelected(self.press),
Description: self.c.Tr.Checkout,
},
{
Key: opts.GetKey(opts.Config.Universal.New),
@@ -300,18 +299,6 @@ 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)
}
@@ -576,7 +563,14 @@ func (self *BranchesController) fastForward(branch *models.Branch) error {
action := self.c.Tr.Actions.FastForwardBranch
return self.c.WithInlineStatus(branch, types.ItemOperationFastForwarding, context.LOCAL_BRANCHES_CONTEXT_KEY, func(task gocui.Task) error {
message := utils.ResolvePlaceholderString(
self.c.Tr.FastForwarding,
map[string]string{
"branch": branch.Name,
},
)
return self.c.WithWaitingStatus(message, func(task gocui.Task) error {
worktree, ok := self.worktreeForBranch(branch)
if ok {
self.c.LogAction(action)
@@ -596,17 +590,24 @@ func (self *BranchesController) fastForward(branch *models.Branch) error {
WorktreeGitDir: worktreeGitDir,
},
)
_ = self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
return err
if err != nil {
_ = self.c.Error(err)
}
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
} 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
})
}
-2
View File
@@ -46,7 +46,6 @@ type Helpers struct {
Confirmation *ConfirmationHelper
Mode *ModeHelper
AppStatus *AppStatusHelper
InlineStatus *InlineStatusHelper
WindowArrangement *WindowArrangementHelper
Search *SearchHelper
Worktree *WorktreeHelper
@@ -82,7 +81,6 @@ func NewStubHelpers() *Helpers {
Confirmation: &ConfirmationHelper{},
Mode: &ModeHelper{},
AppStatus: &AppStatusHelper{},
InlineStatus: &InlineStatusHelper{},
WindowArrangement: &WindowArrangementHelper{},
Search: &SearchHelper{},
Worktree: &WorktreeHelper{},
@@ -1,129 +0,0 @@
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
})
}
+23 -46
View File
@@ -28,7 +28,6 @@ type RefreshHelper struct {
stagingHelper *StagingHelper
mergeConflictsHelper *MergeConflictsHelper
worktreeHelper *WorktreeHelper
searchHelper *SearchHelper
}
func NewRefreshHelper(
@@ -39,7 +38,6 @@ func NewRefreshHelper(
stagingHelper *StagingHelper,
mergeConflictsHelper *MergeConflictsHelper,
worktreeHelper *WorktreeHelper,
searchHelper *SearchHelper,
) *RefreshHelper {
return &RefreshHelper{
c: c,
@@ -49,7 +47,6 @@ func NewRefreshHelper(
stagingHelper: stagingHelper,
mergeConflictsHelper: mergeConflictsHelper,
worktreeHelper: worktreeHelper,
searchHelper: searchHelper,
}
}
@@ -118,15 +115,12 @@ 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)
includeWorktreesWithBranches = scopeSet.Includes(types.WORKTREES)
refresh("reflog and branches", func() { self.refreshReflogAndBranches(includeWorktreesWithBranches) })
refresh("reflog and branches", self.refreshReflogAndBranches)
} 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
@@ -163,7 +157,7 @@ func (self *RefreshHelper) Refresh(options types.RefreshOptions) error {
refresh("remotes", func() { _ = self.refreshRemotes() })
}
if scopeSet.Includes(types.WORKTREES) && !includeWorktreesWithBranches {
if scopeSet.Includes(types.WORKTREES) {
refresh("worktrees", func() { _ = self.refreshWorktrees() })
}
@@ -248,7 +242,7 @@ func (self *RefreshHelper) refreshReflogCommitsConsideringStartup() {
case types.INITIAL:
self.c.OnWorker(func(_ gocui.Task) {
_ = self.refreshReflogCommits()
self.refreshBranches(false)
self.refreshBranches()
self.c.State().GetRepoState().SetStartupStage(types.COMPLETE)
})
@@ -257,10 +251,10 @@ func (self *RefreshHelper) refreshReflogCommitsConsideringStartup() {
}
}
func (self *RefreshHelper) refreshReflogAndBranches(refreshWorktrees bool) {
func (self *RefreshHelper) refreshReflogAndBranches() {
self.refreshReflogCommitsConsideringStartup()
self.refreshBranches(refreshWorktrees)
self.refreshBranches()
}
func (self *RefreshHelper) refreshCommitsAndCommitFiles() {
@@ -331,7 +325,7 @@ func (self *RefreshHelper) refreshCommitsWithLimit() error {
self.c.Model().WorkingTreeStateAtLastCommitRefresh = self.c.Git().Status.WorkingTreeState()
self.c.Model().CheckedOutBranch = checkedOutBranchName
return self.refreshView(self.c.Contexts().LocalCommits)
return self.c.PostRefreshUpdate(self.c.Contexts().LocalCommits)
}
func (self *RefreshHelper) refreshSubCommitsWithLimit() error {
@@ -354,7 +348,7 @@ func (self *RefreshHelper) refreshSubCommitsWithLimit() error {
self.c.Model().SubCommits = commits
self.RefreshAuthors(commits)
return self.refreshView(self.c.Contexts().SubCommits)
return self.c.PostRefreshUpdate(self.c.Contexts().SubCommits)
}
func (self *RefreshHelper) RefreshAuthors(commits []*models.Commit) {
@@ -384,7 +378,7 @@ func (self *RefreshHelper) refreshCommitFilesContext() error {
self.c.Model().CommitFiles = files
self.c.Contexts().CommitFiles.CommitFileTreeViewModel.SetTree()
return self.refreshView(self.c.Contexts().CommitFiles)
return self.c.PostRefreshUpdate(self.c.Contexts().CommitFiles)
}
func (self *RefreshHelper) refreshRebaseCommits() error {
@@ -398,7 +392,7 @@ func (self *RefreshHelper) refreshRebaseCommits() error {
self.c.Model().Commits = updatedCommits
self.c.Model().WorkingTreeStateAtLastCommitRefresh = self.c.Git().Status.WorkingTreeState()
return self.refreshView(self.c.Contexts().LocalCommits)
return self.c.PostRefreshUpdate(self.c.Contexts().LocalCommits)
}
func (self *RefreshHelper) refreshTags() error {
@@ -409,7 +403,7 @@ func (self *RefreshHelper) refreshTags() error {
self.c.Model().Tags = tags
return self.refreshView(self.c.Contexts().Tags)
return self.c.PostRefreshUpdate(self.c.Contexts().Tags)
}
func (self *RefreshHelper) refreshStateSubmoduleConfigs() error {
@@ -425,7 +419,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(refreshWorktrees bool) {
func (self *RefreshHelper) refreshBranches() {
self.c.Mutexes().RefreshingBranchesMutex.Lock()
defer self.c.Mutexes().RefreshingBranchesMutex.Unlock()
@@ -449,24 +443,15 @@ func (self *RefreshHelper) refreshBranches(refreshWorktrees bool) {
self.c.Model().Branches = branches
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 {
if err := self.c.PostRefreshUpdate(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()
}
@@ -488,11 +473,11 @@ func (self *RefreshHelper) refreshFilesAndSubmodules() error {
}
self.c.OnUIThread(func() error {
if err := self.refreshView(self.c.Contexts().Submodules); err != nil {
if err := self.c.PostRefreshUpdate(self.c.Contexts().Submodules); err != nil {
self.c.Log.Error(err)
}
if err := self.refreshView(self.c.Contexts().Files); err != nil {
if err := self.c.PostRefreshUpdate(self.c.Contexts().Files); err != nil {
self.c.Log.Error(err)
}
@@ -614,7 +599,7 @@ func (self *RefreshHelper) refreshReflogCommits() error {
model.FilteredReflogCommits = model.ReflogCommits
}
return self.refreshView(self.c.Contexts().ReflogCommits)
return self.c.PostRefreshUpdate(self.c.Contexts().ReflogCommits)
}
func (self *RefreshHelper) refreshRemotes() error {
@@ -638,44 +623,41 @@ func (self *RefreshHelper) refreshRemotes() error {
}
}
if err := self.refreshView(self.c.Contexts().Remotes); err != nil {
if err := self.c.PostRefreshUpdate(self.c.Contexts().Remotes); err != nil {
return err
}
if err := self.refreshView(self.c.Contexts().RemoteBranches); err != nil {
if err := self.c.PostRefreshUpdate(self.c.Contexts().RemoteBranches); err != nil {
return err
}
return nil
}
func (self *RefreshHelper) loadWorktrees() {
func (self *RefreshHelper) refreshWorktrees() error {
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.refreshView(self.c.Contexts().Branches); err != nil {
if err := self.c.PostRefreshUpdate(self.c.Contexts().Branches); err != nil {
return err
}
return self.refreshView(self.c.Contexts().Worktrees)
return self.c.PostRefreshUpdate(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.refreshView(self.c.Contexts().Stash)
return self.c.PostRefreshUpdate(self.c.Contexts().Stash)
}
// never call this on its own, it should only be called from within refreshCommits()
@@ -694,7 +676,7 @@ func (self *RefreshHelper) refreshStatus() {
repoName := self.c.Git().RepoPaths.RepoName()
status := presentation.FormatStatus(repoName, currentBranch, types.ItemOperationNone, linkedWorktreeName, workingTreeState, self.c.Tr)
status := presentation.FormatStatus(repoName, currentBranch, linkedWorktreeName, workingTreeState, self.c.Tr)
self.c.SetViewContent(self.c.Views().Status, status)
}
@@ -714,8 +696,3 @@ 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,6 +173,11 @@ 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,18 +227,6 @@ 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,24 +34,14 @@ 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
if self.shouldUsePortraitMode(width, height) {
portraitMode := width <= 84 && height > 45
if portraitMode {
sidePanelsDirection = boxlayout.ROW
}
+25 -1
View File
@@ -1,6 +1,7 @@
package controllers
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
@@ -16,7 +17,7 @@ func (self *ScreenModeActions) Next() error {
),
)
return nil
return self.rerenderViewsWithScreenModeDependentContent()
}
func (self *ScreenModeActions) Prev() error {
@@ -27,9 +28,32 @@ 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 {
+1 -2
View File
@@ -4,7 +4,6 @@ import (
"errors"
"fmt"
"strings"
"time"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
"github.com/jesseduffield/lazygit/pkg/constants"
@@ -107,7 +106,7 @@ func (self *StatusController) onClick() error {
}
cx, _ := self.c.Views().Status.Cursor()
upstreamStatus := presentation.BranchStatus(currentBranch, types.ItemOperationNone, self.c.Tr, time.Now())
upstreamStatus := presentation.BranchStatus(currentBranch, self.c.Tr)
repoName := self.c.Git().RepoPaths.RepoName()
workingTreeState := self.c.Git().Status.WorkingTreeState()
switch workingTreeState {
+20 -35
View File
@@ -7,7 +7,6 @@ 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"
)
@@ -31,16 +30,14 @@ 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),
GetDisabledReason: self.getDisabledReasonForPushOrPull,
Description: self.c.Tr.Push,
Key: opts.GetKey(opts.Config.Universal.Push),
Handler: opts.Guards.NoPopupPanel(self.HandlePush),
Description: self.c.Tr.Push,
},
{
Key: opts.GetKey(opts.Config.Universal.Pull),
Handler: opts.Guards.NoPopupPanel(self.HandlePull),
GetDisabledReason: self.getDisabledReasonForPushOrPull,
Description: self.c.Tr.Pull,
Key: opts.GetKey(opts.Config.Universal.Pull),
Handler: opts.Guards.NoPopupPanel(self.HandlePull),
Description: self.c.Tr.Pull,
},
}
@@ -59,18 +56,6 @@ 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()
@@ -88,13 +73,13 @@ func (self *SyncController) push(currentBranch *models.Branch) error {
if currentBranch.IsTrackingRemote() {
opts := pushOpts{}
if currentBranch.HasCommitsToPull() {
return self.requestToForcePush(currentBranch, opts)
return self.requestToForcePush(opts)
} else {
return self.pushAux(currentBranch, opts)
return self.pushAux(opts)
}
} else {
if self.c.Git().Config.GetPushToCurrent() {
return self.pushAux(currentBranch, pushOpts{setUpstream: true})
return self.pushAux(pushOpts{setUpstream: true})
} else {
return self.c.Helpers().Upstream.PromptForUpstreamWithInitialContent(currentBranch, func(upstream string) error {
upstreamRemote, upstreamBranch, err := self.c.Helpers().Upstream.ParseUpstream(upstream)
@@ -102,7 +87,7 @@ func (self *SyncController) push(currentBranch *models.Branch) error {
return self.c.Error(err)
}
return self.pushAux(currentBranch, pushOpts{
return self.pushAux(pushOpts{
setUpstream: true,
upstreamRemote: upstreamRemote,
upstreamBranch: upstreamBranch,
@@ -122,11 +107,11 @@ func (self *SyncController) pull(currentBranch *models.Branch) error {
return self.c.Error(err)
}
return self.PullAux(currentBranch, PullFilesOptions{Action: action})
return self.PullAux(PullFilesOptions{Action: action})
})
}
return self.PullAux(currentBranch, PullFilesOptions{Action: action})
return self.PullAux(PullFilesOptions{Action: action})
}
func (self *SyncController) setCurrentBranchUpstream(upstream string) error {
@@ -154,8 +139,8 @@ type PullFilesOptions struct {
Action string
}
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 {
func (self *SyncController) PullAux(opts PullFilesOptions) error {
return self.c.WithWaitingStatus(self.c.Tr.PullingStatus, func(task gocui.Task) error {
return self.pullWithLock(task, opts)
})
}
@@ -182,8 +167,8 @@ type pushOpts struct {
setUpstream bool
}
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 {
func (self *SyncController) pushAux(opts pushOpts) error {
return self.c.WithWaitingStatus(self.c.Tr.PushingStatus, func(task gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.Push)
err := self.c.Git().Sync.Push(
task,
@@ -207,18 +192,18 @@ func (self *SyncController) pushAux(currentBranch *models.Branch, opts pushOpts)
newOpts := opts
newOpts.force = true
return self.pushAux(currentBranch, newOpts)
return self.pushAux(newOpts)
},
})
return nil
}
return err
_ = self.c.Error(err)
}
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
})
}
func (self *SyncController) requestToForcePush(currentBranch *models.Branch, opts pushOpts) error {
func (self *SyncController) requestToForcePush(opts pushOpts) error {
forcePushDisabled := self.c.UserConfig.Git.DisableForcePushing
if forcePushDisabled {
return self.c.ErrorMsg(self.c.Tr.ForcePushDisabled)
@@ -229,7 +214,7 @@ func (self *SyncController) requestToForcePush(currentBranch *models.Branch, opt
Prompt: self.forcePushPrompt(),
HandleConfirm: func() error {
opts.force = true
return self.pushAux(currentBranch, opts)
return self.pushAux(opts)
},
})
}
+12 -14
View File
@@ -92,9 +92,10 @@ 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)
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
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}})
})
}
@@ -129,10 +130,10 @@ func (self *TagsController) remoteDelete(tag *models.Tag) error {
Title: confirmTitle,
Prompt: confirmPrompt,
HandleConfirm: func() error {
return self.c.WithInlineStatus(tag, types.ItemOperationDeleting, context.TAGS_CONTEXT_KEY, func(task gocui.Task) error {
return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(t gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.DeleteRemoteTag)
if err := self.c.Git().Remote.DeleteRemoteTag(task, upstream, tag.Name); err != nil {
return err
if err := self.c.Git().Remote.DeleteRemoteTag(t, upstream, tag.Name); err != nil {
return self.c.Error(err)
}
self.c.Toast(self.c.Tr.RemoteTagDeletedMessage)
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.COMMITS, types.TAGS}})
@@ -188,17 +189,14 @@ func (self *TagsController) push(tag *models.Tag) error {
InitialContent: "origin",
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRemoteSuggestionsFunc(),
HandleConfirm: func(response string) error {
return self.c.WithInlineStatus(tag, types.ItemOperationPushing, context.TAGS_CONTEXT_KEY, func(task gocui.Task) error {
return self.c.WithWaitingStatus(self.c.Tr.PushingTagStatus, 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)
}
// Render again to remove the inline status:
self.c.OnUIThread(func() error {
_ = self.c.Contexts().Tags.HandleRender()
return nil
})
return err
return nil
})
},
})
+3 -34
View File
@@ -110,12 +110,6 @@ 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
@@ -186,27 +180,6 @@ 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 {
@@ -300,6 +273,7 @@ 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
@@ -489,6 +463,7 @@ func NewGui(
RefreshingFilesMutex: &deadlock.Mutex{},
RefreshingBranchesMutex: &deadlock.Mutex{},
RefreshingStatusMutex: &deadlock.Mutex{},
SyncMutex: &deadlock.Mutex{},
LocalCommitsMutex: &deadlock.Mutex{},
SubCommitsMutex: &deadlock.Mutex{},
AuthorsMutex: &deadlock.Mutex{},
@@ -498,9 +473,6 @@ func NewGui(
},
InitialDir: initialDir,
afterLayoutFuncs: make(chan func() error, 1000),
itemOperations: make(map[string]types.ItemOperation),
itemOperationsMutex: &deadlock.Mutex{},
}
gui.PopupHandler = popup.NewPopupHandler(
@@ -651,10 +623,7 @@ func (gui *Gui) Run(startArgs appTypes.StartArgs) error {
deadlock.Opts.LogBuf = utils.NewOnceWriter(os.Stderr, func() {
gui.g.Close()
})
// 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) == ""
deadlock.Opts.Disable = !gui.Debug
if err := gui.Config.ReloadUserConfig(); err != nil {
return nil
-6
View File
@@ -5,7 +5,6 @@ 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"
)
@@ -200,8 +199,3 @@ 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
}
+1 -3
View File
@@ -100,9 +100,7 @@ func LabelFromKey(key types.Key) string {
func GetKey(key string) types.Key {
runeCount := utf8.RuneCountInString(key)
if key == "<disabled>" {
return nil
} else if runeCount > 1 {
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)
+2 -23
View File
@@ -44,13 +44,8 @@ func (gui *Gui) layout(g *gocui.Gui) error {
}
}
contextsToRerender := []types.Context{}
// we assume that the view has already been created.
setViewFromDimensions := func(context types.Context) (*gocui.View, error) {
viewName := context.GetViewName()
windowName := context.GetWindowName()
setViewFromDimensions := func(viewName string, windowName string) (*gocui.View, error) {
dimensionsObj, ok := viewDimensions[windowName]
view, err := g.View(viewName)
@@ -72,16 +67,6 @@ 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,
@@ -100,7 +85,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
continue
}
_, err := setViewFromDimensions(context)
_, err := setViewFromDimensions(context.GetViewName(), context.GetWindowName())
if err != nil && !gocui.IsUnknownView(err) {
return err
}
@@ -161,12 +146,6 @@ 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
+10 -55
View File
@@ -3,18 +3,15 @@ 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"
)
@@ -22,52 +19,27 @@ 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, getItemOperation(branch), fullDescription, diffed, viewWidth, tr, userConfig, worktrees, time.Now())
return getBranchDisplayStrings(branch, fullDescription, diffed, tr, userConfig, worktrees)
})
}
// 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
@@ -78,19 +50,13 @@ 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)
if checkedOutByWorkTree {
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))
coloredName = fmt.Sprintf("%s %s", coloredName, style.FgDefault.Sprint(worktreeIcon))
}
if len(branchStatus) > 0 {
coloredStatus := branchStatusColor(b, itemOperation).Sprint(branchStatus)
coloredName = fmt.Sprintf("%s %s", coloredName, coloredStatus)
}
coloredName = fmt.Sprintf("%s %s", coloredName, branchStatus)
recencyColor := style.FgCyan
if b.Recency == " *" {
@@ -104,7 +70,7 @@ func getBranchDisplayStrings(
res = append(res, nameTextStyle.Sprint(icons.IconForBranch(b)))
}
if showCommitHash {
if fullDescription || userConfig.Gui.ShowBranchCommitHash {
res = append(res, utils.ShortSha(b.CommitHash))
}
@@ -143,11 +109,9 @@ func GetBranchTextStyle(name string) style.TextStyle {
}
}
func branchStatusColor(branch *models.Branch, itemOperation types.ItemOperation) style.TextStyle {
func ColoredBranchStatus(branch *models.Branch, tr *i18n.TranslationSet) string {
colour := style.FgYellow
if itemOperation != types.ItemOperationNone {
colour = style.FgCyan
} else if branch.UpstreamGone {
if branch.UpstreamGone {
colour = style.FgRed
} else if branch.MatchesUpstream() {
colour = style.FgGreen
@@ -155,19 +119,10 @@ func branchStatusColor(branch *models.Branch, itemOperation types.ItemOperation)
colour = style.FgMagenta
}
return colour
return colour.Sprint(BranchStatus(branch, tr))
}
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)
}
func BranchStatus(branch *models.Branch, tr *i18n.TranslationSet) string {
if !branch.IsTrackingRemote() {
return ""
}
-214
View File
@@ -1,214 +0,0 @@
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)
})
}
}
+2 -7
View File
@@ -3,7 +3,6 @@ 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"
@@ -160,9 +159,7 @@ func getFileLine(hasUnstagedChanges bool, hasStagedChanges bool, name string, di
isDirectory := file == nil
if icons.IsIconEnabled() {
icon := icons.IconForFile(name, isSubmodule, isLinkedWorktree, isDirectory)
paint := color.C256(icon.Color, false)
output += paint.Sprint(icon.Icon) + " "
output += restColor.Sprintf("%s ", icons.IconForFile(name, isSubmodule, isLinkedWorktree, isDirectory))
}
output += restColor.Sprint(utils.EscapeSpecialChars(name))
@@ -201,9 +198,7 @@ func getCommitFileLine(name string, diffName string, commitFile *models.CommitFi
isDirectory := commitFile == nil
if icons.IsIconEnabled() {
icon := icons.IconForFile(name, isSubmodule, isLinkedWorktree, isDirectory)
paint := color.C256(icon.Color, false)
output += paint.Sprint(icon.Icon) + " "
output += colour.Sprintf("%s ", icons.IconForFile(name, isSubmodule, isLinkedWorktree, isDirectory))
}
output += colour.Sprint(name)
+313 -314
View File
@@ -5,328 +5,327 @@ import (
)
// https://github.com/ogham/exa/blob/master/src/output/icons.rs
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} //
const (
DEFAULT_FILE_ICON = "\uf15b" // 
DEFAULT_SUBMODULE_ICON = "\uf1d3" // 
DEFAULT_DIRECTORY_ICON = "\uf114" //
)
// 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 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", //
}
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}, // 
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", // 
}
func patchFileIconsForNerdFontsV2() {
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} // ﵂
extIconMap[".cs"] = "\uf81a" // 
extIconMap[".csproj"] = "\uf81a" // 
extIconMap[".csx"] = "\uf81a" // 
extIconMap[".license"] = "\uf718" // 
extIconMap[".node"] = "\uf898" // 
extIconMap[".rtf"] = "\uf718" // 
extIconMap[".vue"] = "\ufd42" // ﵂
}
func IconForFile(name string, isSubmodule bool, isLinkedWorktree bool, isDirectory bool) IconProperties {
func IconForFile(name string, isSubmodule bool, isLinkedWorktree bool, isDirectory bool) string {
base := filepath.Base(name)
if icon, ok := nameIconMap[base]; ok {
return icon
@@ -340,7 +339,7 @@ func IconForFile(name string, isSubmodule bool, isLinkedWorktree bool, isDirecto
if isSubmodule {
return DEFAULT_SUBMODULE_ICON
} else if isLinkedWorktree {
return IconProperties{LINKED_WORKTREE_ICON, 239}
return LINKED_WORKTREE_ICON
} else if isDirectory {
return DEFAULT_DIRECTORY_ICON
}
+9 -18
View File
@@ -6,11 +6,6 @@ import (
"github.com/samber/lo"
)
type IconProperties struct {
Icon string
Color uint8
}
var isIconEnabled = false
func IsIconEnabled() bool {
@@ -18,18 +13,14 @@ func IsIconEnabled() bool {
}
func SetNerdFontsVersion(version string) {
if version == "" {
isIconEnabled = false
} else {
if !lo.Contains([]string{"2", "3"}, version) {
log.Fatalf("Unsupported nerdFontVersion %s", version)
}
if version == "2" {
patchGitIconsForNerdFontsV2()
patchFileIconsForNerdFontsV2()
}
isIconEnabled = true
if !lo.Contains([]string{"2", "3"}, version) {
log.Fatalf("Unsupported nerdFontVersion %s", version)
}
if version == "2" {
patchGitIconsForNerdFontsV2()
patchFileIconsForNerdFontsV2()
}
isIconEnabled = true
}
-23
View File
@@ -1,23 +0,0 @@
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 ""
}
+2 -3
View File
@@ -7,15 +7,14 @@ 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, itemOperation types.ItemOperation, linkedWorktreeName string, workingTreeState enums.RebaseMode, tr *i18n.TranslationSet) string {
func FormatStatus(repoName string, currentBranch *models.Branch, linkedWorktreeName string, workingTreeState enums.RebaseMode, tr *i18n.TranslationSet) string {
status := ""
if currentBranch.IsRealBranch() {
status += ColoredBranchStatus(currentBranch, itemOperation, tr) + " "
status += ColoredBranchStatus(currentBranch, tr) + " "
}
if workingTreeState != enums.REBASE_MODE_NONE {
+4 -19
View File
@@ -1,32 +1,22 @@
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,
getItemOperation func(item types.HasUrn) types.ItemOperation,
diffName string,
tr *i18n.TranslationSet,
) [][]string {
func GetTagListDisplayStrings(tags []*models.Tag, diffName string) [][]string {
return lo.Map(tags, func(tag *models.Tag, _ int) []string {
diffed := tag.Name == diffName
return getTagDisplayStrings(tag, getItemOperation(tag), diffed, tr)
return getTagDisplayStrings(tag, diffed)
})
}
// getTagDisplayStrings returns the display string of branch
func getTagDisplayStrings(t *models.Tag, itemOperation types.ItemOperation, diffed bool, tr *i18n.TranslationSet) []string {
func getTagDisplayStrings(t *models.Tag, diffed bool) []string {
textStyle := theme.DefaultTextColor
if diffed {
textStyle = theme.DiffTerminalColor
@@ -36,11 +26,6 @@ func getTagDisplayStrings(t *models.Tag, itemOperation types.ItemOperation, diff
res = append(res, textStyle.Sprint(icons.IconForTag(t)))
}
descriptionColor := style.FgYellow
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)
res = append(res, textStyle.Sprint(t.Name), descriptionColor.Sprint(t.Description()))
return res
}
+1 -1
View File
@@ -71,7 +71,7 @@ func (self *StatusManager) GetStatusString() string {
}
topStatus := self.statuses[0]
if topStatus.statusType == "waiting" {
return topStatus.message + " " + utils.Loader(time.Now())
return topStatus.message + " " + utils.Loader()
}
return topStatus.message
}
+4 -6
View File
@@ -45,12 +45,10 @@ func (gui *Gui) handleTestMode() {
log.Fatal("gocui should have already exited")
}()
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")
})
}
go utils.Safe(func() {
time.Sleep(time.Second * 40)
log.Fatal("40 seconds is up, lazygit recording took too long to complete")
})
}
}
+1 -27
View File
@@ -87,12 +87,6 @@ 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
@@ -262,6 +256,7 @@ type Mutexes struct {
RefreshingFilesMutex *deadlock.Mutex
RefreshingBranchesMutex *deadlock.Mutex
RefreshingStatusMutex *deadlock.Mutex
SyncMutex *deadlock.Mutex
LocalCommitsMutex *deadlock.Mutex
SubCommitsMutex *deadlock.Mutex
AuthorsMutex *deadlock.Mutex
@@ -270,24 +265,6 @@ 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
@@ -300,9 +277,6 @@ type IStateAccessor interface {
SetShowExtrasWindow(bool)
GetRetainOriginalDir() bool
SetRetainOriginalDir(bool)
GetItemOperation(item HasUrn) ItemOperation
SetItemOperation(item HasUrn, operation ItemOperation)
ClearItemOperation(item HasUrn)
}
type IRepoStateAccessor interface {
-4
View File
@@ -60,9 +60,6 @@ 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
@@ -105,7 +102,6 @@ type IFilterableContext interface {
SetFilter(string)
GetFilter() string
ClearFilter()
ReApplyFilter()
IsFiltering() bool
IsFilterableContext()
}
+1 -1
View File
@@ -169,7 +169,7 @@ func chineseTranslationSet() TranslationSet {
ToggleStagingPanel: `切换到其他面板`,
ReturnToFilesPanel: `返回文件面板`,
FastForward: `从上游快进此分支`,
FastForwarding: "抓取并快进",
FastForwarding: "抓取并快进 {{.branch}} ...",
FoundConflictsTitle: "自动合并失败",
ViewMergeRebaseOptions: "查看 合并/变基 选项",
NotMergingOrRebasing: "您目前既不进行变基也不进行合并",
+1 -1
View File
@@ -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",
FastForwarding: "Fast-forwarding {{.branch}} ...",
FoundConflictsTitle: "Conflicten!",
ViewMergeRebaseOptions: "Bekijk merge/rebase opties",
NotMergingOrRebasing: "Je bent momenteel niet aan het rebasen of mergen",
+1 -5
View File
@@ -57,8 +57,6 @@ type TranslationSet struct {
ResetFilter string
MergeConflictsTitle string
Checkout string
CantCheckoutBranchWhilePulling string
CantPullOrPushSameBranchTwice string
NoChangedFiles string
SoftReset string
AlreadyCheckedOutBranch string
@@ -848,8 +846,6 @@ 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",
@@ -984,7 +980,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",
FastForwarding: "Fast-forwarding {{.branch}}",
FoundConflictsTitle: "Conflicts!",
ViewConflictsMenuItem: "View conflicts",
AbortMenuItem: "Abort the %s",
+1 -1
View File
@@ -170,7 +170,7 @@ func koreanTranslationSet() TranslationSet {
ToggleStagingPanel: `패널 전환`,
ReturnToFilesPanel: `파일 목록으로 돌아가기`,
FastForward: `Fast-forward this branch from its upstream`,
FastForwarding: "Fast-forwarding",
FastForwarding: "Fast-forwarding {{.branch}} ...",
FoundConflictsTitle: "Auto-merge failed",
ViewMergeRebaseOptions: "View merge/rebase options",
NotMergingOrRebasing: "You are currently neither rebasing nor merging",
+1 -1
View File
@@ -201,7 +201,7 @@ func RussianTranslationSet() TranslationSet {
ToggleStagingPanel: `Переключиться на другую панель (проиндексированные/непроиндексированные изменения)`,
ReturnToFilesPanel: `Вернуться к панели файлов`,
FastForward: `Перемотать эту ветку вперёд из её upstream-ветки`,
FastForwarding: "Получить изменения и перемотать вперёд",
FastForwarding: "Получить изменения и перемотать вперёд {{.branch}} ...",
FoundConflictsTitle: "Конфликты!",
ViewConflictsMenuItem: "Просмотр конфликтов",
AbortMenuItem: "Прервать %s",
+1 -1
View File
@@ -234,7 +234,7 @@ func traditionalChineseTranslationSet() TranslationSet {
ToggleStagingPanel: `切換至另一個面板 (已預存/未預存更改)`,
ReturnToFilesPanel: `返回檔案面板`,
FastForward: `從上游快進此分支`,
FastForwarding: "的擷取和快進中",
FastForwarding: "{{.branch}} 的擷取和快進中...",
FoundConflictsTitle: "自動合併失敗",
ViewMergeRebaseOptions: "查看合併/變基選項",
NotMergingOrRebasing: "你當前既不在變基也不在合併中",
+1 -1
View File
@@ -31,7 +31,7 @@ func main() {
integrationTest := getIntegrationTest()
if os.Getenv(components.WAIT_FOR_DEBUGGER_ENV_VAR) != "" {
if os.Getenv("WAIT_FOR_DEBUGGER") != "" {
println("Waiting for debugger to attach...")
for !isDebuggerAttached() {
time.Sleep(time.Millisecond * 100)
+3 -9
View File
@@ -16,7 +16,6 @@ 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"
)
@@ -43,7 +42,7 @@ func RunTests(
testDir := filepath.Join(projectRootDir, "test", "_results")
if err := buildLazygit(waitForDebugger, raceDetector); err != nil {
if err := buildLazygit(raceDetector); err != nil {
return err
}
@@ -139,17 +138,12 @@ func prepareTestDir(
return createFixture(test, paths, rootDir)
}
func buildLazygit(debug bool, raceDetector bool) error {
func buildLazygit(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")
}
@@ -216,7 +210,7 @@ func getLazygitCommand(test *IntegrationTest, paths Paths, rootDir string, sandb
cmdObj.AddEnvVars(fmt.Sprintf("%s=%s", SANDBOX_ENV_VAR, "true"))
}
if waitForDebugger {
cmdObj.AddEnvVars(fmt.Sprintf("%s=true", WAIT_FOR_DEBUGGER_ENV_VAR))
cmdObj.AddEnvVars("WAIT_FOR_DEBUGGER=true")
}
// Set a race detector log path only to avoid spamming the terminal with the
// logs. We are not showing this anywhere yet.
+1 -1
View File
@@ -19,7 +19,7 @@ import (
const unitTestDescription = "test test"
const (
defaultWidth = 150
defaultWidth = 100
defaultHeight = 100
)
@@ -1,42 +0,0 @@
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"),
)
},
})
@@ -1,52 +0,0 @@
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"),
)
},
})
@@ -1,26 +0,0 @@
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()
},
})
-3
View File
@@ -131,9 +131,7 @@ 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,
@@ -172,7 +170,6 @@ var tests = []*components.IntegrationTest{
interactive_rebase.SwapWithConflict,
misc.ConfirmOnQuit,
misc.CopyToClipboard,
misc.DisabledKeybindings,
misc.InitialOpen,
misc.RecentReposOnLaunch,
patch_building.Apply,
@@ -19,8 +19,6 @@ import (
)
func main() {
println("Generating test_list.go...")
code := generateCode()
formattedCode, err := format.Source(code)
+2 -1
View File
@@ -28,8 +28,9 @@ func GetProjectRoot() string {
const LoaderAnimationInterval = 50
// Loader dumps a string to be displayed as a loader
func Loader(now time.Time) string {
func Loader() string {
characters := "|/-\\"
now := time.Now()
milliseconds := now.UnixMilli()
index := milliseconds / LoaderAnimationInterval % int64(len(characters))
return characters[index : index+1]
+27
View File
@@ -0,0 +1,27 @@
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'")
}
}