From 3fe491fcb25a31268a1dd428d9a00bdcd027d155 Mon Sep 17 00:00:00 2001
From: Alex March
Date: Wed, 20 Dec 2023 15:38:05 +0900
Subject: [PATCH 001/280] Implement a sort order menu for remote branches
---
docs/keybindings/Keybindings_en.md | 1 +
docs/keybindings/Keybindings_ja.md | 1 +
docs/keybindings/Keybindings_ko.md | 1 +
docs/keybindings/Keybindings_nl.md | 1 +
docs/keybindings/Keybindings_pl.md | 1 +
docs/keybindings/Keybindings_ru.md | 1 +
docs/keybindings/Keybindings_zh-CN.md | 1 +
docs/keybindings/Keybindings_zh-TW.md | 1 +
pkg/commands/git_commands/remote_loader.go | 24 +++++++++-----
pkg/config/app_config.go | 10 +++---
pkg/config/user_config.go | 2 ++
pkg/gui/controllers/helpers/refs_helper.go | 31 +++++++++++++++++++
.../controllers/remote_branches_controller.go | 18 +++++++++++
pkg/i18n/english.go | 6 ++++
pkg/i18n/japanese.go | 7 +++--
pkg/i18n/russian.go | 3 ++
schema/config.json | 4 +++
17 files changed, 100 insertions(+), 13 deletions(-)
diff --git a/docs/keybindings/Keybindings_en.md b/docs/keybindings/Keybindings_en.md
index 5c7d2f1f97d6..e4f6fd8be036 100644
--- a/docs/keybindings/Keybindings_en.md
+++ b/docs/keybindings/Keybindings_en.md
@@ -258,6 +258,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
r: Rebase checked-out branch onto this branch
d: Delete remote tag
u: Set as upstream of checked-out branch
+ s: Sort order
g: View reset options
w: View worktree options
<enter>: View commits
diff --git a/docs/keybindings/Keybindings_ja.md b/docs/keybindings/Keybindings_ja.md
index 2acbd3777cbe..16e2d8a4dfc6 100644
--- a/docs/keybindings/Keybindings_ja.md
+++ b/docs/keybindings/Keybindings_ja.md
@@ -323,6 +323,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
r: Rebase checked-out branch onto this branch
d: Delete remote tag
u: Set as upstream of checked-out branch
+ s: 並び替え
g: View reset options
w: View worktree options
<enter>: コミットを閲覧
diff --git a/docs/keybindings/Keybindings_ko.md b/docs/keybindings/Keybindings_ko.md
index 68e3a25ed5a2..35dd0ebc9bd6 100644
--- a/docs/keybindings/Keybindings_ko.md
+++ b/docs/keybindings/Keybindings_ko.md
@@ -241,6 +241,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
r: 체크아웃된 브랜치를 이 브랜치에 리베이스
d: Delete remote tag
u: Set as upstream of checked-out branch
+ s: Sort order
g: View reset options
w: View worktree options
<enter>: 커밋 보기
diff --git a/docs/keybindings/Keybindings_nl.md b/docs/keybindings/Keybindings_nl.md
index a53ad13a8753..c5fe3f3b3d1d 100644
--- a/docs/keybindings/Keybindings_nl.md
+++ b/docs/keybindings/Keybindings_nl.md
@@ -236,6 +236,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
r: Rebase branch
d: Delete remote tag
u: Stel in als upstream van uitgecheckte branch
+ s: Sort order
g: Bekijk reset opties
w: View worktree options
<enter>: Bekijk commits
diff --git a/docs/keybindings/Keybindings_pl.md b/docs/keybindings/Keybindings_pl.md
index 9ffffade50a8..558e3b50f4d7 100644
--- a/docs/keybindings/Keybindings_pl.md
+++ b/docs/keybindings/Keybindings_pl.md
@@ -235,6 +235,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
r: Zmiana bazy gałęzi
d: Delete remote tag
u: Set as upstream of checked-out branch
+ s: Sort order
g: Wyświetl opcje resetu
w: View worktree options
<enter>: View commits
diff --git a/docs/keybindings/Keybindings_ru.md b/docs/keybindings/Keybindings_ru.md
index 3296c8c1615f..22600aea7889 100644
--- a/docs/keybindings/Keybindings_ru.md
+++ b/docs/keybindings/Keybindings_ru.md
@@ -296,6 +296,7 @@ _Связки клавиш_
r: Перебазировать переключённую ветку на эту ветку
d: Delete remote tag
u: Установить как upstream-ветку переключённую ветку
+ s: Порядок сортировки
g: Просмотреть параметры сброса
w: View worktree options
<enter>: Просмотреть коммиты
diff --git a/docs/keybindings/Keybindings_zh-CN.md b/docs/keybindings/Keybindings_zh-CN.md
index 3f9d060ca456..354ef952a36a 100644
--- a/docs/keybindings/Keybindings_zh-CN.md
+++ b/docs/keybindings/Keybindings_zh-CN.md
@@ -337,6 +337,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
r: 将已检出的分支变基到该分支
d: Delete remote tag
u: 设置为检出分支的上游
+ s: Sort order
g: 查看重置选项
w: View worktree options
<enter>: 查看提交
diff --git a/docs/keybindings/Keybindings_zh-TW.md b/docs/keybindings/Keybindings_zh-TW.md
index 78fd756872fe..6e3094afd9c9 100644
--- a/docs/keybindings/Keybindings_zh-TW.md
+++ b/docs/keybindings/Keybindings_zh-TW.md
@@ -347,6 +347,7 @@ _說明:`` 表示 Ctrl+B、`` 表示 Alt+B,`B`表示 Shift+B_
r: 將已檢出的分支變基至此分支
d: Delete remote tag
u: 將此分支設為當前分支之上游
+ s: Sort order
g: 檢視重設選項
w: View worktree options
<enter>: 檢視提交
diff --git a/pkg/commands/git_commands/remote_loader.go b/pkg/commands/git_commands/remote_loader.go
index 6551ecb25cf6..9d48374c52ae 100644
--- a/pkg/commands/git_commands/remote_loader.go
+++ b/pkg/commands/git_commands/remote_loader.go
@@ -1,6 +1,7 @@
package git_commands
import (
+ "fmt"
"strings"
"sync"
@@ -83,14 +84,23 @@ func (self *RemoteLoader) GetRemotes() ([]*models.Remote, error) {
func (self *RemoteLoader) getRemoteBranchesByRemoteName() (map[string][]*models.RemoteBranch, error) {
remoteBranchesByRemoteName := make(map[string][]*models.RemoteBranch)
- cmdArgs := NewGitCmd("branch").Arg("-r").ToArgv()
- err := self.cmd.New(cmdArgs).DontLog().RunAndProcessLines(func(line string) (bool, error) {
- // excluding lines like 'origin/HEAD -> origin/master' (there will be a separate
- // line for 'origin/master')
- if strings.Contains(line, "->") {
- return false, nil
- }
+ var sortOrder string
+ switch strings.ToLower(self.AppState.RemoteBranchSortOrder) {
+ case "alphabetical":
+ sortOrder = "refname"
+ case "date":
+ sortOrder = "-committerdate"
+ default:
+ sortOrder = "refname"
+ }
+
+ cmdArgs := NewGitCmd("for-each-ref").
+ Arg(fmt.Sprintf("--sort=%s", sortOrder)).
+ Arg("--format=%(refname:short)").
+ Arg("refs/remotes").
+ ToArgv()
+ err := self.cmd.New(cmdArgs).DontLog().RunAndProcessLines(func(line string) (bool, error) {
line = strings.TrimSpace(line)
split := strings.SplitN(line, "/", 2)
diff --git a/pkg/config/app_config.go b/pkg/config/app_config.go
index 54d41b695e24..927f9f309734 100644
--- a/pkg/config/app_config.go
+++ b/pkg/config/app_config.go
@@ -323,14 +323,16 @@ type AppState struct {
HideCommandLog bool
IgnoreWhitespaceInDiffView bool
DiffContextSize int
+ RemoteBranchSortOrder string
}
func getDefaultAppState() *AppState {
return &AppState{
- LastUpdateCheck: 0,
- RecentRepos: []string{},
- StartupPopupVersion: 0,
- DiffContextSize: 3,
+ LastUpdateCheck: 0,
+ RecentRepos: []string{},
+ StartupPopupVersion: 0,
+ DiffContextSize: 3,
+ RemoteBranchSortOrder: "alphabetical",
}
}
diff --git a/pkg/config/user_config.go b/pkg/config/user_config.go
index 3d772e778d40..749b41822404 100644
--- a/pkg/config/user_config.go
+++ b/pkg/config/user_config.go
@@ -389,6 +389,7 @@ type KeybindingBranchesConfig struct {
PushTag string `yaml:"pushTag"`
SetUpstream string `yaml:"setUpstream"`
FetchRemote string `yaml:"fetchRemote"`
+ SortOrder string `yaml:"sortOrder"`
}
type KeybindingWorktreesConfig struct {
@@ -781,6 +782,7 @@ func GetDefaultConfig() *UserConfig {
PushTag: "P",
SetUpstream: "u",
FetchRemote: "f",
+ SortOrder: "s",
},
Worktrees: KeybindingWorktreesConfig{
ViewWorktreeOptions: "w",
diff --git a/pkg/gui/controllers/helpers/refs_helper.go b/pkg/gui/controllers/helpers/refs_helper.go
index a6de0d39fe88..b4617c8a033a 100644
--- a/pkg/gui/controllers/helpers/refs_helper.go
+++ b/pkg/gui/controllers/helpers/refs_helper.go
@@ -119,6 +119,37 @@ func (self *RefsHelper) ResetToRef(ref string, strength string, envVars []string
return nil
}
+func (self *RefsHelper) CreateSortOrderMenu(onSelected func(sortOrder string) error) error {
+ type sortOrderWithKey struct {
+ key types.Key
+ label string
+ sortKey string
+ sortOrder string
+ }
+ sortKeys := []sortOrderWithKey{
+ {label: self.c.Tr.SortAlphabetical, sortKey: "refname", sortOrder: "alphabetical", key: 'a'},
+ {label: self.c.Tr.SortByDate, sortKey: "-committerdate", sortOrder: "date", key: 'd'},
+ }
+
+ menuItems := lo.Map(sortKeys, func(row sortOrderWithKey, _ int) *types.MenuItem {
+ return &types.MenuItem{
+ LabelColumns: []string{
+ row.label,
+ style.FgYellow.Sprintf("--sort=%s", row.sortKey),
+ },
+ OnPress: func() error {
+ return onSelected(row.sortOrder)
+ },
+ Key: row.key,
+ }
+ })
+
+ return self.c.Menu(types.CreateMenuOptions{
+ Title: self.c.Tr.SortOrder,
+ Items: menuItems,
+ })
+}
+
func (self *RefsHelper) CreateGitResetMenu(ref string) error {
type strengthWithKey struct {
strength string
diff --git a/pkg/gui/controllers/remote_branches_controller.go b/pkg/gui/controllers/remote_branches_controller.go
index ffb55d5cad21..6d224474631b 100644
--- a/pkg/gui/controllers/remote_branches_controller.go
+++ b/pkg/gui/controllers/remote_branches_controller.go
@@ -58,6 +58,12 @@ func (self *RemoteBranchesController) GetKeybindings(opts types.KeybindingsOpts)
Handler: self.checkSelected(self.setAsUpstream),
Description: self.c.Tr.SetAsUpstream,
},
+ {
+ Key: opts.GetKey(opts.Config.Branches.SortOrder),
+ Handler: self.createSortMenu,
+ Description: self.c.Tr.SortOrder,
+ OpensMenu: true,
+ },
{
Key: opts.GetKey(opts.Config.Commits.ViewResetOptions),
Handler: self.checkSelected(self.createResetMenu),
@@ -121,6 +127,18 @@ func (self *RemoteBranchesController) rebase(selectedBranch *models.RemoteBranch
return self.c.Helpers().MergeAndRebase.RebaseOntoRef(selectedBranch.FullName())
}
+func (self *RemoteBranchesController) createSortMenu() error {
+ return self.c.Helpers().Refs.CreateSortOrderMenu(func(sortOrder string) error {
+ if self.c.GetAppState().RemoteBranchSortOrder != sortOrder {
+ self.c.GetAppState().RemoteBranchSortOrder = sortOrder
+ self.c.SaveAppStateAndLogError()
+ self.c.Contexts().RemoteBranches.SetSelectedLineIdx(0)
+ return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.REMOTES}})
+ }
+ return nil
+ })
+}
+
func (self *RemoteBranchesController) createResetMenu(selectedBranch *models.RemoteBranch) error {
return self.c.Helpers().Refs.CreateGitResetMenu(selectedBranch.FullName())
}
diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go
index 32392cee7a40..260976d649e4 100644
--- a/pkg/i18n/english.go
+++ b/pkg/i18n/english.go
@@ -555,6 +555,9 @@ type TranslationSet struct {
LogMenuTitle string
ToggleShowGitGraphAll string
ShowGitGraph string
+ SortOrder string
+ SortAlphabetical string
+ SortByDate string
SortCommits string
CantChangeContextSizeError string
OpenCommitInBrowser string
@@ -1364,6 +1367,9 @@ func EnglishTranslationSet() TranslationSet {
LogMenuTitle: "Commit Log Options",
ToggleShowGitGraphAll: "Toggle show whole git graph (pass the `--all` flag to `git log`)",
ShowGitGraph: "Show git graph",
+ SortOrder: "Sort order",
+ SortAlphabetical: "Alphabetical",
+ SortByDate: "Date",
SortCommits: "Commit sort order",
CantChangeContextSizeError: "Cannot change context while in patch building mode because we were too lazy to support it when releasing the feature. If you really want it, please let us know!",
OpenCommitInBrowser: "Open commit in browser",
diff --git a/pkg/i18n/japanese.go b/pkg/i18n/japanese.go
index 03e4ff6f48d0..089c95ed6807 100644
--- a/pkg/i18n/japanese.go
+++ b/pkg/i18n/japanese.go
@@ -461,8 +461,11 @@ func japaneseTranslationSet() TranslationSet {
OpenLogMenu: "ログメニューを開く",
LogMenuTitle: "コミットログオプション",
// ToggleShowGitGraphAll: "Toggle show whole git graph (pass the `--all` flag to `git log`)",
- ShowGitGraph: "コミットグラフの表示",
- SortCommits: "コミットの表示順",
+ ShowGitGraph: "コミットグラフの表示",
+ SortOrder: "並び替え",
+ SortAlphabetical: "アルファベット順",
+ SortByDate: "日付順",
+ SortCommits: "コミットの表示順",
// CantChangeContextSizeError: "Cannot change context while in patch building mode because we were too lazy to support it when releasing the feature. If you really want it, please let us know!",
OpenCommitInBrowser: "ブラウザでコミットを開く",
// LcViewBisectOptions: "View bisect options",
diff --git a/pkg/i18n/russian.go b/pkg/i18n/russian.go
index 3332f72cdb2d..e4b67a77ecf6 100644
--- a/pkg/i18n/russian.go
+++ b/pkg/i18n/russian.go
@@ -525,6 +525,9 @@ func RussianTranslationSet() TranslationSet {
LogMenuTitle: "Параметры журнала коммитов",
ToggleShowGitGraphAll: "Переключить отображение всего git графа (передать флаг --all в git log )",
ShowGitGraph: "Показать git граф",
+ SortOrder: "Порядок сортировки",
+ SortAlphabetical: "По алфавиту",
+ SortByDate: "По дате",
SortCommits: "Упорядочить коммиты",
CantChangeContextSizeError: "Невозможно изменить контекст в режиме создания патча, потому что мы были слишком ленивы, чтобы поддерживать его при выпуске функции. Если вы действительно этого хотите, пожалуйста, дайте нам знать!",
OpenCommitInBrowser: "Открыть коммит в браузере",
diff --git a/schema/config.json b/schema/config.json
index 186ca25760bc..54f6d69612b2 100644
--- a/schema/config.json
+++ b/schema/config.json
@@ -1014,6 +1014,10 @@
"fetchRemote": {
"type": "string",
"default": "f"
+ },
+ "sortOrder": {
+ "type": "string",
+ "default": "s"
}
},
"additionalProperties": false,
From 1e3935cbafedb0666a6193858e9a3f45191fd82c Mon Sep 17 00:00:00 2001
From: Stefan Haller
Date: Wed, 20 Dec 2023 11:54:20 +0100
Subject: [PATCH 002/280] Add integration test for remote branch sort order
---
pkg/integration/components/shell.go | 20 ++++++-
.../tests/branch/sort_remote_branches.go | 55 +++++++++++++++++++
pkg/integration/tests/test_list.go | 1 +
3 files changed, 74 insertions(+), 2 deletions(-)
create mode 100644 pkg/integration/tests/branch/sort_remote_branches.go
diff --git a/pkg/integration/components/shell.go b/pkg/integration/components/shell.go
index 33b82fc58d5f..5a3bbaa29a89 100644
--- a/pkg/integration/components/shell.go
+++ b/pkg/integration/components/shell.go
@@ -31,7 +31,11 @@ func NewShell(dir string, fail func(string)) *Shell {
}
func (self *Shell) RunCommand(args []string) *Shell {
- output, err := self.runCommandWithOutput(args)
+ return self.RunCommandWithEnv(args, []string{})
+}
+
+func (self *Shell) RunCommandWithEnv(args []string, env []string) *Shell {
+ output, err := self.runCommandWithOutputAndEnv(args, env)
if err != nil {
self.fail(fmt.Sprintf("error running command: %v\n%s", args, output))
}
@@ -49,8 +53,12 @@ func (self *Shell) RunCommandExpectError(args []string) *Shell {
}
func (self *Shell) runCommandWithOutput(args []string) (string, error) {
+ return self.runCommandWithOutputAndEnv(args, []string{})
+}
+
+func (self *Shell) runCommandWithOutputAndEnv(args []string, env []string) (string, error) {
cmd := exec.Command(args[0], args[1:]...)
- cmd.Env = os.Environ()
+ cmd.Env = append(os.Environ(), env...)
cmd.Dir = self.dir
output, err := cmd.CombinedOutput()
@@ -164,6 +172,14 @@ func (self *Shell) EmptyCommitDaysAgo(message string, daysAgo int) *Shell {
return self.RunCommand([]string{"git", "commit", "--allow-empty", "--date", fmt.Sprintf("%d days ago", daysAgo), "-m", message})
}
+func (self *Shell) EmptyCommitWithDate(message string, date string) *Shell {
+ env := []string{
+ "GIT_AUTHOR_DATE=" + date,
+ "GIT_COMMITTER_DATE=" + date,
+ }
+ return self.RunCommandWithEnv([]string{"git", "commit", "--allow-empty", "-m", message}, env)
+}
+
func (self *Shell) Revert(ref string) *Shell {
return self.RunCommand([]string{"git", "revert", ref})
}
diff --git a/pkg/integration/tests/branch/sort_remote_branches.go b/pkg/integration/tests/branch/sort_remote_branches.go
new file mode 100644
index 000000000000..35e2f700a07a
--- /dev/null
+++ b/pkg/integration/tests/branch/sort_remote_branches.go
@@ -0,0 +1,55 @@
+package branch
+
+import (
+ "github.com/jesseduffield/lazygit/pkg/config"
+ . "github.com/jesseduffield/lazygit/pkg/integration/components"
+)
+
+var SortRemoteBranches = NewIntegrationTest(NewIntegrationTestArgs{
+ Description: "Sort remote branches alphabetically or by date",
+ ExtraCmdArgs: []string{},
+ Skip: false,
+ SetupConfig: func(config *config.AppConfig) {},
+ SetupRepo: func(shell *Shell) {
+ shell.NewBranch("first")
+ shell.EmptyCommitWithDate("commit", "2023-04-07 10:00:00")
+ shell.NewBranch("second")
+ shell.EmptyCommitWithDate("commit", "2023-04-07 12:00:00")
+ shell.NewBranch("third")
+ shell.EmptyCommitWithDate("commit", "2023-04-07 11:00:00")
+ shell.CloneIntoRemote("origin")
+ },
+ Run: func(t *TestDriver, keys config.KeybindingConfig) {
+ t.Views().Remotes().
+ Focus().
+ Lines(
+ Contains("origin").IsSelected(),
+ ).
+ PressEnter()
+
+ // sorted alphabetically by default
+ t.Views().RemoteBranches().
+ IsFocused().
+ Lines(
+ Contains("first").IsSelected(),
+ Contains("second"),
+ Contains("third"),
+ ).
+ SelectNextItem() // to test that the selection jumps back to the first when sorting
+
+ t.Views().RemoteBranches().
+ Press(keys.Branches.SortOrder)
+
+ t.ExpectPopup().Menu().Title(Equals("Sort order")).
+ Select(Contains("-committerdate")).
+ Confirm()
+
+ t.Views().RemoteBranches().
+ IsFocused().
+ Lines(
+ Contains("second").IsSelected(),
+ Contains("third"),
+ Contains("first"),
+ )
+ },
+})
diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go
index 0aa61b463bf8..9246aa82a80f 100644
--- a/pkg/integration/tests/test_list.go
+++ b/pkg/integration/tests/test_list.go
@@ -54,6 +54,7 @@ var tests = []*components.IntegrationTest{
branch.ResetToUpstream,
branch.SetUpstream,
branch.ShowDivergenceFromUpstream,
+ branch.SortRemoteBranches,
branch.Suggestions,
branch.UnsetUpstream,
cherry_pick.CherryPick,
From 7f36494eb2710588dc08b2dd110608f90764fd87 Mon Sep 17 00:00:00 2001
From: README-bot
Date: Fri, 22 Dec 2023 08:16:48 +0000
Subject: [PATCH 003/280] Updated README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 70e4086af39b..44409c7f06c5 100644
--- a/README.md
+++ b/README.md
@@ -37,7 +37,7 @@ A simple terminal UI for git commands
-
+
## Elevator Pitch
From bc330b8ff3ef7533bc1f96c7d2b309eaf94adf61 Mon Sep 17 00:00:00 2001
From: AzraelSec
Date: Wed, 20 Dec 2023 20:51:39 +0100
Subject: [PATCH 004/280] feat: add age on stash lines
---
pkg/commands/git_commands/stash_loader.go | 21 ++++++++++++++++---
.../git_commands/stash_loader_test.go | 4 ++--
pkg/commands/models/stash_entry.go | 5 +++--
pkg/gui/presentation/stash_entries.go | 6 +++++-
pkg/integration/tests/stash/rename.go | 6 +++---
pkg/integration/tests/stash/stash.go | 2 +-
6 files changed, 32 insertions(+), 12 deletions(-)
diff --git a/pkg/commands/git_commands/stash_loader.go b/pkg/commands/git_commands/stash_loader.go
index 1ab0e0ad0e95..318e1ce0f0d9 100644
--- a/pkg/commands/git_commands/stash_loader.go
+++ b/pkg/commands/git_commands/stash_loader.go
@@ -32,7 +32,7 @@ func (self *StashLoader) GetStashEntries(filterPath string) []*models.StashEntry
return self.getUnfilteredStashEntries()
}
- cmdArgs := NewGitCmd("stash").Arg("list", "--name-only").ToArgv()
+ cmdArgs := NewGitCmd("stash").Arg("list", "--name-only", "--pretty=%ct|%gs").ToArgv()
rawString, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
if err != nil {
return self.getUnfilteredStashEntries()
@@ -66,7 +66,7 @@ outer:
}
func (self *StashLoader) getUnfilteredStashEntries() []*models.StashEntry {
- cmdArgs := NewGitCmd("stash").Arg("list", "-z", "--pretty=%gs").ToArgv()
+ cmdArgs := NewGitCmd("stash").Arg("list", "-z", "--pretty=%ct|%gs").ToArgv()
rawString, _ := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
return lo.Map(utils.SplitNul(rawString), func(line string, index int) *models.StashEntry {
@@ -75,8 +75,23 @@ func (self *StashLoader) getUnfilteredStashEntries() []*models.StashEntry {
}
func (c *StashLoader) stashEntryFromLine(line string, index int) *models.StashEntry {
- return &models.StashEntry{
+ model := &models.StashEntry{
Name: line,
Index: index,
}
+
+ tstr, msg, ok := strings.Cut(line, "|")
+ if !ok {
+ return model
+ }
+
+ t, err := strconv.ParseInt(tstr, 10, 64)
+ if err != nil {
+ return model
+ }
+
+ model.Name = msg
+ model.Recency = utils.UnixToTimeAgo(t)
+
+ return model
}
diff --git a/pkg/commands/git_commands/stash_loader_test.go b/pkg/commands/git_commands/stash_loader_test.go
index d48b83be7493..393b4d4b93b2 100644
--- a/pkg/commands/git_commands/stash_loader_test.go
+++ b/pkg/commands/git_commands/stash_loader_test.go
@@ -22,14 +22,14 @@ func TestGetStashEntries(t *testing.T) {
"No stash entries found",
"",
oscommands.NewFakeRunner(t).
- ExpectGitArgs([]string{"stash", "list", "-z", "--pretty=%gs"}, "", nil),
+ ExpectGitArgs([]string{"stash", "list", "-z", "--pretty=%ct|%gs"}, "", nil),
[]*models.StashEntry{},
},
{
"Several stash entries found",
"",
oscommands.NewFakeRunner(t).
- ExpectGitArgs([]string{"stash", "list", "-z", "--pretty=%gs"},
+ ExpectGitArgs([]string{"stash", "list", "-z", "--pretty=%ct|%gs"},
"WIP on add-pkg-commands-test: 55c6af2 increase parallel build\x00WIP on master: bb86a3f update github template\x00",
nil,
),
diff --git a/pkg/commands/models/stash_entry.go b/pkg/commands/models/stash_entry.go
index e70dfbf09828..2a1cc8435cb8 100644
--- a/pkg/commands/models/stash_entry.go
+++ b/pkg/commands/models/stash_entry.go
@@ -4,8 +4,9 @@ import "fmt"
// StashEntry : A git stash entry
type StashEntry struct {
- Index int
- Name string
+ Index int
+ Recency string
+ Name string
}
func (s *StashEntry) FullRefName() string {
diff --git a/pkg/gui/presentation/stash_entries.go b/pkg/gui/presentation/stash_entries.go
index c45e919825b8..c4a1a4de167c 100644
--- a/pkg/gui/presentation/stash_entries.go
+++ b/pkg/gui/presentation/stash_entries.go
@@ -3,6 +3,7 @@ package presentation
import (
"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/theme"
"github.com/samber/lo"
)
@@ -21,10 +22,13 @@ func getStashEntryDisplayStrings(s *models.StashEntry, diffed bool) []string {
textStyle = theme.DiffTerminalColor
}
- res := make([]string, 0, 2)
+ res := make([]string, 0, 3)
+ res = append(res, style.FgCyan.Sprint(s.Recency))
+
if icons.IsIconEnabled() {
res = append(res, textStyle.Sprint(icons.IconForStash(s)))
}
+
res = append(res, textStyle.Sprint(s.Name))
return res
}
diff --git a/pkg/integration/tests/stash/rename.go b/pkg/integration/tests/stash/rename.go
index eb57fa6541ee..4122b3aa8937 100644
--- a/pkg/integration/tests/stash/rename.go
+++ b/pkg/integration/tests/stash/rename.go
@@ -22,14 +22,14 @@ var Rename = NewIntegrationTest(NewIntegrationTestArgs{
t.Views().Stash().
Focus().
Lines(
- Equals("On master: bar"),
- Equals("On master: foo"),
+ Contains("On master: bar"),
+ Contains("On master: foo"),
).
SelectNextItem().
Press(keys.Stash.RenameStash).
Tap(func() {
t.ExpectPopup().Prompt().Title(Equals("Rename stash: stash@{1}")).Type(" baz").Confirm()
}).
- SelectedLine(Equals("On master: foo baz"))
+ SelectedLine(Contains("On master: foo baz"))
},
})
diff --git a/pkg/integration/tests/stash/stash.go b/pkg/integration/tests/stash/stash.go
index d5fc3e92e81c..9f829215649d 100644
--- a/pkg/integration/tests/stash/stash.go
+++ b/pkg/integration/tests/stash/stash.go
@@ -29,7 +29,7 @@ var Stash = NewIntegrationTest(NewIntegrationTestArgs{
t.Views().Stash().
Lines(
- Contains("my stashed file"),
+ MatchesRegexp(`\ds .* my stashed file`),
)
t.Views().Files().
From 50044dd5e0ad7d00ea1dbbf0b7c8c97a32bf3af2 Mon Sep 17 00:00:00 2001
From: AzraelSec
Date: Thu, 21 Dec 2023 19:21:13 +0100
Subject: [PATCH 005/280] chore: use null char as a stash entries divider
during loading
---
pkg/commands/git_commands/stash_loader.go | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/pkg/commands/git_commands/stash_loader.go b/pkg/commands/git_commands/stash_loader.go
index 318e1ce0f0d9..f97cc718a445 100644
--- a/pkg/commands/git_commands/stash_loader.go
+++ b/pkg/commands/git_commands/stash_loader.go
@@ -32,14 +32,14 @@ func (self *StashLoader) GetStashEntries(filterPath string) []*models.StashEntry
return self.getUnfilteredStashEntries()
}
- cmdArgs := NewGitCmd("stash").Arg("list", "--name-only", "--pretty=%ct|%gs").ToArgv()
+ cmdArgs := NewGitCmd("stash").Arg("list", "-z", "--name-only", "--pretty=%ct|%gs").ToArgv()
rawString, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
if err != nil {
return self.getUnfilteredStashEntries()
}
stashEntries := []*models.StashEntry{}
var currentStashEntry *models.StashEntry
- lines := utils.SplitLines(rawString)
+ lines := utils.SplitNul(rawString)
isAStash := func(line string) bool { return strings.HasPrefix(line, "stash@{") }
re := regexp.MustCompile(`stash@\{(\d+)\}`)
From 1e85c4379f8d2faa4b9cfb2e43bac1d5fc41638a Mon Sep 17 00:00:00 2001
From: README-bot
Date: Wed, 27 Dec 2023 10:24:12 +0000
Subject: [PATCH 006/280] Updated README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 44409c7f06c5..dd811a752994 100644
--- a/README.md
+++ b/README.md
@@ -37,7 +37,7 @@ A simple terminal UI for git commands
-
+
## Elevator Pitch
From 36a29f225b417d993ba7497be4325cbceeffb4cb Mon Sep 17 00:00:00 2001
From: Alex March
Date: Thu, 21 Dec 2023 17:57:58 +0900
Subject: [PATCH 007/280] Add a sort order menu for local branches
---
docs/keybindings/Keybindings_en.md | 1 +
docs/keybindings/Keybindings_ja.md | 1 +
docs/keybindings/Keybindings_ko.md | 1 +
docs/keybindings/Keybindings_nl.md | 1 +
docs/keybindings/Keybindings_pl.md | 1 +
docs/keybindings/Keybindings_ru.md | 1 +
docs/keybindings/Keybindings_zh-CN.md | 1 +
docs/keybindings/Keybindings_zh-TW.md | 1 +
pkg/commands/git_commands/branch_loader.go | 73 ++++++++++++-------
.../git_commands/branch_loader_test.go | 54 ++++++++++----
pkg/config/app_config.go | 2 +
pkg/gui/controllers/branches_controller.go | 18 +++++
pkg/gui/controllers/helpers/refresh_helper.go | 2 +-
pkg/gui/controllers/helpers/refs_helper.go | 39 ++++++----
.../controllers/remote_branches_controller.go | 2 +-
pkg/i18n/english.go | 4 +
16 files changed, 146 insertions(+), 56 deletions(-)
diff --git a/docs/keybindings/Keybindings_en.md b/docs/keybindings/Keybindings_en.md
index e4f6fd8be036..b5dc938980f6 100644
--- a/docs/keybindings/Keybindings_en.md
+++ b/docs/keybindings/Keybindings_en.md
@@ -154,6 +154,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
M: Merge into currently checked out branch
f: Fast-forward this branch from its upstream
T: Create tag
+ s: Sort order
g: View reset options
R: Rename branch
u: View upstream options
diff --git a/docs/keybindings/Keybindings_ja.md b/docs/keybindings/Keybindings_ja.md
index 16e2d8a4dfc6..3b7ef69918cc 100644
--- a/docs/keybindings/Keybindings_ja.md
+++ b/docs/keybindings/Keybindings_ja.md
@@ -226,6 +226,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
M: 現在のブランチにマージ
f: Fast-forward this branch from its upstream
T: タグを作成
+ s: 並び替え
g: View reset options
R: ブランチ名を変更
u: View upstream options
diff --git a/docs/keybindings/Keybindings_ko.md b/docs/keybindings/Keybindings_ko.md
index 35dd0ebc9bd6..02b5db76e550 100644
--- a/docs/keybindings/Keybindings_ko.md
+++ b/docs/keybindings/Keybindings_ko.md
@@ -188,6 +188,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
M: 현재 브랜치에 병합
f: Fast-forward this branch from its upstream
T: 태그를 생성
+ s: Sort order
g: View reset options
R: 브랜치 이름 변경
u: View upstream options
diff --git a/docs/keybindings/Keybindings_nl.md b/docs/keybindings/Keybindings_nl.md
index c5fe3f3b3d1d..e1bddf9298ec 100644
--- a/docs/keybindings/Keybindings_nl.md
+++ b/docs/keybindings/Keybindings_nl.md
@@ -96,6 +96,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
M: Merge in met huidige checked out branch
f: Fast-forward deze branch vanaf zijn upstream
T: Creëer tag
+ s: Sort order
g: Bekijk reset opties
R: Hernoem branch
u: View upstream options
diff --git a/docs/keybindings/Keybindings_pl.md b/docs/keybindings/Keybindings_pl.md
index 558e3b50f4d7..f9eb05e09826 100644
--- a/docs/keybindings/Keybindings_pl.md
+++ b/docs/keybindings/Keybindings_pl.md
@@ -111,6 +111,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
M: Scal do obecnej gałęzi
f: Fast-forward this branch from its upstream
T: Create tag
+ s: Sort order
g: Wyświetl opcje resetu
R: Rename branch
u: View upstream options
diff --git a/docs/keybindings/Keybindings_ru.md b/docs/keybindings/Keybindings_ru.md
index 22600aea7889..f0a1fd4e2d74 100644
--- a/docs/keybindings/Keybindings_ru.md
+++ b/docs/keybindings/Keybindings_ru.md
@@ -186,6 +186,7 @@ _Связки клавиш_
M: Слияние с текущей переключённой веткой
f: Перемотать эту ветку вперёд из её upstream-ветки
T: Создать тег
+ s: Порядок сортировки
g: Просмотреть параметры сброса
R: Переименовать ветку
u: View upstream options
diff --git a/docs/keybindings/Keybindings_zh-CN.md b/docs/keybindings/Keybindings_zh-CN.md
index 354ef952a36a..9b015285149e 100644
--- a/docs/keybindings/Keybindings_zh-CN.md
+++ b/docs/keybindings/Keybindings_zh-CN.md
@@ -89,6 +89,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
M: 合并到当前检出的分支
f: 从上游快进此分支
T: 创建标签
+ s: Sort order
g: 查看重置选项
R: 重命名分支
u: View upstream options
diff --git a/docs/keybindings/Keybindings_zh-TW.md b/docs/keybindings/Keybindings_zh-TW.md
index 6e3094afd9c9..8665255814b5 100644
--- a/docs/keybindings/Keybindings_zh-TW.md
+++ b/docs/keybindings/Keybindings_zh-TW.md
@@ -261,6 +261,7 @@ _說明:`` 表示 Ctrl+B、`` 表示 Alt+B,`B`表示 Shift+B_
M: 合併到當前檢出的分支
f: 從上游快進此分支
T: 建立標籤
+ s: Sort order
g: 檢視重設選項
R: 重新命名分支
u: View upstream options
diff --git a/pkg/commands/git_commands/branch_loader.go b/pkg/commands/git_commands/branch_loader.go
index b7e55a9108e6..198368502b68 100644
--- a/pkg/commands/git_commands/branch_loader.go
+++ b/pkg/commands/git_commands/branch_loader.go
@@ -3,6 +3,7 @@ package git_commands
import (
"fmt"
"regexp"
+ "strconv"
"strings"
"github.com/jesseduffield/generics/set"
@@ -62,32 +63,33 @@ func NewBranchLoader(
func (self *BranchLoader) Load(reflogCommits []*models.Commit) ([]*models.Branch, error) {
branches := self.obtainBranches()
- reflogBranches := self.obtainReflogBranches(reflogCommits)
-
- // loop through reflog branches. If there is a match, merge them, then remove it from the branches and keep it in the reflog branches
- branchesWithRecency := make([]*models.Branch, 0)
-outer:
- for _, reflogBranch := range reflogBranches {
- for j, branch := range branches {
- if branch.Head {
- continue
- }
- if strings.EqualFold(reflogBranch.Name, branch.Name) {
- branch.Recency = reflogBranch.Recency
- branchesWithRecency = append(branchesWithRecency, branch)
- branches = utils.Remove(branches, j)
- continue outer
+ if self.AppState.LocalBranchSortOrder == "recency" {
+ reflogBranches := self.obtainReflogBranches(reflogCommits)
+ // loop through reflog branches. If there is a match, merge them, then remove it from the branches and keep it in the reflog branches
+ branchesWithRecency := make([]*models.Branch, 0)
+ outer:
+ for _, reflogBranch := range reflogBranches {
+ for j, branch := range branches {
+ if branch.Head {
+ continue
+ }
+ if strings.EqualFold(reflogBranch.Name, branch.Name) {
+ branch.Recency = reflogBranch.Recency
+ branchesWithRecency = append(branchesWithRecency, branch)
+ branches = utils.Remove(branches, j)
+ continue outer
+ }
}
}
- }
- // Sort branches that don't have a recency value alphabetically
- // (we're really doing this for the sake of deterministic behaviour across git versions)
- slices.SortFunc(branches, func(a *models.Branch, b *models.Branch) bool {
- return a.Name < b.Name
- })
+ // Sort branches that don't have a recency value alphabetically
+ // (we're really doing this for the sake of deterministic behaviour across git versions)
+ slices.SortFunc(branches, func(a *models.Branch, b *models.Branch) bool {
+ return a.Name < b.Name
+ })
- branches = utils.Prepend(branches, branchesWithRecency...)
+ branches = utils.Prepend(branches, branchesWithRecency...)
+ }
foundHead := false
for i, branch := range branches {
@@ -144,7 +146,8 @@ func (self *BranchLoader) obtainBranches() []*models.Branch {
return nil, false
}
- return obtainBranch(split), true
+ storeCommitDateAsRecency := self.AppState.LocalBranchSortOrder != "recency"
+ return obtainBranch(split, storeCommitDateAsRecency), true
})
}
@@ -156,8 +159,18 @@ func (self *BranchLoader) getRawBranches() (string, error) {
"%00",
)
+ var sortOrder string
+ switch strings.ToLower(self.AppState.LocalBranchSortOrder) {
+ case "recency", "date":
+ sortOrder = "-committerdate"
+ case "alphabetical":
+ sortOrder = "refname"
+ default:
+ sortOrder = "refname"
+ }
+
cmdArgs := NewGitCmd("for-each-ref").
- Arg("--sort=-committerdate").
+ Arg(fmt.Sprintf("--sort=%s", sortOrder)).
Arg(fmt.Sprintf("--format=%s", format)).
Arg("refs/heads").
ToArgv()
@@ -172,22 +185,32 @@ var branchFields = []string{
"upstream:track",
"subject",
"objectname",
+ "committerdate:unix",
}
// Obtain branch information from parsed line output of getRawBranches()
-func obtainBranch(split []string) *models.Branch {
+func obtainBranch(split []string, storeCommitDateAsRecency bool) *models.Branch {
headMarker := split[0]
fullName := split[1]
upstreamName := split[2]
track := split[3]
subject := split[4]
commitHash := split[5]
+ commitDate := split[6]
name := strings.TrimPrefix(fullName, "heads/")
pushables, pullables, gone := parseUpstreamInfo(upstreamName, track)
+ recency := ""
+ if storeCommitDateAsRecency {
+ if unixTimestamp, err := strconv.ParseInt(commitDate, 10, 64); err == nil {
+ recency = utils.UnixToTimeAgo(unixTimestamp)
+ }
+ }
+
return &models.Branch{
Name: name,
+ Recency: recency,
Pushables: pushables,
Pullables: pullables,
UpstreamGone: gone,
diff --git a/pkg/commands/git_commands/branch_loader_test.go b/pkg/commands/git_commands/branch_loader_test.go
index f16dcf5f46ef..9e56666fee7d 100644
--- a/pkg/commands/git_commands/branch_loader_test.go
+++ b/pkg/commands/git_commands/branch_loader_test.go
@@ -2,7 +2,9 @@ package git_commands
// "*|feat/detect-purge|origin/feat/detect-purge|[ahead 1]"
import (
+ "strconv"
"testing"
+ "time"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/stretchr/testify/assert"
@@ -10,15 +12,21 @@ import (
func TestObtainBranch(t *testing.T) {
type scenario struct {
- testName string
- input []string
- expectedBranch *models.Branch
+ testName string
+ input []string
+ storeCommitDateAsRecency bool
+ expectedBranch *models.Branch
}
+ // Use a time stamp of 2 1/2 hours ago, resulting in a recency string of "2h"
+ now := time.Now().Unix()
+ timeStamp := strconv.Itoa(int(now - 2.5*60*60))
+
scenarios := []scenario{
{
- testName: "TrimHeads",
- input: []string{"", "heads/a_branch", "", "", "subject", "123"},
+ testName: "TrimHeads",
+ input: []string{"", "heads/a_branch", "", "", "subject", "123", timeStamp},
+ storeCommitDateAsRecency: false,
expectedBranch: &models.Branch{
Name: "a_branch",
Pushables: "?",
@@ -29,8 +37,9 @@ func TestObtainBranch(t *testing.T) {
},
},
{
- testName: "NoUpstream",
- input: []string{"", "a_branch", "", "", "subject", "123"},
+ testName: "NoUpstream",
+ input: []string{"", "a_branch", "", "", "subject", "123", timeStamp},
+ storeCommitDateAsRecency: false,
expectedBranch: &models.Branch{
Name: "a_branch",
Pushables: "?",
@@ -41,8 +50,9 @@ func TestObtainBranch(t *testing.T) {
},
},
{
- testName: "IsHead",
- input: []string{"*", "a_branch", "", "", "subject", "123"},
+ testName: "IsHead",
+ input: []string{"*", "a_branch", "", "", "subject", "123", timeStamp},
+ storeCommitDateAsRecency: false,
expectedBranch: &models.Branch{
Name: "a_branch",
Pushables: "?",
@@ -53,8 +63,9 @@ func TestObtainBranch(t *testing.T) {
},
},
{
- testName: "IsBehindAndAhead",
- input: []string{"", "a_branch", "a_remote/a_branch", "[behind 2, ahead 3]", "subject", "123"},
+ testName: "IsBehindAndAhead",
+ input: []string{"", "a_branch", "a_remote/a_branch", "[behind 2, ahead 3]", "subject", "123", timeStamp},
+ storeCommitDateAsRecency: false,
expectedBranch: &models.Branch{
Name: "a_branch",
Pushables: "3",
@@ -65,8 +76,9 @@ func TestObtainBranch(t *testing.T) {
},
},
{
- testName: "RemoteBranchIsGone",
- input: []string{"", "a_branch", "a_remote/a_branch", "[gone]", "subject", "123"},
+ testName: "RemoteBranchIsGone",
+ input: []string{"", "a_branch", "a_remote/a_branch", "[gone]", "subject", "123", timeStamp},
+ storeCommitDateAsRecency: false,
expectedBranch: &models.Branch{
Name: "a_branch",
UpstreamGone: true,
@@ -77,11 +89,25 @@ func TestObtainBranch(t *testing.T) {
CommitHash: "123",
},
},
+ {
+ testName: "WithCommitDateAsRecency",
+ input: []string{"", "a_branch", "", "", "subject", "123", timeStamp},
+ storeCommitDateAsRecency: true,
+ expectedBranch: &models.Branch{
+ Name: "a_branch",
+ Recency: "2h",
+ Pushables: "?",
+ Pullables: "?",
+ Head: false,
+ Subject: "subject",
+ CommitHash: "123",
+ },
+ },
}
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
- branch := obtainBranch(s.input)
+ branch := obtainBranch(s.input, s.storeCommitDateAsRecency)
assert.EqualValues(t, s.expectedBranch, branch)
})
}
diff --git a/pkg/config/app_config.go b/pkg/config/app_config.go
index 927f9f309734..b6366cd2d7e6 100644
--- a/pkg/config/app_config.go
+++ b/pkg/config/app_config.go
@@ -323,6 +323,7 @@ type AppState struct {
HideCommandLog bool
IgnoreWhitespaceInDiffView bool
DiffContextSize int
+ LocalBranchSortOrder string
RemoteBranchSortOrder string
}
@@ -332,6 +333,7 @@ func getDefaultAppState() *AppState {
RecentRepos: []string{},
StartupPopupVersion: 0,
DiffContextSize: 3,
+ LocalBranchSortOrder: "recency",
RemoteBranchSortOrder: "alphabetical",
}
}
diff --git a/pkg/gui/controllers/branches_controller.go b/pkg/gui/controllers/branches_controller.go
index 3de005a2b971..22390fc8e8be 100644
--- a/pkg/gui/controllers/branches_controller.go
+++ b/pkg/gui/controllers/branches_controller.go
@@ -97,6 +97,12 @@ func (self *BranchesController) GetKeybindings(opts types.KeybindingsOpts) []*ty
Handler: self.checkSelected(self.createTag),
Description: self.c.Tr.CreateTag,
},
+ {
+ Key: opts.GetKey(opts.Config.Branches.SortOrder),
+ Handler: self.createSortMenu,
+ Description: self.c.Tr.SortOrder,
+ OpensMenu: true,
+ },
{
Key: opts.GetKey(opts.Config.Commits.ViewResetOptions),
Handler: self.checkSelected(self.createResetMenu),
@@ -617,6 +623,18 @@ func (self *BranchesController) createTag(branch *models.Branch) error {
return self.c.Helpers().Tags.OpenCreateTagPrompt(branch.FullRefName(), func() {})
}
+func (self *BranchesController) createSortMenu() error {
+ return self.c.Helpers().Refs.CreateSortOrderMenu([]string{"recency", "alphabetical", "date"}, func(sortOrder string) error {
+ if self.c.GetAppState().LocalBranchSortOrder != sortOrder {
+ self.c.GetAppState().LocalBranchSortOrder = sortOrder
+ self.c.SaveAppStateAndLogError()
+ self.c.Contexts().Branches.SetSelectedLineIdx(0)
+ return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}})
+ }
+ return nil
+ })
+}
+
func (self *BranchesController) createResetMenu(selectedBranch *models.Branch) error {
return self.c.Helpers().Refs.CreateGitResetMenu(selectedBranch.Name)
}
diff --git a/pkg/gui/controllers/helpers/refresh_helper.go b/pkg/gui/controllers/helpers/refresh_helper.go
index dc43844e2932..15ecd8d4e251 100644
--- a/pkg/gui/controllers/helpers/refresh_helper.go
+++ b/pkg/gui/controllers/helpers/refresh_helper.go
@@ -430,7 +430,7 @@ func (self *RefreshHelper) refreshBranches(refreshWorktrees bool) {
defer self.c.Mutexes().RefreshingBranchesMutex.Unlock()
reflogCommits := self.c.Model().FilteredReflogCommits
- if self.c.Modes().Filtering.Active() {
+ if self.c.Modes().Filtering.Active() && self.c.AppState.LocalBranchSortOrder == "recency" {
// in filter mode we filter our reflog commits to just those containing the path
// however we need all the reflog entries to populate the recencies of our branches
// which allows us to order them correctly. So if we're filtering we'll just
diff --git a/pkg/gui/controllers/helpers/refs_helper.go b/pkg/gui/controllers/helpers/refs_helper.go
index b4617c8a033a..6d0d64983270 100644
--- a/pkg/gui/controllers/helpers/refs_helper.go
+++ b/pkg/gui/controllers/helpers/refs_helper.go
@@ -119,31 +119,40 @@ func (self *RefsHelper) ResetToRef(ref string, strength string, envVars []string
return nil
}
-func (self *RefsHelper) CreateSortOrderMenu(onSelected func(sortOrder string) error) error {
- type sortOrderWithKey struct {
- key types.Key
- label string
- sortKey string
- sortOrder string
+func (self *RefsHelper) CreateSortOrderMenu(sortOptionsOrder []string, onSelected func(sortOrder string) error) error {
+ type sortMenuOption struct {
+ key types.Key
+ label string
+ description string
+ sortOrder string
}
- sortKeys := []sortOrderWithKey{
- {label: self.c.Tr.SortAlphabetical, sortKey: "refname", sortOrder: "alphabetical", key: 'a'},
- {label: self.c.Tr.SortByDate, sortKey: "-committerdate", sortOrder: "date", key: 'd'},
+ availableSortOptions := map[string]sortMenuOption{
+ "recency": {label: self.c.Tr.SortByRecency, description: self.c.Tr.SortBasedOnReflog, key: 'r'},
+ "alphabetical": {label: self.c.Tr.SortAlphabetical, description: "--sort=refname", key: 'a'},
+ "date": {label: self.c.Tr.SortByDate, description: "--sort=-committerdate", key: 'd'},
+ }
+ sortOptions := make([]sortMenuOption, 0, len(sortOptionsOrder))
+ for _, key := range sortOptionsOrder {
+ sortOption, ok := availableSortOptions[key]
+ if !ok {
+ panic(fmt.Sprintf("unexpected sort order: %s", key))
+ }
+ sortOption.sortOrder = key
+ sortOptions = append(sortOptions, sortOption)
}
- menuItems := lo.Map(sortKeys, func(row sortOrderWithKey, _ int) *types.MenuItem {
+ menuItems := lo.Map(sortOptions, func(opt sortMenuOption, _ int) *types.MenuItem {
return &types.MenuItem{
LabelColumns: []string{
- row.label,
- style.FgYellow.Sprintf("--sort=%s", row.sortKey),
+ opt.label,
+ style.FgYellow.Sprint(opt.description),
},
OnPress: func() error {
- return onSelected(row.sortOrder)
+ return onSelected(opt.sortOrder)
},
- Key: row.key,
+ Key: opt.key,
}
})
-
return self.c.Menu(types.CreateMenuOptions{
Title: self.c.Tr.SortOrder,
Items: menuItems,
diff --git a/pkg/gui/controllers/remote_branches_controller.go b/pkg/gui/controllers/remote_branches_controller.go
index 6d224474631b..04afd6415a2b 100644
--- a/pkg/gui/controllers/remote_branches_controller.go
+++ b/pkg/gui/controllers/remote_branches_controller.go
@@ -128,7 +128,7 @@ func (self *RemoteBranchesController) rebase(selectedBranch *models.RemoteBranch
}
func (self *RemoteBranchesController) createSortMenu() error {
- return self.c.Helpers().Refs.CreateSortOrderMenu(func(sortOrder string) error {
+ return self.c.Helpers().Refs.CreateSortOrderMenu([]string{"alphabetical", "date"}, func(sortOrder string) error {
if self.c.GetAppState().RemoteBranchSortOrder != sortOrder {
self.c.GetAppState().RemoteBranchSortOrder = sortOrder
self.c.SaveAppStateAndLogError()
diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go
index 260976d649e4..80e89ceff0b4 100644
--- a/pkg/i18n/english.go
+++ b/pkg/i18n/english.go
@@ -558,6 +558,8 @@ type TranslationSet struct {
SortOrder string
SortAlphabetical string
SortByDate string
+ SortByRecency string
+ SortBasedOnReflog string
SortCommits string
CantChangeContextSizeError string
OpenCommitInBrowser string
@@ -1370,6 +1372,8 @@ func EnglishTranslationSet() TranslationSet {
SortOrder: "Sort order",
SortAlphabetical: "Alphabetical",
SortByDate: "Date",
+ SortByRecency: "Recency",
+ SortBasedOnReflog: "(based on reflog)",
SortCommits: "Commit sort order",
CantChangeContextSizeError: "Cannot change context while in patch building mode because we were too lazy to support it when releasing the feature. If you really want it, please let us know!",
OpenCommitInBrowser: "Open commit in browser",
From 21334fa889dea0e16f36782f36273311b975c9f3 Mon Sep 17 00:00:00 2001
From: Alex March
Date: Tue, 26 Dec 2023 15:40:57 +0900
Subject: [PATCH 008/280] Add integration test for local branch sort order
---
.../tests/branch/sort_local_branches.go | 68 +++++++++++++++++++
pkg/integration/tests/test_list.go | 1 +
2 files changed, 69 insertions(+)
create mode 100644 pkg/integration/tests/branch/sort_local_branches.go
diff --git a/pkg/integration/tests/branch/sort_local_branches.go b/pkg/integration/tests/branch/sort_local_branches.go
new file mode 100644
index 000000000000..9daf28424d56
--- /dev/null
+++ b/pkg/integration/tests/branch/sort_local_branches.go
@@ -0,0 +1,68 @@
+package branch
+
+import (
+ "github.com/jesseduffield/lazygit/pkg/config"
+ . "github.com/jesseduffield/lazygit/pkg/integration/components"
+)
+
+var SortLocalBranches = NewIntegrationTest(NewIntegrationTestArgs{
+ Description: "Sort local branches by recency, date or alphabetically",
+ ExtraCmdArgs: []string{},
+ Skip: false,
+ SetupConfig: func(config *config.AppConfig) {},
+ SetupRepo: func(shell *Shell) {
+ shell.
+ EmptyCommit("commit").
+ NewBranch("first").
+ EmptyCommitWithDate("commit", "2023-04-07 10:00:00").
+ NewBranch("second").
+ EmptyCommitWithDate("commit", "2023-04-07 12:00:00").
+ NewBranch("third").
+ EmptyCommitWithDate("commit", "2023-04-07 11:00:00").
+ Checkout("master")
+ },
+ Run: func(t *TestDriver, keys config.KeybindingConfig) {
+ // sorted by recency by default
+ t.Views().Branches().
+ Focus().
+ Lines(
+ Contains("master").IsSelected(),
+ Contains("third"),
+ Contains("second"),
+ Contains("first"),
+ ).
+ SelectNextItem() // to test that the selection jumps back to the top when sorting
+
+ t.Views().Branches().
+ Press(keys.Branches.SortOrder)
+
+ t.ExpectPopup().Menu().Title(Equals("Sort order")).
+ Select(Contains("-committerdate")).
+ Confirm()
+
+ t.Views().Branches().
+ IsFocused().
+ Lines(
+ Contains("master").IsSelected(),
+ Contains("second"),
+ Contains("third"),
+ Contains("first"),
+ )
+
+ t.Views().Branches().
+ Press(keys.Branches.SortOrder)
+
+ t.ExpectPopup().Menu().Title(Equals("Sort order")).
+ Select(Contains("refname")).
+ Confirm()
+
+ t.Views().Branches().
+ IsFocused().
+ Lines(
+ Contains("master").IsSelected(),
+ Contains("first"),
+ Contains("second"),
+ Contains("third"),
+ )
+ },
+})
diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go
index 9246aa82a80f..ba2f8d6aa743 100644
--- a/pkg/integration/tests/test_list.go
+++ b/pkg/integration/tests/test_list.go
@@ -54,6 +54,7 @@ var tests = []*components.IntegrationTest{
branch.ResetToUpstream,
branch.SetUpstream,
branch.ShowDivergenceFromUpstream,
+ branch.SortLocalBranches,
branch.SortRemoteBranches,
branch.Suggestions,
branch.UnsetUpstream,
From 4172be6bc8f585d272c482de76ee4c05a9b6ed46 Mon Sep 17 00:00:00 2001
From: Simon Whitaker
Date: Tue, 2 Jan 2024 18:22:08 +0000
Subject: [PATCH 009/280] Show a friendly error message when starting lazygit
from a non-existent cwd
Closes 3187
---
pkg/app/errors.go | 4 ++++
pkg/i18n/english.go | 2 ++
2 files changed, 6 insertions(+)
diff --git a/pkg/app/errors.go b/pkg/app/errors.go
index 2561fccb8ef9..02e9470e0c68 100644
--- a/pkg/app/errors.go
+++ b/pkg/app/errors.go
@@ -27,6 +27,10 @@ func knownError(tr *i18n.TranslationSet, err error) (string, bool) {
originalError: "fatal: not a git repository",
newError: tr.NotARepository,
},
+ {
+ originalError: "getwd: no such file or directory",
+ newError: tr.WorkingDirectoryDoesNotExist,
+ },
}
if mapping, ok := lo.Find(mappings, func(mapping errorMapping) bool {
diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go
index 80e89ceff0b4..a7f4a0de4467 100644
--- a/pkg/i18n/english.go
+++ b/pkg/i18n/english.go
@@ -335,6 +335,7 @@ type TranslationSet struct {
StashIncludeUntrackedChanges string
StashOptions string
NotARepository string
+ WorkingDirectoryDoesNotExist string
Jump string
ScrollLeftRight string
ScrollLeft string
@@ -1120,6 +1121,7 @@ func EnglishTranslationSet() TranslationSet {
InitialBranch: "Branch name? (leave empty for git's default): ",
NoRecentRepositories: "Must open lazygit in a git repository. No valid recent repositories. Exiting.",
IncorrectNotARepository: "The value of 'notARepository' is incorrect. It should be one of 'prompt', 'create', 'skip', or 'quit'.",
+ WorkingDirectoryDoesNotExist: "The current working directory does not exist",
AutoStashTitle: "Autostash?",
AutoStashPrompt: "You must stash and pop your changes to bring them across. Do this automatically? (enter/esc)",
StashPrefix: "Auto-stashing changes for ",
From 0cdca9ac2c0e1e5adc7b8c83b42129ac6914c5d9 Mon Sep 17 00:00:00 2001
From: Simon Whitaker
Date: Tue, 2 Jan 2024 18:43:39 +0000
Subject: [PATCH 010/280] Update error message
---
pkg/i18n/english.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go
index a7f4a0de4467..b9ccc95dc7bc 100644
--- a/pkg/i18n/english.go
+++ b/pkg/i18n/english.go
@@ -1121,7 +1121,6 @@ func EnglishTranslationSet() TranslationSet {
InitialBranch: "Branch name? (leave empty for git's default): ",
NoRecentRepositories: "Must open lazygit in a git repository. No valid recent repositories. Exiting.",
IncorrectNotARepository: "The value of 'notARepository' is incorrect. It should be one of 'prompt', 'create', 'skip', or 'quit'.",
- WorkingDirectoryDoesNotExist: "The current working directory does not exist",
AutoStashTitle: "Autostash?",
AutoStashPrompt: "You must stash and pop your changes to bring them across. Do this automatically? (enter/esc)",
StashPrefix: "Auto-stashing changes for ",
@@ -1155,6 +1154,7 @@ func EnglishTranslationSet() TranslationSet {
StashIncludeUntrackedChanges: "Stash all changes including untracked files",
StashOptions: "Stash options",
NotARepository: "Error: must be run inside a git repository",
+ WorkingDirectoryDoesNotExist: "Error: the current working directory does not exist",
Jump: "Jump to panel",
ScrollLeftRight: "Scroll left/right",
ScrollLeft: "Scroll left",
From 2c2436574d77b7f58a17cf99abfd210d96432482 Mon Sep 17 00:00:00 2001
From: Karim Khaleel
Date: Mon, 1 Jan 2024 16:52:40 +0300
Subject: [PATCH 011/280] Replace copy SHA with copy subject on commit 'y s'
---
pkg/commands/git_commands/commit.go | 9 +++++
.../controllers/basic_commits_controller.go | 35 +++++++++++++++----
pkg/i18n/english.go | 8 ++++-
pkg/i18n/russian.go | 5 ++-
4 files changed, 48 insertions(+), 9 deletions(-)
diff --git a/pkg/commands/git_commands/commit.go b/pkg/commands/git_commands/commit.go
index 7a218e4e1b14..812d2f6b5750 100644
--- a/pkg/commands/git_commands/commit.go
+++ b/pkg/commands/git_commands/commit.go
@@ -146,6 +146,15 @@ func (self *CommitCommands) GetCommitMessage(commitSha string) (string, error) {
return strings.TrimSpace(message), err
}
+func (self *CommitCommands) GetCommitSubject(commitSha string) (string, error) {
+ cmdArgs := NewGitCmd("log").
+ Arg("--format=%s", "--max-count=1", commitSha).
+ ToArgv()
+
+ subject, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
+ return strings.TrimSpace(subject), err
+}
+
func (self *CommitCommands) GetCommitDiff(commitSha string) (string, error) {
cmdArgs := NewGitCmd("show").Arg("--no-color", commitSha).ToArgv()
diff --git a/pkg/gui/controllers/basic_commits_controller.go b/pkg/gui/controllers/basic_commits_controller.go
index aec24838bb7c..0e98d7b4e289 100644
--- a/pkg/gui/controllers/basic_commits_controller.go
+++ b/pkg/gui/controllers/basic_commits_controller.go
@@ -105,8 +105,21 @@ func (self *BasicCommitsController) copyCommitAttribute(commit *models.Commit) e
OnPress: func() error {
return self.copyCommitSHAToClipboard(commit)
},
+ },
+ {
+ Label: self.c.Tr.CommitSubject,
+ OnPress: func() error {
+ return self.copyCommitSubjectToClipboard(commit)
+ },
Key: 's',
},
+ {
+ Label: self.c.Tr.CommitMessage,
+ OnPress: func() error {
+ return self.copyCommitMessageToClipboard(commit)
+ },
+ Key: 'm',
+ },
{
Label: self.c.Tr.CommitURL,
OnPress: func() error {
@@ -121,13 +134,6 @@ func (self *BasicCommitsController) copyCommitAttribute(commit *models.Commit) e
},
Key: 'd',
},
- {
- Label: self.c.Tr.CommitMessage,
- OnPress: func() error {
- return self.copyCommitMessageToClipboard(commit)
- },
- Key: 'm',
- },
{
Label: self.c.Tr.CommitAuthor,
OnPress: func() error {
@@ -211,6 +217,21 @@ func (self *BasicCommitsController) copyCommitMessageToClipboard(commit *models.
return nil
}
+func (self *BasicCommitsController) copyCommitSubjectToClipboard(commit *models.Commit) error {
+ message, err := self.c.Git().Commit.GetCommitSubject(commit.Sha)
+ if err != nil {
+ return self.c.Error(err)
+ }
+
+ self.c.LogAction(self.c.Tr.Actions.CopyCommitSubjectToClipboard)
+ if err := self.c.OS().CopyToClipboard(message); err != nil {
+ return self.c.Error(err)
+ }
+
+ self.c.Toast(self.c.Tr.CommitSubjectCopiedToClipboard)
+ return nil
+}
+
func (self *BasicCommitsController) openInBrowser(commit *models.Commit) error {
url, err := self.c.Helpers().Host.GetCommitURL(commit.Sha)
if err != nil {
diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go
index 80e89ceff0b4..3cddfb02f38e 100644
--- a/pkg/i18n/english.go
+++ b/pkg/i18n/english.go
@@ -462,6 +462,7 @@ type TranslationSet struct {
CommitURL string
CopyCommitMessageToClipboard string
CommitMessage string
+ CommitSubject string
CommitAuthor string
CopyCommitAttributeToClipboard string
CopyBranchNameToClipboard string
@@ -519,6 +520,7 @@ type TranslationSet struct {
CommitSHACopiedToClipboard string
CommitURLCopiedToClipboard string
CommitMessageCopiedToClipboard string
+ CommitSubjectCopiedToClipboard string
CommitAuthorCopiedToClipboard string
PatchCopiedToClipboard string
CopiedToClipboard string
@@ -707,6 +709,7 @@ type Actions struct {
MoveCommitUp string
MoveCommitDown string
CopyCommitMessageToClipboard string
+ CopyCommitSubjectToClipboard string
CopyCommitDiffToClipboard string
CopyCommitSHAToClipboard string
CopyCommitURLToClipboard string
@@ -1277,7 +1280,8 @@ func EnglishTranslationSet() TranslationSet {
CommitSha: "Commit SHA",
CommitURL: "Commit URL",
CopyCommitMessageToClipboard: "Copy commit message to clipboard",
- CommitMessage: "Commit message",
+ CommitMessage: "Full commit message",
+ CommitSubject: "Commit subject",
CommitAuthor: "Commit author",
CopyCommitAttributeToClipboard: "Copy commit attribute",
CopyBranchNameToClipboard: "Copy branch name to clipboard",
@@ -1334,6 +1338,7 @@ func EnglishTranslationSet() TranslationSet {
CommitSHACopiedToClipboard: "Commit SHA copied to clipboard",
CommitURLCopiedToClipboard: "Commit URL copied to clipboard",
CommitMessageCopiedToClipboard: "Commit message copied to clipboard",
+ CommitSubjectCopiedToClipboard: "Commit subject copied to clipboard",
CommitAuthorCopiedToClipboard: "Commit author copied to clipboard",
PatchCopiedToClipboard: "Patch copied to clipboard",
CopiedToClipboard: "Copied to clipboard",
@@ -1478,6 +1483,7 @@ func EnglishTranslationSet() TranslationSet {
CreateLightweightTag: "Create lightweight tag",
CreateAnnotatedTag: "Create annotated tag",
CopyCommitMessageToClipboard: "Copy commit message to clipboard",
+ CopyCommitSubjectToClipboard: "Copy commit subject to clipboard",
CopyCommitDiffToClipboard: "Copy commit diff to clipboard",
CopyCommitSHAToClipboard: "Copy commit SHA to clipboard",
CopyCommitURLToClipboard: "Copy commit URL to clipboard",
diff --git a/pkg/i18n/russian.go b/pkg/i18n/russian.go
index e4b67a77ecf6..0e63841b98c2 100644
--- a/pkg/i18n/russian.go
+++ b/pkg/i18n/russian.go
@@ -435,7 +435,8 @@ func RussianTranslationSet() TranslationSet {
CommitSha: "SHA коммита",
CommitURL: "URL коммита",
CopyCommitMessageToClipboard: "Скопировать сообщение коммита в буфер обмена",
- CommitMessage: "Сообщение коммита",
+ CommitMessage: "Полное сообщение коммита",
+ CommitSubject: "Тема коммита",
CommitAuthor: "Автор коммита",
CopyCommitAttributeToClipboard: "Скопировать атрибут коммита",
CopyBranchNameToClipboard: "Скопировать название ветки в буфер обмена",
@@ -492,6 +493,7 @@ func RussianTranslationSet() TranslationSet {
CommitSHACopiedToClipboard: "SHA коммита скопировано в буфер обмена",
CommitURLCopiedToClipboard: "URL коммита скопирован в буфер обмена",
CommitMessageCopiedToClipboard: "Сообщение коммита скопировано в буфер обмена",
+ CommitSubjectCopiedToClipboard: "Тема коммита скопирована в буфер обмена",
CommitAuthorCopiedToClipboard: "Автор коммита скопирован в буфер обмена",
PatchCopiedToClipboard: "Патч скопирован в буфер обмена",
CopiedToClipboard: "Скопировано в буфер обмена",
@@ -586,6 +588,7 @@ func RussianTranslationSet() TranslationSet {
CreateLightweightTag: "Создать легковесный тег",
CreateAnnotatedTag: "Создать аннотированный тег",
CopyCommitMessageToClipboard: "Скопировать сообщение коммита в буфер обмена",
+ CopyCommitSubjectToClipboard: "Скопировать тему коммита в буфер обмена",
CopyCommitDiffToClipboard: "Скопировать сравнения коммита в буфер обмена",
CopyCommitSHAToClipboard: "Скопировать SHA коммита в буфер обмена",
CopyCommitURLToClipboard: "Скопировать URL коммита в буфер обмена",
From 93cae68e94cb49a9519501a37f5d79e8d72581d6 Mon Sep 17 00:00:00 2001
From: README-bot
Date: Tue, 9 Jan 2024 09:55:08 +0000
Subject: [PATCH 012/280] Updated README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index dd811a752994..492d2307574e 100644
--- a/README.md
+++ b/README.md
@@ -37,7 +37,7 @@ A simple terminal UI for git commands
-
+
## Elevator Pitch
From 2b97f0fb43481f04095ea2be465dbaba16b5264e Mon Sep 17 00:00:00 2001
From: Stefan Haller
Date: Fri, 22 Dec 2023 15:28:53 +0100
Subject: [PATCH 013/280] Add test demonstrating the problem
When there's a branch with the same name as the tag, the branch gets checked out
instead of the tag.
---
...ckout_when_branch_with_same_name_exists.go | 38 +++++++++++++++++++
pkg/integration/tests/test_list.go | 1 +
2 files changed, 39 insertions(+)
create mode 100644 pkg/integration/tests/tag/checkout_when_branch_with_same_name_exists.go
diff --git a/pkg/integration/tests/tag/checkout_when_branch_with_same_name_exists.go b/pkg/integration/tests/tag/checkout_when_branch_with_same_name_exists.go
new file mode 100644
index 000000000000..2f291e29fc91
--- /dev/null
+++ b/pkg/integration/tests/tag/checkout_when_branch_with_same_name_exists.go
@@ -0,0 +1,38 @@
+package tag
+
+import (
+ "github.com/jesseduffield/lazygit/pkg/config"
+ . "github.com/jesseduffield/lazygit/pkg/integration/components"
+)
+
+var CheckoutWhenBranchWithSameNameExists = NewIntegrationTest(NewIntegrationTestArgs{
+ Description: "Checkout a tag when there's a branch with the same name",
+ ExtraCmdArgs: []string{},
+ Skip: false,
+ SetupConfig: func(config *config.AppConfig) {},
+ SetupRepo: func(shell *Shell) {
+ shell.EmptyCommit("one")
+ shell.NewBranch("tag")
+ shell.Checkout("master")
+ shell.EmptyCommit("two")
+ shell.CreateLightweightTag("tag", "HEAD")
+ },
+ Run: func(t *TestDriver, keys config.KeybindingConfig) {
+ t.Views().Tags().
+ Focus().
+ Lines(
+ Contains("tag").IsSelected(),
+ ).
+ PressPrimaryAction() // checkout tag
+
+ t.Views().Branches().IsFocused().Lines(
+ /* EXPECTED:
+ Contains("HEAD detached at tag").IsSelected(),
+ Contains("master"),
+ Contains("tag"),
+ ACTUAL: */
+ Contains("* tag").DoesNotContain("HEAD detached").IsSelected(),
+ Contains("master"),
+ )
+ },
+})
diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go
index ba2f8d6aa743..c61a7c2e6d7a 100644
--- a/pkg/integration/tests/test_list.go
+++ b/pkg/integration/tests/test_list.go
@@ -245,6 +245,7 @@ var tests = []*components.IntegrationTest{
sync.PushWithCredentialPrompt,
sync.RenameBranchAndPull,
tag.Checkout,
+ tag.CheckoutWhenBranchWithSameNameExists,
tag.CreateWhileCommitting,
tag.CrudAnnotated,
tag.CrudLightweight,
From f244ec8251d77e41d02507811f49c388aa67a042 Mon Sep 17 00:00:00 2001
From: Stefan Haller
Date: Fri, 22 Dec 2023 15:30:54 +0100
Subject: [PATCH 014/280] Fix checking out a tag when a branch with the same
name exists
---
pkg/gui/controllers/tags_controller.go | 2 +-
.../tests/tag/checkout_when_branch_with_same_name_exists.go | 4 ----
2 files changed, 1 insertion(+), 5 deletions(-)
diff --git a/pkg/gui/controllers/tags_controller.go b/pkg/gui/controllers/tags_controller.go
index 224ad83a4be7..dcbef4d2cfe8 100644
--- a/pkg/gui/controllers/tags_controller.go
+++ b/pkg/gui/controllers/tags_controller.go
@@ -83,7 +83,7 @@ func (self *TagsController) GetOnRenderToMain() func() error {
func (self *TagsController) checkout(tag *models.Tag) error {
self.c.LogAction(self.c.Tr.Actions.CheckoutTag)
- if err := self.c.Helpers().Refs.CheckoutRef(tag.Name, types.CheckoutRefOptions{}); err != nil {
+ if err := self.c.Helpers().Refs.CheckoutRef(tag.FullRefName(), types.CheckoutRefOptions{}); err != nil {
return err
}
return self.c.PushContext(self.c.Contexts().Branches)
diff --git a/pkg/integration/tests/tag/checkout_when_branch_with_same_name_exists.go b/pkg/integration/tests/tag/checkout_when_branch_with_same_name_exists.go
index 2f291e29fc91..73e597f8f410 100644
--- a/pkg/integration/tests/tag/checkout_when_branch_with_same_name_exists.go
+++ b/pkg/integration/tests/tag/checkout_when_branch_with_same_name_exists.go
@@ -26,13 +26,9 @@ var CheckoutWhenBranchWithSameNameExists = NewIntegrationTest(NewIntegrationTest
PressPrimaryAction() // checkout tag
t.Views().Branches().IsFocused().Lines(
- /* EXPECTED:
Contains("HEAD detached at tag").IsSelected(),
Contains("master"),
Contains("tag"),
- ACTUAL: */
- Contains("* tag").DoesNotContain("HEAD detached").IsSelected(),
- Contains("master"),
)
},
})
From c1cb95db6f301eb3418299631f1a79511bdb85de Mon Sep 17 00:00:00 2001
From: Stefan Haller
Date: Sun, 5 Mar 2023 14:07:27 +0100
Subject: [PATCH 015/280] Remove unused function
---
pkg/commands/git_commands/working_tree.go | 4 ----
1 file changed, 4 deletions(-)
diff --git a/pkg/commands/git_commands/working_tree.go b/pkg/commands/git_commands/working_tree.go
index 7921cd26f8d3..9630e2870eec 100644
--- a/pkg/commands/git_commands/working_tree.go
+++ b/pkg/commands/git_commands/working_tree.go
@@ -31,10 +31,6 @@ func (self *WorkingTreeCommands) OpenMergeToolCmdObj() oscommands.ICmdObj {
return self.cmd.New(NewGitCmd("mergetool").ToArgv())
}
-func (self *WorkingTreeCommands) OpenMergeTool() error {
- return self.OpenMergeToolCmdObj().Run()
-}
-
// StageFile stages a file
func (self *WorkingTreeCommands) StageFile(path string) error {
return self.StageFiles([]string{path})
From 517e0f824849e28cf24e3125418761c86eafb0cd Mon Sep 17 00:00:00 2001
From: Stefan Haller
Date: Sun, 5 Mar 2023 14:15:31 +0100
Subject: [PATCH 016/280] Add command to open git difftool
---
pkg/commands/git_commands/diff.go | 38 +++++++++++++++++++
pkg/config/user_config.go | 2 +
.../controllers/basic_commits_controller.go | 21 ++++++++++
.../controllers/commits_files_controller.go | 22 +++++++++++
pkg/gui/controllers/files_controller.go | 26 +++++++++++++
pkg/i18n/english.go | 4 ++
6 files changed, 113 insertions(+)
diff --git a/pkg/commands/git_commands/diff.go b/pkg/commands/git_commands/diff.go
index 1e5f9824402a..41e71e94116d 100644
--- a/pkg/commands/git_commands/diff.go
+++ b/pkg/commands/git_commands/diff.go
@@ -40,3 +40,41 @@ func (self *DiffCommands) GetAllDiff(staged bool) (string, error) {
ToArgv(),
).RunWithOutput()
}
+
+type DiffToolCmdOptions struct {
+ // The path to show a diff for. Pass "." for the entire repo.
+ Filepath string
+
+ // The commit against which to show the diff. Leave empty to show a diff of
+ // the working copy.
+ FromCommit string
+
+ // The commit to diff against FromCommit. Leave empty to diff the working
+ // copy against FromCommit. Leave both FromCommit and ToCommit empty to show
+ // the diff of the unstaged working copy changes against the index if Staged
+ // is false, or the staged changes against HEAD if Staged is true.
+ ToCommit string
+
+ // Whether to reverse the left and right sides of the diff.
+ Reverse bool
+
+ // Whether the given Filepath is a directory. We'll pass --dir-diff to
+ // git-difftool in that case.
+ IsDirectory bool
+
+ // Whether to show the staged or the unstaged changes. Must be false if both
+ // FromCommit and ToCommit are non-empty.
+ Staged bool
+}
+
+func (self *DiffCommands) OpenDiffToolCmdObj(opts DiffToolCmdOptions) oscommands.ICmdObj {
+ return self.cmd.New(NewGitCmd("difftool").
+ Arg("--no-prompt").
+ ArgIf(opts.IsDirectory, "--dir-diff").
+ ArgIf(opts.Staged, "--cached").
+ ArgIf(opts.FromCommit != "", opts.FromCommit).
+ ArgIf(opts.ToCommit != "", opts.ToCommit).
+ ArgIf(opts.Reverse, "-R").
+ Arg("--", opts.Filepath).
+ ToArgv())
+}
diff --git a/pkg/config/user_config.go b/pkg/config/user_config.go
index 749b41822404..2392d49cf6e3 100644
--- a/pkg/config/user_config.go
+++ b/pkg/config/user_config.go
@@ -347,6 +347,7 @@ type KeybindingUniversalConfig struct {
ToggleWhitespaceInDiffView string `yaml:"toggleWhitespaceInDiffView"`
IncreaseContextInDiffView string `yaml:"increaseContextInDiffView"`
DecreaseContextInDiffView string `yaml:"decreaseContextInDiffView"`
+ OpenDiffTool string `yaml:"openDiffTool"`
}
type KeybindingStatusConfig struct {
@@ -743,6 +744,7 @@ func GetDefaultConfig() *UserConfig {
ToggleWhitespaceInDiffView: "",
IncreaseContextInDiffView: "}",
DecreaseContextInDiffView: "{",
+ OpenDiffTool: "",
},
Status: KeybindingStatusConfig{
CheckForUpdate: "u",
diff --git a/pkg/gui/controllers/basic_commits_controller.go b/pkg/gui/controllers/basic_commits_controller.go
index 0e98d7b4e289..5513494667a5 100644
--- a/pkg/gui/controllers/basic_commits_controller.go
+++ b/pkg/gui/controllers/basic_commits_controller.go
@@ -3,6 +3,7 @@ package controllers
import (
"fmt"
+ "github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
@@ -76,6 +77,11 @@ func (self *BasicCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
Handler: self.c.Helpers().CherryPick.Reset,
Description: self.c.Tr.ResetCherryPick,
},
+ {
+ Key: opts.GetKey(opts.Config.Universal.OpenDiffTool),
+ Handler: self.checkSelected(self.openDiffTool),
+ Description: self.c.Tr.OpenDiffTool,
+ },
}
return bindings
@@ -272,3 +278,18 @@ func (self *BasicCommitsController) copy(commit *models.Commit) error {
func (self *BasicCommitsController) copyRange(*models.Commit) error {
return self.c.Helpers().CherryPick.CopyRange(self.context.GetSelectedLineIdx(), self.context.GetCommits(), self.context)
}
+
+func (self *BasicCommitsController) openDiffTool(commit *models.Commit) error {
+ to := commit.RefName()
+ from, reverse := self.c.Modes().Diffing.GetFromAndReverseArgsForDiff(commit.ParentRefName())
+ _, err := self.c.RunSubprocess(self.c.Git().Diff.OpenDiffToolCmdObj(
+ git_commands.DiffToolCmdOptions{
+ Filepath: ".",
+ FromCommit: from,
+ ToCommit: to,
+ Reverse: reverse,
+ IsDirectory: true,
+ Staged: false,
+ }))
+ return err
+}
diff --git a/pkg/gui/controllers/commits_files_controller.go b/pkg/gui/controllers/commits_files_controller.go
index 3730a096591d..5b3097363d91 100644
--- a/pkg/gui/controllers/commits_files_controller.go
+++ b/pkg/gui/controllers/commits_files_controller.go
@@ -2,6 +2,7 @@ package controllers
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/commands/patch"
"github.com/jesseduffield/lazygit/pkg/gui/context"
@@ -47,6 +48,11 @@ func (self *CommitFilesController) GetKeybindings(opts types.KeybindingsOpts) []
Handler: self.checkSelected(self.edit),
Description: self.c.Tr.EditFile,
},
+ {
+ Key: opts.GetKey(opts.Config.Universal.OpenDiffTool),
+ Handler: self.checkSelected(self.openDiffTool),
+ Description: self.c.Tr.OpenDiffTool,
+ },
{
Key: opts.GetKey(opts.Config.Universal.Select),
Handler: self.checkSelected(self.toggleForPatch),
@@ -201,6 +207,22 @@ func (self *CommitFilesController) edit(node *filetree.CommitFileNode) error {
return self.c.Helpers().Files.EditFile(node.GetPath())
}
+func (self *CommitFilesController) openDiffTool(node *filetree.CommitFileNode) error {
+ ref := self.context().GetRef()
+ to := ref.RefName()
+ from, reverse := self.c.Modes().Diffing.GetFromAndReverseArgsForDiff(ref.ParentRefName())
+ _, err := self.c.RunSubprocess(self.c.Git().Diff.OpenDiffToolCmdObj(
+ git_commands.DiffToolCmdOptions{
+ Filepath: node.GetPath(),
+ FromCommit: from,
+ ToCommit: to,
+ Reverse: reverse,
+ IsDirectory: !node.IsFile(),
+ Staged: false,
+ }))
+ return err
+}
+
func (self *CommitFilesController) toggleForPatch(node *filetree.CommitFileNode) error {
toggle := func() error {
return self.c.WithWaitingStatus(self.c.Tr.UpdatingPatch, func(gocui.Task) error {
diff --git a/pkg/gui/controllers/files_controller.go b/pkg/gui/controllers/files_controller.go
index 733487d5ad59..0db509ca3b0f 100644
--- a/pkg/gui/controllers/files_controller.go
+++ b/pkg/gui/controllers/files_controller.go
@@ -4,6 +4,7 @@ import (
"strings"
"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/filetree"
@@ -122,6 +123,11 @@ func (self *FilesController) GetKeybindings(opts types.KeybindingsOpts) []*types
Handler: self.toggleTreeView,
Description: self.c.Tr.ToggleTreeView,
},
+ {
+ Key: opts.GetKey(opts.Config.Universal.OpenDiffTool),
+ Handler: self.checkSelectedFileNode(self.openDiffTool),
+ Description: self.c.Tr.OpenDiffTool,
+ },
{
Key: opts.GetKey(opts.Config.Files.OpenMergeTool),
Handler: self.c.Helpers().WorkingTree.OpenMergeTool,
@@ -684,6 +690,26 @@ func (self *FilesController) Open() error {
return self.c.Helpers().Files.OpenFile(node.GetPath())
}
+func (self *FilesController) openDiffTool(node *filetree.FileNode) error {
+ fromCommit := ""
+ reverse := false
+ if self.c.Modes().Diffing.Active() {
+ fromCommit = self.c.Modes().Diffing.Ref
+ reverse = self.c.Modes().Diffing.Reverse
+ }
+ return self.c.RunSubprocessAndRefresh(
+ self.c.Git().Diff.OpenDiffToolCmdObj(
+ git_commands.DiffToolCmdOptions{
+ Filepath: node.Path,
+ FromCommit: fromCommit,
+ ToCommit: "",
+ Reverse: reverse,
+ IsDirectory: !node.IsFile(),
+ Staged: !node.GetHasUnstagedChanges(),
+ }),
+ )
+}
+
func (self *FilesController) switchToMerge() error {
file := self.getSelectedFile()
if file == nil {
diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go
index 06c66d0a62f6..34df4aba1872 100644
--- a/pkg/i18n/english.go
+++ b/pkg/i18n/english.go
@@ -46,6 +46,7 @@ type TranslationSet struct {
ToggleStaged string
ToggleStagedAll string
ToggleTreeView string
+ OpenDiffTool string
OpenMergeTool string
Refresh string
Push string
@@ -782,6 +783,7 @@ type Actions struct {
Undo string
Redo string
CopyPullRequestURL string
+ OpenDiffTool string
OpenMergeTool string
OpenCommitInBrowser string
OpenPullRequest string
@@ -862,6 +864,7 @@ func EnglishTranslationSet() TranslationSet {
ToggleStaged: "Toggle staged",
ToggleStagedAll: "Stage/unstage all",
ToggleTreeView: "Toggle file tree view",
+ OpenDiffTool: "Open external diff tool (git difftool)",
OpenMergeTool: "Open external merge tool (git mergetool)",
Refresh: "Refresh",
Push: "Push",
@@ -1558,6 +1561,7 @@ func EnglishTranslationSet() TranslationSet {
Undo: "Undo",
Redo: "Redo",
CopyPullRequestURL: "Copy pull request URL",
+ OpenDiffTool: "Open diff tool",
OpenMergeTool: "Open merge tool",
OpenCommitInBrowser: "Open commit in browser",
OpenPullRequest: "Open pull request in browser",
From a6174271aa6830577fae9c35bb09608c64bd550b Mon Sep 17 00:00:00 2001
From: Stefan Haller
Date: Sun, 10 Dec 2023 10:37:16 +0100
Subject: [PATCH 017/280] Update cheat sheets and json schema
---
docs/keybindings/Keybindings_en.md | 5 +++++
docs/keybindings/Keybindings_ja.md | 5 +++++
docs/keybindings/Keybindings_ko.md | 5 +++++
docs/keybindings/Keybindings_nl.md | 5 +++++
docs/keybindings/Keybindings_pl.md | 5 +++++
docs/keybindings/Keybindings_ru.md | 5 +++++
docs/keybindings/Keybindings_zh-CN.md | 5 +++++
docs/keybindings/Keybindings_zh-TW.md | 5 +++++
schema/config.json | 4 ++++
9 files changed, 44 insertions(+)
diff --git a/docs/keybindings/Keybindings_en.md b/docs/keybindings/Keybindings_en.md
index b5dc938980f6..f7aaa46c8173 100644
--- a/docs/keybindings/Keybindings_en.md
+++ b/docs/keybindings/Keybindings_en.md
@@ -52,6 +52,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
d: Discard this commit's changes to this file
o: Open file
e: Edit file
+ <c-t>: Open external diff tool (git difftool)
<space>: Toggle file included in patch
a: Toggle all files included in patch
<enter>: Enter file to add selected lines to the patch (or toggle directory collapsed)
@@ -98,6 +99,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
g: View reset options
c: Copy commit (cherry-pick)
C: Copy commit range (cherry-pick)
+ <c-t>: Open external diff tool (git difftool)
<enter>: View selected item's files
/: Search the current view by text
@@ -132,6 +134,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
g: View upstream reset options
D: View reset options
`: Toggle file tree view
+ <c-t>: Open external diff tool (git difftool)
M: Open external merge tool (git mergetool)
f: Fetch
/: Search the current view by text
@@ -245,6 +248,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
c: Copy commit (cherry-pick)
C: Copy commit range (cherry-pick)
<c-r>: Reset cherry-picked (copied) commits selection
+ <c-t>: Open external diff tool (git difftool)
<enter>: View commits
/: Filter the current view by text
@@ -312,6 +316,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
c: Copy commit (cherry-pick)
C: Copy commit range (cherry-pick)
<c-r>: Reset cherry-picked (copied) commits selection
+ <c-t>: Open external diff tool (git difftool)
<enter>: View selected item's files
/: Search the current view by text
diff --git a/docs/keybindings/Keybindings_ja.md b/docs/keybindings/Keybindings_ja.md
index 3b7ef69918cc..a5a54b872ca4 100644
--- a/docs/keybindings/Keybindings_ja.md
+++ b/docs/keybindings/Keybindings_ja.md
@@ -70,6 +70,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
c: コミットをコピー (cherry-pick)
C: コミットを範囲コピー (cherry-pick)
<c-r>: Reset cherry-picked (copied) commits selection
+ <c-t>: Open external diff tool (git difftool)
<enter>: View selected item's files
/: 検索を開始
@@ -117,6 +118,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
g: View reset options
c: コミットをコピー (cherry-pick)
C: コミットを範囲コピー (cherry-pick)
+ <c-t>: Open external diff tool (git difftool)
<enter>: View selected item's files
/: 検索を開始
@@ -129,6 +131,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
d: Discard this commit's changes to this file
o: ファイルを開く
e: ファイルを編集
+ <c-t>: Open external diff tool (git difftool)
<space>: Toggle file included in patch
a: Toggle all files included in patch
<enter>: Enter file to add selected lines to the patch (or toggle directory collapsed)
@@ -204,6 +207,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
g: View upstream reset options
D: View reset options
`: ファイルツリーの表示を切り替え
+ <c-t>: Open external diff tool (git difftool)
M: Git mergetoolを開く
f: Fetch
/: 検索を開始
@@ -344,6 +348,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
c: コミットをコピー (cherry-pick)
C: コミットを範囲コピー (cherry-pick)
<c-r>: Reset cherry-picked (copied) commits selection
+ <c-t>: Open external diff tool (git difftool)
<enter>: コミットを閲覧
/: Filter the current view by text
diff --git a/docs/keybindings/Keybindings_ko.md b/docs/keybindings/Keybindings_ko.md
index 02b5db76e550..fc7291c4ba82 100644
--- a/docs/keybindings/Keybindings_ko.md
+++ b/docs/keybindings/Keybindings_ko.md
@@ -57,6 +57,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
c: 커밋을 복사 (cherry-pick)
C: 커밋을 범위로 복사 (cherry-pick)
<c-r>: Reset cherry-picked (copied) commits selection
+ <c-t>: Open external diff tool (git difftool)
<enter>: 커밋 보기
/: Filter the current view by text
@@ -87,6 +88,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
c: 커밋을 복사 (cherry-pick)
C: 커밋을 범위로 복사 (cherry-pick)
<c-r>: Reset cherry-picked (copied) commits selection
+ <c-t>: Open external diff tool (git difftool)
<enter>: View selected item's files
/: 검색 시작
@@ -281,6 +283,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
g: View reset options
c: 커밋을 복사 (cherry-pick)
C: 커밋을 범위로 복사 (cherry-pick)
+ <c-t>: Open external diff tool (git difftool)
<enter>: View selected item's files
/: 검색 시작
@@ -293,6 +296,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
d: Discard this commit's changes to this file
o: 파일 닫기
e: 파일 편집
+ <c-t>: Open external diff tool (git difftool)
<space>: Toggle file included in patch
a: Toggle all files included in patch
<enter>: Enter file to add selected lines to the patch (or toggle directory collapsed)
@@ -343,6 +347,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
g: View upstream reset options
D: View reset options
`: 파일 트리뷰로 전환
+ <c-t>: Open external diff tool (git difftool)
M: Git mergetool를 열기
f: Fetch
/: 검색 시작
diff --git a/docs/keybindings/Keybindings_nl.md b/docs/keybindings/Keybindings_nl.md
index e1bddf9298ec..c2d81a06d479 100644
--- a/docs/keybindings/Keybindings_nl.md
+++ b/docs/keybindings/Keybindings_nl.md
@@ -67,6 +67,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
g: Bekijk upstream reset opties
D: Bekijk reset opties
`: Toggle bestandsboom weergave
+ <c-t>: Open external diff tool (git difftool)
M: Open external merge tool (git mergetool)
f: Fetch
/: Start met zoeken
@@ -120,6 +121,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
d: Uitsluit deze commit zijn veranderingen aan dit bestand
o: Open bestand
e: Verander bestand
+ <c-t>: Open external diff tool (git difftool)
<space>: Toggle bestand inbegrepen in patch
a: Toggle all files included in patch
<enter>: Enter bestand om geselecteerde regels toe te voegen aan de patch
@@ -159,6 +161,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
g: Bekijk reset opties
c: Kopieer commit (cherry-pick)
C: Kopieer commit reeks (cherry-pick)
+ <c-t>: Open external diff tool (git difftool)
<enter>: Bekijk gecommite bestanden
/: Start met zoeken
@@ -223,6 +226,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
c: Kopieer commit (cherry-pick)
C: Kopieer commit reeks (cherry-pick)
<c-r>: Reset cherry-picked (gekopieerde) commits selectie
+ <c-t>: Open external diff tool (git difftool)
<enter>: Bekijk commits
/: Filter the current view by text
@@ -312,6 +316,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
c: Kopieer commit (cherry-pick)
C: Kopieer commit reeks (cherry-pick)
<c-r>: Reset cherry-picked (gekopieerde) commits selectie
+ <c-t>: Open external diff tool (git difftool)
<enter>: Bekijk gecommite bestanden
/: Start met zoeken
diff --git a/docs/keybindings/Keybindings_pl.md b/docs/keybindings/Keybindings_pl.md
index f9eb05e09826..2136a5cdf696 100644
--- a/docs/keybindings/Keybindings_pl.md
+++ b/docs/keybindings/Keybindings_pl.md
@@ -83,6 +83,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
g: Wyświetl opcje resetu
c: Kopiuj commit (przebieranie)
C: Kopiuj zakres commitów (przebieranie)
+ <c-t>: Open external diff tool (git difftool)
<enter>: Przeglądaj pliki commita
/: Search the current view by text
@@ -167,6 +168,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
g: View upstream reset options
D: Wyświetl opcje resetu
`: Toggle file tree view
+ <c-t>: Open external diff tool (git difftool)
M: Open external merge tool (git mergetool)
f: Pobierz
/: Search the current view by text
@@ -180,6 +182,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
d: Porzuć zmiany commita dla tego pliku
o: Otwórz plik
e: Edytuj plik
+ <c-t>: Open external diff tool (git difftool)
<space>: Toggle file included in patch
a: Toggle all files included in patch
<enter>: Enter file to add selected lines to the patch (or toggle directory collapsed)
@@ -222,6 +225,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
c: Kopiuj commit (przebieranie)
C: Kopiuj zakres commitów (przebieranie)
<c-r>: Reset cherry-picked (copied) commits selection
+ <c-t>: Open external diff tool (git difftool)
<enter>: View commits
/: Filter the current view by text
@@ -305,6 +309,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
c: Kopiuj commit (przebieranie)
C: Kopiuj zakres commitów (przebieranie)
<c-r>: Reset cherry-picked (copied) commits selection
+ <c-t>: Open external diff tool (git difftool)
<enter>: Przeglądaj pliki commita
/: Search the current view by text
diff --git a/docs/keybindings/Keybindings_ru.md b/docs/keybindings/Keybindings_ru.md
index f0a1fd4e2d74..ab82515ad5a5 100644
--- a/docs/keybindings/Keybindings_ru.md
+++ b/docs/keybindings/Keybindings_ru.md
@@ -129,6 +129,7 @@ _Связки клавиш_
c: Скопировать отобранные коммит (cherry-pick)
C: Скопировать несколько отобранных коммитов (cherry-pick)
<c-r>: Сбросить отобранную (скопированную | cherry-picked) выборку коммитов
+ <c-t>: Open external diff tool (git difftool)
<enter>: Просмотреть коммиты
/: Filter the current view by text
@@ -165,6 +166,7 @@ _Связки клавиш_
g: Просмотреть параметры сброса
c: Скопировать отобранные коммит (cherry-pick)
C: Скопировать несколько отобранных коммитов (cherry-pick)
+ <c-t>: Open external diff tool (git difftool)
<enter>: Просмотреть файлы выбранного элемента
/: Найти
@@ -223,6 +225,7 @@ _Связки клавиш_
c: Скопировать отобранные коммит (cherry-pick)
C: Скопировать несколько отобранных коммитов (cherry-pick)
<c-r>: Сбросить отобранную (скопированную | cherry-picked) выборку коммитов
+ <c-t>: Open external diff tool (git difftool)
<enter>: Просмотреть файлы выбранного элемента
/: Найти
@@ -257,6 +260,7 @@ _Связки клавиш_
d: Отменить изменения коммита в этом файле
o: Открыть файл
e: Редактировать файл
+ <c-t>: Open external diff tool (git difftool)
<space>: Переключить файлы включённые в патч
a: Переключить все файлы, включённые в патч
<enter>: Введите файл, чтобы добавить выбранные строки в патч (или свернуть каталог переключения)
@@ -337,6 +341,7 @@ _Связки клавиш_
g: Просмотреть параметры сброса upstream-ветки
D: Просмотреть параметры сброса
`: Переключить вид дерева файлов
+ <c-t>: Open external diff tool (git difftool)
M: Открыть внешний инструмент слияния (git mergetool)
f: Получить изменения
/: Найти
diff --git a/docs/keybindings/Keybindings_zh-CN.md b/docs/keybindings/Keybindings_zh-CN.md
index 9b015285149e..49bce5ece13a 100644
--- a/docs/keybindings/Keybindings_zh-CN.md
+++ b/docs/keybindings/Keybindings_zh-CN.md
@@ -57,6 +57,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
c: 复制提交(拣选)
C: 复制提交范围(拣选)
<c-r>: 重置已拣选(复制)的提交
+ <c-t>: Open external diff tool (git difftool)
<enter>: 查看提交
/: Filter the current view by text
@@ -111,6 +112,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
c: 复制提交(拣选)
C: 复制提交范围(拣选)
<c-r>: 重置已拣选(复制)的提交
+ <c-t>: Open external diff tool (git difftool)
<enter>: 查看提交的文件
/: 开始搜索
@@ -162,6 +164,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
g: 查看重置选项
c: 复制提交(拣选)
C: 复制提交范围(拣选)
+ <c-t>: Open external diff tool (git difftool)
<enter>: 查看提交的文件
/: 开始搜索
@@ -174,6 +177,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
d: 放弃对此文件的提交更改
o: 打开文件
e: 编辑文件
+ <c-t>: Open external diff tool (git difftool)
<space>: 补丁中包含的切换文件
a: Toggle all files included in patch
<enter>: 输入文件以将所选行添加到补丁中(或切换目录折叠)
@@ -211,6 +215,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
g: 查看上游重置选项
D: 查看重置选项
`: 切换文件树视图
+ <c-t>: Open external diff tool (git difftool)
M: 打开外部合并工具 (git mergetool)
f: 抓取
/: 开始搜索
diff --git a/docs/keybindings/Keybindings_zh-TW.md b/docs/keybindings/Keybindings_zh-TW.md
index 8665255814b5..201f7955500e 100644
--- a/docs/keybindings/Keybindings_zh-TW.md
+++ b/docs/keybindings/Keybindings_zh-TW.md
@@ -57,6 +57,7 @@ _說明:`` 表示 Ctrl+B、`` 表示 Alt+B,`B`表示 Shift+B_
c: 複製提交 (揀選)
C: 複製提交範圍 (揀選)
<c-r>: 重設選定的揀選 (複製) 提交
+ <c-t>: Open external diff tool (git difftool)
<enter>: 檢視提交
/: Filter the current view by text
@@ -154,6 +155,7 @@ _說明:`` 表示 Ctrl+B、`` 表示 Alt+B,`B`表示 Shift+B_
c: 複製提交 (揀選)
C: 複製提交範圍 (揀選)
<c-r>: 重設選定的揀選 (複製) 提交
+ <c-t>: Open external diff tool (git difftool)
<enter>: 檢視所選項目的檔案
/: 開始搜尋
@@ -205,6 +207,7 @@ _說明:`` 表示 Ctrl+B、`` 表示 Alt+B,`B`表示 Shift+B_
g: 檢視重設選項
c: 複製提交 (揀選)
C: 複製提交範圍 (揀選)
+ <c-t>: Open external diff tool (git difftool)
<enter>: 檢視所選項目的檔案
/: 開始搜尋
@@ -224,6 +227,7 @@ _說明:`` 表示 Ctrl+B、`` 表示 Alt+B,`B`表示 Shift+B_
d: 捨棄此提交對此檔案的更改
o: 開啟檔案
e: 編輯檔案
+ <c-t>: Open external diff tool (git difftool)
<space>: 切換檔案是否包含在補丁中
a: 切換所有檔案是否包含在補丁中
<enter>: 輸入檔案以將選定的行添加至補丁(或切換目錄折疊)
@@ -306,6 +310,7 @@ _說明:`` 表示 Ctrl+B、`` 表示 Alt+B,`B`表示 Shift+B_
g: 檢視上游重設選項
D: 檢視重設選項
`: 切換檔案樹狀視圖
+ <c-t>: Open external diff tool (git difftool)
M: 開啟外部合併工具 (git mergetool)
f: 擷取
/: 開始搜尋
diff --git a/schema/config.json b/schema/config.json
index 54f6d69612b2..45976cbb9203 100644
--- a/schema/config.json
+++ b/schema/config.json
@@ -864,6 +864,10 @@
"decreaseContextInDiffView": {
"type": "string",
"default": "{"
+ },
+ "openDiffTool": {
+ "type": "string",
+ "default": "\u003cc-t\u003e"
}
},
"additionalProperties": false,
From b6a9220343bb5c2ac3b10663c82b4351f819d6d8 Mon Sep 17 00:00:00 2001
From: README-bot
Date: Tue, 9 Jan 2024 13:30:54 +0000
Subject: [PATCH 018/280] Updated README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 492d2307574e..0de41a5ec5f5 100644
--- a/README.md
+++ b/README.md
@@ -37,7 +37,7 @@ A simple terminal UI for git commands
-
+
## Elevator Pitch
From daf9b8cfa9213a791d10d41e8f00c61d21ac7aa4 Mon Sep 17 00:00:00 2001
From: Stefan Haller
Date: Tue, 19 Dec 2023 15:03:35 +0100
Subject: [PATCH 019/280] Simplify GetCommitMessage
Use git log instead of git rev-list, this way we don't get a line "commit "
at the beginning that we then have to discard again.
The test TestGetCommitMsg is becoming a bit pointless now, since it just
compares that input and output are identical.
---
pkg/commands/git_commands/commit.go | 5 ++---
pkg/commands/git_commands/commit_test.go | 15 ++++++---------
2 files changed, 8 insertions(+), 12 deletions(-)
diff --git a/pkg/commands/git_commands/commit.go b/pkg/commands/git_commands/commit.go
index 812d2f6b5750..443289f6ea87 100644
--- a/pkg/commands/git_commands/commit.go
+++ b/pkg/commands/git_commands/commit.go
@@ -137,12 +137,11 @@ func (self *CommitCommands) signoffFlag() string {
}
func (self *CommitCommands) GetCommitMessage(commitSha string) (string, error) {
- cmdArgs := NewGitCmd("rev-list").
+ cmdArgs := NewGitCmd("log").
Arg("--format=%B", "--max-count=1", commitSha).
ToArgv()
- messageWithHeader, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
- message := strings.Join(strings.SplitAfter(messageWithHeader, "\n")[1:], "")
+ message, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
return strings.TrimSpace(message), err
}
diff --git a/pkg/commands/git_commands/commit_test.go b/pkg/commands/git_commands/commit_test.go
index a6f887a1213b..0ade25a8de56 100644
--- a/pkg/commands/git_commands/commit_test.go
+++ b/pkg/commands/git_commands/commit_test.go
@@ -260,19 +260,17 @@ func TestGetCommitMsg(t *testing.T) {
scenarios := []scenario{
{
"empty",
- ` commit deadbeef`,
+ ``,
``,
},
{
"no line breaks (single line)",
- `commit deadbeef
-use generics to DRY up context code`,
+ `use generics to DRY up context code`,
`use generics to DRY up context code`,
},
{
"with line breaks",
- `commit deadbeef
-Merge pull request #1750 from mark2185/fix-issue-template
+ `Merge pull request #1750 from mark2185/fix-issue-template
'git-rev parse' should be 'git rev-parse'`,
`Merge pull request #1750 from mark2185/fix-issue-template
@@ -285,7 +283,7 @@ Merge pull request #1750 from mark2185/fix-issue-template
s := s
t.Run(s.testName, func(t *testing.T) {
instance := buildCommitCommands(commonDeps{
- runner: oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"rev-list", "--format=%B", "--max-count=1", "deadbeef"}, s.input, nil),
+ runner: oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"log", "--format=%B", "--max-count=1", "deadbeef"}, s.input, nil),
})
output, err := instance.GetCommitMessage("deadbeef")
@@ -306,15 +304,14 @@ func TestGetCommitMessageFromHistory(t *testing.T) {
scenarios := []scenario{
{
"Empty message",
- oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"log", "-1", "--skip=2", "--pretty=%H"}, "", nil).ExpectGitArgs([]string{"rev-list", "--format=%B", "--max-count=1"}, "", nil),
+ oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"log", "-1", "--skip=2", "--pretty=%H"}, "", nil).ExpectGitArgs([]string{"log", "--format=%B", "--max-count=1"}, "", nil),
func(output string, err error) {
assert.Error(t, err)
},
},
{
"Default case to retrieve a commit in history",
- oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"log", "-1", "--skip=2", "--pretty=%H"}, "sha3 \n", nil).ExpectGitArgs([]string{"rev-list", "--format=%B", "--max-count=1", "sha3"}, `commit sha3
- use generics to DRY up context code`, nil),
+ oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"log", "-1", "--skip=2", "--pretty=%H"}, "sha3 \n", nil).ExpectGitArgs([]string{"log", "--format=%B", "--max-count=1", "sha3"}, `use generics to DRY up context code`, nil),
func(output string, err error) {
assert.NoError(t, err)
assert.Equal(t, "use generics to DRY up context code", output)
From 9a423c388ddf7185a22d8515a37a6f9a16318d82 Mon Sep 17 00:00:00 2001
From: Stefan Haller
Date: Tue, 19 Dec 2023 16:40:06 +0100
Subject: [PATCH 020/280] Remove unused function
I think this is a left-over from before we had the new commit message panel. It
no longer makes sense to add a newline to the commit subject.
---
pkg/integration/components/commit_message_panel_driver.go | 6 ------
1 file changed, 6 deletions(-)
diff --git a/pkg/integration/components/commit_message_panel_driver.go b/pkg/integration/components/commit_message_panel_driver.go
index 52ad60815a2f..b3dda6a0407e 100644
--- a/pkg/integration/components/commit_message_panel_driver.go
+++ b/pkg/integration/components/commit_message_panel_driver.go
@@ -38,12 +38,6 @@ func (self *CommitMessagePanelDriver) SwitchToDescription() *CommitDescriptionPa
return &CommitDescriptionPanelDriver{t: self.t}
}
-func (self *CommitMessagePanelDriver) AddNewline() *CommitMessagePanelDriver {
- self.t.press(self.t.keys.Universal.Confirm)
-
- return self
-}
-
func (self *CommitMessagePanelDriver) Clear() *CommitMessagePanelDriver {
// clearing multiple times in case there's multiple lines
// (the clear button only clears a single line at a time)
From 3ebba5f32cdd1549f032a68592928b23bbddf3f8 Mon Sep 17 00:00:00 2001
From: Stefan Haller
Date: Tue, 19 Dec 2023 16:44:16 +0100
Subject: [PATCH 021/280] Add test demonstrating a bug with preserving the
commit message
SplitCommitMessageAndDescription splits at the first '\n\n' that it finds (if
there is one), which in this case is between the two paragraphs of the
description. This is wrong.
---
.../commit_description_panel_driver.go | 11 +++++
.../tests/commit/preserve_commit_message.go | 48 +++++++++++++++++++
pkg/integration/tests/test_list.go | 1 +
3 files changed, 60 insertions(+)
create mode 100644 pkg/integration/tests/commit/preserve_commit_message.go
diff --git a/pkg/integration/components/commit_description_panel_driver.go b/pkg/integration/components/commit_description_panel_driver.go
index 993b1316fffb..0c4b2cfbb08f 100644
--- a/pkg/integration/components/commit_description_panel_driver.go
+++ b/pkg/integration/components/commit_description_panel_driver.go
@@ -8,6 +8,13 @@ func (self *CommitDescriptionPanelDriver) getViewDriver() *ViewDriver {
return self.t.Views().CommitDescription()
}
+// asserts on the current context of the description
+func (self *CommitDescriptionPanelDriver) Content(expected *TextMatcher) *CommitDescriptionPanelDriver {
+ self.getViewDriver().Content(expected)
+
+ return self
+}
+
func (self *CommitDescriptionPanelDriver) Type(value string) *CommitDescriptionPanelDriver {
self.t.typeContent(value)
@@ -29,3 +36,7 @@ func (self *CommitDescriptionPanelDriver) Title(expected *TextMatcher) *CommitDe
return self
}
+
+func (self *CommitDescriptionPanelDriver) Cancel() {
+ self.getViewDriver().PressEscape()
+}
diff --git a/pkg/integration/tests/commit/preserve_commit_message.go b/pkg/integration/tests/commit/preserve_commit_message.go
new file mode 100644
index 000000000000..5a580e8544f5
--- /dev/null
+++ b/pkg/integration/tests/commit/preserve_commit_message.go
@@ -0,0 +1,48 @@
+package commit
+
+import (
+ "github.com/jesseduffield/lazygit/pkg/config"
+ . "github.com/jesseduffield/lazygit/pkg/integration/components"
+)
+
+var PreserveCommitMessage = NewIntegrationTest(NewIntegrationTestArgs{
+ Description: "Test that the commit message is preserved correctly when canceling the commit message panel",
+ ExtraCmdArgs: []string{},
+ Skip: false,
+ SetupConfig: func(config *config.AppConfig) {},
+ SetupRepo: func(shell *Shell) {
+ shell.CreateFileAndAdd("myfile", "myfile content")
+ },
+ Run: func(t *TestDriver, keys config.KeybindingConfig) {
+ t.Views().Files().
+ IsFocused().
+ Press(keys.Files.CommitChanges)
+
+ t.ExpectPopup().CommitMessagePanel().
+ InitialText(Equals("")).
+ Type("my commit message").
+ SwitchToDescription().
+ Type("first paragraph").
+ AddNewline().
+ AddNewline().
+ Type("second paragraph").
+ Cancel()
+
+ t.Views().Files().
+ IsFocused().
+ Press(keys.Files.CommitChanges)
+
+ /* EXPECTED:
+ t.ExpectPopup().CommitMessagePanel().
+ Content(Equals("my commit message")).
+ SwitchToDescription().
+ Content(Equals("first paragraph\n\nsecond paragraph"))
+
+ ACTUAL:
+ */
+ t.ExpectPopup().CommitMessagePanel().
+ Content(Equals("my commit message\nfirst paragraph")).
+ SwitchToDescription().
+ Content(Equals("second paragraph"))
+ },
+})
diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go
index c61a7c2e6d7a..ccefd1dd23eb 100644
--- a/pkg/integration/tests/test_list.go
+++ b/pkg/integration/tests/test_list.go
@@ -74,6 +74,7 @@ var tests = []*components.IntegrationTest{
commit.History,
commit.HistoryComplex,
commit.NewBranch,
+ commit.PreserveCommitMessage,
commit.ResetAuthor,
commit.Revert,
commit.RevertMerge,
From cd50c79ae49b1fb710ce2d4f934f2132d762d3c0 Mon Sep 17 00:00:00 2001
From: Stefan Haller
Date: Tue, 19 Dec 2023 16:47:55 +0100
Subject: [PATCH 022/280] Preserve the commit message correctly even if the
description has blank lines
There are two possible fixes for this bug, and they differ in behavior when
rewording a commit. The one I chose here always splits at the first line feed,
which means that for an improperly formatted commit message such as this one:
This is a very long multi-line subject,
which you shouldn't really use in git.
And this is the body (we call it "description" in lazygit).
we split after the first line instead of after the first paragraph. This is
arguably not what the original author meant, but splitting after the first
paragraph doesn't really work well in lazygit, because we would try to put both
lines into the one-line subject field of the message panel, and you'd only see
the second and not even know that there are more.
The other potential fix would have been to join subject and description with two
line feeds instead of one in JoinCommitMessageAndDescription; this would have
fixed our bug in the same way, but would result in splitting the above message
after the second line instead of the first. I think that's worse, so I decided
for the first fix.
While we're at it, simplify the code a little bit; strings.Cut is documented to
return (s, "") when the separator is not found, so there's no need to do this on
our side.
We do have to trim spaces on the description now, to support the regular reword
case where subject and body are separated by a blank line.
---
pkg/gui/controllers/helpers/commits_helper.go | 9 ++-------
pkg/integration/tests/commit/preserve_commit_message.go | 8 --------
2 files changed, 2 insertions(+), 15 deletions(-)
diff --git a/pkg/gui/controllers/helpers/commits_helper.go b/pkg/gui/controllers/helpers/commits_helper.go
index 5d388c319871..8691518cd558 100644
--- a/pkg/gui/controllers/helpers/commits_helper.go
+++ b/pkg/gui/controllers/helpers/commits_helper.go
@@ -41,13 +41,8 @@ func NewCommitsHelper(
}
func (self *CommitsHelper) SplitCommitMessageAndDescription(message string) (string, string) {
- for _, separator := range []string{"\n\n", "\n\r\n\r", "\n", "\n\r"} {
- msg, description, found := strings.Cut(message, separator)
- if found {
- return msg, description
- }
- }
- return message, ""
+ msg, description, _ := strings.Cut(message, "\n")
+ return msg, strings.TrimSpace(description)
}
func (self *CommitsHelper) SetMessageAndDescriptionInView(message string) {
diff --git a/pkg/integration/tests/commit/preserve_commit_message.go b/pkg/integration/tests/commit/preserve_commit_message.go
index 5a580e8544f5..e9297ab76bfa 100644
--- a/pkg/integration/tests/commit/preserve_commit_message.go
+++ b/pkg/integration/tests/commit/preserve_commit_message.go
@@ -32,17 +32,9 @@ var PreserveCommitMessage = NewIntegrationTest(NewIntegrationTestArgs{
IsFocused().
Press(keys.Files.CommitChanges)
- /* EXPECTED:
t.ExpectPopup().CommitMessagePanel().
Content(Equals("my commit message")).
SwitchToDescription().
Content(Equals("first paragraph\n\nsecond paragraph"))
-
- ACTUAL:
- */
- t.ExpectPopup().CommitMessagePanel().
- Content(Equals("my commit message\nfirst paragraph")).
- SwitchToDescription().
- Content(Equals("second paragraph"))
},
})
From d70dd5123dfca5248ab9f2396307c46eff96bcb4 Mon Sep 17 00:00:00 2001
From: Stefan Haller
Date: Sun, 26 Nov 2023 13:11:50 +0100
Subject: [PATCH 023/280] Add config setting for side panel location (left or
top) in half screen mode
---
docs/Config.md | 1 +
pkg/config/user_config.go | 6 ++
.../helpers/window_arrangement_helper.go | 10 ++-
.../helpers/window_arrangement_helper_test.go | 64 +++++++++++++++++++
schema/config.json | 5 ++
5 files changed, 85 insertions(+), 1 deletion(-)
diff --git a/docs/Config.md b/docs/Config.md
index 2f1f57ecc233..741a2c2b90a7 100644
--- a/docs/Config.md
+++ b/docs/Config.md
@@ -40,6 +40,7 @@ gui:
sidePanelWidth: 0.3333 # number from 0 to 1
expandFocusedSidePanel: false
mainPanelSplitMode: 'flexible' # one of 'horizontal' | 'flexible' | 'vertical'
+ enlargedSideViewLocation: 'left' # one of 'left' | 'top'
language: 'auto' # one of 'auto' | 'en' | 'zh-CN' | 'zh-TW' | 'pl' | 'nl' | 'ja' | 'ko' | 'ru'
timeFormat: '02 Jan 06' # https://pkg.go.dev/time#Time.Format
shortTimeFormat: '3:04PM'
diff --git a/pkg/config/user_config.go b/pkg/config/user_config.go
index 2392d49cf6e3..ffc27eb88d63 100644
--- a/pkg/config/user_config.go
+++ b/pkg/config/user_config.go
@@ -84,6 +84,11 @@ type GuiConfig struct {
// - 'vertical': split the window vertically
// - 'flexible': (default) split the window horizontally if the window is wide enough, otherwise split vertically
MainPanelSplitMode string `yaml:"mainPanelSplitMode" jsonschema:"enum=horizontal,enum=flexible,enum=vertical"`
+ // How the window is split when in half screen mode (i.e. after hitting '+' once).
+ // Possible values:
+ // - 'left': split the window horizontally (side panel on the left, main view on the right)
+ // - 'top': split the window vertically (side panel on top, main view below)
+ EnlargedSideViewLocation string `yaml:"enlargedSideViewLocation"`
// One of 'auto' (default) | 'en' | 'zh-CN' | 'zh-TW' | 'pl' | 'nl' | 'ja' | 'ko' | 'ru'
Language string `yaml:"language" jsonschema:"enum=auto,enum=en,enum=zh-TW,enum=zh-CN,enum=pl,enum=nl,enum=ja,enum=ko,enum=ru"`
// Format used when displaying time e.g. commit time.
@@ -604,6 +609,7 @@ func GetDefaultConfig() *UserConfig {
SidePanelWidth: 0.3333,
ExpandFocusedSidePanel: false,
MainPanelSplitMode: "flexible",
+ EnlargedSideViewLocation: "left",
Language: "auto",
TimeFormat: "02 Jan 06",
ShortTimeFormat: time.Kitchen,
diff --git a/pkg/gui/controllers/helpers/window_arrangement_helper.go b/pkg/gui/controllers/helpers/window_arrangement_helper.go
index 0d9b119e360f..8615769dc1b4 100644
--- a/pkg/gui/controllers/helpers/window_arrangement_helper.go
+++ b/pkg/gui/controllers/helpers/window_arrangement_helper.go
@@ -107,6 +107,10 @@ func (self *WindowArrangementHelper) GetWindowDimensions(informationStr string,
}
func shouldUsePortraitMode(args WindowArrangementArgs) bool {
+ if args.ScreenMode == types.SCREEN_HALF {
+ return args.UserConfig.Gui.EnlargedSideViewLocation == "top"
+ }
+
switch args.UserConfig.Gui.PortraitMode {
case "never":
return false
@@ -241,7 +245,11 @@ func getMidSectionWeights(args WindowArrangementArgs) (int, int) {
}
} else {
if args.ScreenMode == types.SCREEN_HALF {
- mainSectionWeight = 1
+ if args.UserConfig.Gui.EnlargedSideViewLocation == "top" {
+ mainSectionWeight = 2
+ } else {
+ mainSectionWeight = 1
+ }
} else if args.ScreenMode == types.SCREEN_FULL {
mainSectionWeight = 0
}
diff --git a/pkg/gui/controllers/helpers/window_arrangement_helper_test.go b/pkg/gui/controllers/helpers/window_arrangement_helper_test.go
index 4e299ec2e9f1..9c455ff331d8 100644
--- a/pkg/gui/controllers/helpers/window_arrangement_helper_test.go
+++ b/pkg/gui/controllers/helpers/window_arrangement_helper_test.go
@@ -121,6 +121,70 @@ func TestGetWindowDimensions(t *testing.T) {
B: information
`,
},
+ {
+ name: "half screen mode, enlargedSideViewLocation left",
+ mutateArgs: func(args *WindowArrangementArgs) {
+ args.Height = 20 // smaller height because we don't more here
+ args.ScreenMode = types.SCREEN_HALF
+ args.UserConfig.Gui.EnlargedSideViewLocation = "left"
+ },
+ expected: `
+ ╭status──────────────────────────────╮╭main───────────────────────────────╮
+ │ ││ │
+ │ ││ │
+ │ ││ │
+ │ ││ │
+ │ ││ │
+ │ ││ │
+ │ ││ │
+ │ ││ │
+ │ ││ │
+ │ ││ │
+ │ ││ │
+ │ ││ │
+ │ ││ │
+ │ ││ │
+ │ ││ │
+ │ ││ │
+ │ ││ │
+ ╰────────────────────────────────────╯╰───────────────────────────────────╯
+ A
+ A: statusSpacer1
+ B: information
+ `,
+ },
+ {
+ name: "half screen mode, enlargedSideViewLocation top",
+ mutateArgs: func(args *WindowArrangementArgs) {
+ args.Height = 20 // smaller height because we don't more here
+ args.ScreenMode = types.SCREEN_HALF
+ args.UserConfig.Gui.EnlargedSideViewLocation = "top"
+ },
+ expected: `
+ ╭status───────────────────────────────────────────────────────────────────╮
+ │ │
+ │ │
+ │ │
+ │ │
+ │ │
+ ╰─────────────────────────────────────────────────────────────────────────╯
+ ╭main─────────────────────────────────────────────────────────────────────╮
+ │ │
+ │ │
+ │ │
+ │ │
+ │ │
+ │ │
+ │ │
+ │ │
+ │ │
+ │ │
+ ╰─────────────────────────────────────────────────────────────────────────╯
+ A
+ A: statusSpacer1
+ B: information
+ `,
+ },
{
name: "search mode",
mutateArgs: func(args *WindowArrangementArgs) {
diff --git a/schema/config.json b/schema/config.json
index 45976cbb9203..a5cbc0b67261 100644
--- a/schema/config.json
+++ b/schema/config.json
@@ -81,6 +81,11 @@
"description": "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.\nOptions are:\n- 'horizontal': split the window horizontally\n- 'vertical': split the window vertically\n- 'flexible': (default) split the window horizontally if the window is wide enough, otherwise split vertically",
"default": "flexible"
},
+ "enlargedSideViewLocation": {
+ "type": "string",
+ "description": "How the window is split when in half screen mode (i.e. after hitting '+' once).\nPossible values:\n- 'left': split the window horizontally (side panel on the left, main view on the right)\n- 'top': split the window vertically (side panel on top, main view below)",
+ "default": "left"
+ },
"language": {
"type": "string",
"enum": [
From 8ca78412acdc73526248ff10c332f98ceba39fd2 Mon Sep 17 00:00:00 2001
From: Stefan Haller
Date: Tue, 1 Aug 2023 14:54:56 +0200
Subject: [PATCH 024/280] Add command to find base commit for creating a fixup
---
docs/Config.md | 1 +
docs/Fixup_Commits.md | 64 +++++++
docs/keybindings/Keybindings_en.md | 1 +
docs/keybindings/Keybindings_ja.md | 1 +
docs/keybindings/Keybindings_ko.md | 1 +
docs/keybindings/Keybindings_nl.md | 1 +
docs/keybindings/Keybindings_pl.md | 1 +
docs/keybindings/Keybindings_ru.md | 1 +
docs/keybindings/Keybindings_zh-CN.md | 1 +
docs/keybindings/Keybindings_zh-TW.md | 1 +
pkg/commands/git.go | 3 +
pkg/commands/git_commands/blame.go | 33 ++++
pkg/commands/git_commands/commit.go | 14 ++
pkg/commands/git_commands/diff.go | 8 +
pkg/config/user_config.go | 2 +
pkg/gui/controllers.go | 1 +
pkg/gui/controllers/files_controller.go | 6 +
pkg/gui/controllers/helpers/fixup_helper.go | 178 ++++++++++++++++++
pkg/gui/controllers/helpers/helpers.go | 2 +
pkg/i18n/english.go | 16 ++
.../commit/find_base_commit_for_fixup.go | 79 ++++++++
pkg/integration/tests/test_list.go | 1 +
schema/config.json | 4 +
23 files changed, 420 insertions(+)
create mode 100644 docs/Fixup_Commits.md
create mode 100644 pkg/commands/git_commands/blame.go
create mode 100644 pkg/gui/controllers/helpers/fixup_helper.go
create mode 100644 pkg/integration/tests/commit/find_base_commit_for_fixup.go
diff --git a/docs/Config.md b/docs/Config.md
index 741a2c2b90a7..8b37c6aaa654 100644
--- a/docs/Config.md
+++ b/docs/Config.md
@@ -209,6 +209,7 @@ keybinding:
commitChangesWithoutHook: 'w' # commit changes without pre-commit hook
amendLastCommit: 'A'
commitChangesWithEditor: 'C'
+ findBaseCommitForFixup: ''
confirmDiscard: 'x'
ignoreFile: 'i'
refreshFiles: 'r'
diff --git a/docs/Fixup_Commits.md b/docs/Fixup_Commits.md
new file mode 100644
index 000000000000..1d148c546c3f
--- /dev/null
+++ b/docs/Fixup_Commits.md
@@ -0,0 +1,64 @@
+# Fixup Commits
+
+## Background
+
+There's this common scenario that you have a PR in review, the reviewer is
+requesting some changes, and you make those changes and would normally simply
+squash them into the original commit that they came from. If you do that,
+however, there's no way for the reviewer to see what you changed. You could just
+make a separate commit with those changes at the end of the branch, but this is
+not ideal because it results in a git history that is not very clean.
+
+To help with this, git has a concept of fixup commits: you do make a separate
+commit, but the subject of this commit is the string "fixup! " followed by the
+original commit subject. This both tells the reviewer what's going on (you are
+making a change that you later will squash into the designated commit), and it
+provides an easy way to actually perform this squash operation when you are
+ready to do that (before merging).
+
+## Creating fixup commits
+
+You could of course create fixup commits manually by typing in the commit
+message with the prefix yourself. But lazygit has an easier way to do that:
+in the Commits view, select the commit that you want to create a fixup for, and
+press shift-F (for "Create fixup commit for this commit"). This automatically
+creates a commit with the appropriate subject line.
+
+Don't confuse this with the lowercase "f" command ("Fixup commit"); that one
+squashes the selected commit into its parent, this is not what we want here.
+
+## Squashing fixup commits
+
+When you're ready to merge the branch and want to squash all these fixup commits
+that you created, that's very easy to do: select the first commit of your branch
+and hit shift-S (for "Squash all 'fixup!' commits above selected commit
+(autosquash)"). Boom, done.
+
+## Finding the commit to create a fixup for
+
+When you are making changes to code that you changed earlier in a long branch,
+it can be tedious to find the commit to squash it into. Lazygit has a command to
+help you with this, too: in the Files view, press ctrl-f to select the right
+base commit in the Commits view automatically. From there, you can either press
+shift-F to create a fixup commit for it, or shift-A to amend your changes into
+the commit if you haven't published your branch yet.
+
+This command works in many cases, and when it does it almost feels like magic,
+but it's important to understand its limitations because it doesn't always work.
+The way it works is that it looks at the deleted lines of your current
+modifications, blames them to find out which commit those lines come from, and
+if they all come from the same commit, it selects it. So here are cases where it
+doesn't work:
+
+- Your current diff has only added lines, but no deleted lines. In this case
+ there's no way for lazygit to know which commit you want to add them to.
+- The deleted lines belong to multiple different commits. In this case you can
+ help lazygit by staging a set of files or hunks that all belong to the same
+ commit; if some changes are staged, the ctrl-f command works only on those.
+- The found commit is already on master; in this case, lazygit refuses to select
+ it, because it doesn't make sense to create fixups for it, let alone amend to
+ it.
+
+To sum it up: the command works great if you are changing code again that you
+changed or added earlier in the same branch. This is a common enough case to
+make the command useful.
diff --git a/docs/keybindings/Keybindings_en.md b/docs/keybindings/Keybindings_en.md
index f7aaa46c8173..ef6299613bf6 100644
--- a/docs/keybindings/Keybindings_en.md
+++ b/docs/keybindings/Keybindings_en.md
@@ -123,6 +123,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
w: Commit changes without pre-commit hook
A: Amend last commit
C: Commit changes using git editor
+ <c-f>: Find base commit for fixup
e: Edit file
o: Open file
i: Ignore or exclude file
diff --git a/docs/keybindings/Keybindings_ja.md b/docs/keybindings/Keybindings_ja.md
index a5a54b872ca4..36389ecc1110 100644
--- a/docs/keybindings/Keybindings_ja.md
+++ b/docs/keybindings/Keybindings_ja.md
@@ -196,6 +196,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
w: pre-commitフックを実行せずに変更をコミット
A: 最新のコミットにamend
C: gitエディタを使用して変更をコミット
+ <c-f>: Find base commit for fixup
e: ファイルを編集
o: ファイルを開く
i: ファイルをignore
diff --git a/docs/keybindings/Keybindings_ko.md b/docs/keybindings/Keybindings_ko.md
index fc7291c4ba82..d96283192051 100644
--- a/docs/keybindings/Keybindings_ko.md
+++ b/docs/keybindings/Keybindings_ko.md
@@ -336,6 +336,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
w: Commit changes without pre-commit hook
A: 마지맛 커밋 수정
C: Git 편집기를 사용하여 변경 내용을 커밋합니다.
+ <c-f>: Find base commit for fixup
e: 파일 편집
o: 파일 닫기
i: Ignore file
diff --git a/docs/keybindings/Keybindings_nl.md b/docs/keybindings/Keybindings_nl.md
index c2d81a06d479..23bf0a477158 100644
--- a/docs/keybindings/Keybindings_nl.md
+++ b/docs/keybindings/Keybindings_nl.md
@@ -56,6 +56,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
w: Commit veranderingen zonder pre-commit hook
A: Wijzig laatste commit
C: Commit veranderingen met de git editor
+ <c-f>: Find base commit for fixup
e: Verander bestand
o: Open bestand
i: Ignore or exclude file
diff --git a/docs/keybindings/Keybindings_pl.md b/docs/keybindings/Keybindings_pl.md
index 2136a5cdf696..601ec72f5932 100644
--- a/docs/keybindings/Keybindings_pl.md
+++ b/docs/keybindings/Keybindings_pl.md
@@ -157,6 +157,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
w: Zatwierdź zmiany bez skryptu pre-commit
A: Zmień ostatni commit
C: Zatwierdź zmiany używając edytora
+ <c-f>: Find base commit for fixup
e: Edytuj plik
o: Otwórz plik
i: Ignore or exclude file
diff --git a/docs/keybindings/Keybindings_ru.md b/docs/keybindings/Keybindings_ru.md
index ab82515ad5a5..6277f3db8d4b 100644
--- a/docs/keybindings/Keybindings_ru.md
+++ b/docs/keybindings/Keybindings_ru.md
@@ -330,6 +330,7 @@ _Связки клавиш_
w: Закоммитить изменения без предварительного хука коммита
A: Правка последнего коммита
C: Сохранить изменения с помощью редактора git
+ <c-f>: Find base commit for fixup
e: Редактировать файл
o: Открыть файл
i: Игнорировать или исключить файл
diff --git a/docs/keybindings/Keybindings_zh-CN.md b/docs/keybindings/Keybindings_zh-CN.md
index 49bce5ece13a..4b4a8e028da7 100644
--- a/docs/keybindings/Keybindings_zh-CN.md
+++ b/docs/keybindings/Keybindings_zh-CN.md
@@ -204,6 +204,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
w: 提交更改而无需预先提交钩子
A: 修补最后一次提交
C: 提交更改(使用编辑器编辑提交信息)
+ <c-f>: Find base commit for fixup
e: 编辑文件
o: 打开文件
i: 忽略文件
diff --git a/docs/keybindings/Keybindings_zh-TW.md b/docs/keybindings/Keybindings_zh-TW.md
index 201f7955500e..8fd6a274bda3 100644
--- a/docs/keybindings/Keybindings_zh-TW.md
+++ b/docs/keybindings/Keybindings_zh-TW.md
@@ -299,6 +299,7 @@ _說明:`` 表示 Ctrl+B、`` 表示 Alt+B,`B`表示 Shift+B_
w: 沒有預提交 hook 就提交更改
A: 修正上次提交
C: 使用 git 編輯器提交變更
+ <c-f>: Find base commit for fixup
e: 編輯檔案
o: 開啟檔案
i: 忽略或排除檔案
diff --git a/pkg/commands/git.go b/pkg/commands/git.go
index 510661034168..b1b04a72fa07 100644
--- a/pkg/commands/git.go
+++ b/pkg/commands/git.go
@@ -21,6 +21,7 @@ import (
// GitCommand is our main git interface
type GitCommand struct {
+ Blame *git_commands.BlameCommands
Branch *git_commands.BranchCommands
Commit *git_commands.CommitCommands
Config *git_commands.ConfigCommands
@@ -160,6 +161,7 @@ func NewGitCommandAux(
patchCommands := git_commands.NewPatchCommands(gitCommon, rebaseCommands, commitCommands, statusCommands, stashCommands, patchBuilder)
bisectCommands := git_commands.NewBisectCommands(gitCommon)
worktreeCommands := git_commands.NewWorktreeCommands(gitCommon)
+ blameCommands := git_commands.NewBlameCommands(gitCommon)
branchLoader := git_commands.NewBranchLoader(cmn, cmd, branchCommands.CurrentBranchInfo, configCommands)
commitFileLoader := git_commands.NewCommitFileLoader(cmn, cmd)
@@ -171,6 +173,7 @@ func NewGitCommandAux(
tagLoader := git_commands.NewTagLoader(cmn, cmd)
return &GitCommand{
+ Blame: blameCommands,
Branch: branchCommands,
Commit: commitCommands,
Config: configCommands,
diff --git a/pkg/commands/git_commands/blame.go b/pkg/commands/git_commands/blame.go
new file mode 100644
index 000000000000..aba1c63fe84c
--- /dev/null
+++ b/pkg/commands/git_commands/blame.go
@@ -0,0 +1,33 @@
+package git_commands
+
+import (
+ "fmt"
+)
+
+type BlameCommands struct {
+ *GitCommon
+}
+
+func NewBlameCommands(gitCommon *GitCommon) *BlameCommands {
+ return &BlameCommands{
+ GitCommon: gitCommon,
+ }
+}
+
+// Blame a range of lines. For each line, output the hash of the commit where
+// the line last changed, then a space, then a description of the commit (author
+// and date), another space, and then the line. For example:
+//
+// ac90ebac688fe8bc2ffd922157a9d2c54681d2aa (Stefan Haller 2023-08-01 14:54:56 +0200 11) func NewBlameCommands(gitCommon *GitCommon) *BlameCommands {
+// ac90ebac688fe8bc2ffd922157a9d2c54681d2aa (Stefan Haller 2023-08-01 14:54:56 +0200 12) return &BlameCommands{
+// ac90ebac688fe8bc2ffd922157a9d2c54681d2aa (Stefan Haller 2023-08-01 14:54:56 +0200 13) GitCommon: gitCommon,
+func (self *BlameCommands) BlameLineRange(filename string, commit string, firstLine int, numLines int) (string, error) {
+ cmdArgs := NewGitCmd("blame").
+ Arg("-l").
+ Arg(fmt.Sprintf("-L%d,+%d", firstLine, numLines)).
+ Arg(commit).
+ Arg("--").
+ Arg(filename)
+
+ return self.cmd.New(cmdArgs.ToArgv()).RunWithOutput()
+}
diff --git a/pkg/commands/git_commands/commit.go b/pkg/commands/git_commands/commit.go
index 443289f6ea87..e0b5b8a9aeb1 100644
--- a/pkg/commands/git_commands/commit.go
+++ b/pkg/commands/git_commands/commit.go
@@ -198,6 +198,20 @@ func (self *CommitCommands) GetCommitMessagesFirstLine(shas []string) (string, e
return self.cmd.New(cmdArgs).DontLog().RunWithOutput()
}
+// Example output:
+//
+// cd50c79ae Preserve the commit message correctly even if the description has blank lines
+// 3ebba5f32 Add test demonstrating a bug with preserving the commit message
+// 9a423c388 Remove unused function
+func (self *CommitCommands) GetShasAndCommitMessagesFirstLine(shas []string) (string, error) {
+ cmdArgs := NewGitCmd("show").
+ Arg("--no-patch", "--pretty=format:%h %s").
+ Arg(shas...).
+ ToArgv()
+
+ return self.cmd.New(cmdArgs).DontLog().RunWithOutput()
+}
+
func (self *CommitCommands) GetCommitsOneline(shas []string) (string, error) {
cmdArgs := NewGitCmd("show").
Arg("--no-patch", "--oneline").
diff --git a/pkg/commands/git_commands/diff.go b/pkg/commands/git_commands/diff.go
index 41e71e94116d..3729390241a9 100644
--- a/pkg/commands/git_commands/diff.go
+++ b/pkg/commands/git_commands/diff.go
@@ -78,3 +78,11 @@ func (self *DiffCommands) OpenDiffToolCmdObj(opts DiffToolCmdOptions) oscommands
Arg("--", opts.Filepath).
ToArgv())
}
+
+func (self *DiffCommands) DiffIndexCmdObj(diffArgs ...string) oscommands.ICmdObj {
+ return self.cmd.New(
+ NewGitCmd("diff-index").
+ Arg("--submodule", "--no-ext-diff", "--no-color", "--patch").
+ Arg(diffArgs...).ToArgv(),
+ )
+}
diff --git a/pkg/config/user_config.go b/pkg/config/user_config.go
index ffc27eb88d63..d0d5184551fe 100644
--- a/pkg/config/user_config.go
+++ b/pkg/config/user_config.go
@@ -366,6 +366,7 @@ type KeybindingFilesConfig struct {
CommitChangesWithoutHook string `yaml:"commitChangesWithoutHook"`
AmendLastCommit string `yaml:"amendLastCommit"`
CommitChangesWithEditor string `yaml:"commitChangesWithEditor"`
+ FindBaseCommitForFixup string `yaml:"findBaseCommitForFixup"`
ConfirmDiscard string `yaml:"confirmDiscard"`
IgnoreFile string `yaml:"ignoreFile"`
RefreshFiles string `yaml:"refreshFiles"`
@@ -762,6 +763,7 @@ func GetDefaultConfig() *UserConfig {
CommitChangesWithoutHook: "w",
AmendLastCommit: "A",
CommitChangesWithEditor: "C",
+ FindBaseCommitForFixup: "",
IgnoreFile: "i",
RefreshFiles: "r",
StashAllChanges: "s",
diff --git a/pkg/gui/controllers.go b/pkg/gui/controllers.go
index 37c54a655a57..b4cbec79e88c 100644
--- a/pkg/gui/controllers.go
+++ b/pkg/gui/controllers.go
@@ -103,6 +103,7 @@ func (gui *Gui) resetHelpersAndControllers() {
CherryPick: cherryPickHelper,
Upstream: helpers.NewUpstreamHelper(helperCommon, suggestionsHelper.GetRemoteBranchesSuggestionsFunc),
AmendHelper: helpers.NewAmendHelper(helperCommon, gpgHelper),
+ FixupHelper: helpers.NewFixupHelper(helperCommon),
Commits: commitsHelper,
Snake: helpers.NewSnakeHelper(helperCommon),
Diff: diffHelper,
diff --git a/pkg/gui/controllers/files_controller.go b/pkg/gui/controllers/files_controller.go
index 0db509ca3b0f..d60773ec14da 100644
--- a/pkg/gui/controllers/files_controller.go
+++ b/pkg/gui/controllers/files_controller.go
@@ -64,6 +64,12 @@ func (self *FilesController) GetKeybindings(opts types.KeybindingsOpts) []*types
Handler: self.c.Helpers().WorkingTree.HandleCommitEditorPress,
Description: self.c.Tr.CommitChangesWithEditor,
},
+ {
+ Key: opts.GetKey(opts.Config.Files.FindBaseCommitForFixup),
+ Handler: self.c.Helpers().FixupHelper.HandleFindBaseCommitForFixupPress,
+ Description: self.c.Tr.FindBaseCommitForFixup,
+ Tooltip: self.c.Tr.FindBaseCommitForFixupTooltip,
+ },
{
Key: opts.GetKey(opts.Config.Universal.Edit),
Handler: self.checkSelectedFileNode(self.edit),
diff --git a/pkg/gui/controllers/helpers/fixup_helper.go b/pkg/gui/controllers/helpers/fixup_helper.go
new file mode 100644
index 000000000000..2198d11cb2b9
--- /dev/null
+++ b/pkg/gui/controllers/helpers/fixup_helper.go
@@ -0,0 +1,178 @@
+package helpers
+
+import (
+ "regexp"
+ "strings"
+ "sync"
+
+ "github.com/jesseduffield/generics/set"
+ "github.com/jesseduffield/lazygit/pkg/commands/models"
+ "github.com/jesseduffield/lazygit/pkg/gui/types"
+ "github.com/jesseduffield/lazygit/pkg/utils"
+ "github.com/samber/lo"
+)
+
+type FixupHelper struct {
+ c *HelperCommon
+}
+
+func NewFixupHelper(
+ c *HelperCommon,
+) *FixupHelper {
+ return &FixupHelper{
+ c: c,
+ }
+}
+
+type deletedLineInfo struct {
+ filename string
+ startLineIdx int
+ numLines int
+}
+
+func (self *FixupHelper) HandleFindBaseCommitForFixupPress() error {
+ diff, hasStagedChanges, err := self.getDiff()
+ if err != nil {
+ return err
+ }
+ if diff == "" {
+ return self.c.ErrorMsg(self.c.Tr.NoChangedFiles)
+ }
+
+ deletedLineInfos := self.parseDiff(diff)
+ if len(deletedLineInfos) == 0 {
+ return self.c.ErrorMsg(self.c.Tr.NoDeletedLinesInDiff)
+ }
+
+ shas := self.blameDeletedLines(deletedLineInfos)
+
+ if len(shas) == 0 {
+ // This should never happen
+ return self.c.ErrorMsg(self.c.Tr.NoBaseCommitsFound)
+ }
+ if len(shas) > 1 {
+ subjects, err := self.c.Git().Commit.GetShasAndCommitMessagesFirstLine(shas)
+ if err != nil {
+ return err
+ }
+ message := lo.Ternary(hasStagedChanges,
+ self.c.Tr.MultipleBaseCommitsFoundStaged,
+ self.c.Tr.MultipleBaseCommitsFoundUnstaged)
+ return self.c.ErrorMsg(message + "\n\n" + subjects)
+ }
+
+ commit, index, ok := lo.FindIndexOf(self.c.Model().Commits, func(commit *models.Commit) bool {
+ return commit.Sha == shas[0]
+ })
+ if !ok {
+ commits := self.c.Model().Commits
+ if commits[len(commits)-1].Status == models.StatusMerged {
+ // If the commit is not found, it's most likely because it's already
+ // merged, and more than 300 commits away. Check if the last known
+ // commit is already merged; if so, show the "already merged" error.
+ return self.c.ErrorMsg(self.c.Tr.BaseCommitIsAlreadyOnMainBranch)
+ }
+ // If we get here, the current branch must have more then 300 commits. Unlikely...
+ return self.c.ErrorMsg(self.c.Tr.BaseCommitIsNotInCurrentView)
+ }
+ if commit.Status == models.StatusMerged {
+ return self.c.ErrorMsg(self.c.Tr.BaseCommitIsAlreadyOnMainBranch)
+ }
+
+ if !useIndex {
+ if err := self.c.Git().WorkingTree.StageAll(); err != nil {
+ return err
+ }
+ _ = self.c.Refresh(types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{types.FILES}})
+ }
+
+ self.c.Contexts().LocalCommits.SetSelectedLineIdx(index)
+ return self.c.PushContext(self.c.Contexts().LocalCommits)
+}
+
+func (self *FixupHelper) getDiff() (string, bool, error) {
+ args := []string{"-U0", "--ignore-submodules=all", "HEAD", "--"}
+
+ // Try staged changes first
+ hasStagedChanges := true
+ diff, err := self.c.Git().Diff.DiffIndexCmdObj(append([]string{"--cached"}, args...)...).RunWithOutput()
+
+ if err == nil && diff == "" {
+ hasStagedChanges = false
+ // If there are no staged changes, try unstaged changes
+ diff, err = self.c.Git().Diff.DiffIndexCmdObj(args...).RunWithOutput()
+ }
+
+ return diff, hasStagedChanges, err
+}
+
+func (self *FixupHelper) parseDiff(diff string) []*deletedLineInfo {
+ lines := strings.Split(strings.TrimSuffix(diff, "\n"), "\n")
+
+ deletedLineInfos := []*deletedLineInfo{}
+
+ hunkHeaderRegexp := regexp.MustCompile(`@@ -(\d+)(?:,\d+)? \+\d+(?:,\d+)? @@`)
+
+ var filename string
+ var currentLineInfo *deletedLineInfo
+ finishHunk := func() {
+ if currentLineInfo != nil && currentLineInfo.numLines > 0 {
+ deletedLineInfos = append(deletedLineInfos, currentLineInfo)
+ }
+ }
+ for _, line := range lines {
+ if strings.HasPrefix(line, "diff --git") {
+ finishHunk()
+ currentLineInfo = nil
+ } else if strings.HasPrefix(line, "--- ") {
+ // For some reason, the line ends with a tab character if the file
+ // name contains spaces
+ filename = strings.TrimRight(line[6:], "\t")
+ } else if strings.HasPrefix(line, "@@ ") {
+ finishHunk()
+ match := hunkHeaderRegexp.FindStringSubmatch(line)
+ startIdx := utils.MustConvertToInt(match[1])
+ currentLineInfo = &deletedLineInfo{filename, startIdx, 0}
+ } else if currentLineInfo != nil && line[0] == '-' {
+ currentLineInfo.numLines++
+ }
+ }
+ finishHunk()
+
+ return deletedLineInfos
+}
+
+// returns the list of commit hashes that introduced the lines which have now been deleted
+func (self *FixupHelper) blameDeletedLines(deletedLineInfos []*deletedLineInfo) []string {
+ var wg sync.WaitGroup
+ shaChan := make(chan string)
+
+ for _, info := range deletedLineInfos {
+ wg.Add(1)
+ go func(info *deletedLineInfo) {
+ defer wg.Done()
+
+ blameOutput, err := self.c.Git().Blame.BlameLineRange(info.filename, "HEAD", info.startLineIdx, info.numLines)
+ if err != nil {
+ self.c.Log.Errorf("Error blaming file '%s': %v", info.filename, err)
+ return
+ }
+ blameLines := strings.Split(strings.TrimSuffix(blameOutput, "\n"), "\n")
+ for _, line := range blameLines {
+ shaChan <- strings.Split(line, " ")[0]
+ }
+ }(info)
+ }
+
+ go func() {
+ wg.Wait()
+ close(shaChan)
+ }()
+
+ result := set.New[string]()
+ for sha := range shaChan {
+ result.Add(sha)
+ }
+
+ return result.ToSlice()
+}
diff --git a/pkg/gui/controllers/helpers/helpers.go b/pkg/gui/controllers/helpers/helpers.go
index 9e05ba163740..1f1050dc974a 100644
--- a/pkg/gui/controllers/helpers/helpers.go
+++ b/pkg/gui/controllers/helpers/helpers.go
@@ -33,6 +33,7 @@ type Helpers struct {
GPG *GpgHelper
Upstream *UpstreamHelper
AmendHelper *AmendHelper
+ FixupHelper *FixupHelper
Commits *CommitsHelper
Snake *SnakeHelper
// lives in context package because our contexts need it to render to main
@@ -70,6 +71,7 @@ func NewStubHelpers() *Helpers {
GPG: &GpgHelper{},
Upstream: &UpstreamHelper{},
AmendHelper: &AmendHelper{},
+ FixupHelper: &FixupHelper{},
Commits: &CommitsHelper{},
Snake: &SnakeHelper{},
Diff: &DiffHelper{},
diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go
index 34df4aba1872..e2559f3d21ac 100644
--- a/pkg/i18n/english.go
+++ b/pkg/i18n/english.go
@@ -39,6 +39,14 @@ type TranslationSet struct {
SureToAmend string
NoCommitToAmend string
CommitChangesWithEditor string
+ FindBaseCommitForFixup string
+ FindBaseCommitForFixupTooltip string
+ NoDeletedLinesInDiff string
+ NoBaseCommitsFound string
+ MultipleBaseCommitsFoundStaged string
+ MultipleBaseCommitsFoundUnstaged string
+ BaseCommitIsAlreadyOnMainBranch string
+ BaseCommitIsNotInCurrentView string
StatusTitle string
GlobalTitle string
Menu string
@@ -858,6 +866,14 @@ func EnglishTranslationSet() TranslationSet {
SureToAmend: "Are you sure you want to amend last commit? Afterwards, you can change the commit message from the commits panel.",
NoCommitToAmend: "There's no commit to amend.",
CommitChangesWithEditor: "Commit changes using git editor",
+ FindBaseCommitForFixup: "Find base commit for fixup",
+ FindBaseCommitForFixupTooltip: "Find the commit that your current changes are building upon, for the sake of amending/fixing up the commit. This spares you from having to look through your branch's commits one-by-one to see which commit should be amended/fixed up. See docs: ",
+ NoDeletedLinesInDiff: "No deleted lines in diff",
+ NoBaseCommitsFound: "No base commits found",
+ MultipleBaseCommitsFoundStaged: "Multiple base commits found. (Try staging fewer changes at once)",
+ MultipleBaseCommitsFoundUnstaged: "Multiple base commits found. (Try staging some of the changes)",
+ BaseCommitIsAlreadyOnMainBranch: "The base commit for this change is already on the main branch",
+ BaseCommitIsNotInCurrentView: "Base commit is not in current view",
StatusTitle: "Status",
Menu: "Menu",
Execute: "Execute",
diff --git a/pkg/integration/tests/commit/find_base_commit_for_fixup.go b/pkg/integration/tests/commit/find_base_commit_for_fixup.go
new file mode 100644
index 000000000000..4440932e9091
--- /dev/null
+++ b/pkg/integration/tests/commit/find_base_commit_for_fixup.go
@@ -0,0 +1,79 @@
+package commit
+
+import (
+ "github.com/jesseduffield/lazygit/pkg/config"
+ . "github.com/jesseduffield/lazygit/pkg/integration/components"
+)
+
+var FindBaseCommitForFixup = NewIntegrationTest(NewIntegrationTestArgs{
+ Description: "Finds the base commit to create a fixup for",
+ ExtraCmdArgs: []string{},
+ Skip: false,
+ SetupConfig: func(config *config.AppConfig) {},
+ SetupRepo: func(shell *Shell) {
+ shell.NewBranch("mybranch").
+ EmptyCommit("1st commit").
+ CreateFileAndAdd("file1", "file1 content\n").
+ Commit("2nd commit").
+ CreateFileAndAdd("file2", "file2 content\n").
+ Commit("3rd commit").
+ UpdateFile("file1", "file1 changed content").
+ UpdateFile("file2", "file2 changed content")
+ },
+ Run: func(t *TestDriver, keys config.KeybindingConfig) {
+ t.Views().Commits().
+ Lines(
+ Contains("3rd commit"),
+ Contains("2nd commit"),
+ Contains("1st commit"),
+ )
+
+ // Two changes from different commits: this fails
+ t.Views().Files().
+ Focus().
+ Press(keys.Files.FindBaseCommitForFixup)
+
+ t.ExpectPopup().Alert().
+ Title(Equals("Error")).
+ Content(
+ Contains("Multiple base commits found").
+ Contains("2nd commit").
+ Contains("3rd commit"),
+ ).
+ Confirm()
+
+ // Stage only one of the files: this succeeds
+ t.Views().Files().
+ IsFocused().
+ NavigateToLine(Contains("file1")).
+ PressPrimaryAction().
+ Press(keys.Files.FindBaseCommitForFixup)
+
+ t.Views().Commits().
+ IsFocused().
+ Lines(
+ Contains("3rd commit"),
+ Contains("2nd commit").IsSelected(),
+ Contains("1st commit"),
+ ).
+ Press(keys.Commits.AmendToCommit)
+
+ t.ExpectPopup().Confirmation().
+ Title(Equals("Amend commit")).
+ Content(Contains("Are you sure you want to amend this commit with your staged files?")).
+ Confirm()
+
+ // Now only the other file is modified (and unstaged); this works now
+ t.Views().Files().
+ Focus().
+ Press(keys.Files.FindBaseCommitForFixup)
+
+ t.Views().Commits().
+ IsFocused().
+ Lines(
+ Contains("3rd commit").IsSelected(),
+ Contains("2nd commit"),
+ Contains("1st commit"),
+ )
+ },
+})
diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go
index ccefd1dd23eb..c018d84e78cc 100644
--- a/pkg/integration/tests/test_list.go
+++ b/pkg/integration/tests/test_list.go
@@ -70,6 +70,7 @@ var tests = []*components.IntegrationTest{
commit.CommitWithPrefix,
commit.CreateTag,
commit.DiscardOldFileChange,
+ commit.FindBaseCommitForFixup,
commit.Highlight,
commit.History,
commit.HistoryComplex,
diff --git a/schema/config.json b/schema/config.json
index a5cbc0b67261..1251240a1d52 100644
--- a/schema/config.json
+++ b/schema/config.json
@@ -914,6 +914,10 @@
"type": "string",
"default": "C"
},
+ "findBaseCommitForFixup": {
+ "type": "string",
+ "default": "\u003cc-f\u003e"
+ },
"confirmDiscard": {
"type": "string",
"default": "x"
From b35f8776e1044414071d828010d2314665b81d82 Mon Sep 17 00:00:00 2001
From: Stefan Haller
Date: Sun, 7 Jan 2024 16:22:46 +0100
Subject: [PATCH 025/280] Warn when there are hunks with only added lines
The algorithm works by blaming the deleted lines, so if a hunk contains only
added lines, we can only hope that it also belongs in the same commit. Warn the
user about this.
Note: the warning might be overly agressive, we'll have to see if this is
annoying. The reason is that it depends on the diff context size whether added
lines go into their own hunk or are grouped together with other added or deleted
lines into one hunk. However, our algorithm uses a diff context size of 0,
because that makes it easiest to parse the diff; this results in hunks having
only added lines more often than what the user sees. For example, moving a line
of code down by two lines will likely result in a single hunk for the user, but
in two hunks for our algorithm. On the other hand, being this strict makes the
warning consistent. We could consider using the user's diff context size in the
algorithm, but then it would depend on the current context size whether the
warning appears, which could be confusing. Plus, it would make the algorithm
quite a bit more complicated.
---
pkg/gui/controllers/helpers/fixup_helper.go | 41 +++++++++++-----
pkg/i18n/english.go | 2 +
...ommit_for_fixup_warning_for_added_lines.go | 48 +++++++++++++++++++
pkg/integration/tests/test_list.go | 1 +
4 files changed, 81 insertions(+), 11 deletions(-)
create mode 100644 pkg/integration/tests/commit/find_base_commit_for_fixup_warning_for_added_lines.go
diff --git a/pkg/gui/controllers/helpers/fixup_helper.go b/pkg/gui/controllers/helpers/fixup_helper.go
index 2198d11cb2b9..35c8233b8ca5 100644
--- a/pkg/gui/controllers/helpers/fixup_helper.go
+++ b/pkg/gui/controllers/helpers/fixup_helper.go
@@ -39,7 +39,7 @@ func (self *FixupHelper) HandleFindBaseCommitForFixupPress() error {
return self.c.ErrorMsg(self.c.Tr.NoChangedFiles)
}
- deletedLineInfos := self.parseDiff(diff)
+ deletedLineInfos, hasHunksWithOnlyAddedLines := self.parseDiff(diff)
if len(deletedLineInfos) == 0 {
return self.c.ErrorMsg(self.c.Tr.NoDeletedLinesInDiff)
}
@@ -79,15 +79,29 @@ func (self *FixupHelper) HandleFindBaseCommitForFixupPress() error {
return self.c.ErrorMsg(self.c.Tr.BaseCommitIsAlreadyOnMainBranch)
}
- if !useIndex {
- if err := self.c.Git().WorkingTree.StageAll(); err != nil {
- return err
+ doIt := func() error {
+ if !hasStagedChanges {
+ if err := self.c.Git().WorkingTree.StageAll(); err != nil {
+ return err
+ }
+ _ = self.c.Refresh(types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{types.FILES}})
}
- _ = self.c.Refresh(types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{types.FILES}})
+
+ self.c.Contexts().LocalCommits.SetSelectedLineIdx(index)
+ return self.c.PushContext(self.c.Contexts().LocalCommits)
}
- self.c.Contexts().LocalCommits.SetSelectedLineIdx(index)
- return self.c.PushContext(self.c.Contexts().LocalCommits)
+ if hasHunksWithOnlyAddedLines {
+ return self.c.Confirm(types.ConfirmOpts{
+ Title: self.c.Tr.FindBaseCommitForFixup,
+ Prompt: self.c.Tr.HunksWithOnlyAddedLinesWarning,
+ HandleConfirm: func() error {
+ return doIt()
+ },
+ })
+ }
+
+ return doIt()
}
func (self *FixupHelper) getDiff() (string, bool, error) {
@@ -106,18 +120,23 @@ func (self *FixupHelper) getDiff() (string, bool, error) {
return diff, hasStagedChanges, err
}
-func (self *FixupHelper) parseDiff(diff string) []*deletedLineInfo {
+func (self *FixupHelper) parseDiff(diff string) ([]*deletedLineInfo, bool) {
lines := strings.Split(strings.TrimSuffix(diff, "\n"), "\n")
deletedLineInfos := []*deletedLineInfo{}
+ hasHunksWithOnlyAddedLines := false
hunkHeaderRegexp := regexp.MustCompile(`@@ -(\d+)(?:,\d+)? \+\d+(?:,\d+)? @@`)
var filename string
var currentLineInfo *deletedLineInfo
finishHunk := func() {
- if currentLineInfo != nil && currentLineInfo.numLines > 0 {
- deletedLineInfos = append(deletedLineInfos, currentLineInfo)
+ if currentLineInfo != nil {
+ if currentLineInfo.numLines > 0 {
+ deletedLineInfos = append(deletedLineInfos, currentLineInfo)
+ } else {
+ hasHunksWithOnlyAddedLines = true
+ }
}
}
for _, line := range lines {
@@ -139,7 +158,7 @@ func (self *FixupHelper) parseDiff(diff string) []*deletedLineInfo {
}
finishHunk()
- return deletedLineInfos
+ return deletedLineInfos, hasHunksWithOnlyAddedLines
}
// returns the list of commit hashes that introduced the lines which have now been deleted
diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go
index e2559f3d21ac..b645291e7010 100644
--- a/pkg/i18n/english.go
+++ b/pkg/i18n/english.go
@@ -47,6 +47,7 @@ type TranslationSet struct {
MultipleBaseCommitsFoundUnstaged string
BaseCommitIsAlreadyOnMainBranch string
BaseCommitIsNotInCurrentView string
+ HunksWithOnlyAddedLinesWarning string
StatusTitle string
GlobalTitle string
Menu string
@@ -874,6 +875,7 @@ func EnglishTranslationSet() TranslationSet {
MultipleBaseCommitsFoundUnstaged: "Multiple base commits found. (Try staging some of the changes)",
BaseCommitIsAlreadyOnMainBranch: "The base commit for this change is already on the main branch",
BaseCommitIsNotInCurrentView: "Base commit is not in current view",
+ HunksWithOnlyAddedLinesWarning: "There are ranges of only added lines in the diff; be careful to check that these belong in the found base commit.\n\nProceed?",
StatusTitle: "Status",
Menu: "Menu",
Execute: "Execute",
diff --git a/pkg/integration/tests/commit/find_base_commit_for_fixup_warning_for_added_lines.go b/pkg/integration/tests/commit/find_base_commit_for_fixup_warning_for_added_lines.go
new file mode 100644
index 000000000000..315b757dbdf3
--- /dev/null
+++ b/pkg/integration/tests/commit/find_base_commit_for_fixup_warning_for_added_lines.go
@@ -0,0 +1,48 @@
+package commit
+
+import (
+ "github.com/jesseduffield/lazygit/pkg/config"
+ . "github.com/jesseduffield/lazygit/pkg/integration/components"
+)
+
+var FindBaseCommitForFixupWarningForAddedLines = NewIntegrationTest(NewIntegrationTestArgs{
+ Description: "Finds the base commit to create a fixup for, and warns that there are hunks with only added lines",
+ ExtraCmdArgs: []string{},
+ Skip: false,
+ SetupConfig: func(config *config.AppConfig) {},
+ SetupRepo: func(shell *Shell) {
+ shell.NewBranch("mybranch").
+ EmptyCommit("1st commit").
+ CreateFileAndAdd("file1", "file1 content\n").
+ Commit("2nd commit").
+ CreateFileAndAdd("file2", "file2 content\n").
+ Commit("3rd commit").
+ UpdateFile("file1", "file1 changed content").
+ UpdateFile("file2", "file2 content\nadded content")
+ },
+ Run: func(t *TestDriver, keys config.KeybindingConfig) {
+ t.Views().Commits().
+ Lines(
+ Contains("3rd commit").IsSelected(),
+ Contains("2nd commit"),
+ Contains("1st commit"),
+ )
+
+ t.Views().Files().
+ Focus().
+ Press(keys.Files.FindBaseCommitForFixup)
+
+ t.ExpectPopup().Confirmation().
+ Title(Equals("Find base commit for fixup")).
+ Content(Contains("There are ranges of only added lines in the diff; be careful to check that these belong in the found base commit.")).
+ Confirm()
+
+ t.Views().Commits().
+ IsFocused().
+ Lines(
+ Contains("3rd commit"),
+ Contains("2nd commit").IsSelected(),
+ Contains("1st commit"),
+ )
+ },
+})
diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go
index c018d84e78cc..1b6ab75e9826 100644
--- a/pkg/integration/tests/test_list.go
+++ b/pkg/integration/tests/test_list.go
@@ -71,6 +71,7 @@ var tests = []*components.IntegrationTest{
commit.CreateTag,
commit.DiscardOldFileChange,
commit.FindBaseCommitForFixup,
+ commit.FindBaseCommitForFixupWarningForAddedLines,
commit.Highlight,
commit.History,
commit.HistoryComplex,
From b657fc4f0b7d7600b7097fbbdb8d1774341885e3 Mon Sep 17 00:00:00 2001
From: README-bot
Date: Wed, 10 Jan 2024 08:17:42 +0000
Subject: [PATCH 026/280] Updated README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 0de41a5ec5f5..16a1230329e6 100644
--- a/README.md
+++ b/README.md
@@ -37,7 +37,7 @@ A simple terminal UI for git commands
-
+
## Elevator Pitch
From 6255728e636eee77451521ded983e50ec57be9b0 Mon Sep 17 00:00:00 2001
From: Stefan Haller
Date: Tue, 9 Jan 2024 13:27:35 +0100
Subject: [PATCH 027/280] Add a method GitVersion.IsAtLeast
---
pkg/commands/git_commands/rebase.go | 4 ++--
pkg/commands/git_commands/version.go | 8 ++++++++
pkg/commands/git_commands/version_test.go | 9 +++++++++
pkg/integration/components/test.go | 2 +-
4 files changed, 20 insertions(+), 3 deletions(-)
diff --git a/pkg/commands/git_commands/rebase.go b/pkg/commands/git_commands/rebase.go
index 2dd1ee886c1f..fde049cda604 100644
--- a/pkg/commands/git_commands/rebase.go
+++ b/pkg/commands/git_commands/rebase.go
@@ -221,9 +221,9 @@ func (self *RebaseCommands) PrepareInteractiveRebaseCommand(opts PrepareInteract
Arg("--interactive").
Arg("--autostash").
Arg("--keep-empty").
- ArgIf(opts.keepCommitsThatBecomeEmpty && !self.version.IsOlderThan(2, 26, 0), "--empty=keep").
+ ArgIf(opts.keepCommitsThatBecomeEmpty && self.version.IsAtLeast(2, 26, 0), "--empty=keep").
Arg("--no-autosquash").
- ArgIf(!self.version.IsOlderThan(2, 22, 0), "--rebase-merges").
+ ArgIf(self.version.IsAtLeast(2, 22, 0), "--rebase-merges").
ArgIf(opts.onto != "", "--onto", opts.onto).
Arg(opts.baseShaOrRoot).
ToArgv()
diff --git a/pkg/commands/git_commands/version.go b/pkg/commands/git_commands/version.go
index a089d7e0618b..aab912ba5bf0 100644
--- a/pkg/commands/git_commands/version.go
+++ b/pkg/commands/git_commands/version.go
@@ -69,3 +69,11 @@ func (v *GitVersion) IsOlderThan(major, minor, patch int) bool {
func (v *GitVersion) IsOlderThanVersion(version *GitVersion) bool {
return v.IsOlderThan(version.Major, version.Minor, version.Patch)
}
+
+func (v *GitVersion) IsAtLeast(major, minor, patch int) bool {
+ return !v.IsOlderThan(major, minor, patch)
+}
+
+func (v *GitVersion) IsAtLeastVersion(version *GitVersion) bool {
+ return v.IsAtLeast(version.Major, version.Minor, version.Patch)
+}
diff --git a/pkg/commands/git_commands/version_test.go b/pkg/commands/git_commands/version_test.go
index 0c57813ef2b0..46b002f606be 100644
--- a/pkg/commands/git_commands/version_test.go
+++ b/pkg/commands/git_commands/version_test.go
@@ -45,3 +45,12 @@ func TestGitVersionIsOlderThan(t *testing.T) {
assert.True(t, (&GitVersion{2, 0, 1, ""}).IsOlderThan(2, 1, 0))
assert.True(t, (&GitVersion{2, 0, 1, ""}).IsOlderThan(3, 0, 0))
}
+
+func TestGitVersionIsAtLeast(t *testing.T) {
+ assert.True(t, (&GitVersion{2, 0, 0, ""}).IsAtLeast(1, 99, 99))
+ assert.True(t, (&GitVersion{2, 0, 0, ""}).IsAtLeast(2, 0, 0))
+ assert.True(t, (&GitVersion{2, 1, 0, ""}).IsAtLeast(2, 0, 9))
+
+ assert.False(t, (&GitVersion{2, 0, 1, ""}).IsAtLeast(2, 1, 0))
+ assert.False(t, (&GitVersion{2, 0, 1, ""}).IsAtLeast(3, 0, 0))
+}
diff --git a/pkg/integration/components/test.go b/pkg/integration/components/test.go
index 41c16ac0da5f..7a088c80adeb 100644
--- a/pkg/integration/components/test.go
+++ b/pkg/integration/components/test.go
@@ -100,7 +100,7 @@ func (self GitVersionRestriction) shouldRunOnVersion(version *git_commands.GitVe
if err != nil {
panic("Invalid git version string: " + self.from)
}
- return !version.IsOlderThanVersion(from)
+ return version.IsAtLeastVersion(from)
}
if self.before != "" {
before, err := git_commands.ParseGitVersion(self.before)
From 5b91cd0cc86a121f4ec96f9d5f7f35eb0ae810b9 Mon Sep 17 00:00:00 2001
From: Stefan Haller
Date: Tue, 2 Jan 2024 21:07:44 +0100
Subject: [PATCH 028/280] Extract a function fetchCommandBuilder
---
pkg/commands/git_commands/sync.go | 17 +++++++++--------
1 file changed, 9 insertions(+), 8 deletions(-)
diff --git a/pkg/commands/git_commands/sync.go b/pkg/commands/git_commands/sync.go
index fd7584aea774..d049deb078ff 100644
--- a/pkg/commands/git_commands/sync.go
+++ b/pkg/commands/git_commands/sync.go
@@ -49,10 +49,13 @@ func (self *SyncCommands) Push(task gocui.Task, opts PushOpts) error {
return cmdObj.Run()
}
+func (self *SyncCommands) fetchCommandBuilder(fetchAll bool) *GitCommandBuilder {
+ return NewGitCmd("fetch").
+ ArgIf(fetchAll, "--all")
+}
+
func (self *SyncCommands) FetchCmdObj(task gocui.Task) oscommands.ICmdObj {
- cmdArgs := NewGitCmd("fetch").
- ArgIf(self.UserConfig.Git.FetchAll, "--all").
- ToArgv()
+ cmdArgs := self.fetchCommandBuilder(self.UserConfig.Git.FetchAll).ToArgv()
cmdObj := self.cmd.New(cmdArgs)
cmdObj.PromptOnCredentialRequest(task)
@@ -64,9 +67,7 @@ func (self *SyncCommands) Fetch(task gocui.Task) error {
}
func (self *SyncCommands) FetchBackgroundCmdObj() oscommands.ICmdObj {
- cmdArgs := NewGitCmd("fetch").
- ArgIf(self.UserConfig.Git.FetchAll, "--all").
- ToArgv()
+ cmdArgs := self.fetchCommandBuilder(self.UserConfig.Git.FetchAll).ToArgv()
cmdObj := self.cmd.New(cmdArgs)
cmdObj.DontLog().FailOnCredentialRequest()
@@ -104,7 +105,7 @@ func (self *SyncCommands) FastForward(
remoteName string,
remoteBranchName string,
) error {
- cmdArgs := NewGitCmd("fetch").
+ cmdArgs := self.fetchCommandBuilder(false).
Arg(remoteName).
Arg(remoteBranchName + ":" + branchName).
ToArgv()
@@ -113,7 +114,7 @@ func (self *SyncCommands) FastForward(
}
func (self *SyncCommands) FetchRemote(task gocui.Task, remoteName string) error {
- cmdArgs := NewGitCmd("fetch").
+ cmdArgs := self.fetchCommandBuilder(false).
Arg(remoteName).
ToArgv()
From 76e39af76ff280a0bf184e9b7d4bf23577bfe902 Mon Sep 17 00:00:00 2001
From: Stefan Haller
Date: Tue, 2 Jan 2024 22:17:11 +0100
Subject: [PATCH 029/280] Allow multiple fetch commands (or fetch and pull) to
run concurrently
Git has a bug [1] whereby running multiple fetch commands at the same time
causes all of them to append their information to the .git/FETCH_HEAD file,
causing the next git pull that wants to use the information to become confused,
and show an error like "Cannot rebase onto multiple branches". This error would
occur when pressing "f" and "p" in quick succession in the files panel, but also
when pressing "p" while a background fetch happens to be running. One likely
situation for this is pressing "p" right after startup.
Since lazygit never uses the information written to .git/FETCH_HEAD, it's best
to avoid writing to it, which fixes the scenarios described above.
However, it doesn't fix the problem of repeatedly pressing "f" quickly on the
checked-out branch; since we call "git pull" in that case, the above fix doesn't
help there. We'll address this separately in another PR.
[1] See https://public-inbox.org/git/xmqqy1daffk8.fsf@gitster.g/ for more
information.
---
pkg/commands/git_commands/sync.go | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/pkg/commands/git_commands/sync.go b/pkg/commands/git_commands/sync.go
index d049deb078ff..4ab1f336bf09 100644
--- a/pkg/commands/git_commands/sync.go
+++ b/pkg/commands/git_commands/sync.go
@@ -51,7 +51,10 @@ func (self *SyncCommands) Push(task gocui.Task, opts PushOpts) error {
func (self *SyncCommands) fetchCommandBuilder(fetchAll bool) *GitCommandBuilder {
return NewGitCmd("fetch").
- ArgIf(fetchAll, "--all")
+ ArgIf(fetchAll, "--all").
+ // avoid writing to .git/FETCH_HEAD; this allows running a pull
+ // concurrently without getting errors
+ ArgIf(self.version.IsAtLeast(2, 29, 0), "--no-write-fetch-head")
}
func (self *SyncCommands) FetchCmdObj(task gocui.Task) oscommands.ICmdObj {
From b470442a4664fe8433b1c9a0263fb411a192888b Mon Sep 17 00:00:00 2001
From: Stefan Haller
Date: Thu, 21 Dec 2023 12:53:06 +0100
Subject: [PATCH 030/280] Obtain remote URL by calling "ls-remote --get-url"
instead of using git config
This has the advantage that it still works when the user has configured aliases
using the insteadOf feature [1].
[1] https://git-scm.com/docs/git-config/2.42.0#Documentation/git-config.txt-urlltbasegtinsteadOf)
---
pkg/commands/git_commands/remote.go | 12 +++++++++++
pkg/gui/controllers/helpers/host_helper.go | 24 +++++++++++++++-------
2 files changed, 29 insertions(+), 7 deletions(-)
diff --git a/pkg/commands/git_commands/remote.go b/pkg/commands/git_commands/remote.go
index acfb51dc9402..e2b3c6086d8e 100644
--- a/pkg/commands/git_commands/remote.go
+++ b/pkg/commands/git_commands/remote.go
@@ -2,6 +2,7 @@ package git_commands
import (
"fmt"
+ "strings"
"github.com/jesseduffield/gocui"
)
@@ -74,3 +75,14 @@ func (self *RemoteCommands) CheckRemoteBranchExists(branchName string) bool {
return err == nil
}
+
+// Resolve what might be a aliased URL into a full URL
+// SEE: `man -P 'less +/--get-url +n' git-ls-remote`
+func (self *RemoteCommands) GetRemoteURL(remoteName string) (string, error) {
+ cmdArgs := NewGitCmd("ls-remote").
+ Arg("--get-url", remoteName).
+ ToArgv()
+
+ url, err := self.cmd.New(cmdArgs).RunWithOutput()
+ return strings.TrimSpace(url), err
+}
diff --git a/pkg/gui/controllers/helpers/host_helper.go b/pkg/gui/controllers/helpers/host_helper.go
index 06102cc1382c..77fdd530e4e7 100644
--- a/pkg/gui/controllers/helpers/host_helper.go
+++ b/pkg/gui/controllers/helpers/host_helper.go
@@ -24,18 +24,28 @@ func NewHostHelper(
}
func (self *HostHelper) GetPullRequestURL(from string, to string) (string, error) {
- return self.getHostingServiceMgr().GetPullRequestURL(from, to)
+ mgr, err := self.getHostingServiceMgr()
+ if err != nil {
+ return "", err
+ }
+ return mgr.GetPullRequestURL(from, to)
}
func (self *HostHelper) GetCommitURL(commitSha string) (string, error) {
- return self.getHostingServiceMgr().GetCommitURL(commitSha)
+ mgr, err := self.getHostingServiceMgr()
+ if err != nil {
+ return "", err
+ }
+ return mgr.GetCommitURL(commitSha)
}
// getting this on every request rather than storing it in state in case our remoteURL changes
-// from one invocation to the next. Note however that we're currently caching config
-// results so we might want to invalidate the cache here if it becomes a problem.
-func (self *HostHelper) getHostingServiceMgr() *hosting_service.HostingServiceMgr {
- remoteUrl := self.c.Git().Config.GetRemoteURL()
+// from one invocation to the next.
+func (self *HostHelper) getHostingServiceMgr() (*hosting_service.HostingServiceMgr, error) {
+ remoteUrl, err := self.c.Git().Remote.GetRemoteURL("origin")
+ if err != nil {
+ return nil, err
+ }
configServices := self.c.UserConfig.Services
- return hosting_service.NewHostingServiceMgr(self.c.Log, self.c.Tr, remoteUrl, configServices)
+ return hosting_service.NewHostingServiceMgr(self.c.Log, self.c.Tr, remoteUrl, configServices), nil
}
From cb5d0bca1c1444720249c9957cfe420825277594 Mon Sep 17 00:00:00 2001
From: Stefan Haller
Date: Wed, 3 Jan 2024 20:29:25 +0100
Subject: [PATCH 031/280] Bump gocui
... and switch back from stefanhaller's tcell fork to the official tcell. This
basically reverts 7ccb871a459.
---
go.mod | 4 +-
go.sum | 14 +-
pkg/gui/gui_driver.go | 2 +-
.../tcell/v2/.gitignore | 0
.../tcell/v2/AUTHORS | 0
.../tcell/v2/CHANGESv2.md | 0
.../tcell/v2/LICENSE | 0
.../tcell/v2/README-wasm.md | 0
.../tcell/v2/README.md | 1 +
.../tcell/v2/TUTORIAL.md | 2 +-
.../tcell/v2/UKRAINE.md | 0
.../tcell/v2/attr.go | 0
.../tcell/v2/cell.go | 72 +++++--
.../tcell/v2/charset_stub.go | 0
.../tcell/v2/charset_unix.go | 0
.../tcell/v2/charset_windows.go | 0
.../tcell/v2/color.go | 49 ++++-
.../tcell/v2/colorfit.go | 0
.../tcell/v2/console_stub.go | 0
.../tcell/v2/console_win.go | 168 ++++++----------
.../{stefanhaller => gdamore}/tcell/v2/doc.go | 0
.../tcell/v2/encoding.go | 1 -
.../tcell/v2/errors.go | 2 +-
.../tcell/v2/event.go | 0
.../tcell/v2/focus.go | 0
.../tcell/v2/interrupt.go | 0
.../{stefanhaller => gdamore}/tcell/v2/key.go | 0
.../tcell/v2/mouse.go | 0
.../tcell/v2/nonblock_bsd.go | 0
.../tcell/v2/nonblock_unix.go | 0
.../tcell/v2/paste.go | 0
.../tcell/v2/resize.go | 34 +++-
.../tcell/v2/runes.go | 0
.../tcell/v2/screen.go | 173 +++++++++++++++-
.../tcell/v2/simulation.go | 120 +++---------
.../tcell/v2/stdin_unix.go | 16 +-
.../tcell/v2/style.go | 0
.../tcell/v2/terminfo/.gitignore | 0
.../tcell/v2/terminfo/README.md | 0
.../tcell/v2/terminfo/TERMINALS.md | 0
.../tcell/v2/terminfo/a/aixterm/term.go | 2 +-
.../tcell/v2/terminfo/a/alacritty/direct.go | 2 +-
.../tcell/v2/terminfo/a/alacritty/term.go | 2 +-
.../tcell/v2/terminfo/a/ansi/term.go | 2 +-
.../tcell/v2/terminfo/b/beterm/term.go | 2 +-
.../tcell/v2/terminfo/base/base.go | 10 +-
.../tcell/v2/terminfo/c/cygwin/term.go | 2 +-
.../tcell/v2/terminfo/d/dtterm/term.go | 2 +-
.../tcell/v2/terminfo/dynamic/dynamic.go | 12 +-
.../tcell/v2/terminfo/e/emacs/term.go | 2 +-
.../tcell/v2/terminfo/extended/extended.go | 58 ++++++
.../tcell/v2/terminfo/f/foot/foot.go | 2 +-
.../tcell/v2/terminfo/g/gnome/term.go | 2 +-
.../tcell/v2/terminfo/gen.sh | 1 +
.../tcell/v2/terminfo/h/hpterm/term.go | 2 +-
.../tcell/v2/terminfo/k/konsole/term.go | 2 +-
.../tcell/v2/terminfo/k/kterm/term.go | 2 +-
.../tcell/v2/terminfo/l/linux/term.go | 2 +-
.../tcell/v2/terminfo/models.txt | 0
.../tcell/v2/terminfo/p/pcansi/term.go | 2 +-
.../tcell/v2/terminfo/r/rxvt/term.go | 2 +-
.../tcell/v2/terminfo/s/screen/term.go | 2 +-
.../tcell/v2/terminfo/s/simpleterm/term.go | 2 +-
.../tcell/v2/terminfo/s/sun/term.go | 2 +-
.../tcell/v2/terminfo/t/termite/term.go | 2 +-
.../tcell/v2/terminfo/t/tmux/term.go | 2 +-
.../tcell/v2/terminfo/terminfo.go | 0
.../tcell/v2/terminfo/v/vt100/term.go | 2 +-
.../tcell/v2/terminfo/v/vt102/term.go | 2 +-
.../tcell/v2/terminfo/v/vt220/term.go | 2 +-
.../tcell/v2/terminfo/v/vt320/term.go | 2 +-
.../tcell/v2/terminfo/v/vt400/term.go | 2 +-
.../tcell/v2/terminfo/v/vt420/term.go | 2 +-
.../tcell/v2/terminfo/v/vt52/term.go | 2 +-
.../tcell/v2/terminfo/w/wy50/term.go | 2 +-
.../tcell/v2/terminfo/w/wy60/term.go | 2 +-
.../tcell/v2/terminfo/w/wy99_ansi/term.go | 2 +-
.../tcell/v2/terminfo/x/xfce/term.go | 2 +-
.../tcell/v2/terminfo/x/xterm/direct.go | 2 +-
.../tcell/v2/terminfo/x/xterm/term.go | 2 +-
.../tcell/v2/terminfo/x/xterm_kitty/term.go | 2 +-
.../tcell/v2/terminfo/x/xterm_termite/term.go | 2 +-
.../tcell/v2/terms_default.go | 2 +-
.../tcell/v2/terms_dynamic.go | 4 +-
.../tcell/v2/terms_static.go | 2 +-
.../tcell/v2/tscreen.go | 164 ++++++----------
.../tcell/v2/tscreen_stub.go | 0
.../tcell/v2/tscreen_unix.go | 0
.../{stefanhaller => gdamore}/tcell/v2/tty.go | 2 +-
.../tcell/v2/tty_unix.go | 16 +-
.../tcell/v2/wscreen.go | 185 ++++++++----------
.../jesseduffield/gocui/attribute.go | 2 +-
vendor/github.com/jesseduffield/gocui/gui.go | 2 +-
.../jesseduffield/gocui/keybinding.go | 2 +-
.../jesseduffield/gocui/tcell_driver.go | 2 +-
vendor/github.com/jesseduffield/gocui/view.go | 2 +-
.../tcell/v2/terminfo/extended/extended.go | 58 ------
vendor/modules.txt | 84 ++++----
98 files changed, 724 insertions(+), 614 deletions(-)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/.gitignore (100%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/AUTHORS (100%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/CHANGESv2.md (100%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/LICENSE (100%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/README-wasm.md (100%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/README.md (98%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/TUTORIAL.md (99%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/UKRAINE.md (100%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/attr.go (100%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/cell.go (72%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/charset_stub.go (100%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/charset_unix.go (100%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/charset_windows.go (100%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/color.go (96%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/colorfit.go (100%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/console_stub.go (100%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/console_win.go (91%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/doc.go (100%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/encoding.go (99%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/errors.go (98%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/event.go (100%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/focus.go (100%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/interrupt.go (100%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/key.go (100%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/mouse.go (100%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/nonblock_bsd.go (100%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/nonblock_unix.go (100%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/paste.go (100%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/resize.go (59%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/runes.go (100%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/screen.go (75%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/simulation.go (86%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/stdin_unix.go (92%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/style.go (100%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terminfo/.gitignore (100%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terminfo/README.md (100%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terminfo/TERMINALS.md (100%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terminfo/a/aixterm/term.go (97%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terminfo/a/alacritty/direct.go (97%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terminfo/a/alacritty/term.go (97%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terminfo/a/ansi/term.go (95%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terminfo/b/beterm/term.go (96%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terminfo/base/base.go (80%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terminfo/c/cygwin/term.go (97%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terminfo/d/dtterm/term.go (97%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terminfo/dynamic/dynamic.go (98%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terminfo/e/emacs/term.go (96%)
create mode 100644 vendor/github.com/gdamore/tcell/v2/terminfo/extended/extended.go
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terminfo/f/foot/foot.go (97%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terminfo/g/gnome/term.go (98%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terminfo/gen.sh (97%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terminfo/h/hpterm/term.go (95%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terminfo/k/konsole/term.go (98%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terminfo/k/kterm/term.go (97%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terminfo/l/linux/term.go (97%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terminfo/models.txt (100%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terminfo/p/pcansi/term.go (95%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terminfo/r/rxvt/term.go (99%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terminfo/s/screen/term.go (98%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terminfo/s/simpleterm/term.go (98%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terminfo/s/sun/term.go (98%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terminfo/t/termite/term.go (97%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terminfo/t/tmux/term.go (98%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terminfo/terminfo.go (100%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terminfo/v/vt100/term.go (95%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terminfo/v/vt102/term.go (95%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terminfo/v/vt220/term.go (96%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terminfo/v/vt320/term.go (96%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terminfo/v/vt400/term.go (95%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terminfo/v/vt420/term.go (96%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terminfo/v/vt52/term.go (92%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terminfo/w/wy50/term.go (96%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terminfo/w/wy60/term.go (96%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terminfo/w/wy99_ansi/term.go (98%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terminfo/x/xfce/term.go (97%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terminfo/x/xterm/direct.go (98%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terminfo/x/xterm/term.go (99%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terminfo/x/xterm_kitty/term.go (97%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terminfo/x/xterm_termite/term.go (97%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terms_default.go (93%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terms_dynamic.go (93%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/terms_static.go (95%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/tscreen.go (95%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/tscreen_stub.go (100%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/tscreen_unix.go (100%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/tty.go (98%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/tty_unix.go (92%)
rename vendor/github.com/{stefanhaller => gdamore}/tcell/v2/wscreen.go (86%)
delete mode 100644 vendor/github.com/stefanhaller/tcell/v2/terminfo/extended/extended.go
diff --git a/go.mod b/go.mod
index ecf0e3cfd087..0d709c3701cd 100644
--- a/go.mod
+++ b/go.mod
@@ -9,13 +9,14 @@ require (
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21
github.com/creack/pty v1.1.11
github.com/fsmiamoto/git-todo-parser v0.0.5
+ github.com/gdamore/tcell/v2 v2.7.1-0.20240103180601-96e29905643b
github.com/go-errors/errors v1.5.1
github.com/gookit/color v1.4.2
github.com/imdario/mergo v0.3.11
github.com/integrii/flaggy v1.4.0
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d
- github.com/jesseduffield/gocui v0.3.1-0.20231209142059-968d8b62e1ef
+ github.com/jesseduffield/gocui v0.3.1-0.20240103192639-2874168c14db
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e
@@ -34,7 +35,6 @@ require (
github.com/sirupsen/logrus v1.4.2
github.com/spf13/afero v1.9.5
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad
- github.com/stefanhaller/tcell/v2 v2.6.2-0.20230806061358-2dfa11eddb68
github.com/stretchr/testify v1.8.1
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778
golang.org/x/exp v0.0.0-20220318154914-8dddf5d87bd8
diff --git a/go.sum b/go.sum
index d1257c029842..1762cf6058a2 100644
--- a/go.sum
+++ b/go.sum
@@ -89,6 +89,8 @@ github.com/fsmiamoto/git-todo-parser v0.0.5/go.mod h1:B+AgTbNE2BARvJqzXygThzqxLI
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
+github.com/gdamore/tcell/v2 v2.7.1-0.20240103180601-96e29905643b h1:VkiXff8uJUkhjcxcLwwzQLYBCc+k5tJeOVx4W0qMYTk=
+github.com/gdamore/tcell/v2 v2.7.1-0.20240103180601-96e29905643b/go.mod h1:hl/KtAANGBecfIPxk+FzKvThTqI84oplgbPEmVX60b8=
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
@@ -185,8 +187,8 @@ github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 h1:EQP2Tv8T
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68/go.mod h1:+LLj9/WUPAP8LqCchs7P+7X0R98HiFujVFANdNaxhGk=
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d h1:bO+OmbreIv91rCe8NmscRwhFSqkDJtzWCPV4Y+SQuXE=
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o=
-github.com/jesseduffield/gocui v0.3.1-0.20231209142059-968d8b62e1ef h1:pMi1UJqTE1j4NRxAeQ835qv+tj364zCocQ+/r9ZLrM0=
-github.com/jesseduffield/gocui v0.3.1-0.20231209142059-968d8b62e1ef/go.mod h1:trXE7RRGL2hTsv+Ntk+SHLtRobg9JE138n3Ug/X2Cf4=
+github.com/jesseduffield/gocui v0.3.1-0.20240103192639-2874168c14db h1:ihJdYk85/XQLGiG3b6m8P2z+RUohRMtPmX74YR9IT8s=
+github.com/jesseduffield/gocui v0.3.1-0.20240103192639-2874168c14db/go.mod h1:9zkyjnUmdL3+sUknJrQy/3HweUu8mVln/3J2wRF/l8M=
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 h1:jmpr7KpX2+2GRiE91zTgfq49QvgiqB0nbmlwZ8UnOx0=
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10/go.mod h1:aA97kHeNA+sj2Hbki0pvLslmE4CbDyhBeSSTUUnOuVo=
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 h1:CDuQmfOjAtb1Gms6a1p5L2P8RhbLUq5t8aL7PiQd2uY=
@@ -234,7 +236,6 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
-github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mgutz/str v1.2.0 h1:4IzWSdIz9qPQWLfKZ0rJcV0jcUDpxvP4JVZ4GXQyvSw=
@@ -281,8 +282,6 @@ github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad h1:fiWzISvDn0Csy5H0iwgAuJGQTUpVfEMJJd4nRFXogbc=
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
-github.com/stefanhaller/tcell/v2 v2.6.2-0.20230806061358-2dfa11eddb68 h1:NSTj9xAKUu85d6pAdNFyblL84BfiOB1rVnzxQO/cYUk=
-github.com/stefanhaller/tcell/v2 v2.6.2-0.20230806061358-2dfa11eddb68/go.mod h1:PuJ7T6QKbsU7iVOHlhRoV3D/ipIAJsyiV4dbwcVaYj8=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
@@ -364,6 +363,7 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -398,6 +398,7 @@ golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -420,6 +421,7 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20170407050850-f3918c30c5c2/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -467,7 +469,6 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@@ -537,6 +538,7 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/pkg/gui/gui_driver.go b/pkg/gui/gui_driver.go
index 7fd51c952f80..c36e68e93965 100644
--- a/pkg/gui/gui_driver.go
+++ b/pkg/gui/gui_driver.go
@@ -6,13 +6,13 @@ import (
"strings"
"time"
+ "github.com/gdamore/tcell/v2"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/types"
integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types"
- "github.com/stefanhaller/tcell/v2"
)
// this gives our integration test a way of interacting with the gui for sending keypresses
diff --git a/vendor/github.com/stefanhaller/tcell/v2/.gitignore b/vendor/github.com/gdamore/tcell/v2/.gitignore
similarity index 100%
rename from vendor/github.com/stefanhaller/tcell/v2/.gitignore
rename to vendor/github.com/gdamore/tcell/v2/.gitignore
diff --git a/vendor/github.com/stefanhaller/tcell/v2/AUTHORS b/vendor/github.com/gdamore/tcell/v2/AUTHORS
similarity index 100%
rename from vendor/github.com/stefanhaller/tcell/v2/AUTHORS
rename to vendor/github.com/gdamore/tcell/v2/AUTHORS
diff --git a/vendor/github.com/stefanhaller/tcell/v2/CHANGESv2.md b/vendor/github.com/gdamore/tcell/v2/CHANGESv2.md
similarity index 100%
rename from vendor/github.com/stefanhaller/tcell/v2/CHANGESv2.md
rename to vendor/github.com/gdamore/tcell/v2/CHANGESv2.md
diff --git a/vendor/github.com/stefanhaller/tcell/v2/LICENSE b/vendor/github.com/gdamore/tcell/v2/LICENSE
similarity index 100%
rename from vendor/github.com/stefanhaller/tcell/v2/LICENSE
rename to vendor/github.com/gdamore/tcell/v2/LICENSE
diff --git a/vendor/github.com/stefanhaller/tcell/v2/README-wasm.md b/vendor/github.com/gdamore/tcell/v2/README-wasm.md
similarity index 100%
rename from vendor/github.com/stefanhaller/tcell/v2/README-wasm.md
rename to vendor/github.com/gdamore/tcell/v2/README-wasm.md
diff --git a/vendor/github.com/stefanhaller/tcell/v2/README.md b/vendor/github.com/gdamore/tcell/v2/README.md
similarity index 98%
rename from vendor/github.com/stefanhaller/tcell/v2/README.md
rename to vendor/github.com/gdamore/tcell/v2/README.md
index 8ca95eda9d1a..37c7dea3c062 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/README.md
+++ b/vendor/github.com/gdamore/tcell/v2/README.md
@@ -66,6 +66,7 @@ A brief, and still somewhat rough, [tutorial](TUTORIAL.md) is available.
- [gosnakego](https://github.com/liweiyi88/gosnakego) - a snake game
- [gbb](https://github.com/sdemingo/gbb) - A classical bulletin board app for tildes or public unix servers
- [lil](https://github.com/andrievsky/lil) - A simple and flexible interface for any service by implementing only list and get operations
+- [hero.go](https://github.com/barisbll/hero.go) - 2d monster shooter ([video](https://user-images.githubusercontent.com/40062673/277157369-240d7606-b471-4aa1-8c54-4379a513122b.mp4))
## Pure Go Terminfo Database
diff --git a/vendor/github.com/stefanhaller/tcell/v2/TUTORIAL.md b/vendor/github.com/gdamore/tcell/v2/TUTORIAL.md
similarity index 99%
rename from vendor/github.com/stefanhaller/tcell/v2/TUTORIAL.md
rename to vendor/github.com/gdamore/tcell/v2/TUTORIAL.md
index 2e6f9bc2728d..f52fcff0d402 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/TUTORIAL.md
+++ b/vendor/github.com/gdamore/tcell/v2/TUTORIAL.md
@@ -172,7 +172,7 @@ import (
"fmt"
"log"
- "github.com/stefanhaller/tcell/v2"
+ "github.com/gdamore/tcell/v2"
)
func drawText(s tcell.Screen, x1, y1, x2, y2 int, style tcell.Style, text string) {
diff --git a/vendor/github.com/stefanhaller/tcell/v2/UKRAINE.md b/vendor/github.com/gdamore/tcell/v2/UKRAINE.md
similarity index 100%
rename from vendor/github.com/stefanhaller/tcell/v2/UKRAINE.md
rename to vendor/github.com/gdamore/tcell/v2/UKRAINE.md
diff --git a/vendor/github.com/stefanhaller/tcell/v2/attr.go b/vendor/github.com/gdamore/tcell/v2/attr.go
similarity index 100%
rename from vendor/github.com/stefanhaller/tcell/v2/attr.go
rename to vendor/github.com/gdamore/tcell/v2/attr.go
diff --git a/vendor/github.com/stefanhaller/tcell/v2/cell.go b/vendor/github.com/gdamore/tcell/v2/cell.go
similarity index 72%
rename from vendor/github.com/stefanhaller/tcell/v2/cell.go
rename to vendor/github.com/gdamore/tcell/v2/cell.go
index 756a5068ddf6..f01b113ddbe8 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/cell.go
+++ b/vendor/github.com/gdamore/tcell/v2/cell.go
@@ -1,4 +1,4 @@
-// Copyright 2022 The TCell Authors
+// Copyright 2023 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use file except in compliance with the License.
@@ -28,9 +28,10 @@ type cell struct {
lastStyle Style
lastComb []rune
width int
+ lock bool
}
-// CellBuffer represents a two dimensional array of character cells.
+// CellBuffer represents a two-dimensional array of character cells.
// This is primarily intended for use by Screen implementors; it
// contains much of the common code they need. To create one, just
// declare a variable of its type; no explicit initialization is necessary.
@@ -43,10 +44,12 @@ type CellBuffer struct {
}
// SetContent sets the contents (primary rune, combining runes,
-// and style) for a cell at a given location.
+// and style) for a cell at a given location. If the background or
+// foreground of the style is set to ColorNone, then the respective
+// color is left un changed.
func (cb *CellBuffer) SetContent(x int, y int,
- mainc rune, combc []rune, style Style) {
-
+ mainc rune, combc []rune, style Style,
+) {
if x >= 0 && y >= 0 && x < cb.w && y < cb.h {
c := &cb.cells[(y*cb.w)+x]
@@ -60,6 +63,12 @@ func (cb *CellBuffer) SetContent(x int, y int,
c.width = runewidth.RuneWidth(mainc)
}
c.currMain = mainc
+ if style.fg == ColorNone {
+ style.fg = c.currStyle.fg
+ }
+ if style.bg == ColorNone {
+ style.bg = c.currStyle.bg
+ }
c.currStyle = style
}
}
@@ -96,13 +105,15 @@ func (cb *CellBuffer) Invalidate() {
}
}
-// Dirty checks if a character at the given location needs an
-// to be refreshed on the physical display. This returns true
-// if the cell content is different since the last time it was
-// marked clean.
+// Dirty checks if a character at the given location needs to be
+// refreshed on the physical display. This returns true if the cell
+// content is different since the last time it was marked clean.
func (cb *CellBuffer) Dirty(x, y int) bool {
if x >= 0 && y >= 0 && x < cb.w && y < cb.h {
c := &cb.cells[(y*cb.w)+x]
+ if c.lock {
+ return false
+ }
if c.lastMain == rune(0) {
return true
}
@@ -143,11 +154,39 @@ func (cb *CellBuffer) SetDirty(x, y int, dirty bool) {
}
}
+// LockCell locks a cell from being drawn, effectively marking it "clean" until
+// the lock is removed. This can be used to prevent tcell from drawing a given
+// cell, even if the underlying content has changed. For example, when drawing a
+// sixel graphic directly to a TTY screen an implementer must lock the region
+// underneath the graphic to prevent tcell from drawing on top of the graphic.
+func (cb *CellBuffer) LockCell(x, y int) {
+ if x < 0 || y < 0 {
+ return
+ }
+ if x >= cb.w || y >= cb.h {
+ return
+ }
+ c := &cb.cells[(y*cb.w)+x]
+ c.lock = true
+}
+
+// UnlockCell removes a lock from the cell and marks it as dirty
+func (cb *CellBuffer) UnlockCell(x, y int) {
+ if x < 0 || y < 0 {
+ return
+ }
+ if x >= cb.w || y >= cb.h {
+ return
+ }
+ c := &cb.cells[(y*cb.w)+x]
+ c.lock = false
+ cb.SetDirty(x, y, true)
+}
+
// Resize is used to resize the cells array, with different dimensions,
// while preserving the original contents. The cells will be invalidated
// so that they can be redrawn.
func (cb *CellBuffer) Resize(w, h int) {
-
if cb.h == h && cb.w == w {
return
}
@@ -172,12 +211,21 @@ func (cb *CellBuffer) Resize(w, h int) {
// Fill fills the entire cell buffer array with the specified character
// and style. Normally choose ' ' to clear the screen. This API doesn't
// support combining characters, or characters with a width larger than one.
+// If either the foreground or background are ColorNone, then the respective
+// color is unchanged.
func (cb *CellBuffer) Fill(r rune, style Style) {
for i := range cb.cells {
c := &cb.cells[i]
c.currMain = r
c.currComb = nil
- c.currStyle = style
+ cs := style
+ if cs.fg == ColorNone {
+ cs.fg = c.currStyle.fg
+ }
+ if cs.bg == ColorNone {
+ cs.bg = c.currStyle.bg
+ }
+ c.currStyle = cs
c.width = 1
}
}
@@ -192,7 +240,7 @@ func init() {
runewidth.DefaultCondition.EastAsianWidth = false
}
- // For performance reasons, we create a lookup table. However some users
+ // For performance reasons, we create a lookup table. However, some users
// might be more memory conscious. If that's you, set the TCELL_MINIMIZE
// environment variable.
if os.Getenv("TCELL_MINIMIZE") == "" {
diff --git a/vendor/github.com/stefanhaller/tcell/v2/charset_stub.go b/vendor/github.com/gdamore/tcell/v2/charset_stub.go
similarity index 100%
rename from vendor/github.com/stefanhaller/tcell/v2/charset_stub.go
rename to vendor/github.com/gdamore/tcell/v2/charset_stub.go
diff --git a/vendor/github.com/stefanhaller/tcell/v2/charset_unix.go b/vendor/github.com/gdamore/tcell/v2/charset_unix.go
similarity index 100%
rename from vendor/github.com/stefanhaller/tcell/v2/charset_unix.go
rename to vendor/github.com/gdamore/tcell/v2/charset_unix.go
diff --git a/vendor/github.com/stefanhaller/tcell/v2/charset_windows.go b/vendor/github.com/gdamore/tcell/v2/charset_windows.go
similarity index 100%
rename from vendor/github.com/stefanhaller/tcell/v2/charset_windows.go
rename to vendor/github.com/gdamore/tcell/v2/charset_windows.go
diff --git a/vendor/github.com/stefanhaller/tcell/v2/color.go b/vendor/github.com/gdamore/tcell/v2/color.go
similarity index 96%
rename from vendor/github.com/stefanhaller/tcell/v2/color.go
rename to vendor/github.com/gdamore/tcell/v2/color.go
index e6581b0f6301..face860fbee9 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/color.go
+++ b/vendor/github.com/gdamore/tcell/v2/color.go
@@ -1,4 +1,4 @@
-// Copyright 2020 The TCell Authors
+// Copyright 2023 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use file except in compliance with the License.
@@ -15,6 +15,7 @@
package tcell
import (
+ "fmt"
ic "image/color"
"strconv"
)
@@ -838,6 +839,11 @@ const (
// ColorReset is used to indicate that the color should use the
// vanilla terminal colors. (Basically go back to the defaults.)
ColorReset = ColorSpecial | iota
+
+ // ColorNone indicates that we should not change the color from
+ // whatever is already displayed. This can only be used in limited
+ // circumstances.
+ ColorNone
)
// ColorNames holds the written names of colors. Useful to present a list of
@@ -1001,6 +1007,47 @@ func (c Color) IsRGB() bool {
return c&(ColorValid|ColorIsRGB) == (ColorValid | ColorIsRGB)
}
+// CSS returns the CSS hex string ( #ABCDEF ) if valid
+// if not a valid color returns empty string
+func (c Color) CSS() string {
+ if !c.Valid() {
+ return ""
+ }
+ return fmt.Sprintf("#%06X", c.Hex())
+}
+
+// String implements fmt.Stringer to return either the
+// W3C name if it has one or the CSS hex string '#ABCDEF'
+func (c Color) String() string {
+ if !c.Valid() {
+ switch c {
+ case ColorNone:
+ return "none"
+ case ColorDefault:
+ return "default"
+ case ColorReset:
+ return "reset"
+ }
+ return ""
+ }
+ return c.Name(true)
+}
+
+// Name returns W3C name or an empty string if no arguments
+// if passed true as an argument it will falls back to
+// the CSS hex string if no W3C name found '#ABCDEF'
+func (c Color) Name(css ...bool) string {
+ for name, hex := range ColorNames {
+ if c == hex {
+ return name
+ }
+ }
+ if len(css) > 0 && css[0] {
+ return c.CSS()
+ }
+ return ""
+}
+
// Hex returns the color's hexadecimal RGB 24-bit value with each component
// consisting of a single byte, R << 16 | G << 8 | B. If the color
// is unknown or unset, -1 is returned.
diff --git a/vendor/github.com/stefanhaller/tcell/v2/colorfit.go b/vendor/github.com/gdamore/tcell/v2/colorfit.go
similarity index 100%
rename from vendor/github.com/stefanhaller/tcell/v2/colorfit.go
rename to vendor/github.com/gdamore/tcell/v2/colorfit.go
diff --git a/vendor/github.com/stefanhaller/tcell/v2/console_stub.go b/vendor/github.com/gdamore/tcell/v2/console_stub.go
similarity index 100%
rename from vendor/github.com/stefanhaller/tcell/v2/console_stub.go
rename to vendor/github.com/gdamore/tcell/v2/console_stub.go
diff --git a/vendor/github.com/stefanhaller/tcell/v2/console_win.go b/vendor/github.com/gdamore/tcell/v2/console_win.go
similarity index 91%
rename from vendor/github.com/stefanhaller/tcell/v2/console_win.go
rename to vendor/github.com/gdamore/tcell/v2/console_win.go
index 01fa1f7bfa2e..ffa0049070c1 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/console_win.go
+++ b/vendor/github.com/gdamore/tcell/v2/console_win.go
@@ -1,7 +1,7 @@
//go:build windows
// +build windows
-// Copyright 2022 The TCell Authors
+// Copyright 2023 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use file except in compliance with the License.
@@ -33,12 +33,10 @@ type cScreen struct {
out syscall.Handle
cancelflag syscall.Handle
scandone chan struct{}
- evch chan Event
quit chan struct{}
curx int
cury int
style Style
- clear bool
fini bool
vten bool
truecolor bool
@@ -54,11 +52,11 @@ type cScreen struct {
oomode uint32
cells CellBuffer
- finiOnce sync.Once
-
mouseEnabled bool
wg sync.WaitGroup
+ eventQ chan Event
stopQ chan struct{}
+ finiOnce sync.Once
sync.Mutex
}
@@ -147,7 +145,7 @@ const (
vtSgr0 = "\x1b[0m"
vtBold = "\x1b[1m"
vtUnderline = "\x1b[4m"
- vtBlink = "\x1b[5m" // Not sure this is processed
+ vtBlink = "\x1b[5m" // Not sure if this is processed
vtReverse = "\x1b[7m"
vtSetFg = "\x1b[38;5;%dm"
vtSetBg = "\x1b[48;5;%dm"
@@ -176,11 +174,11 @@ var vtCursorStyles = map[CursorStyle]string{
// with the current process. The Screen makes use of the Windows Console
// API to display content and read events.
func NewConsoleScreen() (Screen, error) {
- return &cScreen{}, nil
+ return &baseScreen{screenImpl: &cScreen{}}, nil
}
func (s *cScreen) Init() error {
- s.evch = make(chan Event, 10)
+ s.eventQ = make(chan Event, 10)
s.quit = make(chan struct{})
s.scandone = make(chan struct{})
@@ -231,7 +229,7 @@ func (s *cScreen) Init() error {
// 24-bit color is opt-in for now, because we can't figure out
// to make it work consistently.
if s.truecolor {
- s.setOutMode(modeVtOutput | modeNoAutoNL | modeCookedOut)
+ s.setOutMode(modeVtOutput | modeNoAutoNL | modeCookedOut | modeUnderline)
var om uint32
s.getOutMode(&om)
if om&modeVtOutput == modeVtOutput {
@@ -287,7 +285,10 @@ func (s *cScreen) EnableFocus() {}
func (s *cScreen) DisableFocus() {}
func (s *cScreen) Fini() {
- s.disengage()
+ s.finiOnce.Do(func() {
+ close(s.quit)
+ s.disengage()
+ })
}
func (s *cScreen) disengage() {
@@ -338,7 +339,7 @@ func (s *cScreen) engage() error {
s.enableMouse(s.mouseEnabled)
if s.vten {
- s.setOutMode(modeVtOutput | modeNoAutoNL | modeCookedOut)
+ s.setOutMode(modeVtOutput | modeNoAutoNL | modeCookedOut | modeUnderline)
} else {
s.setOutMode(0)
}
@@ -357,52 +358,6 @@ func (s *cScreen) engage() error {
return nil
}
-func (s *cScreen) PostEventWait(ev Event) {
- s.evch <- ev
-}
-
-func (s *cScreen) PostEvent(ev Event) error {
- select {
- case s.evch <- ev:
- return nil
- default:
- return ErrEventQFull
- }
-}
-
-func (s *cScreen) ChannelEvents(ch chan<- Event, quit <-chan struct{}) {
- defer close(ch)
- for {
- select {
- case <-quit:
- return
- case <-s.stopQ:
- return
- case ev := <-s.evch:
- select {
- case <-quit:
- return
- case <-s.stopQ:
- return
- case ch <- ev:
- }
- }
- }
-}
-
-func (s *cScreen) PollEvent() Event {
- select {
- case <-s.stopQ:
- return nil
- case ev := <-s.evch:
- return ev
- }
-}
-
-func (s *cScreen) HasPendingEvent() bool {
- return len(s.evch) > 0
-}
-
type cursorInfo struct {
size uint32
visible uint32
@@ -644,12 +599,17 @@ func geti16(v []byte) int16 {
func mod2mask(cks uint32) ModMask {
mm := ModNone
// Left or right control
- if (cks & (0x0008 | 0x0004)) != 0 {
- mm |= ModCtrl
- }
+ ctrl := (cks & (0x0008 | 0x0004)) != 0
// Left or right alt
- if (cks & (0x0002 | 0x0001)) != 0 {
- mm |= ModAlt
+ alt := (cks & (0x0002 | 0x0001)) != 0
+ // Filter out ctrl+alt (it means AltGr)
+ if !(ctrl && alt) {
+ if ctrl {
+ mm |= ModCtrl
+ }
+ if alt {
+ mm |= ModAlt
+ }
}
// Any shift
if (cks & 0x0010) != 0 {
@@ -702,6 +662,13 @@ func mrec2btns(mbtns, flags uint32) ButtonMask {
return btns
}
+func (s *cScreen) postEvent(ev Event) {
+ select {
+ case s.eventQ <- ev:
+ case <-s.quit:
+ }
+}
+
func (s *cScreen) getConsoleInput() error {
// cancelFlag comes first as WaitForMultipleObjects returns the lowest index
// in the event that both events are signalled.
@@ -744,7 +711,7 @@ func (s *cScreen) getConsoleInput() error {
krec.mod = getu32(rec.data[12:])
if krec.isdown == 0 || krec.repeat < 1 {
- // its a key release event, ignore it
+ // it's a key release event, ignore it
return nil
}
if krec.ch != 0 {
@@ -752,11 +719,9 @@ func (s *cScreen) getConsoleInput() error {
for krec.repeat > 0 {
// convert shift+tab to backtab
if mod2mask(krec.mod) == ModShift && krec.ch == vkTab {
- s.PostEventWait(NewEventKey(KeyBacktab, 0,
- ModNone))
+ s.postEvent(NewEventKey(KeyBacktab, 0, ModNone))
} else {
- s.PostEventWait(NewEventKey(KeyRune, rune(krec.ch),
- mod2mask(krec.mod)))
+ s.postEvent(NewEventKey(KeyRune, rune(krec.ch), mod2mask(krec.mod)))
}
krec.repeat--
}
@@ -768,8 +733,7 @@ func (s *cScreen) getConsoleInput() error {
return nil
}
for krec.repeat > 0 {
- s.PostEventWait(NewEventKey(key, rune(krec.ch),
- mod2mask(krec.mod)))
+ s.postEvent(NewEventKey(key, rune(krec.ch), mod2mask(krec.mod)))
krec.repeat--
}
@@ -782,14 +746,13 @@ func (s *cScreen) getConsoleInput() error {
mrec.flags = getu32(rec.data[12:])
btns := mrec2btns(mrec.btns, mrec.flags)
// we ignore double click, events are delivered normally
- s.PostEventWait(NewEventMouse(int(mrec.x), int(mrec.y), btns,
- mod2mask(mrec.mod)))
+ s.postEvent(NewEventMouse(int(mrec.x), int(mrec.y), btns, mod2mask(mrec.mod)))
case resizeEvent:
var rrec resizeRecord
rrec.x = geti16(rec.data[0:])
rrec.y = geti16(rec.data[2:])
- s.PostEventWait(NewEventResize(int(rrec.x), int(rrec.y)))
+ s.postEvent(NewEventResize(int(rrec.x), int(rrec.y)))
default:
}
@@ -895,29 +858,6 @@ func (s *cScreen) mapStyle(style Style) uint16 {
return attr
}
-func (s *cScreen) SetCell(x, y int, style Style, ch ...rune) {
- if len(ch) > 0 {
- s.SetContent(x, y, ch[0], ch[1:], style)
- } else {
- s.SetContent(x, y, ' ', nil, style)
- }
-}
-
-func (s *cScreen) SetContent(x, y int, primary rune, combining []rune, style Style) {
- s.Lock()
- if !s.fini {
- s.cells.SetContent(x, y, primary, combining, style)
- }
- s.Unlock()
-}
-
-func (s *cScreen) GetContent(x, y int) (rune, []rune, Style, int) {
- s.Lock()
- primary, combining, style, width := s.cells.GetContent(x, y)
- s.Unlock()
- return primary, combining, style, width
-}
-
func (s *cScreen) sendVtStyle(style Style) {
esc := &strings.Builder{}
@@ -972,11 +912,6 @@ func (s *cScreen) writeString(x, y int, style Style, ch []uint16) {
func (s *cScreen) draw() {
// allocate a scratch line bit enough for no combining chars.
// if you have combining characters, you may pay for extra allocations.
- if s.clear {
- s.clearScreen(s.style, s.vten)
- s.clear = false
- s.cells.Invalidate()
- }
buf := make([]uint16, 0, s.w)
wcs := buf[:]
lstyle := styleInvalid
@@ -1157,20 +1092,10 @@ func (s *cScreen) resize() {
uintptr(s.out),
uintptr(1),
uintptr(unsafe.Pointer(&r)))
- _ = s.PostEvent(NewEventResize(w, h))
-}
-
-func (s *cScreen) Clear() {
- s.Fill(' ', s.style)
-}
-
-func (s *cScreen) Fill(r rune, style Style) {
- s.Lock()
- if !s.fini {
- s.cells.Fill(r, style)
- s.clear = true
+ select {
+ case s.eventQ <- NewEventResize(w, h):
+ default:
}
- s.Unlock()
}
func (s *cScreen) clearScreen(style Style, vtEnable bool) {
@@ -1217,6 +1142,7 @@ const (
modeCookedOut uint32 = 0x0001
modeVtOutput = 0x0004
modeNoAutoNL = 0x0008
+ modeUnderline = 0x0010 // ENABLE_LVB_GRID_WORLDWIDE, needed for underlines
// modeWrapEOL = 0x0002
)
@@ -1331,3 +1257,19 @@ func (s *cScreen) Suspend() error {
func (s *cScreen) Resume() error {
return s.engage()
}
+
+func (s *cScreen) Tty() (Tty, bool) {
+ return nil, false
+}
+
+func (s *cScreen) GetCells() *CellBuffer {
+ return &s.cells
+}
+
+func (s *cScreen) EventQ() chan Event {
+ return s.eventQ
+}
+
+func (s *cScreen) StopQ() <-chan struct{} {
+ return s.stopQ
+}
diff --git a/vendor/github.com/stefanhaller/tcell/v2/doc.go b/vendor/github.com/gdamore/tcell/v2/doc.go
similarity index 100%
rename from vendor/github.com/stefanhaller/tcell/v2/doc.go
rename to vendor/github.com/gdamore/tcell/v2/doc.go
diff --git a/vendor/github.com/stefanhaller/tcell/v2/encoding.go b/vendor/github.com/gdamore/tcell/v2/encoding.go
similarity index 99%
rename from vendor/github.com/stefanhaller/tcell/v2/encoding.go
rename to vendor/github.com/gdamore/tcell/v2/encoding.go
index 8bb449d67909..b7644c27e85a 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/encoding.go
+++ b/vendor/github.com/gdamore/tcell/v2/encoding.go
@@ -69,7 +69,6 @@ var encodingFallback EncodingFallback = EncodingFallbackFail
// The East Asian encodings have been seen to add 100-200K per encoding to the
// size of the resulting binary.
-//
func RegisterEncoding(charset string, enc encoding.Encoding) {
encodingLk.Lock()
charset = strings.ToLower(charset)
diff --git a/vendor/github.com/stefanhaller/tcell/v2/errors.go b/vendor/github.com/gdamore/tcell/v2/errors.go
similarity index 98%
rename from vendor/github.com/stefanhaller/tcell/v2/errors.go
rename to vendor/github.com/gdamore/tcell/v2/errors.go
index b76a7255623d..201dff9f8068 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/errors.go
+++ b/vendor/github.com/gdamore/tcell/v2/errors.go
@@ -18,7 +18,7 @@ import (
"errors"
"time"
- "github.com/stefanhaller/tcell/v2/terminfo"
+ "github.com/gdamore/tcell/v2/terminfo"
)
var (
diff --git a/vendor/github.com/stefanhaller/tcell/v2/event.go b/vendor/github.com/gdamore/tcell/v2/event.go
similarity index 100%
rename from vendor/github.com/stefanhaller/tcell/v2/event.go
rename to vendor/github.com/gdamore/tcell/v2/event.go
diff --git a/vendor/github.com/stefanhaller/tcell/v2/focus.go b/vendor/github.com/gdamore/tcell/v2/focus.go
similarity index 100%
rename from vendor/github.com/stefanhaller/tcell/v2/focus.go
rename to vendor/github.com/gdamore/tcell/v2/focus.go
diff --git a/vendor/github.com/stefanhaller/tcell/v2/interrupt.go b/vendor/github.com/gdamore/tcell/v2/interrupt.go
similarity index 100%
rename from vendor/github.com/stefanhaller/tcell/v2/interrupt.go
rename to vendor/github.com/gdamore/tcell/v2/interrupt.go
diff --git a/vendor/github.com/stefanhaller/tcell/v2/key.go b/vendor/github.com/gdamore/tcell/v2/key.go
similarity index 100%
rename from vendor/github.com/stefanhaller/tcell/v2/key.go
rename to vendor/github.com/gdamore/tcell/v2/key.go
diff --git a/vendor/github.com/stefanhaller/tcell/v2/mouse.go b/vendor/github.com/gdamore/tcell/v2/mouse.go
similarity index 100%
rename from vendor/github.com/stefanhaller/tcell/v2/mouse.go
rename to vendor/github.com/gdamore/tcell/v2/mouse.go
diff --git a/vendor/github.com/stefanhaller/tcell/v2/nonblock_bsd.go b/vendor/github.com/gdamore/tcell/v2/nonblock_bsd.go
similarity index 100%
rename from vendor/github.com/stefanhaller/tcell/v2/nonblock_bsd.go
rename to vendor/github.com/gdamore/tcell/v2/nonblock_bsd.go
diff --git a/vendor/github.com/stefanhaller/tcell/v2/nonblock_unix.go b/vendor/github.com/gdamore/tcell/v2/nonblock_unix.go
similarity index 100%
rename from vendor/github.com/stefanhaller/tcell/v2/nonblock_unix.go
rename to vendor/github.com/gdamore/tcell/v2/nonblock_unix.go
diff --git a/vendor/github.com/stefanhaller/tcell/v2/paste.go b/vendor/github.com/gdamore/tcell/v2/paste.go
similarity index 100%
rename from vendor/github.com/stefanhaller/tcell/v2/paste.go
rename to vendor/github.com/gdamore/tcell/v2/paste.go
diff --git a/vendor/github.com/stefanhaller/tcell/v2/resize.go b/vendor/github.com/gdamore/tcell/v2/resize.go
similarity index 59%
rename from vendor/github.com/stefanhaller/tcell/v2/resize.go
rename to vendor/github.com/gdamore/tcell/v2/resize.go
index 0385673c838f..f3e2b3a5fa14 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/resize.go
+++ b/vendor/github.com/gdamore/tcell/v2/resize.go
@@ -20,15 +20,18 @@ import (
// EventResize is sent when the window size changes.
type EventResize struct {
- t time.Time
- w int
- h int
+ t time.Time
+ ws WindowSize
}
// NewEventResize creates an EventResize with the new updated window size,
// which is given in character cells.
func NewEventResize(width, height int) *EventResize {
- return &EventResize{t: time.Now(), w: width, h: height}
+ ws := WindowSize{
+ Width: width,
+ Height: height,
+ }
+ return &EventResize{t: time.Now(), ws: ws}
}
// When returns the time when the Event was created.
@@ -38,5 +41,26 @@ func (ev *EventResize) When() time.Time {
// Size returns the new window size as width, height in character cells.
func (ev *EventResize) Size() (int, int) {
- return ev.w, ev.h
+ return ev.ws.Width, ev.ws.Height
+}
+
+// PixelSize returns the new window size as width, height in pixels. The size
+// will be 0,0 if the screen doesn't support this feature
+func (ev *EventResize) PixelSize() (int, int) {
+ return ev.ws.PixelWidth, ev.ws.PixelHeight
+}
+
+type WindowSize struct {
+ Width int
+ Height int
+ PixelWidth int
+ PixelHeight int
+}
+
+// CellDimensions returns the dimensions of a single cell, in pixels
+func (ws WindowSize) CellDimensions() (int, int) {
+ if ws.PixelWidth == 0 || ws.PixelHeight == 0 {
+ return 0, 0
+ }
+ return (ws.PixelWidth / ws.Width), (ws.PixelHeight / ws.Height)
}
diff --git a/vendor/github.com/stefanhaller/tcell/v2/runes.go b/vendor/github.com/gdamore/tcell/v2/runes.go
similarity index 100%
rename from vendor/github.com/stefanhaller/tcell/v2/runes.go
rename to vendor/github.com/gdamore/tcell/v2/runes.go
diff --git a/vendor/github.com/stefanhaller/tcell/v2/screen.go b/vendor/github.com/gdamore/tcell/v2/screen.go
similarity index 75%
rename from vendor/github.com/stefanhaller/tcell/v2/screen.go
rename to vendor/github.com/gdamore/tcell/v2/screen.go
index bd35a89dd58b..6ab27ca96cd6 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/screen.go
+++ b/vendor/github.com/gdamore/tcell/v2/screen.go
@@ -1,4 +1,4 @@
-// Copyright 2022 The TCell Authors
+// Copyright 2023 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use file except in compliance with the License.
@@ -14,6 +14,8 @@
package tcell
+import "sync"
+
// Screen represents the physical (or emulated) screen.
// This can be a terminal window or a physical console. Platforms implement
// this differently.
@@ -255,6 +257,14 @@ type Screen interface {
// does not support application-initiated resizing, whereas the legacy terminal does.
// Also, some emulators can support this but may have it disabled by default.
SetSize(int, int)
+
+ // LockRegion sets or unsets a lock on a region of cells. A lock on a
+ // cell prevents the cell from being redrawn.
+ LockRegion(x, y, width, height int, lock bool)
+
+ // Tty returns the underlying Tty. If the screen is not a terminal, the
+ // returned bool will be false
+ Tty() (Tty, bool)
}
// NewScreen returns a default Screen suitable for the user's terminal
@@ -293,3 +303,164 @@ const (
CursorStyleBlinkingBar
CursorStyleSteadyBar
)
+
+// screenImpl is a subset of Screen that can be used with baseScreen to formulate
+// a complete implementation of Screen. See Screen for doc comments about methods.
+type screenImpl interface {
+ Init() error
+ Fini()
+ SetStyle(style Style)
+ ShowCursor(x int, y int)
+ HideCursor()
+ SetCursorStyle(CursorStyle)
+ Size() (width, height int)
+ EnableMouse(...MouseFlags)
+ DisableMouse()
+ EnablePaste()
+ DisablePaste()
+ EnableFocus()
+ DisableFocus()
+ HasMouse() bool
+ Colors() int
+ Show()
+ Sync()
+ CharacterSet() string
+ RegisterRuneFallback(r rune, subst string)
+ UnregisterRuneFallback(r rune)
+ CanDisplay(r rune, checkFallbacks bool) bool
+ Resize(int, int, int, int)
+ HasKey(Key) bool
+ Suspend() error
+ Resume() error
+ Beep() error
+ SetSize(int, int)
+ Tty() (Tty, bool)
+
+ // Following methods are not part of the Screen api, but are used for interaction with
+ // the common layer code.
+
+ // Locker locks the underlying data structures so that we can access them
+ // in a thread-safe way.
+ sync.Locker
+
+ // GetCells returns a pointer to the underlying CellBuffer that the implementation uses.
+ // Various methods will write to these for performance, but will use the lock to do so.
+ GetCells() *CellBuffer
+
+ // StopQ is closed when the screen is shut down via Fini. It remains open if the screen
+ // is merely suspended.
+ StopQ() <-chan struct{}
+
+ // EventQ delivers events. Events are posted to this by the screen in response to
+ // key presses, resizes, etc. Application code receives events from this via the
+ // Screen.PollEvent, Screen.ChannelEvents APIs.
+ EventQ() chan Event
+}
+
+type baseScreen struct {
+ screenImpl
+}
+
+func (b *baseScreen) SetCell(x int, y int, style Style, ch ...rune) {
+ if len(ch) > 0 {
+ b.SetContent(x, y, ch[0], ch[1:], style)
+ } else {
+ b.SetContent(x, y, ' ', nil, style)
+ }
+}
+
+func (b *baseScreen) Clear() {
+ b.Fill(' ', StyleDefault)
+}
+
+func (b *baseScreen) Fill(r rune, style Style) {
+ cb := b.GetCells()
+ b.Lock()
+ cb.Fill(r, style)
+ b.Unlock()
+}
+
+func (b *baseScreen) SetContent(x, y int, mainc rune, combc []rune, st Style) {
+
+ cells := b.GetCells()
+ b.Lock()
+ cells.SetContent(x, y, mainc, combc, st)
+ b.Unlock()
+}
+
+func (b *baseScreen) GetContent(x, y int) (rune, []rune, Style, int) {
+ var primary rune
+ var combining []rune
+ var style Style
+ var width int
+ cells := b.GetCells()
+ b.Lock()
+ primary, combining, style, width = cells.GetContent(x, y)
+ b.Unlock()
+ return primary, combining, style, width
+}
+
+func (b *baseScreen) LockRegion(x, y, width, height int, lock bool) {
+ cells := b.GetCells()
+ b.Lock()
+ for j := y; j < (y + height); j += 1 {
+ for i := x; i < (x + width); i += 1 {
+ switch lock {
+ case true:
+ cells.LockCell(i, j)
+ case false:
+ cells.UnlockCell(i, j)
+ }
+ }
+ }
+ b.Unlock()
+}
+
+func (b *baseScreen) ChannelEvents(ch chan<- Event, quit <-chan struct{}) {
+ defer close(ch)
+ for {
+ select {
+ case <-quit:
+ return
+ case <-b.StopQ():
+ return
+ case ev := <-b.EventQ():
+ select {
+ case <-quit:
+ return
+ case <-b.StopQ():
+ return
+ case ch <- ev:
+ }
+ }
+ }
+}
+
+func (b *baseScreen) PollEvent() Event {
+ select {
+ case <-b.StopQ():
+ return nil
+ case ev := <-b.EventQ():
+ return ev
+ }
+}
+
+func (b *baseScreen) HasPendingEvent() bool {
+ return len(b.EventQ()) > 0
+}
+
+func (b *baseScreen) PostEventWait(ev Event) {
+ select {
+ case b.EventQ() <- ev:
+ case <-b.StopQ():
+ }
+}
+
+func (b *baseScreen) PostEvent(ev Event) error {
+ select {
+ case b.EventQ() <- ev:
+ return nil
+ default:
+ return ErrEventQFull
+ }
+}
diff --git a/vendor/github.com/stefanhaller/tcell/v2/simulation.go b/vendor/github.com/gdamore/tcell/v2/simulation.go
similarity index 86%
rename from vendor/github.com/stefanhaller/tcell/v2/simulation.go
rename to vendor/github.com/gdamore/tcell/v2/simulation.go
index b18b6648dbeb..eb08b8fe9278 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/simulation.go
+++ b/vendor/github.com/gdamore/tcell/v2/simulation.go
@@ -1,4 +1,4 @@
-// Copyright 2022 The TCell Authors
+// Copyright 2023 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use file except in compliance with the License.
@@ -27,14 +27,17 @@ func NewSimulationScreen(charset string) SimulationScreen {
if charset == "" {
charset = "UTF-8"
}
- s := &simscreen{charset: charset}
- return s
+ ss := &simscreen{charset: charset}
+ ss.Screen = &baseScreen{screenImpl: ss}
+ return ss
}
// SimulationScreen represents a screen simulation. This is intended to
// be a superset of normal Screens, but also adds some important interfaces
// for testing.
type SimulationScreen interface {
+ Screen
+
// InjectKeyBytes injects a stream of bytes corresponding to
// the native encoding (see charset). It turns true if the entire
// set of bytes were processed and delivered as KeyEvents, false
@@ -57,8 +60,6 @@ type SimulationScreen interface {
// GetCursor returns the cursor details.
GetCursor() (x int, y int, visible bool)
-
- Screen
}
// SimCell represents a simulated screen cell. The purpose of this
@@ -98,6 +99,7 @@ type simscreen struct {
fillstyle Style
fallback map[rune]string
+ Screen
sync.Mutex
}
@@ -150,43 +152,6 @@ func (s *simscreen) SetStyle(style Style) {
s.Unlock()
}
-func (s *simscreen) Clear() {
- s.Fill(' ', s.style)
-}
-
-func (s *simscreen) Fill(r rune, style Style) {
- s.Lock()
- s.back.Fill(r, style)
- s.Unlock()
-}
-
-func (s *simscreen) SetCell(x, y int, style Style, ch ...rune) {
-
- if len(ch) > 0 {
- s.SetContent(x, y, ch[0], ch[1:], style)
- } else {
- s.SetContent(x, y, ' ', nil, style)
- }
-}
-
-func (s *simscreen) SetContent(x, y int, mainc rune, combc []rune, st Style) {
-
- s.Lock()
- s.back.SetContent(x, y, mainc, combc, st)
- s.Unlock()
-}
-
-func (s *simscreen) GetContent(x, y int) (rune, []rune, Style, int) {
- var mainc rune
- var combc []rune
- var style Style
- var width int
- s.Lock()
- mainc, combc, style, width = s.back.GetContent(x, y)
- s.Unlock()
- return mainc, combc, style, width
-}
-
func (s *simscreen) drawCell(x, y int) int {
mainc, combc, style, width := s.back.GetContent(x, y)
@@ -344,7 +309,7 @@ func (s *simscreen) resize() {
if w != ow || h != oh {
s.back.Resize(w, h)
ev := NewEventResize(w, h)
- s.PostEvent(ev)
+ s.postEvent(ev)
}
}
@@ -352,60 +317,21 @@ func (s *simscreen) Colors() int {
return 256
}
-func (s *simscreen) ChannelEvents(ch chan<- Event, quit <-chan struct{}) {
- defer close(ch)
- for {
- select {
- case <-quit:
- return
- case <-s.quit:
- return
- case ev := <-s.evch:
- select {
- case <-quit:
- return
- case <-s.quit:
- return
- case ch <- ev:
- }
- }
- }
-}
-
-func (s *simscreen) PollEvent() Event {
- select {
- case <-s.quit:
- return nil
- case ev := <-s.evch:
- return ev
- }
-}
-
-func (s *simscreen) HasPendingEvent() bool {
- return len(s.evch) > 0
-}
-
-func (s *simscreen) PostEventWait(ev Event) {
- s.evch <- ev
-}
-
-func (s *simscreen) PostEvent(ev Event) error {
+func (s *simscreen) postEvent(ev Event) {
select {
case s.evch <- ev:
- return nil
- default:
- return ErrEventQFull
+ case <-s.quit:
}
}
func (s *simscreen) InjectMouse(x, y int, buttons ButtonMask, mod ModMask) {
ev := NewEventMouse(x, y, buttons, mod)
- s.PostEvent(ev)
+ s.postEvent(ev)
}
func (s *simscreen) InjectKey(key Key, r rune, mod ModMask) {
ev := NewEventKey(key, r, mod)
- s.PostEvent(ev)
+ s.postEvent(ev)
}
func (s *simscreen) InjectKeyBytes(b []byte) bool {
@@ -416,7 +342,7 @@ outer:
if b[0] >= ' ' && b[0] <= 0x7F {
// printable ASCII easy to deal with -- no encodings
ev := NewEventKey(KeyRune, rune(b[0]), ModNone)
- s.PostEvent(ev)
+ s.postEvent(ev)
b = b[1:]
continue
}
@@ -428,7 +354,7 @@ outer:
mod = ModCtrl
}
ev := NewEventKey(Key(b[0]), 0, mod)
- s.PostEvent(ev)
+ s.postEvent(ev)
b = b[1:]
continue
}
@@ -442,7 +368,7 @@ outer:
r, _ := utf8.DecodeRune(utfb[:nout])
if r != utf8.RuneError {
ev := NewEventKey(KeyRune, r, ModNone)
- s.PostEvent(ev)
+ s.postEvent(ev)
}
b = b[nin:]
continue outer
@@ -553,3 +479,19 @@ func (s *simscreen) Suspend() error {
func (s *simscreen) Resume() error {
return nil
}
+
+func (s *simscreen) Tty() (Tty, bool) {
+ return nil, false
+}
+
+func (s *simscreen) GetCells() *CellBuffer {
+ return &s.back
+}
+
+func (s *simscreen) EventQ() chan Event {
+ return s.evch
+}
+
+func (s *simscreen) StopQ() <-chan struct{} {
+ return s.quit
+}
diff --git a/vendor/github.com/stefanhaller/tcell/v2/stdin_unix.go b/vendor/github.com/gdamore/tcell/v2/stdin_unix.go
similarity index 92%
rename from vendor/github.com/stefanhaller/tcell/v2/stdin_unix.go
rename to vendor/github.com/gdamore/tcell/v2/stdin_unix.go
index 4c0d6e12d82c..b478b8918cd1 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/stdin_unix.go
+++ b/vendor/github.com/gdamore/tcell/v2/stdin_unix.go
@@ -27,6 +27,7 @@ import (
"syscall"
"time"
+ "golang.org/x/sys/unix"
"golang.org/x/term"
)
@@ -133,11 +134,14 @@ func (tty *stdIoTty) Stop() error {
return nil
}
-func (tty *stdIoTty) WindowSize() (int, int, error) {
- w, h, err := term.GetSize(tty.fd)
+func (tty *stdIoTty) WindowSize() (WindowSize, error) {
+ size := WindowSize{}
+ ws, err := unix.IoctlGetWinsize(tty.fd, unix.TIOCGWINSZ)
if err != nil {
- return 0, 0, err
+ return size, err
}
+ w := int(ws.Col)
+ h := int(ws.Row)
if w == 0 {
w, _ = strconv.Atoi(os.Getenv("COLUMNS"))
}
@@ -150,7 +154,11 @@ func (tty *stdIoTty) WindowSize() (int, int, error) {
if h == 0 {
h = 25 // default
}
- return w, h, nil
+ size.Width = w
+ size.Height = h
+ size.PixelWidth = int(ws.Xpixel)
+ size.PixelHeight = int(ws.Ypixel)
+ return size, nil
}
func (tty *stdIoTty) NotifyResize(cb func()) {
diff --git a/vendor/github.com/stefanhaller/tcell/v2/style.go b/vendor/github.com/gdamore/tcell/v2/style.go
similarity index 100%
rename from vendor/github.com/stefanhaller/tcell/v2/style.go
rename to vendor/github.com/gdamore/tcell/v2/style.go
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terminfo/.gitignore b/vendor/github.com/gdamore/tcell/v2/terminfo/.gitignore
similarity index 100%
rename from vendor/github.com/stefanhaller/tcell/v2/terminfo/.gitignore
rename to vendor/github.com/gdamore/tcell/v2/terminfo/.gitignore
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terminfo/README.md b/vendor/github.com/gdamore/tcell/v2/terminfo/README.md
similarity index 100%
rename from vendor/github.com/stefanhaller/tcell/v2/terminfo/README.md
rename to vendor/github.com/gdamore/tcell/v2/terminfo/README.md
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terminfo/TERMINALS.md b/vendor/github.com/gdamore/tcell/v2/terminfo/TERMINALS.md
similarity index 100%
rename from vendor/github.com/stefanhaller/tcell/v2/terminfo/TERMINALS.md
rename to vendor/github.com/gdamore/tcell/v2/terminfo/TERMINALS.md
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terminfo/a/aixterm/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/a/aixterm/term.go
similarity index 97%
rename from vendor/github.com/stefanhaller/tcell/v2/terminfo/a/aixterm/term.go
rename to vendor/github.com/gdamore/tcell/v2/terminfo/a/aixterm/term.go
index 165056e236f7..503c9199edca 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/terminfo/a/aixterm/term.go
+++ b/vendor/github.com/gdamore/tcell/v2/terminfo/a/aixterm/term.go
@@ -2,7 +2,7 @@
package aixterm
-import "github.com/stefanhaller/tcell/v2/terminfo"
+import "github.com/gdamore/tcell/v2/terminfo"
func init() {
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terminfo/a/alacritty/direct.go b/vendor/github.com/gdamore/tcell/v2/terminfo/a/alacritty/direct.go
similarity index 97%
rename from vendor/github.com/stefanhaller/tcell/v2/terminfo/a/alacritty/direct.go
rename to vendor/github.com/gdamore/tcell/v2/terminfo/a/alacritty/direct.go
index fd8663cadb66..db6351af2b2a 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/terminfo/a/alacritty/direct.go
+++ b/vendor/github.com/gdamore/tcell/v2/terminfo/a/alacritty/direct.go
@@ -2,7 +2,7 @@
package alacritty
-import "github.com/stefanhaller/tcell/v2/terminfo"
+import "github.com/gdamore/tcell/v2/terminfo"
func init() {
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terminfo/a/alacritty/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/a/alacritty/term.go
similarity index 97%
rename from vendor/github.com/stefanhaller/tcell/v2/terminfo/a/alacritty/term.go
rename to vendor/github.com/gdamore/tcell/v2/terminfo/a/alacritty/term.go
index ace85a69869c..5b9799846979 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/terminfo/a/alacritty/term.go
+++ b/vendor/github.com/gdamore/tcell/v2/terminfo/a/alacritty/term.go
@@ -2,7 +2,7 @@
package alacritty
-import "github.com/stefanhaller/tcell/v2/terminfo"
+import "github.com/gdamore/tcell/v2/terminfo"
func init() {
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terminfo/a/ansi/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/a/ansi/term.go
similarity index 95%
rename from vendor/github.com/stefanhaller/tcell/v2/terminfo/a/ansi/term.go
rename to vendor/github.com/gdamore/tcell/v2/terminfo/a/ansi/term.go
index 8316373db2f3..5c572fd4961b 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/terminfo/a/ansi/term.go
+++ b/vendor/github.com/gdamore/tcell/v2/terminfo/a/ansi/term.go
@@ -2,7 +2,7 @@
package ansi
-import "github.com/stefanhaller/tcell/v2/terminfo"
+import "github.com/gdamore/tcell/v2/terminfo"
func init() {
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terminfo/b/beterm/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/b/beterm/term.go
similarity index 96%
rename from vendor/github.com/stefanhaller/tcell/v2/terminfo/b/beterm/term.go
rename to vendor/github.com/gdamore/tcell/v2/terminfo/b/beterm/term.go
index dcdbabb44a82..e6d88838c3fb 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/terminfo/b/beterm/term.go
+++ b/vendor/github.com/gdamore/tcell/v2/terminfo/b/beterm/term.go
@@ -2,7 +2,7 @@
package beterm
-import "github.com/stefanhaller/tcell/v2/terminfo"
+import "github.com/gdamore/tcell/v2/terminfo"
func init() {
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terminfo/base/base.go b/vendor/github.com/gdamore/tcell/v2/terminfo/base/base.go
similarity index 80%
rename from vendor/github.com/stefanhaller/tcell/v2/terminfo/base/base.go
rename to vendor/github.com/gdamore/tcell/v2/terminfo/base/base.go
index 514a909f7a3f..fbecdfa93e9c 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/terminfo/base/base.go
+++ b/vendor/github.com/gdamore/tcell/v2/terminfo/base/base.go
@@ -24,9 +24,9 @@ package base
import (
// The following imports just register themselves --
// thse are the terminal types we aggregate in this package.
- _ "github.com/stefanhaller/tcell/v2/terminfo/a/ansi"
- _ "github.com/stefanhaller/tcell/v2/terminfo/v/vt100"
- _ "github.com/stefanhaller/tcell/v2/terminfo/v/vt102"
- _ "github.com/stefanhaller/tcell/v2/terminfo/v/vt220"
- _ "github.com/stefanhaller/tcell/v2/terminfo/x/xterm"
+ _ "github.com/gdamore/tcell/v2/terminfo/a/ansi"
+ _ "github.com/gdamore/tcell/v2/terminfo/v/vt100"
+ _ "github.com/gdamore/tcell/v2/terminfo/v/vt102"
+ _ "github.com/gdamore/tcell/v2/terminfo/v/vt220"
+ _ "github.com/gdamore/tcell/v2/terminfo/x/xterm"
)
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terminfo/c/cygwin/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/c/cygwin/term.go
similarity index 97%
rename from vendor/github.com/stefanhaller/tcell/v2/terminfo/c/cygwin/term.go
rename to vendor/github.com/gdamore/tcell/v2/terminfo/c/cygwin/term.go
index 33ecba451784..46a0a4a3a25a 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/terminfo/c/cygwin/term.go
+++ b/vendor/github.com/gdamore/tcell/v2/terminfo/c/cygwin/term.go
@@ -2,7 +2,7 @@
package cygwin
-import "github.com/stefanhaller/tcell/v2/terminfo"
+import "github.com/gdamore/tcell/v2/terminfo"
func init() {
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terminfo/d/dtterm/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/d/dtterm/term.go
similarity index 97%
rename from vendor/github.com/stefanhaller/tcell/v2/terminfo/d/dtterm/term.go
rename to vendor/github.com/gdamore/tcell/v2/terminfo/d/dtterm/term.go
index fb2a1979896d..f471c80d2384 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/terminfo/d/dtterm/term.go
+++ b/vendor/github.com/gdamore/tcell/v2/terminfo/d/dtterm/term.go
@@ -2,7 +2,7 @@
package dtterm
-import "github.com/stefanhaller/tcell/v2/terminfo"
+import "github.com/gdamore/tcell/v2/terminfo"
func init() {
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terminfo/dynamic/dynamic.go b/vendor/github.com/gdamore/tcell/v2/terminfo/dynamic/dynamic.go
similarity index 98%
rename from vendor/github.com/stefanhaller/tcell/v2/terminfo/dynamic/dynamic.go
rename to vendor/github.com/gdamore/tcell/v2/terminfo/dynamic/dynamic.go
index c6dbde54a0dc..047ebded6b26 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/terminfo/dynamic/dynamic.go
+++ b/vendor/github.com/gdamore/tcell/v2/terminfo/dynamic/dynamic.go
@@ -29,7 +29,7 @@ import (
"strconv"
"strings"
- "github.com/stefanhaller/tcell/v2/terminfo"
+ "github.com/gdamore/tcell/v2/terminfo"
)
type termcap struct {
@@ -185,16 +185,10 @@ func (tc *termcap) setupterm(name string) error {
func LoadTerminfo(name string) (*terminfo.Terminfo, string, error) {
var tc termcap
if err := tc.setupterm(name); err != nil {
- if err != nil {
- return nil, "", err
- }
+ return nil, "", err
}
t := &terminfo.Terminfo{}
- // If this is an alias record, then just emit the alias
t.Name = tc.name
- if t.Name != name {
- return t, "", nil
- }
t.Aliases = tc.aliases
t.Colors = tc.getnum("colors")
t.Columns = tc.getnum("cols")
@@ -314,6 +308,8 @@ func LoadTerminfo(name string) (*terminfo.Terminfo, string, error) {
// but modern XTerm and emulators often have them. Let's add them,
// if the shifted right and left arrows are defined.
if t.KeyShfRight == "\x1b[1;2C" && t.KeyShfLeft == "\x1b[1;2D" {
+ t.Modifiers = terminfo.ModifiersXTerm
+
t.KeyShfUp = "\x1b[1;2A"
t.KeyShfDown = "\x1b[1;2B"
t.KeyMetaUp = "\x1b[1;9A"
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terminfo/e/emacs/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/e/emacs/term.go
similarity index 96%
rename from vendor/github.com/stefanhaller/tcell/v2/terminfo/e/emacs/term.go
rename to vendor/github.com/gdamore/tcell/v2/terminfo/e/emacs/term.go
index fe7667363a6d..b0b9b4771e62 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/terminfo/e/emacs/term.go
+++ b/vendor/github.com/gdamore/tcell/v2/terminfo/e/emacs/term.go
@@ -2,7 +2,7 @@
package emacs
-import "github.com/stefanhaller/tcell/v2/terminfo"
+import "github.com/gdamore/tcell/v2/terminfo"
func init() {
diff --git a/vendor/github.com/gdamore/tcell/v2/terminfo/extended/extended.go b/vendor/github.com/gdamore/tcell/v2/terminfo/extended/extended.go
new file mode 100644
index 000000000000..c69bef13d530
--- /dev/null
+++ b/vendor/github.com/gdamore/tcell/v2/terminfo/extended/extended.go
@@ -0,0 +1,58 @@
+// Copyright 2020 The TCell Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use file except in compliance with the License.
+// You may obtain a copy of the license at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package extended contains an extended set of terminal descriptions.
+// Applications desiring to have a better chance of Just Working by
+// default should include this package. This will significantly increase
+// the size of the program.
+package extended
+
+import (
+ // The following imports just register themselves --
+ // these are the terminal types we aggregate in this package.
+ _ "github.com/gdamore/tcell/v2/terminfo/a/aixterm"
+ _ "github.com/gdamore/tcell/v2/terminfo/a/alacritty"
+ _ "github.com/gdamore/tcell/v2/terminfo/a/ansi"
+ _ "github.com/gdamore/tcell/v2/terminfo/b/beterm"
+ _ "github.com/gdamore/tcell/v2/terminfo/c/cygwin"
+ _ "github.com/gdamore/tcell/v2/terminfo/d/dtterm"
+ _ "github.com/gdamore/tcell/v2/terminfo/e/emacs"
+ _ "github.com/gdamore/tcell/v2/terminfo/f/foot"
+ _ "github.com/gdamore/tcell/v2/terminfo/g/gnome"
+ _ "github.com/gdamore/tcell/v2/terminfo/h/hpterm"
+ _ "github.com/gdamore/tcell/v2/terminfo/k/konsole"
+ _ "github.com/gdamore/tcell/v2/terminfo/k/kterm"
+ _ "github.com/gdamore/tcell/v2/terminfo/l/linux"
+ _ "github.com/gdamore/tcell/v2/terminfo/p/pcansi"
+ _ "github.com/gdamore/tcell/v2/terminfo/r/rxvt"
+ _ "github.com/gdamore/tcell/v2/terminfo/s/screen"
+ _ "github.com/gdamore/tcell/v2/terminfo/s/simpleterm"
+ _ "github.com/gdamore/tcell/v2/terminfo/s/sun"
+ _ "github.com/gdamore/tcell/v2/terminfo/t/termite"
+ _ "github.com/gdamore/tcell/v2/terminfo/t/tmux"
+ _ "github.com/gdamore/tcell/v2/terminfo/v/vt100"
+ _ "github.com/gdamore/tcell/v2/terminfo/v/vt102"
+ _ "github.com/gdamore/tcell/v2/terminfo/v/vt220"
+ _ "github.com/gdamore/tcell/v2/terminfo/v/vt320"
+ _ "github.com/gdamore/tcell/v2/terminfo/v/vt400"
+ _ "github.com/gdamore/tcell/v2/terminfo/v/vt420"
+ _ "github.com/gdamore/tcell/v2/terminfo/v/vt52"
+ _ "github.com/gdamore/tcell/v2/terminfo/w/wy50"
+ _ "github.com/gdamore/tcell/v2/terminfo/w/wy60"
+ _ "github.com/gdamore/tcell/v2/terminfo/w/wy99_ansi"
+ _ "github.com/gdamore/tcell/v2/terminfo/x/xfce"
+ _ "github.com/gdamore/tcell/v2/terminfo/x/xterm"
+ _ "github.com/gdamore/tcell/v2/terminfo/x/xterm_kitty"
+ _ "github.com/gdamore/tcell/v2/terminfo/x/xterm_termite"
+)
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terminfo/f/foot/foot.go b/vendor/github.com/gdamore/tcell/v2/terminfo/f/foot/foot.go
similarity index 97%
rename from vendor/github.com/stefanhaller/tcell/v2/terminfo/f/foot/foot.go
rename to vendor/github.com/gdamore/tcell/v2/terminfo/f/foot/foot.go
index 7b132a1c00f9..5daa3c8acdfd 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/terminfo/f/foot/foot.go
+++ b/vendor/github.com/gdamore/tcell/v2/terminfo/f/foot/foot.go
@@ -2,7 +2,7 @@
package foot
-import "github.com/stefanhaller/tcell/v2/terminfo"
+import "github.com/gdamore/tcell/v2/terminfo"
func init() {
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terminfo/g/gnome/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/g/gnome/term.go
similarity index 98%
rename from vendor/github.com/stefanhaller/tcell/v2/terminfo/g/gnome/term.go
rename to vendor/github.com/gdamore/tcell/v2/terminfo/g/gnome/term.go
index 09eb7692558b..e85a3a343d44 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/terminfo/g/gnome/term.go
+++ b/vendor/github.com/gdamore/tcell/v2/terminfo/g/gnome/term.go
@@ -2,7 +2,7 @@
package gnome
-import "github.com/stefanhaller/tcell/v2/terminfo"
+import "github.com/gdamore/tcell/v2/terminfo"
func init() {
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terminfo/gen.sh b/vendor/github.com/gdamore/tcell/v2/terminfo/gen.sh
similarity index 97%
rename from vendor/github.com/stefanhaller/tcell/v2/terminfo/gen.sh
rename to vendor/github.com/gdamore/tcell/v2/terminfo/gen.sh
index 2fc0611234e9..851175a3ff8e 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/terminfo/gen.sh
+++ b/vendor/github.com/gdamore/tcell/v2/terminfo/gen.sh
@@ -1,3 +1,4 @@
+#!/bin/bash
while read line
do
case "$line" in
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terminfo/h/hpterm/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/h/hpterm/term.go
similarity index 95%
rename from vendor/github.com/stefanhaller/tcell/v2/terminfo/h/hpterm/term.go
rename to vendor/github.com/gdamore/tcell/v2/terminfo/h/hpterm/term.go
index 9bc4a4cf3a18..123bfb939089 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/terminfo/h/hpterm/term.go
+++ b/vendor/github.com/gdamore/tcell/v2/terminfo/h/hpterm/term.go
@@ -2,7 +2,7 @@
package hpterm
-import "github.com/stefanhaller/tcell/v2/terminfo"
+import "github.com/gdamore/tcell/v2/terminfo"
func init() {
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terminfo/k/konsole/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/k/konsole/term.go
similarity index 98%
rename from vendor/github.com/stefanhaller/tcell/v2/terminfo/k/konsole/term.go
rename to vendor/github.com/gdamore/tcell/v2/terminfo/k/konsole/term.go
index c2dcdaa910c0..236db9db2d91 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/terminfo/k/konsole/term.go
+++ b/vendor/github.com/gdamore/tcell/v2/terminfo/k/konsole/term.go
@@ -2,7 +2,7 @@
package konsole
-import "github.com/stefanhaller/tcell/v2/terminfo"
+import "github.com/gdamore/tcell/v2/terminfo"
func init() {
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terminfo/k/kterm/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/k/kterm/term.go
similarity index 97%
rename from vendor/github.com/stefanhaller/tcell/v2/terminfo/k/kterm/term.go
rename to vendor/github.com/gdamore/tcell/v2/terminfo/k/kterm/term.go
index f65c5d827798..eedbe6de0af7 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/terminfo/k/kterm/term.go
+++ b/vendor/github.com/gdamore/tcell/v2/terminfo/k/kterm/term.go
@@ -2,7 +2,7 @@
package kterm
-import "github.com/stefanhaller/tcell/v2/terminfo"
+import "github.com/gdamore/tcell/v2/terminfo"
func init() {
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terminfo/l/linux/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/l/linux/term.go
similarity index 97%
rename from vendor/github.com/stefanhaller/tcell/v2/terminfo/l/linux/term.go
rename to vendor/github.com/gdamore/tcell/v2/terminfo/l/linux/term.go
index cdfb447eb420..8783b4c7ff69 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/terminfo/l/linux/term.go
+++ b/vendor/github.com/gdamore/tcell/v2/terminfo/l/linux/term.go
@@ -2,7 +2,7 @@
package linux
-import "github.com/stefanhaller/tcell/v2/terminfo"
+import "github.com/gdamore/tcell/v2/terminfo"
func init() {
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terminfo/models.txt b/vendor/github.com/gdamore/tcell/v2/terminfo/models.txt
similarity index 100%
rename from vendor/github.com/stefanhaller/tcell/v2/terminfo/models.txt
rename to vendor/github.com/gdamore/tcell/v2/terminfo/models.txt
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terminfo/p/pcansi/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/p/pcansi/term.go
similarity index 95%
rename from vendor/github.com/stefanhaller/tcell/v2/terminfo/p/pcansi/term.go
rename to vendor/github.com/gdamore/tcell/v2/terminfo/p/pcansi/term.go
index 875929c80664..9e89c19772f1 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/terminfo/p/pcansi/term.go
+++ b/vendor/github.com/gdamore/tcell/v2/terminfo/p/pcansi/term.go
@@ -2,7 +2,7 @@
package pcansi
-import "github.com/stefanhaller/tcell/v2/terminfo"
+import "github.com/gdamore/tcell/v2/terminfo"
func init() {
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terminfo/r/rxvt/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/r/rxvt/term.go
similarity index 99%
rename from vendor/github.com/stefanhaller/tcell/v2/terminfo/r/rxvt/term.go
rename to vendor/github.com/gdamore/tcell/v2/terminfo/r/rxvt/term.go
index 6d2154554229..6fa9e7fa465e 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/terminfo/r/rxvt/term.go
+++ b/vendor/github.com/gdamore/tcell/v2/terminfo/r/rxvt/term.go
@@ -2,7 +2,7 @@
package rxvt
-import "github.com/stefanhaller/tcell/v2/terminfo"
+import "github.com/gdamore/tcell/v2/terminfo"
func init() {
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terminfo/s/screen/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/s/screen/term.go
similarity index 98%
rename from vendor/github.com/stefanhaller/tcell/v2/terminfo/s/screen/term.go
rename to vendor/github.com/gdamore/tcell/v2/terminfo/s/screen/term.go
index 2769a6a98952..d95d636337b0 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/terminfo/s/screen/term.go
+++ b/vendor/github.com/gdamore/tcell/v2/terminfo/s/screen/term.go
@@ -2,7 +2,7 @@
package screen
-import "github.com/stefanhaller/tcell/v2/terminfo"
+import "github.com/gdamore/tcell/v2/terminfo"
func init() {
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terminfo/s/simpleterm/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/s/simpleterm/term.go
similarity index 98%
rename from vendor/github.com/stefanhaller/tcell/v2/terminfo/s/simpleterm/term.go
rename to vendor/github.com/gdamore/tcell/v2/terminfo/s/simpleterm/term.go
index 87714bf0cec2..f633f29414e8 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/terminfo/s/simpleterm/term.go
+++ b/vendor/github.com/gdamore/tcell/v2/terminfo/s/simpleterm/term.go
@@ -2,7 +2,7 @@
package simpleterm
-import "github.com/stefanhaller/tcell/v2/terminfo"
+import "github.com/gdamore/tcell/v2/terminfo"
func init() {
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terminfo/s/sun/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/s/sun/term.go
similarity index 98%
rename from vendor/github.com/stefanhaller/tcell/v2/terminfo/s/sun/term.go
rename to vendor/github.com/gdamore/tcell/v2/terminfo/s/sun/term.go
index 9eaef4002343..16cb96c20a8a 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/terminfo/s/sun/term.go
+++ b/vendor/github.com/gdamore/tcell/v2/terminfo/s/sun/term.go
@@ -20,7 +20,7 @@
package sun
-import "github.com/stefanhaller/tcell/v2/terminfo"
+import "github.com/gdamore/tcell/v2/terminfo"
func init() {
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terminfo/t/termite/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/t/termite/term.go
similarity index 97%
rename from vendor/github.com/stefanhaller/tcell/v2/terminfo/t/termite/term.go
rename to vendor/github.com/gdamore/tcell/v2/terminfo/t/termite/term.go
index dfd08287efc9..593d3855612f 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/terminfo/t/termite/term.go
+++ b/vendor/github.com/gdamore/tcell/v2/terminfo/t/termite/term.go
@@ -2,7 +2,7 @@
package termite
-import "github.com/stefanhaller/tcell/v2/terminfo"
+import "github.com/gdamore/tcell/v2/terminfo"
func init() {
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terminfo/t/tmux/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/t/tmux/term.go
similarity index 98%
rename from vendor/github.com/stefanhaller/tcell/v2/terminfo/t/tmux/term.go
rename to vendor/github.com/gdamore/tcell/v2/terminfo/t/tmux/term.go
index d4fafac6e820..2da0c3cfbeff 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/terminfo/t/tmux/term.go
+++ b/vendor/github.com/gdamore/tcell/v2/terminfo/t/tmux/term.go
@@ -2,7 +2,7 @@
package tmux
-import "github.com/stefanhaller/tcell/v2/terminfo"
+import "github.com/gdamore/tcell/v2/terminfo"
func init() {
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terminfo/terminfo.go b/vendor/github.com/gdamore/tcell/v2/terminfo/terminfo.go
similarity index 100%
rename from vendor/github.com/stefanhaller/tcell/v2/terminfo/terminfo.go
rename to vendor/github.com/gdamore/tcell/v2/terminfo/terminfo.go
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terminfo/v/vt100/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt100/term.go
similarity index 95%
rename from vendor/github.com/stefanhaller/tcell/v2/terminfo/v/vt100/term.go
rename to vendor/github.com/gdamore/tcell/v2/terminfo/v/vt100/term.go
index d73fd3de6af4..0ae3918aca14 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/terminfo/v/vt100/term.go
+++ b/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt100/term.go
@@ -2,7 +2,7 @@
package vt100
-import "github.com/stefanhaller/tcell/v2/terminfo"
+import "github.com/gdamore/tcell/v2/terminfo"
func init() {
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terminfo/v/vt102/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt102/term.go
similarity index 95%
rename from vendor/github.com/stefanhaller/tcell/v2/terminfo/v/vt102/term.go
rename to vendor/github.com/gdamore/tcell/v2/terminfo/v/vt102/term.go
index 7738c9b93d6d..ec8dae24681f 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/terminfo/v/vt102/term.go
+++ b/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt102/term.go
@@ -2,7 +2,7 @@
package vt102
-import "github.com/stefanhaller/tcell/v2/terminfo"
+import "github.com/gdamore/tcell/v2/terminfo"
func init() {
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terminfo/v/vt220/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt220/term.go
similarity index 96%
rename from vendor/github.com/stefanhaller/tcell/v2/terminfo/v/vt220/term.go
rename to vendor/github.com/gdamore/tcell/v2/terminfo/v/vt220/term.go
index ef07d388ba36..75ab9a8ca4d4 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/terminfo/v/vt220/term.go
+++ b/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt220/term.go
@@ -2,7 +2,7 @@
package vt220
-import "github.com/stefanhaller/tcell/v2/terminfo"
+import "github.com/gdamore/tcell/v2/terminfo"
func init() {
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terminfo/v/vt320/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt320/term.go
similarity index 96%
rename from vendor/github.com/stefanhaller/tcell/v2/terminfo/v/vt320/term.go
rename to vendor/github.com/gdamore/tcell/v2/terminfo/v/vt320/term.go
index 0bfcb673842f..3fd3d39f0fb9 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/terminfo/v/vt320/term.go
+++ b/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt320/term.go
@@ -2,7 +2,7 @@
package vt320
-import "github.com/stefanhaller/tcell/v2/terminfo"
+import "github.com/gdamore/tcell/v2/terminfo"
func init() {
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terminfo/v/vt400/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt400/term.go
similarity index 95%
rename from vendor/github.com/stefanhaller/tcell/v2/terminfo/v/vt400/term.go
rename to vendor/github.com/gdamore/tcell/v2/terminfo/v/vt400/term.go
index 9839510b20eb..0c07e9d2c51b 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/terminfo/v/vt400/term.go
+++ b/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt400/term.go
@@ -2,7 +2,7 @@
package vt400
-import "github.com/stefanhaller/tcell/v2/terminfo"
+import "github.com/gdamore/tcell/v2/terminfo"
func init() {
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terminfo/v/vt420/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt420/term.go
similarity index 96%
rename from vendor/github.com/stefanhaller/tcell/v2/terminfo/v/vt420/term.go
rename to vendor/github.com/gdamore/tcell/v2/terminfo/v/vt420/term.go
index 3d92ef9273d3..094886e270fc 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/terminfo/v/vt420/term.go
+++ b/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt420/term.go
@@ -2,7 +2,7 @@
package vt420
-import "github.com/stefanhaller/tcell/v2/terminfo"
+import "github.com/gdamore/tcell/v2/terminfo"
func init() {
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terminfo/v/vt52/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt52/term.go
similarity index 92%
rename from vendor/github.com/stefanhaller/tcell/v2/terminfo/v/vt52/term.go
rename to vendor/github.com/gdamore/tcell/v2/terminfo/v/vt52/term.go
index 1390f7d35b3e..ba49f7f5ee18 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/terminfo/v/vt52/term.go
+++ b/vendor/github.com/gdamore/tcell/v2/terminfo/v/vt52/term.go
@@ -2,7 +2,7 @@
package vt52
-import "github.com/stefanhaller/tcell/v2/terminfo"
+import "github.com/gdamore/tcell/v2/terminfo"
func init() {
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terminfo/w/wy50/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/w/wy50/term.go
similarity index 96%
rename from vendor/github.com/stefanhaller/tcell/v2/terminfo/w/wy50/term.go
rename to vendor/github.com/gdamore/tcell/v2/terminfo/w/wy50/term.go
index 1b3bf4f7cdd3..beced62d5c21 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/terminfo/w/wy50/term.go
+++ b/vendor/github.com/gdamore/tcell/v2/terminfo/w/wy50/term.go
@@ -2,7 +2,7 @@
package wy50
-import "github.com/stefanhaller/tcell/v2/terminfo"
+import "github.com/gdamore/tcell/v2/terminfo"
func init() {
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terminfo/w/wy60/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/w/wy60/term.go
similarity index 96%
rename from vendor/github.com/stefanhaller/tcell/v2/terminfo/w/wy60/term.go
rename to vendor/github.com/gdamore/tcell/v2/terminfo/w/wy60/term.go
index 56a7b90d4a29..5b79310a770b 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/terminfo/w/wy60/term.go
+++ b/vendor/github.com/gdamore/tcell/v2/terminfo/w/wy60/term.go
@@ -2,7 +2,7 @@
package wy60
-import "github.com/stefanhaller/tcell/v2/terminfo"
+import "github.com/gdamore/tcell/v2/terminfo"
func init() {
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terminfo/w/wy99_ansi/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/w/wy99_ansi/term.go
similarity index 98%
rename from vendor/github.com/stefanhaller/tcell/v2/terminfo/w/wy99_ansi/term.go
rename to vendor/github.com/gdamore/tcell/v2/terminfo/w/wy99_ansi/term.go
index 35fecc85bec4..af470e46f350 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/terminfo/w/wy99_ansi/term.go
+++ b/vendor/github.com/gdamore/tcell/v2/terminfo/w/wy99_ansi/term.go
@@ -2,7 +2,7 @@
package wy99_ansi
-import "github.com/stefanhaller/tcell/v2/terminfo"
+import "github.com/gdamore/tcell/v2/terminfo"
func init() {
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terminfo/x/xfce/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/x/xfce/term.go
similarity index 97%
rename from vendor/github.com/stefanhaller/tcell/v2/terminfo/x/xfce/term.go
rename to vendor/github.com/gdamore/tcell/v2/terminfo/x/xfce/term.go
index 0a5b286b6163..d70b2e910c9b 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/terminfo/x/xfce/term.go
+++ b/vendor/github.com/gdamore/tcell/v2/terminfo/x/xfce/term.go
@@ -2,7 +2,7 @@
package xfce
-import "github.com/stefanhaller/tcell/v2/terminfo"
+import "github.com/gdamore/tcell/v2/terminfo"
func init() {
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terminfo/x/xterm/direct.go b/vendor/github.com/gdamore/tcell/v2/terminfo/x/xterm/direct.go
similarity index 98%
rename from vendor/github.com/stefanhaller/tcell/v2/terminfo/x/xterm/direct.go
rename to vendor/github.com/gdamore/tcell/v2/terminfo/x/xterm/direct.go
index 6e66f69802e6..358ebae91cfa 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/terminfo/x/xterm/direct.go
+++ b/vendor/github.com/gdamore/tcell/v2/terminfo/x/xterm/direct.go
@@ -20,7 +20,7 @@
package xterm
-import "github.com/stefanhaller/tcell/v2/terminfo"
+import "github.com/gdamore/tcell/v2/terminfo"
func init() {
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terminfo/x/xterm/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/x/xterm/term.go
similarity index 99%
rename from vendor/github.com/stefanhaller/tcell/v2/terminfo/x/xterm/term.go
rename to vendor/github.com/gdamore/tcell/v2/terminfo/x/xterm/term.go
index 9d87ef51f286..daa19c3f172f 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/terminfo/x/xterm/term.go
+++ b/vendor/github.com/gdamore/tcell/v2/terminfo/x/xterm/term.go
@@ -2,7 +2,7 @@
package xterm
-import "github.com/stefanhaller/tcell/v2/terminfo"
+import "github.com/gdamore/tcell/v2/terminfo"
func init() {
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terminfo/x/xterm_kitty/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/x/xterm_kitty/term.go
similarity index 97%
rename from vendor/github.com/stefanhaller/tcell/v2/terminfo/x/xterm_kitty/term.go
rename to vendor/github.com/gdamore/tcell/v2/terminfo/x/xterm_kitty/term.go
index 80d2c3ff01c0..ab50003e051e 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/terminfo/x/xterm_kitty/term.go
+++ b/vendor/github.com/gdamore/tcell/v2/terminfo/x/xterm_kitty/term.go
@@ -2,7 +2,7 @@
package xterm_kitty
-import "github.com/stefanhaller/tcell/v2/terminfo"
+import "github.com/gdamore/tcell/v2/terminfo"
func init() {
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terminfo/x/xterm_termite/term.go b/vendor/github.com/gdamore/tcell/v2/terminfo/x/xterm_termite/term.go
similarity index 97%
rename from vendor/github.com/stefanhaller/tcell/v2/terminfo/x/xterm_termite/term.go
rename to vendor/github.com/gdamore/tcell/v2/terminfo/x/xterm_termite/term.go
index 7b7477da6c43..f2d02210143c 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/terminfo/x/xterm_termite/term.go
+++ b/vendor/github.com/gdamore/tcell/v2/terminfo/x/xterm_termite/term.go
@@ -2,7 +2,7 @@
package xterm_termite
-import "github.com/stefanhaller/tcell/v2/terminfo"
+import "github.com/gdamore/tcell/v2/terminfo"
func init() {
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terms_default.go b/vendor/github.com/gdamore/tcell/v2/terms_default.go
similarity index 93%
rename from vendor/github.com/stefanhaller/tcell/v2/terms_default.go
rename to vendor/github.com/gdamore/tcell/v2/terms_default.go
index 3a6b462e447e..fefcf8938fb8 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/terms_default.go
+++ b/vendor/github.com/gdamore/tcell/v2/terms_default.go
@@ -20,5 +20,5 @@ package tcell
import (
// This imports the default terminal entries. To disable, use the
// tcell_minimal build tag.
- _ "github.com/stefanhaller/tcell/v2/terminfo/extended"
+ _ "github.com/gdamore/tcell/v2/terminfo/extended"
)
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terms_dynamic.go b/vendor/github.com/gdamore/tcell/v2/terms_dynamic.go
similarity index 93%
rename from vendor/github.com/stefanhaller/tcell/v2/terms_dynamic.go
rename to vendor/github.com/gdamore/tcell/v2/terms_dynamic.go
index 357c18d428ba..f552b0e8e220 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/terms_dynamic.go
+++ b/vendor/github.com/gdamore/tcell/v2/terms_dynamic.go
@@ -25,8 +25,8 @@ import (
// also don't support Android here, because you really don't want
// to run external programs there. Generally the android terminals
// will be automatically included anyway.
- "github.com/stefanhaller/tcell/v2/terminfo"
- "github.com/stefanhaller/tcell/v2/terminfo/dynamic"
+ "github.com/gdamore/tcell/v2/terminfo"
+ "github.com/gdamore/tcell/v2/terminfo/dynamic"
)
func loadDynamicTerminfo(term string) (*terminfo.Terminfo, error) {
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terms_static.go b/vendor/github.com/gdamore/tcell/v2/terms_static.go
similarity index 95%
rename from vendor/github.com/stefanhaller/tcell/v2/terms_static.go
rename to vendor/github.com/gdamore/tcell/v2/terms_static.go
index 286ef99d0e20..6d725cbccc55 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/terms_static.go
+++ b/vendor/github.com/gdamore/tcell/v2/terms_static.go
@@ -20,7 +20,7 @@ package tcell
import (
"errors"
- "github.com/stefanhaller/tcell/v2/terminfo"
+ "github.com/gdamore/tcell/v2/terminfo"
)
func loadDynamicTerminfo(_ string) (*terminfo.Terminfo, error) {
diff --git a/vendor/github.com/stefanhaller/tcell/v2/tscreen.go b/vendor/github.com/gdamore/tcell/v2/tscreen.go
similarity index 95%
rename from vendor/github.com/stefanhaller/tcell/v2/tscreen.go
rename to vendor/github.com/gdamore/tcell/v2/tscreen.go
index 3746fc1f088d..b804affd091e 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/tscreen.go
+++ b/vendor/github.com/gdamore/tcell/v2/tscreen.go
@@ -1,4 +1,4 @@
-// Copyright 2022 The TCell Authors
+// Copyright 2023 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use file except in compliance with the License.
@@ -31,10 +31,10 @@ import (
"golang.org/x/term"
"golang.org/x/text/transform"
- "github.com/stefanhaller/tcell/v2/terminfo"
+ "github.com/gdamore/tcell/v2/terminfo"
// import the stock terminals
- _ "github.com/stefanhaller/tcell/v2/terminfo/base"
+ _ "github.com/gdamore/tcell/v2/terminfo/base"
)
// NewTerminfoScreen returns a Screen that uses the stock TTY interface
@@ -94,7 +94,7 @@ func NewTerminfoScreenFromTtyTerminfo(tty Tty, ti *terminfo.Terminfo) (s Screen,
t.fallback[k] = v
}
- return t, nil
+ return &baseScreen{screenImpl: t}, nil
}
// NewTerminfoScreenFromTty returns a Screen using a custom Tty implementation.
@@ -123,7 +123,6 @@ type tScreen struct {
buf bytes.Buffer
curstyle Style
style Style
- evch chan Event
resizeQ chan bool
quit chan struct{}
keyexist map[Key]bool
@@ -159,6 +158,7 @@ type tScreen struct {
cursorStyle CursorStyle
saved *term.State
stopQ chan struct{}
+ eventQ chan Event
running bool
wg sync.WaitGroup
mouseFlags MouseFlags
@@ -173,7 +173,6 @@ func (t *tScreen) Init() error {
return e
}
- t.evch = make(chan Event, 10)
t.keychan = make(chan []byte, 10)
t.keytimer = time.NewTimer(time.Millisecond * 50)
t.charset = "UTF-8"
@@ -217,6 +216,7 @@ func (t *tScreen) Init() error {
}
t.quit = make(chan struct{})
+ t.eventQ = make(chan Event, 10)
t.Lock()
t.cx = -1
@@ -349,8 +349,8 @@ func (t *tScreen) prepareBracketedPaste() {
func (t *tScreen) prepareExtendedOSC() {
// Linux is a special beast - because it has a mouse entry, but does
// not swallow these OSC commands properly.
- if (strings.Contains(t.ti.Name, "linux")) {
- return;
+ if strings.Contains(t.ti.Name, "linux") {
+ return
}
// More stuff for limits in terminfo. This time we are applying
// the most common OSC (operating system commands). Generally
@@ -595,41 +595,6 @@ func (t *tScreen) SetStyle(style Style) {
t.Unlock()
}
-func (t *tScreen) Clear() {
- t.Fill(' ', t.style)
-}
-
-func (t *tScreen) Fill(r rune, style Style) {
- t.Lock()
- if !t.fini {
- t.cells.Fill(r, style)
- }
- t.Unlock()
-}
-
-func (t *tScreen) SetContent(x, y int, mainc rune, combc []rune, style Style) {
- t.Lock()
- if !t.fini {
- t.cells.SetContent(x, y, mainc, combc, style)
- }
- t.Unlock()
-}
-
-func (t *tScreen) GetContent(x, y int) (rune, []rune, Style, int) {
- t.Lock()
- mainc, combc, style, width := t.cells.GetContent(x, y)
- t.Unlock()
- return mainc, combc, style, width
-}
-
-func (t *tScreen) SetCell(x, y int, style Style, ch ...rune) {
- if len(ch) > 0 {
- t.SetContent(x, y, ch[0], ch[1:], style)
- } else {
- t.SetContent(x, y, ' ', nil, style)
- }
-}
-
func (t *tScreen) encodeRune(r rune, buf []byte) []byte {
nb := make([]byte, 6)
@@ -1091,18 +1056,24 @@ func (t *tScreen) Size() (int, int) {
}
func (t *tScreen) resize() {
- if w, h, e := t.tty.WindowSize(); e == nil {
- if w != t.w || h != t.h {
- t.cx = -1
- t.cy = -1
+ ws, err := t.tty.WindowSize()
+ if err != nil {
+ return
+ }
+ if ws.Width == t.w && ws.Height == t.h {
+ return
+ }
+ t.cx = -1
+ t.cy = -1
- t.cells.Resize(w, h)
- t.cells.Invalidate()
- t.h = h
- t.w = w
- ev := NewEventResize(w, h)
- _ = t.PostEvent(ev)
- }
+ t.cells.Resize(ws.Width, ws.Height)
+ t.cells.Invalidate()
+ t.h = ws.Height
+ t.w = ws.Width
+ ev := &EventResize{t: time.Now(), ws: ws}
+ select {
+ case t.eventQ <- ev:
+ default:
}
}
@@ -1121,39 +1092,6 @@ func (t *tScreen) nColors() int {
return t.ti.Colors
}
-func (t *tScreen) ChannelEvents(ch chan<- Event, quit <-chan struct{}) {
- defer close(ch)
- for {
- select {
- case <-quit:
- return
- case <-t.quit:
- return
- case ev := <-t.evch:
- select {
- case <-quit:
- return
- case <-t.quit:
- return
- case ch <- ev:
- }
- }
- }
-}
-
-func (t *tScreen) PollEvent() Event {
- select {
- case <-t.quit:
- return nil
- case ev := <-t.evch:
- return ev
- }
-}
-
-func (t *tScreen) HasPendingEvent() bool {
- return len(t.evch) > 0
-}
-
// vtACSNames is a map of bytes defined by terminfo that are used in
// the terminals Alternate Character Set to represent other glyphs.
// For example, the upper left corner of the box drawing set can be
@@ -1218,19 +1156,6 @@ func (t *tScreen) buildAcsMap() {
}
}
-func (t *tScreen) PostEventWait(ev Event) {
- t.evch <- ev
-}
-
-func (t *tScreen) PostEvent(ev Event) error {
- select {
- case t.evch <- ev:
- return nil
- default:
- return ErrEventQFull
- }
-}
-
func (t *tScreen) clip(x, y int) (int, int) {
w, h := t.cells.Size()
if x < 0 {
@@ -1311,6 +1236,7 @@ func (t *tScreen) parseSgrMouse(buf *bytes.Buffer, evs *[]Event) (bool, bool) {
dig := false
neg := false
motion := false
+ scroll := false
i := 0
val := 0
@@ -1385,6 +1311,7 @@ func (t *tScreen) parseSgrMouse(buf *bytes.Buffer, evs *[]Event) (bool, bool) {
y = val - 1
motion = (btn & 32) != 0
+ scroll = (btn & 0x42) == 0x40
btn &^= 32
if b[i] == 'm' {
// mouse release, clear all buttons
@@ -1403,7 +1330,7 @@ func (t *tScreen) parseSgrMouse(buf *bytes.Buffer, evs *[]Event) (bool, bool) {
btn |= 3
btn &^= 0x40
}
- } else {
+ } else if !scroll {
t.buttondn = true
}
// consume the event bytes
@@ -1590,7 +1517,11 @@ func (t *tScreen) scanInput(buf *bytes.Buffer, expire bool) {
evs := t.collectEventsFromInput(buf, expire)
for _, ev := range evs {
- t.PostEventWait(ev)
+ select {
+ case t.eventQ <- ev:
+ case <-t.quit:
+ return
+ }
}
}
@@ -1659,7 +1590,7 @@ func (t *tScreen) collectEventsFromInput(buf *bytes.Buffer, expire bool) []Event
_, _ = buf.ReadByte()
continue
}
- // Nothing was going to match, or we timed out
+ // Nothing was going to match, or we timed-out
// waiting for more data -- just deliver the characters
// to the app & let them sort it out. Possibly we
// should only do this for control characters like ESC.
@@ -1754,7 +1685,10 @@ func (t *tScreen) inputLoop(stopQ chan struct{}) {
running := t.running
t.Unlock()
if running {
- _ = t.PostEvent(NewEventError(e))
+ select {
+ case t.eventQ <- NewEventError(e):
+ case <-t.quit:
+ }
}
return
}
@@ -1850,6 +1784,10 @@ func (t *tScreen) Resume() error {
return t.engage()
}
+func (t *tScreen) Tty() (Tty, bool) {
+ return t.tty, true
+}
+
// engage is used to place the terminal in raw mode and establish screen size, etc.
// Think of this is as tcell "engaging" the clutch, as it's going to be driving the
// terminal interface.
@@ -1872,8 +1810,8 @@ func (t *tScreen) engage() error {
return err
}
t.running = true
- if w, h, err := t.tty.WindowSize(); err == nil && w != 0 && h != 0 {
- t.cells.Resize(w, h)
+ if ws, err := t.tty.WindowSize(); err == nil && ws.Width != 0 && ws.Height != 0 {
+ t.cells.Resize(ws.Width, ws.Height)
}
stopQ := make(chan struct{})
t.stopQ = stopQ
@@ -1922,7 +1860,7 @@ func (t *tScreen) disengage() {
t.cells.Resize(0, 0)
t.TPuts(ti.ShowCursor)
if t.cursorStyles != nil && t.cursorStyle != CursorStyleDefault {
- t.TPuts(t.cursorStyles[t.cursorStyle])
+ t.TPuts(t.cursorStyles[CursorStyleDefault])
}
t.TPuts(ti.ResetFgBg)
t.TPuts(ti.AttrOff)
@@ -1948,3 +1886,15 @@ func (t *tScreen) finalize() {
t.disengage()
_ = t.tty.Close()
}
+
+func (t *tScreen) StopQ() <-chan struct{} {
+ return t.stopQ
+}
+
+func (t *tScreen) EventQ() chan Event {
+ return t.eventQ
+}
+
+func (t *tScreen) GetCells() *CellBuffer {
+ return &t.cells
+}
diff --git a/vendor/github.com/stefanhaller/tcell/v2/tscreen_stub.go b/vendor/github.com/gdamore/tcell/v2/tscreen_stub.go
similarity index 100%
rename from vendor/github.com/stefanhaller/tcell/v2/tscreen_stub.go
rename to vendor/github.com/gdamore/tcell/v2/tscreen_stub.go
diff --git a/vendor/github.com/stefanhaller/tcell/v2/tscreen_unix.go b/vendor/github.com/gdamore/tcell/v2/tscreen_unix.go
similarity index 100%
rename from vendor/github.com/stefanhaller/tcell/v2/tscreen_unix.go
rename to vendor/github.com/gdamore/tcell/v2/tscreen_unix.go
diff --git a/vendor/github.com/stefanhaller/tcell/v2/tty.go b/vendor/github.com/gdamore/tcell/v2/tty.go
similarity index 98%
rename from vendor/github.com/stefanhaller/tcell/v2/tty.go
rename to vendor/github.com/gdamore/tcell/v2/tty.go
index 1e7c02e4ecb1..8bb1ac5066cf 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/tty.go
+++ b/vendor/github.com/gdamore/tcell/v2/tty.go
@@ -50,7 +50,7 @@ type Tty interface {
// WindowSize is called to determine the terminal dimensions. This might be determined
// by an ioctl or other means.
- WindowSize() (width int, height int, err error)
+ WindowSize() (WindowSize, error)
io.ReadWriteCloser
}
diff --git a/vendor/github.com/stefanhaller/tcell/v2/tty_unix.go b/vendor/github.com/gdamore/tcell/v2/tty_unix.go
similarity index 92%
rename from vendor/github.com/stefanhaller/tcell/v2/tty_unix.go
rename to vendor/github.com/gdamore/tcell/v2/tty_unix.go
index 05d5a7dd0cf6..ca82d83d8401 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/tty_unix.go
+++ b/vendor/github.com/gdamore/tcell/v2/tty_unix.go
@@ -27,6 +27,7 @@ import (
"syscall"
"time"
+ "golang.org/x/sys/unix"
"golang.org/x/term"
)
@@ -136,11 +137,14 @@ func (tty *devTty) Stop() error {
return nil
}
-func (tty *devTty) WindowSize() (int, int, error) {
- w, h, err := term.GetSize(tty.fd)
+func (tty *devTty) WindowSize() (WindowSize, error) {
+ size := WindowSize{}
+ ws, err := unix.IoctlGetWinsize(tty.fd, unix.TIOCGWINSZ)
if err != nil {
- return 0, 0, err
+ return size, err
}
+ w := int(ws.Col)
+ h := int(ws.Row)
if w == 0 {
w, _ = strconv.Atoi(os.Getenv("COLUMNS"))
}
@@ -153,7 +157,11 @@ func (tty *devTty) WindowSize() (int, int, error) {
if h == 0 {
h = 25 // default
}
- return w, h, nil
+ size.Width = w
+ size.Height = h
+ size.PixelWidth = int(ws.Xpixel)
+ size.PixelHeight = int(ws.Ypixel)
+ return size, nil
}
func (tty *devTty) NotifyResize(cb func()) {
diff --git a/vendor/github.com/stefanhaller/tcell/v2/wscreen.go b/vendor/github.com/gdamore/tcell/v2/wscreen.go
similarity index 86%
rename from vendor/github.com/stefanhaller/tcell/v2/wscreen.go
rename to vendor/github.com/gdamore/tcell/v2/wscreen.go
index 080472065e44..137968cc479b 100644
--- a/vendor/github.com/stefanhaller/tcell/v2/wscreen.go
+++ b/vendor/github.com/gdamore/tcell/v2/wscreen.go
@@ -19,6 +19,7 @@ package tcell
import (
"errors"
+ "github.com/gdamore/tcell/v2/terminfo"
"strings"
"sync"
"syscall/js"
@@ -29,7 +30,7 @@ func NewTerminfoScreen() (Screen, error) {
t := &wScreen{}
t.fallback = make(map[rune]string)
- return t, nil
+ return &baseScreen{screenImpl: t}, nil
}
type wScreen struct {
@@ -48,6 +49,7 @@ type wScreen struct {
quit chan struct{}
evch chan Event
fallback map[rune]string
+ finiOnce sync.Once
sync.Mutex
}
@@ -69,7 +71,9 @@ func (t *wScreen) Init() error {
}
func (t *wScreen) Fini() {
- close(t.quit)
+ t.finiOnce.Do(func() {
+ close(t.quit)
+ })
}
func (t *wScreen) SetStyle(style Style) {
@@ -78,65 +82,34 @@ func (t *wScreen) SetStyle(style Style) {
t.Unlock()
}
-func (t *wScreen) Clear() {
- t.Fill(' ', t.style)
-}
-
-func (t *wScreen) Fill(r rune, style Style) {
- t.Lock()
- t.cells.Fill(r, style)
- t.Unlock()
-}
-
-func (t *wScreen) SetContent(x, y int, mainc rune, combc []rune, style Style) {
- t.Lock()
- t.cells.SetContent(x, y, mainc, combc, style)
- t.Unlock()
-}
-
-func (t *wScreen) GetContent(x, y int) (rune, []rune, Style, int) {
- t.Lock()
- mainc, combc, style, width := t.cells.GetContent(x, y)
- t.Unlock()
- return mainc, combc, style, width
-}
-
-func (t *wScreen) SetCell(x, y int, style Style, ch ...rune) {
- if len(ch) > 0 {
- t.SetContent(x, y, ch[0], ch[1:], style)
- } else {
- t.SetContent(x, y, ' ', nil, style)
- }
-}
-
// paletteColor gives a more natural palette color actually matching
// typical XTerm. We might in the future want to permit styling these
// via CSS.
var palette = map[Color]int32{
- ColorBlack: 0x000000,
- ColorMaroon: 0xcd0000,
- ColorGreen: 0x00cd00,
- ColorOlive: 0xcdcd00,
- ColorNavy: 0x0000ee,
- ColorPurple: 0xcd00cd,
- ColorTeal: 0x00cdcd,
- ColorSilver: 0xe5e5e5,
- ColorGray: 0x7f7f7f,
- ColorRed: 0xff0000,
- ColorLime: 0x00ff00,
- ColorYellow: 0xffff00,
- ColorBlue: 0x5c5cff,
+ ColorBlack: 0x000000,
+ ColorMaroon: 0xcd0000,
+ ColorGreen: 0x00cd00,
+ ColorOlive: 0xcdcd00,
+ ColorNavy: 0x0000ee,
+ ColorPurple: 0xcd00cd,
+ ColorTeal: 0x00cdcd,
+ ColorSilver: 0xe5e5e5,
+ ColorGray: 0x7f7f7f,
+ ColorRed: 0xff0000,
+ ColorLime: 0x00ff00,
+ ColorYellow: 0xffff00,
+ ColorBlue: 0x5c5cff,
ColorFuchsia: 0xff00ff,
- ColorAqua: 0x00ffff,
- ColorWhite: 0xffffff,
+ ColorAqua: 0x00ffff,
+ ColorWhite: 0xffffff,
}
func paletteColor(c Color) int32 {
- if (c.IsRGB()) {
- return int32(c & 0xffffff);
+ if c.IsRGB() {
+ return int32(c & 0xffffff)
}
- if (c >= ColorBlack && c <= ColorWhite) {
+ if c >= ColorBlack && c <= ColorWhite {
return palette[c]
}
return c.Hex()
@@ -154,11 +127,11 @@ func (t *wScreen) drawCell(x, y int) int {
}
fg, bg := paletteColor(style.fg), paletteColor(style.bg)
- if (fg == -1) {
- fg = 0xe5e5e5;
+ if fg == -1 {
+ fg = 0xe5e5e5
}
- if (bg == -1) {
- bg = 0x000000;
+ if bg == -1 {
+ bg = 0x000000
}
var combcarr []interface{} = make([]interface{}, len(combc))
@@ -275,6 +248,18 @@ func (t *wScreen) enablePasting(on bool) {
}
}
+func (t *wScreen) EnableFocus() {
+ t.Lock()
+ js.Global().Set("onFocus", js.FuncOf(t.onFocus))
+ t.Unlock()
+}
+
+func (t *wScreen) DisableFocus() {
+ t.Lock()
+ js.Global().Set("onFocus", js.FuncOf(t.unset))
+ t.Unlock()
+}
+
func (t *wScreen) Size() (int, int) {
t.Lock()
w, h := t.w, t.h
@@ -290,52 +275,6 @@ func (t *wScreen) Colors() int {
return 16777216 // 256 ^ 3
}
-func (t *wScreen) ChannelEvents(ch chan<- Event, quit <-chan struct{}) {
- defer close(ch)
- for {
- select {
- case <-quit:
- return
- case <-t.quit:
- return
- case ev := <-t.evch:
- select {
- case <-quit:
- return
- case <-t.quit:
- return
- case ch <- ev:
- }
- }
- }
-}
-
-func (t *wScreen) PollEvent() Event {
- select {
- case <-t.quit:
- return nil
- case ev := <-t.evch:
- return ev
- }
-}
-
-func (t *wScreen) HasPendingEvent() bool {
- return len(t.evch) > 0
-}
-
-func (t *wScreen) PostEventWait(ev Event) {
- t.evch <- ev
-}
-
-func (t *wScreen) PostEvent(ev Event) error {
- select {
- case t.evch <- ev:
- return nil
- default:
- return ErrEventQFull
- }
-}
-
func (t *wScreen) clip(x, y int) (int, int) {
w, h := t.cells.Size()
if x < 0 {
@@ -353,6 +292,13 @@ func (t *wScreen) clip(x, y int) (int, int) {
return x, y
}
+func (t *wScreen) postEvent(ev Event) {
+ select {
+ case t.evch <- ev:
+ case <-t.quit:
+ }
+}
+
func (t *wScreen) onMouseEvent(this js.Value, args []js.Value) interface{} {
mod := ModNone
button := ButtonNone
@@ -384,7 +330,7 @@ func (t *wScreen) onMouseEvent(this js.Value, args []js.Value) interface{} {
mod |= ModCtrl
}
- t.PostEventWait(NewEventMouse(args[0].Int(), args[1].Int(), button, mod))
+ t.postEvent(NewEventMouse(args[0].Int(), args[1].Int(), button, mod))
return nil
}
@@ -416,25 +362,30 @@ func (t *wScreen) onKeyEvent(this js.Value, args []js.Value) interface{} {
// check for special case of Ctrl + key
if mod == ModCtrl {
if k, ok := WebKeyNames["Ctrl-"+strings.ToLower(key)]; ok {
- t.PostEventWait(NewEventKey(k, 0, mod))
+ t.postEvent(NewEventKey(k, 0, mod))
return nil
}
}
// next try function keys
if k, ok := WebKeyNames[key]; ok {
- t.PostEventWait(NewEventKey(k, 0, mod))
+ t.postEvent(NewEventKey(k, 0, mod))
return nil
}
// finally try normal, printable chars
r, _ := utf8.DecodeRuneInString(key)
- t.PostEventWait(NewEventKey(KeyRune, r, mod))
+ t.postEvent(NewEventKey(KeyRune, r, mod))
return nil
}
func (t *wScreen) onPaste(this js.Value, args []js.Value) interface{} {
- t.PostEventWait(NewEventPaste(args[0].Bool()))
+ t.postEvent(NewEventPaste(args[0].Bool()))
+ return nil
+}
+
+func (t *wScreen) onFocus(this js.Value, args []js.Value) interface{} {
+ t.postEvent(NewEventFocus(args[0].Bool()))
return nil
}
@@ -501,7 +452,7 @@ func (t *wScreen) SetSize(w, h int) {
t.cells.Resize(w, h)
js.Global().Call("resize", w, h)
t.w, t.h = w, h
- t.PostEvent(NewEventResize(w, h))
+ t.postEvent(NewEventResize(w, h))
}
func (t *wScreen) Resize(int, int, int, int) {}
@@ -544,6 +495,22 @@ func (t *wScreen) Beep() error {
return nil
}
+func (t *wScreen) Tty() (Tty, bool) {
+ return nil, false
+}
+
+func (t *wScreen) GetCells() *CellBuffer {
+ return &t.cells
+}
+
+func (t *wScreen) EventQ() chan Event {
+ return t.evch
+}
+
+func (t *wScreen) StopQ() <-chan struct{} {
+ return t.quit
+}
+
// WebKeyNames maps string names reported from HTML
// (KeyboardEvent.key) to tcell accepted keys.
var WebKeyNames = map[string]Key{
@@ -676,3 +643,7 @@ var curStyleClasses = map[CursorStyle]string{
CursorStyleBlinkingBar: "cursor-blinking-bar",
CursorStyleSteadyBar: "cursor-steady-bar",
}
+
+func LookupTerminfo(name string) (ti *terminfo.Terminfo, e error) {
+ return nil, errors.New("LookupTermInfo not supported")
+}
diff --git a/vendor/github.com/jesseduffield/gocui/attribute.go b/vendor/github.com/jesseduffield/gocui/attribute.go
index c413bd36530d..b6cbf39d08b9 100644
--- a/vendor/github.com/jesseduffield/gocui/attribute.go
+++ b/vendor/github.com/jesseduffield/gocui/attribute.go
@@ -4,7 +4,7 @@
package gocui
-import "github.com/stefanhaller/tcell/v2"
+import "github.com/gdamore/tcell/v2"
// Attribute affects the presentation of characters, such as color, boldness, etc.
type Attribute uint64
diff --git a/vendor/github.com/jesseduffield/gocui/gui.go b/vendor/github.com/jesseduffield/gocui/gui.go
index 80303fdca5bd..e23a2716c478 100644
--- a/vendor/github.com/jesseduffield/gocui/gui.go
+++ b/vendor/github.com/jesseduffield/gocui/gui.go
@@ -12,9 +12,9 @@ import (
"sync"
"time"
+ "github.com/gdamore/tcell/v2"
"github.com/go-errors/errors"
"github.com/mattn/go-runewidth"
- "github.com/stefanhaller/tcell/v2"
)
// OutputMode represents an output mode, which determines how colors
diff --git a/vendor/github.com/jesseduffield/gocui/keybinding.go b/vendor/github.com/jesseduffield/gocui/keybinding.go
index 52cfb8a2700b..bee180aea3c9 100644
--- a/vendor/github.com/jesseduffield/gocui/keybinding.go
+++ b/vendor/github.com/jesseduffield/gocui/keybinding.go
@@ -7,7 +7,7 @@ package gocui
import (
"strings"
- "github.com/stefanhaller/tcell/v2"
+ "github.com/gdamore/tcell/v2"
)
// Key represents special keys or keys combinations.
diff --git a/vendor/github.com/jesseduffield/gocui/tcell_driver.go b/vendor/github.com/jesseduffield/gocui/tcell_driver.go
index edd5509ec97b..a3153d4c716f 100644
--- a/vendor/github.com/jesseduffield/gocui/tcell_driver.go
+++ b/vendor/github.com/jesseduffield/gocui/tcell_driver.go
@@ -5,8 +5,8 @@
package gocui
import (
+ "github.com/gdamore/tcell/v2"
"github.com/mattn/go-runewidth"
- "github.com/stefanhaller/tcell/v2"
)
// We probably don't want this being a global variable for YOLO for now
diff --git a/vendor/github.com/jesseduffield/gocui/view.go b/vendor/github.com/jesseduffield/gocui/view.go
index e3f03d0384cf..51c968b14255 100644
--- a/vendor/github.com/jesseduffield/gocui/view.go
+++ b/vendor/github.com/jesseduffield/gocui/view.go
@@ -13,9 +13,9 @@ import (
"unicode"
"unicode/utf8"
+ "github.com/gdamore/tcell/v2"
"github.com/go-errors/errors"
"github.com/mattn/go-runewidth"
- "github.com/stefanhaller/tcell/v2"
)
// Constants for overlapping edges
diff --git a/vendor/github.com/stefanhaller/tcell/v2/terminfo/extended/extended.go b/vendor/github.com/stefanhaller/tcell/v2/terminfo/extended/extended.go
deleted file mode 100644
index 9e5845656357..000000000000
--- a/vendor/github.com/stefanhaller/tcell/v2/terminfo/extended/extended.go
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright 2020 The TCell Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use file except in compliance with the License.
-// You may obtain a copy of the license at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package extended contains an extended set of terminal descriptions.
-// Applications desiring to have a better chance of Just Working by
-// default should include this package. This will significantly increase
-// the size of the program.
-package extended
-
-import (
- // The following imports just register themselves --
- // these are the terminal types we aggregate in this package.
- _ "github.com/stefanhaller/tcell/v2/terminfo/a/aixterm"
- _ "github.com/stefanhaller/tcell/v2/terminfo/a/alacritty"
- _ "github.com/stefanhaller/tcell/v2/terminfo/a/ansi"
- _ "github.com/stefanhaller/tcell/v2/terminfo/b/beterm"
- _ "github.com/stefanhaller/tcell/v2/terminfo/c/cygwin"
- _ "github.com/stefanhaller/tcell/v2/terminfo/d/dtterm"
- _ "github.com/stefanhaller/tcell/v2/terminfo/e/emacs"
- _ "github.com/stefanhaller/tcell/v2/terminfo/f/foot"
- _ "github.com/stefanhaller/tcell/v2/terminfo/g/gnome"
- _ "github.com/stefanhaller/tcell/v2/terminfo/h/hpterm"
- _ "github.com/stefanhaller/tcell/v2/terminfo/k/konsole"
- _ "github.com/stefanhaller/tcell/v2/terminfo/k/kterm"
- _ "github.com/stefanhaller/tcell/v2/terminfo/l/linux"
- _ "github.com/stefanhaller/tcell/v2/terminfo/p/pcansi"
- _ "github.com/stefanhaller/tcell/v2/terminfo/r/rxvt"
- _ "github.com/stefanhaller/tcell/v2/terminfo/s/screen"
- _ "github.com/stefanhaller/tcell/v2/terminfo/s/simpleterm"
- _ "github.com/stefanhaller/tcell/v2/terminfo/s/sun"
- _ "github.com/stefanhaller/tcell/v2/terminfo/t/termite"
- _ "github.com/stefanhaller/tcell/v2/terminfo/t/tmux"
- _ "github.com/stefanhaller/tcell/v2/terminfo/v/vt100"
- _ "github.com/stefanhaller/tcell/v2/terminfo/v/vt102"
- _ "github.com/stefanhaller/tcell/v2/terminfo/v/vt220"
- _ "github.com/stefanhaller/tcell/v2/terminfo/v/vt320"
- _ "github.com/stefanhaller/tcell/v2/terminfo/v/vt400"
- _ "github.com/stefanhaller/tcell/v2/terminfo/v/vt420"
- _ "github.com/stefanhaller/tcell/v2/terminfo/v/vt52"
- _ "github.com/stefanhaller/tcell/v2/terminfo/w/wy50"
- _ "github.com/stefanhaller/tcell/v2/terminfo/w/wy60"
- _ "github.com/stefanhaller/tcell/v2/terminfo/w/wy99_ansi"
- _ "github.com/stefanhaller/tcell/v2/terminfo/x/xfce"
- _ "github.com/stefanhaller/tcell/v2/terminfo/x/xterm"
- _ "github.com/stefanhaller/tcell/v2/terminfo/x/xterm_kitty"
- _ "github.com/stefanhaller/tcell/v2/terminfo/x/xterm_termite"
-)
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 752aad6e0d1e..1121027e05e7 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -39,6 +39,47 @@ github.com/fsmiamoto/git-todo-parser/todo
# github.com/gdamore/encoding v1.0.0
## explicit; go 1.9
github.com/gdamore/encoding
+# github.com/gdamore/tcell/v2 v2.7.1-0.20240103180601-96e29905643b
+## explicit; go 1.12
+github.com/gdamore/tcell/v2
+github.com/gdamore/tcell/v2/terminfo
+github.com/gdamore/tcell/v2/terminfo/a/aixterm
+github.com/gdamore/tcell/v2/terminfo/a/alacritty
+github.com/gdamore/tcell/v2/terminfo/a/ansi
+github.com/gdamore/tcell/v2/terminfo/b/beterm
+github.com/gdamore/tcell/v2/terminfo/base
+github.com/gdamore/tcell/v2/terminfo/c/cygwin
+github.com/gdamore/tcell/v2/terminfo/d/dtterm
+github.com/gdamore/tcell/v2/terminfo/dynamic
+github.com/gdamore/tcell/v2/terminfo/e/emacs
+github.com/gdamore/tcell/v2/terminfo/extended
+github.com/gdamore/tcell/v2/terminfo/f/foot
+github.com/gdamore/tcell/v2/terminfo/g/gnome
+github.com/gdamore/tcell/v2/terminfo/h/hpterm
+github.com/gdamore/tcell/v2/terminfo/k/konsole
+github.com/gdamore/tcell/v2/terminfo/k/kterm
+github.com/gdamore/tcell/v2/terminfo/l/linux
+github.com/gdamore/tcell/v2/terminfo/p/pcansi
+github.com/gdamore/tcell/v2/terminfo/r/rxvt
+github.com/gdamore/tcell/v2/terminfo/s/screen
+github.com/gdamore/tcell/v2/terminfo/s/simpleterm
+github.com/gdamore/tcell/v2/terminfo/s/sun
+github.com/gdamore/tcell/v2/terminfo/t/termite
+github.com/gdamore/tcell/v2/terminfo/t/tmux
+github.com/gdamore/tcell/v2/terminfo/v/vt100
+github.com/gdamore/tcell/v2/terminfo/v/vt102
+github.com/gdamore/tcell/v2/terminfo/v/vt220
+github.com/gdamore/tcell/v2/terminfo/v/vt320
+github.com/gdamore/tcell/v2/terminfo/v/vt400
+github.com/gdamore/tcell/v2/terminfo/v/vt420
+github.com/gdamore/tcell/v2/terminfo/v/vt52
+github.com/gdamore/tcell/v2/terminfo/w/wy50
+github.com/gdamore/tcell/v2/terminfo/w/wy60
+github.com/gdamore/tcell/v2/terminfo/w/wy99_ansi
+github.com/gdamore/tcell/v2/terminfo/x/xfce
+github.com/gdamore/tcell/v2/terminfo/x/xterm
+github.com/gdamore/tcell/v2/terminfo/x/xterm_kitty
+github.com/gdamore/tcell/v2/terminfo/x/xterm_termite
# github.com/go-errors/errors v1.5.1
## explicit; go 1.14
github.com/go-errors/errors
@@ -132,7 +173,7 @@ github.com/jesseduffield/go-git/v5/utils/merkletrie/filesystem
github.com/jesseduffield/go-git/v5/utils/merkletrie/index
github.com/jesseduffield/go-git/v5/utils/merkletrie/internal/frame
github.com/jesseduffield/go-git/v5/utils/merkletrie/noder
-# github.com/jesseduffield/gocui v0.3.1-0.20231209142059-968d8b62e1ef
+# github.com/jesseduffield/gocui v0.3.1-0.20240103192639-2874168c14db
## explicit; go 1.12
github.com/jesseduffield/gocui
# github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10
@@ -232,47 +273,6 @@ github.com/spf13/afero/mem
# github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad
## explicit
github.com/spkg/bom
-# github.com/stefanhaller/tcell/v2 v2.6.2-0.20230806061358-2dfa11eddb68
-## explicit; go 1.12
-github.com/stefanhaller/tcell/v2
-github.com/stefanhaller/tcell/v2/terminfo
-github.com/stefanhaller/tcell/v2/terminfo/a/aixterm
-github.com/stefanhaller/tcell/v2/terminfo/a/alacritty
-github.com/stefanhaller/tcell/v2/terminfo/a/ansi
-github.com/stefanhaller/tcell/v2/terminfo/b/beterm
-github.com/stefanhaller/tcell/v2/terminfo/base
-github.com/stefanhaller/tcell/v2/terminfo/c/cygwin
-github.com/stefanhaller/tcell/v2/terminfo/d/dtterm
-github.com/stefanhaller/tcell/v2/terminfo/dynamic
-github.com/stefanhaller/tcell/v2/terminfo/e/emacs
-github.com/stefanhaller/tcell/v2/terminfo/extended
-github.com/stefanhaller/tcell/v2/terminfo/f/foot
-github.com/stefanhaller/tcell/v2/terminfo/g/gnome
-github.com/stefanhaller/tcell/v2/terminfo/h/hpterm
-github.com/stefanhaller/tcell/v2/terminfo/k/konsole
-github.com/stefanhaller/tcell/v2/terminfo/k/kterm
-github.com/stefanhaller/tcell/v2/terminfo/l/linux
-github.com/stefanhaller/tcell/v2/terminfo/p/pcansi
-github.com/stefanhaller/tcell/v2/terminfo/r/rxvt
-github.com/stefanhaller/tcell/v2/terminfo/s/screen
-github.com/stefanhaller/tcell/v2/terminfo/s/simpleterm
-github.com/stefanhaller/tcell/v2/terminfo/s/sun
-github.com/stefanhaller/tcell/v2/terminfo/t/termite
-github.com/stefanhaller/tcell/v2/terminfo/t/tmux
-github.com/stefanhaller/tcell/v2/terminfo/v/vt100
-github.com/stefanhaller/tcell/v2/terminfo/v/vt102
-github.com/stefanhaller/tcell/v2/terminfo/v/vt220
-github.com/stefanhaller/tcell/v2/terminfo/v/vt320
-github.com/stefanhaller/tcell/v2/terminfo/v/vt400
-github.com/stefanhaller/tcell/v2/terminfo/v/vt420
-github.com/stefanhaller/tcell/v2/terminfo/v/vt52
-github.com/stefanhaller/tcell/v2/terminfo/w/wy50
-github.com/stefanhaller/tcell/v2/terminfo/w/wy60
-github.com/stefanhaller/tcell/v2/terminfo/w/wy99_ansi
-github.com/stefanhaller/tcell/v2/terminfo/x/xfce
-github.com/stefanhaller/tcell/v2/terminfo/x/xterm
-github.com/stefanhaller/tcell/v2/terminfo/x/xterm_kitty
-github.com/stefanhaller/tcell/v2/terminfo/x/xterm_termite
# github.com/stretchr/testify v1.8.1
## explicit; go 1.13
github.com/stretchr/testify/assert
From caf6a3629d7a261757e6e9666806aefbdd2d68a1 Mon Sep 17 00:00:00 2001
From: Jesse Duffield
Date: Tue, 9 Jan 2024 14:36:23 +1100
Subject: [PATCH 032/280] Add codebase guide
---
CONTRIBUTING.md | 9 ++++
docs/dev/Codebase_Guide.md | 91 ++++++++++++++++++++++++++++++++++++++
docs/dev/README.md | 3 +-
3 files changed, 102 insertions(+), 1 deletion(-)
create mode 100644 docs/dev/Codebase_Guide.md
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index a723fda9d95c..861c7fd35961 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -10,6 +10,15 @@ before making a change.
[This video](https://www.youtube.com/watch?v=kNavnhzZHtk) walks through the process of adding a small feature to lazygit. If you have no idea where to start, watching that video is a good first step.
+## Codebase guide
+
+[This doc](./docs/dev/Codebase_Guide.md) explains:
+* what the different packages in the codebase are for
+* where important files live
+* important concepts in the code
+* how the event loop works
+* other useful information
+
## All code changes happen through Pull Requests
Pull requests are the best way to propose changes to the codebase. We actively
diff --git a/docs/dev/Codebase_Guide.md b/docs/dev/Codebase_Guide.md
new file mode 100644
index 000000000000..6803088d2a23
--- /dev/null
+++ b/docs/dev/Codebase_Guide.md
@@ -0,0 +1,91 @@
+# Lazygit Codebase Guide
+
+## Packages
+
+* `pkg/app`: Contains startup code, inititalises a bunch of stuff like logging, the user config, etc, before starting the gui. Catches and handles some errors that the gui raises.
+* `pkg/app/daemon`: Contains code relating to the lazygit daemon. This could be better named: it's is not a daemon in the sense that it's a long-running background process; rather it's a short-lived background process that we pass to git for certain tasks, like GIT_EDITOR for when we want to set the TODO file for an interactive rebase.
+* `pkg/cheatsheet`: Generates the keybinding cheatsheets in `docs/keybindings`.
+* `pkg/commands/git_commands`: All communication to the git binary happens here. So for example there's a `Checkout` method which calls `git checkout`.
+* `pkg/commands/oscommands`: Contains code for talking to the OS, and for invoking commands in general
+* `pkg/commands/git_config`: Reading of the git config all happens here.
+* `pkg/commands/hosting_service`: Contains code that is specific to git hosting services (aka forges).
+* `pkg/commands/models`: Contains model structs that represent commits, branches, files, etc.
+* `pkg/commands/patch`: Contains code for parsing and working with git patches
+* `pkg/common`: Contains the `Common` struct which holds common dependencies like the logger, i18n, and the user config. Most structs in the code will have a field named `c` which holds a common struct (or a derivative of the common struct).
+* `pkg/config`: Contains code relating to the Lazygit user config. Specifically `pkg/config/user_config/go` defines the user config struct and its default values.
+* `pkg/constants`: Contains some constant strings (e.g. links to docs)
+* `pkg/env`: Contains code relating to setting/getting environment variables
+* `pkg/i18n`: Contains internationalised strings
+* `pkg/integration`: Contains end-to-end tests
+* `pkg/jsonschema`: Contains generator for user config JSON schema.
+* `pkg/logs`: Contains code for instantiating the logger and for tailing the logs via `lazygit --logs`
+* `pkg/tasks`: Contains code for running asynchronous tasks: mostly related to efficiently rendering command output to the main window.
+* `pkg/theme`: Contains code related to colour themes.
+* `pkg/updates`: Contains code related to Lazygit updates (checking for update, download and installing the update)
+* `pkg/utils`: Contains lots of low-level helper functions
+* `pkg/gui`: Contains code related to the gui. We've still got a God Struct in the form of our Gui struct, but over time code has been moved out into contexts, controllers, and helpers, and we intend to continue moving more code out over time.
+* `pkg/gui/context`: Contains code relating to contexts. There is a context for each view e.g. a branches context, a tags context, etc. Contexts manage state related to the view and receive keypresses.
+* `pkg/gui/controllers`: Contains code relating to controllers. Controllers define a list of keybindings and their associated handlers. One controller can be assigned to multiple contexts, and one context can contain multiple controllers.
+* `pkg/gui/controllers/helpers`: Contains code that is shared between multiple controllers.
+* `pkg/gui/filetree`: Contains code relating to the representation of filetrees.
+* `pkg/gui/keybindings`: Contains code for mapping between keybindings and their labels
+* `pkg/gui/mergeconflicts`: Contains code relating to the handling of merge conflicts
+* `pkg/gui/modes`: Contains code relating to the state of different modes e.g. cherry picking mode, rebase mode.
+* `pkg/gui/patch_exploring`: Contains code relating to the state of patch-oriented views like the staging view.
+* `pkg/gui/popup`: Contains code that lets you easily raise popups
+* `pkg/gui/presentation`: Contains presentation code i.e. code concerned with rendering content inside views
+* `pkg/gui/services/custom_commands`: Contains code related to user-defined custom commands.
+* `pkg/gui/status`: Contains code for invoking loaders and toasts
+* `pkg/gui/style`: Contains code for specifying text styles (colour, bold, etc)
+* `pkg/gui/types`: Contains various gui-specific types and interfaces. Lots of code lives here to avoid circular dependencies
+* `vendor/github.com/jesseduffield/gocui`: Gocui is the underlying library used for handling the gui event loop, handling keypresses, and rendering the UI. It defines the View struct which our own context structs build upon.
+
+## Important files
+
+* `pkg/config/user_config.go`: defines the user config and default values
+* `pkg/gui/keybindings.go`: defines keybindings which have not yet been moved into a controller (originally all keybindings were defined here)
+* `pkg/gui/controllers.go`: links up controllers with contexts
+* `pkg/gui/controllers/helpers/helpers.go`: defines all the different helper structs
+* `pkg/commands/git.go`: defines all the different git command structs
+* `pkg/gui/gui.go`: defines the top-level gui state and gui initialisation/run code
+* `pkg/gui/layout.go`: defines what happens on each render
+* `pkg/gui/controllers/helpers/window_arrangement_helper.go`: defines the layout of the UI and the size/position of each window
+* `pkg/gui/context/context.go`: defines the different contexts
+* `pkg/gui/context/setup.go`: defines initialisation code for all contexts
+* `pkg/gui/context/context.go`: manages the lifecycle of contexts, the context stack, and focus changes.
+* `pkg/gui/types/views.go`: defines views
+* `pkg/gui/views.go`: defines the ordering of views (front to back) and their initialisation code
+* `pkg/gui/gui_common.go`: defines gui-specific methods that all controllers and helpers have access to
+* `pkg/i18n/english.go`: defines the set of i18n strings and their English values
+* `pkg/gui/controllers/helpers/refresh_helper.go`: manages refreshing of models. The refresh helper is typically invoked at the end of an action to re-load affected models from git (e.g. re-load branches after doing a git pull)
+* `vendor/github.com/jesseduffield/gocui/gui.go`: defines the gocui gui struct
+* `vendor/github.com/jesseduffield/gocui/view.go`: defines the gocui view struct
+
+## Concepts
+
+* **View**: Views are defined in the gocui package, and they maintain an internal buffer of content which is rendered each time the screen is drawn.
+* **Context**: A context is tied to a view and contains some additional state and logic specific to that view e.g. the branches context has code relating specifically to branches, and writes the list of branches to the branches view. Views and contexts share some responsibilities for historical reasons.
+* **Controller**: A controller defined keybindings with associated handlers. One controller can be assigned to multiple contexts and one context can have multiple controllers. For example the list controller handles keybindings relating to navigating a list, and is assigned to all list contexts (e.g. the branches context).
+* **Helper**: A helper defines shared code used by controllers, or used by some other parts of the application. Often a controller will have a method that ends up needing to be used by another controller, so in that case we move the method out into a helper so that both controllers can use it. We need to do this because controllers cannot refer to other controllers' methods.
+
+In terms of dependencies, controllers sit at the highest level, so they can refer to helpers, contexts, and views (although it's preferable for view-specific code to live in contexts). Helpers can refer to contexts and views, and contexts can only refer to views. Views can't refer to contexts, controllers, or helpers.
+
+* **Window**: A window is a section of the screen which will render a view. Windows are named after the default view that appears there, so for example there is a 'stash' window that is so named because by default the stash view appears there. But if you press enter on a stash entry, the stash entry's files will be shown in a different view, but in the same window.
+* **Panel**: The term 'panel' is still used in a few places to refer to either a view or a window, and it's a term that is now deprecated in favour of 'view' and 'window'.
+* **Tab**: Each tab in a window (e.g. Files, Worktrees, Submodules) actually has a corresponding view which we bring to the front upon changing tabs.
+* **Model**: Representation of a git object e.g. commits, branches, files.
+* **ViewModel**: Used by a context to maintain state related to the view.
+* **Common structs**: Most structs have a field named `c` which contains a 'common' struct: a struct containing a bag of dependencies that most structs of the same layer require. For example if you want to access a helper from a controller you can do so with `self.c.Helpers.MyHelper`.
+
+## Event loop and threads
+
+The event loop is managed in the `MainLoop` function of `vendor/github.com/jesseduffield/gocui/gui.go`. Any time there is an event like a key press or a window resize, the event will be processed and then the screen will be redrawn. This involves calling the `layout` function defined in `pkg/gui/layout.go`, which lays out the windows and invokes some on-render hooks.
+
+Often, as part of handling a keypress, we'll want to run some code asynchronously so that it doesn't block the UI thread. For this we'll typically run `self.c.OnWorker(myFunc)`. If the worker wants to then do something on the UI thread again it can call `self.c.OnUIThread(myOtherFunc)`.
+
+## Legacy code structure
+
+Before we had controllers and contexts, all the code lived directly in the gui package under a gui God Struct. This was fairly bloated and so we split things out to have a better separation of concerns. Nonetheless, it's a big effort to migrate all the code so we still have some logic in the gui struct that ought to live somewhere else. Likewise, we have some keybindings defined in `pkg/gui/keybindings.go` that ought to live on a controller (all keybindings used to be defined in that one file).
+
+The new structure has its own problems: we don't have a clear guide on whether code should live in a controller or helper. The current approach is to put code in a controller until it's needed by another controller, and to then extract it out into a helper. We may be better off just putting code in helpers to start with and leaving controllers super-thin, with the responsibility of just pairing keys with corresponding helper functions. But it's not clear to me if that would be better than the current approach.
+
diff --git a/docs/dev/README.md b/docs/dev/README.md
index b29daa1015a7..44534741780c 100644
--- a/docs/dev/README.md
+++ b/docs/dev/README.md
@@ -1,5 +1,6 @@
# Dev Documentation Overview
-* [Busy/Idle tracking](./Busy.md).
+* [Codebase Guide](./Codebase_Guide.md)
+* [Busy/Idle Tracking](./Busy.md)
* [Integration Tests](../../pkg/integration/README.md)
* [Demo Recordings](./Demo_Recordings.md)
From 5c888a0b474ed2fc45c7640b4e73e2bf272f2d3c Mon Sep 17 00:00:00 2001
From: Jesse Duffield
Date: Thu, 11 Jan 2024 09:57:05 +1100
Subject: [PATCH 033/280] Update codebase guide
fixes a line that used an incorrect path
---
docs/dev/Codebase_Guide.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/dev/Codebase_Guide.md b/docs/dev/Codebase_Guide.md
index 6803088d2a23..93a771274537 100644
--- a/docs/dev/Codebase_Guide.md
+++ b/docs/dev/Codebase_Guide.md
@@ -52,7 +52,7 @@
* `pkg/gui/controllers/helpers/window_arrangement_helper.go`: defines the layout of the UI and the size/position of each window
* `pkg/gui/context/context.go`: defines the different contexts
* `pkg/gui/context/setup.go`: defines initialisation code for all contexts
-* `pkg/gui/context/context.go`: manages the lifecycle of contexts, the context stack, and focus changes.
+* `pkg/gui/context.go`: manages the lifecycle of contexts, the context stack, and focus changes.
* `pkg/gui/types/views.go`: defines views
* `pkg/gui/views.go`: defines the ordering of views (front to back) and their initialisation code
* `pkg/gui/gui_common.go`: defines gui-specific methods that all controllers and helpers have access to
From 8c716184c15226b617ffb4831cef66f0b93779e7 Mon Sep 17 00:00:00 2001
From: Jesse Duffield
Date: Fri, 12 Jan 2024 19:52:19 +1100
Subject: [PATCH 034/280] Set working directory in lazygit test command
We need to fetch our list of tests both outside of our test binary and within. We need
to get the list from within so that we can run the code that drives the test and runs
assertions. To get the list of tests we need to know where the root of the lazygit repo
is, given that the tests live in files under that root.
So far, we've used this GetLazyRootDirectory() function for that, but it assumes that
we're not in a test directory (it just looks for the first .git dir it can find). Because
we didn't want to properly fix this before, we've been setting the working directory of
the test command to the lazygit root, and using the --path CLI arg to override it when
the test itself ran. This was a terrible hack.
Now, we're passing the lazygit root directory as an env var to the integration test, so
that we can set the working directory to the actual path of the test repo; removing the
need to use the --path arg.
---
pkg/integration/README.md | 2 +
pkg/integration/clients/cli.go | 3 +-
pkg/integration/clients/go_test.go | 3 +-
pkg/integration/clients/injector/main.go | 3 +-
pkg/integration/clients/tui.go | 2 +-
pkg/integration/components/runner.go | 36 +++++++++++-----
pkg/integration/components/test.go | 42 ++++++++-----------
pkg/integration/tests/tests.go | 5 +--
pkg/integration/tests/worktree/bare_repo.go | 2 +
.../tests/worktree/dotfile_bare_repo.go | 4 +-
10 files changed, 57 insertions(+), 45 deletions(-)
diff --git a/pkg/integration/README.md b/pkg/integration/README.md
index 44b9459d1ffb..0c50d8f4e05b 100644
--- a/pkg/integration/README.md
+++ b/pkg/integration/README.md
@@ -24,6 +24,8 @@ Each test has two important steps: the setup step and the run step.
In the setup step, we prepare a repo with shell commands, for example, creating a merge conflict that will need to be resolved upon opening lazygit. This is all done via the `shell` argument.
+When the test runs, lazygit will open in the same working directory that the shell ends up in (so if you want to start lazygit somewhere other than the default location, you can use `shell.Chdir()` at the end of the setup step to set that working directory.
+
### Run step
The run step has two arguments passed in:
diff --git a/pkg/integration/clients/cli.go b/pkg/integration/clients/cli.go
index 9ff5453f0922..10958c164776 100644
--- a/pkg/integration/clients/cli.go
+++ b/pkg/integration/clients/cli.go
@@ -8,6 +8,7 @@ import (
"strconv"
"strings"
+ "github.com/jesseduffield/lazycore/pkg/utils"
"github.com/jesseduffield/lazygit/pkg/integration/components"
"github.com/jesseduffield/lazygit/pkg/integration/tests"
"github.com/samber/lo"
@@ -53,7 +54,7 @@ func runAndPrintFatalError(test *components.IntegrationTest, f func() error) {
}
func getTestsToRun(testNames []string) []*components.IntegrationTest {
- allIntegrationTests := tests.GetTests()
+ allIntegrationTests := tests.GetTests(utils.GetLazyRootDirectory())
var testsToRun []*components.IntegrationTest
if len(testNames) == 0 {
diff --git a/pkg/integration/clients/go_test.go b/pkg/integration/clients/go_test.go
index d6fecdb2dc71..6984e0debc8a 100644
--- a/pkg/integration/clients/go_test.go
+++ b/pkg/integration/clients/go_test.go
@@ -15,6 +15,7 @@ import (
"testing"
"github.com/creack/pty"
+ "github.com/jesseduffield/lazycore/pkg/utils"
"github.com/jesseduffield/lazygit/pkg/integration/components"
"github.com/jesseduffield/lazygit/pkg/integration/tests"
"github.com/stretchr/testify/assert"
@@ -35,7 +36,7 @@ func TestIntegration(t *testing.T) {
testNumber := 0
err := components.RunTests(components.RunTestArgs{
- Tests: tests.GetTests(),
+ Tests: tests.GetTests(utils.GetLazyRootDirectory()),
Logf: t.Logf,
RunCmd: runCmdHeadless,
TestWrapper: func(test *components.IntegrationTest, f func() error) {
diff --git a/pkg/integration/clients/injector/main.go b/pkg/integration/clients/injector/main.go
index 223ff4ecb020..1f4c845e83d8 100644
--- a/pkg/integration/clients/injector/main.go
+++ b/pkg/integration/clients/injector/main.go
@@ -58,7 +58,8 @@ func getIntegrationTest() integrationTypes.IntegrationTest {
))
}
- allTests := tests.GetTests()
+ lazygitRootDir := os.Getenv(components.LAZYGIT_ROOT_DIR)
+ allTests := tests.GetTests(lazygitRootDir)
for _, candidateTest := range allTests {
if candidateTest.Name() == integrationTestName {
return candidateTest
diff --git a/pkg/integration/clients/tui.go b/pkg/integration/clients/tui.go
index 05db989910a4..ded94f74db7f 100644
--- a/pkg/integration/clients/tui.go
+++ b/pkg/integration/clients/tui.go
@@ -233,7 +233,7 @@ type app struct {
}
func newApp(testDir string) *app {
- return &app{testDir: testDir, allTests: tests.GetTests()}
+ return &app{testDir: testDir, allTests: tests.GetTests(utils.GetLazyRootDirectory())}
}
func (self *app) getCurrentTest() *components.IntegrationTest {
diff --git a/pkg/integration/components/runner.go b/pkg/integration/components/runner.go
index fd3ce7bf096f..064dd5c5b264 100644
--- a/pkg/integration/components/runner.go
+++ b/pkg/integration/components/runner.go
@@ -14,6 +14,7 @@ import (
)
const (
+ LAZYGIT_ROOT_DIR = "LAZYGIT_ROOT_DIR"
TEST_NAME_ENV_VAR = "TEST_NAME"
SANDBOX_ENV_VAR = "SANDBOX"
WAIT_FOR_DEBUGGER_ENV_VAR = "WAIT_FOR_DEBUGGER"
@@ -98,11 +99,12 @@ func runTest(
return nil
}
- if err := prepareTestDir(test, paths, projectRootDir); err != nil {
+ workingDir, err := prepareTestDir(test, paths, projectRootDir)
+ if err != nil {
return err
}
- cmd, err := getLazygitCommand(test, args, paths, projectRootDir)
+ cmd, err := getLazygitCommand(test, args, paths, projectRootDir, workingDir)
if err != nil {
return err
}
@@ -124,16 +126,18 @@ func prepareTestDir(
test *IntegrationTest,
paths Paths,
rootDir string,
-) error {
+) (string, error) {
findOrCreateDir(paths.Root())
deleteAndRecreateEmptyDir(paths.Actual())
err := os.Mkdir(paths.ActualRepo(), 0o777)
if err != nil {
- return err
+ return "", err
}
- return createFixture(test, paths, rootDir)
+ workingDir := createFixture(test, paths, rootDir)
+
+ return workingDir, nil
}
func buildLazygit(testArgs RunTestArgs) error {
@@ -154,7 +158,9 @@ func buildLazygit(testArgs RunTestArgs) error {
return osCommand.Cmd.New(args).Run()
}
-func createFixture(test *IntegrationTest, paths Paths, rootDir string) error {
+// Sets up the fixture for test and returns the working directory to invoke
+// lazygit in.
+func createFixture(test *IntegrationTest, paths Paths, rootDir string) string {
shell := NewShell(paths.ActualRepo(), func(errorMsg string) { panic(errorMsg) })
shell.Init()
@@ -162,7 +168,7 @@ func createFixture(test *IntegrationTest, paths Paths, rootDir string) error {
test.SetupRepo(shell)
- return nil
+ return shell.dir
}
func globalGitConfigPath(rootDir string) string {
@@ -179,7 +185,13 @@ func getGitVersion() (*git_commands.GitVersion, error) {
return git_commands.ParseGitVersion(versionStr)
}
-func getLazygitCommand(test *IntegrationTest, args RunTestArgs, paths Paths, rootDir string) (*exec.Cmd, error) {
+func getLazygitCommand(
+ test *IntegrationTest,
+ args RunTestArgs,
+ paths Paths,
+ rootDir string,
+ workingDir string,
+) (*exec.Cmd, error) {
osCommand := oscommands.NewDummyOSCommand()
err := os.RemoveAll(paths.Config())
@@ -194,9 +206,7 @@ func getLazygitCommand(test *IntegrationTest, args RunTestArgs, paths Paths, roo
}
cmdArgs := []string{tempLazygitPath(), "-debug", "--use-config-dir=" + paths.Config()}
- if !test.useCustomPath {
- cmdArgs = append(cmdArgs, "--path="+paths.ActualRepo())
- }
+
resolvedExtraArgs := lo.Map(test.ExtraCmdArgs(), func(arg string, _ int) string {
return utils.ResolvePlaceholderString(arg, map[string]string{
"actualPath": paths.Actual(),
@@ -207,6 +217,10 @@ func getLazygitCommand(test *IntegrationTest, args RunTestArgs, paths Paths, roo
cmdObj := osCommand.Cmd.New(cmdArgs)
+ cmdObj.SetWd(workingDir)
+
+ cmdObj.AddEnvVars(fmt.Sprintf("%s=%s", LAZYGIT_ROOT_DIR, rootDir))
+
if args.CodeCoverageDir != "" {
// We set this explicitly here rather than inherit it from the test runner's
// environment because the test runner has its own coverage directory that
diff --git a/pkg/integration/components/test.go b/pkg/integration/components/test.go
index 7a088c80adeb..c837d8be8009 100644
--- a/pkg/integration/components/test.go
+++ b/pkg/integration/components/test.go
@@ -35,11 +35,10 @@ type IntegrationTest struct {
testDriver *TestDriver,
keys config.KeybindingConfig,
)
- gitVersion GitVersionRestriction
- width int
- height int
- isDemo bool
- useCustomPath bool
+ gitVersion GitVersionRestriction
+ width int
+ height int
+ isDemo bool
}
var _ integrationTypes.IntegrationTest = &IntegrationTest{}
@@ -55,9 +54,9 @@ type NewIntegrationTestArgs struct {
Run func(t *TestDriver, keys config.KeybindingConfig)
// additional args passed to lazygit
ExtraCmdArgs []string
- // for when a test is flakey
ExtraEnvVars map[string]string
- Skip bool
+ // for when a test is flakey
+ Skip bool
// to run a test only on certain git versions
GitVersion GitVersionRestriction
// width and height when running in headless mode, for testing
@@ -67,10 +66,6 @@ type NewIntegrationTestArgs struct {
Height int
// If true, this is not a test but a demo to be added to our docs
IsDemo bool
- // If true, the test won't invoke lazygit with the --path arg.
- // Useful for when we're passing --git-dir and --work-tree (because --path is
- // incompatible with those args)
- UseCustomPath bool
}
type GitVersionRestriction struct {
@@ -130,19 +125,18 @@ func NewIntegrationTest(args NewIntegrationTestArgs) *IntegrationTest {
}
return &IntegrationTest{
- name: name,
- description: args.Description,
- extraCmdArgs: args.ExtraCmdArgs,
- extraEnvVars: args.ExtraEnvVars,
- skip: args.Skip,
- setupRepo: args.SetupRepo,
- setupConfig: args.SetupConfig,
- run: args.Run,
- gitVersion: args.GitVersion,
- width: args.Width,
- height: args.Height,
- isDemo: args.IsDemo,
- useCustomPath: args.UseCustomPath,
+ name: name,
+ description: args.Description,
+ extraCmdArgs: args.ExtraCmdArgs,
+ extraEnvVars: args.ExtraEnvVars,
+ skip: args.Skip,
+ setupRepo: args.SetupRepo,
+ setupConfig: args.SetupConfig,
+ run: args.Run,
+ gitVersion: args.GitVersion,
+ width: args.Width,
+ height: args.Height,
+ isDemo: args.IsDemo,
}
}
diff --git a/pkg/integration/tests/tests.go b/pkg/integration/tests/tests.go
index 600c98af63c0..22ca7d8d7a9d 100644
--- a/pkg/integration/tests/tests.go
+++ b/pkg/integration/tests/tests.go
@@ -9,12 +9,11 @@ import (
"strings"
"github.com/jesseduffield/generics/set"
- "github.com/jesseduffield/lazycore/pkg/utils"
"github.com/jesseduffield/lazygit/pkg/integration/components"
"github.com/samber/lo"
)
-func GetTests() []*components.IntegrationTest {
+func GetTests(lazygitRootDir string) []*components.IntegrationTest {
// first we ensure that each test in this directory has actually been added to the above list.
testCount := 0
@@ -27,7 +26,7 @@ func GetTests() []*components.IntegrationTest {
missingTestNames := []string{}
- if err := filepath.Walk(filepath.Join(utils.GetLazyRootDirectory(), "pkg/integration/tests"), func(path string, info os.FileInfo, err error) error {
+ if err := filepath.Walk(filepath.Join(lazygitRootDir, "pkg/integration/tests"), func(path string, info os.FileInfo, err error) error {
if !info.IsDir() && strings.HasSuffix(path, ".go") {
// ignoring non-test files
if filepath.Base(path) == "tests.go" || filepath.Base(path) == "test_list.go" || filepath.Base(path) == "test_list_generator.go" {
diff --git a/pkg/integration/tests/worktree/bare_repo.go b/pkg/integration/tests/worktree/bare_repo.go
index 2343b112e323..cfcaa028020b 100644
--- a/pkg/integration/tests/worktree/bare_repo.go
+++ b/pkg/integration/tests/worktree/bare_repo.go
@@ -38,6 +38,8 @@ var BareRepo = NewIntegrationTest(NewIntegrationTestArgs{
shell.RunCommand([]string{"git", "--git-dir", ".bare", "worktree", "add", "-b", "repo", "repo", "mybranch"})
shell.RunCommand([]string{"git", "--git-dir", ".bare", "worktree", "add", "-b", "worktree2", "worktree2", "mybranch"})
+
+ shell.Chdir("repo")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Branches().
diff --git a/pkg/integration/tests/worktree/dotfile_bare_repo.go b/pkg/integration/tests/worktree/dotfile_bare_repo.go
index 86b2a0fb4827..7b8f68af1af3 100644
--- a/pkg/integration/tests/worktree/dotfile_bare_repo.go
+++ b/pkg/integration/tests/worktree/dotfile_bare_repo.go
@@ -12,9 +12,7 @@ var DotfileBareRepo = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Open lazygit in the worktree of a dotfile bare repo and add a file and commit",
ExtraCmdArgs: []string{"--git-dir={{.actualPath}}/.bare", "--work-tree={{.actualPath}}/repo"},
Skip: false,
- // passing this because we're explicitly passing --git-dir and --work-tree args
- UseCustomPath: true,
- SetupConfig: func(config *config.AppConfig) {},
+ SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) {
// we're going to have a directory structure like this:
// project
From a1ce6029c13ab69ae6e7d08a8f9593435abb5b92 Mon Sep 17 00:00:00 2001
From: Jesse Duffield
Date: Fri, 12 Jan 2024 20:14:26 +1100
Subject: [PATCH 035/280] Pass -f as single arg in integration test
For some bizarre reason `pkg/integration/tests/filter_by_path/cli_arg.go` is failing as of 8c716184 like so:
```
test_lazygit
Usage:
test_lazygit [git-arg]
Positional Variables:
git-arg Panel to focus upon opening lazygit. Accepted values (based on git terminology): status, branch, log, stash. Ignored if --filter arg is passed.
Flags:
-h --help Displays help with available flag, subcommand, and positional value parameters.
-p --path Path of git repo. (equivalent to --work-tree= --git-dir=/.git/)
-f --filter Path to filter on in `git log -- `. When in filter mode, the commits, reflog, and stash are filtered based on the given path, and some operations are restricted
-v --version Print the current version
-d --debug Run in debug mode with logging (see --logs flag below). Use the LOG_LEVEL env var to set the log level (debug/info/warn/error) (default: false)
-l --logs Tail lazygit logs (intended to be used when `lazygit --debug` is called in a separate terminal tab)
-c --config Print the default config
-cd --print-config-dir Print the config directory
-ucd --use-config-dir override default config directory with provided directory
-w --work-tree equivalent of the --work-tree git argument
-g --git-dir equivalent of the --git-dir git argument
-ucf --use-config-file Comma separated list to custom config file(s)
Unknown arguments supplied: filterFile
```
where the CLI args are:
```
([]string) (len=5 cap=5) {
(string) (len=25) "/tmp/lazygit/test_lazygit",
(string) (len=6) "-debug",
(string) (len=108) "--use-config-dir=/Users/jesseduffieldduffield/repos/lazygit/test/_results/filter_by_path/cli_arg/used_config",
(string) (len=2) "-f",
(string) (len=10) "filterFile"
}
```
This appears to be a bug in flaggy itself. I've updated to the latest version but it still breaks. Bizarrely it works fine on CI and
only fails locally. Running lazygit locally with `lg -f pkg/gui/controllers/helpers/refresh_helper.go` it works fine. So I don't
know what's going on there. At any rate, I'm just going to get the test passing by passing `-f=filterFile` as a single argument.
---
pkg/integration/tests/filter_by_path/cli_arg.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pkg/integration/tests/filter_by_path/cli_arg.go b/pkg/integration/tests/filter_by_path/cli_arg.go
index de368c17dffe..5b3912829ac0 100644
--- a/pkg/integration/tests/filter_by_path/cli_arg.go
+++ b/pkg/integration/tests/filter_by_path/cli_arg.go
@@ -7,7 +7,7 @@ import (
var CliArg = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Filter commits by file path, using CLI arg",
- ExtraCmdArgs: []string{"-f", "filterFile"},
+ ExtraCmdArgs: []string{"-f=filterFile"},
Skip: false,
SetupConfig: func(config *config.AppConfig) {
},
From 53a8bd2e3fbbc474bbd70564f38f6fe1e1d10079 Mon Sep 17 00:00:00 2001
From: Jesse Duffield
Date: Fri, 12 Jan 2024 10:32:20 +1100
Subject: [PATCH 036/280] Add ability to start an interactive rebase onto an
appropriate base
A common issue I have is that I want to move a commit from the top of my branch
all the way down to the first commit on the branch. To do that, I need to navigate
down to the first commit on my branch, press 'e' to start an interactive rebase,
then navigate back up to the top of the branch, then move my commit back down to
the base. This is annoying.
Similarly annoying is moving the commit one-by-one without explicitly starting
an interactive rebase, because then each individual step is its own rebase which
takes a while in aggregate.
This PR allows you to press 'i' from the commits view to start an interactive
rebase from an 'appropriate' base. By appropriate, we mean that we want to start
from the HEAD and stop when we reach the first merge commit or commit on the main
branch. This may end up including more commits than you need, but it doesn't make
a difference.
---
docs/keybindings/Keybindings_en.md | 1 +
docs/keybindings/Keybindings_ja.md | 1 +
docs/keybindings/Keybindings_ko.md | 1 +
docs/keybindings/Keybindings_nl.md | 1 +
docs/keybindings/Keybindings_pl.md | 1 +
docs/keybindings/Keybindings_ru.md | 1 +
docs/keybindings/Keybindings_zh-CN.md | 1 +
docs/keybindings/Keybindings_zh-TW.md | 1 +
pkg/config/user_config.go | 2 +
.../controllers/local_commits_controller.go | 88 ++++++++++++-
pkg/i18n/english.go | 6 +
pkg/integration/components/common.go | 18 +++
.../tests/interactive_rebase/quick_start.go | 123 ++++++++++++++++++
pkg/integration/tests/test_list.go | 1 +
schema/config.json | 4 +
15 files changed, 247 insertions(+), 3 deletions(-)
create mode 100644 pkg/integration/tests/interactive_rebase/quick_start.go
diff --git a/docs/keybindings/Keybindings_en.md b/docs/keybindings/Keybindings_en.md
index ef6299613bf6..9ffd287461fc 100644
--- a/docs/keybindings/Keybindings_en.md
+++ b/docs/keybindings/Keybindings_en.md
@@ -79,6 +79,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
R: Reword commit with editor
d: Delete commit
e: Edit commit
+ i: Start interactive rebase
p: Pick commit (when mid-rebase)
F: Create fixup commit for this commit
S: Squash all 'fixup!' commits above selected commit (autosquash)
diff --git a/docs/keybindings/Keybindings_ja.md b/docs/keybindings/Keybindings_ja.md
index 36389ecc1110..f2f26db5aa33 100644
--- a/docs/keybindings/Keybindings_ja.md
+++ b/docs/keybindings/Keybindings_ja.md
@@ -98,6 +98,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
R: エディタでコミットメッセージを編集
d: コミットを削除
e: コミットを編集
+ i: Start interactive rebase
p: Pick commit (when mid-rebase)
F: このコミットに対するfixupコミットを作成
S: Squash all 'fixup!' commits above selected commit (autosquash)
diff --git a/docs/keybindings/Keybindings_ko.md b/docs/keybindings/Keybindings_ko.md
index d96283192051..36c045ba1f19 100644
--- a/docs/keybindings/Keybindings_ko.md
+++ b/docs/keybindings/Keybindings_ko.md
@@ -263,6 +263,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
R: 에디터에서 커밋메시지 수정
d: 커밋 삭제
e: 커밋을 편집
+ i: Start interactive rebase
p: Pick commit (when mid-rebase)
F: Create fixup commit for this commit
S: Squash all 'fixup!' commits above selected commit (autosquash)
diff --git a/docs/keybindings/Keybindings_nl.md b/docs/keybindings/Keybindings_nl.md
index 23bf0a477158..bd78ff69481a 100644
--- a/docs/keybindings/Keybindings_nl.md
+++ b/docs/keybindings/Keybindings_nl.md
@@ -142,6 +142,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
R: Hernoem commit met editor
d: Verwijder commit
e: Wijzig commit
+ i: Start interactive rebase
p: Kies commit (wanneer midden in rebase)
F: Creëer fixup commit
S: Squash bovenstaande commits
diff --git a/docs/keybindings/Keybindings_pl.md b/docs/keybindings/Keybindings_pl.md
index 601ec72f5932..048dd490e869 100644
--- a/docs/keybindings/Keybindings_pl.md
+++ b/docs/keybindings/Keybindings_pl.md
@@ -63,6 +63,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
R: Zmień nazwę commita w edytorze
d: Usuń commit
e: Edytuj commit
+ i: Start interactive rebase
p: Wybierz commit (podczas zmiany bazy)
F: Utwórz commit naprawczy dla tego commita
S: Spłaszcz wszystkie commity naprawcze powyżej zaznaczonych commitów (autosquash)
diff --git a/docs/keybindings/Keybindings_ru.md b/docs/keybindings/Keybindings_ru.md
index 6277f3db8d4b..ba7af912f152 100644
--- a/docs/keybindings/Keybindings_ru.md
+++ b/docs/keybindings/Keybindings_ru.md
@@ -146,6 +146,7 @@ _Связки клавиш_
R: Переписать коммит с помощью редактора
d: Удалить коммит
e: Изменить коммит
+ i: Start interactive rebase
p: Выбрать коммит (в середине перебазирования)
F: Создать fixup коммит для этого коммита
S: Объединить все 'fixup!' коммиты выше в выбранный коммит (автосохранение)
diff --git a/docs/keybindings/Keybindings_zh-CN.md b/docs/keybindings/Keybindings_zh-CN.md
index 4b4a8e028da7..eaa7c725f807 100644
--- a/docs/keybindings/Keybindings_zh-CN.md
+++ b/docs/keybindings/Keybindings_zh-CN.md
@@ -144,6 +144,7 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
R: 使用编辑器重命名提交
d: 删除提交
e: 编辑提交
+ i: Start interactive rebase
p: 选择提交(变基过程中)
F: 创建修正提交
S: 压缩在所选提交之上的所有“fixup!”提交(自动压缩)
diff --git a/docs/keybindings/Keybindings_zh-TW.md b/docs/keybindings/Keybindings_zh-TW.md
index 8fd6a274bda3..9f623678bf46 100644
--- a/docs/keybindings/Keybindings_zh-TW.md
+++ b/docs/keybindings/Keybindings_zh-TW.md
@@ -187,6 +187,7 @@ _說明:`` 表示 Ctrl+B、`` 表示 Alt+B,`B`表示 Shift+B_
R: 使用編輯器改寫提交
d: 刪除提交
e: 編輯提交
+ i: Start interactive rebase
p: 挑選提交 (於變基過程中)
F: 為此提交建立修復提交
S: 壓縮上方所有的“fixup!”提交 (自動壓縮)
diff --git a/pkg/config/user_config.go b/pkg/config/user_config.go
index d0d5184551fe..602dc54bf6b0 100644
--- a/pkg/config/user_config.go
+++ b/pkg/config/user_config.go
@@ -428,6 +428,7 @@ type KeybindingCommitsConfig struct {
OpenLogMenu string `yaml:"openLogMenu"`
OpenInBrowser string `yaml:"openInBrowser"`
ViewBisectOptions string `yaml:"viewBisectOptions"`
+ StartInteractiveRebase string `yaml:"startInteractiveRebase"`
}
type KeybindingStashConfig struct {
@@ -822,6 +823,7 @@ func GetDefaultConfig() *UserConfig {
OpenLogMenu: "",
OpenInBrowser: "o",
ViewBisectOptions: "b",
+ StartInteractiveRebase: "i",
},
Stash: KeybindingStashConfig{
PopStash: "g",
diff --git a/pkg/gui/controllers/local_commits_controller.go b/pkg/gui/controllers/local_commits_controller.go
index efb06fe71c6e..f3637397bc61 100644
--- a/pkg/gui/controllers/local_commits_controller.go
+++ b/pkg/gui/controllers/local_commits_controller.go
@@ -4,11 +4,13 @@ import (
"fmt"
"github.com/fsmiamoto/git-todo-parser/todo"
+ "github.com/go-errors/errors"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
+ "github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
@@ -42,6 +44,8 @@ func NewLocalCommitsController(
}
func (self *LocalCommitsController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
+ editCommitKey := opts.Config.Universal.Edit
+
outsideFilterModeBindings := []*types.Binding{
{
Key: opts.GetKey(opts.Config.Commits.SquashDown),
@@ -74,11 +78,23 @@ func (self *LocalCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
Description: self.c.Tr.DeleteCommit,
},
{
- Key: opts.GetKey(opts.Config.Universal.Edit),
+ Key: opts.GetKey(editCommitKey),
Handler: self.checkSelected(self.edit),
GetDisabledReason: self.getDisabledReasonForRebaseCommandWithSelectedCommit(todo.Edit),
Description: self.c.Tr.EditCommit,
},
+ {
+ // The user-facing description here is 'Start interactive rebase' but internally
+ // we're calling it 'quick-start interactive rebase' to differentiate it from
+ // when you manually select the base commit.
+ Key: opts.GetKey(opts.Config.Commits.StartInteractiveRebase),
+ Handler: self.checkSelected(self.quickStartInteractiveRebase),
+ GetDisabledReason: self.require(self.notMidRebase, self.canFindCommitForQuickStart),
+ Description: self.c.Tr.QuickStartInteractiveRebase,
+ Tooltip: utils.ResolvePlaceholderString(self.c.Tr.QuickStartInteractiveRebaseTooltip, map[string]string{
+ "editKey": keybindings.Label(editCommitKey),
+ }),
+ },
{
Key: opts.GetKey(opts.Config.Commits.PickCommit),
Handler: self.checkSelected(self.pick),
@@ -414,14 +430,33 @@ func (self *LocalCommitsController) edit(commit *models.Commit) error {
return nil
}
+ return self.startInteractiveRebaseWithEdit(commit, commit)
+}
+
+func (self *LocalCommitsController) quickStartInteractiveRebase(selectedCommit *models.Commit) error {
+ commitToEdit, err := self.findCommitForQuickStartInteractiveRebase()
+ if err != nil {
+ return self.c.Error(err)
+ }
+
+ return self.startInteractiveRebaseWithEdit(commitToEdit, selectedCommit)
+}
+
+func (self *LocalCommitsController) startInteractiveRebaseWithEdit(
+ commitToEdit *models.Commit,
+ selectedCommit *models.Commit,
+) error {
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.EditCommit)
- err := self.c.Git().Rebase.EditRebase(commit.Sha)
+ err := self.c.Git().Rebase.EditRebase(commitToEdit.Sha)
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptions(
err,
types.RefreshOptions{Mode: types.BLOCK_UI, Then: func() {
+ // We need to select the same commit again because after starting a rebase,
+ // new lines can be added for update-ref commands in the TODO file, due to
+ // stacked branches. So the commit may be in a different position in the list.
_, index, ok := lo.FindIndexOf(self.c.Model().Commits, func(c *models.Commit) bool {
- return c.Sha == commit.Sha
+ return c.Sha == selectedCommit.Sha
})
if ok {
self.context().SetSelectedLineIdx(index)
@@ -430,6 +465,22 @@ func (self *LocalCommitsController) edit(commit *models.Commit) error {
})
}
+func (self *LocalCommitsController) findCommitForQuickStartInteractiveRebase() (*models.Commit, error) {
+ commit, index, ok := lo.FindIndexOf(self.c.Model().Commits, func(c *models.Commit) bool {
+ return c.IsMerge() || c.Status == models.StatusMerged
+ })
+
+ if !ok || index == 0 {
+ errorMsg := utils.ResolvePlaceholderString(self.c.Tr.CannotQuickStartInteractiveRebase, map[string]string{
+ "editKey": keybindings.Label(self.c.UserConfig.Keybinding.Universal.Edit),
+ })
+
+ return nil, errors.New(errorMsg)
+ }
+
+ return commit, nil
+}
+
func (self *LocalCommitsController) pick(commit *models.Commit) error {
applied, err := self.handleMidRebaseCommand(todo.Pick, commit)
if err != nil {
@@ -827,6 +878,24 @@ func (self *LocalCommitsController) getDisabledReasonForSquashAllAboveFixupCommi
return ""
}
+// For getting disabled reason
+func (self *LocalCommitsController) notMidRebase() string {
+ if self.c.Git().Status.WorkingTreeState() != enums.REBASE_MODE_NONE {
+ return self.c.Tr.AlreadyRebasing
+ }
+
+ return ""
+}
+
+// For getting disabled reason
+func (self *LocalCommitsController) canFindCommitForQuickStart() string {
+ if _, err := self.findCommitForQuickStartInteractiveRebase(); err != nil {
+ return err.Error()
+ }
+
+ return ""
+}
+
func (self *LocalCommitsController) createTag(commit *models.Commit) error {
return self.c.Helpers().Tags.OpenCreateTagPrompt(commit.Sha, func() {})
}
@@ -1030,6 +1099,19 @@ func (self *LocalCommitsController) isHeadCommit() bool {
return models.IsHeadCommit(self.c.Model().Commits, self.context().GetSelectedLineIdx())
}
+// Convenience function for composing multiple disabled reason functions
+func (self *LocalCommitsController) require(callbacks ...func() string) func() string {
+ return func() string {
+ for _, callback := range callbacks {
+ if disabledReason := callback(); disabledReason != "" {
+ return disabledReason
+ }
+ }
+
+ return ""
+ }
+}
+
func isChangeOfRebaseTodoAllowed(action todo.TodoCommand) bool {
allowedActions := []todo.TodoCommand{
todo.Pick,
diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go
index b645291e7010..8886217c3545 100644
--- a/pkg/i18n/english.go
+++ b/pkg/i18n/english.go
@@ -648,6 +648,9 @@ type TranslationSet struct {
DisabledMenuItemPrefix string
NoCommitSelected string
NoCopiedCommits string
+ QuickStartInteractiveRebase string
+ QuickStartInteractiveRebaseTooltip string
+ CannotQuickStartInteractiveRebase string
Actions Actions
Bisect Bisect
Log Log
@@ -1477,6 +1480,9 @@ func EnglishTranslationSet() TranslationSet {
DisabledMenuItemPrefix: "Disabled: ",
NoCommitSelected: "No commit selected",
NoCopiedCommits: "No copied commits",
+ QuickStartInteractiveRebase: "Start interactive rebase",
+ QuickStartInteractiveRebaseTooltip: "Start an interactive rebase for the commits on your branch. This will include all commits from the HEAD commit down to the first merge commit or main branch commit.\nIf you would instead like to start an interactive rebase from the selected commit, press `{{.editKey}}`.",
+ CannotQuickStartInteractiveRebase: "Cannot start interactive rebase: the HEAD commit is a merge commit or is present on the main branch, so there is no appropriate base commit to start the rebase from. You can start an interactive rebase from a specific commit by selecting the commit and pressing `{{.editKey}}`.",
Actions: Actions{
// TODO: combine this with the original keybinding descriptions (those are all in lowercase atm)
CheckoutCommit: "Checkout commit",
diff --git a/pkg/integration/components/common.go b/pkg/integration/components/common.go
index 12afaee2faf0..4f7cd975448f 100644
--- a/pkg/integration/components/common.go
+++ b/pkg/integration/components/common.go
@@ -18,6 +18,24 @@ func (self *Common) ContinueRebase() {
self.ContinueMerge()
}
+func (self *Common) AbortRebase() {
+ self.t.GlobalPress(self.t.keys.Universal.CreateRebaseOptionsMenu)
+
+ self.t.ExpectPopup().Menu().
+ Title(Equals("Rebase options")).
+ Select(Contains("abort")).
+ Confirm()
+}
+
+func (self *Common) AbortMerge() {
+ self.t.GlobalPress(self.t.keys.Universal.CreateRebaseOptionsMenu)
+
+ self.t.ExpectPopup().Menu().
+ Title(Equals("Merge options")).
+ Select(Contains("abort")).
+ Confirm()
+}
+
func (self *Common) AcknowledgeConflicts() {
self.t.ExpectPopup().Menu().
Title(Equals("Conflicts!")).
diff --git a/pkg/integration/tests/interactive_rebase/quick_start.go b/pkg/integration/tests/interactive_rebase/quick_start.go
new file mode 100644
index 000000000000..713e00cdb003
--- /dev/null
+++ b/pkg/integration/tests/interactive_rebase/quick_start.go
@@ -0,0 +1,123 @@
+package interactive_rebase
+
+import (
+ "github.com/jesseduffield/lazygit/pkg/config"
+ . "github.com/jesseduffield/lazygit/pkg/integration/components"
+)
+
+var QuickStart = NewIntegrationTest(NewIntegrationTestArgs{
+ Description: "Quick-starts an interactive rebase in several contexts",
+ ExtraCmdArgs: []string{},
+ Skip: false,
+ SetupConfig: func(config *config.AppConfig) {},
+ SetupRepo: func(shell *Shell) {
+ // we're going to test the following:
+ // * quick start from main fails
+ // * quick start from feature branch starts from main
+ // * quick start from branch with merge commit starts from merge commit
+
+ shell.NewBranch("main")
+ shell.EmptyCommit("initial commit")
+ shell.EmptyCommit("last main commit")
+
+ shell.NewBranch("feature-branch")
+ shell.NewBranch("branch-to-merge")
+ shell.NewBranch("branch-with-merge-commit")
+
+ shell.Checkout("feature-branch")
+ shell.EmptyCommit("feature-branch one")
+ shell.EmptyCommit("feature-branch two")
+
+ shell.Checkout("branch-to-merge")
+ shell.EmptyCommit("branch-to-merge one")
+ shell.EmptyCommit("branch-to-merge two")
+
+ shell.Checkout("branch-with-merge-commit")
+ shell.EmptyCommit("branch-with-merge one")
+ shell.EmptyCommit("branch-with-merge two")
+
+ shell.Merge("branch-to-merge")
+
+ shell.EmptyCommit("branch-with-merge three")
+
+ shell.Checkout("main")
+ },
+ Run: func(t *TestDriver, keys config.KeybindingConfig) {
+ t.Views().Commits().
+ Focus().
+ Lines(
+ Contains("last main commit"),
+ Contains("initial commit"),
+ ).
+ // Verify we can't quick start from main
+ Press(keys.Commits.StartInteractiveRebase).
+ Tap(func() {
+ t.ExpectPopup().Alert().
+ Title(Equals("Error")).
+ Content(Contains("Cannot start interactive rebase: the HEAD commit is a merge commit or is present on the main branch, so there is no appropriate base commit to start the rebase from. You can start an interactive rebase from a specific commit by selecting the commit and pressing `e`.")).
+ Confirm()
+ })
+
+ t.Views().Branches().
+ Focus().
+ NavigateToLine(Contains("feature-branch")).
+ Press(keys.Universal.Select)
+
+ t.Views().Commits().
+ Focus().
+ Lines(
+ Contains("feature-branch two").IsSelected(),
+ Contains("feature-branch one"),
+ Contains("last main commit"),
+ Contains("initial commit"),
+ ).
+ // Verify quick start picks the last commit on the main branch
+ Press(keys.Commits.StartInteractiveRebase).
+ Lines(
+ Contains("feature-branch two").IsSelected(),
+ Contains("feature-branch one"),
+ Contains("last main commit").Contains("YOU ARE HERE"),
+ Contains("initial commit"),
+ ).
+ // Try again, verify we fail because we're already rebasing
+ Press(keys.Commits.StartInteractiveRebase).
+ Tap(func() {
+ t.ExpectPopup().Alert().
+ Title(Equals("Error")).
+ Content(Contains("Can't perform this action during a rebase")).
+ Confirm()
+
+ t.Common().AbortRebase()
+ })
+
+ // Verify if a merge commit is present on the branch we start from there
+ t.Views().Branches().
+ Focus().
+ NavigateToLine(Contains("branch-with-merge-commit")).
+ Press(keys.Universal.Select)
+
+ t.Views().Commits().
+ Focus().
+ Lines(
+ Contains("branch-with-merge three").IsSelected(),
+ Contains("Merge branch 'branch-to-merge'"),
+ Contains("branch-to-merge two"),
+ Contains("branch-to-merge one"),
+ Contains("branch-with-merge two"),
+ Contains("branch-with-merge one"),
+ Contains("last main commit"),
+ Contains("initial commit"),
+ ).
+ Press(keys.Commits.StartInteractiveRebase).
+ Lines(
+ Contains("branch-with-merge three").IsSelected(),
+ Contains("Merge branch 'branch-to-merge'").Contains("YOU ARE HERE"),
+ Contains("branch-to-merge two"),
+ Contains("branch-to-merge one"),
+ Contains("branch-with-merge two"),
+ Contains("branch-with-merge one"),
+ Contains("last main commit"),
+ Contains("initial commit"),
+ )
+ },
+})
diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go
index 1b6ab75e9826..a0e58d6fbc37 100644
--- a/pkg/integration/tests/test_list.go
+++ b/pkg/integration/tests/test_list.go
@@ -164,6 +164,7 @@ var tests = []*components.IntegrationTest{
interactive_rebase.MoveInRebase,
interactive_rebase.MoveWithCustomCommentChar,
interactive_rebase.PickRescheduled,
+ interactive_rebase.QuickStart,
interactive_rebase.Rebase,
interactive_rebase.RewordCommitWithEditorAndFail,
interactive_rebase.RewordFirstCommit,
diff --git a/schema/config.json b/schema/config.json
index 1251240a1d52..3de7df17d2f1 100644
--- a/schema/config.json
+++ b/schema/config.json
@@ -1143,6 +1143,10 @@
"viewBisectOptions": {
"type": "string",
"default": "b"
+ },
+ "startInteractiveRebase": {
+ "type": "string",
+ "default": "i"
}
},
"additionalProperties": false,
From a0b63090e03c42ddf10514851fbf7758299e0d59 Mon Sep 17 00:00:00 2001
From: Jesse Duffield
Date: Fri, 12 Jan 2024 11:17:26 +1100
Subject: [PATCH 037/280] Update codebase guide
I'm adding a couple more terms: Keybinding and Action, to keep things standardised
---
docs/dev/Codebase_Guide.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/docs/dev/Codebase_Guide.md b/docs/dev/Codebase_Guide.md
index 93a771274537..307482e2ab88 100644
--- a/docs/dev/Codebase_Guide.md
+++ b/docs/dev/Codebase_Guide.md
@@ -75,6 +75,8 @@ In terms of dependencies, controllers sit at the highest level, so they can refe
* **Tab**: Each tab in a window (e.g. Files, Worktrees, Submodules) actually has a corresponding view which we bring to the front upon changing tabs.
* **Model**: Representation of a git object e.g. commits, branches, files.
* **ViewModel**: Used by a context to maintain state related to the view.
+* **Keybinding**: A keybinding associates a _key_ with an _action_. For example if you press the 'down' arrow, the action performed will be your cursor moving down a list by one.
+* **Action**: An action is the thing that happens when you press a key. Often an action will invoke a git command, but not always: for example, navigation actions don't involve git.
* **Common structs**: Most structs have a field named `c` which contains a 'common' struct: a struct containing a bag of dependencies that most structs of the same layer require. For example if you want to access a helper from a controller you can do so with `self.c.Helpers.MyHelper`.
## Event loop and threads
From 23a98937b47abec81b5523f5e0cd5c1ba967948e Mon Sep 17 00:00:00 2001
From: Jesse Duffield
Date: Fri, 12 Jan 2024 11:21:28 +1100
Subject: [PATCH 038/280] Update README to add backticks to key
Missed a spot when I originally wrote this
---
README.md | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 16a1230329e6..bcebc8fec2a4 100644
--- a/README.md
+++ b/README.md
@@ -114,7 +114,9 @@ Press space on the selected line to stage it, or press `v` to start selecting a
### Interactive Rebase
-Press `e` on a commit to start an interactive rebase on it: causing all above commits to become part of the TODO file. Then squash (`s`), fixup (`f`), drop (`d`), edit (`e`), move up (ctrl+i) or move down (ctrl+j) any of TODO commits, before continuing the rebase by bringing up the rebase options menu with `m` and then selecting `continue`. You can also perform any these actions as a once-off (e.g. pressing `s` on a commit to squash it) without explicitly starting a rebase.
+Press `e` on a commit to start an interactive rebase on it: causing all above commits to become part of the TODO file. Then squash (`s`), fixup (`f`), drop (`d`), edit (`e`), move up (`ctrl+i`) or move down (`ctrl+j`) any of TODO commits, before continuing the rebase by bringing up the rebase options menu with `m` and then selecting `continue`.
+
+You can also perform any these actions as a once-off (e.g. pressing `s` on a commit to squash it) without explicitly starting a rebase.
![interactive_rebase](../assets/demo/interactive_rebase-compressed.gif)
From ee106f9af8354867c7ce310a52c726e24891f596 Mon Sep 17 00:00:00 2001
From: Jesse Duffield
Date: Sat, 13 Jan 2024 00:28:04 +1100
Subject: [PATCH 039/280] Do not perform IO work when getting disabled reason
with local commits
Because we obtain disabled reasons after every action, we need to keep the code for doing so
super fast. As such, we should not be hitting the filesystem to get rebase state, instead
we should just get the cached state.
I feel like we should actually be using the cached state everywhere like we do with all
our other models if only for the sake of consistency.
---
pkg/gui/controllers/local_commits_controller.go | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/pkg/gui/controllers/local_commits_controller.go b/pkg/gui/controllers/local_commits_controller.go
index f3637397bc61..d3b92bc935ba 100644
--- a/pkg/gui/controllers/local_commits_controller.go
+++ b/pkg/gui/controllers/local_commits_controller.go
@@ -534,7 +534,7 @@ func (self *LocalCommitsController) rebaseCommandEnabled(action todo.TodoCommand
}
if !commit.IsTODO() {
- if self.c.Git().Status.WorkingTreeState() != enums.REBASE_MODE_NONE {
+ if self.c.Model().WorkingTreeStateAtLastCommitRefresh != enums.REBASE_MODE_NONE {
// If we are in a rebase, the only action that is allowed for
// non-todo commits is rewording the current head commit
if !(action == todo.Reword && self.isHeadCommit()) {
@@ -688,7 +688,7 @@ func (self *LocalCommitsController) amendTo(commit *models.Commit) error {
}
func (self *LocalCommitsController) getDisabledReasonForAmendTo(commit *models.Commit) string {
- if !self.isHeadCommit() && self.c.Git().Status.WorkingTreeState() != enums.REBASE_MODE_NONE {
+ if !self.isHeadCommit() && self.c.Model().WorkingTreeStateAtLastCommitRefresh != enums.REBASE_MODE_NONE {
return self.c.Tr.AlreadyRebasing
}
@@ -871,7 +871,7 @@ func (self *LocalCommitsController) squashAllAboveFixupCommits(commit *models.Co
}
func (self *LocalCommitsController) getDisabledReasonForSquashAllAboveFixupCommits(commit *models.Commit) string {
- if self.c.Git().Status.WorkingTreeState() != enums.REBASE_MODE_NONE {
+ if self.c.Model().WorkingTreeStateAtLastCommitRefresh != enums.REBASE_MODE_NONE {
return self.c.Tr.AlreadyRebasing
}
@@ -880,7 +880,7 @@ func (self *LocalCommitsController) getDisabledReasonForSquashAllAboveFixupCommi
// For getting disabled reason
func (self *LocalCommitsController) notMidRebase() string {
- if self.c.Git().Status.WorkingTreeState() != enums.REBASE_MODE_NONE {
+ if self.c.Model().WorkingTreeStateAtLastCommitRefresh != enums.REBASE_MODE_NONE {
return self.c.Tr.AlreadyRebasing
}
From 9fa43394fed0aaa1114798decb814ebddc9eddc8 Mon Sep 17 00:00:00 2001
From: Stefan Haller
Date: Fri, 12 Jan 2024 08:12:39 +0100
Subject: [PATCH 040/280] Make it possible to handle toasts in integration
tests
Use it in two selected tests to demonstrate what it looks like.
---
pkg/app/app.go | 7 ++++---
pkg/cheatsheet/generate.go | 2 +-
pkg/gui/dummies.go | 2 +-
pkg/gui/gui.go | 1 +
pkg/gui/gui_driver.go | 10 ++++++++++
pkg/gui/popup/popup_handler.go | 4 ++++
pkg/gui/test_mode.go | 7 ++++++-
pkg/gui/types/common.go | 1 +
pkg/integration/components/test_driver.go | 15 +++++++++++++--
pkg/integration/components/test_test.go | 4 ++++
pkg/integration/tests/tag/crud_annotated.go | 1 +
pkg/integration/tests/tag/crud_lightweight.go | 1 +
pkg/integration/types/types.go | 2 ++
13 files changed, 49 insertions(+), 8 deletions(-)
diff --git a/pkg/app/app.go b/pkg/app/app.go
index f8e267ee99b9..a16fbcc1fe7b 100644
--- a/pkg/app/app.go
+++ b/pkg/app/app.go
@@ -23,6 +23,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/env"
"github.com/jesseduffield/lazygit/pkg/gui"
"github.com/jesseduffield/lazygit/pkg/i18n"
+ integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types"
"github.com/jesseduffield/lazygit/pkg/logs"
"github.com/jesseduffield/lazygit/pkg/updates"
)
@@ -42,7 +43,7 @@ func Run(
common *common.Common,
startArgs appTypes.StartArgs,
) {
- app, err := NewApp(config, common)
+ app, err := NewApp(config, startArgs.IntegrationTest, common)
if err == nil {
err = app.Run(startArgs)
@@ -94,7 +95,7 @@ func newLogger(cfg config.AppConfigurer) *logrus.Entry {
}
// NewApp bootstrap a new application
-func NewApp(config config.AppConfigurer, common *common.Common) (*App, error) {
+func NewApp(config config.AppConfigurer, test integrationTypes.IntegrationTest, common *common.Common) (*App, error) {
app := &App{
closers: []io.Closer{},
Config: config,
@@ -128,7 +129,7 @@ func NewApp(config config.AppConfigurer, common *common.Common) (*App, error) {
showRecentRepos = true
}
- app.Gui, err = gui.NewGui(common, config, gitVersion, updater, showRecentRepos, dirName)
+ app.Gui, err = gui.NewGui(common, config, gitVersion, updater, showRecentRepos, dirName, test)
if err != nil {
return app, err
}
diff --git a/pkg/cheatsheet/generate.go b/pkg/cheatsheet/generate.go
index 392e9d64d4b0..205abf7cb371 100644
--- a/pkg/cheatsheet/generate.go
+++ b/pkg/cheatsheet/generate.go
@@ -61,7 +61,7 @@ func generateAtDir(cheatsheetDir string) {
if err != nil {
log.Fatal(err)
}
- mApp, _ := app.NewApp(mConfig, common)
+ mApp, _ := app.NewApp(mConfig, nil, common)
path := cheatsheetDir + "/Keybindings_" + lang + ".md"
file, err := os.Create(path)
if err != nil {
diff --git a/pkg/gui/dummies.go b/pkg/gui/dummies.go
index 90bd094d862a..144df1019d4b 100644
--- a/pkg/gui/dummies.go
+++ b/pkg/gui/dummies.go
@@ -17,6 +17,6 @@ func NewDummyUpdater() *updates.Updater {
func NewDummyGui() *Gui {
newAppConfig := config.NewDummyAppConfig()
- dummyGui, _ := NewGui(utils.NewDummyCommon(), newAppConfig, &git_commands.GitVersion{}, NewDummyUpdater(), false, "")
+ dummyGui, _ := NewGui(utils.NewDummyCommon(), newAppConfig, &git_commands.GitVersion{}, NewDummyUpdater(), false, "", nil)
return dummyGui
}
diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go
index dd685dc72609..6eaa1b8db926 100644
--- a/pkg/gui/gui.go
+++ b/pkg/gui/gui.go
@@ -467,6 +467,7 @@ func NewGui(
updater *updates.Updater,
showRecentRepos bool,
initialDir string,
+ test integrationTypes.IntegrationTest,
) (*Gui, error) {
gui := &Gui{
Common: cmn,
diff --git a/pkg/gui/gui_driver.go b/pkg/gui/gui_driver.go
index c36e68e93965..ddeb3fab4d47 100644
--- a/pkg/gui/gui_driver.go
+++ b/pkg/gui/gui_driver.go
@@ -20,6 +20,7 @@ import (
type GuiDriver struct {
gui *Gui
isIdleChan chan struct{}
+ toastChan chan string
}
var _ integrationTypes.GuiDriver = &GuiDriver{}
@@ -133,3 +134,12 @@ func (self *GuiDriver) SetCaptionPrefix(prefix string) {
self.gui.setCaptionPrefix(prefix)
self.waitTillIdle()
}
+
+func (self *GuiDriver) NextToast() *string {
+ select {
+ case t := <-self.toastChan:
+ return &t
+ default:
+ return nil
+ }
+}
diff --git a/pkg/gui/popup/popup_handler.go b/pkg/gui/popup/popup_handler.go
index 33c01e0cca3a..82bf846e053f 100644
--- a/pkg/gui/popup/popup_handler.go
+++ b/pkg/gui/popup/popup_handler.go
@@ -66,6 +66,10 @@ func (self *PopupHandler) Toast(message string) {
self.toastFn(message)
}
+func (self *PopupHandler) SetToastFunc(f func(string)) {
+ self.toastFn = f
+}
+
func (self *PopupHandler) WithWaitingStatus(message string, f func(gocui.Task) error) error {
self.withWaitingStatusFn(message, f)
return nil
diff --git a/pkg/gui/test_mode.go b/pkg/gui/test_mode.go
index 151cb72465ac..bc4436053734 100644
--- a/pkg/gui/test_mode.go
+++ b/pkg/gui/test_mode.go
@@ -6,6 +6,7 @@ import (
"time"
"github.com/jesseduffield/gocui"
+ "github.com/jesseduffield/lazygit/pkg/gui/popup"
"github.com/jesseduffield/lazygit/pkg/integration/components"
"github.com/jesseduffield/lazygit/pkg/utils"
)
@@ -32,7 +33,11 @@ func (gui *Gui) handleTestMode() {
go func() {
waitUntilIdle()
- test.Run(&GuiDriver{gui: gui, isIdleChan: isIdleChan})
+ toastChan := make(chan string, 100)
+ gui.PopupHandler.(*popup.PopupHandler).SetToastFunc(
+ func(message string) { toastChan <- message })
+
+ test.Run(&GuiDriver{gui: gui, isIdleChan: isIdleChan, toastChan: toastChan})
gui.g.Update(func(*gocui.Gui) error {
return gocui.ErrQuit
diff --git a/pkg/gui/types/common.go b/pkg/gui/types/common.go
index 366b0fcf0b18..6e16df6b01f5 100644
--- a/pkg/gui/types/common.go
+++ b/pkg/gui/types/common.go
@@ -144,6 +144,7 @@ type IPopupHandler interface {
WithWaitingStatusSync(message string, f func() error) error
Menu(opts CreateMenuOptions) error
Toast(message string)
+ SetToastFunc(func(string))
GetPromptInput() string
}
diff --git a/pkg/integration/components/test_driver.go b/pkg/integration/components/test_driver.go
index a862dce06df1..d266dfb73512 100644
--- a/pkg/integration/components/test_driver.go
+++ b/pkg/integration/components/test_driver.go
@@ -102,8 +102,19 @@ func (self *TestDriver) ExpectPopup() *Popup {
return &Popup{t: self}
}
-func (self *TestDriver) ExpectToast(matcher *TextMatcher) {
- self.Views().AppStatus().Content(matcher)
+func (self *TestDriver) ExpectToast(matcher *TextMatcher) *TestDriver {
+ t := self.gui.NextToast()
+ if t == nil {
+ self.gui.Fail("Expected toast, but didn't get one")
+ } else {
+ self.matchString(matcher, "Unexpected toast message",
+ func() string {
+ return *t
+ },
+ )
+ }
+
+ return self
}
func (self *TestDriver) ExpectClipboard(matcher *TextMatcher) {
diff --git a/pkg/integration/components/test_test.go b/pkg/integration/components/test_test.go
index 99e52f5e9879..fa4401de570d 100644
--- a/pkg/integration/components/test_test.go
+++ b/pkg/integration/components/test_test.go
@@ -78,6 +78,10 @@ func (self *fakeGuiDriver) SetCaption(string) {
func (self *fakeGuiDriver) SetCaptionPrefix(string) {
}
+func (self *fakeGuiDriver) NextToast() *string {
+ return nil
+}
+
func TestManualFailure(t *testing.T) {
test := NewIntegrationTest(NewIntegrationTestArgs{
Description: unitTestDescription,
diff --git a/pkg/integration/tests/tag/crud_annotated.go b/pkg/integration/tests/tag/crud_annotated.go
index 930859c902c1..12fa166451af 100644
--- a/pkg/integration/tests/tag/crud_annotated.go
+++ b/pkg/integration/tests/tag/crud_annotated.go
@@ -65,6 +65,7 @@ var CrudAnnotated = NewIntegrationTest(NewIntegrationTestArgs{
Title(Equals("Delete tag 'new-tag'?")).
Content(Equals("Are you sure you want to delete the remote tag 'new-tag' from 'origin'?")).
Confirm()
+ t.ExpectToast(Equals("Remote tag deleted"))
}).
Lines(
MatchesRegexp(`new-tag.*message`).IsSelected(),
diff --git a/pkg/integration/tests/tag/crud_lightweight.go b/pkg/integration/tests/tag/crud_lightweight.go
index 6aab10dd1833..dd6614683a1d 100644
--- a/pkg/integration/tests/tag/crud_lightweight.go
+++ b/pkg/integration/tests/tag/crud_lightweight.go
@@ -70,6 +70,7 @@ var CrudLightweight = NewIntegrationTest(NewIntegrationTestArgs{
Title(Equals("Delete tag 'new-tag'?")).
Content(Equals("Are you sure you want to delete the remote tag 'new-tag' from 'origin'?")).
Confirm()
+ t.ExpectToast(Equals("Remote tag deleted"))
}).
Lines(
MatchesRegexp(`new-tag.*initial commit`).IsSelected(),
diff --git a/pkg/integration/types/types.go b/pkg/integration/types/types.go
index 15a2d514f80a..b9b981d74ac8 100644
--- a/pkg/integration/types/types.go
+++ b/pkg/integration/types/types.go
@@ -43,4 +43,6 @@ type GuiDriver interface {
View(viewName string) *gocui.View
SetCaption(caption string)
SetCaptionPrefix(prefix string)
+ // Pop the next toast that was displayed; returns nil if there was none
+ NextToast() *string
}
From 8ca8a4396859559d818c2b5a51411da7d618ee70 Mon Sep 17 00:00:00 2001
From: Stefan Haller
Date: Sat, 13 Jan 2024 16:17:41 +0100
Subject: [PATCH 041/280] Make it mandatory to acknowledge toasts in tests
---
pkg/gui/gui_driver.go | 10 ++++++++++
pkg/integration/components/test.go | 2 ++
pkg/integration/components/test_test.go | 2 ++
pkg/integration/tests/file/copy_menu.go | 12 ++++++++++++
pkg/integration/tests/misc/copy_to_clipboard.go | 2 ++
.../tests/staging/diff_context_change.go | 15 +++++++++++++++
pkg/integration/types/types.go | 1 +
7 files changed, 44 insertions(+)
diff --git a/pkg/gui/gui_driver.go b/pkg/gui/gui_driver.go
index ddeb3fab4d47..e7fa097d3618 100644
--- a/pkg/gui/gui_driver.go
+++ b/pkg/gui/gui_driver.go
@@ -26,6 +26,8 @@ type GuiDriver struct {
var _ integrationTypes.GuiDriver = &GuiDriver{}
func (self *GuiDriver) PressKey(keyStr string) {
+ self.CheckAllToastsAcknowledged()
+
key := keybindings.GetKey(keyStr)
var r rune
@@ -47,6 +49,8 @@ func (self *GuiDriver) PressKey(keyStr string) {
}
func (self *GuiDriver) Click(x, y int) {
+ self.CheckAllToastsAcknowledged()
+
self.gui.g.ReplayedEvents.MouseEvents <- gocui.NewTcellMouseEventWrapper(
tcell.NewEventMouse(x, y, tcell.ButtonPrimary, 0),
0,
@@ -59,6 +63,12 @@ func (self *GuiDriver) waitTillIdle() {
<-self.isIdleChan
}
+func (self *GuiDriver) CheckAllToastsAcknowledged() {
+ if t := self.NextToast(); t != nil {
+ self.Fail("Toast not acknowledged: " + *t)
+ }
+}
+
func (self *GuiDriver) Keys() config.KeybindingConfig {
return self.gui.Config.GetUserConfig().Keybinding
}
diff --git a/pkg/integration/components/test.go b/pkg/integration/components/test.go
index c837d8be8009..203436ae7555 100644
--- a/pkg/integration/components/test.go
+++ b/pkg/integration/components/test.go
@@ -194,6 +194,8 @@ func (self *IntegrationTest) Run(gui integrationTypes.GuiDriver) {
self.run(testDriver, keys)
+ gui.CheckAllToastsAcknowledged()
+
if InputDelay() > 0 {
// Clear whatever caption there was so it doesn't linger
testDriver.SetCaption("")
diff --git a/pkg/integration/components/test_test.go b/pkg/integration/components/test_test.go
index fa4401de570d..047ee507c681 100644
--- a/pkg/integration/components/test_test.go
+++ b/pkg/integration/components/test_test.go
@@ -82,6 +82,8 @@ func (self *fakeGuiDriver) NextToast() *string {
return nil
}
+func (self *fakeGuiDriver) CheckAllToastsAcknowledged() {}
+
func TestManualFailure(t *testing.T) {
test := NewIntegrationTest(NewIntegrationTestArgs{
Description: unitTestDescription,
diff --git a/pkg/integration/tests/file/copy_menu.go b/pkg/integration/tests/file/copy_menu.go
index f00425c967f0..5f7f00623e03 100644
--- a/pkg/integration/tests/file/copy_menu.go
+++ b/pkg/integration/tests/file/copy_menu.go
@@ -101,6 +101,8 @@ var CopyMenu = NewIntegrationTest(NewIntegrationTestArgs{
Select(Contains("File name")).
Confirm()
+ t.ExpectToast(Equals("File name copied to clipboard"))
+
expectClipboard(t, Contains("unstaged_file"))
})
@@ -113,6 +115,8 @@ var CopyMenu = NewIntegrationTest(NewIntegrationTestArgs{
Select(Contains("Path")).
Confirm()
+ t.ExpectToast(Equals("File path copied to clipboard"))
+
expectClipboard(t, Contains("dir/1-unstaged_file"))
})
@@ -126,6 +130,8 @@ var CopyMenu = NewIntegrationTest(NewIntegrationTestArgs{
Tooltip(Equals("If there are staged items, this command considers only them. Otherwise, it considers all the unstaged ones.")).
Confirm()
+ t.ExpectToast(Equals("File diff copied to clipboard"))
+
expectClipboard(t, Contains("+unstaged content (new)"))
})
@@ -145,6 +151,8 @@ var CopyMenu = NewIntegrationTest(NewIntegrationTestArgs{
Tooltip(Equals("If there are staged items, this command considers only them. Otherwise, it considers all the unstaged ones.")).
Confirm()
+ t.ExpectToast(Equals("File diff copied to clipboard"))
+
expectClipboard(t, Contains("+staged content (new)"))
})
@@ -158,6 +166,8 @@ var CopyMenu = NewIntegrationTest(NewIntegrationTestArgs{
Tooltip(Equals("If there are staged items, this command considers only them. Otherwise, it considers all the unstaged ones.")).
Confirm()
+ t.ExpectToast(Equals("All files diff copied to clipboard"))
+
expectClipboard(t, Contains("+staged content (new)"))
})
@@ -179,6 +189,8 @@ var CopyMenu = NewIntegrationTest(NewIntegrationTestArgs{
Tooltip(Equals("If there are staged items, this command considers only them. Otherwise, it considers all the unstaged ones.")).
Confirm()
+ t.ExpectToast(Equals("All files diff copied to clipboard"))
+
expectClipboard(t, Contains("+staged content (new)").Contains("+unstaged content (new)"))
})
},
diff --git a/pkg/integration/tests/misc/copy_to_clipboard.go b/pkg/integration/tests/misc/copy_to_clipboard.go
index 48fae8136644..6b1d5d1f6d46 100644
--- a/pkg/integration/tests/misc/copy_to_clipboard.go
+++ b/pkg/integration/tests/misc/copy_to_clipboard.go
@@ -27,6 +27,8 @@ var CopyToClipboard = NewIntegrationTest(NewIntegrationTestArgs{
).
Press(keys.Universal.CopyToClipboard)
+ t.ExpectToast(Equals("'branch-a' Copied to clipboard"))
+
t.Views().Files().
Focus()
diff --git a/pkg/integration/tests/staging/diff_context_change.go b/pkg/integration/tests/staging/diff_context_change.go
index ca0aa0780a20..141f8bcec50a 100644
--- a/pkg/integration/tests/staging/diff_context_change.go
+++ b/pkg/integration/tests/staging/diff_context_change.go
@@ -62,6 +62,9 @@ var DiffContextChange = NewIntegrationTest(NewIntegrationTestArgs{
Contains(` 6a`),
).
Press(keys.Universal.IncreaseContextInDiffView).
+ Tap(func() {
+ t.ExpectToast(Equals("Changed diff context size to 4"))
+ }).
SelectedLines(
Contains(`@@ -1,7 +1,7 @@`),
Contains(` 1a`),
@@ -74,6 +77,9 @@ var DiffContextChange = NewIntegrationTest(NewIntegrationTestArgs{
Contains(` 7a`),
).
Press(keys.Universal.DecreaseContextInDiffView).
+ Tap(func() {
+ t.ExpectToast(Equals("Changed diff context size to 3"))
+ }).
SelectedLines(
Contains(`@@ -1,6 +1,6 @@`),
Contains(` 1a`),
@@ -85,6 +91,9 @@ var DiffContextChange = NewIntegrationTest(NewIntegrationTestArgs{
Contains(` 6a`),
).
Press(keys.Universal.DecreaseContextInDiffView).
+ Tap(func() {
+ t.ExpectToast(Equals("Changed diff context size to 2"))
+ }).
SelectedLines(
Contains(`@@ -1,5 +1,5 @@`),
Contains(` 1a`),
@@ -95,6 +104,9 @@ var DiffContextChange = NewIntegrationTest(NewIntegrationTestArgs{
Contains(` 5a`),
).
Press(keys.Universal.DecreaseContextInDiffView).
+ Tap(func() {
+ t.ExpectToast(Equals("Changed diff context size to 1"))
+ }).
SelectedLines(
Contains(`@@ -2,3 +2,3 @@`),
Contains(` 2a`),
@@ -116,6 +128,9 @@ var DiffContextChange = NewIntegrationTest(NewIntegrationTestArgs{
Contains(` 4a`),
).
Press(keys.Universal.IncreaseContextInDiffView).
+ Tap(func() {
+ t.ExpectToast(Equals("Changed diff context size to 2"))
+ }).
SelectedLines(
Contains(`@@ -1,5 +1,5 @@`),
Contains(` 1a`),
diff --git a/pkg/integration/types/types.go b/pkg/integration/types/types.go
index b9b981d74ac8..a30aeb055ba0 100644
--- a/pkg/integration/types/types.go
+++ b/pkg/integration/types/types.go
@@ -45,4 +45,5 @@ type GuiDriver interface {
SetCaptionPrefix(prefix string)
// Pop the next toast that was displayed; returns nil if there was none
NextToast() *string
+ CheckAllToastsAcknowledged()
}
From 99a3ccde71ceebcbf8045655ef604a1965e64a5a Mon Sep 17 00:00:00 2001
From: Stefan Haller
Date: Fri, 22 Dec 2023 17:31:13 +0100
Subject: [PATCH 042/280] Add ErrorToast function
---
.../controllers/helpers/app_status_helper.go | 14 +++++++----
pkg/gui/gui.go | 2 +-
pkg/gui/popup/popup_handler.go | 12 ++++++---
pkg/gui/status/status_manager.go | 25 +++++++++++++------
pkg/gui/test_mode.go | 3 ++-
pkg/gui/types/common.go | 10 +++++++-
6 files changed, 46 insertions(+), 20 deletions(-)
diff --git a/pkg/gui/controllers/helpers/app_status_helper.go b/pkg/gui/controllers/helpers/app_status_helper.go
index 8e4741bd53bb..fd6bc247fa35 100644
--- a/pkg/gui/controllers/helpers/app_status_helper.go
+++ b/pkg/gui/controllers/helpers/app_status_helper.go
@@ -5,6 +5,7 @@ import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/status"
+ "github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
)
@@ -23,7 +24,7 @@ func NewAppStatusHelper(c *HelperCommon, statusMgr func() *status.StatusManager,
}
}
-func (self *AppStatusHelper) Toast(message string) {
+func (self *AppStatusHelper) Toast(message string, kind types.ToastKind) {
if self.c.RunningIntegrationTest() {
// Don't bother showing toasts in integration tests. You can't check for
// them anyway, and they would only slow down the test unnecessarily by
@@ -31,7 +32,7 @@ func (self *AppStatusHelper) Toast(message string) {
return
}
- self.statusMgr().AddToastStatus(message)
+ self.statusMgr().AddToastStatus(message, kind)
self.renderAppStatus()
}
@@ -87,7 +88,8 @@ func (self *AppStatusHelper) HasStatus() bool {
}
func (self *AppStatusHelper) GetStatusString() string {
- return self.statusMgr().GetStatusString()
+ appStatus, _ := self.statusMgr().GetStatusString()
+ return appStatus
}
func (self *AppStatusHelper) renderAppStatus() {
@@ -95,7 +97,8 @@ func (self *AppStatusHelper) renderAppStatus() {
ticker := time.NewTicker(time.Millisecond * utils.LoaderAnimationInterval)
defer ticker.Stop()
for range ticker.C {
- appStatus := self.statusMgr().GetStatusString()
+ appStatus, color := self.statusMgr().GetStatusString()
+ self.c.Views().AppStatus.FgColor = color
self.c.OnUIThread(func() error {
self.c.SetViewContent(self.c.Views().AppStatus, appStatus)
return nil
@@ -127,7 +130,8 @@ func (self *AppStatusHelper) renderAppStatusSync(stop chan struct{}) {
for {
select {
case <-ticker.C:
- appStatus := self.statusMgr().GetStatusString()
+ appStatus, color := self.statusMgr().GetStatusString()
+ self.c.Views().AppStatus.FgColor = color
self.c.SetViewContent(self.c.Views().AppStatus, appStatus)
// Redraw all views of the bottom line:
bottomLineViews := []*gocui.View{
diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go
index 6eaa1b8db926..6acdc804ca45 100644
--- a/pkg/gui/gui.go
+++ b/pkg/gui/gui.go
@@ -517,7 +517,7 @@ func NewGui(
func(message string, f func() error) {
gui.helpers.AppStatus.WithWaitingStatusSync(message, f)
},
- func(message string) { gui.helpers.AppStatus.Toast(message) },
+ func(message string, kind types.ToastKind) { gui.helpers.AppStatus.Toast(message, kind) },
func() string { return gui.Views.Confirmation.TextArea.GetContent() },
func() bool { return gui.c.InDemo() },
)
diff --git a/pkg/gui/popup/popup_handler.go b/pkg/gui/popup/popup_handler.go
index 82bf846e053f..1eb81e800667 100644
--- a/pkg/gui/popup/popup_handler.go
+++ b/pkg/gui/popup/popup_handler.go
@@ -22,7 +22,7 @@ type PopupHandler struct {
createMenuFn func(types.CreateMenuOptions) error
withWaitingStatusFn func(message string, f func(gocui.Task) error)
withWaitingStatusSyncFn func(message string, f func() error)
- toastFn func(message string)
+ toastFn func(message string, kind types.ToastKind)
getPromptInputFn func() string
inDemo func() bool
}
@@ -38,7 +38,7 @@ func NewPopupHandler(
createMenuFn func(types.CreateMenuOptions) error,
withWaitingStatusFn func(message string, f func(gocui.Task) error),
withWaitingStatusSyncFn func(message string, f func() error),
- toastFn func(message string),
+ toastFn func(message string, kind types.ToastKind),
getPromptInputFn func() string,
inDemo func() bool,
) *PopupHandler {
@@ -63,10 +63,14 @@ func (self *PopupHandler) Menu(opts types.CreateMenuOptions) error {
}
func (self *PopupHandler) Toast(message string) {
- self.toastFn(message)
+ self.toastFn(message, types.ToastKindStatus)
}
-func (self *PopupHandler) SetToastFunc(f func(string)) {
+func (self *PopupHandler) ErrorToast(message string) {
+ self.toastFn(message, types.ToastKindError)
+}
+
+func (self *PopupHandler) SetToastFunc(f func(string, types.ToastKind)) {
self.toastFn = f
}
diff --git a/pkg/gui/status/status_manager.go b/pkg/gui/status/status_manager.go
index da4f5f14cba7..f85036463333 100644
--- a/pkg/gui/status/status_manager.go
+++ b/pkg/gui/status/status_manager.go
@@ -3,6 +3,8 @@ package status
import (
"time"
+ "github.com/jesseduffield/gocui"
+ "github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
"github.com/sasha-s/go-deadlock"
@@ -26,7 +28,7 @@ type WaitingStatusHandle struct {
}
func (self *WaitingStatusHandle) Show() {
- self.id = self.statusManager.addStatus(self.message, "waiting")
+ self.id = self.statusManager.addStatus(self.message, "waiting", types.ToastKindStatus)
self.renderFunc()
}
@@ -37,6 +39,7 @@ func (self *WaitingStatusHandle) Hide() {
type appStatus struct {
message string
statusType string
+ color gocui.Attribute
id int
}
@@ -53,8 +56,8 @@ func (self *StatusManager) WithWaitingStatus(message string, renderFunc func(),
handle.Hide()
}
-func (self *StatusManager) AddToastStatus(message string) int {
- id := self.addStatus(message, "toast")
+func (self *StatusManager) AddToastStatus(message string, kind types.ToastKind) int {
+ id := self.addStatus(message, "toast", kind)
go func() {
time.Sleep(time.Second * 2)
@@ -65,31 +68,37 @@ func (self *StatusManager) AddToastStatus(message string) int {
return id
}
-func (self *StatusManager) GetStatusString() string {
+func (self *StatusManager) GetStatusString() (string, gocui.Attribute) {
if len(self.statuses) == 0 {
- return ""
+ return "", gocui.ColorDefault
}
topStatus := self.statuses[0]
if topStatus.statusType == "waiting" {
- return topStatus.message + " " + utils.Loader(time.Now())
+ return topStatus.message + " " + utils.Loader(time.Now()), topStatus.color
}
- return topStatus.message
+ return topStatus.message, topStatus.color
}
func (self *StatusManager) HasStatus() bool {
return len(self.statuses) > 0
}
-func (self *StatusManager) addStatus(message string, statusType string) int {
+func (self *StatusManager) addStatus(message string, statusType string, kind types.ToastKind) int {
self.mutex.Lock()
defer self.mutex.Unlock()
self.nextId++
id := self.nextId
+ color := gocui.ColorCyan
+ if kind == types.ToastKindError {
+ color = gocui.ColorRed
+ }
+
newStatus := appStatus{
message: message,
statusType: statusType,
+ color: color,
id: id,
}
self.statuses = append([]appStatus{newStatus}, self.statuses...)
diff --git a/pkg/gui/test_mode.go b/pkg/gui/test_mode.go
index bc4436053734..65009fb78b75 100644
--- a/pkg/gui/test_mode.go
+++ b/pkg/gui/test_mode.go
@@ -7,6 +7,7 @@ import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/popup"
+ "github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/integration/components"
"github.com/jesseduffield/lazygit/pkg/utils"
)
@@ -35,7 +36,7 @@ func (gui *Gui) handleTestMode() {
toastChan := make(chan string, 100)
gui.PopupHandler.(*popup.PopupHandler).SetToastFunc(
- func(message string) { toastChan <- message })
+ func(message string, kind types.ToastKind) { toastChan <- message })
test.Run(&GuiDriver{gui: gui, isIdleChan: isIdleChan, toastChan: toastChan})
diff --git a/pkg/gui/types/common.go b/pkg/gui/types/common.go
index 6e16df6b01f5..afa4156c81a6 100644
--- a/pkg/gui/types/common.go
+++ b/pkg/gui/types/common.go
@@ -144,10 +144,18 @@ type IPopupHandler interface {
WithWaitingStatusSync(message string, f func() error) error
Menu(opts CreateMenuOptions) error
Toast(message string)
- SetToastFunc(func(string))
+ ErrorToast(message string)
+ SetToastFunc(func(string, ToastKind))
GetPromptInput() string
}
+type ToastKind int
+
+const (
+ ToastKindStatus ToastKind = iota
+ ToastKindError
+)
+
type CreateMenuOptions struct {
Title string
Items []*MenuItem
From 09a24ee97dfa7fd38706e73e0ff7eaef3457c4e7 Mon Sep 17 00:00:00 2001
From: Stefan Haller
Date: Fri, 22 Dec 2023 17:31:45 +0100
Subject: [PATCH 043/280] Use ErrorToast instead of error panel when invoking a
disabled command
---
pkg/gui/context/menu_context.go | 3 +-
pkg/gui/keybindings.go | 3 +-
pkg/integration/components/menu_driver.go | 9 ++++-
pkg/integration/tests/branch/delete.go | 11 +++----
.../tests/branch/rebase_to_upstream.go | 10 +++---
.../tests/branch/reset_to_upstream.go | 10 +++---
pkg/integration/tests/file/copy_menu.go | 33 +++++++++----------
.../amend_non_head_commit_during_rebase.go | 5 +--
.../edit_non_todo_commit_during_rebase.go | 5 +--
.../edit_the_confl_commit.go | 5 +--
.../interactive_rebase/fixup_first_commit.go | 5 +--
.../tests/interactive_rebase/quick_start.go | 23 ++++---------
.../squash_down_first_commit.go | 5 +--
13 files changed, 54 insertions(+), 73 deletions(-)
diff --git a/pkg/gui/context/menu_context.go b/pkg/gui/context/menu_context.go
index f972f2fbb4e9..1ea0e64c136e 100644
--- a/pkg/gui/context/menu_context.go
+++ b/pkg/gui/context/menu_context.go
@@ -173,7 +173,8 @@ func (self *MenuContext) GetKeybindings(opts types.KeybindingsOpts) []*types.Bin
func (self *MenuContext) OnMenuPress(selectedItem *types.MenuItem) error {
if selectedItem != nil && selectedItem.DisabledReason != "" {
- return self.c.ErrorMsg(selectedItem.DisabledReason)
+ self.c.ErrorToast(self.c.Tr.DisabledMenuItemPrefix + selectedItem.DisabledReason)
+ return nil
}
if err := self.c.PopContext(); err != nil {
diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go
index afb12ce8570d..a2e9fafbd8dc 100644
--- a/pkg/gui/keybindings.go
+++ b/pkg/gui/keybindings.go
@@ -416,7 +416,8 @@ func (gui *Gui) callKeybindingHandler(binding *types.Binding) error {
disabledReason = binding.GetDisabledReason()
}
if disabledReason != "" {
- return gui.c.ErrorMsg(disabledReason)
+ gui.c.ErrorToast(gui.Tr.DisabledMenuItemPrefix + disabledReason)
+ return nil
}
return binding.Handler()
}
diff --git a/pkg/integration/components/menu_driver.go b/pkg/integration/components/menu_driver.go
index 7ab42a3e1c85..2f93df82d49b 100644
--- a/pkg/integration/components/menu_driver.go
+++ b/pkg/integration/components/menu_driver.go
@@ -18,10 +18,12 @@ func (self *MenuDriver) Title(expected *TextMatcher) *MenuDriver {
return self
}
-func (self *MenuDriver) Confirm() {
+func (self *MenuDriver) Confirm() *MenuDriver {
self.checkNecessaryChecksCompleted()
self.getViewDriver().PressEnter()
+
+ return self
}
func (self *MenuDriver) Cancel() {
@@ -72,6 +74,11 @@ func (self *MenuDriver) Tooltip(option *TextMatcher) *MenuDriver {
return self
}
+func (self *MenuDriver) Tap(f func()) *MenuDriver {
+ self.getViewDriver().Tap(f)
+ return self
+}
+
func (self *MenuDriver) checkNecessaryChecksCompleted() {
if !self.hasCheckedTitle {
self.t.Fail("You must check the title of a menu popup by calling Title() before calling Confirm()/Cancel().")
diff --git a/pkg/integration/tests/branch/delete.go b/pkg/integration/tests/branch/delete.go
index cdda78ec6a19..0b6adfac4d02 100644
--- a/pkg/integration/tests/branch/delete.go
+++ b/pkg/integration/tests/branch/delete.go
@@ -37,12 +37,11 @@ var Delete = NewIntegrationTest(NewIntegrationTestArgs{
Tooltip(Contains("You cannot delete the checked out branch!")).
Title(Equals("Delete branch 'branch-three'?")).
Select(Contains("Delete local branch")).
- Confirm()
- t.ExpectPopup().
- Alert().
- Title(Equals("Error")).
- Content(Contains("You cannot delete the checked out branch!")).
- Confirm()
+ Confirm().
+ Tap(func() {
+ t.ExpectToast(Contains("You cannot delete the checked out branch!"))
+ }).
+ Cancel()
}).
SelectNextItem().
Press(keys.Universal.Remove).
diff --git a/pkg/integration/tests/branch/rebase_to_upstream.go b/pkg/integration/tests/branch/rebase_to_upstream.go
index 1fc51350d0df..5c9e0f388cff 100644
--- a/pkg/integration/tests/branch/rebase_to_upstream.go
+++ b/pkg/integration/tests/branch/rebase_to_upstream.go
@@ -48,11 +48,11 @@ var RebaseToUpstream = NewIntegrationTest(NewIntegrationTestArgs{
Title(Equals("Upstream options")).
Select(Contains("Rebase checked-out branch onto upstream of selected branch")).
Tooltip(Contains("Disabled: The selected branch has no upstream (or the upstream is not stored locally)")).
- Confirm()
- t.ExpectPopup().Alert().
- Title(Equals("Error")).
- Content(Equals("The selected branch has no upstream (or the upstream is not stored locally)")).
- Confirm()
+ Confirm().
+ Tap(func() {
+ t.ExpectToast(Equals("Disabled: The selected branch has no upstream (or the upstream is not stored locally)"))
+ }).
+ Cancel()
}).
SelectNextItem().
Lines(
diff --git a/pkg/integration/tests/branch/reset_to_upstream.go b/pkg/integration/tests/branch/reset_to_upstream.go
index 1eecd689b0c4..c933787e492b 100644
--- a/pkg/integration/tests/branch/reset_to_upstream.go
+++ b/pkg/integration/tests/branch/reset_to_upstream.go
@@ -42,11 +42,11 @@ var ResetToUpstream = NewIntegrationTest(NewIntegrationTestArgs{
Title(Equals("Upstream options")).
Select(Contains("Reset checked-out branch onto upstream of selected branch")).
Tooltip(Contains("Disabled: The selected branch has no upstream (or the upstream is not stored locally)")).
- Confirm()
- t.ExpectPopup().Alert().
- Title(Equals("Error")).
- Content(Equals("The selected branch has no upstream (or the upstream is not stored locally)")).
- Confirm()
+ Confirm().
+ Tap(func() {
+ t.ExpectToast(Equals("Disabled: The selected branch has no upstream (or the upstream is not stored locally)"))
+ }).
+ Cancel()
}).
SelectNextItem().
Lines(
diff --git a/pkg/integration/tests/file/copy_menu.go b/pkg/integration/tests/file/copy_menu.go
index 5f7f00623e03..a1af13d7b40e 100644
--- a/pkg/integration/tests/file/copy_menu.go
+++ b/pkg/integration/tests/file/copy_menu.go
@@ -30,12 +30,11 @@ var CopyMenu = NewIntegrationTest(NewIntegrationTestArgs{
Title(Equals("Copy to clipboard")).
Select(Contains("File name")).
Tooltip(Equals("Disabled: Nothing to copy")).
- Confirm()
-
- t.ExpectPopup().Alert().
- Title(Equals("Error")).
- Content(Equals("Nothing to copy")).
- Confirm()
+ Confirm().
+ Tap(func() {
+ t.ExpectToast(Equals("Disabled: Nothing to copy"))
+ }).
+ Cancel()
})
t.Shell().
@@ -56,12 +55,11 @@ var CopyMenu = NewIntegrationTest(NewIntegrationTestArgs{
Title(Equals("Copy to clipboard")).
Select(Contains("Diff of selected file")).
Tooltip(Contains("Disabled: Nothing to copy")).
- Confirm()
-
- t.ExpectPopup().Alert().
- Title(Equals("Error")).
- Content(Equals("Nothing to copy")).
- Confirm()
+ Confirm().
+ Tap(func() {
+ t.ExpectToast(Equals("Disabled: Nothing to copy"))
+ }).
+ Cancel()
}).
Press(keys.Files.CopyFileInfoToClipboard).
Tap(func() {
@@ -69,12 +67,11 @@ var CopyMenu = NewIntegrationTest(NewIntegrationTestArgs{
Title(Equals("Copy to clipboard")).
Select(Contains("Diff of all files")).
Tooltip(Contains("Disabled: Nothing to copy")).
- Confirm()
-
- t.ExpectPopup().Alert().
- Title(Equals("Error")).
- Content(Equals("Nothing to copy")).
- Confirm()
+ Confirm().
+ Tap(func() {
+ t.ExpectToast(Equals("Disabled: Nothing to copy"))
+ }).
+ Cancel()
})
t.Shell().
diff --git a/pkg/integration/tests/interactive_rebase/amend_non_head_commit_during_rebase.go b/pkg/integration/tests/interactive_rebase/amend_non_head_commit_during_rebase.go
index e9c421297c61..a0d0f066e509 100644
--- a/pkg/integration/tests/interactive_rebase/amend_non_head_commit_during_rebase.go
+++ b/pkg/integration/tests/interactive_rebase/amend_non_head_commit_during_rebase.go
@@ -34,10 +34,7 @@ var AmendNonHeadCommitDuringRebase = NewIntegrationTest(NewIntegrationTestArgs{
NavigateToLine(Contains(commit)).
Press(keys.Commits.AmendToCommit)
- t.ExpectPopup().Alert().
- Title(Equals("Error")).
- Content(Contains("Can't perform this action during a rebase")).
- Confirm()
+ t.ExpectToast(Contains("Can't perform this action during a rebase"))
}
},
})
diff --git a/pkg/integration/tests/interactive_rebase/edit_non_todo_commit_during_rebase.go b/pkg/integration/tests/interactive_rebase/edit_non_todo_commit_during_rebase.go
index 57f2192275f4..78cb875acb94 100644
--- a/pkg/integration/tests/interactive_rebase/edit_non_todo_commit_during_rebase.go
+++ b/pkg/integration/tests/interactive_rebase/edit_non_todo_commit_during_rebase.go
@@ -29,9 +29,6 @@ var EditNonTodoCommitDuringRebase = NewIntegrationTest(NewIntegrationTestArgs{
NavigateToLine(Contains("commit 01")).
Press(keys.Universal.Edit)
- t.ExpectPopup().Alert().
- Title(Equals("Error")).
- Content(Contains("Can't perform this action during a rebase")).
- Confirm()
+ t.ExpectToast(Contains("Can't perform this action during a rebase"))
},
})
diff --git a/pkg/integration/tests/interactive_rebase/edit_the_confl_commit.go b/pkg/integration/tests/interactive_rebase/edit_the_confl_commit.go
index 96ec81c740e7..85a3df27c4a2 100644
--- a/pkg/integration/tests/interactive_rebase/edit_the_confl_commit.go
+++ b/pkg/integration/tests/interactive_rebase/edit_the_confl_commit.go
@@ -39,9 +39,6 @@ var EditTheConflCommit = NewIntegrationTest(NewIntegrationTestArgs{
NavigateToLine(Contains("<-- YOU ARE HERE --- commit three")).
Press(keys.Commits.RenameCommit)
- t.ExpectPopup().Alert().
- Title(Equals("Error")).
- Content(Contains("Changing this kind of rebase todo entry is not allowed")).
- Confirm()
+ t.ExpectToast(Contains("Changing this kind of rebase todo entry is not allowed"))
},
})
diff --git a/pkg/integration/tests/interactive_rebase/fixup_first_commit.go b/pkg/integration/tests/interactive_rebase/fixup_first_commit.go
index a45c2f050a9c..ff099d760992 100644
--- a/pkg/integration/tests/interactive_rebase/fixup_first_commit.go
+++ b/pkg/integration/tests/interactive_rebase/fixup_first_commit.go
@@ -24,10 +24,7 @@ var FixupFirstCommit = NewIntegrationTest(NewIntegrationTestArgs{
NavigateToLine(Contains("commit 01")).
Press(keys.Commits.MarkCommitAsFixup).
Tap(func() {
- t.ExpectPopup().Alert().
- Title(Equals("Error")).
- Content(Equals("There's no commit below to squash into")).
- Confirm()
+ t.ExpectToast(Equals("Disabled: There's no commit below to squash into"))
}).
Lines(
Contains("commit 02"),
diff --git a/pkg/integration/tests/interactive_rebase/quick_start.go b/pkg/integration/tests/interactive_rebase/quick_start.go
index 713e00cdb003..9e95f961e426 100644
--- a/pkg/integration/tests/interactive_rebase/quick_start.go
+++ b/pkg/integration/tests/interactive_rebase/quick_start.go
@@ -50,13 +50,9 @@ var QuickStart = NewIntegrationTest(NewIntegrationTestArgs{
Contains("initial commit"),
).
// Verify we can't quick start from main
- Press(keys.Commits.StartInteractiveRebase).
- Tap(func() {
- t.ExpectPopup().Alert().
- Title(Equals("Error")).
- Content(Contains("Cannot start interactive rebase: the HEAD commit is a merge commit or is present on the main branch, so there is no appropriate base commit to start the rebase from. You can start an interactive rebase from a specific commit by selecting the commit and pressing `e`.")).
- Confirm()
- })
+ Press(keys.Commits.StartInteractiveRebase)
+
+ t.ExpectToast(Equals("Disabled: Cannot start interactive rebase: the HEAD commit is a merge commit or is present on the main branch, so there is no appropriate base commit to start the rebase from. You can start an interactive rebase from a specific commit by selecting the commit and pressing `e`."))
t.Views().Branches().
Focus().
@@ -80,15 +76,10 @@ var QuickStart = NewIntegrationTest(NewIntegrationTestArgs{
Contains("initial commit"),
).
// Try again, verify we fail because we're already rebasing
- Press(keys.Commits.StartInteractiveRebase).
- Tap(func() {
- t.ExpectPopup().Alert().
- Title(Equals("Error")).
- Content(Contains("Can't perform this action during a rebase")).
- Confirm()
-
- t.Common().AbortRebase()
- })
+ Press(keys.Commits.StartInteractiveRebase)
+
+ t.ExpectToast(Equals("Disabled: Can't perform this action during a rebase"))
+ t.Common().AbortRebase()
// Verify if a merge commit is present on the branch we start from there
t.Views().Branches().
diff --git a/pkg/integration/tests/interactive_rebase/squash_down_first_commit.go b/pkg/integration/tests/interactive_rebase/squash_down_first_commit.go
index 0f58419f9920..65d6bfaa7c9d 100644
--- a/pkg/integration/tests/interactive_rebase/squash_down_first_commit.go
+++ b/pkg/integration/tests/interactive_rebase/squash_down_first_commit.go
@@ -24,10 +24,7 @@ var SquashDownFirstCommit = NewIntegrationTest(NewIntegrationTestArgs{
NavigateToLine(Contains("commit 01")).
Press(keys.Commits.SquashDown).
Tap(func() {
- t.ExpectPopup().Alert().
- Title(Equals("Error")).
- Content(Equals("There's no commit below to squash into")).
- Confirm()
+ t.ExpectToast(Equals("Disabled: There's no commit below to squash into"))
}).
Lines(
Contains("commit 02"),
From 84e1d15079ab688d3a7379868c8b6b71a3a6edb1 Mon Sep 17 00:00:00 2001
From: Stefan Haller
Date: Sat, 13 Jan 2024 20:02:10 +0100
Subject: [PATCH 044/280] Make DisabledReason a struct
This is a pure refactoring, no change in behavior yet. We'll add another field
to the struct in the next commit.
---
pkg/gui/context/menu_context.go | 6 +-
pkg/gui/controllers/branches_controller.go | 24 +++---
pkg/gui/controllers/files_controller.go | 10 +--
.../helpers/confirmation_helper.go | 4 +-
.../controllers/local_commits_controller.go | 74 +++++++++----------
pkg/gui/controllers/options_menu_action.go | 2 +-
pkg/gui/controllers/sync_controller.go | 6 +-
pkg/gui/keybindings.go | 6 +-
pkg/gui/types/common.go | 8 +-
pkg/gui/types/keybindings.go | 2 +-
10 files changed, 73 insertions(+), 69 deletions(-)
diff --git a/pkg/gui/context/menu_context.go b/pkg/gui/context/menu_context.go
index 1ea0e64c136e..d7c982651967 100644
--- a/pkg/gui/context/menu_context.go
+++ b/pkg/gui/context/menu_context.go
@@ -90,7 +90,7 @@ func (self *MenuViewModel) GetDisplayStrings(_ int, _ int) [][]string {
return lo.Map(menuItems, func(item *types.MenuItem, _ int) []string {
displayStrings := item.LabelColumns
- if item.DisabledReason != "" {
+ if item.DisabledReason != nil {
displayStrings[0] = style.FgDefault.SetStrikethrough().Sprint(displayStrings[0])
}
@@ -172,8 +172,8 @@ func (self *MenuContext) GetKeybindings(opts types.KeybindingsOpts) []*types.Bin
}
func (self *MenuContext) OnMenuPress(selectedItem *types.MenuItem) error {
- if selectedItem != nil && selectedItem.DisabledReason != "" {
- self.c.ErrorToast(self.c.Tr.DisabledMenuItemPrefix + selectedItem.DisabledReason)
+ if selectedItem != nil && selectedItem.DisabledReason != nil {
+ self.c.ErrorToast(self.c.Tr.DisabledMenuItemPrefix + selectedItem.DisabledReason.Text)
return nil
}
diff --git a/pkg/gui/controllers/branches_controller.go b/pkg/gui/controllers/branches_controller.go
index 22390fc8e8be..e0a38d463cae 100644
--- a/pkg/gui/controllers/branches_controller.go
+++ b/pkg/gui/controllers/branches_controller.go
@@ -264,13 +264,13 @@ func (self *BranchesController) viewUpstreamOptions(selectedBranch *models.Branc
}
if !selectedBranch.IsTrackingRemote() {
- unsetUpstreamItem.DisabledReason = self.c.Tr.UpstreamNotSetError
+ unsetUpstreamItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.UpstreamNotSetError}
}
if !selectedBranch.RemoteBranchStoredLocally() {
- viewDivergenceItem.DisabledReason = self.c.Tr.UpstreamNotSetError
- upstreamResetItem.DisabledReason = self.c.Tr.UpstreamNotSetError
- upstreamRebaseItem.DisabledReason = self.c.Tr.UpstreamNotSetError
+ viewDivergenceItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.UpstreamNotSetError}
+ upstreamResetItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.UpstreamNotSetError}
+ upstreamRebaseItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.UpstreamNotSetError}
}
options := []*types.MenuItem{
@@ -309,16 +309,16 @@ func (self *BranchesController) press(selectedBranch *models.Branch) error {
return self.c.Helpers().Refs.CheckoutRef(selectedBranch.Name, types.CheckoutRefOptions{})
}
-func (self *BranchesController) getDisabledReasonForPress() string {
+func (self *BranchesController) getDisabledReasonForPress() *types.DisabledReason {
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 &types.DisabledReason{Text: self.c.Tr.CantCheckoutBranchWhilePulling}
}
}
- return ""
+ return nil
}
func (self *BranchesController) worktreeForBranch(branch *models.Branch) (*models.Worktree, bool) {
@@ -525,7 +525,7 @@ func (self *BranchesController) delete(branch *models.Branch) error {
},
}
if checkedOutBranch.Name == branch.Name {
- localDeleteItem.DisabledReason = self.c.Tr.CantDeleteCheckOutBranch
+ localDeleteItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.CantDeleteCheckOutBranch}
}
remoteDeleteItem := &types.MenuItem{
@@ -536,7 +536,7 @@ func (self *BranchesController) delete(branch *models.Branch) error {
},
}
if !branch.IsTrackingRemote() || branch.UpstreamGone {
- remoteDeleteItem.DisabledReason = self.c.Tr.UpstreamNotSetError
+ remoteDeleteItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.UpstreamNotSetError}
}
menuTitle := utils.ResolvePlaceholderString(
@@ -562,14 +562,14 @@ func (self *BranchesController) rebase() error {
return self.c.Helpers().MergeAndRebase.RebaseOntoRef(selectedBranchName)
}
-func (self *BranchesController) getDisabledReasonForRebase() string {
+func (self *BranchesController) getDisabledReasonForRebase() *types.DisabledReason {
selectedBranchName := self.context().GetSelected().Name
checkedOutBranch := self.c.Helpers().Refs.GetCheckedOutRef().Name
if selectedBranchName == checkedOutBranch {
- return self.c.Tr.CantRebaseOntoSelf
+ return &types.DisabledReason{Text: self.c.Tr.CantRebaseOntoSelf}
}
- return ""
+ return nil
}
func (self *BranchesController) fastForward(branch *models.Branch) error {
diff --git a/pkg/gui/controllers/files_controller.go b/pkg/gui/controllers/files_controller.go
index d60773ec14da..7776da5b33cc 100644
--- a/pkg/gui/controllers/files_controller.go
+++ b/pkg/gui/controllers/files_controller.go
@@ -848,15 +848,15 @@ func (self *FilesController) openCopyMenu() error {
}
if node == nil {
- copyNameItem.DisabledReason = self.c.Tr.NoContentToCopyError
- copyPathItem.DisabledReason = self.c.Tr.NoContentToCopyError
- copyFileDiffItem.DisabledReason = self.c.Tr.NoContentToCopyError
+ copyNameItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.NoContentToCopyError}
+ copyPathItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.NoContentToCopyError}
+ copyFileDiffItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.NoContentToCopyError}
}
if node != nil && !node.GetHasStagedOrTrackedChanges() {
- copyFileDiffItem.DisabledReason = self.c.Tr.NoContentToCopyError
+ copyFileDiffItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.NoContentToCopyError}
}
if !self.anyStagedOrTrackedFile() {
- copyAllDiff.DisabledReason = self.c.Tr.NoContentToCopyError
+ copyAllDiff.DisabledReason = &types.DisabledReason{Text: self.c.Tr.NoContentToCopyError}
}
return self.c.Menu(types.CreateMenuOptions{
diff --git a/pkg/gui/controllers/helpers/confirmation_helper.go b/pkg/gui/controllers/helpers/confirmation_helper.go
index 62beaacda244..8a61a86e14e6 100644
--- a/pkg/gui/controllers/helpers/confirmation_helper.go
+++ b/pkg/gui/controllers/helpers/confirmation_helper.go
@@ -378,11 +378,11 @@ func (self *ConfirmationHelper) IsPopupPanelFocused() bool {
func (self *ConfirmationHelper) TooltipForMenuItem(menuItem *types.MenuItem) string {
tooltip := menuItem.Tooltip
- if menuItem.DisabledReason != "" {
+ if menuItem.DisabledReason != nil {
if tooltip != "" {
tooltip += "\n\n"
}
- tooltip += style.FgRed.Sprintf(self.c.Tr.DisabledMenuItemPrefix) + menuItem.DisabledReason
+ tooltip += style.FgRed.Sprintf(self.c.Tr.DisabledMenuItemPrefix) + menuItem.DisabledReason.Text
}
return tooltip
}
diff --git a/pkg/gui/controllers/local_commits_controller.go b/pkg/gui/controllers/local_commits_controller.go
index d3b92bc935ba..ea2038f71770 100644
--- a/pkg/gui/controllers/local_commits_controller.go
+++ b/pkg/gui/controllers/local_commits_controller.go
@@ -261,9 +261,9 @@ func (self *LocalCommitsController) squashDown(commit *models.Commit) error {
})
}
-func (self *LocalCommitsController) getDisabledReasonForSquashDown(commit *models.Commit) string {
+func (self *LocalCommitsController) getDisabledReasonForSquashDown(commit *models.Commit) *types.DisabledReason {
if self.context().GetSelectedLineIdx() >= len(self.c.Model().Commits)-1 {
- return self.c.Tr.CannotSquashOrFixupFirstCommit
+ return &types.DisabledReason{Text: self.c.Tr.CannotSquashOrFixupFirstCommit}
}
return self.rebaseCommandEnabled(todo.Squash, commit)
@@ -290,9 +290,9 @@ func (self *LocalCommitsController) fixup(commit *models.Commit) error {
})
}
-func (self *LocalCommitsController) getDisabledReasonForFixup(commit *models.Commit) string {
+func (self *LocalCommitsController) getDisabledReasonForFixup(commit *models.Commit) *types.DisabledReason {
if self.context().GetSelectedLineIdx() >= len(self.c.Model().Commits)-1 {
- return self.c.Tr.CannotSquashOrFixupFirstCommit
+ return &types.DisabledReason{Text: self.c.Tr.CannotSquashOrFixupFirstCommit}
}
return self.rebaseCommandEnabled(todo.Squash, commit)
@@ -528,9 +528,9 @@ func (self *LocalCommitsController) handleMidRebaseCommand(action todo.TodoComma
})
}
-func (self *LocalCommitsController) rebaseCommandEnabled(action todo.TodoCommand, commit *models.Commit) string {
+func (self *LocalCommitsController) rebaseCommandEnabled(action todo.TodoCommand, commit *models.Commit) *types.DisabledReason {
if commit.Action == models.ActionConflict {
- return self.c.Tr.ChangingThisActionIsNotAllowed
+ return &types.DisabledReason{Text: self.c.Tr.ChangingThisActionIsNotAllowed}
}
if !commit.IsTODO() {
@@ -538,11 +538,11 @@ func (self *LocalCommitsController) rebaseCommandEnabled(action todo.TodoCommand
// If we are in a rebase, the only action that is allowed for
// non-todo commits is rewording the current head commit
if !(action == todo.Reword && self.isHeadCommit()) {
- return self.c.Tr.AlreadyRebasing
+ return &types.DisabledReason{Text: self.c.Tr.AlreadyRebasing}
}
}
- return ""
+ return nil
}
// for now we do not support setting 'reword' because it requires an editor
@@ -550,14 +550,14 @@ func (self *LocalCommitsController) rebaseCommandEnabled(action todo.TodoCommand
// our input or we set a lazygit client as the EDITOR env variable and have it
// request us to edit the commit message when prompted.
if action == todo.Reword {
- return self.c.Tr.RewordNotSupported
+ return &types.DisabledReason{Text: self.c.Tr.RewordNotSupported}
}
if allowed := isChangeOfRebaseTodoAllowed(action); !allowed {
- return self.c.Tr.ChangingThisActionIsNotAllowed
+ return &types.DisabledReason{Text: self.c.Tr.ChangingThisActionIsNotAllowed}
}
- return ""
+ return nil
}
func (self *LocalCommitsController) moveDown(commit *models.Commit) error {
@@ -687,12 +687,12 @@ func (self *LocalCommitsController) amendTo(commit *models.Commit) error {
})
}
-func (self *LocalCommitsController) getDisabledReasonForAmendTo(commit *models.Commit) string {
+func (self *LocalCommitsController) getDisabledReasonForAmendTo(commit *models.Commit) *types.DisabledReason {
if !self.isHeadCommit() && self.c.Model().WorkingTreeStateAtLastCommitRefresh != enums.REBASE_MODE_NONE {
- return self.c.Tr.AlreadyRebasing
+ return &types.DisabledReason{Text: self.c.Tr.AlreadyRebasing}
}
- return ""
+ return nil
}
func (self *LocalCommitsController) amendAttribute(commit *models.Commit) error {
@@ -870,30 +870,30 @@ func (self *LocalCommitsController) squashAllAboveFixupCommits(commit *models.Co
})
}
-func (self *LocalCommitsController) getDisabledReasonForSquashAllAboveFixupCommits(commit *models.Commit) string {
+func (self *LocalCommitsController) getDisabledReasonForSquashAllAboveFixupCommits(commit *models.Commit) *types.DisabledReason {
if self.c.Model().WorkingTreeStateAtLastCommitRefresh != enums.REBASE_MODE_NONE {
- return self.c.Tr.AlreadyRebasing
+ return &types.DisabledReason{Text: self.c.Tr.AlreadyRebasing}
}
- return ""
+ return nil
}
// For getting disabled reason
-func (self *LocalCommitsController) notMidRebase() string {
+func (self *LocalCommitsController) notMidRebase() *types.DisabledReason {
if self.c.Model().WorkingTreeStateAtLastCommitRefresh != enums.REBASE_MODE_NONE {
- return self.c.Tr.AlreadyRebasing
+ return &types.DisabledReason{Text: self.c.Tr.AlreadyRebasing}
}
- return ""
+ return nil
}
// For getting disabled reason
-func (self *LocalCommitsController) canFindCommitForQuickStart() string {
+func (self *LocalCommitsController) canFindCommitForQuickStart() *types.DisabledReason {
if _, err := self.findCommitForQuickStartInteractiveRebase(); err != nil {
- return err.Error()
+ return &types.DisabledReason{Text: err.Error()}
}
- return ""
+ return nil
}
func (self *LocalCommitsController) createTag(commit *models.Commit) error {
@@ -1028,23 +1028,23 @@ func (self *LocalCommitsController) checkSelected(callback func(*models.Commit)
}
}
-func (self *LocalCommitsController) callGetDisabledReasonFuncWithSelectedCommit(callback func(*models.Commit) string) func() string {
- return func() string {
+func (self *LocalCommitsController) callGetDisabledReasonFuncWithSelectedCommit(callback func(*models.Commit) *types.DisabledReason) func() *types.DisabledReason {
+ return func() *types.DisabledReason {
commit := self.context().GetSelected()
if commit == nil {
- return self.c.Tr.NoCommitSelected
+ return &types.DisabledReason{Text: self.c.Tr.NoCommitSelected}
}
return callback(commit)
}
}
-func (self *LocalCommitsController) disabledIfNoSelectedCommit() func() string {
- return self.callGetDisabledReasonFuncWithSelectedCommit(func(*models.Commit) string { return "" })
+func (self *LocalCommitsController) disabledIfNoSelectedCommit() func() *types.DisabledReason {
+ return self.callGetDisabledReasonFuncWithSelectedCommit(func(*models.Commit) *types.DisabledReason { return nil })
}
-func (self *LocalCommitsController) getDisabledReasonForRebaseCommandWithSelectedCommit(action todo.TodoCommand) func() string {
- return self.callGetDisabledReasonFuncWithSelectedCommit(func(commit *models.Commit) string {
+func (self *LocalCommitsController) getDisabledReasonForRebaseCommandWithSelectedCommit(action todo.TodoCommand) func() *types.DisabledReason {
+ return self.callGetDisabledReasonFuncWithSelectedCommit(func(commit *models.Commit) *types.DisabledReason {
return self.rebaseCommandEnabled(action, commit)
})
}
@@ -1077,12 +1077,12 @@ func (self *LocalCommitsController) paste() error {
return self.c.Helpers().CherryPick.Paste()
}
-func (self *LocalCommitsController) getDisabledReasonForPaste() string {
+func (self *LocalCommitsController) getDisabledReasonForPaste() *types.DisabledReason {
if !self.c.Helpers().CherryPick.CanPaste() {
- return self.c.Tr.NoCopiedCommits
+ return &types.DisabledReason{Text: self.c.Tr.NoCopiedCommits}
}
- return ""
+ return nil
}
func (self *LocalCommitsController) markAsBaseCommit(commit *models.Commit) error {
@@ -1100,15 +1100,15 @@ func (self *LocalCommitsController) isHeadCommit() bool {
}
// Convenience function for composing multiple disabled reason functions
-func (self *LocalCommitsController) require(callbacks ...func() string) func() string {
- return func() string {
+func (self *LocalCommitsController) require(callbacks ...func() *types.DisabledReason) func() *types.DisabledReason {
+ return func() *types.DisabledReason {
for _, callback := range callbacks {
- if disabledReason := callback(); disabledReason != "" {
+ if disabledReason := callback(); disabledReason != nil {
return disabledReason
}
}
- return ""
+ return nil
}
}
diff --git a/pkg/gui/controllers/options_menu_action.go b/pkg/gui/controllers/options_menu_action.go
index 27a2915b8c15..1100cf876583 100644
--- a/pkg/gui/controllers/options_menu_action.go
+++ b/pkg/gui/controllers/options_menu_action.go
@@ -25,7 +25,7 @@ func (self *OptionsMenuAction) Call() error {
appendBindings := func(bindings []*types.Binding, section *types.MenuSection) {
menuItems = append(menuItems,
lo.Map(bindings, func(binding *types.Binding, _ int) *types.MenuItem {
- disabledReason := ""
+ var disabledReason *types.DisabledReason
if binding.GetDisabledReason != nil {
disabledReason = binding.GetDisabledReason()
}
diff --git a/pkg/gui/controllers/sync_controller.go b/pkg/gui/controllers/sync_controller.go
index 4e3369e0a2e6..ada4997d2241 100644
--- a/pkg/gui/controllers/sync_controller.go
+++ b/pkg/gui/controllers/sync_controller.go
@@ -59,16 +59,16 @@ func (self *SyncController) HandlePull() error {
return self.branchCheckedOut(self.pull)()
}
-func (self *SyncController) getDisabledReasonForPushOrPull() string {
+func (self *SyncController) getDisabledReasonForPushOrPull() *types.DisabledReason {
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 &types.DisabledReason{Text: self.c.Tr.CantPullOrPushSameBranchTwice}
}
}
- return ""
+ return nil
}
func (self *SyncController) branchCheckedOut(f func(*models.Branch) error) func() error {
diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go
index a2e9fafbd8dc..4a64dbde5002 100644
--- a/pkg/gui/keybindings.go
+++ b/pkg/gui/keybindings.go
@@ -411,12 +411,12 @@ func (gui *Gui) SetMouseKeybinding(binding *gocui.ViewMouseBinding) error {
}
func (gui *Gui) callKeybindingHandler(binding *types.Binding) error {
- disabledReason := ""
+ var disabledReason *types.DisabledReason
if binding.GetDisabledReason != nil {
disabledReason = binding.GetDisabledReason()
}
- if disabledReason != "" {
- gui.c.ErrorToast(gui.Tr.DisabledMenuItemPrefix + disabledReason)
+ if disabledReason != nil {
+ gui.c.ErrorToast(gui.Tr.DisabledMenuItemPrefix + disabledReason.Text)
return nil
}
return binding.Handler()
diff --git a/pkg/gui/types/common.go b/pkg/gui/types/common.go
index afa4156c81a6..79b4e153c93d 100644
--- a/pkg/gui/types/common.go
+++ b/pkg/gui/types/common.go
@@ -201,6 +201,10 @@ type MenuSection struct {
Column int // The column that this section title should be aligned with
}
+type DisabledReason struct {
+ Text string
+}
+
type MenuItem struct {
Label string
@@ -219,9 +223,9 @@ type MenuItem struct {
// The tooltip will be displayed upon highlighting the menu item
Tooltip string
- // If non-empty, show this in a tooltip, style the menu item as disabled,
+ // If non-nil, show this in a tooltip, style the menu item as disabled,
// and refuse to invoke the command
- DisabledReason string
+ DisabledReason *DisabledReason
// Can be used to group menu items into sections with headers. MenuItems
// with the same Section should be contiguous, and will automatically get a
diff --git a/pkg/gui/types/keybindings.go b/pkg/gui/types/keybindings.go
index 4c3d02c6fdc5..bb125d4e59b5 100644
--- a/pkg/gui/types/keybindings.go
+++ b/pkg/gui/types/keybindings.go
@@ -31,7 +31,7 @@ type Binding struct {
// disabled and we show the given text in an error message when trying to
// invoke it. When left nil, the command is always enabled. Note that this
// function must not do expensive calls.
- GetDisabledReason func() string
+ GetDisabledReason func() *DisabledReason
}
// A guard is a decorator which checks something before executing a handler
From 83337d9fa8ede87b42c5ac5133f5f8c1165ae329 Mon Sep 17 00:00:00 2001
From: Stefan Haller
Date: Sat, 13 Jan 2024 20:30:18 +0100
Subject: [PATCH 045/280] Allow showing Disabled errors as error panel instead
of toast
---
pkg/gui/context/menu_context.go | 4 ++++
pkg/gui/controllers/local_commits_controller.go | 2 +-
pkg/gui/keybindings.go | 4 ++++
pkg/gui/types/common.go | 6 ++++++
pkg/integration/tests/interactive_rebase/quick_start.go | 5 ++++-
5 files changed, 19 insertions(+), 2 deletions(-)
diff --git a/pkg/gui/context/menu_context.go b/pkg/gui/context/menu_context.go
index d7c982651967..b5c1a3c20bf3 100644
--- a/pkg/gui/context/menu_context.go
+++ b/pkg/gui/context/menu_context.go
@@ -173,6 +173,10 @@ func (self *MenuContext) GetKeybindings(opts types.KeybindingsOpts) []*types.Bin
func (self *MenuContext) OnMenuPress(selectedItem *types.MenuItem) error {
if selectedItem != nil && selectedItem.DisabledReason != nil {
+ if selectedItem.DisabledReason.ShowErrorInPanel {
+ return self.c.ErrorMsg(selectedItem.DisabledReason.Text)
+ }
+
self.c.ErrorToast(self.c.Tr.DisabledMenuItemPrefix + selectedItem.DisabledReason.Text)
return nil
}
diff --git a/pkg/gui/controllers/local_commits_controller.go b/pkg/gui/controllers/local_commits_controller.go
index ea2038f71770..97151f4feb69 100644
--- a/pkg/gui/controllers/local_commits_controller.go
+++ b/pkg/gui/controllers/local_commits_controller.go
@@ -890,7 +890,7 @@ func (self *LocalCommitsController) notMidRebase() *types.DisabledReason {
// For getting disabled reason
func (self *LocalCommitsController) canFindCommitForQuickStart() *types.DisabledReason {
if _, err := self.findCommitForQuickStartInteractiveRebase(); err != nil {
- return &types.DisabledReason{Text: err.Error()}
+ return &types.DisabledReason{Text: err.Error(), ShowErrorInPanel: true}
}
return nil
diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go
index 4a64dbde5002..26ce8ec91f98 100644
--- a/pkg/gui/keybindings.go
+++ b/pkg/gui/keybindings.go
@@ -416,6 +416,10 @@ func (gui *Gui) callKeybindingHandler(binding *types.Binding) error {
disabledReason = binding.GetDisabledReason()
}
if disabledReason != nil {
+ if disabledReason.ShowErrorInPanel {
+ return gui.c.ErrorMsg(disabledReason.Text)
+ }
+
gui.c.ErrorToast(gui.Tr.DisabledMenuItemPrefix + disabledReason.Text)
return nil
}
diff --git a/pkg/gui/types/common.go b/pkg/gui/types/common.go
index 79b4e153c93d..9053e43f9b1e 100644
--- a/pkg/gui/types/common.go
+++ b/pkg/gui/types/common.go
@@ -203,6 +203,12 @@ type MenuSection struct {
type DisabledReason struct {
Text string
+
+ // When trying to invoke a disabled key binding or menu item, we normally
+ // show the disabled reason as a toast; setting this to true shows it as an
+ // error panel instead. This is useful if the text is very long, or if it is
+ // important enough to show it more prominently, or both.
+ ShowErrorInPanel bool
}
type MenuItem struct {
diff --git a/pkg/integration/tests/interactive_rebase/quick_start.go b/pkg/integration/tests/interactive_rebase/quick_start.go
index 9e95f961e426..d0454d6cf749 100644
--- a/pkg/integration/tests/interactive_rebase/quick_start.go
+++ b/pkg/integration/tests/interactive_rebase/quick_start.go
@@ -52,7 +52,10 @@ var QuickStart = NewIntegrationTest(NewIntegrationTestArgs{
// Verify we can't quick start from main
Press(keys.Commits.StartInteractiveRebase)
- t.ExpectToast(Equals("Disabled: Cannot start interactive rebase: the HEAD commit is a merge commit or is present on the main branch, so there is no appropriate base commit to start the rebase from. You can start an interactive rebase from a specific commit by selecting the commit and pressing `e`."))
+ t.ExpectPopup().Alert().
+ Title(Equals("Error")).
+ Content(Equals("Cannot start interactive rebase: the HEAD commit is a merge commit or is present on the main branch, so there is no appropriate base commit to start the rebase from. You can start an interactive rebase from a specific commit by selecting the commit and pressing `e`.")).
+ Confirm()
t.Views().Branches().
Focus().
From f133224683f3f8828064e58dde718f130751e8d3 Mon Sep 17 00:00:00 2001
From: Stefan Haller
Date: Sun, 14 Jan 2024 17:31:27 +0100
Subject: [PATCH 046/280] Double the duration of error toasts
This gives users more time to read them.
---
pkg/gui/status/status_manager.go | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/pkg/gui/status/status_manager.go b/pkg/gui/status/status_manager.go
index f85036463333..eb5b01d4a393 100644
--- a/pkg/gui/status/status_manager.go
+++ b/pkg/gui/status/status_manager.go
@@ -60,7 +60,8 @@ func (self *StatusManager) AddToastStatus(message string, kind types.ToastKind)
id := self.addStatus(message, "toast", kind)
go func() {
- time.Sleep(time.Second * 2)
+ delay := lo.Ternary(kind == types.ToastKindError, time.Second*4, time.Second*2)
+ time.Sleep(delay)
self.removeStatus(id)
}()
From d4a0ca35c7e37c8006f76ba426fb54ce15337193 Mon Sep 17 00:00:00 2001
From: README-bot
Date: Mon, 15 Jan 2024 03:45:52 +0000
Subject: [PATCH 047/280] Updated README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index bcebc8fec2a4..8316bd366ef1 100644
--- a/README.md
+++ b/README.md
@@ -37,7 +37,7 @@ A simple terminal UI for git commands
-
+
## Elevator Pitch
From fc8998e377a59da74e69654c302428527d5a3efb Mon Sep 17 00:00:00 2001
From: Jesse Duffield
Date: Mon, 15 Jan 2024 20:08:11 +1100
Subject: [PATCH 048/280] Do not include keybindings from another view in
keybindings menu
Previously we included all navigation keybindings from all views in the keybindings menu, meaning
if you pressed enter on 'next page' in the commits view, you'd end up triggering the action
in the sub-commits view.
---
pkg/gui/controllers/options_menu_action.go | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/pkg/gui/controllers/options_menu_action.go b/pkg/gui/controllers/options_menu_action.go
index 1100cf876583..d46025afc5df 100644
--- a/pkg/gui/controllers/options_menu_action.go
+++ b/pkg/gui/controllers/options_menu_action.go
@@ -69,10 +69,12 @@ func (self *OptionsMenuAction) getBindings(context types.Context) ([]*types.Bind
if keybindings.LabelFromKey(binding.Key) != "" && binding.Description != "" {
if binding.ViewName == "" {
bindingsGlobal = append(bindingsGlobal, binding)
- } else if binding.Tag == "navigation" {
- bindingsNavigation = append(bindingsNavigation, binding)
} else if binding.ViewName == context.GetViewName() {
- bindingsPanel = append(bindingsPanel, binding)
+ if binding.Tag == "navigation" {
+ bindingsNavigation = append(bindingsNavigation, binding)
+ } else {
+ bindingsPanel = append(bindingsPanel, binding)
+ }
}
}
}
From 36e57d16bd9f3167c09def1879aac15e884a5752 Mon Sep 17 00:00:00 2001
From: Stefan Haller
Date: Sat, 13 Jan 2024 14:10:34 +0100
Subject: [PATCH 049/280] Don't try to shorten branch names that are already 3
characters or less
This fixes a potential crash when the available width is very small and the
branch name is one to three characters long.
---
pkg/gui/presentation/branches.go | 3 ++-
pkg/gui/presentation/branches_test.go | 27 +++++++++++++++++++++++++++
2 files changed, 29 insertions(+), 1 deletion(-)
diff --git a/pkg/gui/presentation/branches.go b/pkg/gui/presentation/branches.go
index a2bb97ed2b47..bb1f0bdc3c95 100644
--- a/pkg/gui/presentation/branches.go
+++ b/pkg/gui/presentation/branches.go
@@ -78,7 +78,8 @@ func getBranchDisplayStrings(
nameTextStyle = theme.DiffTerminalColor
}
- if len(displayName) > availableWidth {
+ // Don't bother shortening branch names that are already 3 characters or less
+ if len(displayName) > utils.Max(availableWidth, 3) {
// Never shorten the branch name to less then 3 characters
len := utils.Max(availableWidth, 4)
displayName = displayName[:len-1] + "…"
diff --git a/pkg/gui/presentation/branches_test.go b/pkg/gui/presentation/branches_test.go
index 2c8374feb7df..2414572ca526 100644
--- a/pkg/gui/presentation/branches_test.go
+++ b/pkg/gui/presentation/branches_test.go
@@ -176,6 +176,33 @@ func Test_getBranchDisplayStrings(t *testing.T) {
checkedOutByWorktree: false,
expected: []string{"1m", "branc… Pushing |"},
},
+ {
+ branch: &models.Branch{Name: "abc", Recency: "1m"},
+ itemOperation: types.ItemOperationPushing,
+ fullDescription: false,
+ viewWidth: -1,
+ useIcons: false,
+ checkedOutByWorktree: false,
+ expected: []string{"1m", "abc Pushing |"},
+ },
+ {
+ branch: &models.Branch{Name: "ab", Recency: "1m"},
+ itemOperation: types.ItemOperationPushing,
+ fullDescription: false,
+ viewWidth: -1,
+ useIcons: false,
+ checkedOutByWorktree: false,
+ expected: []string{"1m", "ab Pushing |"},
+ },
+ {
+ branch: &models.Branch{Name: "a", Recency: "1m"},
+ itemOperation: types.ItemOperationPushing,
+ fullDescription: false,
+ viewWidth: -1,
+ useIcons: false,
+ checkedOutByWorktree: false,
+ expected: []string{"1m", "a Pushing |"},
+ },
{
branch: &models.Branch{
Name: "branch_name",
From cf59b40a1b91735c4e2ece93f377c97b4eb892d7 Mon Sep 17 00:00:00 2001
From: Stefan Haller
Date: Mon, 15 Jan 2024 12:53:21 +0100
Subject: [PATCH 050/280] Fix exit code of run_integration_tests.sh when
capturing code coverage data
---
scripts/run_integration_tests.sh | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/scripts/run_integration_tests.sh b/scripts/run_integration_tests.sh
index e94d4778666d..d7c45257db69 100755
--- a/scripts/run_integration_tests.sh
+++ b/scripts/run_integration_tests.sh
@@ -18,6 +18,7 @@ if [ -n "$LAZYGIT_GOCOVERDIR" ]; then
# arg, but if we do that then the GOCOVERDIR env var (which you typically pass to the test binary) will be overwritten by the test runner. So we're passing LAZYGIT_COCOVERDIR instead
# and then internally passing that to the test binary as GOCOVERDIR.
go test -cover -coverpkg=github.com/jesseduffield/lazygit/pkg/... pkg/integration/clients/*.go -args -test.gocoverdir="/tmp/code_coverage"
+ EXITCODE=$?
# We're merging the coverage data for the sake of having fewer artefacts to upload.
# We can't merge inline so we're merging to a tmp dir then moving back to the original.
@@ -27,10 +28,9 @@ if [ -n "$LAZYGIT_GOCOVERDIR" ]; then
mv /tmp/code_coverage_merged /tmp/code_coverage
else
go test pkg/integration/clients/*.go
+ EXITCODE=$?
fi
-EXITCODE=$?
-
if test -f ~/.gitconfig.lazygit.bak; then
mv ~/.gitconfig.lazygit.bak ~/.gitconfig
fi
From 8a94663df167b5e78f70ef570b9c8ef82aa5da99 Mon Sep 17 00:00:00 2001
From: README-bot
Date: Tue, 16 Jan 2024 12:24:57 +0000
Subject: [PATCH 051/280] Updated README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 8316bd366ef1..ac45872e5a0c 100644
--- a/README.md
+++ b/README.md
@@ -37,7 +37,7 @@ A simple terminal UI for git commands
-
+
## Elevator Pitch
From e887a2eb3c0ece1d2be78b869ff936a0703a3940 Mon Sep 17 00:00:00 2001
From: Jesse Duffield
Date: Thu, 11 Jan 2024 09:39:20 +1100
Subject: [PATCH 052/280] Stop hiding debug vscode launch tasks
No idea why these were hidden in the first place
---
.vscode/launch.json | 6 ------
1 file changed, 6 deletions(-)
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 4e05edffb1a6..be436b55fc53 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -13,9 +13,6 @@
],
"hideSystemGoroutines": true,
"console": "integratedTerminal",
- "presentation": {
- "hidden": true
- }
},
{
"name": "Tail Lazygit logs",
@@ -28,9 +25,6 @@
"--use-config-file=${workspaceFolder}/.vscode/debugger_config.yml"
],
"console": "integratedTerminal",
- "presentation": {
- "hidden": true
- }
},
{
"name": "Attach to a running Lazygit",
From 24a4302c528e3a11b30855c006669de1adf9e1d4 Mon Sep 17 00:00:00 2001
From: Jesse Duffield
Date: Sun, 7 Jan 2024 19:44:19 +1100
Subject: [PATCH 053/280] Add range selection ability on list contexts
This adds range select ability in two ways:
1) Sticky: like what we already have with the staging view i.e. press v then use arrow keys
2) Non-sticky: where you just use shift+up/down to expand the range
The state machine works like this:
(no range, press 'v') -> sticky range
(no range, press arrow) -> no range
(no range, press shift+arrow) -> nonsticky range
(sticky range, press 'v') -> no range
(sticky range, press arrow) -> sticky range
(sticky range, press shift+arrow) -> nonsticky range
(nonsticky range, press 'v') -> no range
(nonsticky range, press arrow) -> no range
(nonsticky range, press shift+arrow) -> nonsticky range
---
docs/Config.md | 5 +-
docs/README.md | 3 +-
docs/Range_Select.md | 14 ++
go.mod | 6 +-
go.sum | 10 +-
pkg/config/user_config.go | 22 +--
pkg/gui/context/list_context_trait.go | 10 +-
pkg/gui/context/traits/list_cursor.go | 103 ++++++++++--
pkg/gui/context/view_trait.go | 8 +
pkg/gui/controllers/list_controller.go | 46 +++++-
.../controllers/patch_explorer_controller.go | 9 +-
pkg/gui/filetree/file_tree_view_model.go | 2 +-
pkg/gui/keybindings/keybindings.go | 122 +++++++-------
pkg/gui/types/context.go | 10 +-
pkg/i18n/chinese.go | 2 +-
pkg/i18n/dutch.go | 2 +-
pkg/i18n/english.go | 8 +-
pkg/i18n/japanese.go | 2 +-
pkg/i18n/korean.go | 2 +-
pkg/i18n/russian.go | 2 +-
pkg/i18n/traditional_chinese.go | 2 +-
.../tests/commit/stage_range_of_lines.go | 2 +-
pkg/integration/tests/demo/stage_lines.go | 2 +-
.../patch_building/specific_selection.go | 2 +-
pkg/integration/tests/staging/stage_ranges.go | 6 +-
pkg/utils/utils.go | 7 +
schema/config.json | 10 +-
.../jesseduffield/gocui/keybinding.go | 48 +++---
.../jesseduffield/gocui/tcell_driver.go | 8 +
vendor/github.com/jesseduffield/gocui/view.go | 150 ++++++++++++++----
vendor/golang.org/x/sys/unix/mkerrors.sh | 37 +++--
vendor/golang.org/x/sys/unix/zerrors_linux.go | 54 +++++++
.../x/sys/unix/zsyscall_openbsd_386.go | 2 -
.../x/sys/unix/zsyscall_openbsd_amd64.go | 2 -
.../x/sys/unix/zsyscall_openbsd_arm.go | 2 -
.../x/sys/unix/zsyscall_openbsd_arm64.go | 2 -
.../x/sys/unix/zsyscall_openbsd_mips64.go | 2 -
.../x/sys/unix/zsyscall_openbsd_ppc64.go | 2 -
.../x/sys/unix/zsyscall_openbsd_riscv64.go | 2 -
.../x/sys/windows/syscall_windows.go | 1 +
.../x/sys/windows/zsyscall_windows.go | 9 ++
vendor/modules.txt | 6 +-
42 files changed, 533 insertions(+), 213 deletions(-)
create mode 100644 docs/Range_Select.md
diff --git a/docs/Config.md b/docs/Config.md
index 8b37c6aaa654..8b8b62c11832 100644
--- a/docs/Config.md
+++ b/docs/Config.md
@@ -201,6 +201,9 @@ keybinding:
toggleWhitespaceInDiffView: ''
increaseContextInDiffView: '}'
decreaseContextInDiffView: '{'
+ toggleRangeSelect: 'v'
+ rangeSelectUp: ''
+ rangeSelectDown: ''
status:
checkForUpdate: 'u'
recentRepos: ''
@@ -263,8 +266,6 @@ keybinding:
commitFiles:
checkoutCommitFile: 'c'
main:
- toggleDragSelect: 'v'
- toggleDragSelect-alt: 'V'
toggleSelectHunk: 'a'
pickBothHunks: 'b'
submodules:
diff --git a/docs/README.md b/docs/README.md
index 604c8a07a9d3..d840637a045b 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -3,8 +3,9 @@
* [Configuration](./Config.md).
* [Custom Commands](./Custom_Command_Keybindings.md)
* [Custom Pagers](./Custom_Pagers.md)
+* [Dev docs](./dev)
* [Keybindings](./keybindings)
* [Undo/Redo](./Undoing.md)
+* [Range Select](./Range_Select.md)
* [Searching/Filtering](./Searching.md)
* [Stacked Branches](./Stacked_Branches.md)
-* [Dev docs](./dev)
diff --git a/docs/Range_Select.md b/docs/Range_Select.md
new file mode 100644
index 000000000000..e46c26897257
--- /dev/null
+++ b/docs/Range_Select.md
@@ -0,0 +1,14 @@
+# Range Select
+
+Some actions can be performed on a range of contiguous items. For example:
+* staging multiple files at once
+* squashing multiple commits at once
+* copying (for cherry-pick) multiple commits at once
+
+There are two ways to select a range of items:
+1. Sticky range select: Press 'v' to toggle range select, then expand the selection using the up/down arrow key. To reset the selection, press 'v' again.
+2. Non-sticky range select: Press shift+up or shift+down to expand the selection. To reset the selection, press up/down without shift.
+
+The sticky option will be more familiar to vim users, and the second option will feel more natural to users who aren't used to doing things in a modal way.
+
+In order to perform an action on a range of items, simply press the normal key for that action. If the action only works on individual items, it will raise an error. This is a new feature and the plan is to incrementally support range select for more and more actions. If there is an action you would like to support range select which currently does not, please raise an issue in the repo.
diff --git a/go.mod b/go.mod
index 0d709c3701cd..182b420885b9 100644
--- a/go.mod
+++ b/go.mod
@@ -16,7 +16,7 @@ require (
github.com/integrii/flaggy v1.4.0
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d
- github.com/jesseduffield/gocui v0.3.1-0.20240103192639-2874168c14db
+ github.com/jesseduffield/gocui v0.3.1-0.20240118234343-2d41754af383
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e
@@ -74,8 +74,8 @@ require (
github.com/xanzy/ssh-agent v0.2.1 // indirect
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
golang.org/x/net v0.7.0 // indirect
- golang.org/x/sys v0.15.0 // indirect
- golang.org/x/term v0.15.0 // indirect
+ golang.org/x/sys v0.16.0 // indirect
+ golang.org/x/term v0.16.0 // indirect
golang.org/x/text v0.14.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
)
diff --git a/go.sum b/go.sum
index 1762cf6058a2..46d48d48e503 100644
--- a/go.sum
+++ b/go.sum
@@ -187,8 +187,8 @@ github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 h1:EQP2Tv8T
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68/go.mod h1:+LLj9/WUPAP8LqCchs7P+7X0R98HiFujVFANdNaxhGk=
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d h1:bO+OmbreIv91rCe8NmscRwhFSqkDJtzWCPV4Y+SQuXE=
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o=
-github.com/jesseduffield/gocui v0.3.1-0.20240103192639-2874168c14db h1:ihJdYk85/XQLGiG3b6m8P2z+RUohRMtPmX74YR9IT8s=
-github.com/jesseduffield/gocui v0.3.1-0.20240103192639-2874168c14db/go.mod h1:9zkyjnUmdL3+sUknJrQy/3HweUu8mVln/3J2wRF/l8M=
+github.com/jesseduffield/gocui v0.3.1-0.20240118234343-2d41754af383 h1:twcgVo+K7UTXwrsNtlCvTi8AyCp7CuBX//+j4wWkivQ=
+github.com/jesseduffield/gocui v0.3.1-0.20240118234343-2d41754af383/go.mod h1:9zkyjnUmdL3+sUknJrQy/3HweUu8mVln/3J2wRF/l8M=
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 h1:jmpr7KpX2+2GRiE91zTgfq49QvgiqB0nbmlwZ8UnOx0=
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10/go.mod h1:aA97kHeNA+sj2Hbki0pvLslmE4CbDyhBeSSTUUnOuVo=
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 h1:CDuQmfOjAtb1Gms6a1p5L2P8RhbLUq5t8aL7PiQd2uY=
@@ -469,13 +469,15 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
+golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
-golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
+golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
+golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
diff --git a/pkg/config/user_config.go b/pkg/config/user_config.go
index 602dc54bf6b0..d7bea4f03f57 100644
--- a/pkg/config/user_config.go
+++ b/pkg/config/user_config.go
@@ -304,6 +304,9 @@ type KeybindingUniversalConfig struct {
ScrollRight string `yaml:"scrollRight"`
GotoTop string `yaml:"gotoTop"`
GotoBottom string `yaml:"gotoBottom"`
+ ToggleRangeSelect string `yaml:"toggleRangeSelect"`
+ RangeSelectDown string `yaml:"rangeSelectDown"`
+ RangeSelectUp string `yaml:"rangeSelectUp"`
PrevBlock string `yaml:"prevBlock"`
NextBlock string `yaml:"nextBlock"`
PrevBlockAlt string `yaml:"prevBlock-alt"`
@@ -441,11 +444,9 @@ type KeybindingCommitFilesConfig struct {
}
type KeybindingMainConfig struct {
- ToggleDragSelect string `yaml:"toggleDragSelect"`
- ToggleDragSelectAlt string `yaml:"toggleDragSelect-alt"`
- ToggleSelectHunk string `yaml:"toggleSelectHunk"`
- PickBothHunks string `yaml:"pickBothHunks"`
- EditSelectHunk string `yaml:"editSelectHunk"`
+ ToggleSelectHunk string `yaml:"toggleSelectHunk"`
+ PickBothHunks string `yaml:"pickBothHunks"`
+ EditSelectHunk string `yaml:"editSelectHunk"`
}
type KeybindingSubmodulesConfig struct {
@@ -704,6 +705,9 @@ func GetDefaultConfig() *UserConfig {
ScrollRight: "L",
GotoTop: "<",
GotoBottom: ">",
+ ToggleRangeSelect: "v",
+ RangeSelectDown: "",
+ RangeSelectUp: "",
PrevBlock: "",
NextBlock: "",
PrevBlockAlt: "h",
@@ -833,11 +837,9 @@ func GetDefaultConfig() *UserConfig {
CheckoutCommitFile: "c",
},
Main: KeybindingMainConfig{
- ToggleDragSelect: "v",
- ToggleDragSelectAlt: "V",
- ToggleSelectHunk: "a",
- PickBothHunks: "b",
- EditSelectHunk: "E",
+ ToggleSelectHunk: "a",
+ PickBothHunks: "b",
+ EditSelectHunk: "E",
},
Submodules: KeybindingSubmodulesConfig{
Init: "i",
diff --git a/pkg/gui/context/list_context_trait.go b/pkg/gui/context/list_context_trait.go
index ca3b3254f326..c4f0e2549bc4 100644
--- a/pkg/gui/context/list_context_trait.go
+++ b/pkg/gui/context/list_context_trait.go
@@ -32,6 +32,14 @@ func (self *ListContextTrait) FocusLine() {
self.GetViewTrait().FocusPoint(
self.ModelIndexToViewIndex(self.list.GetSelectedLineIdx()))
+ selectRangeIndex, isSelectingRange := self.list.GetRangeStartIdx()
+ if isSelectingRange {
+ selectRangeIndex = self.ModelIndexToViewIndex(selectRangeIndex)
+ self.GetViewTrait().SetRangeSelectStart(selectRangeIndex)
+ } else {
+ self.GetViewTrait().CancelRangeSelect()
+ }
+
// If FocusPoint() caused the view to scroll (because the selected line
// was out of view before), we need to rerender the view port again.
// This can happen when pressing , or . to scroll by pages, or < or > to
@@ -84,7 +92,7 @@ func (self *ListContextTrait) HandleFocusLost(opts types.OnFocusLostOpts) error
// OnFocus assumes that the content of the context has already been rendered to the view. OnRender is the function which actually renders the content to the view
func (self *ListContextTrait) HandleRender() error {
- self.list.RefreshSelectedIdx()
+ self.list.ClampSelection()
content := self.renderLines(-1, -1)
self.GetViewTrait().SetContent(content)
self.c.Render()
diff --git a/pkg/gui/context/traits/list_cursor.go b/pkg/gui/context/traits/list_cursor.go
index 9e86d51396a5..647f2a36b4f2 100644
--- a/pkg/gui/context/traits/list_cursor.go
+++ b/pkg/gui/context/traits/list_cursor.go
@@ -9,13 +9,34 @@ type HasLength interface {
Len() int
}
+type RangeSelectMode int
+
+const (
+ // None means we are not selecting a range
+ RangeSelectModeNone RangeSelectMode = iota
+ // Sticky range select is started by pressing 'v', then the range is expanded
+ // when you move up or down. It is cancelled by pressing 'v' again.
+ RangeSelectModeSticky
+ // Nonsticky range select is started by pressing shift+arrow and cancelled
+ // when pressing up/down without shift, or by pressing 'v'
+ RangeSelectModeNonSticky
+)
+
type ListCursor struct {
- selectedIdx int
- list HasLength
+ selectedIdx int
+ rangeSelectMode RangeSelectMode
+ // value is ignored when rangeSelectMode is RangeSelectModeNone
+ rangeStartIdx int
+ list HasLength
}
func NewListCursor(list HasLength) *ListCursor {
- return &ListCursor{selectedIdx: 0, list: list}
+ return &ListCursor{
+ selectedIdx: 0,
+ rangeStartIdx: 0,
+ rangeSelectMode: RangeSelectModeNone,
+ list: list,
+ }
}
var _ types.IListCursor = (*ListCursor)(nil)
@@ -25,24 +46,86 @@ func (self *ListCursor) GetSelectedLineIdx() int {
}
func (self *ListCursor) SetSelectedLineIdx(value int) {
+ self.selectedIdx = self.clampValue(value)
+}
+
+func (self *ListCursor) clampValue(value int) int {
clampedValue := -1
if self.list.Len() > 0 {
clampedValue = utils.Clamp(value, 0, self.list.Len()-1)
}
- self.selectedIdx = clampedValue
+ return clampedValue
+}
+
+// Moves the cursor up or down by the given amount.
+// If we are in non-sticky range select mode, this will cancel the range select
+func (self *ListCursor) MoveSelectedLine(change int) {
+ if self.rangeSelectMode == RangeSelectModeNonSticky {
+ self.CancelRangeSelect()
+ }
+
+ self.SetSelectedLineIdx(self.selectedIdx + change)
}
-// moves the cursor up or down by the given amount
-func (self *ListCursor) MoveSelectedLine(delta int) {
- self.SetSelectedLineIdx(self.selectedIdx + delta)
+// Moves the cursor up or down by the given amount, and also moves the range start
+// index by the same amount
+func (self *ListCursor) MoveSelection(delta int) {
+ self.selectedIdx = self.clampValue(self.selectedIdx + delta)
+ if self.IsSelectingRange() {
+ self.rangeStartIdx = self.clampValue(self.rangeStartIdx + delta)
+ }
}
-// to be called when the model might have shrunk so that our selection is not not out of bounds
-func (self *ListCursor) RefreshSelectedIdx() {
- self.SetSelectedLineIdx(self.selectedIdx)
+// To be called when the model might have shrunk so that our selection is not out of bounds
+func (self *ListCursor) ClampSelection() {
+ self.selectedIdx = self.clampValue(self.selectedIdx)
+ self.rangeStartIdx = self.clampValue(self.rangeStartIdx)
}
func (self *ListCursor) Len() int {
return self.list.Len()
}
+
+func (self *ListCursor) GetRangeStartIdx() (int, bool) {
+ if self.IsSelectingRange() {
+ return self.rangeStartIdx, true
+ }
+
+ return 0, false
+}
+
+func (self *ListCursor) CancelRangeSelect() {
+ self.rangeSelectMode = RangeSelectModeNone
+}
+
+func (self *ListCursor) IsSelectingRange() bool {
+ return self.rangeSelectMode != RangeSelectModeNone
+}
+
+func (self *ListCursor) GetSelectionRange() (int, int) {
+ if self.IsSelectingRange() {
+ return utils.MinMax(self.selectedIdx, self.rangeStartIdx)
+ }
+
+ return self.selectedIdx, self.selectedIdx
+}
+
+func (self *ListCursor) ToggleStickyRange() {
+ if self.IsSelectingRange() {
+ self.CancelRangeSelect()
+ } else {
+ self.rangeStartIdx = self.selectedIdx
+ self.rangeSelectMode = RangeSelectModeSticky
+ }
+}
+
+func (self *ListCursor) ExpandNonStickyRange(change int) {
+ if !self.IsSelectingRange() {
+ self.rangeStartIdx = self.selectedIdx
+ }
+
+ self.rangeSelectMode = RangeSelectModeNonSticky
+
+ self.SetSelectedLineIdx(self.selectedIdx + change)
+}
diff --git a/pkg/gui/context/view_trait.go b/pkg/gui/context/view_trait.go
index bf8a49e43584..1179a8b1486d 100644
--- a/pkg/gui/context/view_trait.go
+++ b/pkg/gui/context/view_trait.go
@@ -21,6 +21,14 @@ func (self *ViewTrait) FocusPoint(yIdx int) {
self.view.FocusPoint(self.view.OriginX(), yIdx)
}
+func (self *ViewTrait) SetRangeSelectStart(yIdx int) {
+ self.view.SetRangeSelectStart(yIdx)
+}
+
+func (self *ViewTrait) CancelRangeSelect() {
+ self.view.CancelRangeSelect()
+}
+
func (self *ViewTrait) SetViewPortContent(content string) {
_, y := self.view.Origin()
self.view.OverwriteLines(y, content)
diff --git a/pkg/gui/controllers/list_controller.go b/pkg/gui/controllers/list_controller.go
index 025561993d0d..1f3c743bcc6f 100644
--- a/pkg/gui/controllers/list_controller.go
+++ b/pkg/gui/controllers/list_controller.go
@@ -71,9 +71,25 @@ func (self *ListController) scrollHorizontal(scrollFunc func()) error {
}
func (self *ListController) handleLineChange(change int) error {
- before := self.context.GetList().GetSelectedLineIdx()
- self.context.GetList().MoveSelectedLine(change)
- after := self.context.GetList().GetSelectedLineIdx()
+ return self.handleLineChangeAux(
+ self.context.GetList().MoveSelectedLine, change,
+ )
+}
+
+func (self *ListController) HandleRangeSelectChange(change int) error {
+ return self.handleLineChangeAux(
+ self.context.GetList().ExpandNonStickyRange, change,
+ )
+}
+
+func (self *ListController) handleLineChangeAux(f func(int), change int) error {
+ list := self.context.GetList()
+
+ rangeBefore := list.IsSelectingRange()
+ before := list.GetSelectedLineIdx()
+ f(change)
+ rangeAfter := list.IsSelectingRange()
+ after := list.GetSelectedLineIdx()
if err := self.pushContextIfNotFocused(); err != nil {
return err
@@ -81,7 +97,8 @@ func (self *ListController) handleLineChange(change int) error {
// doing this check so that if we're holding the up key at the start of the list
// we're not constantly re-rendering the main view.
- if before != after {
+ cursorMoved := before != after
+ if cursorMoved {
if change == -1 {
checkScrollUp(self.context.GetViewTrait(), self.c.UserConfig,
self.context.ModelIndexToViewIndex(before), self.context.ModelIndexToViewIndex(after))
@@ -89,7 +106,9 @@ func (self *ListController) handleLineChange(change int) error {
checkScrollDown(self.context.GetViewTrait(), self.c.UserConfig,
self.context.ModelIndexToViewIndex(before), self.context.ModelIndexToViewIndex(after))
}
+ }
+ if cursorMoved || rangeBefore != rangeAfter {
return self.context.HandleFocus(types.OnFocusOpts{})
}
@@ -112,6 +131,22 @@ func (self *ListController) HandleGotoBottom() error {
return self.handleLineChange(self.context.GetList().Len())
}
+func (self *ListController) HandleToggleRangeSelect() error {
+ list := self.context.GetList()
+
+ list.ToggleStickyRange()
+
+ return self.context.HandleFocus(types.OnFocusOpts{})
+}
+
+func (self *ListController) HandleRangeSelectDown() error {
+ return self.HandleRangeSelectChange(1)
+}
+
+func (self *ListController) HandleRangeSelectUp() error {
+ return self.HandleRangeSelectChange(-1)
+}
+
func (self *ListController) HandleClick(opts gocui.ViewMouseBindingOpts) error {
prevSelectedLineIdx := self.context.GetList().GetSelectedLineIdx()
newSelectedLineIdx := self.context.ViewIndexToModelIndex(opts.Y)
@@ -159,6 +194,9 @@ func (self *ListController) GetKeybindings(opts types.KeybindingsOpts) []*types.
{Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.ScrollLeft), Handler: self.HandleScrollLeft},
{Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.ScrollRight), Handler: self.HandleScrollRight},
{Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.GotoBottom), Handler: self.HandleGotoBottom, Description: self.c.Tr.GotoBottom},
+ {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.ToggleRangeSelect), Handler: self.HandleToggleRangeSelect, Description: self.c.Tr.ToggleRangeSelect},
+ {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.RangeSelectDown), Handler: self.HandleRangeSelectDown, Description: self.c.Tr.RangeSelectDown},
+ {Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.RangeSelectUp), Handler: self.HandleRangeSelectUp, Description: self.c.Tr.RangeSelectUp},
}
}
diff --git a/pkg/gui/controllers/patch_explorer_controller.go b/pkg/gui/controllers/patch_explorer_controller.go
index 6f193cf2d2c7..2083c2943ffa 100644
--- a/pkg/gui/controllers/patch_explorer_controller.go
+++ b/pkg/gui/controllers/patch_explorer_controller.go
@@ -75,14 +75,9 @@ func (self *PatchExplorerController) GetKeybindings(opts types.KeybindingsOpts)
Handler: self.withRenderAndFocus(self.HandleNextHunk),
},
{
- Key: opts.GetKey(opts.Config.Main.ToggleDragSelect),
+ Key: opts.GetKey(opts.Config.Universal.ToggleRangeSelect),
Handler: self.withRenderAndFocus(self.HandleToggleSelectRange),
- Description: self.c.Tr.ToggleDragSelect,
- },
- {
- Key: opts.GetKey(opts.Config.Main.ToggleDragSelectAlt),
- Handler: self.withRenderAndFocus(self.HandleToggleSelectRange),
- Description: self.c.Tr.ToggleDragSelect,
+ Description: self.c.Tr.ToggleRangeSelect,
},
{
Key: opts.GetKey(opts.Config.Main.ToggleSelectHunk),
diff --git a/pkg/gui/filetree/file_tree_view_model.go b/pkg/gui/filetree/file_tree_view_model.go
index 547b62b91be5..f19f74fbc2df 100644
--- a/pkg/gui/filetree/file_tree_view_model.go
+++ b/pkg/gui/filetree/file_tree_view_model.go
@@ -85,7 +85,7 @@ func (self *FileTreeViewModel) SetTree() {
}
}
- self.RefreshSelectedIdx()
+ self.ClampSelection()
}
// Let's try to find our file again and move the cursor to that.
diff --git a/pkg/gui/keybindings/keybindings.go b/pkg/gui/keybindings/keybindings.go
index fd8c694cfd4d..da917b82e0e0 100644
--- a/pkg/gui/keybindings/keybindings.go
+++ b/pkg/gui/keybindings/keybindings.go
@@ -13,66 +13,68 @@ import (
)
var labelByKey = map[gocui.Key]string{
- gocui.KeyF1: "",
- gocui.KeyF2: "",
- gocui.KeyF3: "",
- gocui.KeyF4: "",
- gocui.KeyF5: "",
- gocui.KeyF6: "",
- gocui.KeyF7: "",
- gocui.KeyF8: "",
- gocui.KeyF9: "",
- gocui.KeyF10: "",
- gocui.KeyF11: "",
- gocui.KeyF12: "",
- gocui.KeyInsert: "",
- gocui.KeyDelete: "",
- gocui.KeyHome: "",
- gocui.KeyEnd: "",
- gocui.KeyPgup: "",
- gocui.KeyPgdn: "",
- gocui.KeyArrowUp: "",
- gocui.KeyArrowDown: "",
- gocui.KeyArrowLeft: "",
- gocui.KeyArrowRight: "",
- gocui.KeyTab: "", //
- gocui.KeyBacktab: "",
- gocui.KeyEnter: "", //
- gocui.KeyAltEnter: "",
- gocui.KeyEsc: "", // ,
- gocui.KeyBackspace: "", //
- gocui.KeyCtrlSpace: "", // ,
- gocui.KeyCtrlSlash: "", //
- gocui.KeySpace: "",
- gocui.KeyCtrlA: "",
- gocui.KeyCtrlB: "",
- gocui.KeyCtrlC: "",
- gocui.KeyCtrlD: "",
- gocui.KeyCtrlE: "",
- gocui.KeyCtrlF: "",
- gocui.KeyCtrlG: "",
- gocui.KeyCtrlJ: "",
- gocui.KeyCtrlK: "",
- gocui.KeyCtrlL: "",
- gocui.KeyCtrlN: "",
- gocui.KeyCtrlO: "",
- gocui.KeyCtrlP: "",
- gocui.KeyCtrlQ: "",
- gocui.KeyCtrlR: "",
- gocui.KeyCtrlS: "",
- gocui.KeyCtrlT: "",
- gocui.KeyCtrlU: "",
- gocui.KeyCtrlV: "",
- gocui.KeyCtrlW: "",
- gocui.KeyCtrlX: "",
- gocui.KeyCtrlY: "",
- gocui.KeyCtrlZ: "",
- gocui.KeyCtrl4: "", //
- gocui.KeyCtrl5: "", //
- gocui.KeyCtrl6: "",
- gocui.KeyCtrl8: "",
- gocui.MouseWheelUp: "mouse wheel up",
- gocui.MouseWheelDown: "mouse wheel down",
+ gocui.KeyF1: "",
+ gocui.KeyF2: "",
+ gocui.KeyF3: "