mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-28 10:16:22 -04:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a71b5f837 |
+6
-10
@@ -3,7 +3,7 @@
|
||||
Default path for the global config file:
|
||||
|
||||
- Linux: `~/.config/lazygit/config.yml`
|
||||
- MacOS: `~/.config/lazygit/config.yml` (previously `~/Library/Application\ Support/lazygit/config.yml`)
|
||||
- MacOS: `~/Library/Application\ Support/lazygit/config.yml`
|
||||
- Windows: `%LOCALAPPDATA%\lazygit\config.yml` (default location, but it will also be found in `%APPDATA%\lazygit\config.yml`
|
||||
|
||||
For old installations (slightly embarrassing: I didn't realise at the time that you didn't need to supply a vendor name to the path so I just used my name):
|
||||
@@ -12,6 +12,10 @@ For old installations (slightly embarrassing: I didn't realise at the time that
|
||||
- MacOS: `~/Library/Application\ Support/jesseduffield/lazygit/config.yml`
|
||||
- Windows: `%APPDATA%\jesseduffield\lazygit\config.yml`
|
||||
|
||||
If you want to change the config directory:
|
||||
|
||||
- MacOS: `export XDG_CONFIG_HOME="$HOME/.config"`
|
||||
|
||||
In addition to the global config file you can create repo-specific config files in `<repo>/.git/lazygit.yml`. Settings in these files override settings in the global config file. In addition, files called `.lazygit.yml` in any of the parent directories of a repo will also be loaded; this can be useful if you have settings that you want to apply to a group of repositories.
|
||||
|
||||
JSON schema is available for `config.yml` so that IntelliSense in Visual Studio Code (completion and error checking) is automatically enabled when the [YAML Red Hat][yaml] extension is installed. However, note that automatic schema detection only works if your config file is in one of the standard paths mentioned above. If you override the path to the file, you can still make IntelliSense work by adding
|
||||
@@ -83,11 +87,6 @@ gui:
|
||||
# - 'top': split the window vertically (side panel on top, main view below)
|
||||
enlargedSideViewLocation: left
|
||||
|
||||
# If true, wrap lines in the staging view to the width of the view. This
|
||||
# makes it much easier to work with diffs that have long lines, e.g.
|
||||
# paragraphs of markdown text.
|
||||
wrapLinesInStagingView: true
|
||||
|
||||
# One of 'auto' (default) | 'en' | 'zh-CN' | 'zh-TW' | 'pl' | 'nl' | 'ja' | 'ko' | 'ru'
|
||||
language: auto
|
||||
|
||||
@@ -165,9 +164,6 @@ gui:
|
||||
# This can be toggled from within Lazygit with the '~' key, but that will not change the default.
|
||||
showFileTree: true
|
||||
|
||||
# If true, show the number of lines changed per file in the Files view
|
||||
showNumstatInFilesView: false
|
||||
|
||||
# If true, show a random tip in the command log when Lazygit starts
|
||||
showRandomTip: true
|
||||
|
||||
@@ -328,7 +324,7 @@ git:
|
||||
branchLogCmd: git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium {{branchName}} --
|
||||
|
||||
# Command used to display git log of all branches in the main window.
|
||||
# Deprecated: Use `allBranchesLogCmds` instead.
|
||||
# Deprecated: User `allBranchesLogCmds` instead.
|
||||
allBranchesLogCmd: git log --graph --all --color=always --abbrev-commit --decorate --date=relative --pretty=medium
|
||||
|
||||
# If true, do not spawn a separate process when using GPG
|
||||
|
||||
@@ -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.20241223111608-9967d0e928a0
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20240928100326-393cf89a5d3f
|
||||
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
|
||||
@@ -38,7 +38,7 @@ require (
|
||||
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
|
||||
golang.org/x/sync v0.10.0
|
||||
golang.org/x/sync v0.8.0
|
||||
gopkg.in/ozeidan/fuzzy-patricia.v3 v3.0.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
@@ -75,8 +75,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.28.0 // indirect
|
||||
golang.org/x/term v0.27.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
golang.org/x/sys v0.25.0 // indirect
|
||||
golang.org/x/term v0.24.0 // indirect
|
||||
golang.org/x/text v0.18.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
)
|
||||
|
||||
@@ -188,8 +188,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.20241223111608-9967d0e928a0 h1:R29+E15wHqTDBfZxmzCLu0x34j5ljsXWT/DhR+2YiOU=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20241223111608-9967d0e928a0/go.mod h1:XtEbqCbn45keRXEu+OMZkjN5gw6AEob59afsgHjokZ8=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20240928100326-393cf89a5d3f h1:ZzsAUDwPFLPITKLcJpMSqt/3rERdI8YRZKr2l0plrls=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20240928100326-393cf89a5d3f/go.mod h1:XtEbqCbn45keRXEu+OMZkjN5gw6AEob59afsgHjokZ8=
|
||||
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=
|
||||
@@ -424,8 +424,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
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/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
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=
|
||||
@@ -475,14 +475,14 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
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.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.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.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
|
||||
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
|
||||
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=
|
||||
@@ -493,8 +493,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
"github.com/stefanhaller/git-todo-parser/todo"
|
||||
)
|
||||
|
||||
// Sometimes lazygit will be invoked in daemon mode from a parent lazygit process.
|
||||
@@ -38,7 +39,6 @@ const (
|
||||
DaemonKindMoveTodosDown
|
||||
DaemonKindInsertBreak
|
||||
DaemonKindChangeTodoActions
|
||||
DaemonKindDropMergeCommit
|
||||
DaemonKindMoveFixupCommitDown
|
||||
DaemonKindWriteRebaseTodo
|
||||
)
|
||||
@@ -58,7 +58,6 @@ func getInstruction() Instruction {
|
||||
DaemonKindRemoveUpdateRefsForCopiedBranch: deserializeInstruction[*RemoveUpdateRefsForCopiedBranchInstruction],
|
||||
DaemonKindCherryPick: deserializeInstruction[*CherryPickCommitsInstruction],
|
||||
DaemonKindChangeTodoActions: deserializeInstruction[*ChangeTodoActionsInstruction],
|
||||
DaemonKindDropMergeCommit: deserializeInstruction[*DropMergeCommitInstruction],
|
||||
DaemonKindMoveFixupCommitDown: deserializeInstruction[*MoveFixupCommitDownInstruction],
|
||||
DaemonKindMoveTodosUp: deserializeInstruction[*MoveTodosUpInstruction],
|
||||
DaemonKindMoveTodosDown: deserializeInstruction[*MoveTodosDownInstruction],
|
||||
@@ -236,6 +235,7 @@ func (self *ChangeTodoActionsInstruction) run(common *common.Common) error {
|
||||
changes := lo.Map(self.Changes, func(c ChangeTodoAction, _ int) utils.TodoChange {
|
||||
return utils.TodoChange{
|
||||
Hash: c.Hash,
|
||||
OldAction: todo.Pick,
|
||||
NewAction: c.NewAction,
|
||||
}
|
||||
})
|
||||
@@ -244,30 +244,6 @@ func (self *ChangeTodoActionsInstruction) run(common *common.Common) error {
|
||||
})
|
||||
}
|
||||
|
||||
type DropMergeCommitInstruction struct {
|
||||
Hash string
|
||||
}
|
||||
|
||||
func NewDropMergeCommitInstruction(hash string) Instruction {
|
||||
return &DropMergeCommitInstruction{
|
||||
Hash: hash,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *DropMergeCommitInstruction) Kind() DaemonKind {
|
||||
return DaemonKindDropMergeCommit
|
||||
}
|
||||
|
||||
func (self *DropMergeCommitInstruction) SerializedInstructions() string {
|
||||
return serializeInstruction(self)
|
||||
}
|
||||
|
||||
func (self *DropMergeCommitInstruction) run(common *common.Common) error {
|
||||
return handleInteractiveRebase(common, func(path string) error {
|
||||
return utils.DropMergeCommit(path, self.Hash, getCommentChar())
|
||||
})
|
||||
}
|
||||
|
||||
// Takes the hash of some commit, and the hash of a fixup commit that was created
|
||||
// at the end of the branch, then moves the fixup commit down to right after the
|
||||
// original commit, changing its type to "fixup" (only if ChangeToFixup is true)
|
||||
@@ -320,12 +296,13 @@ func (self *MoveTodosUpInstruction) SerializedInstructions() string {
|
||||
func (self *MoveTodosUpInstruction) run(common *common.Common) error {
|
||||
todosToMove := lo.Map(self.Hashes, func(hash string, _ int) utils.Todo {
|
||||
return utils.Todo{
|
||||
Hash: hash,
|
||||
Hash: hash,
|
||||
Action: todo.Pick,
|
||||
}
|
||||
})
|
||||
|
||||
return handleInteractiveRebase(common, func(path string) error {
|
||||
return utils.MoveTodosUp(path, todosToMove, false, getCommentChar())
|
||||
return utils.MoveTodosUp(path, todosToMove, getCommentChar())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -350,12 +327,13 @@ func (self *MoveTodosDownInstruction) SerializedInstructions() string {
|
||||
func (self *MoveTodosDownInstruction) run(common *common.Common) error {
|
||||
todosToMove := lo.Map(self.Hashes, func(hash string, _ int) utils.Todo {
|
||||
return utils.Todo{
|
||||
Hash: hash,
|
||||
Hash: hash,
|
||||
Action: todo.Pick,
|
||||
}
|
||||
})
|
||||
|
||||
return handleInteractiveRebase(common, func(path string) error {
|
||||
return utils.MoveTodosDown(path, todosToMove, false, getCommentChar())
|
||||
return utils.MoveTodosDown(path, todosToMove, getCommentChar())
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -109,10 +109,10 @@ func (self *BranchCommands) CurrentBranchName() (string, error) {
|
||||
}
|
||||
|
||||
// LocalDelete delete branch locally
|
||||
func (self *BranchCommands) LocalDelete(branches []string, force bool) error {
|
||||
func (self *BranchCommands) LocalDelete(branch string, force bool) error {
|
||||
cmdArgs := NewGitCmd("branch").
|
||||
ArgIfElse(force, "-D", "-d").
|
||||
Arg(branches...).
|
||||
Arg(branch).
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).Run()
|
||||
|
||||
@@ -62,57 +62,36 @@ func TestBranchNewBranch(t *testing.T) {
|
||||
|
||||
func TestBranchDeleteBranch(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
branchNames []string
|
||||
force bool
|
||||
runner *oscommands.FakeCmdObjRunner
|
||||
test func(error)
|
||||
testName string
|
||||
force bool
|
||||
runner *oscommands.FakeCmdObjRunner
|
||||
test func(error)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
"Delete a branch",
|
||||
[]string{"test"},
|
||||
false,
|
||||
oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"branch", "-d", "test"}, "", nil),
|
||||
func(err error) {
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
"Delete multiple branches",
|
||||
[]string{"test1", "test2", "test3"},
|
||||
false,
|
||||
oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"branch", "-d", "test1", "test2", "test3"}, "", nil),
|
||||
func(err error) {
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
"Force delete a branch",
|
||||
[]string{"test"},
|
||||
true,
|
||||
oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"branch", "-D", "test"}, "", nil),
|
||||
func(err error) {
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
"Force delete multiple branches",
|
||||
[]string{"test1", "test2", "test3"},
|
||||
true,
|
||||
oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"branch", "-D", "test1", "test2", "test3"}, "", nil),
|
||||
func(err error) {
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildBranchCommands(commonDeps{runner: s.runner})
|
||||
|
||||
s.test(instance.LocalDelete(s.branchNames, s.force))
|
||||
s.test(instance.LocalDelete("test", s.force))
|
||||
s.runner.CheckForMissingCalls()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ func NewDiffCommands(gitCommon *GitCommon) *DiffCommands {
|
||||
func (self *DiffCommands) DiffCmdObj(diffArgs []string) oscommands.ICmdObj {
|
||||
extDiffCmd := self.UserConfig().Git.Paging.ExternalDiffCommand
|
||||
useExtDiff := extDiffCmd != ""
|
||||
ignoreWhitespace := self.AppState.IgnoreWhitespaceInDiffView
|
||||
|
||||
return self.cmd.New(
|
||||
NewGitCmd("diff").
|
||||
@@ -28,8 +27,6 @@ func (self *DiffCommands) DiffCmdObj(diffArgs []string) oscommands.ICmdObj {
|
||||
ArgIfElse(useExtDiff, "--ext-diff", "--no-ext-diff").
|
||||
Arg("--submodule").
|
||||
Arg(fmt.Sprintf("--color=%s", self.UserConfig().Git.Paging.ColorArg)).
|
||||
ArgIf(ignoreWhitespace, "--ignore-all-space").
|
||||
Arg(fmt.Sprintf("--unified=%d", self.AppState.DiffContextSize)).
|
||||
Arg(diffArgs...).
|
||||
Dir(self.repoPaths.worktreePath).
|
||||
ToArgv(),
|
||||
|
||||
@@ -3,7 +3,6 @@ package git_commands
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
@@ -49,14 +48,6 @@ func (self *FileLoader) GetStatusFiles(opts GetStatusFileOptions) []*models.File
|
||||
}
|
||||
files := []*models.File{}
|
||||
|
||||
fileDiffs := map[string]FileDiff{}
|
||||
if self.GitCommon.Common.UserConfig().Gui.ShowNumstatInFilesView {
|
||||
fileDiffs, err = self.getFileDiffs()
|
||||
if err != nil {
|
||||
self.Log.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, status := range statuses {
|
||||
if strings.HasPrefix(status.StatusString, "warning") {
|
||||
self.Log.Warningf("warning when calling git status: %s", status.StatusString)
|
||||
@@ -69,11 +60,6 @@ func (self *FileLoader) GetStatusFiles(opts GetStatusFileOptions) []*models.File
|
||||
DisplayString: status.StatusString,
|
||||
}
|
||||
|
||||
if diff, ok := fileDiffs[status.Name]; ok {
|
||||
file.LinesAdded = diff.LinesAdded
|
||||
file.LinesDeleted = diff.LinesDeleted
|
||||
}
|
||||
|
||||
models.SetStatusFields(file, status.Change)
|
||||
files = append(files, file)
|
||||
}
|
||||
@@ -101,45 +87,6 @@ func (self *FileLoader) GetStatusFiles(opts GetStatusFileOptions) []*models.File
|
||||
return files
|
||||
}
|
||||
|
||||
type FileDiff struct {
|
||||
LinesAdded int
|
||||
LinesDeleted int
|
||||
}
|
||||
|
||||
func (fileLoader *FileLoader) getFileDiffs() (map[string]FileDiff, error) {
|
||||
diffs, err := fileLoader.gitDiffNumStat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
splitLines := strings.Split(diffs, "\x00")
|
||||
|
||||
fileDiffs := map[string]FileDiff{}
|
||||
for _, line := range splitLines {
|
||||
splitLine := strings.Split(line, "\t")
|
||||
if len(splitLine) != 3 {
|
||||
continue
|
||||
}
|
||||
|
||||
linesAdded, err := strconv.Atoi(splitLine[0])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
linesDeleted, err := strconv.Atoi(splitLine[1])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
fileName := splitLine[2]
|
||||
fileDiffs[fileName] = FileDiff{
|
||||
LinesAdded: linesAdded,
|
||||
LinesDeleted: linesDeleted,
|
||||
}
|
||||
}
|
||||
|
||||
return fileDiffs, nil
|
||||
}
|
||||
|
||||
// GitStatus returns the file status of the repo
|
||||
type GitStatusOptions struct {
|
||||
NoRenames bool
|
||||
@@ -153,16 +100,6 @@ type FileStatus struct {
|
||||
PreviousName string
|
||||
}
|
||||
|
||||
func (fileLoader *FileLoader) gitDiffNumStat() (string, error) {
|
||||
return fileLoader.cmd.New(
|
||||
NewGitCmd("diff").
|
||||
Arg("--numstat").
|
||||
Arg("-z").
|
||||
Arg("HEAD").
|
||||
ToArgv(),
|
||||
).DontLog().RunWithOutput()
|
||||
}
|
||||
|
||||
func (self *FileLoader) gitStatus(opts GitStatusOptions) ([]FileStatus, error) {
|
||||
cmdArgs := NewGitCmd("status").
|
||||
Arg(opts.UntrackedFilesArg).
|
||||
|
||||
@@ -11,35 +11,29 @@ import (
|
||||
|
||||
func TestFileGetStatusFiles(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
similarityThreshold int
|
||||
runner oscommands.ICmdObjRunner
|
||||
showNumstatInFilesView bool
|
||||
expectedFiles []*models.File
|
||||
testName string
|
||||
similarityThreshold int
|
||||
runner oscommands.ICmdObjRunner
|
||||
expectedFiles []*models.File
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
testName: "No files found",
|
||||
similarityThreshold: 50,
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
"No files found",
|
||||
50,
|
||||
oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z", "--find-renames=50%"}, "", nil),
|
||||
expectedFiles: []*models.File{},
|
||||
[]*models.File{},
|
||||
},
|
||||
{
|
||||
testName: "Several files found",
|
||||
similarityThreshold: 50,
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
"Several files found",
|
||||
50,
|
||||
oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z", "--find-renames=50%"},
|
||||
"MM file1.txt\x00A file3.txt\x00AM file2.txt\x00?? file4.txt\x00UU file5.txt",
|
||||
nil,
|
||||
).
|
||||
ExpectGitArgs([]string{"diff", "--numstat", "-z", "HEAD"},
|
||||
"4\t1\tfile1.txt\x001\t0\tfile2.txt\x002\t2\tfile3.txt\x000\t2\tfile4.txt\x002\t2\tfile5.txt",
|
||||
nil,
|
||||
),
|
||||
showNumstatInFilesView: true,
|
||||
expectedFiles: []*models.File{
|
||||
[]*models.File{
|
||||
{
|
||||
Name: "file1.txt",
|
||||
HasStagedChanges: true,
|
||||
@@ -51,8 +45,6 @@ func TestFileGetStatusFiles(t *testing.T) {
|
||||
HasInlineMergeConflicts: false,
|
||||
DisplayString: "MM file1.txt",
|
||||
ShortStatus: "MM",
|
||||
LinesAdded: 4,
|
||||
LinesDeleted: 1,
|
||||
},
|
||||
{
|
||||
Name: "file3.txt",
|
||||
@@ -65,8 +57,6 @@ func TestFileGetStatusFiles(t *testing.T) {
|
||||
HasInlineMergeConflicts: false,
|
||||
DisplayString: "A file3.txt",
|
||||
ShortStatus: "A ",
|
||||
LinesAdded: 2,
|
||||
LinesDeleted: 2,
|
||||
},
|
||||
{
|
||||
Name: "file2.txt",
|
||||
@@ -79,8 +69,6 @@ func TestFileGetStatusFiles(t *testing.T) {
|
||||
HasInlineMergeConflicts: false,
|
||||
DisplayString: "AM file2.txt",
|
||||
ShortStatus: "AM",
|
||||
LinesAdded: 1,
|
||||
LinesDeleted: 0,
|
||||
},
|
||||
{
|
||||
Name: "file4.txt",
|
||||
@@ -93,8 +81,6 @@ func TestFileGetStatusFiles(t *testing.T) {
|
||||
HasInlineMergeConflicts: false,
|
||||
DisplayString: "?? file4.txt",
|
||||
ShortStatus: "??",
|
||||
LinesAdded: 0,
|
||||
LinesDeleted: 2,
|
||||
},
|
||||
{
|
||||
Name: "file5.txt",
|
||||
@@ -107,17 +93,15 @@ func TestFileGetStatusFiles(t *testing.T) {
|
||||
HasInlineMergeConflicts: true,
|
||||
DisplayString: "UU file5.txt",
|
||||
ShortStatus: "UU",
|
||||
LinesAdded: 2,
|
||||
LinesDeleted: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "File with new line char",
|
||||
similarityThreshold: 50,
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
"File with new line char",
|
||||
50,
|
||||
oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z", "--find-renames=50%"}, "MM a\nb.txt", nil),
|
||||
expectedFiles: []*models.File{
|
||||
[]*models.File{
|
||||
{
|
||||
Name: "a\nb.txt",
|
||||
HasStagedChanges: true,
|
||||
@@ -133,14 +117,14 @@ func TestFileGetStatusFiles(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Renamed files",
|
||||
similarityThreshold: 50,
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
"Renamed files",
|
||||
50,
|
||||
oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z", "--find-renames=50%"},
|
||||
"R after1.txt\x00before1.txt\x00RM after2.txt\x00before2.txt",
|
||||
nil,
|
||||
),
|
||||
expectedFiles: []*models.File{
|
||||
[]*models.File{
|
||||
{
|
||||
Name: "after1.txt",
|
||||
PreviousName: "before1.txt",
|
||||
@@ -170,14 +154,14 @@ func TestFileGetStatusFiles(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "File with arrow in name",
|
||||
similarityThreshold: 50,
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
"File with arrow in name",
|
||||
50,
|
||||
oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z", "--find-renames=50%"},
|
||||
`?? a -> b.txt`,
|
||||
nil,
|
||||
),
|
||||
expectedFiles: []*models.File{
|
||||
[]*models.File{
|
||||
{
|
||||
Name: "a -> b.txt",
|
||||
HasStagedChanges: false,
|
||||
@@ -201,14 +185,8 @@ func TestFileGetStatusFiles(t *testing.T) {
|
||||
appState := &config.AppState{}
|
||||
appState.RenameSimilarityThreshold = s.similarityThreshold
|
||||
|
||||
userConfig := &config.UserConfig{
|
||||
Gui: config.GuiConfig{
|
||||
ShowNumstatInFilesView: s.showNumstatInFilesView,
|
||||
},
|
||||
}
|
||||
|
||||
loader := &FileLoader{
|
||||
GitCommon: buildGitCommon(commonDeps{appState: appState, userConfig: userConfig}),
|
||||
GitCommon: buildGitCommon(commonDeps{appState: appState}),
|
||||
cmd: cmd,
|
||||
config: &FakeFileLoaderConfig{showUntrackedFiles: "yes"},
|
||||
getFileType: func(string) string { return "file" },
|
||||
|
||||
@@ -145,11 +145,11 @@ func (self *RebaseCommands) InteractiveRebase(commits []*models.Commit, startIdx
|
||||
|
||||
baseHashOrRoot := getBaseHashOrRoot(commits, baseIndex)
|
||||
|
||||
changes := lo.FilterMap(commits[startIdx:endIdx+1], func(commit *models.Commit, _ int) (daemon.ChangeTodoAction, bool) {
|
||||
changes := lo.Map(commits[startIdx:endIdx+1], func(commit *models.Commit, _ int) daemon.ChangeTodoAction {
|
||||
return daemon.ChangeTodoAction{
|
||||
Hash: commit.Hash,
|
||||
NewAction: action,
|
||||
}, !commit.IsMerge()
|
||||
}
|
||||
})
|
||||
|
||||
self.os.LogCommand(logTodoChanges(changes), false)
|
||||
@@ -324,9 +324,9 @@ func (self *RebaseCommands) MoveFixupCommitDown(commits []*models.Commit, target
|
||||
|
||||
func todoFromCommit(commit *models.Commit) utils.Todo {
|
||||
if commit.Action == todo.UpdateRef {
|
||||
return utils.Todo{Ref: commit.Name}
|
||||
return utils.Todo{Ref: commit.Name, Action: commit.Action}
|
||||
} else {
|
||||
return utils.Todo{Hash: commit.Hash}
|
||||
return utils.Todo{Hash: commit.Hash, Action: commit.Action}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,6 +335,7 @@ func (self *RebaseCommands) EditRebaseTodo(commits []*models.Commit, action todo
|
||||
commitsWithAction := lo.Map(commits, func(commit *models.Commit, _ int) utils.TodoChange {
|
||||
return utils.TodoChange{
|
||||
Hash: commit.Hash,
|
||||
OldAction: commit.Action,
|
||||
NewAction: action,
|
||||
}
|
||||
})
|
||||
@@ -369,7 +370,7 @@ func (self *RebaseCommands) MoveTodosDown(commits []*models.Commit) error {
|
||||
return todoFromCommit(commit)
|
||||
})
|
||||
|
||||
return utils.MoveTodosDown(fileName, todosToMove, true, self.config.GetCoreCommentChar())
|
||||
return utils.MoveTodosDown(fileName, todosToMove, self.config.GetCoreCommentChar())
|
||||
}
|
||||
|
||||
func (self *RebaseCommands) MoveTodosUp(commits []*models.Commit) error {
|
||||
@@ -378,7 +379,7 @@ func (self *RebaseCommands) MoveTodosUp(commits []*models.Commit) error {
|
||||
return todoFromCommit(commit)
|
||||
})
|
||||
|
||||
return utils.MoveTodosUp(fileName, todosToMove, true, self.config.GetCoreCommentChar())
|
||||
return utils.MoveTodosUp(fileName, todosToMove, self.config.GetCoreCommentChar())
|
||||
}
|
||||
|
||||
// SquashAllAboveFixupCommits squashes all fixup! commits above the given one
|
||||
@@ -564,13 +565,6 @@ func (self *RebaseCommands) CherryPickCommitsDuringRebase(commits []*models.Comm
|
||||
return utils.PrependStrToTodoFile(filePath, []byte(todo))
|
||||
}
|
||||
|
||||
func (self *RebaseCommands) DropMergeCommit(commits []*models.Commit, commitIndex int) error {
|
||||
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
||||
baseHashOrRoot: getBaseHashOrRoot(commits, commitIndex+1),
|
||||
instruction: daemon.NewDropMergeCommitInstruction(commits[commitIndex].Hash),
|
||||
}).Run()
|
||||
}
|
||||
|
||||
// we can't start an interactive rebase from the first commit without passing the
|
||||
// '--root' arg
|
||||
func getBaseHashOrRoot(commits []*models.Commit, index int) string {
|
||||
|
||||
@@ -49,10 +49,9 @@ func (self *RemoteCommands) UpdateRemoteUrl(remoteName string, updatedUrl string
|
||||
return self.cmd.New(cmdArgs).Run()
|
||||
}
|
||||
|
||||
func (self *RemoteCommands) DeleteRemoteBranch(task gocui.Task, remoteName string, branchNames []string) error {
|
||||
func (self *RemoteCommands) DeleteRemoteBranch(task gocui.Task, remoteName string, branchName string) error {
|
||||
cmdArgs := NewGitCmd("push").
|
||||
Arg(remoteName, "--delete").
|
||||
Arg(branchNames...).
|
||||
Arg(remoteName, "--delete", branchName).
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run()
|
||||
|
||||
@@ -19,8 +19,6 @@ type File struct {
|
||||
HasInlineMergeConflicts bool
|
||||
DisplayString string
|
||||
ShortStatus string // e.g. 'AD', ' A', 'M ', '??'
|
||||
LinesDeleted int
|
||||
LinesAdded int
|
||||
|
||||
// If true, this must be a worktree folder
|
||||
IsWorktree bool
|
||||
|
||||
@@ -52,7 +52,7 @@ func (self *CmdObjBuilder) NewShell(commandStr string) ICmdObj {
|
||||
}
|
||||
|
||||
func (self *CmdObjBuilder) NewInteractiveShell(commandStr string) ICmdObj {
|
||||
quotedCommand := self.quotedCommandString(commandStr + self.platform.InteractiveShellExit)
|
||||
quotedCommand := self.quotedCommandString(commandStr)
|
||||
cmdArgs := str.ToArgv(fmt.Sprintf("%s %s %s %s", self.platform.InteractiveShell, self.platform.InteractiveShellArg, self.platform.ShellArg, quotedCommand))
|
||||
|
||||
return self.New(cmdArgs)
|
||||
|
||||
@@ -51,14 +51,13 @@ func NewDummyCmdObjBuilder(runner ICmdObjRunner) *CmdObjBuilder {
|
||||
}
|
||||
|
||||
var dummyPlatform = &Platform{
|
||||
OS: "darwin",
|
||||
Shell: "bash",
|
||||
InteractiveShell: "bash",
|
||||
ShellArg: "-c",
|
||||
InteractiveShellArg: "-i",
|
||||
InteractiveShellExit: "; exit $?",
|
||||
OpenCommand: "open {{filename}}",
|
||||
OpenLinkCommand: "open {{link}}",
|
||||
OS: "darwin",
|
||||
Shell: "bash",
|
||||
InteractiveShell: "bash",
|
||||
ShellArg: "-c",
|
||||
InteractiveShellArg: "-i",
|
||||
OpenCommand: "open {{filename}}",
|
||||
OpenLinkCommand: "open {{link}}",
|
||||
}
|
||||
|
||||
func NewDummyOSCommandWithRunner(runner *FakeCmdObjRunner) *OSCommand {
|
||||
|
||||
@@ -35,14 +35,13 @@ type OSCommand struct {
|
||||
|
||||
// Platform stores the os state
|
||||
type Platform struct {
|
||||
OS string
|
||||
Shell string
|
||||
InteractiveShell string
|
||||
ShellArg string
|
||||
InteractiveShellArg string
|
||||
InteractiveShellExit string
|
||||
OpenCommand string
|
||||
OpenLinkCommand string
|
||||
OS string
|
||||
Shell string
|
||||
InteractiveShell string
|
||||
ShellArg string
|
||||
InteractiveShellArg string
|
||||
OpenCommand string
|
||||
OpenLinkCommand string
|
||||
}
|
||||
|
||||
// NewOSCommand os command runner
|
||||
|
||||
@@ -10,14 +10,13 @@ import (
|
||||
|
||||
func GetPlatform() *Platform {
|
||||
return &Platform{
|
||||
OS: runtime.GOOS,
|
||||
Shell: "bash",
|
||||
InteractiveShell: getUserShell(),
|
||||
ShellArg: "-c",
|
||||
InteractiveShellArg: "-i",
|
||||
InteractiveShellExit: "; exit $?",
|
||||
OpenCommand: "open {{filename}}",
|
||||
OpenLinkCommand: "open {{link}}",
|
||||
OS: runtime.GOOS,
|
||||
Shell: "bash",
|
||||
InteractiveShell: getUserShell(),
|
||||
ShellArg: "-c",
|
||||
InteractiveShellArg: "-i",
|
||||
OpenCommand: "open {{filename}}",
|
||||
OpenLinkCommand: "open {{link}}",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,11 +2,10 @@ package oscommands
|
||||
|
||||
func GetPlatform() *Platform {
|
||||
return &Platform{
|
||||
OS: "windows",
|
||||
Shell: "cmd",
|
||||
InteractiveShell: "cmd",
|
||||
ShellArg: "/c",
|
||||
InteractiveShellArg: "",
|
||||
InteractiveShellExit: "",
|
||||
OS: "windows",
|
||||
Shell: "cmd",
|
||||
InteractiveShell: "cmd",
|
||||
ShellArg: "/c",
|
||||
InteractiveShellArg: "",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,34 +349,13 @@ func findConfigFile(filename string) (exists bool, path string) {
|
||||
return true, legacyConfigPath
|
||||
}
|
||||
|
||||
// if on macOs, default to looking in ~/.config/lazygit first since that's
|
||||
// the most common location for CLI apps to store configuration when
|
||||
// `XDG_CONFIG_HOME` is not set. The `xdg` package uses `~/Library/Application\ Support`
|
||||
// as the default directory for macOS, so we have to handle this case manually.
|
||||
if overrideXdgConfigHome && os.Getenv("XDG_CONFIG_HOME") == "" {
|
||||
homeConfigFilePath, err := findHomeConfigFile(filename)
|
||||
if err == nil {
|
||||
return true, homeConfigFilePath
|
||||
}
|
||||
}
|
||||
|
||||
// look for lazygit/filename in XDG_CONFIG_HOME and XDG_CONFIG_DIRS
|
||||
configFilepath, err := xdg.SearchConfigFile(filepath.Join("lazygit", filename))
|
||||
if err == nil {
|
||||
return true, configFilepath
|
||||
}
|
||||
|
||||
configHome := xdg.ConfigHome
|
||||
// If we're on macOS and XDG_CONFIG_HOME is not set, we use
|
||||
// ~/.config/lazygit instead of the default `~/Library/Application\ Support`
|
||||
// directory the `xdg` package uses.
|
||||
if overrideXdgConfigHome && os.Getenv("XDG_CONFIG_HOME") == "" {
|
||||
if homeDir, err := os.UserHomeDir(); err == nil {
|
||||
return false, filepath.Join(homeDir, ".config", "lazygit", filename)
|
||||
}
|
||||
}
|
||||
|
||||
return false, filepath.Join(configHome, "lazygit", filename)
|
||||
return false, filepath.Join(xdg.ConfigHome, "lazygit", filename)
|
||||
}
|
||||
|
||||
var ConfigFilename = "config.yml"
|
||||
@@ -515,17 +494,3 @@ func LogPath() (string, error) {
|
||||
|
||||
return stateFilePath("development.log")
|
||||
}
|
||||
|
||||
func findHomeConfigFile(filename string) (string, error) {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
path := filepath.Join(home, ".config", "lazygit", filename)
|
||||
if _, err = os.Stat(path); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return path, nil
|
||||
}
|
||||
|
||||
@@ -21,8 +21,8 @@ func isContainer() bool {
|
||||
func GetPlatformDefaultConfig() OSConfig {
|
||||
if isWSL() && !isContainer() {
|
||||
return OSConfig{
|
||||
Open: `powershell.exe start explorer.exe "$(wslpath -w {{filename}})" >/dev/null`,
|
||||
OpenLink: `powershell.exe start '{{link}}' >/dev/null`,
|
||||
Open: `powershell.exe start explorer.exe {{filename}} >/dev/null`,
|
||||
OpenLink: `powershell.exe start {{link}} >/dev/null`,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -91,10 +91,6 @@ type GuiConfig struct {
|
||||
// - '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"`
|
||||
// If true, wrap lines in the staging view to the width of the view. This
|
||||
// makes it much easier to work with diffs that have long lines, e.g.
|
||||
// paragraphs of markdown text.
|
||||
WrapLinesInStagingView bool `yaml:"wrapLinesInStagingView"`
|
||||
// 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.
|
||||
@@ -113,8 +109,6 @@ type GuiConfig struct {
|
||||
// If true, display the files in the file views as a tree. If false, display the files as a flat list.
|
||||
// This can be toggled from within Lazygit with the '~' key, but that will not change the default.
|
||||
ShowFileTree bool `yaml:"showFileTree"`
|
||||
// If true, show the number of lines changed per file in the Files view
|
||||
ShowNumstatInFilesView bool `yaml:"showNumstatInFilesView"`
|
||||
// If true, show a random tip in the command log when Lazygit starts
|
||||
ShowRandomTip bool `yaml:"showRandomTip"`
|
||||
// If true, show the command log
|
||||
@@ -244,7 +238,7 @@ type GitConfig struct {
|
||||
// Command used when displaying the current branch git log in the main window
|
||||
BranchLogCmd string `yaml:"branchLogCmd"`
|
||||
// Command used to display git log of all branches in the main window.
|
||||
// Deprecated: Use `allBranchesLogCmds` instead.
|
||||
// Deprecated: User `allBranchesLogCmds` instead.
|
||||
AllBranchesLogCmd string `yaml:"allBranchesLogCmd"`
|
||||
// Commands used to display git log of all branches in the main window, they will be cycled in order of appearance
|
||||
AllBranchesLogCmds []string `yaml:"allBranchesLogCmds"`
|
||||
@@ -696,7 +690,6 @@ func GetDefaultConfig() *UserConfig {
|
||||
ExpandedSidePanelWeight: 2,
|
||||
MainPanelSplitMode: "flexible",
|
||||
EnlargedSideViewLocation: "left",
|
||||
WrapLinesInStagingView: true,
|
||||
Language: "auto",
|
||||
TimeFormat: "02 Jan 06",
|
||||
ShortTimeFormat: time.Kitchen,
|
||||
@@ -721,7 +714,6 @@ func GetDefaultConfig() *UserConfig {
|
||||
ShowBottomLine: true,
|
||||
ShowPanelJumps: true,
|
||||
ShowFileTree: true,
|
||||
ShowNumstatInFilesView: false,
|
||||
ShowRandomTip: true,
|
||||
ShowIcons: false,
|
||||
NerdFontsVersion: "",
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
//go:build !darwin
|
||||
|
||||
package config
|
||||
|
||||
const overrideXdgConfigHome = false
|
||||
@@ -1,5 +0,0 @@
|
||||
//go:build darwin
|
||||
|
||||
package config
|
||||
|
||||
const overrideXdgConfigHome = true
|
||||
+15
-11
@@ -3,6 +3,7 @@ package gui
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
@@ -75,18 +76,21 @@ func (self *BackgroundRoutineMgr) startBackgroundRoutines() {
|
||||
func (self *BackgroundRoutineMgr) startBackgroundFetch() {
|
||||
self.gui.waitForIntro.Wait()
|
||||
|
||||
fetch := func() error {
|
||||
err := self.backgroundFetch()
|
||||
self.gui.c.Render()
|
||||
return err
|
||||
}
|
||||
|
||||
// We want an immediate fetch at startup, and since goEvery starts by
|
||||
// waiting for the interval, we need to trigger one manually first
|
||||
_ = fetch()
|
||||
|
||||
isNew := self.gui.IsNewRepo
|
||||
userConfig := self.gui.UserConfig()
|
||||
self.goEvery(time.Second*time.Duration(userConfig.Refresher.FetchInterval), self.gui.stopChan, fetch)
|
||||
if !isNew {
|
||||
time.After(time.Duration(userConfig.Refresher.FetchInterval) * time.Second)
|
||||
}
|
||||
err := self.backgroundFetch()
|
||||
if err != nil && strings.Contains(err.Error(), "exit status 128") && isNew {
|
||||
self.gui.c.Alert(self.gui.c.Tr.NoAutomaticGitFetchTitle, self.gui.c.Tr.NoAutomaticGitFetchBody)
|
||||
} else {
|
||||
self.goEvery(time.Second*time.Duration(userConfig.Refresher.FetchInterval), self.gui.stopChan, func() error {
|
||||
err := self.backgroundFetch()
|
||||
self.gui.c.Render()
|
||||
return err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (self *BackgroundRoutineMgr) startBackgroundFilesRefresh(refreshInterval int) {
|
||||
|
||||
@@ -70,6 +70,8 @@ func (self *ContextMgr) Push(c types.Context, opts ...types.OnFocusOpts) {
|
||||
return
|
||||
}
|
||||
|
||||
gui.resetHelpersAndControllers()
|
||||
|
||||
contextsToDeactivate, contextToActivate := self.pushToContextStack(c)
|
||||
|
||||
for _, contextToDeactivate := range contextsToDeactivate {
|
||||
|
||||
@@ -30,7 +30,7 @@ func NewBranchesContext(c *ContextCommon) *BranchesContext {
|
||||
c.State().GetItemOperation,
|
||||
c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL,
|
||||
c.Modes().Diffing.Ref,
|
||||
c.Views().Branches.InnerWidth(),
|
||||
c.Views().Branches.Width(),
|
||||
c.Tr,
|
||||
c.UserConfig(),
|
||||
c.Model().Worktrees,
|
||||
|
||||
@@ -30,9 +30,6 @@ type CommitMessageViewModel struct {
|
||||
// if true, then upon escaping from the commit message panel, we will preserve
|
||||
// the message so that it's still shown next time we open the panel
|
||||
preserveMessage bool
|
||||
// we remember the initial message so that we can tell whether we should preserve
|
||||
// the message; if it's still identical to the initial message, we don't
|
||||
initialMessage string
|
||||
// the full preserved message (combined summary and description)
|
||||
preservedMessage string
|
||||
// invoked when pressing enter in the commit message panel
|
||||
@@ -87,10 +84,6 @@ func (self *CommitMessageContext) SetPreservedMessage(message string) {
|
||||
self.viewModel.preservedMessage = message
|
||||
}
|
||||
|
||||
func (self *CommitMessageContext) GetInitialMessage() string {
|
||||
return strings.TrimSpace(self.viewModel.initialMessage)
|
||||
}
|
||||
|
||||
func (self *CommitMessageContext) GetHistoryMessage() string {
|
||||
return self.viewModel.historyMessage
|
||||
}
|
||||
@@ -108,13 +101,11 @@ func (self *CommitMessageContext) SetPanelState(
|
||||
summaryTitle string,
|
||||
descriptionTitle string,
|
||||
preserveMessage bool,
|
||||
initialMessage string,
|
||||
onConfirm func(string, string) error,
|
||||
onSwitchToEditor func(string) error,
|
||||
) {
|
||||
self.viewModel.selectedindex = index
|
||||
self.viewModel.preserveMessage = preserveMessage
|
||||
self.viewModel.initialMessage = initialMessage
|
||||
self.viewModel.onConfirm = onConfirm
|
||||
self.viewModel.onSwitchToEditor = onSwitchToEditor
|
||||
self.GetView().Title = summaryTitle
|
||||
|
||||
@@ -14,6 +14,7 @@ type ConfirmationContext struct {
|
||||
type ConfirmationContextState struct {
|
||||
OnConfirm func() error
|
||||
OnClose func() error
|
||||
Multiline bool
|
||||
}
|
||||
|
||||
var _ types.Context = (*ConfirmationContext)(nil)
|
||||
|
||||
@@ -115,5 +115,5 @@ func (self *MergeConflictsContext) SetSelectedLineRange() {
|
||||
func (self *MergeConflictsContext) GetOriginY() int {
|
||||
view := self.GetView()
|
||||
conflictMiddle := self.GetState().GetConflictMiddle()
|
||||
return int(math.Max(0, float64(conflictMiddle-(view.InnerHeight()/2))))
|
||||
return int(math.Max(0, float64(conflictMiddle-(view.Height()/2))))
|
||||
}
|
||||
|
||||
@@ -39,13 +39,12 @@ func NewPatchExplorerContext(
|
||||
mutex: &deadlock.Mutex{},
|
||||
getIncludedLineIndices: getIncludedLineIndices,
|
||||
SimpleContext: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: view,
|
||||
WindowName: windowName,
|
||||
Key: key,
|
||||
Kind: types.MAIN_CONTEXT,
|
||||
Focusable: true,
|
||||
HighlightOnFocus: true,
|
||||
NeedsRerenderOnWidthChange: types.NEEDS_RERENDER_ON_WIDTH_CHANGE_WHEN_WIDTH_CHANGES,
|
||||
View: view,
|
||||
WindowName: windowName,
|
||||
Key: key,
|
||||
Kind: types.MAIN_CONTEXT,
|
||||
Focusable: true,
|
||||
HighlightOnFocus: true,
|
||||
})),
|
||||
SearchTrait: NewSearchTrait(c),
|
||||
}
|
||||
@@ -59,8 +58,6 @@ func NewPatchExplorerContext(
|
||||
}),
|
||||
)
|
||||
|
||||
ctx.SetHandleRenderFunc(ctx.OnViewWidthChanged)
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
@@ -107,15 +104,16 @@ func (self *PatchExplorerContext) setContent() {
|
||||
func (self *PatchExplorerContext) FocusSelection() {
|
||||
view := self.GetView()
|
||||
state := self.GetState()
|
||||
bufferHeight := view.InnerHeight()
|
||||
_, viewHeight := view.Size()
|
||||
bufferHeight := viewHeight - 1
|
||||
_, origin := view.Origin()
|
||||
numLines := view.ViewLinesHeight()
|
||||
numLines := view.LinesHeight()
|
||||
|
||||
newOriginY := state.CalculateOrigin(origin, bufferHeight, numLines)
|
||||
|
||||
view.SetOriginY(newOriginY)
|
||||
|
||||
startIdx, endIdx := state.SelectedViewRange()
|
||||
startIdx, endIdx := state.SelectedRange()
|
||||
// As far as the view is concerned, we are always selecting a range
|
||||
view.SetRangeSelectStart(startIdx)
|
||||
view.SetCursorY(endIdx - newOriginY)
|
||||
@@ -143,11 +141,3 @@ func (self *PatchExplorerContext) GetMutex() *deadlock.Mutex {
|
||||
func (self *PatchExplorerContext) ModelSearchResults(searchStr string, caseSensitive bool) []gocui.SearchPosition {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *PatchExplorerContext) OnViewWidthChanged() {
|
||||
if state := self.GetState(); state != nil {
|
||||
state.OnViewWidthChanged(self.GetView())
|
||||
self.setContent()
|
||||
self.RenderAndFocus()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
|
||||
type SimpleContext struct {
|
||||
*BaseContext
|
||||
handleRenderFunc func()
|
||||
}
|
||||
|
||||
func NewSimpleContext(baseContext *BaseContext) *SimpleContext {
|
||||
@@ -55,13 +54,6 @@ func (self *SimpleContext) HandleFocusLost(opts types.OnFocusLostOpts) {
|
||||
}
|
||||
|
||||
func (self *SimpleContext) HandleRender() {
|
||||
if self.handleRenderFunc != nil {
|
||||
self.handleRenderFunc()
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SimpleContext) SetHandleRenderFunc(f func()) {
|
||||
self.handleRenderFunc = f
|
||||
}
|
||||
|
||||
func (self *SimpleContext) HandleRenderToMain() {
|
||||
|
||||
@@ -63,7 +63,7 @@ func (self *ViewTrait) SetOriginX(value int) {
|
||||
// tells us the start of line indexes shown in the view currently as well as the capacity of lines shown in the viewport.
|
||||
func (self *ViewTrait) ViewPortYBounds() (int, int) {
|
||||
_, start := self.view.Origin()
|
||||
length := self.view.InnerHeight()
|
||||
length := self.view.InnerHeight() + 1
|
||||
return start, length
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ func (self *ViewTrait) ScrollDown(value int) {
|
||||
|
||||
// this returns the amount we'll scroll if we want to scroll by a page.
|
||||
func (self *ViewTrait) PageDelta() int {
|
||||
height := self.view.InnerHeight()
|
||||
_, height := self.view.Size()
|
||||
|
||||
delta := height - 1
|
||||
if delta == 0 {
|
||||
|
||||
@@ -30,8 +30,7 @@ func NewWorkingTreeContext(c *ContextCommon) *WorkingTreeContext {
|
||||
|
||||
getDisplayStrings := func(_ int, _ int) [][]string {
|
||||
showFileIcons := icons.IsIconEnabled() && c.UserConfig().Gui.ShowFileIcons
|
||||
showNumstat := c.UserConfig().Gui.ShowNumstatInFilesView
|
||||
lines := presentation.RenderFileTree(viewModel, c.Model().Submodules, showFileIcons, showNumstat)
|
||||
lines := presentation.RenderFileTree(viewModel, c.Model().Submodules, showFileIcons)
|
||||
return lo.Map(lines, func(line string, _ int) []string {
|
||||
return []string{line}
|
||||
})
|
||||
|
||||
@@ -299,7 +299,7 @@ func (gui *Gui) resetHelpersAndControllers() {
|
||||
)
|
||||
|
||||
controllers.AttachControllers(gui.State.Contexts.CustomPatchBuilderSecondary,
|
||||
verticalScrollControllerFactory.Create(gui.State.Contexts.CustomPatchBuilderSecondary),
|
||||
verticalScrollControllerFactory.Create(gui.State.Contexts.CustomPatchBuilder),
|
||||
)
|
||||
|
||||
controllers.AttachControllers(gui.State.Contexts.MergeConflicts,
|
||||
|
||||
@@ -91,8 +91,8 @@ func (self *BranchesController) GetKeybindings(opts types.KeybindingsOpts) []*ty
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Remove),
|
||||
Handler: self.withItems(self.delete),
|
||||
GetDisabledReason: self.require(self.itemRangeSelected(self.branchesAreReal)),
|
||||
Handler: self.withItem(self.delete),
|
||||
GetDisabledReason: self.require(self.singleItemSelected(self.branchIsReal)),
|
||||
Description: self.c.Tr.Delete,
|
||||
Tooltip: self.c.Tr.BranchDeleteTooltip,
|
||||
OpensMenu: true,
|
||||
@@ -520,80 +520,62 @@ func (self *BranchesController) createNewBranchWithName(newBranchName string) er
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, KeepBranchSelectionIndex: true})
|
||||
}
|
||||
|
||||
func (self *BranchesController) localDelete(branches []*models.Branch) error {
|
||||
return self.c.Helpers().BranchesHelper.ConfirmLocalDelete(branches)
|
||||
func (self *BranchesController) localDelete(branch *models.Branch) error {
|
||||
return self.c.Helpers().BranchesHelper.ConfirmLocalDelete(branch)
|
||||
}
|
||||
|
||||
func (self *BranchesController) remoteDelete(branches []*models.Branch) error {
|
||||
remoteBranches := lo.Map(branches, func(branch *models.Branch, _ int) *models.RemoteBranch {
|
||||
return &models.RemoteBranch{Name: branch.UpstreamBranch, RemoteName: branch.UpstreamRemote}
|
||||
})
|
||||
return self.c.Helpers().BranchesHelper.ConfirmDeleteRemote(remoteBranches)
|
||||
func (self *BranchesController) remoteDelete(branch *models.Branch) error {
|
||||
return self.c.Helpers().BranchesHelper.ConfirmDeleteRemote(branch.UpstreamRemote, branch.UpstreamBranch)
|
||||
}
|
||||
|
||||
func (self *BranchesController) localAndRemoteDelete(branches []*models.Branch) error {
|
||||
return self.c.Helpers().BranchesHelper.ConfirmLocalAndRemoteDelete(branches)
|
||||
func (self *BranchesController) localAndRemoteDelete(branch *models.Branch) error {
|
||||
return self.c.Helpers().BranchesHelper.ConfirmLocalAndRemoteDelete(branch)
|
||||
}
|
||||
|
||||
func (self *BranchesController) delete(branches []*models.Branch) error {
|
||||
func (self *BranchesController) delete(branch *models.Branch) error {
|
||||
checkedOutBranch := self.c.Helpers().Refs.GetCheckedOutRef()
|
||||
isBranchCheckedOut := lo.SomeBy(branches, func(branch *models.Branch) bool {
|
||||
return checkedOutBranch.Name == branch.Name
|
||||
})
|
||||
hasUpstream := lo.EveryBy(branches, func(branch *models.Branch) bool {
|
||||
return branch.IsTrackingRemote() && !branch.UpstreamGone
|
||||
})
|
||||
|
||||
localDeleteItem := &types.MenuItem{
|
||||
Label: lo.Ternary(len(branches) > 1, self.c.Tr.DeleteLocalBranches, self.c.Tr.DeleteLocalBranch),
|
||||
Label: self.c.Tr.DeleteLocalBranch,
|
||||
Key: 'c',
|
||||
OnPress: func() error {
|
||||
return self.localDelete(branches)
|
||||
return self.localDelete(branch)
|
||||
},
|
||||
}
|
||||
if isBranchCheckedOut {
|
||||
if checkedOutBranch.Name == branch.Name {
|
||||
localDeleteItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.CantDeleteCheckOutBranch}
|
||||
}
|
||||
|
||||
remoteDeleteItem := &types.MenuItem{
|
||||
Label: lo.Ternary(len(branches) > 1, self.c.Tr.DeleteRemoteBranches, self.c.Tr.DeleteRemoteBranch),
|
||||
Label: self.c.Tr.DeleteRemoteBranch,
|
||||
Key: 'r',
|
||||
OnPress: func() error {
|
||||
return self.remoteDelete(branches)
|
||||
return self.remoteDelete(branch)
|
||||
},
|
||||
}
|
||||
if !hasUpstream {
|
||||
remoteDeleteItem.DisabledReason = &types.DisabledReason{
|
||||
Text: lo.Ternary(len(branches) > 1, self.c.Tr.UpstreamsNotSetError, self.c.Tr.UpstreamNotSetError),
|
||||
}
|
||||
if !branch.IsTrackingRemote() || branch.UpstreamGone {
|
||||
remoteDeleteItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.UpstreamNotSetError}
|
||||
}
|
||||
|
||||
deleteBothItem := &types.MenuItem{
|
||||
Label: lo.Ternary(len(branches) > 1, self.c.Tr.DeleteLocalAndRemoteBranches, self.c.Tr.DeleteLocalAndRemoteBranch),
|
||||
Label: self.c.Tr.DeleteLocalAndRemoteBranch,
|
||||
Key: 'b',
|
||||
OnPress: func() error {
|
||||
return self.localAndRemoteDelete(branches)
|
||||
return self.localAndRemoteDelete(branch)
|
||||
},
|
||||
}
|
||||
if isBranchCheckedOut {
|
||||
if checkedOutBranch.Name == branch.Name {
|
||||
deleteBothItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.CantDeleteCheckOutBranch}
|
||||
} else if !hasUpstream {
|
||||
deleteBothItem.DisabledReason = &types.DisabledReason{
|
||||
Text: lo.Ternary(len(branches) > 1, self.c.Tr.UpstreamsNotSetError, self.c.Tr.UpstreamNotSetError),
|
||||
}
|
||||
} else if !branch.IsTrackingRemote() || branch.UpstreamGone {
|
||||
deleteBothItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.UpstreamNotSetError}
|
||||
}
|
||||
|
||||
var menuTitle string
|
||||
if len(branches) == 1 {
|
||||
menuTitle = utils.ResolvePlaceholderString(
|
||||
self.c.Tr.DeleteBranchTitle,
|
||||
map[string]string{
|
||||
"selectedBranchName": branches[0].Name,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
menuTitle = self.c.Tr.DeleteBranchesTitle
|
||||
}
|
||||
menuTitle := utils.ResolvePlaceholderString(
|
||||
self.c.Tr.DeleteBranchTitle,
|
||||
map[string]string{
|
||||
"selectedBranchName": branch.Name,
|
||||
},
|
||||
)
|
||||
|
||||
return self.c.Menu(types.CreateMenuOptions{
|
||||
Title: menuTitle,
|
||||
@@ -748,23 +730,11 @@ func (self *BranchesController) createPullRequestMenu(selectedBranch *models.Bra
|
||||
{
|
||||
LabelColumns: fromToLabelColumns(branch.Name, self.c.Tr.SelectBranch),
|
||||
OnPress: func() error {
|
||||
if !branch.IsTrackingRemote() {
|
||||
return errors.New(self.c.Tr.PullRequestNoUpstream)
|
||||
}
|
||||
|
||||
if len(self.c.Model().Remotes) == 1 {
|
||||
toRemote := self.c.Model().Remotes[0].Name
|
||||
self.c.Log.Debugf("PR will target the only existing remote '%s'", toRemote)
|
||||
return self.promptForTargetBranchNameAndCreatePullRequest(branch, toRemote)
|
||||
}
|
||||
|
||||
self.c.Prompt(types.PromptOpts{
|
||||
Title: self.c.Tr.SelectTargetRemote,
|
||||
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRemoteSuggestionsFunc(),
|
||||
HandleConfirm: func(toRemote string) error {
|
||||
self.c.Log.Debugf("PR will target remote '%s'", toRemote)
|
||||
|
||||
return self.promptForTargetBranchNameAndCreatePullRequest(branch, toRemote)
|
||||
Title: branch.Name + " →",
|
||||
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRemoteBranchesSuggestionsFunc("/"),
|
||||
HandleConfirm: func(targetBranchName string) error {
|
||||
return self.createPullRequest(branch.Name, targetBranchName)
|
||||
},
|
||||
})
|
||||
|
||||
@@ -794,26 +764,6 @@ func (self *BranchesController) createPullRequestMenu(selectedBranch *models.Bra
|
||||
return self.c.Menu(types.CreateMenuOptions{Title: fmt.Sprint(self.c.Tr.CreatePullRequestOptions), Items: menuItems})
|
||||
}
|
||||
|
||||
func (self *BranchesController) promptForTargetBranchNameAndCreatePullRequest(fromBranch *models.Branch, toRemote string) error {
|
||||
remoteDoesNotExist := lo.NoneBy(self.c.Model().Remotes, func(remote *models.Remote) bool {
|
||||
return remote.Name == toRemote
|
||||
})
|
||||
if remoteDoesNotExist {
|
||||
return fmt.Errorf(self.c.Tr.NoValidRemoteName, toRemote)
|
||||
}
|
||||
|
||||
self.c.Prompt(types.PromptOpts{
|
||||
Title: fmt.Sprintf("%s → %s/", fromBranch.UpstreamBranch, toRemote),
|
||||
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRemoteBranchesForRemoteSuggestionsFunc(toRemote),
|
||||
HandleConfirm: func(toBranch string) error {
|
||||
self.c.Log.Debugf("PR will target branch '%s' on remote '%s'", toBranch, toRemote)
|
||||
return self.createPullRequest(fromBranch.UpstreamBranch, toBranch)
|
||||
},
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *BranchesController) createPullRequest(from string, to string) error {
|
||||
url, err := self.c.Helpers().Host.GetPullRequestURL(from, to)
|
||||
if err != nil {
|
||||
@@ -837,16 +787,6 @@ func (self *BranchesController) branchIsReal(branch *models.Branch) *types.Disab
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *BranchesController) branchesAreReal(selectedBranches []*models.Branch, startIdx int, endIdx int) *types.DisabledReason {
|
||||
if !lo.EveryBy(selectedBranches, func(branch *models.Branch) bool {
|
||||
return branch.IsRealBranch()
|
||||
}) {
|
||||
return &types.DisabledReason{Text: self.c.Tr.SelectedItemIsNotABranch}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *BranchesController) notMergingIntoYourself(branch *models.Branch) *types.DisabledReason {
|
||||
selectedBranchName := branch.Name
|
||||
checkedOutBranch := self.c.Helpers().Refs.GetCheckedOutRef().Name
|
||||
|
||||
@@ -24,20 +24,54 @@ func NewConfirmationController(
|
||||
}
|
||||
|
||||
func (self *ConfirmationController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
|
||||
bindings := []*types.Binding{
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Confirm),
|
||||
Handler: func() error { return self.context().State.OnConfirm() },
|
||||
Description: self.c.Tr.Confirm,
|
||||
DisplayOnScreen: true,
|
||||
},
|
||||
{
|
||||
bindings := []*types.Binding{}
|
||||
|
||||
if self.context().State.Multiline {
|
||||
bindings = append(bindings,
|
||||
&types.Binding{
|
||||
// Hard coding because this is always the button that adds a newline.
|
||||
Key: opts.GetKey("<enter>"),
|
||||
Handler: func() error {
|
||||
self.c.Log.Warn("In <enter> handler")
|
||||
self.c.Views().Confirmation.TextArea.TypeString("\n")
|
||||
self.c.Views().Confirmation.RenderTextArea()
|
||||
return nil
|
||||
},
|
||||
Description: self.c.Tr.Confirm,
|
||||
DisplayOnScreen: false,
|
||||
},
|
||||
&types.Binding{
|
||||
Key: opts.GetKey(opts.Config.Universal.ConfirmInEditor),
|
||||
Handler: func() error {
|
||||
self.c.Log.Warn("In <alt-enter> handler")
|
||||
return self.context().State.OnConfirm()
|
||||
},
|
||||
Description: self.c.Tr.Confirm,
|
||||
DisplayOnScreen: true,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
bindings = append(bindings,
|
||||
&types.Binding{
|
||||
Key: opts.GetKey(opts.Config.Universal.Confirm),
|
||||
Handler: func() error {
|
||||
self.c.Log.Warn("In single-line confirm handler")
|
||||
return self.context().State.OnConfirm()
|
||||
},
|
||||
Description: self.c.Tr.Confirm,
|
||||
DisplayOnScreen: true,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
bindings = append(bindings,
|
||||
&types.Binding{
|
||||
Key: opts.GetKey(opts.Config.Universal.Return),
|
||||
Handler: func() error { return self.context().State.OnClose() },
|
||||
Description: self.c.Tr.CloseCancel,
|
||||
DisplayOnScreen: true,
|
||||
},
|
||||
{
|
||||
&types.Binding{
|
||||
Key: opts.GetKey(opts.Config.Universal.TogglePanel),
|
||||
Handler: func() error {
|
||||
if len(self.c.Contexts().Suggestions.State.Suggestions) > 0 {
|
||||
@@ -54,7 +88,7 @@ func (self *ConfirmationController) GetKeybindings(opts types.KeybindingsOpts) [
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
return bindings
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
@@ -10,7 +9,6 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type BranchesHelper struct {
|
||||
@@ -25,16 +23,12 @@ func NewBranchesHelper(c *HelperCommon, worktreeHelper *WorktreeHelper) *Branche
|
||||
}
|
||||
}
|
||||
|
||||
func (self *BranchesHelper) ConfirmLocalDelete(branches []*models.Branch) error {
|
||||
if len(branches) > 1 {
|
||||
if lo.SomeBy(branches, func(branch *models.Branch) bool { return self.checkedOutByOtherWorktree(branch) }) {
|
||||
return errors.New(self.c.Tr.SomeBranchesCheckedOutByWorktreeError)
|
||||
}
|
||||
} else if self.checkedOutByOtherWorktree(branches[0]) {
|
||||
return self.promptWorktreeBranchDelete(branches[0])
|
||||
func (self *BranchesHelper) ConfirmLocalDelete(branch *models.Branch) error {
|
||||
if self.checkedOutByOtherWorktree(branch) {
|
||||
return self.promptWorktreeBranchDelete(branch)
|
||||
}
|
||||
|
||||
allBranchesMerged, err := self.allBranchesMerged(branches)
|
||||
isMerged, err := self.c.Git().Branch.IsBranchMerged(branch, self.c.Model().MainBranches)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -42,32 +36,24 @@ func (self *BranchesHelper) ConfirmLocalDelete(branches []*models.Branch) error
|
||||
doDelete := func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(_ gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.DeleteLocalBranch)
|
||||
branchNames := lo.Map(branches, func(branch *models.Branch, _ int) string { return branch.Name })
|
||||
if err := self.c.Git().Branch.LocalDelete(branchNames, true); err != nil {
|
||||
if err := self.c.Git().Branch.LocalDelete(branch.Name, true); err != nil {
|
||||
return err
|
||||
}
|
||||
selectionStart, _ := self.c.Contexts().Branches.GetSelectionRange()
|
||||
self.c.Contexts().Branches.SetSelectedLineIdx(selectionStart)
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}})
|
||||
})
|
||||
}
|
||||
|
||||
if allBranchesMerged {
|
||||
if isMerged {
|
||||
return doDelete()
|
||||
}
|
||||
|
||||
title := self.c.Tr.ForceDeleteBranchTitle
|
||||
var message string
|
||||
if len(branches) == 1 {
|
||||
message = utils.ResolvePlaceholderString(
|
||||
self.c.Tr.ForceDeleteBranchMessage,
|
||||
map[string]string{
|
||||
"selectedBranchName": branches[0].Name,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
message = self.c.Tr.ForceDeleteBranchesMessage
|
||||
}
|
||||
message := utils.ResolvePlaceholderString(
|
||||
self.c.Tr.ForceDeleteBranchMessage,
|
||||
map[string]string{
|
||||
"selectedBranchName": branch.Name,
|
||||
},
|
||||
)
|
||||
|
||||
self.c.Confirm(types.ConfirmOpts{
|
||||
Title: title,
|
||||
@@ -80,36 +66,27 @@ func (self *BranchesHelper) ConfirmLocalDelete(branches []*models.Branch) error
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *BranchesHelper) ConfirmDeleteRemote(remoteBranches []*models.RemoteBranch) error {
|
||||
var title string
|
||||
if len(remoteBranches) == 1 {
|
||||
title = utils.ResolvePlaceholderString(
|
||||
self.c.Tr.DeleteBranchTitle,
|
||||
map[string]string{
|
||||
"selectedBranchName": remoteBranches[0].Name,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
title = self.c.Tr.DeleteBranchesTitle
|
||||
}
|
||||
var prompt string
|
||||
if len(remoteBranches) == 1 {
|
||||
prompt = utils.ResolvePlaceholderString(
|
||||
self.c.Tr.DeleteRemoteBranchPrompt,
|
||||
map[string]string{
|
||||
"selectedBranchName": remoteBranches[0].Name,
|
||||
"upstream": remoteBranches[0].RemoteName,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
prompt = self.c.Tr.DeleteRemoteBranchesPrompt
|
||||
}
|
||||
func (self *BranchesHelper) ConfirmDeleteRemote(remoteName string, branchName string) error {
|
||||
title := utils.ResolvePlaceholderString(
|
||||
self.c.Tr.DeleteBranchTitle,
|
||||
map[string]string{
|
||||
"selectedBranchName": branchName,
|
||||
},
|
||||
)
|
||||
prompt := utils.ResolvePlaceholderString(
|
||||
self.c.Tr.DeleteRemoteBranchPrompt,
|
||||
map[string]string{
|
||||
"selectedBranchName": branchName,
|
||||
"upstream": remoteName,
|
||||
},
|
||||
)
|
||||
self.c.Confirm(types.ConfirmOpts{
|
||||
Title: title,
|
||||
Prompt: prompt,
|
||||
HandleConfirm: func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(task gocui.Task) error {
|
||||
if err := self.deleteRemoteBranches(remoteBranches, task); err != nil {
|
||||
self.c.LogAction(self.c.Tr.Actions.DeleteRemoteBranch)
|
||||
if err := self.c.Git().Remote.DeleteRemoteBranch(task, remoteName, branchName); err != nil {
|
||||
return err
|
||||
}
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}})
|
||||
@@ -120,41 +97,32 @@ func (self *BranchesHelper) ConfirmDeleteRemote(remoteBranches []*models.RemoteB
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *BranchesHelper) ConfirmLocalAndRemoteDelete(branches []*models.Branch) error {
|
||||
if lo.SomeBy(branches, func(branch *models.Branch) bool { return self.checkedOutByOtherWorktree(branch) }) {
|
||||
return errors.New(self.c.Tr.SomeBranchesCheckedOutByWorktreeError)
|
||||
func (self *BranchesHelper) ConfirmLocalAndRemoteDelete(branch *models.Branch) error {
|
||||
if self.checkedOutByOtherWorktree(branch) {
|
||||
return self.promptWorktreeBranchDelete(branch)
|
||||
}
|
||||
|
||||
allBranchesMerged, err := self.allBranchesMerged(branches)
|
||||
isMerged, err := self.c.Git().Branch.IsBranchMerged(branch, self.c.Model().MainBranches)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var prompt string
|
||||
if len(branches) == 1 {
|
||||
prompt = utils.ResolvePlaceholderString(
|
||||
self.c.Tr.DeleteLocalAndRemoteBranchPrompt,
|
||||
prompt := utils.ResolvePlaceholderString(
|
||||
self.c.Tr.DeleteLocalAndRemoteBranchPrompt,
|
||||
map[string]string{
|
||||
"localBranchName": branch.Name,
|
||||
"remoteBranchName": branch.UpstreamBranch,
|
||||
"remoteName": branch.UpstreamRemote,
|
||||
},
|
||||
)
|
||||
|
||||
if !isMerged {
|
||||
prompt += "\n\n" + utils.ResolvePlaceholderString(
|
||||
self.c.Tr.ForceDeleteBranchMessage,
|
||||
map[string]string{
|
||||
"localBranchName": branches[0].Name,
|
||||
"remoteBranchName": branches[0].UpstreamBranch,
|
||||
"remoteName": branches[0].UpstreamRemote,
|
||||
"selectedBranchName": branch.Name,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
prompt = self.c.Tr.DeleteLocalAndRemoteBranchesPrompt
|
||||
}
|
||||
|
||||
if !allBranchesMerged {
|
||||
if len(branches) == 1 {
|
||||
prompt += "\n\n" + utils.ResolvePlaceholderString(
|
||||
self.c.Tr.ForceDeleteBranchMessage,
|
||||
map[string]string{
|
||||
"selectedBranchName": branches[0].Name,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
prompt += "\n\n" + self.c.Tr.ForceDeleteBranchesMessage
|
||||
}
|
||||
}
|
||||
|
||||
self.c.Confirm(types.ConfirmOpts{
|
||||
@@ -162,24 +130,18 @@ func (self *BranchesHelper) ConfirmLocalAndRemoteDelete(branches []*models.Branc
|
||||
Prompt: prompt,
|
||||
HandleConfirm: func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(task gocui.Task) error {
|
||||
// Delete the remote branches first so that we keep the local ones
|
||||
// Delete the remote branch first so that we keep the local one
|
||||
// in case of failure
|
||||
remoteBranches := lo.Map(branches, func(branch *models.Branch, _ int) *models.RemoteBranch {
|
||||
return &models.RemoteBranch{Name: branch.UpstreamBranch, RemoteName: branch.UpstreamRemote}
|
||||
})
|
||||
if err := self.deleteRemoteBranches(remoteBranches, task); err != nil {
|
||||
self.c.LogAction(self.c.Tr.Actions.DeleteRemoteBranch)
|
||||
if err := self.c.Git().Remote.DeleteRemoteBranch(task, branch.UpstreamRemote, branch.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
self.c.LogAction(self.c.Tr.Actions.DeleteLocalBranch)
|
||||
branchNames := lo.Map(branches, func(branch *models.Branch, _ int) string { return branch.Name })
|
||||
if err := self.c.Git().Branch.LocalDelete(branchNames, true); err != nil {
|
||||
if err := self.c.Git().Branch.LocalDelete(branch.Name, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
selectionStart, _ := self.c.Contexts().Branches.GetSelectionRange()
|
||||
self.c.Contexts().Branches.SetSelectedLineIdx(selectionStart)
|
||||
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}})
|
||||
})
|
||||
},
|
||||
@@ -236,30 +198,3 @@ func (self *BranchesHelper) promptWorktreeBranchDelete(selectedBranch *models.Br
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (self *BranchesHelper) allBranchesMerged(branches []*models.Branch) (bool, error) {
|
||||
allBranchesMerged := true
|
||||
for _, branch := range branches {
|
||||
isMerged, err := self.c.Git().Branch.IsBranchMerged(branch, self.c.Model().MainBranches)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !isMerged {
|
||||
allBranchesMerged = false
|
||||
break
|
||||
}
|
||||
}
|
||||
return allBranchesMerged, nil
|
||||
}
|
||||
|
||||
func (self *BranchesHelper) deleteRemoteBranches(remoteBranches []*models.RemoteBranch, task gocui.Task) error {
|
||||
remotes := lo.GroupBy(remoteBranches, func(branch *models.RemoteBranch) string { return branch.RemoteName })
|
||||
for remote, branches := range remotes {
|
||||
self.c.LogAction(self.c.Tr.Actions.DeleteRemoteBranch)
|
||||
branchNames := lo.Map(branches, func(branch *models.RemoteBranch, _ int) string { return branch.Name })
|
||||
if err := self.c.Git().Remote.DeleteRemoteBranch(task, remote, branchNames); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -143,7 +143,6 @@ func (self *CommitsHelper) OpenCommitMessagePanel(opts *OpenCommitMessagePanelOp
|
||||
opts.SummaryTitle,
|
||||
opts.DescriptionTitle,
|
||||
opts.PreserveMessage,
|
||||
opts.InitialMessage,
|
||||
onConfirm,
|
||||
opts.OnSwitchToEditor,
|
||||
)
|
||||
@@ -178,9 +177,8 @@ func (self *CommitsHelper) HandleCommitConfirm() error {
|
||||
func (self *CommitsHelper) CloseCommitMessagePanel() {
|
||||
if self.c.Contexts().CommitMessage.GetPreserveMessage() {
|
||||
message := self.JoinCommitMessageAndUnwrappedDescription()
|
||||
if message != self.c.Contexts().CommitMessage.GetInitialMessage() {
|
||||
self.c.Contexts().CommitMessage.SetPreservedMessage(message)
|
||||
}
|
||||
|
||||
self.c.Contexts().CommitMessage.SetPreservedMessage(message)
|
||||
} else {
|
||||
self.SetMessageAndDescriptionInView("")
|
||||
}
|
||||
|
||||
@@ -3,11 +3,13 @@ package helpers
|
||||
import (
|
||||
goContext "context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/mattn/go-runewidth"
|
||||
)
|
||||
|
||||
type ConfirmationHelper struct {
|
||||
@@ -56,9 +58,61 @@ func (self *ConfirmationHelper) DeactivateConfirmationPrompt() {
|
||||
self.clearConfirmationViewKeyBindings()
|
||||
}
|
||||
|
||||
// Temporary hack: we're just duplicating the logic in `gocui.lineWrap`
|
||||
func getMessageHeight(wrap bool, message string, width int) int {
|
||||
wrappedLines, _, _ := utils.WrapViewLinesToWidth(wrap, message, width)
|
||||
return len(wrappedLines)
|
||||
return len(wrapMessageToWidth(wrap, message, width))
|
||||
}
|
||||
|
||||
func wrapMessageToWidth(wrap bool, message string, width int) []string {
|
||||
lines := strings.Split(message, "\n")
|
||||
if !wrap {
|
||||
return lines
|
||||
}
|
||||
|
||||
wrappedLines := make([]string, 0, len(lines))
|
||||
|
||||
for _, line := range lines {
|
||||
n := 0
|
||||
offset := 0
|
||||
lastWhitespaceIndex := -1
|
||||
for i, currChr := range line {
|
||||
rw := runewidth.RuneWidth(currChr)
|
||||
n += rw
|
||||
|
||||
if n > width {
|
||||
if currChr == ' ' {
|
||||
wrappedLines = append(wrappedLines, line[offset:i])
|
||||
offset = i + 1
|
||||
n = 0
|
||||
} else if currChr == '-' {
|
||||
wrappedLines = append(wrappedLines, line[offset:i])
|
||||
offset = i
|
||||
n = rw
|
||||
} else if lastWhitespaceIndex != -1 && lastWhitespaceIndex+1 != i {
|
||||
if line[lastWhitespaceIndex] == '-' {
|
||||
wrappedLines = append(wrappedLines, line[offset:lastWhitespaceIndex+1])
|
||||
offset = lastWhitespaceIndex + 1
|
||||
n = i - lastWhitespaceIndex
|
||||
} else {
|
||||
wrappedLines = append(wrappedLines, line[offset:lastWhitespaceIndex])
|
||||
offset = lastWhitespaceIndex + 1
|
||||
n = i - lastWhitespaceIndex + 1
|
||||
}
|
||||
} else {
|
||||
wrappedLines = append(wrappedLines, line[offset:i])
|
||||
offset = i
|
||||
n = rw
|
||||
}
|
||||
lastWhitespaceIndex = -1
|
||||
} else if currChr == ' ' || currChr == '-' {
|
||||
lastWhitespaceIndex = i
|
||||
}
|
||||
}
|
||||
|
||||
wrappedLines = append(wrappedLines, line[offset:])
|
||||
}
|
||||
|
||||
return wrappedLines
|
||||
}
|
||||
|
||||
func (self *ConfirmationHelper) getPopupPanelDimensionsForContentHeight(panelWidth, contentHeight int, parentPopupContext types.Context) (int, int, int, int) {
|
||||
@@ -103,12 +157,14 @@ func (self *ConfirmationHelper) getPopupPanelWidth() int {
|
||||
func (self *ConfirmationHelper) prepareConfirmationPanel(
|
||||
opts types.ConfirmOpts,
|
||||
) {
|
||||
self.c.Views().Confirmation.Title = opts.Title
|
||||
self.c.Contexts().Confirmation.State.Multiline = opts.Multiline
|
||||
confirmationView := self.c.Views().Confirmation
|
||||
confirmationView.Title = opts.Title
|
||||
// for now we do not support wrapping in our editor
|
||||
self.c.Views().Confirmation.Wrap = !opts.Editable
|
||||
self.c.Views().Confirmation.FgColor = theme.GocuiDefaultTextColor
|
||||
self.c.Views().Confirmation.Mask = runeForMask(opts.Mask)
|
||||
self.c.Views().Confirmation.SetOrigin(0, 0)
|
||||
confirmationView.Wrap = !opts.Editable
|
||||
confirmationView.FgColor = theme.GocuiDefaultTextColor
|
||||
confirmationView.Mask = runeForMask(opts.Mask)
|
||||
confirmationView.SetOrigin(0, 0)
|
||||
|
||||
suggestionsContext := self.c.Contexts().Suggestions
|
||||
suggestionsContext.State.FindSuggestions = opts.FindSuggestionsFunc
|
||||
@@ -153,6 +209,7 @@ func (self *ConfirmationHelper) CreatePopupPanel(ctx goContext.Context, opts typ
|
||||
self.prepareConfirmationPanel(
|
||||
types.ConfirmOpts{
|
||||
Title: opts.Title,
|
||||
Multiline: opts.Multiline,
|
||||
Prompt: opts.Prompt,
|
||||
FindSuggestionsFunc: opts.FindSuggestionsFunc,
|
||||
Editable: opts.Editable,
|
||||
@@ -160,6 +217,14 @@ func (self *ConfirmationHelper) CreatePopupPanel(ctx goContext.Context, opts typ
|
||||
})
|
||||
confirmationView := self.c.Views().Confirmation
|
||||
confirmationView.Editable = opts.Editable
|
||||
if opts.Multiline {
|
||||
confirmationView.Subtitle = utils.ResolvePlaceholderString(
|
||||
self.c.Tr.PressToSubmit,
|
||||
map[string]string{"Key": self.c.UserConfig().Keybinding.Universal.ConfirmInEditor},
|
||||
)
|
||||
} else {
|
||||
confirmationView.Subtitle = ""
|
||||
}
|
||||
|
||||
if opts.Editable {
|
||||
textArea := confirmationView.TextArea
|
||||
@@ -276,7 +341,7 @@ func (self *ConfirmationHelper) layoutMenuPrompt(contentWidth int) int {
|
||||
var promptLines []string
|
||||
prompt := self.c.Contexts().Menu.GetPrompt()
|
||||
if len(prompt) > 0 {
|
||||
promptLines, _, _ = utils.WrapViewLinesToWidth(true, prompt, contentWidth)
|
||||
promptLines = wrapMessageToWidth(true, prompt, contentWidth)
|
||||
promptLines = append(promptLines, "")
|
||||
}
|
||||
self.c.Contexts().Menu.SetPromptLines(promptLines)
|
||||
@@ -304,14 +369,22 @@ func (self *ConfirmationHelper) resizeConfirmationPanel(parentPopupContext types
|
||||
suggestionsViewHeight = 11
|
||||
}
|
||||
panelWidth := self.getPopupPanelWidth()
|
||||
contentWidth := panelWidth - 2 // minus 2 for the frame
|
||||
prompt := self.c.Views().Confirmation.Buffer()
|
||||
wrap := true
|
||||
if self.c.Views().Confirmation.Editable {
|
||||
prompt = self.c.Views().Confirmation.TextArea.GetContent()
|
||||
wrap = false
|
||||
}
|
||||
panelHeight := getMessageHeight(wrap, prompt, contentWidth) + suggestionsViewHeight
|
||||
|
||||
multiline := self.c.Contexts().Confirmation.State.Multiline
|
||||
inputHeight := getMessageHeight(wrap, prompt, panelWidth)
|
||||
|
||||
minMultilineHeight := 5
|
||||
if multiline && inputHeight < minMultilineHeight {
|
||||
inputHeight = minMultilineHeight
|
||||
}
|
||||
|
||||
panelHeight := inputHeight + suggestionsViewHeight
|
||||
x0, y0, x1, y1 := self.getPopupPanelDimensionsAux(panelWidth, panelHeight, parentPopupContext)
|
||||
confirmationViewBottom := y1 - suggestionsViewHeight
|
||||
_, _ = self.c.GocuiGui().SetView(self.c.Views().Confirmation.Name(), x0, y0, x1, confirmationViewBottom, 0)
|
||||
|
||||
@@ -34,6 +34,10 @@ func (self *DiffHelper) DiffArgs() []string {
|
||||
output = append(output, "-R")
|
||||
}
|
||||
|
||||
if self.c.GetAppState().IgnoreWhitespaceInDiffView {
|
||||
output = append(output, "--ignore-all-space")
|
||||
}
|
||||
|
||||
output = append(output, "--")
|
||||
|
||||
file := self.currentlySelectedFilename()
|
||||
@@ -55,6 +59,9 @@ func (self *DiffHelper) GetUpdateTaskForRenderingCommitsDiff(commit *models.Comm
|
||||
if refRange != nil {
|
||||
from, to := refRange.From, refRange.To
|
||||
args := []string{from.ParentRefName(), to.RefName(), "--stat", "-p"}
|
||||
if self.c.GetAppState().IgnoreWhitespaceInDiffView {
|
||||
args = append(args, "--ignore-all-space")
|
||||
}
|
||||
args = append(args, "--")
|
||||
if path := self.c.Modes().Filtering.GetPath(); path != "" {
|
||||
args = append(args, path)
|
||||
|
||||
@@ -91,7 +91,7 @@ func (self *PatchBuildingHelper) RefreshPatchBuildingPanel(opts types.OnFocusOpt
|
||||
|
||||
oldState := context.GetState()
|
||||
|
||||
state := patch_exploring.NewState(diff, selectedLineIdx, context.GetView(), oldState)
|
||||
state := patch_exploring.NewState(diff, selectedLineIdx, oldState, self.c.Log)
|
||||
context.SetState(state)
|
||||
if state == nil {
|
||||
self.Escape()
|
||||
|
||||
@@ -23,7 +23,7 @@ func NewSnakeHelper(c *HelperCommon) *SnakeHelper {
|
||||
func (self *SnakeHelper) StartGame() {
|
||||
view := self.c.Views().Snake
|
||||
|
||||
game := snake.NewGame(view.InnerWidth(), view.InnerHeight(), self.renderSnakeGame, self.c.LogAction)
|
||||
game := snake.NewGame(view.Width(), view.Height(), self.renderSnakeGame, self.c.LogAction)
|
||||
self.game = game
|
||||
game.Start()
|
||||
}
|
||||
|
||||
@@ -63,11 +63,11 @@ func (self *StagingHelper) RefreshStagingPanel(focusOpts types.OnFocusOpts) {
|
||||
secondaryContext.GetMutex().Lock()
|
||||
|
||||
mainContext.SetState(
|
||||
patch_exploring.NewState(mainDiff, mainSelectedLineIdx, mainContext.GetView(), mainContext.GetState()),
|
||||
patch_exploring.NewState(mainDiff, mainSelectedLineIdx, mainContext.GetState(), self.c.Log),
|
||||
)
|
||||
|
||||
secondaryContext.SetState(
|
||||
patch_exploring.NewState(secondaryDiff, secondarySelectedLineIdx, secondaryContext.GetView(), secondaryContext.GetState()),
|
||||
patch_exploring.NewState(secondaryDiff, secondarySelectedLineIdx, secondaryContext.GetState(), self.c.Log),
|
||||
)
|
||||
|
||||
mainState := mainContext.GetState()
|
||||
|
||||
@@ -162,26 +162,10 @@ func (self *SuggestionsHelper) getRemoteBranchNames(separator string) []string {
|
||||
})
|
||||
}
|
||||
|
||||
func (self *SuggestionsHelper) getRemoteBranchNamesForRemote(remoteName string) []string {
|
||||
remote, ok := lo.Find(self.c.Model().Remotes, func(remote *models.Remote) bool {
|
||||
return remote.Name == remoteName
|
||||
})
|
||||
if ok {
|
||||
return lo.Map(remote.Branches, func(branch *models.RemoteBranch, _ int) string {
|
||||
return branch.Name
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SuggestionsHelper) GetRemoteBranchesSuggestionsFunc(separator string) func(string) []*types.Suggestion {
|
||||
return FilterFunc(self.getRemoteBranchNames(separator), self.c.UserConfig().Gui.UseFuzzySearch())
|
||||
}
|
||||
|
||||
func (self *SuggestionsHelper) GetRemoteBranchesForRemoteSuggestionsFunc(remoteName string) func(string) []*types.Suggestion {
|
||||
return FilterFunc(self.getRemoteBranchNamesForRemote(remoteName), self.c.UserConfig().Gui.UseFuzzySearch())
|
||||
}
|
||||
|
||||
func (self *SuggestionsHelper) getTagNames() []string {
|
||||
return lo.Map(self.c.Model().Tags, func(tag *models.Tag, _ int) string {
|
||||
return tag.Name
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"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/context/traits"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
@@ -116,7 +115,7 @@ func (self *LocalCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(editCommitKey),
|
||||
Handler: self.withItemsRange(self.edit),
|
||||
Handler: self.withItems(self.edit),
|
||||
GetDisabledReason: self.require(
|
||||
self.itemRangeSelected(self.midRebaseCommandEnabled),
|
||||
),
|
||||
@@ -497,17 +496,12 @@ func (self *LocalCommitsController) drop(selectedCommits []*models.Commit, start
|
||||
return self.updateTodos(todo.Drop, selectedCommits)
|
||||
}
|
||||
|
||||
isMerge := selectedCommits[0].IsMerge()
|
||||
|
||||
self.c.Confirm(types.ConfirmOpts{
|
||||
Title: self.c.Tr.DropCommitTitle,
|
||||
Prompt: lo.Ternary(isMerge, self.c.Tr.DropMergeCommitPrompt, self.c.Tr.DropCommitPrompt),
|
||||
Prompt: self.c.Tr.DropCommitPrompt,
|
||||
HandleConfirm: func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.DroppingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.DropCommit)
|
||||
if isMerge {
|
||||
return self.dropMergeCommit(startIdx)
|
||||
}
|
||||
return self.interactiveRebase(todo.Drop, startIdx, endIdx)
|
||||
})
|
||||
},
|
||||
@@ -516,30 +510,11 @@ func (self *LocalCommitsController) drop(selectedCommits []*models.Commit, start
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) dropMergeCommit(commitIdx int) error {
|
||||
err := self.c.Git().Rebase.DropMergeCommit(self.c.Model().Commits, commitIdx)
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) edit(selectedCommits []*models.Commit, startIdx int, endIdx int) error {
|
||||
func (self *LocalCommitsController) edit(selectedCommits []*models.Commit) error {
|
||||
if self.isRebasing() {
|
||||
return self.updateTodos(todo.Edit, selectedCommits)
|
||||
}
|
||||
|
||||
commits := self.c.Model().Commits
|
||||
if !commits[endIdx].IsMerge() {
|
||||
selectionRangeAndMode := self.getSelectionRangeAndMode()
|
||||
err := self.c.Git().Rebase.InteractiveRebase(commits, startIdx, endIdx, todo.Edit)
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptions(
|
||||
err,
|
||||
types.RefreshOptions{
|
||||
Mode: types.BLOCK_UI, Then: func() error {
|
||||
self.restoreSelectionRangeAndMode(selectionRangeAndMode)
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return self.startInteractiveRebaseWithEdit(selectedCommits)
|
||||
}
|
||||
|
||||
@@ -557,7 +532,10 @@ func (self *LocalCommitsController) startInteractiveRebaseWithEdit(
|
||||
) error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.EditCommit)
|
||||
selectionRangeAndMode := self.getSelectionRangeAndMode()
|
||||
selectedIdx, rangeStartIdx, rangeSelectMode := self.context().GetSelectionRangeAndMode()
|
||||
commits := self.c.Model().Commits
|
||||
selectedHash := commits[selectedIdx].Hash
|
||||
rangeStartHash := commits[rangeStartIdx].Hash
|
||||
err := self.c.Git().Rebase.EditRebase(commitsToEdit[len(commitsToEdit)-1].Hash)
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptions(
|
||||
err,
|
||||
@@ -576,41 +554,23 @@ func (self *LocalCommitsController) startInteractiveRebaseWithEdit(
|
||||
}
|
||||
}
|
||||
|
||||
self.restoreSelectionRangeAndMode(selectionRangeAndMode)
|
||||
// We need to select the same commit range 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 selected commits may be in different positions in the list.
|
||||
_, newSelectedIdx, ok1 := lo.FindIndexOf(self.c.Model().Commits, func(c *models.Commit) bool {
|
||||
return c.Hash == selectedHash
|
||||
})
|
||||
_, newRangeStartIdx, ok2 := lo.FindIndexOf(self.c.Model().Commits, func(c *models.Commit) bool {
|
||||
return c.Hash == rangeStartHash
|
||||
})
|
||||
if ok1 && ok2 {
|
||||
self.context().SetSelectionRangeAndMode(newSelectedIdx, newRangeStartIdx, rangeSelectMode)
|
||||
}
|
||||
return nil
|
||||
}})
|
||||
})
|
||||
}
|
||||
|
||||
type SelectionRangeAndMode struct {
|
||||
selectedHash string
|
||||
rangeStartHash string
|
||||
mode traits.RangeSelectMode
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) getSelectionRangeAndMode() SelectionRangeAndMode {
|
||||
selectedIdx, rangeStartIdx, rangeSelectMode := self.context().GetSelectionRangeAndMode()
|
||||
commits := self.c.Model().Commits
|
||||
selectedHash := commits[selectedIdx].Hash
|
||||
rangeStartHash := commits[rangeStartIdx].Hash
|
||||
return SelectionRangeAndMode{selectedHash, rangeStartHash, rangeSelectMode}
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) restoreSelectionRangeAndMode(selectionRangeAndMode SelectionRangeAndMode) {
|
||||
// We need to select the same commit range 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 selected commits may be in different positions in the list.
|
||||
_, newSelectedIdx, ok1 := lo.FindIndexOf(self.c.Model().Commits, func(c *models.Commit) bool {
|
||||
return c.Hash == selectionRangeAndMode.selectedHash
|
||||
})
|
||||
_, newRangeStartIdx, ok2 := lo.FindIndexOf(self.c.Model().Commits, func(c *models.Commit) bool {
|
||||
return c.Hash == selectionRangeAndMode.rangeStartHash
|
||||
})
|
||||
if ok1 && ok2 {
|
||||
self.context().SetSelectionRangeAndMode(newSelectedIdx, newRangeStartIdx, selectionRangeAndMode.mode)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@@ -1282,11 +1242,6 @@ func (self *LocalCommitsController) handleOpenLogMenu() error {
|
||||
OnPress: onPress("author-date-order"),
|
||||
Widget: types.MakeMenuRadioButton(currentValue == "author-date-order"),
|
||||
},
|
||||
{
|
||||
Label: "default",
|
||||
OnPress: onPress("default"),
|
||||
Widget: types.MakeMenuRadioButton(currentValue == "default"),
|
||||
},
|
||||
},
|
||||
})
|
||||
},
|
||||
@@ -1368,15 +1323,11 @@ func (self *LocalCommitsController) canFindCommitForSquashFixupsInCurrentBranch(
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) canSquashOrFixup(selectedCommits []*models.Commit, startIdx int, endIdx int) *types.DisabledReason {
|
||||
func (self *LocalCommitsController) canSquashOrFixup(_selectedCommits []*models.Commit, startIdx int, endIdx int) *types.DisabledReason {
|
||||
if endIdx >= len(self.c.Model().Commits)-1 {
|
||||
return &types.DisabledReason{Text: self.c.Tr.CannotSquashOrFixupFirstCommit}
|
||||
}
|
||||
|
||||
if lo.SomeBy(selectedCommits, func(c *models.Commit) bool { return c.IsMerge() }) {
|
||||
return &types.DisabledReason{Text: self.c.Tr.CannotSquashOrFixupMergeCommit}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1434,10 +1385,6 @@ func (self *LocalCommitsController) midRebaseCommandEnabled(selectedCommits []*m
|
||||
// Ensures that if we are mid-rebase, we're only selecting commits that can be moved
|
||||
func (self *LocalCommitsController) midRebaseMoveCommandEnabled(selectedCommits []*models.Commit, startIdx int, endIdx int) *types.DisabledReason {
|
||||
if !self.isRebasing() {
|
||||
if lo.SomeBy(selectedCommits, func(c *models.Commit) bool { return c.IsMerge() }) {
|
||||
return &types.DisabledReason{Text: self.c.Tr.CannotMoveMergeCommit}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1458,10 +1405,6 @@ func (self *LocalCommitsController) midRebaseMoveCommandEnabled(selectedCommits
|
||||
|
||||
func (self *LocalCommitsController) canDropCommits(selectedCommits []*models.Commit, startIdx int, endIdx int) *types.DisabledReason {
|
||||
if !self.isRebasing() {
|
||||
if len(selectedCommits) > 1 && lo.SomeBy(selectedCommits, func(c *models.Commit) bool { return c.IsMerge() }) {
|
||||
return &types.DisabledReason{Text: self.c.Tr.DroppingMergeRequiresSingleSelection}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ func (self *PatchBuildingController) GetMouseKeybindings(opts types.KeybindingsO
|
||||
func (self *PatchBuildingController) GetOnFocus() func(types.OnFocusOpts) {
|
||||
return func(opts types.OnFocusOpts) {
|
||||
// no need to change wrap on the secondary view because it can't be interacted with
|
||||
self.c.Views().PatchBuilding.Wrap = self.c.UserConfig().Gui.WrapLinesInStagingView
|
||||
self.c.Views().PatchBuilding.Wrap = false
|
||||
|
||||
self.c.Helpers().PatchBuilding.RefreshPatchBuildingPanel(opts)
|
||||
}
|
||||
@@ -73,8 +73,6 @@ func (self *PatchBuildingController) GetOnFocus() func(types.OnFocusOpts) {
|
||||
|
||||
func (self *PatchBuildingController) GetOnFocusLost() func(types.OnFocusLostOpts) {
|
||||
return func(opts types.OnFocusLostOpts) {
|
||||
self.context().SetState(nil)
|
||||
|
||||
self.c.Views().PatchBuilding.Wrap = true
|
||||
|
||||
if self.c.Git().Patch.PatchBuilder.IsEmpty() {
|
||||
@@ -136,13 +134,13 @@ func (self *PatchBuildingController) toggleSelection() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currentLineIsStaged := lo.Contains(includedLineIndices, state.GetSelectedPatchLineIdx())
|
||||
currentLineIsStaged := lo.Contains(includedLineIndices, state.GetSelectedLineIdx())
|
||||
if currentLineIsStaged {
|
||||
toggleFunc = self.c.Git().Patch.PatchBuilder.RemoveFileLineRange
|
||||
}
|
||||
|
||||
// add range of lines to those set for the file
|
||||
firstLineIdx, lastLineIdx := state.SelectedPatchRange()
|
||||
firstLineIdx, lastLineIdx := state.SelectedRange()
|
||||
|
||||
if err := toggleFunc(filename, firstLineIdx, lastLineIdx); err != nil {
|
||||
// might actually want to return an error here
|
||||
|
||||
@@ -170,9 +170,9 @@ func (self *PatchExplorerController) GetMouseKeybindings(opts types.KeybindingsO
|
||||
}
|
||||
|
||||
func (self *PatchExplorerController) HandlePrevLine() error {
|
||||
before := self.context.GetState().GetSelectedViewLineIdx()
|
||||
before := self.context.GetState().GetSelectedLineIdx()
|
||||
self.context.GetState().CycleSelection(false)
|
||||
after := self.context.GetState().GetSelectedViewLineIdx()
|
||||
after := self.context.GetState().GetSelectedLineIdx()
|
||||
|
||||
if self.context.GetState().SelectingLine() {
|
||||
checkScrollUp(self.context.GetViewTrait(), self.c.UserConfig(), before, after)
|
||||
@@ -182,9 +182,9 @@ func (self *PatchExplorerController) HandlePrevLine() error {
|
||||
}
|
||||
|
||||
func (self *PatchExplorerController) HandleNextLine() error {
|
||||
before := self.context.GetState().GetSelectedViewLineIdx()
|
||||
before := self.context.GetState().GetSelectedLineIdx()
|
||||
self.context.GetState().CycleSelection(true)
|
||||
after := self.context.GetState().GetSelectedViewLineIdx()
|
||||
after := self.context.GetState().GetSelectedLineIdx()
|
||||
|
||||
if self.context.GetState().SelectingLine() {
|
||||
checkScrollDown(self.context.GetViewTrait(), self.c.UserConfig(), before, after)
|
||||
|
||||
@@ -66,8 +66,8 @@ func (self *RemoteBranchesController) GetKeybindings(opts types.KeybindingsOpts)
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Remove),
|
||||
Handler: self.withItems(self.delete),
|
||||
GetDisabledReason: self.require(self.itemRangeSelected()),
|
||||
Handler: self.withItem(self.delete),
|
||||
GetDisabledReason: self.require(self.singleItemSelected()),
|
||||
Description: self.c.Tr.Delete,
|
||||
Tooltip: self.c.Tr.DeleteRemoteBranchTooltip,
|
||||
DisplayOnScreen: true,
|
||||
@@ -132,8 +132,8 @@ func (self *RemoteBranchesController) context() *context.RemoteBranchesContext {
|
||||
return self.c.Contexts().RemoteBranches
|
||||
}
|
||||
|
||||
func (self *RemoteBranchesController) delete(selectedBranches []*models.RemoteBranch) error {
|
||||
return self.c.Helpers().BranchesHelper.ConfirmDeleteRemote(selectedBranches)
|
||||
func (self *RemoteBranchesController) delete(selectedBranch *models.RemoteBranch) error {
|
||||
return self.c.Helpers().BranchesHelper.ConfirmDeleteRemote(selectedBranch.RemoteName, selectedBranch.Name)
|
||||
}
|
||||
|
||||
func (self *RemoteBranchesController) merge(selectedBranch *models.RemoteBranch) error {
|
||||
|
||||
@@ -118,9 +118,8 @@ func (self *StagingController) GetMouseKeybindings(opts types.KeybindingsOpts) [
|
||||
|
||||
func (self *StagingController) GetOnFocus() func(types.OnFocusOpts) {
|
||||
return func(opts types.OnFocusOpts) {
|
||||
wrap := self.c.UserConfig().Gui.WrapLinesInStagingView
|
||||
self.c.Views().Staging.Wrap = wrap
|
||||
self.c.Views().StagingSecondary.Wrap = wrap
|
||||
self.c.Views().Staging.Wrap = false
|
||||
self.c.Views().StagingSecondary.Wrap = false
|
||||
|
||||
self.c.Helpers().Staging.RefreshStagingPanel(opts)
|
||||
}
|
||||
@@ -221,7 +220,7 @@ func (self *StagingController) applySelection(reverse bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
firstLineIdx, lastLineIdx := state.SelectedPatchRange()
|
||||
firstLineIdx, lastLineIdx := state.SelectedRange()
|
||||
patchToApply := patch.
|
||||
Parse(state.GetDiff()).
|
||||
Transform(patch.TransformOpts{
|
||||
@@ -250,7 +249,7 @@ func (self *StagingController) applySelection(reverse bool) error {
|
||||
}
|
||||
|
||||
if state.SelectingRange() {
|
||||
firstLine, _ := state.SelectedViewRange()
|
||||
firstLine, _ := state.SelectedRange()
|
||||
state.SelectLine(firstLine)
|
||||
}
|
||||
|
||||
@@ -291,7 +290,7 @@ func (self *StagingController) editHunk() error {
|
||||
}
|
||||
|
||||
lineOffset := 3
|
||||
lineIdxInHunk := state.GetSelectedPatchLineIdx() - hunkStartIdx
|
||||
lineIdxInHunk := state.GetSelectedLineIdx() - hunkStartIdx
|
||||
if err := self.c.Helpers().Files.EditFileAtLineAndWait(patchFilepath, lineIdxInHunk+lineOffset); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -169,7 +169,7 @@ func (self *FilesController) animateExplosion() {
|
||||
// Animates an explosion within the view by drawing a bunch of flamey characters
|
||||
func (self *FilesController) Explode(v *gocui.View, onDone func()) {
|
||||
width := v.InnerWidth()
|
||||
height := v.InnerHeight()
|
||||
height := v.InnerHeight() + 1
|
||||
styles := []style.TextStyle{
|
||||
style.FgLightWhite.SetBold(),
|
||||
style.FgYellow.SetBold(),
|
||||
|
||||
@@ -108,6 +108,8 @@ type Gui struct {
|
||||
|
||||
PopupHandler types.IPopupHandler
|
||||
|
||||
IsNewRepo bool
|
||||
|
||||
IsRefreshingFiles bool
|
||||
|
||||
// we use this to decide whether we'll return to the original directory that
|
||||
|
||||
@@ -30,7 +30,7 @@ func (gui *Gui) handleInfoClick() error {
|
||||
view := gui.Views.Information
|
||||
|
||||
cx, _ := view.Cursor()
|
||||
width := view.Width()
|
||||
width, _ := view.Size()
|
||||
|
||||
if activeMode, ok := gui.helpers.Mode.GetActiveMode(); ok {
|
||||
if width-cx > utils.StringWidth(gui.c.Tr.ResetInParentheses) {
|
||||
|
||||
+12
-6
@@ -193,6 +193,18 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
|
||||
Tooltip: self.c.Tr.OpenCommandLogMenuTooltip,
|
||||
OpensMenu: true,
|
||||
},
|
||||
{
|
||||
ViewName: "secondary",
|
||||
Key: gocui.MouseWheelUp,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: self.scrollUpSecondary,
|
||||
},
|
||||
{
|
||||
ViewName: "secondary",
|
||||
Key: gocui.MouseWheelDown,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: self.scrollDownSecondary,
|
||||
},
|
||||
{
|
||||
ViewName: "main",
|
||||
Key: gocui.MouseWheelDown,
|
||||
@@ -207,12 +219,6 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
|
||||
Description: self.c.Tr.ScrollUp,
|
||||
Alternative: "fn+down",
|
||||
},
|
||||
{
|
||||
ViewName: "secondary",
|
||||
Key: gocui.MouseWheelDown,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: self.scrollDownSecondary,
|
||||
},
|
||||
{
|
||||
ViewName: "secondary",
|
||||
Key: gocui.MouseWheelUp,
|
||||
|
||||
+8
-6
@@ -30,8 +30,8 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
// reading more lines into main view buffers upon resize
|
||||
prevMainView := gui.Views.Main
|
||||
if prevMainView != nil {
|
||||
prevMainHeight := prevMainView.Height()
|
||||
newMainHeight := viewDimensions["main"].Y1 - viewDimensions["main"].Y0 + 1
|
||||
_, prevMainHeight := prevMainView.Size()
|
||||
newMainHeight := viewDimensions["main"].Y1 - viewDimensions["main"].Y0 - 1
|
||||
heightDiff := newMainHeight - prevMainHeight
|
||||
if heightDiff > 0 {
|
||||
if manager, ok := gui.viewBufferManagerMap["main"]; ok {
|
||||
@@ -87,15 +87,17 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
}
|
||||
}
|
||||
if context.NeedsRerenderOnWidthChange() == types.NEEDS_RERENDER_ON_WIDTH_CHANGE_WHEN_WIDTH_CHANGES {
|
||||
oldWidth := view.Width()
|
||||
newWidth := dimensionsObj.X1 - dimensionsObj.X0 + 1
|
||||
// view.Width() returns the width -1 for some reason
|
||||
oldWidth := view.Width() + 1
|
||||
newWidth := dimensionsObj.X1 - dimensionsObj.X0 + 2*frameOffset
|
||||
if oldWidth != newWidth {
|
||||
mustRerender = true
|
||||
}
|
||||
}
|
||||
if context.NeedsRerenderOnHeightChange() {
|
||||
oldHeight := view.Height()
|
||||
newHeight := dimensionsObj.Y1 - dimensionsObj.Y0 + 1
|
||||
// view.Height() returns the height -1 for some reason
|
||||
oldHeight := view.Height() + 1
|
||||
newHeight := dimensionsObj.Y1 - dimensionsObj.Y0 + 2*frameOffset
|
||||
if oldHeight != newHeight {
|
||||
mustRerender = true
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ func (self *OptionsMapMgr) renderContextOptionsMap() {
|
||||
}
|
||||
|
||||
func (self *OptionsMapMgr) formatBindingInfos(bindingInfos []bindingInfo) string {
|
||||
width := self.c.Views().Options.InnerWidth() - 2 // -2 for some padding
|
||||
width := self.c.Views().Options.Width() - 4 // -4 for the padding
|
||||
var builder strings.Builder
|
||||
ellipsis := "…"
|
||||
separator := " | "
|
||||
|
||||
@@ -11,8 +11,8 @@ func calculateOrigin(currentOrigin int, bufferHeight int, numLines int, firstLin
|
||||
// is as close to being in view as possible.
|
||||
func calculateNewOriginWithNeededAndWantedIdx(currentOrigin int, bufferHeight int, numLines int, needToSeeIdx int, wantToSeeIdx int) int {
|
||||
origin := currentOrigin
|
||||
if needToSeeIdx < currentOrigin || needToSeeIdx >= currentOrigin+bufferHeight {
|
||||
origin = max(min(needToSeeIdx-bufferHeight/2, numLines-bufferHeight), 0)
|
||||
if needToSeeIdx < currentOrigin || needToSeeIdx > currentOrigin+bufferHeight {
|
||||
origin = max(min(needToSeeIdx-bufferHeight/2, numLines-bufferHeight-1), 0)
|
||||
}
|
||||
|
||||
bottom := origin + bufferHeight
|
||||
@@ -21,7 +21,7 @@ func calculateNewOriginWithNeededAndWantedIdx(currentOrigin int, bufferHeight in
|
||||
requiredChange := origin - wantToSeeIdx
|
||||
allowedChange := bottom - needToSeeIdx
|
||||
return origin - min(requiredChange, allowedChange)
|
||||
} else if wantToSeeIdx >= bottom {
|
||||
} else if wantToSeeIdx > origin+bufferHeight {
|
||||
requiredChange := wantToSeeIdx - bottom
|
||||
allowedChange := needToSeeIdx - origin
|
||||
return origin + min(requiredChange, allowedChange)
|
||||
|
||||
@@ -62,7 +62,7 @@ func TestNewOrigin(t *testing.T) {
|
||||
lastLineIdx: 199,
|
||||
selectedLineIdx: 199,
|
||||
selectMode: LINE,
|
||||
expected: 100,
|
||||
expected: 99,
|
||||
},
|
||||
{
|
||||
name: "selection within scroll window",
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
package patch_exploring
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/generics/set"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/patch"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// State represents the current state of the patch explorer context i.e. when
|
||||
// you're staging a file or you're building a patch from an existing commit
|
||||
// this struct holds the info about the diff you're interacting with and what's currently selected.
|
||||
type State struct {
|
||||
// These are in terms of view lines (wrapped), not patch lines
|
||||
selectedLineIdx int
|
||||
rangeStartLineIdx int
|
||||
// If a range is sticky, it means we expand the range when we move up or down.
|
||||
@@ -22,11 +18,6 @@ type State struct {
|
||||
diff string
|
||||
patch *patch.Patch
|
||||
selectMode selectMode
|
||||
|
||||
// Array of indices of the wrapped lines indexed by a patch line index
|
||||
viewLineIndices []int
|
||||
// Array of indices of the original patch lines indexed by a wrapped view line index
|
||||
patchLineIndices []int
|
||||
}
|
||||
|
||||
// these represent what select mode we're in
|
||||
@@ -38,7 +29,7 @@ const (
|
||||
HUNK
|
||||
)
|
||||
|
||||
func NewState(diff string, selectedLineIdx int, view *gocui.View, oldState *State) *State {
|
||||
func NewState(diff string, selectedLineIdx int, oldState *State, log *logrus.Entry) *State {
|
||||
if oldState != nil && diff == oldState.diff && selectedLineIdx == -1 {
|
||||
// if we're here then we can return the old state. If selectedLineIdx was not -1
|
||||
// then that would mean we were trying to click and potentiall drag a range, which
|
||||
@@ -52,8 +43,6 @@ func NewState(diff string, selectedLineIdx int, view *gocui.View, oldState *Stat
|
||||
return nil
|
||||
}
|
||||
|
||||
viewLineIndices, patchLineIndices := wrapPatchLines(diff, view)
|
||||
|
||||
rangeStartLineIdx := 0
|
||||
if oldState != nil {
|
||||
rangeStartLineIdx = oldState.rangeStartLineIdx
|
||||
@@ -62,10 +51,6 @@ func NewState(diff string, selectedLineIdx int, view *gocui.View, oldState *Stat
|
||||
selectMode := LINE
|
||||
// if we have clicked from the outside to focus the main view we'll pass in a non-negative line index so that we can instantly select that line
|
||||
if selectedLineIdx >= 0 {
|
||||
// Clamp to the number of wrapped view lines; index might be out of
|
||||
// bounds if a custom pager is being used which produces more lines
|
||||
selectedLineIdx = min(selectedLineIdx, len(viewLineIndices)-1)
|
||||
|
||||
selectMode = RANGE
|
||||
rangeStartLineIdx = selectedLineIdx
|
||||
} else if oldState != nil {
|
||||
@@ -73,9 +58,9 @@ func NewState(diff string, selectedLineIdx int, view *gocui.View, oldState *Stat
|
||||
if oldState.selectMode == HUNK {
|
||||
selectMode = HUNK
|
||||
}
|
||||
selectedLineIdx = viewLineIndices[patch.GetNextChangeIdx(oldState.patchLineIndices[oldState.selectedLineIdx])]
|
||||
selectedLineIdx = patch.GetNextChangeIdx(oldState.selectedLineIdx)
|
||||
} else {
|
||||
selectedLineIdx = viewLineIndices[patch.GetNextChangeIdx(0)]
|
||||
selectedLineIdx = patch.GetNextChangeIdx(0)
|
||||
}
|
||||
|
||||
return &State{
|
||||
@@ -85,33 +70,10 @@ func NewState(diff string, selectedLineIdx int, view *gocui.View, oldState *Stat
|
||||
rangeStartLineIdx: rangeStartLineIdx,
|
||||
rangeIsSticky: false,
|
||||
diff: diff,
|
||||
viewLineIndices: viewLineIndices,
|
||||
patchLineIndices: patchLineIndices,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *State) OnViewWidthChanged(view *gocui.View) {
|
||||
if !view.Wrap {
|
||||
return
|
||||
}
|
||||
|
||||
selectedPatchLineIdx := s.patchLineIndices[s.selectedLineIdx]
|
||||
var rangeStartPatchLineIdx int
|
||||
if s.selectMode == RANGE {
|
||||
rangeStartPatchLineIdx = s.patchLineIndices[s.rangeStartLineIdx]
|
||||
}
|
||||
s.viewLineIndices, s.patchLineIndices = wrapPatchLines(s.diff, view)
|
||||
s.selectedLineIdx = s.viewLineIndices[selectedPatchLineIdx]
|
||||
if s.selectMode == RANGE {
|
||||
s.rangeStartLineIdx = s.viewLineIndices[rangeStartPatchLineIdx]
|
||||
}
|
||||
}
|
||||
|
||||
func (s *State) GetSelectedPatchLineIdx() int {
|
||||
return s.patchLineIndices[s.selectedLineIdx]
|
||||
}
|
||||
|
||||
func (s *State) GetSelectedViewLineIdx() int {
|
||||
func (s *State) GetSelectedLineIdx() int {
|
||||
return s.selectedLineIdx
|
||||
}
|
||||
|
||||
@@ -181,8 +143,8 @@ func (s *State) SelectLine(newSelectedLineIdx int) {
|
||||
func (s *State) selectLineWithoutRangeCheck(newSelectedLineIdx int) {
|
||||
if newSelectedLineIdx < 0 {
|
||||
newSelectedLineIdx = 0
|
||||
} else if newSelectedLineIdx > len(s.patchLineIndices)-1 {
|
||||
newSelectedLineIdx = len(s.patchLineIndices) - 1
|
||||
} else if newSelectedLineIdx > s.patch.LineCount()-1 {
|
||||
newSelectedLineIdx = s.patch.LineCount() - 1
|
||||
}
|
||||
|
||||
s.selectedLineIdx = newSelectedLineIdx
|
||||
@@ -216,12 +178,12 @@ func (s *State) CycleHunk(forward bool) {
|
||||
change = -1
|
||||
}
|
||||
|
||||
hunkIdx := s.patch.HunkContainingLine(s.patchLineIndices[s.selectedLineIdx])
|
||||
hunkIdx := s.patch.HunkContainingLine(s.selectedLineIdx)
|
||||
if hunkIdx != -1 {
|
||||
newHunkIdx := hunkIdx + change
|
||||
if newHunkIdx >= 0 && newHunkIdx < s.patch.HunkCount() {
|
||||
start := s.patch.HunkStartIdx(newHunkIdx)
|
||||
s.selectedLineIdx = s.viewLineIndices[s.patch.GetNextChangeIdx(start)]
|
||||
s.selectedLineIdx = s.patch.GetNextChangeIdx(start)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -254,17 +216,16 @@ func (s *State) CycleRange(forward bool) {
|
||||
|
||||
// returns first and last patch line index of current hunk
|
||||
func (s *State) CurrentHunkBounds() (int, int) {
|
||||
hunkIdx := s.patch.HunkContainingLine(s.patchLineIndices[s.selectedLineIdx])
|
||||
hunkIdx := s.patch.HunkContainingLine(s.selectedLineIdx)
|
||||
start := s.patch.HunkStartIdx(hunkIdx)
|
||||
end := s.patch.HunkEndIdx(hunkIdx)
|
||||
return start, end
|
||||
}
|
||||
|
||||
func (s *State) SelectedViewRange() (int, int) {
|
||||
func (s *State) SelectedRange() (int, int) {
|
||||
switch s.selectMode {
|
||||
case HUNK:
|
||||
start, end := s.CurrentHunkBounds()
|
||||
return s.viewLineIndices[start], s.viewLineIndices[end]
|
||||
return s.CurrentHunkBounds()
|
||||
case RANGE:
|
||||
if s.rangeStartLineIdx > s.selectedLineIdx {
|
||||
return s.selectedLineIdx, s.rangeStartLineIdx
|
||||
@@ -279,13 +240,8 @@ func (s *State) SelectedViewRange() (int, int) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *State) SelectedPatchRange() (int, int) {
|
||||
start, end := s.SelectedViewRange()
|
||||
return s.patchLineIndices[start], s.patchLineIndices[end]
|
||||
}
|
||||
|
||||
func (s *State) CurrentLineNumber() int {
|
||||
return s.patch.LineNumberOfLine(s.patchLineIndices[s.selectedLineIdx])
|
||||
return s.patch.LineNumberOfLine(s.selectedLineIdx)
|
||||
}
|
||||
|
||||
func (s *State) AdjustSelectedLineIdx(change int) {
|
||||
@@ -301,13 +257,13 @@ func (s *State) RenderForLineIndices(includedLineIndices []int) string {
|
||||
}
|
||||
|
||||
func (s *State) PlainRenderSelected() string {
|
||||
firstLineIdx, lastLineIdx := s.SelectedPatchRange()
|
||||
firstLineIdx, lastLineIdx := s.SelectedRange()
|
||||
return s.patch.FormatRangePlain(firstLineIdx, lastLineIdx)
|
||||
}
|
||||
|
||||
func (s *State) SelectBottom() {
|
||||
s.DismissHunkSelectMode()
|
||||
s.SelectLine(len(s.patchLineIndices) - 1)
|
||||
s.SelectLine(s.patch.LineCount() - 1)
|
||||
}
|
||||
|
||||
func (s *State) SelectTop() {
|
||||
@@ -316,13 +272,15 @@ func (s *State) SelectTop() {
|
||||
}
|
||||
|
||||
func (s *State) CalculateOrigin(currentOrigin int, bufferHeight int, numLines int) int {
|
||||
firstLineIdx, lastLineIdx := s.SelectedViewRange()
|
||||
firstLineIdx, lastLineIdx := s.SelectedRange()
|
||||
|
||||
return calculateOrigin(currentOrigin, bufferHeight, numLines, firstLineIdx, lastLineIdx, s.GetSelectedViewLineIdx(), s.selectMode)
|
||||
return calculateOrigin(currentOrigin, bufferHeight, numLines, firstLineIdx, lastLineIdx, s.GetSelectedLineIdx(), s.selectMode)
|
||||
}
|
||||
|
||||
func wrapPatchLines(diff string, view *gocui.View) ([]int, []int) {
|
||||
_, viewLineIndices, patchLineIndices := utils.WrapViewLinesToWidth(
|
||||
view.Wrap, strings.TrimSuffix(diff, "\n"), view.InnerWidth())
|
||||
return viewLineIndices, patchLineIndices
|
||||
func (s *State) RangeStartLineIdx() (int, bool) {
|
||||
if s.selectMode == RANGE {
|
||||
return s.rangeStartLineIdx, true
|
||||
}
|
||||
|
||||
return 0, false
|
||||
}
|
||||
|
||||
@@ -107,6 +107,7 @@ func (self *PopupHandler) Confirm(opts types.ConfirmOpts) {
|
||||
func (self *PopupHandler) Prompt(opts types.PromptOpts) {
|
||||
self.createPopupPanelFn(context.Background(), types.CreatePopupPanelOpts{
|
||||
Title: opts.Title,
|
||||
Multiline: opts.Multiline,
|
||||
Prompt: opts.InitialContent,
|
||||
Editable: true,
|
||||
HandleConfirmPrompt: opts.HandleConfirm,
|
||||
|
||||
@@ -22,13 +22,12 @@ func RenderFileTree(
|
||||
tree filetree.IFileTree,
|
||||
submoduleConfigs []*models.SubmoduleConfig,
|
||||
showFileIcons bool,
|
||||
showNumstat bool,
|
||||
) []string {
|
||||
collapsedPaths := tree.CollapsedPaths()
|
||||
return renderAux(tree.GetRoot().Raw(), collapsedPaths, -1, -1, func(node *filetree.Node[models.File], treeDepth int, visualDepth int, isCollapsed bool) string {
|
||||
fileNode := filetree.NewFileNode(node)
|
||||
|
||||
return getFileLine(isCollapsed, fileNode.GetHasUnstagedChanges(), fileNode.GetHasStagedChanges(), treeDepth, visualDepth, showNumstat, showFileIcons, submoduleConfigs, node)
|
||||
return getFileLine(isCollapsed, fileNode.GetHasUnstagedChanges(), fileNode.GetHasStagedChanges(), treeDepth, visualDepth, showFileIcons, submoduleConfigs, node)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -112,7 +111,6 @@ func getFileLine(
|
||||
hasStagedChanges bool,
|
||||
treeDepth int,
|
||||
visualDepth int,
|
||||
showNumstat,
|
||||
showFileIcons bool,
|
||||
submoduleConfigs []*models.SubmoduleConfig,
|
||||
node *filetree.Node[models.File],
|
||||
@@ -167,12 +165,6 @@ func getFileLine(
|
||||
output += theme.DefaultTextColor.Sprint(" (submodule)")
|
||||
}
|
||||
|
||||
if file != nil && showNumstat {
|
||||
if lineChanges := formatLineChanges(file.LinesAdded, file.LinesDeleted); lineChanges != "" {
|
||||
output += " " + lineChanges
|
||||
}
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
@@ -194,23 +186,6 @@ func formatFileStatus(file *models.File, restColor style.TextStyle) string {
|
||||
return firstCharCl.Sprint(firstChar) + secondCharCl.Sprint(secondChar)
|
||||
}
|
||||
|
||||
func formatLineChanges(linesAdded, linesDeleted int) string {
|
||||
output := ""
|
||||
|
||||
if linesAdded != 0 {
|
||||
output += style.FgGreen.Sprintf("+%d", linesAdded)
|
||||
}
|
||||
|
||||
if linesDeleted != 0 {
|
||||
if output != "" {
|
||||
output += " "
|
||||
}
|
||||
output += style.FgRed.Sprintf("-%d", linesDeleted)
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
func getCommitFileLine(
|
||||
isCollapsed bool,
|
||||
treeDepth int,
|
||||
|
||||
@@ -19,12 +19,11 @@ func toStringSlice(str string) []string {
|
||||
|
||||
func TestRenderFileTree(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
name string
|
||||
root *filetree.FileNode
|
||||
files []*models.File
|
||||
collapsedPaths []string
|
||||
showLineChanges bool
|
||||
expected []string
|
||||
name string
|
||||
root *filetree.FileNode
|
||||
files []*models.File
|
||||
collapsedPaths []string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "nil node",
|
||||
@@ -38,22 +37,6 @@ func TestRenderFileTree(t *testing.T) {
|
||||
},
|
||||
expected: []string{" M test"},
|
||||
},
|
||||
{
|
||||
name: "numstat",
|
||||
files: []*models.File{
|
||||
{Name: "test", ShortStatus: " M", HasStagedChanges: true, LinesAdded: 1, LinesDeleted: 1},
|
||||
{Name: "test2", ShortStatus: " M", HasStagedChanges: true, LinesAdded: 1},
|
||||
{Name: "test3", ShortStatus: " M", HasStagedChanges: true, LinesDeleted: 1},
|
||||
{Name: "test4", ShortStatus: " M", HasStagedChanges: true, LinesAdded: 0, LinesDeleted: 0},
|
||||
},
|
||||
showLineChanges: true,
|
||||
expected: []string{
|
||||
" M test +1 -1",
|
||||
" M test2 +1",
|
||||
" M test3 -1",
|
||||
" M test4",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "big example",
|
||||
files: []*models.File{
|
||||
@@ -89,7 +72,7 @@ M file1
|
||||
for _, path := range s.collapsedPaths {
|
||||
viewModel.ToggleCollapsed(path)
|
||||
}
|
||||
result := RenderFileTree(viewModel, nil, false, s.showLineChanges)
|
||||
result := RenderFileTree(viewModel, nil, false)
|
||||
assert.EqualValues(t, s.expected, result)
|
||||
})
|
||||
}
|
||||
|
||||
+2
-2
@@ -16,7 +16,7 @@ import (
|
||||
)
|
||||
|
||||
func (gui *Gui) desiredPtySize(view *gocui.View) *pty.Winsize {
|
||||
width, height := view.InnerSize()
|
||||
width, height := view.Size()
|
||||
|
||||
return &pty.Winsize{Cols: uint16(width), Rows: uint16(height)}
|
||||
}
|
||||
@@ -45,7 +45,7 @@ func (gui *Gui) onResize() error {
|
||||
// pseudo-terminal meaning we'll get the behaviour we want from the underlying
|
||||
// command.
|
||||
func (gui *Gui) newPtyTask(view *gocui.View, cmd *exec.Cmd, prefix string) error {
|
||||
width := view.InnerWidth()
|
||||
width, _ := view.Size()
|
||||
pager := gui.git.Config.GetPager(width)
|
||||
externalDiffCommand := gui.Config.GetUserConfig().Git.Paging.ExternalDiffCommand
|
||||
|
||||
|
||||
@@ -21,7 +21,8 @@ func (gui *Gui) updateRecentRepoList() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
recentRepos = newRecentReposList(recentRepos, currentRepo)
|
||||
known, recentRepos := newRecentReposList(recentRepos, currentRepo)
|
||||
gui.IsNewRepo = known
|
||||
// TODO: migrate this file to use forward slashes on all OSes for consistency
|
||||
// (windows uses backslashes at the moment)
|
||||
gui.c.GetAppState().RecentRepos = recentRepos
|
||||
@@ -29,7 +30,8 @@ func (gui *Gui) updateRecentRepoList() error {
|
||||
}
|
||||
|
||||
// newRecentReposList returns a new repo list with a new entry but only when it doesn't exist yet
|
||||
func newRecentReposList(recentRepos []string, currentRepo string) []string {
|
||||
func newRecentReposList(recentRepos []string, currentRepo string) (bool, []string) {
|
||||
isNew := true
|
||||
newRepos := []string{currentRepo}
|
||||
for _, repo := range recentRepos {
|
||||
if repo != currentRepo {
|
||||
@@ -37,7 +39,9 @@ func newRecentReposList(recentRepos []string, currentRepo string) []string {
|
||||
continue
|
||||
}
|
||||
newRepos = append(newRepos, repo)
|
||||
} else {
|
||||
isNew = false
|
||||
}
|
||||
}
|
||||
return newRepos
|
||||
return isNew, newRepos
|
||||
}
|
||||
|
||||
@@ -79,6 +79,14 @@ func (self *HandlerCreator) call(customCommand config.CustomCommand) func() erro
|
||||
}
|
||||
return self.inputPrompt(resolvedPrompt, wrappedF)
|
||||
}
|
||||
case "textbox":
|
||||
f = func() error {
|
||||
resolvedPrompt, err := self.resolver.resolvePrompt(&prompt, resolveTemplate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return self.textboxPrompt(resolvedPrompt, wrappedF)
|
||||
}
|
||||
case "menu":
|
||||
f = func() error {
|
||||
resolvedPrompt, err := self.resolver.resolvePrompt(&prompt, resolveTemplate)
|
||||
@@ -130,6 +138,19 @@ func (self *HandlerCreator) inputPrompt(prompt *config.CustomCommandPrompt, wrap
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *HandlerCreator) textboxPrompt(prompt *config.CustomCommandPrompt, wrappedF func(string) error) error {
|
||||
self.c.Prompt(types.PromptOpts{
|
||||
Title: prompt.Title,
|
||||
InitialContent: prompt.InitialValue,
|
||||
Multiline: true,
|
||||
HandleConfirm: func(str string) error {
|
||||
return wrappedF(str)
|
||||
},
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *HandlerCreator) generateFindSuggestionsFunc(prompt *config.CustomCommandPrompt) (func(string) []*types.Suggestion, error) {
|
||||
if prompt.Suggestions.Preset != "" && prompt.Suggestions.Command != "" {
|
||||
return nil, fmt.Errorf(
|
||||
|
||||
@@ -153,6 +153,7 @@ type CreatePopupPanelOpts struct {
|
||||
HasLoader bool
|
||||
Editable bool
|
||||
Title string
|
||||
Multiline bool
|
||||
Prompt string
|
||||
HandleConfirm func() error
|
||||
HandleConfirmPrompt func(string) error
|
||||
@@ -166,6 +167,7 @@ type CreatePopupPanelOpts struct {
|
||||
|
||||
type ConfirmOpts struct {
|
||||
Title string
|
||||
Multiline bool
|
||||
Prompt string
|
||||
HandleConfirm func() error
|
||||
HandleClose func() error
|
||||
@@ -176,6 +178,7 @@ type ConfirmOpts struct {
|
||||
|
||||
type PromptOpts struct {
|
||||
Title string
|
||||
Multiline bool
|
||||
InitialContent string
|
||||
FindSuggestionsFunc func(string) []*Suggestion
|
||||
HandleConfirm func(string) error
|
||||
|
||||
@@ -19,8 +19,8 @@ func (gui *Gui) resetViewOrigin(v *gocui.View) {
|
||||
// that the scrollbar has the correct size, along with the number of lines after
|
||||
// which the view is filled and we can do a first refresh.
|
||||
func (gui *Gui) linesToReadFromCmdTask(v *gocui.View) tasks.LinesToRead {
|
||||
height := v.InnerHeight()
|
||||
oy := v.OriginY()
|
||||
_, height := v.Size()
|
||||
_, oy := v.Origin()
|
||||
|
||||
linesForFirstRefresh := height + oy + 10
|
||||
|
||||
|
||||
+6
-30
@@ -105,17 +105,12 @@ type TranslationSet struct {
|
||||
NewBranchNameBranchOff string
|
||||
CantDeleteCheckOutBranch string
|
||||
DeleteBranchTitle string
|
||||
DeleteBranchesTitle string
|
||||
DeleteLocalBranch string
|
||||
DeleteLocalBranches string
|
||||
DeleteRemoteBranchOption string
|
||||
DeleteRemoteBranchPrompt string
|
||||
DeleteRemoteBranchesPrompt string
|
||||
DeleteLocalAndRemoteBranchPrompt string
|
||||
DeleteLocalAndRemoteBranchesPrompt string
|
||||
ForceDeleteBranchTitle string
|
||||
ForceDeleteBranchMessage string
|
||||
ForceDeleteBranchesMessage string
|
||||
RebaseBranch string
|
||||
RebaseBranchTooltip string
|
||||
CantRebaseOntoSelf string
|
||||
@@ -140,7 +135,6 @@ type TranslationSet struct {
|
||||
Quit string
|
||||
SquashTooltip string
|
||||
CannotSquashOrFixupFirstCommit string
|
||||
CannotSquashOrFixupMergeCommit string
|
||||
Fixup string
|
||||
FixupTooltip string
|
||||
SureFixupThisCommit string
|
||||
@@ -161,7 +155,6 @@ type TranslationSet struct {
|
||||
MoveDownCommit string
|
||||
MoveUpCommit string
|
||||
CannotMoveAnyFurther string
|
||||
CannotMoveMergeCommit string
|
||||
EditCommit string
|
||||
EditCommitTooltip string
|
||||
AmendCommitTooltip string
|
||||
@@ -256,6 +249,8 @@ type TranslationSet struct {
|
||||
NoBranchOnRemote string
|
||||
Fetch string
|
||||
FetchTooltip string
|
||||
NoAutomaticGitFetchTitle string
|
||||
NoAutomaticGitFetchBody string
|
||||
FileEnter string
|
||||
FileEnterTooltip string
|
||||
FileStagingRequirements string
|
||||
@@ -322,7 +317,6 @@ type TranslationSet struct {
|
||||
YouDied string
|
||||
RewordNotSupported string
|
||||
ChangingThisActionIsNotAllowed string
|
||||
DroppingMergeRequiresSingleSelection string
|
||||
CherryPickCopy string
|
||||
CherryPickCopyTooltip string
|
||||
CherryPickCopyRangeTooltip string
|
||||
@@ -350,7 +344,6 @@ type TranslationSet struct {
|
||||
DropCommitTitle string
|
||||
DropCommitPrompt string
|
||||
DropUpdateRefPrompt string
|
||||
DropMergeCommitPrompt string
|
||||
PullingStatus string
|
||||
PushingStatus string
|
||||
FetchingStatus string
|
||||
@@ -481,10 +474,8 @@ type TranslationSet struct {
|
||||
RemoveRemoteTooltip string
|
||||
RemoveRemotePrompt string
|
||||
DeleteRemoteBranch string
|
||||
DeleteRemoteBranches string
|
||||
DeleteRemoteBranchTooltip string
|
||||
DeleteLocalAndRemoteBranch string
|
||||
DeleteLocalAndRemoteBranches string
|
||||
SetAsUpstream string
|
||||
SetAsUpstreamTooltip string
|
||||
SetUpstream string
|
||||
@@ -553,7 +544,6 @@ type TranslationSet struct {
|
||||
ViewBranchUpstreamOptions string
|
||||
ViewBranchUpstreamOptionsTooltip string
|
||||
UpstreamNotSetError string
|
||||
UpstreamsNotSetError string
|
||||
NewGitFlowBranchPrompt string
|
||||
RenameBranchWarning string
|
||||
OpenKeybindingsMenu string
|
||||
@@ -565,6 +555,7 @@ type TranslationSet struct {
|
||||
MustStashWarning string
|
||||
MustStashTitle string
|
||||
ConfirmationTitle string
|
||||
PressToSubmit string
|
||||
PrevPage string
|
||||
NextPage string
|
||||
GotoTop string
|
||||
@@ -696,8 +687,6 @@ type TranslationSet struct {
|
||||
CreatePullRequestOptions string
|
||||
DefaultBranch string
|
||||
SelectBranch string
|
||||
SelectTargetRemote string
|
||||
NoValidRemoteName string
|
||||
CreatePullRequest string
|
||||
SelectConfigFile string
|
||||
NoConfigFileFoundErr string
|
||||
@@ -762,7 +751,6 @@ type TranslationSet struct {
|
||||
SwitchToWorktreeTooltip string
|
||||
AlreadyCheckedOutByWorktree string
|
||||
BranchCheckedOutByWorktree string
|
||||
SomeBranchesCheckedOutByWorktreeError string
|
||||
DetachWorktreeTooltip string
|
||||
Switching string
|
||||
RemoveWorktree string
|
||||
@@ -1100,17 +1088,12 @@ func EnglishTranslationSet() *TranslationSet {
|
||||
NewBranchNameBranchOff: "New branch name (branch is off of '{{.branchName}}')",
|
||||
CantDeleteCheckOutBranch: "You cannot delete the checked out branch!",
|
||||
DeleteBranchTitle: "Delete branch '{{.selectedBranchName}}'?",
|
||||
DeleteBranchesTitle: "Delete selected branches?",
|
||||
DeleteLocalBranch: "Delete local branch",
|
||||
DeleteLocalBranches: "Delete local branches",
|
||||
DeleteRemoteBranchOption: "Delete remote branch",
|
||||
DeleteRemoteBranchPrompt: "Are you sure you want to delete the remote branch '{{.selectedBranchName}}' from '{{.upstream}}'?",
|
||||
DeleteRemoteBranchesPrompt: "Are you sure you want to delete the remote branches of the selected branches from their respective remotes?",
|
||||
DeleteLocalAndRemoteBranchPrompt: "Are you sure you want to delete both '{{.localBranchName}}' from your machine, and '{{.remoteBranchName}}' from '{{.remoteName}}'?",
|
||||
DeleteLocalAndRemoteBranchesPrompt: "Are you sure you want to delete both the selected branches from your machine, and their remote branches from their respective remotes?",
|
||||
ForceDeleteBranchTitle: "Force delete branch",
|
||||
ForceDeleteBranchMessage: "'{{.selectedBranchName}}' is not fully merged. Are you sure you want to delete it?",
|
||||
ForceDeleteBranchesMessage: "Some of the selected branches are not fully merged. Are you sure you want to delete them?",
|
||||
RebaseBranch: "Rebase",
|
||||
RebaseBranchTooltip: "Rebase the checked-out branch onto the selected branch.",
|
||||
CantRebaseOntoSelf: "You cannot rebase a branch onto itself",
|
||||
@@ -1138,7 +1121,6 @@ func EnglishTranslationSet() *TranslationSet {
|
||||
UpdateRefHere: "Update branch '{{.ref}}' here",
|
||||
ExecCommandHere: "Execute the following command here:",
|
||||
CannotSquashOrFixupFirstCommit: "There's no commit below to squash into",
|
||||
CannotSquashOrFixupMergeCommit: "Cannot squash or fixup a merge commit",
|
||||
Fixup: "Fixup",
|
||||
SureFixupThisCommit: "Are you sure you want to 'fixup' the selected commit(s) into the commit below?",
|
||||
SureSquashThisCommit: "Are you sure you want to squash the selected commit(s) into the commit below?",
|
||||
@@ -1158,7 +1140,6 @@ func EnglishTranslationSet() *TranslationSet {
|
||||
MoveDownCommit: "Move commit down one",
|
||||
MoveUpCommit: "Move commit up one",
|
||||
CannotMoveAnyFurther: "Cannot move any further",
|
||||
CannotMoveMergeCommit: "Cannot move a merge commit",
|
||||
EditCommit: "Edit (start interactive rebase)",
|
||||
EditCommitTooltip: "Edit the selected commit. Use this to start an interactive rebase from the selected commit. When already mid-rebase, this will mark the selected commit for editing, which means that upon continuing the rebase, the rebase will pause at the selected commit to allow you to make changes.",
|
||||
AmendCommitTooltip: "Amend commit with staged changes. If the selected commit is the HEAD commit, this will perform `git commit --amend`. Otherwise the commit will be amended via a rebase.",
|
||||
@@ -1253,6 +1234,8 @@ func EnglishTranslationSet() *TranslationSet {
|
||||
NoBranchOnRemote: `This branch doesn't exist on remote. You need to push it to remote first.`,
|
||||
Fetch: `Fetch`,
|
||||
FetchTooltip: "Fetch changes from remote.",
|
||||
NoAutomaticGitFetchTitle: `No automatic git fetch`,
|
||||
NoAutomaticGitFetchBody: `Lazygit can't use "git fetch" in a private repo; use 'f' in the files panel to run "git fetch" manually`,
|
||||
FileEnter: `Stage lines / Collapse directory`,
|
||||
FileEnterTooltip: "If the selected item is a file, focus the staging view so you can stage individual hunks/lines. If the selected item is a directory, collapse/expand it.",
|
||||
FileStagingRequirements: `Can only stage individual lines for tracked files`,
|
||||
@@ -1326,7 +1309,6 @@ func EnglishTranslationSet() *TranslationSet {
|
||||
YouDied: "YOU DIED!",
|
||||
RewordNotSupported: "Rewording commits while interactively rebasing is not currently supported",
|
||||
ChangingThisActionIsNotAllowed: "Changing this kind of rebase todo entry is not allowed",
|
||||
DroppingMergeRequiresSingleSelection: "Dropping a merge commit requires a single selected item",
|
||||
CherryPickCopy: "Copy (cherry-pick)",
|
||||
CherryPickCopyTooltip: "Mark commit as copied. Then, within the local commits view, you can press `{{.paste}}` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `{{.escape}}` to cancel the selection.",
|
||||
CherryPickCopyRangeTooltip: "Mark commits as copied from the last copied commit to the selected commit.",
|
||||
@@ -1353,7 +1335,6 @@ func EnglishTranslationSet() *TranslationSet {
|
||||
AmendCommitPrompt: "Are you sure you want to amend this commit with your staged files?",
|
||||
DropCommitTitle: "Drop commit",
|
||||
DropCommitPrompt: "Are you sure you want to drop the selected commit(s)?",
|
||||
DropMergeCommitPrompt: "Are you sure you want to drop the selected merge commit? Note that it will also drop all the commits that were merged in by it.",
|
||||
DropUpdateRefPrompt: "Are you sure you want to delete the selected update-ref todo(s)? This is irreversible except by aborting the rebase.",
|
||||
PullingStatus: "Pulling",
|
||||
PushingStatus: "Pushing",
|
||||
@@ -1486,10 +1467,8 @@ func EnglishTranslationSet() *TranslationSet {
|
||||
RemoveRemoteTooltip: `Remove the selected remote. Any local branches tracking a remote branch from the remote will be unaffected.`,
|
||||
RemoveRemotePrompt: "Are you sure you want to remove remote?",
|
||||
DeleteRemoteBranch: "Delete remote branch",
|
||||
DeleteRemoteBranches: "Delete remote branches",
|
||||
DeleteRemoteBranchTooltip: "Delete the remote branch from the remote.",
|
||||
DeleteLocalAndRemoteBranch: "Delete local and remote branch",
|
||||
DeleteLocalAndRemoteBranches: "Delete local and remote branches",
|
||||
SetAsUpstream: "Set as upstream",
|
||||
SetAsUpstreamTooltip: "Set the selected remote branch as the upstream of the checked-out branch.",
|
||||
SetUpstream: "Set upstream of selected branch",
|
||||
@@ -1554,7 +1533,6 @@ func EnglishTranslationSet() *TranslationSet {
|
||||
ViewBranchUpstreamOptions: "View upstream options",
|
||||
ViewBranchUpstreamOptionsTooltip: "View options relating to the branch's upstream e.g. setting/unsetting the upstream and resetting to the upstream.",
|
||||
UpstreamNotSetError: "The selected branch has no upstream (or the upstream is not stored locally)",
|
||||
UpstreamsNotSetError: "Some of the selected branches have no upstream (or the upstream is not stored locally)",
|
||||
Upstream: "Upstream",
|
||||
UpstreamTooltip: "View upstream options for selected branch e.g. setting/unsetting the upstream and resetting to the upstream.",
|
||||
NewBranchNamePrompt: "Enter new branch name for branch",
|
||||
@@ -1568,6 +1546,7 @@ func EnglishTranslationSet() *TranslationSet {
|
||||
MustStashWarning: "Pulling a patch out into the index requires stashing and unstashing your changes. If something goes wrong, you'll be able to access your files from the stash. Continue?",
|
||||
MustStashTitle: "Must stash",
|
||||
ConfirmationTitle: "Confirmation panel",
|
||||
PressToSubmit: "Press {{.Key}} to submit",
|
||||
PrevPage: "Previous page",
|
||||
NextPage: "Next page",
|
||||
GotoTop: "Scroll to top",
|
||||
@@ -1699,8 +1678,6 @@ func EnglishTranslationSet() *TranslationSet {
|
||||
CreatePullRequestOptions: "View create pull request options",
|
||||
DefaultBranch: "Default branch",
|
||||
SelectBranch: "Select branch",
|
||||
SelectTargetRemote: "Select target remote",
|
||||
NoValidRemoteName: "A remote named '%s' does not exist",
|
||||
SelectConfigFile: "Select config file",
|
||||
NoConfigFileFoundErr: "No config file found",
|
||||
LoadingFileSuggestions: "Loading file suggestions",
|
||||
@@ -1766,7 +1743,6 @@ func EnglishTranslationSet() *TranslationSet {
|
||||
SwitchToWorktreeTooltip: "Switch to the selected worktree.",
|
||||
AlreadyCheckedOutByWorktree: "This branch is checked out by worktree {{.worktreeName}}. Do you want to switch to that worktree?",
|
||||
BranchCheckedOutByWorktree: "Branch {{.branchName}} is checked out by worktree {{.worktreeName}}",
|
||||
SomeBranchesCheckedOutByWorktreeError: "Some of the selected branches are checked out by other worktrees. Select them one by one to delete them.",
|
||||
DetachWorktreeTooltip: "This will run `git checkout --detach` on the worktree so that it stops hogging the branch, but the worktree's working tree will be left alone.",
|
||||
Switching: "Switching",
|
||||
RemoveWorktree: "Remove worktree",
|
||||
|
||||
@@ -203,7 +203,7 @@ func (self *ViewDriver) validateVisibleLineCount(matchers []*TextMatcher) {
|
||||
view := self.getView()
|
||||
|
||||
self.t.assertWithRetries(func() (bool, string) {
|
||||
count := view.InnerHeight()
|
||||
count := view.InnerHeight() + 1
|
||||
return count == len(matchers), fmt.Sprintf("unexpected number of visible lines in view '%s'. Expected exactly %d, got %d", view.Name(), len(matchers), count)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -150,12 +150,24 @@ var Delete = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Confirm()
|
||||
}).
|
||||
Tap(func() {
|
||||
checkRemoteBranches(t, keys, "origin", []string{
|
||||
"branch-five",
|
||||
"branch-four",
|
||||
"branch-six",
|
||||
"branch-two",
|
||||
})
|
||||
t.Views().Remotes().
|
||||
Focus().
|
||||
Lines(Contains("origin")).
|
||||
PressEnter()
|
||||
|
||||
t.Views().
|
||||
RemoteBranches().
|
||||
Lines(
|
||||
Equals("branch-five"),
|
||||
Equals("branch-four"),
|
||||
Equals("branch-six"),
|
||||
Equals("branch-two"),
|
||||
).
|
||||
Press(keys.Universal.Return)
|
||||
|
||||
t.Views().
|
||||
Branches().
|
||||
Focus()
|
||||
}).
|
||||
Lines(
|
||||
Contains("current-head"),
|
||||
|
||||
@@ -1,186 +0,0 @@
|
||||
package branch
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var DeleteMultiple = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Try some combinations of local and remote branch deletions with a range selection of branches",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {
|
||||
config.GetAppState().LocalBranchSortOrder = "alphabetic"
|
||||
},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.
|
||||
CloneIntoRemote("origin").
|
||||
CloneIntoRemote("other-remote").
|
||||
EmptyCommit("blah").
|
||||
NewBranch("branch-01").
|
||||
EmptyCommit("on branch-01 01").
|
||||
PushBranchAndSetUpstream("origin", "branch-01").
|
||||
EmptyCommit("on branch-01 02").
|
||||
NewBranch("branch-02").
|
||||
EmptyCommit("on branch-02 01").
|
||||
PushBranchAndSetUpstream("origin", "branch-02").
|
||||
NewBranchFrom("branch-03", "master").
|
||||
EmptyCommit("on branch-03 01").
|
||||
NewBranch("current-head").
|
||||
EmptyCommit("on current-head").
|
||||
NewBranchFrom("branch-04", "master").
|
||||
EmptyCommit("on branch-04 01").
|
||||
PushBranchAndSetUpstream("other-remote", "branch-04").
|
||||
EmptyCommit("on branch-04 02").
|
||||
NewBranchFrom("branch-05", "master").
|
||||
EmptyCommit("on branch-05 01").
|
||||
PushBranchAndSetUpstream("origin", "branch-05").
|
||||
NewBranchFrom("branch-06", "master").
|
||||
EmptyCommit("on branch-06 01").
|
||||
PushBranch("origin", "branch-06").
|
||||
PushBranchAndSetUpstream("other-remote", "branch-06").
|
||||
EmptyCommit("on branch-06 02").
|
||||
Checkout("current-head")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Branches().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("current-head").IsSelected(),
|
||||
Contains("branch-01 ↑1"),
|
||||
Contains("branch-02 ✓"),
|
||||
Contains("branch-03"),
|
||||
Contains("branch-04 ↑1"),
|
||||
Contains("branch-05 ✓"),
|
||||
Contains("branch-06 ↑1"),
|
||||
Contains("master"),
|
||||
).
|
||||
Press(keys.Universal.RangeSelectDown).
|
||||
|
||||
// Deleting a range that includes the current branch is not possible
|
||||
Press(keys.Universal.Remove).
|
||||
Tap(func() {
|
||||
t.ExpectPopup().
|
||||
Menu().
|
||||
Tooltip(Contains("You cannot delete the checked out branch!")).
|
||||
Title(Equals("Delete selected branches?")).
|
||||
Select(Contains("Delete local branches")).
|
||||
Confirm().
|
||||
Tap(func() {
|
||||
t.ExpectToast(Contains("You cannot delete the checked out branch!"))
|
||||
}).
|
||||
Cancel()
|
||||
}).
|
||||
|
||||
// Delete branch-03 and branch-04. 04 is not fully merged, so we get
|
||||
// a confirmation popup.
|
||||
NavigateToLine(Contains("branch-03")).
|
||||
Press(keys.Universal.RangeSelectDown).
|
||||
Press(keys.Universal.Remove).
|
||||
Tap(func() {
|
||||
t.ExpectPopup().
|
||||
Menu().
|
||||
Title(Equals("Delete selected branches?")).
|
||||
Select(Contains("Delete local branches")).
|
||||
Confirm()
|
||||
t.ExpectPopup().
|
||||
Confirmation().
|
||||
Title(Equals("Force delete branch")).
|
||||
Content(Equals("Some of the selected branches are not fully merged. Are you sure you want to delete them?")).
|
||||
Confirm()
|
||||
}).
|
||||
Lines(
|
||||
Contains("current-head"),
|
||||
Contains("branch-01 ↑1"),
|
||||
Contains("branch-02 ✓"),
|
||||
Contains("branch-05 ✓").IsSelected(),
|
||||
Contains("branch-06 ↑1"),
|
||||
Contains("master"),
|
||||
).
|
||||
|
||||
// Delete remote branches of branch-05 and branch-06. They are on different remotes.
|
||||
NavigateToLine(Contains("branch-05")).
|
||||
Press(keys.Universal.RangeSelectDown).
|
||||
Press(keys.Universal.Remove).
|
||||
Tap(func() {
|
||||
t.ExpectPopup().
|
||||
Menu().
|
||||
Title(Equals("Delete selected branches?")).
|
||||
Select(Contains("Delete remote branches")).
|
||||
Confirm()
|
||||
}).
|
||||
Tap(func() {
|
||||
t.ExpectPopup().
|
||||
Confirmation().
|
||||
Title(Equals("Delete selected branches?")).
|
||||
Content(Equals("Are you sure you want to delete the remote branches of the selected branches from their respective remotes?")).
|
||||
Confirm()
|
||||
}).
|
||||
Tap(func() {
|
||||
checkRemoteBranches(t, keys, "origin", []string{
|
||||
"branch-01",
|
||||
"branch-02",
|
||||
"branch-06",
|
||||
})
|
||||
checkRemoteBranches(t, keys, "other-remote", []string{
|
||||
"branch-04",
|
||||
})
|
||||
}).
|
||||
Lines(
|
||||
Contains("current-head"),
|
||||
Contains("branch-01 ↑1"),
|
||||
Contains("branch-02 ✓"),
|
||||
Contains("branch-05 (upstream gone)").IsSelected(),
|
||||
Contains("branch-06 (upstream gone)").IsSelected(),
|
||||
Contains("master"),
|
||||
).
|
||||
|
||||
// Try to delete both local and remote branches of branch-02 and
|
||||
// branch-05; not possible because branch-05's upstream is gone
|
||||
Press(keys.Universal.Remove).
|
||||
Tap(func() {
|
||||
t.ExpectPopup().
|
||||
Menu().
|
||||
Title(Equals("Delete selected branches?")).
|
||||
Select(Contains("Delete local and remote branches")).
|
||||
Confirm().
|
||||
Tap(func() {
|
||||
t.ExpectToast(Contains("Some of the selected branches have no upstream (or the upstream is not stored locally)"))
|
||||
}).
|
||||
Cancel()
|
||||
}).
|
||||
|
||||
// Delete both local and remote branches of branch-01 and branch-02. We get
|
||||
// the force-delete warning because branch-01 it is not fully merged.
|
||||
NavigateToLine(Contains("branch-01")).
|
||||
Press(keys.Universal.RangeSelectDown).
|
||||
Press(keys.Universal.Remove).
|
||||
Tap(func() {
|
||||
t.ExpectPopup().
|
||||
Menu().
|
||||
Title(Equals("Delete selected branches?")).
|
||||
Select(Contains("Delete local and remote branches")).
|
||||
Confirm()
|
||||
t.ExpectPopup().
|
||||
Confirmation().
|
||||
Title(Equals("Delete local and remote branch")).
|
||||
Content(Contains("Are you sure you want to delete both the selected branches from your machine, and their remote branches from their respective remotes?").
|
||||
Contains("Some of the selected branches are not fully merged. Are you sure you want to delete them?")).
|
||||
Confirm()
|
||||
}).
|
||||
Lines(
|
||||
Contains("current-head"),
|
||||
Contains("branch-05 (upstream gone)").IsSelected(),
|
||||
Contains("branch-06 (upstream gone)"),
|
||||
Contains("master"),
|
||||
).
|
||||
Tap(func() {
|
||||
checkRemoteBranches(t, keys, "origin", []string{
|
||||
"branch-06",
|
||||
})
|
||||
checkRemoteBranches(t, keys, "other-remote", []string{
|
||||
"branch-04",
|
||||
})
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -1,54 +0,0 @@
|
||||
package branch
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var OpenPullRequestInvalidTargetRemoteName = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Open up a pull request, specifying a non-existing target remote",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
// Create an initial commit ('git branch set-upstream-to' bails out otherwise)
|
||||
shell.CreateFileAndAdd("file", "content1")
|
||||
shell.Commit("one")
|
||||
|
||||
// Create a new branch
|
||||
shell.NewBranch("branch-1")
|
||||
|
||||
// Create a couple of remotes
|
||||
shell.CloneIntoRemote("upstream")
|
||||
shell.CloneIntoRemote("origin")
|
||||
|
||||
// To allow a pull request to be created from a branch, it must have an upstream set.
|
||||
shell.SetBranchUpstream("branch-1", "origin/branch-1")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
// Open a PR for the current branch (i.e. 'branch-1')
|
||||
t.Views().
|
||||
Branches().
|
||||
Focus().
|
||||
Press(keys.Branches.ViewPullRequestOptions)
|
||||
|
||||
t.ExpectPopup().
|
||||
Menu().
|
||||
Title(Equals("View create pull request options")).
|
||||
Select(Contains("Select branch")).
|
||||
Confirm()
|
||||
|
||||
// Verify that we're prompted to enter the remote and enter the name of a non-existing one.
|
||||
t.ExpectPopup().
|
||||
Prompt().
|
||||
Title(Equals("Select target remote")).
|
||||
Type("non-existing-remote").
|
||||
Confirm()
|
||||
|
||||
// Verify that this leads to an error being shown (instead of progressing to branch selection).
|
||||
t.ExpectPopup().Alert().
|
||||
Title(Equals("Error")).
|
||||
Content(Contains("A remote named 'non-existing-remote' does not exist")).
|
||||
Confirm()
|
||||
},
|
||||
})
|
||||
@@ -1,74 +0,0 @@
|
||||
package branch
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var OpenPullRequestSelectRemoteAndTargetBranch = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Open up a pull request, specifying a remote and target branch",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {
|
||||
config.GetUserConfig().OS.OpenLink = "echo {{link}} > /tmp/openlink"
|
||||
},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
// Create an initial commit ('git branch set-upstream-to' bails out otherwise)
|
||||
shell.CreateFileAndAdd("file", "content1")
|
||||
shell.Commit("one")
|
||||
|
||||
// Create a new branch and a remote that has that branch
|
||||
shell.NewBranch("branch-1")
|
||||
shell.CloneIntoRemote("upstream")
|
||||
|
||||
// Create another branch and a second remote. The first remote doesn't have this branch.
|
||||
shell.NewBranch("branch-2")
|
||||
shell.CloneIntoRemote("origin")
|
||||
|
||||
// To allow a pull request to be created from a branch, it must have an upstream set.
|
||||
shell.SetBranchUpstream("branch-2", "origin/branch-2")
|
||||
|
||||
shell.RunCommand([]string{"git", "remote", "set-url", "origin", "https://github.com/my-personal-fork/lazygit"})
|
||||
shell.RunCommand([]string{"git", "remote", "set-url", "upstream", "https://github.com/jesseduffield/lazygit"})
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
// Open a PR for the current branch (i.e. 'branch-2')
|
||||
t.Views().
|
||||
Branches().
|
||||
Focus().
|
||||
Press(keys.Branches.ViewPullRequestOptions)
|
||||
|
||||
t.ExpectPopup().
|
||||
Menu().
|
||||
Title(Equals("View create pull request options")).
|
||||
Select(Contains("Select branch")).
|
||||
Confirm()
|
||||
|
||||
// Verify that we're prompted to enter the remote
|
||||
t.ExpectPopup().
|
||||
Prompt().
|
||||
Title(Equals("Select target remote")).
|
||||
SuggestionLines(
|
||||
Equals("origin"),
|
||||
Equals("upstream")).
|
||||
ConfirmSuggestion(Equals("upstream"))
|
||||
|
||||
// Verify that we're prompted to enter the target branch and that only those branches
|
||||
// present in the selected remote are listed as suggestions (i.e. 'branch-2' is not there).
|
||||
t.ExpectPopup().
|
||||
Prompt().
|
||||
Title(Equals("branch-2 → upstream/")).
|
||||
SuggestionLines(
|
||||
Equals("branch-1"),
|
||||
Equals("master")).
|
||||
ConfirmSuggestion(Equals("master"))
|
||||
|
||||
// Verify that the expected URL is used (by checking the openlink file)
|
||||
//
|
||||
// Please note that when targeting a different remote - like it's done here in this test -
|
||||
// the link is not yet correct. Thus, this test is expected to fail once this is fixed.
|
||||
t.FileSystem().FileContent(
|
||||
"/tmp/openlink",
|
||||
Equals("https://github.com/my-personal-fork/lazygit/compare/master...branch-2?expand=1\n"))
|
||||
},
|
||||
})
|
||||
@@ -1,25 +0,0 @@
|
||||
package branch
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
func checkRemoteBranches(t *TestDriver, keys config.KeybindingConfig, remoteName string, expectedBranches []string) {
|
||||
t.Views().Remotes().
|
||||
Focus().
|
||||
NavigateToLine(Contains(remoteName)).
|
||||
PressEnter()
|
||||
|
||||
t.Views().
|
||||
RemoteBranches().
|
||||
Lines(
|
||||
lo.Map(expectedBranches, func(branch string, _ int) *TextMatcher { return Equals(branch) })...,
|
||||
).
|
||||
Press(keys.Universal.Return)
|
||||
|
||||
t.Views().
|
||||
Branches().
|
||||
Focus()
|
||||
}
|
||||
@@ -10,12 +10,7 @@ var CommitWithPrefix = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(cfg *config.AppConfig) {
|
||||
cfg.GetUserConfig().Git.CommitPrefixes = map[string]config.CommitPrefixConfig{
|
||||
"repo": {
|
||||
Pattern: `^\w+/(\w+-\w+).*`,
|
||||
Replace: "[$1]: ",
|
||||
},
|
||||
}
|
||||
cfg.GetUserConfig().Git.CommitPrefixes = map[string]config.CommitPrefixConfig{"repo": {Pattern: "^\\w+\\/(\\w+-\\w+).*", Replace: "[$1]: "}}
|
||||
},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.NewBranch("feature/TEST-001")
|
||||
@@ -30,15 +25,6 @@ var CommitWithPrefix = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
PressPrimaryAction().
|
||||
Press(keys.Files.CommitChanges)
|
||||
|
||||
t.ExpectPopup().CommitMessagePanel().
|
||||
Title(Equals("Commit summary")).
|
||||
InitialText(Equals("[TEST-001]: ")).
|
||||
Cancel()
|
||||
|
||||
t.Views().Files().
|
||||
IsFocused().
|
||||
Press(keys.Files.CommitChanges)
|
||||
|
||||
t.ExpectPopup().CommitMessagePanel().
|
||||
Title(Equals("Commit summary")).
|
||||
InitialText(Equals("[TEST-001]: ")).
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
package interactive_rebase
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
"github.com/jesseduffield/lazygit/pkg/integration/tests/shared"
|
||||
)
|
||||
|
||||
var DropMergeCommit = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Drops a merge commit outside of an interactive rebase",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
GitVersion: AtLeast("2.22.0"), // first version that supports the --rebase-merges option
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shared.CreateMergeCommit(shell)
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("CI ⏣─╮ Merge branch 'second-change-branch' into first-change-branch").IsSelected(),
|
||||
Contains("CI │ ◯ * second-change-branch unrelated change"),
|
||||
Contains("CI │ ◯ second change"),
|
||||
Contains("CI ◯ │ first change"),
|
||||
Contains("CI ◯─╯ * original"),
|
||||
Contains("CI ◯ three"),
|
||||
Contains("CI ◯ two"),
|
||||
Contains("CI ◯ one"),
|
||||
).
|
||||
Press(keys.Universal.Remove).
|
||||
Tap(func() {
|
||||
t.ExpectPopup().Confirmation().
|
||||
Title(Equals("Drop commit")).
|
||||
Content(Equals("Are you sure you want to drop the selected merge commit? Note that it will also drop all the commits that were merged in by it.")).
|
||||
Confirm()
|
||||
}).
|
||||
Lines(
|
||||
Contains("CI ◯ first change").IsSelected(),
|
||||
Contains("CI ◯ * original"),
|
||||
Contains("CI ◯ three"),
|
||||
Contains("CI ◯ two"),
|
||||
Contains("CI ◯ one"),
|
||||
)
|
||||
},
|
||||
})
|
||||
@@ -38,6 +38,7 @@ var DropTodoCommitWithUpdateRef = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
).
|
||||
NavigateToLine(Contains("commit 02")).
|
||||
Press(keys.Universal.Edit).
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("pick").Contains("CI commit 07"),
|
||||
Contains("pick").Contains("CI commit 06"),
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
package interactive_rebase
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var EditAndAutoAmend = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Edit a commit, make a change and stage it, then continue the rebase to auto-amend the commit",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.
|
||||
CreateNCommits(3)
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("commit 03"),
|
||||
Contains("commit 02"),
|
||||
Contains("commit 01"),
|
||||
).
|
||||
NavigateToLine(Contains("commit 02")).
|
||||
Press(keys.Universal.Edit).
|
||||
Lines(
|
||||
Contains("commit 03"),
|
||||
MatchesRegexp("YOU ARE HERE.*commit 02").IsSelected(),
|
||||
Contains("commit 01"),
|
||||
)
|
||||
|
||||
t.Shell().CreateFile("fixup-file", "fixup content")
|
||||
t.Views().Files().
|
||||
Focus().
|
||||
Press(keys.Files.RefreshFiles).
|
||||
Lines(
|
||||
Contains("??").Contains("fixup-file").IsSelected(),
|
||||
).
|
||||
PressPrimaryAction()
|
||||
|
||||
t.Common().ContinueRebase()
|
||||
|
||||
t.Views().Commits().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("commit 03"),
|
||||
Contains("commit 02").IsSelected(),
|
||||
Contains("commit 01"),
|
||||
)
|
||||
|
||||
t.Views().Main().
|
||||
Content(Contains("fixup content"))
|
||||
},
|
||||
})
|
||||
@@ -1,74 +0,0 @@
|
||||
package interactive_rebase
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var EditLastCommitOfStackedBranch = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Edit and amend the last commit of a branch in a stack of branches, and ensure that it doesn't break the stack",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
GitVersion: AtLeast("2.38.0"),
|
||||
SetupConfig: func(config *config.AppConfig) {
|
||||
config.GetUserConfig().Git.MainBranches = []string{"master"}
|
||||
config.GetAppState().GitLogShowGraph = "never"
|
||||
},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.
|
||||
CreateNCommits(1).
|
||||
NewBranch("branch1").
|
||||
CreateNCommitsStartingAt(2, 2).
|
||||
NewBranch("branch2").
|
||||
CreateNCommitsStartingAt(2, 4)
|
||||
|
||||
shell.SetConfig("rebase.updateRefs", "true")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("CI commit 05").IsSelected(),
|
||||
Contains("CI commit 04"),
|
||||
Contains("CI * commit 03"),
|
||||
Contains("CI commit 02"),
|
||||
Contains("CI commit 01"),
|
||||
).
|
||||
NavigateToLine(Contains("commit 03")).
|
||||
Press(keys.Universal.Edit).
|
||||
Lines(
|
||||
Contains("pick").Contains("CI commit 05"),
|
||||
Contains("pick").Contains("CI commit 04"),
|
||||
Contains("update-ref").Contains("branch1"),
|
||||
Contains("<-- YOU ARE HERE --- * commit 03").IsSelected(),
|
||||
Contains("CI commit 02"),
|
||||
Contains("CI commit 01"),
|
||||
)
|
||||
|
||||
t.Shell().CreateFile("fixup-file", "fixup content")
|
||||
t.Views().Files().
|
||||
Focus().
|
||||
Press(keys.Files.RefreshFiles).
|
||||
Lines(
|
||||
Contains("??").Contains("fixup-file").IsSelected(),
|
||||
).
|
||||
PressPrimaryAction().
|
||||
Press(keys.Files.AmendLastCommit)
|
||||
t.ExpectPopup().Confirmation().
|
||||
Title(Equals("Amend last commit")).
|
||||
Content(Contains("Are you sure you want to amend last commit?")).
|
||||
Confirm()
|
||||
|
||||
t.Common().ContinueRebase()
|
||||
|
||||
t.Views().Commits().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("CI commit 05"),
|
||||
Contains("CI commit 04"),
|
||||
Contains("CI * commit 03"),
|
||||
Contains("CI commit 02"),
|
||||
Contains("CI commit 01"),
|
||||
)
|
||||
},
|
||||
})
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
package interactive_rebase
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
"github.com/jesseduffield/lazygit/pkg/integration/tests/shared"
|
||||
)
|
||||
|
||||
var EditRangeSelectDownToMergeOutsideRebase = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Select a range of commits (the last one being a merge commit) to edit outside of a rebase",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
GitVersion: AtLeast("2.22.0"), // first version that supports the --rebase-merges option
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shared.CreateMergeCommit(shell)
|
||||
shell.CreateNCommits(2)
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().
|
||||
Focus().
|
||||
TopLines(
|
||||
Contains("CI ◯ commit 02").IsSelected(),
|
||||
Contains("CI ◯ commit 01"),
|
||||
Contains("Merge branch 'second-change-branch' into first-change-branch"),
|
||||
).
|
||||
Press(keys.Universal.RangeSelectDown).
|
||||
Press(keys.Universal.RangeSelectDown).
|
||||
Press(keys.Universal.Edit).
|
||||
Lines(
|
||||
Contains("edit CI commit 02").IsSelected(),
|
||||
Contains("edit CI commit 01").IsSelected(),
|
||||
Contains(" CI ⏣─╮ <-- YOU ARE HERE --- Merge branch 'second-change-branch' into first-change-branch").IsSelected(),
|
||||
Contains(" CI │ ◯ * second-change-branch unrelated change"),
|
||||
Contains(" CI │ ◯ second change"),
|
||||
Contains(" CI ◯ │ first change"),
|
||||
Contains(" CI ◯─╯ * original"),
|
||||
Contains(" CI ◯ three"),
|
||||
Contains(" CI ◯ two"),
|
||||
Contains(" CI ◯ one"),
|
||||
)
|
||||
},
|
||||
})
|
||||
-47
@@ -1,47 +0,0 @@
|
||||
package interactive_rebase
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var MoveAcrossBranchBoundaryOutsideRebase = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Move a commit across a branch boundary in a stack of branches",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
GitVersion: AtLeast("2.38.0"),
|
||||
SetupConfig: func(config *config.AppConfig) {
|
||||
config.GetUserConfig().Git.MainBranches = []string{"master"}
|
||||
config.GetAppState().GitLogShowGraph = "never"
|
||||
},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.
|
||||
CreateNCommits(1).
|
||||
NewBranch("branch1").
|
||||
CreateNCommitsStartingAt(2, 2).
|
||||
NewBranch("branch2").
|
||||
CreateNCommitsStartingAt(2, 4)
|
||||
|
||||
shell.SetConfig("rebase.updateRefs", "true")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("CI commit 05").IsSelected(),
|
||||
Contains("CI commit 04"),
|
||||
Contains("CI * commit 03"),
|
||||
Contains("CI commit 02"),
|
||||
Contains("CI commit 01"),
|
||||
).
|
||||
NavigateToLine(Contains("commit 04")).
|
||||
Press(keys.Commits.MoveDownCommit).
|
||||
Lines(
|
||||
Contains("CI commit 05"),
|
||||
Contains("CI * commit 03"),
|
||||
Contains("CI commit 04").IsSelected(),
|
||||
Contains("CI commit 02"),
|
||||
Contains("CI commit 01"),
|
||||
)
|
||||
},
|
||||
})
|
||||
@@ -42,16 +42,13 @@ var tests = []*components.IntegrationTest{
|
||||
branch.CheckoutByName,
|
||||
branch.CreateTag,
|
||||
branch.Delete,
|
||||
branch.DeleteMultiple,
|
||||
branch.DeleteRemoteBranchWithCredentialPrompt,
|
||||
branch.DeleteRemoteBranchWithDifferentName,
|
||||
branch.DetachedHead,
|
||||
branch.NewBranchAutostash,
|
||||
branch.NewBranchFromRemoteTrackingDifferentName,
|
||||
branch.NewBranchFromRemoteTrackingSameName,
|
||||
branch.OpenPullRequestInvalidTargetRemoteName,
|
||||
branch.OpenPullRequestNoUpstream,
|
||||
branch.OpenPullRequestSelectRemoteAndTargetBranch,
|
||||
branch.OpenWithCliArg,
|
||||
branch.Rebase,
|
||||
branch.RebaseAbortOnConflict,
|
||||
@@ -208,14 +205,10 @@ var tests = []*components.IntegrationTest{
|
||||
interactive_rebase.DeleteUpdateRefTodo,
|
||||
interactive_rebase.DontShowBranchHeadsForTodoItems,
|
||||
interactive_rebase.DropCommitInCopiedBranchWithUpdateRef,
|
||||
interactive_rebase.DropMergeCommit,
|
||||
interactive_rebase.DropTodoCommitWithUpdateRef,
|
||||
interactive_rebase.DropWithCustomCommentChar,
|
||||
interactive_rebase.EditAndAutoAmend,
|
||||
interactive_rebase.EditFirstCommit,
|
||||
interactive_rebase.EditLastCommitOfStackedBranch,
|
||||
interactive_rebase.EditNonTodoCommitDuringRebase,
|
||||
interactive_rebase.EditRangeSelectDownToMergeOutsideRebase,
|
||||
interactive_rebase.EditRangeSelectOutsideRebase,
|
||||
interactive_rebase.EditTheConflCommit,
|
||||
interactive_rebase.FixupFirstCommit,
|
||||
@@ -223,7 +216,6 @@ var tests = []*components.IntegrationTest{
|
||||
interactive_rebase.InteractiveRebaseOfCopiedBranch,
|
||||
interactive_rebase.MidRebaseRangeSelect,
|
||||
interactive_rebase.Move,
|
||||
interactive_rebase.MoveAcrossBranchBoundaryOutsideRebase,
|
||||
interactive_rebase.MoveInRebase,
|
||||
interactive_rebase.MoveUpdateRefTodo,
|
||||
interactive_rebase.MoveWithCustomCommentChar,
|
||||
|
||||
@@ -20,7 +20,6 @@ func GetSchemaDir() string {
|
||||
func GenerateSchema() {
|
||||
schema := customReflect(&config.UserConfig{})
|
||||
obj, _ := json.MarshalIndent(schema, "", " ")
|
||||
obj = append(obj, '\n')
|
||||
|
||||
if err := os.WriteFile(GetSchemaDir()+"/config.json", obj, 0o644); err != nil {
|
||||
fmt.Println("Error writing to file:", err)
|
||||
|
||||
@@ -3,8 +3,6 @@ package utils
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
|
||||
"github.com/mattn/go-runewidth"
|
||||
)
|
||||
|
||||
// SplitLines takes a multiline string and splits it on newlines
|
||||
@@ -102,81 +100,3 @@ func ScanLinesAndTruncateWhenLongerThanBuffer(maxBufferSize int) func(data []byt
|
||||
return 0, nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap lines to a given width, and return:
|
||||
// - the wrapped lines
|
||||
// - the line indices of the wrapped lines, indexed by the original line indices
|
||||
// - the line indices of the original lines, indexed by the wrapped line indices
|
||||
// If wrap is false, the text is returned as is.
|
||||
// This code needs to behave the same as `gocui.lineWrap` does.
|
||||
func WrapViewLinesToWidth(wrap bool, text string, width int) ([]string, []int, []int) {
|
||||
lines := strings.Split(text, "\n")
|
||||
if !wrap {
|
||||
indices := make([]int, len(lines))
|
||||
for i := range lines {
|
||||
indices[i] = i
|
||||
}
|
||||
return lines, indices, indices
|
||||
}
|
||||
|
||||
wrappedLines := make([]string, 0, len(lines))
|
||||
wrappedLineIndices := make([]int, 0, len(lines))
|
||||
originalLineIndices := make([]int, 0, len(lines))
|
||||
|
||||
for originalLineIdx, line := range lines {
|
||||
wrappedLineIndices = append(wrappedLineIndices, len(wrappedLines))
|
||||
|
||||
// convert tabs to spaces
|
||||
for i := 0; i < len(line); i++ {
|
||||
if line[i] == '\t' {
|
||||
numSpaces := 4 - (i % 4)
|
||||
line = line[:i] + " "[:numSpaces] + line[i+1:]
|
||||
i += numSpaces - 1
|
||||
}
|
||||
}
|
||||
|
||||
appendWrappedLine := func(str string) {
|
||||
wrappedLines = append(wrappedLines, str)
|
||||
originalLineIndices = append(originalLineIndices, originalLineIdx)
|
||||
}
|
||||
|
||||
n := 0
|
||||
offset := 0
|
||||
lastWhitespaceIndex := -1
|
||||
for i, currChr := range line {
|
||||
rw := runewidth.RuneWidth(currChr)
|
||||
n += rw
|
||||
|
||||
if n > width {
|
||||
if currChr == ' ' {
|
||||
appendWrappedLine(line[offset:i])
|
||||
offset = i + 1
|
||||
n = 0
|
||||
} else if currChr == '-' {
|
||||
appendWrappedLine(line[offset:i])
|
||||
offset = i
|
||||
n = rw
|
||||
} else if lastWhitespaceIndex != -1 {
|
||||
if line[lastWhitespaceIndex] == '-' {
|
||||
appendWrappedLine(line[offset : lastWhitespaceIndex+1])
|
||||
} else {
|
||||
appendWrappedLine(line[offset:lastWhitespaceIndex])
|
||||
}
|
||||
offset = lastWhitespaceIndex + 1
|
||||
n = runewidth.StringWidth(line[offset : i+1])
|
||||
} else {
|
||||
appendWrappedLine(line[offset:i])
|
||||
offset = i
|
||||
n = rw
|
||||
}
|
||||
lastWhitespaceIndex = -1
|
||||
} else if currChr == ' ' || currChr == '-' {
|
||||
lastWhitespaceIndex = i
|
||||
}
|
||||
}
|
||||
|
||||
appendWrappedLine(line[offset:])
|
||||
}
|
||||
|
||||
return wrappedLines, wrappedLineIndices, originalLineIndices
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -165,238 +164,3 @@ func TestScanLinesAndTruncateWhenLongerThanBuffer(t *testing.T) {
|
||||
assert.EqualValues(t, s.expectedLines, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrapViewLinesToWidth(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
wrap bool
|
||||
text string
|
||||
width int
|
||||
expectedWrappedLines []string
|
||||
expectedWrappedLinesIndices []int
|
||||
expectedOriginalLinesIndices []int
|
||||
}{
|
||||
{
|
||||
name: "Wrap off",
|
||||
wrap: false,
|
||||
text: "1st line\n2nd line\n3rd line",
|
||||
width: 5,
|
||||
expectedWrappedLines: []string{
|
||||
"1st line",
|
||||
"2nd line",
|
||||
"3rd line",
|
||||
},
|
||||
expectedWrappedLinesIndices: []int{0, 1, 2},
|
||||
expectedOriginalLinesIndices: []int{0, 1, 2},
|
||||
},
|
||||
{
|
||||
name: "Wrap on space",
|
||||
wrap: true,
|
||||
text: "Hello World",
|
||||
width: 5,
|
||||
expectedWrappedLines: []string{
|
||||
"Hello",
|
||||
"World",
|
||||
},
|
||||
expectedWrappedLinesIndices: []int{0},
|
||||
expectedOriginalLinesIndices: []int{0, 0},
|
||||
},
|
||||
{
|
||||
name: "Wrap on hyphen",
|
||||
wrap: true,
|
||||
text: "Hello-World",
|
||||
width: 6,
|
||||
expectedWrappedLines: []string{
|
||||
"Hello-",
|
||||
"World",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Wrap on hyphen 2",
|
||||
wrap: true,
|
||||
text: "Blah Hello-World",
|
||||
width: 12,
|
||||
expectedWrappedLines: []string{
|
||||
"Blah Hello-",
|
||||
"World",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Wrap on hyphen 3",
|
||||
wrap: true,
|
||||
text: "Blah Hello-World",
|
||||
width: 11,
|
||||
expectedWrappedLines: []string{
|
||||
"Blah Hello-",
|
||||
"World",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Wrap on hyphen 4",
|
||||
wrap: true,
|
||||
text: "Blah Hello-World",
|
||||
width: 10,
|
||||
expectedWrappedLines: []string{
|
||||
"Blah Hello",
|
||||
"-World",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Wrap on space 2",
|
||||
wrap: true,
|
||||
text: "Blah Hello World",
|
||||
width: 10,
|
||||
expectedWrappedLines: []string{
|
||||
"Blah Hello",
|
||||
"World",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Wrap on space with more words",
|
||||
wrap: true,
|
||||
text: "Longer word here",
|
||||
width: 10,
|
||||
expectedWrappedLines: []string{
|
||||
"Longer",
|
||||
"word here",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Split word that's too long",
|
||||
wrap: true,
|
||||
text: "ThisWordIsWayTooLong",
|
||||
width: 10,
|
||||
expectedWrappedLines: []string{
|
||||
"ThisWordIs",
|
||||
"WayTooLong",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Split word that's too long over multiple lines",
|
||||
wrap: true,
|
||||
text: "ThisWordIsWayTooLong",
|
||||
width: 5,
|
||||
expectedWrappedLines: []string{
|
||||
"ThisW",
|
||||
"ordIs",
|
||||
"WayTo",
|
||||
"oLong",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Lots of hyphens",
|
||||
wrap: true,
|
||||
text: "one-two-three-four-five",
|
||||
width: 8,
|
||||
expectedWrappedLines: []string{
|
||||
"one-two-",
|
||||
"three-",
|
||||
"four-",
|
||||
"five",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Several lines using all the available width",
|
||||
wrap: true,
|
||||
text: "aaa bb cc ddd-ee ff",
|
||||
width: 5,
|
||||
expectedWrappedLines: []string{
|
||||
"aaa",
|
||||
"bb cc",
|
||||
"ddd-",
|
||||
"ee ff",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Several lines using all the available width, with multi-cell runes",
|
||||
wrap: true,
|
||||
text: "🐤🐤🐤 🐝🐝 🙉🙉 🦊🦊🦊-🐬🐬 🦢🦢",
|
||||
width: 9,
|
||||
expectedWrappedLines: []string{
|
||||
"🐤🐤🐤",
|
||||
"🐝🐝 🙉🙉",
|
||||
"🦊🦊🦊-",
|
||||
"🐬🐬 🦢🦢",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Space in last column",
|
||||
wrap: true,
|
||||
text: "hello world",
|
||||
width: 6,
|
||||
expectedWrappedLines: []string{
|
||||
"hello",
|
||||
"world",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Hyphen in last column",
|
||||
wrap: true,
|
||||
text: "hello-world",
|
||||
width: 6,
|
||||
expectedWrappedLines: []string{
|
||||
"hello-",
|
||||
"world",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "English text",
|
||||
wrap: true,
|
||||
text: "+The sea reach of the Thames stretched before us like the bedinnind of an interminable waterway. In the offind the sea and the sky were welded todether without a joint, and in the luminous space the tanned sails of the bardes drifting blah blah",
|
||||
width: 81,
|
||||
expectedWrappedLines: []string{
|
||||
"+The sea reach of the Thames stretched before us like the bedinnind of an",
|
||||
"interminable waterway. In the offind the sea and the sky were welded todether",
|
||||
"without a joint, and in the luminous space the tanned sails of the bardes",
|
||||
"drifting blah blah",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Tabs",
|
||||
wrap: true,
|
||||
text: "\ta\tbb\tccc\tdddd\teeeee",
|
||||
width: 50,
|
||||
expectedWrappedLines: []string{
|
||||
" a bb ccc dddd eeeee",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Multiple lines",
|
||||
wrap: true,
|
||||
text: "First paragraph\nThe second paragraph is a bit longer.\nThird paragraph\n",
|
||||
width: 10,
|
||||
expectedWrappedLines: []string{
|
||||
"First",
|
||||
"paragraph",
|
||||
"The second",
|
||||
"paragraph",
|
||||
"is a bit",
|
||||
"longer.",
|
||||
"Third",
|
||||
"paragraph",
|
||||
"",
|
||||
},
|
||||
expectedWrappedLinesIndices: []int{0, 2, 6, 8},
|
||||
expectedOriginalLinesIndices: []int{0, 0, 1, 1, 1, 1, 2, 2, 3},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
wrappedLines, wrappedLinesIndices, originalLinesIndices := WrapViewLinesToWidth(tt.wrap, tt.text, tt.width)
|
||||
assert.Equal(t, tt.expectedWrappedLines, wrappedLines)
|
||||
if tt.expectedWrappedLinesIndices != nil {
|
||||
assert.Equal(t, tt.expectedWrappedLinesIndices, wrappedLinesIndices)
|
||||
}
|
||||
if tt.expectedOriginalLinesIndices != nil {
|
||||
assert.Equal(t, tt.expectedOriginalLinesIndices, originalLinesIndices)
|
||||
}
|
||||
|
||||
// As a sanity check, also test that gocui's line wrapping behaves the same way
|
||||
view := gocui.NewView("", 0, 0, tt.width+1, 1000, gocui.OutputNormal)
|
||||
assert.Equal(t, tt.width, view.InnerWidth())
|
||||
view.Wrap = tt.wrap
|
||||
view.SetContent(tt.text)
|
||||
assert.Equal(t, wrappedLines, view.ViewBufferLines())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+32
-52
@@ -6,18 +6,24 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/samber/lo"
|
||||
"github.com/stefanhaller/git-todo-parser/todo"
|
||||
)
|
||||
|
||||
type Todo struct {
|
||||
Hash string // for todos that have one, e.g. pick, drop, fixup, etc.
|
||||
Ref string // for update-ref todos
|
||||
Hash string // for todos that have one, e.g. pick, drop, fixup, etc.
|
||||
Ref string // for update-ref todos
|
||||
Action todo.TodoCommand
|
||||
}
|
||||
|
||||
// In order to change a TODO in git-rebase-todo, we need to specify the old action,
|
||||
// because sometimes the same hash appears multiple times in the file (e.g. in a pick
|
||||
// and later in a merge)
|
||||
type TodoChange struct {
|
||||
Hash string
|
||||
OldAction todo.TodoCommand
|
||||
NewAction todo.TodoCommand
|
||||
}
|
||||
|
||||
@@ -34,7 +40,7 @@ func EditRebaseTodo(filePath string, changes []TodoChange, commentChar byte) err
|
||||
t := &todos[i]
|
||||
// This is a nested loop, but it's ok because the number of todos should be small
|
||||
for _, change := range changes {
|
||||
if equalHash(t.Commit, change.Hash) {
|
||||
if t.Command == change.OldAction && equalHash(t.Commit, change.Hash) {
|
||||
matchCount++
|
||||
t.Command = change.NewAction
|
||||
}
|
||||
@@ -50,18 +56,18 @@ func EditRebaseTodo(filePath string, changes []TodoChange, commentChar byte) err
|
||||
}
|
||||
|
||||
func equalHash(a, b string) bool {
|
||||
if len(a) == 0 && len(b) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
commonLength := min(len(a), len(b))
|
||||
return commonLength > 0 && a[:commonLength] == b[:commonLength]
|
||||
return strings.HasPrefix(a, b) || strings.HasPrefix(b, a)
|
||||
}
|
||||
|
||||
func findTodo(todos []todo.Todo, todoToFind Todo) (int, bool) {
|
||||
_, idx, ok := lo.FindIndexOf(todos, func(t todo.Todo) bool {
|
||||
// For update-ref todos we also must compare the Ref (they have an empty hash)
|
||||
return equalHash(t.Commit, todoToFind.Hash) && t.Ref == todoToFind.Ref
|
||||
// Comparing just the hash is not enough; we need to compare both the
|
||||
// action and the hash, as the hash could appear multiple times (e.g. in a
|
||||
// pick and later in a merge). For update-ref todos we also must compare
|
||||
// the Ref.
|
||||
return t.Command == todoToFind.Action &&
|
||||
equalHash(t.Commit, todoToFind.Hash) &&
|
||||
t.Ref == todoToFind.Ref
|
||||
})
|
||||
return idx, ok
|
||||
}
|
||||
@@ -141,41 +147,41 @@ func deleteTodos(todos []todo.Todo, todosToDelete []Todo) ([]todo.Todo, error) {
|
||||
return todos, nil
|
||||
}
|
||||
|
||||
func MoveTodosDown(fileName string, todosToMove []Todo, isInRebase bool, commentChar byte) error {
|
||||
func MoveTodosDown(fileName string, todosToMove []Todo, commentChar byte) error {
|
||||
todos, err := ReadRebaseTodoFile(fileName, commentChar)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rearrangedTodos, err := moveTodosDown(todos, todosToMove, isInRebase)
|
||||
rearrangedTodos, err := moveTodosDown(todos, todosToMove)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return WriteRebaseTodoFile(fileName, rearrangedTodos, commentChar)
|
||||
}
|
||||
|
||||
func MoveTodosUp(fileName string, todosToMove []Todo, isInRebase bool, commentChar byte) error {
|
||||
func MoveTodosUp(fileName string, todosToMove []Todo, commentChar byte) error {
|
||||
todos, err := ReadRebaseTodoFile(fileName, commentChar)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rearrangedTodos, err := moveTodosUp(todos, todosToMove, isInRebase)
|
||||
rearrangedTodos, err := moveTodosUp(todos, todosToMove)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return WriteRebaseTodoFile(fileName, rearrangedTodos, commentChar)
|
||||
}
|
||||
|
||||
func moveTodoDown(todos []todo.Todo, todoToMove Todo, isInRebase bool) ([]todo.Todo, error) {
|
||||
rearrangedTodos, err := moveTodoUp(lo.Reverse(todos), todoToMove, isInRebase)
|
||||
func moveTodoDown(todos []todo.Todo, todoToMove Todo) ([]todo.Todo, error) {
|
||||
rearrangedTodos, err := moveTodoUp(lo.Reverse(todos), todoToMove)
|
||||
return lo.Reverse(rearrangedTodos), err
|
||||
}
|
||||
|
||||
func moveTodosDown(todos []todo.Todo, todosToMove []Todo, isInRebase bool) ([]todo.Todo, error) {
|
||||
rearrangedTodos, err := moveTodosUp(lo.Reverse(todos), lo.Reverse(todosToMove), isInRebase)
|
||||
func moveTodosDown(todos []todo.Todo, todosToMove []Todo) ([]todo.Todo, error) {
|
||||
rearrangedTodos, err := moveTodosUp(lo.Reverse(todos), lo.Reverse(todosToMove))
|
||||
return lo.Reverse(rearrangedTodos), err
|
||||
}
|
||||
|
||||
func moveTodoUp(todos []todo.Todo, todoToMove Todo, isInRebase bool) ([]todo.Todo, error) {
|
||||
func moveTodoUp(todos []todo.Todo, todoToMove Todo) ([]todo.Todo, error) {
|
||||
sourceIdx, ok := findTodo(todos, todoToMove)
|
||||
|
||||
if !ok {
|
||||
@@ -188,7 +194,7 @@ func moveTodoUp(todos []todo.Todo, todoToMove Todo, isInRebase bool) ([]todo.Tod
|
||||
// the end of the slice)
|
||||
|
||||
// Find the next todo that we show in lazygit's commits view (skipping the rest)
|
||||
_, skip, ok := lo.FindIndexOf(todos[sourceIdx+1:], func(t todo.Todo) bool { return isRenderedTodo(t, isInRebase) })
|
||||
_, skip, ok := lo.FindIndexOf(todos[sourceIdx+1:], isRenderedTodo)
|
||||
|
||||
if !ok {
|
||||
// We expect callers to guard against this
|
||||
@@ -202,10 +208,10 @@ func moveTodoUp(todos []todo.Todo, todoToMove Todo, isInRebase bool) ([]todo.Tod
|
||||
return rearrangedTodos, nil
|
||||
}
|
||||
|
||||
func moveTodosUp(todos []todo.Todo, todosToMove []Todo, isInRebase bool) ([]todo.Todo, error) {
|
||||
func moveTodosUp(todos []todo.Todo, todosToMove []Todo) ([]todo.Todo, error) {
|
||||
for _, todoToMove := range todosToMove {
|
||||
var newTodos []todo.Todo
|
||||
newTodos, err := moveTodoUp(todos, todoToMove, isInRebase)
|
||||
newTodos, err := moveTodoUp(todos, todoToMove)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -286,33 +292,7 @@ func RemoveUpdateRefsForCopiedBranch(fileName string, commentChar byte) error {
|
||||
}
|
||||
|
||||
// We render a todo in the commits view if it's a commit or if it's an
|
||||
// update-ref or exec. We don't render label, reset, or comment lines.
|
||||
func isRenderedTodo(t todo.Todo, isInRebase bool) bool {
|
||||
return t.Commit != "" || (isInRebase && (t.Command == todo.UpdateRef || t.Command == todo.Exec))
|
||||
}
|
||||
|
||||
func DropMergeCommit(fileName string, hash string, commentChar byte) error {
|
||||
todos, err := ReadRebaseTodoFile(fileName, commentChar)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newTodos, err := dropMergeCommit(todos, hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return WriteRebaseTodoFile(fileName, newTodos, commentChar)
|
||||
}
|
||||
|
||||
func dropMergeCommit(todos []todo.Todo, hash string) ([]todo.Todo, error) {
|
||||
isMerge := func(t todo.Todo) bool {
|
||||
return t.Command == todo.Merge && t.Flag == "-C" && equalHash(t.Commit, hash)
|
||||
}
|
||||
if lo.CountBy(todos, isMerge) != 1 {
|
||||
return nil, fmt.Errorf("Expected exactly one merge commit with hash %s", hash)
|
||||
}
|
||||
|
||||
_, idx, _ := lo.FindIndexOf(todos, isMerge)
|
||||
return slices.Delete(todos, idx, idx+1), nil
|
||||
// update-ref. We don't render label, reset, or comment lines.
|
||||
func isRenderedTodo(t todo.Todo) bool {
|
||||
return t.Commit != "" || t.Command == todo.UpdateRef
|
||||
}
|
||||
|
||||
+19
-141
@@ -2,7 +2,6 @@ package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stefanhaller/git-todo-parser/todo"
|
||||
@@ -14,7 +13,6 @@ func TestRebaseCommands_moveTodoDown(t *testing.T) {
|
||||
testName string
|
||||
todos []todo.Todo
|
||||
todoToMoveDown Todo
|
||||
isInRebase bool
|
||||
expectedErr string
|
||||
expectedTodos []todo.Todo
|
||||
}
|
||||
@@ -27,7 +25,7 @@ func TestRebaseCommands_moveTodoDown(t *testing.T) {
|
||||
{Command: todo.Pick, Commit: "5678"},
|
||||
{Command: todo.Pick, Commit: "abcd"},
|
||||
},
|
||||
todoToMoveDown: Todo{Hash: "5678"},
|
||||
todoToMoveDown: Todo{Hash: "5678", Action: todo.Pick},
|
||||
expectedErr: "",
|
||||
expectedTodos: []todo.Todo{
|
||||
{Command: todo.Pick, Commit: "5678"},
|
||||
@@ -42,7 +40,7 @@ func TestRebaseCommands_moveTodoDown(t *testing.T) {
|
||||
{Command: todo.Pick, Commit: "5678"},
|
||||
{Command: todo.Pick, Commit: "abcd"},
|
||||
},
|
||||
todoToMoveDown: Todo{Hash: "abcd"},
|
||||
todoToMoveDown: Todo{Hash: "abcd", Action: todo.Pick},
|
||||
expectedErr: "",
|
||||
expectedTodos: []todo.Todo{
|
||||
{Command: todo.Pick, Commit: "1234"},
|
||||
@@ -57,7 +55,7 @@ func TestRebaseCommands_moveTodoDown(t *testing.T) {
|
||||
{Command: todo.Pick, Commit: "5678"},
|
||||
{Command: todo.UpdateRef, Ref: "refs/heads/some_branch"},
|
||||
},
|
||||
todoToMoveDown: Todo{Ref: "refs/heads/some_branch"},
|
||||
todoToMoveDown: Todo{Ref: "refs/heads/some_branch", Action: todo.UpdateRef},
|
||||
expectedErr: "",
|
||||
expectedTodos: []todo.Todo{
|
||||
{Command: todo.Pick, Commit: "1234"},
|
||||
@@ -65,54 +63,6 @@ func TestRebaseCommands_moveTodoDown(t *testing.T) {
|
||||
{Command: todo.Pick, Commit: "5678"},
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "move across update-ref todo in rebase",
|
||||
todos: []todo.Todo{
|
||||
{Command: todo.Pick, Commit: "1234"},
|
||||
{Command: todo.UpdateRef, Ref: "refs/heads/some_branch"},
|
||||
{Command: todo.Pick, Commit: "5678"},
|
||||
},
|
||||
todoToMoveDown: Todo{Hash: "5678"},
|
||||
isInRebase: true,
|
||||
expectedErr: "",
|
||||
expectedTodos: []todo.Todo{
|
||||
{Command: todo.Pick, Commit: "1234"},
|
||||
{Command: todo.Pick, Commit: "5678"},
|
||||
{Command: todo.UpdateRef, Ref: "refs/heads/some_branch"},
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "move across update-ref todo outside of rebase",
|
||||
todos: []todo.Todo{
|
||||
{Command: todo.Pick, Commit: "1234"},
|
||||
{Command: todo.UpdateRef, Ref: "refs/heads/some_branch"},
|
||||
{Command: todo.Pick, Commit: "5678"},
|
||||
},
|
||||
todoToMoveDown: Todo{Hash: "5678"},
|
||||
isInRebase: false,
|
||||
expectedErr: "",
|
||||
expectedTodos: []todo.Todo{
|
||||
{Command: todo.Pick, Commit: "5678"},
|
||||
{Command: todo.Pick, Commit: "1234"},
|
||||
{Command: todo.UpdateRef, Ref: "refs/heads/some_branch"},
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "move across exec todo",
|
||||
todos: []todo.Todo{
|
||||
{Command: todo.Pick, Commit: "1234"},
|
||||
{Command: todo.Exec, ExecCommand: "make test"},
|
||||
{Command: todo.Pick, Commit: "5678"},
|
||||
},
|
||||
todoToMoveDown: Todo{Hash: "5678"},
|
||||
isInRebase: true,
|
||||
expectedErr: "",
|
||||
expectedTodos: []todo.Todo{
|
||||
{Command: todo.Pick, Commit: "1234"},
|
||||
{Command: todo.Pick, Commit: "5678"},
|
||||
{Command: todo.Exec, ExecCommand: "make test"},
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "skip an invisible todo",
|
||||
todos: []todo.Todo{
|
||||
@@ -122,7 +72,7 @@ func TestRebaseCommands_moveTodoDown(t *testing.T) {
|
||||
{Command: todo.Pick, Commit: "5678"},
|
||||
{Command: todo.Pick, Commit: "def0"},
|
||||
},
|
||||
todoToMoveDown: Todo{Hash: "5678"},
|
||||
todoToMoveDown: Todo{Hash: "5678", Action: todo.Pick},
|
||||
expectedErr: "",
|
||||
expectedTodos: []todo.Todo{
|
||||
{Command: todo.Pick, Commit: "1234"},
|
||||
@@ -141,7 +91,7 @@ func TestRebaseCommands_moveTodoDown(t *testing.T) {
|
||||
{Command: todo.Pick, Commit: "5678"},
|
||||
{Command: todo.Pick, Commit: "abcd"},
|
||||
},
|
||||
todoToMoveDown: Todo{Hash: "def0"},
|
||||
todoToMoveDown: Todo{Hash: "def0", Action: todo.Pick},
|
||||
expectedErr: "Todo def0 not found in git-rebase-todo",
|
||||
expectedTodos: []todo.Todo{},
|
||||
},
|
||||
@@ -152,7 +102,7 @@ func TestRebaseCommands_moveTodoDown(t *testing.T) {
|
||||
{Command: todo.Pick, Commit: "5678"},
|
||||
{Command: todo.Pick, Commit: "abcd"},
|
||||
},
|
||||
todoToMoveDown: Todo{Hash: "1234"},
|
||||
todoToMoveDown: Todo{Hash: "1234", Action: todo.Pick},
|
||||
expectedErr: "Destination position for moving todo is out of range",
|
||||
expectedTodos: []todo.Todo{},
|
||||
},
|
||||
@@ -164,7 +114,7 @@ func TestRebaseCommands_moveTodoDown(t *testing.T) {
|
||||
{Command: todo.Pick, Commit: "1234"},
|
||||
{Command: todo.Pick, Commit: "5678"},
|
||||
},
|
||||
todoToMoveDown: Todo{Hash: "1234"},
|
||||
todoToMoveDown: Todo{Hash: "1234", Action: todo.Pick},
|
||||
expectedErr: "Destination position for moving todo is out of range",
|
||||
expectedTodos: []todo.Todo{},
|
||||
},
|
||||
@@ -172,7 +122,7 @@ func TestRebaseCommands_moveTodoDown(t *testing.T) {
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
rearrangedTodos, err := moveTodoDown(s.todos, s.todoToMoveDown, s.isInRebase)
|
||||
rearrangedTodos, err := moveTodoDown(s.todos, s.todoToMoveDown)
|
||||
if s.expectedErr == "" {
|
||||
assert.NoError(t, err)
|
||||
} else {
|
||||
@@ -189,7 +139,6 @@ func TestRebaseCommands_moveTodoUp(t *testing.T) {
|
||||
testName string
|
||||
todos []todo.Todo
|
||||
todoToMoveUp Todo
|
||||
isInRebase bool
|
||||
expectedErr string
|
||||
expectedTodos []todo.Todo
|
||||
}
|
||||
@@ -202,7 +151,7 @@ func TestRebaseCommands_moveTodoUp(t *testing.T) {
|
||||
{Command: todo.Pick, Commit: "5678"},
|
||||
{Command: todo.Pick, Commit: "abcd"},
|
||||
},
|
||||
todoToMoveUp: Todo{Hash: "5678"},
|
||||
todoToMoveUp: Todo{Hash: "5678", Action: todo.Pick},
|
||||
expectedErr: "",
|
||||
expectedTodos: []todo.Todo{
|
||||
{Command: todo.Pick, Commit: "1234"},
|
||||
@@ -217,7 +166,7 @@ func TestRebaseCommands_moveTodoUp(t *testing.T) {
|
||||
{Command: todo.Pick, Commit: "5678"},
|
||||
{Command: todo.Pick, Commit: "abcd"},
|
||||
},
|
||||
todoToMoveUp: Todo{Hash: "1234"},
|
||||
todoToMoveUp: Todo{Hash: "1234", Action: todo.Pick},
|
||||
expectedErr: "",
|
||||
expectedTodos: []todo.Todo{
|
||||
{Command: todo.Pick, Commit: "5678"},
|
||||
@@ -232,7 +181,7 @@ func TestRebaseCommands_moveTodoUp(t *testing.T) {
|
||||
{Command: todo.UpdateRef, Ref: "refs/heads/some_branch"},
|
||||
{Command: todo.Pick, Commit: "5678"},
|
||||
},
|
||||
todoToMoveUp: Todo{Ref: "refs/heads/some_branch"},
|
||||
todoToMoveUp: Todo{Ref: "refs/heads/some_branch", Action: todo.UpdateRef},
|
||||
expectedErr: "",
|
||||
expectedTodos: []todo.Todo{
|
||||
{Command: todo.Pick, Commit: "1234"},
|
||||
@@ -240,54 +189,6 @@ func TestRebaseCommands_moveTodoUp(t *testing.T) {
|
||||
{Command: todo.UpdateRef, Ref: "refs/heads/some_branch"},
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "move across update-ref todo in rebase",
|
||||
todos: []todo.Todo{
|
||||
{Command: todo.Pick, Commit: "1234"},
|
||||
{Command: todo.UpdateRef, Ref: "refs/heads/some_branch"},
|
||||
{Command: todo.Pick, Commit: "5678"},
|
||||
},
|
||||
todoToMoveUp: Todo{Hash: "1234"},
|
||||
isInRebase: true,
|
||||
expectedErr: "",
|
||||
expectedTodos: []todo.Todo{
|
||||
{Command: todo.UpdateRef, Ref: "refs/heads/some_branch"},
|
||||
{Command: todo.Pick, Commit: "1234"},
|
||||
{Command: todo.Pick, Commit: "5678"},
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "move across update-ref todo outside of rebase",
|
||||
todos: []todo.Todo{
|
||||
{Command: todo.Pick, Commit: "1234"},
|
||||
{Command: todo.UpdateRef, Ref: "refs/heads/some_branch"},
|
||||
{Command: todo.Pick, Commit: "5678"},
|
||||
},
|
||||
todoToMoveUp: Todo{Hash: "1234"},
|
||||
isInRebase: false,
|
||||
expectedErr: "",
|
||||
expectedTodos: []todo.Todo{
|
||||
{Command: todo.UpdateRef, Ref: "refs/heads/some_branch"},
|
||||
{Command: todo.Pick, Commit: "5678"},
|
||||
{Command: todo.Pick, Commit: "1234"},
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "move across exec todo",
|
||||
todos: []todo.Todo{
|
||||
{Command: todo.Pick, Commit: "1234"},
|
||||
{Command: todo.Exec, ExecCommand: "make test"},
|
||||
{Command: todo.Pick, Commit: "5678"},
|
||||
},
|
||||
todoToMoveUp: Todo{Hash: "1234"},
|
||||
isInRebase: true,
|
||||
expectedErr: "",
|
||||
expectedTodos: []todo.Todo{
|
||||
{Command: todo.Exec, ExecCommand: "make test"},
|
||||
{Command: todo.Pick, Commit: "1234"},
|
||||
{Command: todo.Pick, Commit: "5678"},
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "skip an invisible todo",
|
||||
todos: []todo.Todo{
|
||||
@@ -297,7 +198,7 @@ func TestRebaseCommands_moveTodoUp(t *testing.T) {
|
||||
{Command: todo.Pick, Commit: "5678"},
|
||||
{Command: todo.Pick, Commit: "def0"},
|
||||
},
|
||||
todoToMoveUp: Todo{Hash: "abcd"},
|
||||
todoToMoveUp: Todo{Hash: "abcd", Action: todo.Pick},
|
||||
expectedErr: "",
|
||||
expectedTodos: []todo.Todo{
|
||||
{Command: todo.Pick, Commit: "1234"},
|
||||
@@ -316,7 +217,7 @@ func TestRebaseCommands_moveTodoUp(t *testing.T) {
|
||||
{Command: todo.Pick, Commit: "5678"},
|
||||
{Command: todo.Pick, Commit: "abcd"},
|
||||
},
|
||||
todoToMoveUp: Todo{Hash: "def0"},
|
||||
todoToMoveUp: Todo{Hash: "def0", Action: todo.Pick},
|
||||
expectedErr: "Todo def0 not found in git-rebase-todo",
|
||||
expectedTodos: []todo.Todo{},
|
||||
},
|
||||
@@ -327,7 +228,7 @@ func TestRebaseCommands_moveTodoUp(t *testing.T) {
|
||||
{Command: todo.Pick, Commit: "5678"},
|
||||
{Command: todo.Pick, Commit: "abcd"},
|
||||
},
|
||||
todoToMoveUp: Todo{Hash: "abcd"},
|
||||
todoToMoveUp: Todo{Hash: "abcd", Action: todo.Pick},
|
||||
expectedErr: "Destination position for moving todo is out of range",
|
||||
expectedTodos: []todo.Todo{},
|
||||
},
|
||||
@@ -339,7 +240,7 @@ func TestRebaseCommands_moveTodoUp(t *testing.T) {
|
||||
{Command: todo.Label, Label: "myLabel"},
|
||||
{Command: todo.Reset, Label: "otherlabel"},
|
||||
},
|
||||
todoToMoveUp: Todo{Hash: "5678"},
|
||||
todoToMoveUp: Todo{Hash: "5678", Action: todo.Pick},
|
||||
expectedErr: "Destination position for moving todo is out of range",
|
||||
expectedTodos: []todo.Todo{},
|
||||
},
|
||||
@@ -347,7 +248,7 @@ func TestRebaseCommands_moveTodoUp(t *testing.T) {
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
rearrangedTodos, err := moveTodoUp(s.todos, s.todoToMoveUp, s.isInRebase)
|
||||
rearrangedTodos, err := moveTodoUp(s.todos, s.todoToMoveUp)
|
||||
if s.expectedErr == "" {
|
||||
assert.NoError(t, err)
|
||||
} else {
|
||||
@@ -515,8 +416,8 @@ func TestRebaseCommands_deleteTodos(t *testing.T) {
|
||||
{Command: todo.Pick, Commit: "abcd"},
|
||||
},
|
||||
todosToDelete: []Todo{
|
||||
{Ref: "refs/heads/some_branch"},
|
||||
{Hash: "abcd"},
|
||||
{Ref: "refs/heads/some_branch", Action: todo.UpdateRef},
|
||||
{Hash: "abcd", Action: todo.Pick},
|
||||
},
|
||||
expectedTodos: []todo.Todo{
|
||||
{Command: todo.Pick, Commit: "1234"},
|
||||
@@ -531,7 +432,7 @@ func TestRebaseCommands_deleteTodos(t *testing.T) {
|
||||
{Command: todo.Pick, Commit: "5678"},
|
||||
},
|
||||
todosToDelete: []Todo{
|
||||
{Hash: "abcd"},
|
||||
{Hash: "abcd", Action: todo.Pick},
|
||||
},
|
||||
expectedTodos: []todo.Todo{},
|
||||
expectedErr: errors.New("Todo abcd not found in git-rebase-todo"),
|
||||
@@ -552,26 +453,3 @@ func TestRebaseCommands_deleteTodos(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_equalHash(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
a string
|
||||
b string
|
||||
expected bool
|
||||
}{
|
||||
{"", "", true},
|
||||
{"", "123", false},
|
||||
{"123", "", false},
|
||||
{"123", "123", true},
|
||||
{"123", "123abc", true},
|
||||
{"123abc", "123", true},
|
||||
{"123", "a", false},
|
||||
{"1", "abc", false},
|
||||
}
|
||||
|
||||
for _, scenario := range scenarios {
|
||||
t.Run(fmt.Sprintf("'%s' vs. '%s'", scenario.a, scenario.b), func(t *testing.T) {
|
||||
assert.Equal(t, scenario.expected, equalHash(scenario.a, scenario.b))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+2
-12
@@ -96,11 +96,6 @@
|
||||
"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"
|
||||
},
|
||||
"wrapLinesInStagingView": {
|
||||
"type": "boolean",
|
||||
"description": "If true, wrap lines in the staging view to the width of the view. This\nmakes it much easier to work with diffs that have long lines, e.g.\nparagraphs of markdown text.",
|
||||
"default": true
|
||||
},
|
||||
"language": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
@@ -298,11 +293,6 @@
|
||||
"description": "If true, display the files in the file views as a tree. If false, display the files as a flat list.\nThis can be toggled from within Lazygit with the '~' key, but that will not change the default.",
|
||||
"default": true
|
||||
},
|
||||
"showNumstatInFilesView": {
|
||||
"type": "boolean",
|
||||
"description": "If true, show the number of lines changed per file in the Files view",
|
||||
"default": false
|
||||
},
|
||||
"showRandomTip": {
|
||||
"type": "boolean",
|
||||
"description": "If true, show a random tip in the command log when Lazygit starts",
|
||||
@@ -610,7 +600,7 @@
|
||||
},
|
||||
"allBranchesLogCmd": {
|
||||
"type": "string",
|
||||
"description": "Command used to display git log of all branches in the main window.\nDeprecated: Use `allBranchesLogCmds` instead.",
|
||||
"description": "Command used to display git log of all branches in the main window.\nDeprecated: User `allBranchesLogCmds` instead.",
|
||||
"default": "git log --graph --all --color=always --abbrev-commit --decorate --date=relative --pretty=medium"
|
||||
},
|
||||
"allBranchesLogCmds": {
|
||||
@@ -1735,4 +1725,4 @@
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -335,7 +335,7 @@ func (g *Gui) SetView(name string, x0, y0, x1, y1 int, overlaps byte) (*View, er
|
||||
|
||||
g.Mutexes.ViewsMutex.Lock()
|
||||
|
||||
v := NewView(name, x0, y0, x1, y1, g.outputMode)
|
||||
v := newView(name, x0, y0, x1, y1, g.outputMode)
|
||||
v.BgColor, v.FgColor = g.BgColor, g.FgColor
|
||||
v.SelBgColor, v.SelFgColor = g.SelBgColor, g.SelFgColor
|
||||
v.Overlaps = overlaps
|
||||
@@ -912,7 +912,7 @@ func calcScrollbarRune(
|
||||
}
|
||||
|
||||
func calcRealScrollbarStartEnd(v *View) (bool, int, int) {
|
||||
height := v.InnerHeight()
|
||||
height := v.InnerHeight() + 1
|
||||
fullHeight := v.ViewLinesHeight() - v.scrollMargin()
|
||||
|
||||
if v.CanScrollPastBottom {
|
||||
|
||||
+64
-52
@@ -322,9 +322,14 @@ func (v *View) FocusPoint(cx int, cy int) {
|
||||
if cy < 0 || cy > lineCount {
|
||||
return
|
||||
}
|
||||
height := v.InnerHeight()
|
||||
_, height := v.Size()
|
||||
|
||||
v.oy = calculateNewOrigin(cy, v.oy, lineCount, height)
|
||||
ly := height - 1
|
||||
if ly < 0 {
|
||||
ly = 0
|
||||
}
|
||||
|
||||
v.oy = calculateNewOrigin(cy, v.oy, lineCount, ly)
|
||||
v.cx = cx
|
||||
v.cy = cy - v.oy
|
||||
}
|
||||
@@ -338,16 +343,16 @@ func (v *View) CancelRangeSelect() {
|
||||
}
|
||||
|
||||
func calculateNewOrigin(selectedLine int, oldOrigin int, lineCount int, viewHeight int) int {
|
||||
if viewHeight >= lineCount {
|
||||
if viewHeight > lineCount {
|
||||
return 0
|
||||
} else if selectedLine < oldOrigin || selectedLine >= oldOrigin+viewHeight {
|
||||
} else if selectedLine < oldOrigin || selectedLine > oldOrigin+viewHeight {
|
||||
// If the selected line is outside the visible area, scroll the view so
|
||||
// that the selected line is in the middle.
|
||||
newOrigin := selectedLine - viewHeight/2
|
||||
|
||||
// However, take care not to overflow if the total line count is less
|
||||
// than the view height.
|
||||
maxOrigin := lineCount - viewHeight
|
||||
maxOrigin := lineCount - viewHeight - 1
|
||||
if newOrigin > maxOrigin {
|
||||
newOrigin = maxOrigin
|
||||
}
|
||||
@@ -402,8 +407,8 @@ func (l lineType) String() string {
|
||||
return str
|
||||
}
|
||||
|
||||
// NewView returns a new View object.
|
||||
func NewView(name string, x0, y0, x1, y1 int, mode OutputMode) *View {
|
||||
// newView returns a new View object.
|
||||
func newView(name string, x0, y0, x1, y1 int, mode OutputMode) *View {
|
||||
v := &View{
|
||||
name: name,
|
||||
x0: x0,
|
||||
@@ -433,32 +438,22 @@ func (v *View) Dimensions() (int, int, int, int) {
|
||||
return v.x0, v.y0, v.x1, v.y1
|
||||
}
|
||||
|
||||
// Size returns the number of visible columns and rows in the View, including
|
||||
// the frame if any
|
||||
// Size returns the number of visible columns and rows in the View.
|
||||
func (v *View) Size() (x, y int) {
|
||||
return v.Width(), v.Height()
|
||||
}
|
||||
|
||||
// InnerSize returns the number of usable columns and rows in the View, excluding
|
||||
// the frame if any
|
||||
func (v *View) InnerSize() (x, y int) {
|
||||
return v.InnerWidth(), v.InnerHeight()
|
||||
}
|
||||
|
||||
func (v *View) Width() int {
|
||||
return v.x1 - v.x0 + 1
|
||||
return v.x1 - v.x0 - 1
|
||||
}
|
||||
|
||||
func (v *View) Height() int {
|
||||
return v.y1 - v.y0 + 1
|
||||
return v.y1 - v.y0 - 1
|
||||
}
|
||||
|
||||
// The writeable area of the view is always two less then the view's size,
|
||||
// because if it has a frame, we need to subtract that, but if it doesn't, the
|
||||
// view is made 1 larger on all sides. I'd like to clean this up at some point,
|
||||
// but for now we live with this weirdness.
|
||||
// if a view has a frame, that leaves less space for its writeable area
|
||||
func (v *View) InnerWidth() int {
|
||||
innerWidth := v.Width() - 2
|
||||
innerWidth := v.Width() - v.frameOffset()
|
||||
if innerWidth < 0 {
|
||||
return 0
|
||||
}
|
||||
@@ -467,7 +462,7 @@ func (v *View) InnerWidth() int {
|
||||
}
|
||||
|
||||
func (v *View) InnerHeight() int {
|
||||
innerHeight := v.Height() - 2
|
||||
innerHeight := v.Height() - v.frameOffset()
|
||||
if innerHeight < 0 {
|
||||
return 0
|
||||
}
|
||||
@@ -475,6 +470,14 @@ func (v *View) InnerHeight() int {
|
||||
return innerHeight
|
||||
}
|
||||
|
||||
func (v *View) frameOffset() int {
|
||||
if v.Frame {
|
||||
return 1
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// Name returns the name of the view.
|
||||
func (v *View) Name() string {
|
||||
return v.name
|
||||
@@ -494,15 +497,31 @@ func (v *View) setRune(x, y int, ch rune, fgColor, bgColor Attribute) {
|
||||
bgColor = v.BgColor
|
||||
ch = v.Mask
|
||||
} else if v.Highlight {
|
||||
rangeSelectStart := v.cy
|
||||
rangeSelectEnd := v.cy
|
||||
if v.rangeSelectStartY != -1 {
|
||||
relativeRangeSelectStart := v.rangeSelectStartY - v.oy
|
||||
rangeSelectStart = min(relativeRangeSelectStart, v.cy)
|
||||
rangeSelectEnd = max(relativeRangeSelectStart, v.cy)
|
||||
var ry, rcy int
|
||||
|
||||
_, ry, ok := v.realPosition(x, y)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
_, rrcy, ok := v.realPosition(v.cx, v.cy)
|
||||
// out of bounds is fine
|
||||
if ok {
|
||||
rcy = rrcy
|
||||
}
|
||||
|
||||
if y >= rangeSelectStart && y <= rangeSelectEnd {
|
||||
rangeSelectStart := rcy
|
||||
rangeSelectEnd := rcy
|
||||
if v.rangeSelectStartY != -1 {
|
||||
_, realRangeSelectStart, ok := v.realPosition(0, v.rangeSelectStartY-v.oy)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
rangeSelectStart = min(realRangeSelectStart, rcy)
|
||||
rangeSelectEnd = max(realRangeSelectStart, rcy)
|
||||
}
|
||||
|
||||
if ry >= rangeSelectStart && ry <= rangeSelectEnd {
|
||||
// this ensures we use the bright variant of a colour upon highlight
|
||||
fgColorComponent := fgColor & ^AttrAll
|
||||
if fgColorComponent >= AttrIsValidColor && fgColorComponent < AttrIsValidColor+8 {
|
||||
@@ -554,7 +573,7 @@ func max(a, b int) int {
|
||||
// SetCursor sets the cursor position of the view at the given point,
|
||||
// relative to the view. It checks if the position is valid.
|
||||
func (v *View) SetCursor(x, y int) {
|
||||
maxX, maxY := v.InnerSize()
|
||||
maxX, maxY := v.Size()
|
||||
if x < 0 || x >= maxX || y < 0 || y >= maxY {
|
||||
return
|
||||
}
|
||||
@@ -563,7 +582,7 @@ func (v *View) SetCursor(x, y int) {
|
||||
}
|
||||
|
||||
func (v *View) SetCursorX(x int) {
|
||||
maxX := v.InnerWidth()
|
||||
maxX, _ := v.Size()
|
||||
if x < 0 || x >= maxX {
|
||||
return
|
||||
}
|
||||
@@ -571,7 +590,7 @@ func (v *View) SetCursorX(x int) {
|
||||
}
|
||||
|
||||
func (v *View) SetCursorY(y int) {
|
||||
maxY := v.InnerHeight()
|
||||
_, maxY := v.Size()
|
||||
if y < 0 || y >= maxY {
|
||||
return
|
||||
}
|
||||
@@ -898,7 +917,7 @@ func (v *View) parseInput(ch rune, x int, _ int) (bool, []cell) {
|
||||
for _, cell := range v.lines[v.wy][0:v.wx] {
|
||||
cx += runewidth.RuneWidth(cell.chr)
|
||||
}
|
||||
repeatCount = v.InnerWidth() - cx
|
||||
repeatCount = v.InnerWidth() - cx + 1
|
||||
ch = ' '
|
||||
truncateLine = true
|
||||
} else if isEscape {
|
||||
@@ -1087,8 +1106,6 @@ func (v *View) updateSearchPositions() {
|
||||
|
||||
if v.searcher.modelSearchResults != nil {
|
||||
for _, result := range v.searcher.modelSearchResults {
|
||||
// This code only works when v.Wrap is false.
|
||||
|
||||
if result.Y >= len(v.lines) {
|
||||
break
|
||||
}
|
||||
@@ -1117,9 +1134,8 @@ func (v *View) updateSearchPositions() {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
v.refreshViewLinesIfNeeded()
|
||||
for y, line := range v.viewLines {
|
||||
v.searcher.searchPositions = append(v.searcher.searchPositions, searchPositionsForLine(line.line, y)...)
|
||||
for y, line := range v.lines {
|
||||
v.searcher.searchPositions = append(v.searcher.searchPositions, searchPositionsForLine(line, y)...)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1141,7 +1157,7 @@ func (v *View) draw() {
|
||||
|
||||
v.clearRunes()
|
||||
|
||||
maxX, maxY := v.InnerSize()
|
||||
maxX, maxY := v.Size()
|
||||
|
||||
if v.Wrap {
|
||||
if maxX == 0 {
|
||||
@@ -1234,7 +1250,7 @@ func (v *View) draw() {
|
||||
|
||||
func (v *View) refreshViewLinesIfNeeded() {
|
||||
if v.tainted {
|
||||
maxX := v.InnerWidth()
|
||||
maxX := v.Width()
|
||||
lineIdx := 0
|
||||
lines := v.lines
|
||||
if v.HasLoader {
|
||||
@@ -1325,7 +1341,7 @@ func (v *View) realPosition(vx, vy int) (x, y int, ok bool) {
|
||||
|
||||
// clearRunes erases all the cells in the view.
|
||||
func (v *View) clearRunes() {
|
||||
maxX, maxY := v.InnerSize()
|
||||
maxX, maxY := v.Size()
|
||||
for x := 0; x < maxX; x++ {
|
||||
for y := 0; y < maxY; y++ {
|
||||
tcellSetCell(v.x0+x+1, v.y0+y+1, ' ', v.FgColor, v.BgColor, v.outMode)
|
||||
@@ -1360,8 +1376,6 @@ func (v *View) ViewBufferLines() []string {
|
||||
v.writeMutex.Lock()
|
||||
defer v.writeMutex.Unlock()
|
||||
|
||||
v.refreshViewLinesIfNeeded()
|
||||
|
||||
lines := make([]string, len(v.viewLines))
|
||||
for i, l := range v.viewLines {
|
||||
str := lineType(l.line).String()
|
||||
@@ -1501,20 +1515,18 @@ func lineWrap(line []cell, columns int) [][]cell {
|
||||
lines = append(lines, line[offset:i])
|
||||
offset = i
|
||||
n = rw
|
||||
} else if lastWhitespaceIndex != -1 {
|
||||
} else if lastWhitespaceIndex != -1 && lastWhitespaceIndex+1 != i {
|
||||
// if there is a space in the line and the line is not breaking at a space/hyphen
|
||||
if line[lastWhitespaceIndex].chr == '-' {
|
||||
// if break occurs at hyphen, we'll retain the hyphen
|
||||
lines = append(lines, line[offset:lastWhitespaceIndex+1])
|
||||
offset = lastWhitespaceIndex + 1
|
||||
n = i - offset
|
||||
} else {
|
||||
// if break occurs at space, we'll omit the space
|
||||
lines = append(lines, line[offset:lastWhitespaceIndex])
|
||||
}
|
||||
// Either way, continue *after* the break
|
||||
offset = lastWhitespaceIndex + 1
|
||||
n = 0
|
||||
for _, c := range line[offset : i+1] {
|
||||
n += runewidth.RuneWidth(c.chr)
|
||||
offset = lastWhitespaceIndex + 1
|
||||
n = i - offset + 1
|
||||
}
|
||||
} else {
|
||||
// in this case we're breaking mid-word
|
||||
@@ -1777,7 +1789,7 @@ func (v *View) adjustDownwardScrollAmount(scrollHeight int) int {
|
||||
_, oy := v.Origin()
|
||||
y := oy
|
||||
if !v.CanScrollPastBottom {
|
||||
sy := v.InnerHeight()
|
||||
_, sy := v.Size()
|
||||
y += sy
|
||||
}
|
||||
scrollableLines := v.ViewLinesHeight() - y
|
||||
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
// Copyright 2024 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build darwin && amd64 && gc
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
TEXT libc_sysctl_trampoline<>(SB),NOSPLIT,$0-0
|
||||
JMP libc_sysctl(SB)
|
||||
GLOBL ·libc_sysctl_trampoline_addr(SB), RODATA, $8
|
||||
DATA ·libc_sysctl_trampoline_addr(SB)/8, $libc_sysctl_trampoline<>(SB)
|
||||
|
||||
TEXT libc_sysctlbyname_trampoline<>(SB),NOSPLIT,$0-0
|
||||
JMP libc_sysctlbyname(SB)
|
||||
GLOBL ·libc_sysctlbyname_trampoline_addr(SB), RODATA, $8
|
||||
DATA ·libc_sysctlbyname_trampoline_addr(SB)/8, $libc_sysctlbyname_trampoline<>(SB)
|
||||
-61
@@ -1,61 +0,0 @@
|
||||
// Copyright 2024 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build darwin && amd64 && gc
|
||||
|
||||
package cpu
|
||||
|
||||
// darwinSupportsAVX512 checks Darwin kernel for AVX512 support via sysctl
|
||||
// call (see issue 43089). It also restricts AVX512 support for Darwin to
|
||||
// kernel version 21.3.0 (MacOS 12.2.0) or later (see issue 49233).
|
||||
//
|
||||
// Background:
|
||||
// Darwin implements a special mechanism to economize on thread state when
|
||||
// AVX512 specific registers are not in use. This scheme minimizes state when
|
||||
// preempting threads that haven't yet used any AVX512 instructions, but adds
|
||||
// special requirements to check for AVX512 hardware support at runtime (e.g.
|
||||
// via sysctl call or commpage inspection). See issue 43089 and link below for
|
||||
// full background:
|
||||
// https://github.com/apple-oss-distributions/xnu/blob/xnu-11215.1.10/osfmk/i386/fpu.c#L214-L240
|
||||
//
|
||||
// Additionally, all versions of the Darwin kernel from 19.6.0 through 21.2.0
|
||||
// (corresponding to MacOS 10.15.6 - 12.1) have a bug that can cause corruption
|
||||
// of the AVX512 mask registers (K0-K7) upon signal return. For this reason
|
||||
// AVX512 is considered unsafe to use on Darwin for kernel versions prior to
|
||||
// 21.3.0, where a fix has been confirmed. See issue 49233 for full background.
|
||||
func darwinSupportsAVX512() bool {
|
||||
return darwinSysctlEnabled([]byte("hw.optional.avx512f\x00")) && darwinKernelVersionCheck(21, 3, 0)
|
||||
}
|
||||
|
||||
// Ensure Darwin kernel version is at least major.minor.patch, avoiding dependencies
|
||||
func darwinKernelVersionCheck(major, minor, patch int) bool {
|
||||
var release [256]byte
|
||||
err := darwinOSRelease(&release)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
var mmp [3]int
|
||||
c := 0
|
||||
Loop:
|
||||
for _, b := range release[:] {
|
||||
switch {
|
||||
case b >= '0' && b <= '9':
|
||||
mmp[c] = 10*mmp[c] + int(b-'0')
|
||||
case b == '.':
|
||||
c++
|
||||
if c > 2 {
|
||||
return false
|
||||
}
|
||||
case b == 0:
|
||||
break Loop
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
if c != 2 {
|
||||
return false
|
||||
}
|
||||
return mmp[0] > major || mmp[0] == major && (mmp[1] > minor || mmp[1] == minor && mmp[2] >= patch)
|
||||
}
|
||||
+2
-2
@@ -6,10 +6,10 @@
|
||||
|
||||
package cpu
|
||||
|
||||
// cpuid is implemented in cpu_gc_x86.s for gc compiler
|
||||
// cpuid is implemented in cpu_x86.s for gc compiler
|
||||
// and in cpu_gccgo.c for gccgo.
|
||||
func cpuid(eaxArg, ecxArg uint32) (eax, ebx, ecx, edx uint32)
|
||||
|
||||
// xgetbv with ecx = 0 is implemented in cpu_gc_x86.s for gc compiler
|
||||
// xgetbv with ecx = 0 is implemented in cpu_x86.s for gc compiler
|
||||
// and in cpu_gccgo.c for gccgo.
|
||||
func xgetbv() (eax, edx uint32)
|
||||
|
||||
+6
@@ -23,3 +23,9 @@ func xgetbv() (eax, edx uint32) {
|
||||
gccgoXgetbv(&a, &d)
|
||||
return a, d
|
||||
}
|
||||
|
||||
// gccgo doesn't build on Darwin, per:
|
||||
// https://github.com/Homebrew/homebrew-core/blob/HEAD/Formula/gcc.rb#L76
|
||||
func darwinSupportsAVX512() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
+1
@@ -110,6 +110,7 @@ func doinit() {
|
||||
ARM64.HasASIMDFHM = isSet(hwCap, hwcap_ASIMDFHM)
|
||||
ARM64.HasDIT = isSet(hwCap, hwcap_DIT)
|
||||
|
||||
|
||||
// HWCAP2 feature bits
|
||||
ARM64.HasSVE2 = isSet(hwCap2, hwcap2_SVE2)
|
||||
ARM64.HasI8MM = isSet(hwCap2, hwcap2_I8MM)
|
||||
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
// Copyright 2024 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build 386 || amd64p32 || (amd64 && (!darwin || !gc))
|
||||
|
||||
package cpu
|
||||
|
||||
func darwinSupportsAVX512() bool {
|
||||
panic("only implemented for gc && amd64 && darwin")
|
||||
}
|
||||
+4
-2
@@ -92,8 +92,10 @@ func archInit() {
|
||||
osSupportsAVX = isSet(1, eax) && isSet(2, eax)
|
||||
|
||||
if runtime.GOOS == "darwin" {
|
||||
// Darwin requires special AVX512 checks, see cpu_darwin_x86.go
|
||||
osSupportsAVX512 = osSupportsAVX && darwinSupportsAVX512()
|
||||
// Darwin doesn't save/restore AVX-512 mask registers correctly across signal handlers.
|
||||
// Since users can't rely on mask register contents, let's not advertise AVX-512 support.
|
||||
// See issue 49233.
|
||||
osSupportsAVX512 = false
|
||||
} else {
|
||||
// Check if OPMASK and ZMM registers have OS support.
|
||||
osSupportsAVX512 = osSupportsAVX && isSet(5, eax) && isSet(6, eax) && isSet(7, eax)
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ TEXT ·cpuid(SB), NOSPLIT, $0-24
|
||||
RET
|
||||
|
||||
// func xgetbv() (eax, edx uint32)
|
||||
TEXT ·xgetbv(SB), NOSPLIT, $0-8
|
||||
TEXT ·xgetbv(SB),NOSPLIT,$0-8
|
||||
MOVL $0, CX
|
||||
XGETBV
|
||||
MOVL AX, eax+0(FP)
|
||||
-98
@@ -1,98 +0,0 @@
|
||||
// Copyright 2024 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Minimal copy of x/sys/unix so the cpu package can make a
|
||||
// system call on Darwin without depending on x/sys/unix.
|
||||
|
||||
//go:build darwin && amd64 && gc
|
||||
|
||||
package cpu
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type _C_int int32
|
||||
|
||||
// adapted from unix.Uname() at x/sys/unix/syscall_darwin.go L419
|
||||
func darwinOSRelease(release *[256]byte) error {
|
||||
// from x/sys/unix/zerrors_openbsd_amd64.go
|
||||
const (
|
||||
CTL_KERN = 0x1
|
||||
KERN_OSRELEASE = 0x2
|
||||
)
|
||||
|
||||
mib := []_C_int{CTL_KERN, KERN_OSRELEASE}
|
||||
n := unsafe.Sizeof(*release)
|
||||
|
||||
return sysctl(mib, &release[0], &n, nil, 0)
|
||||
}
|
||||
|
||||
type Errno = syscall.Errno
|
||||
|
||||
var _zero uintptr // Single-word zero for use when we need a valid pointer to 0 bytes.
|
||||
|
||||
// from x/sys/unix/zsyscall_darwin_amd64.go L791-807
|
||||
func sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) error {
|
||||
var _p0 unsafe.Pointer
|
||||
if len(mib) > 0 {
|
||||
_p0 = unsafe.Pointer(&mib[0])
|
||||
} else {
|
||||
_p0 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
if _, _, err := syscall_syscall6(
|
||||
libc_sysctl_trampoline_addr,
|
||||
uintptr(_p0),
|
||||
uintptr(len(mib)),
|
||||
uintptr(unsafe.Pointer(old)),
|
||||
uintptr(unsafe.Pointer(oldlen)),
|
||||
uintptr(unsafe.Pointer(new)),
|
||||
uintptr(newlen),
|
||||
); err != 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var libc_sysctl_trampoline_addr uintptr
|
||||
|
||||
// adapted from internal/cpu/cpu_arm64_darwin.go
|
||||
func darwinSysctlEnabled(name []byte) bool {
|
||||
out := int32(0)
|
||||
nout := unsafe.Sizeof(out)
|
||||
if ret := sysctlbyname(&name[0], (*byte)(unsafe.Pointer(&out)), &nout, nil, 0); ret != nil {
|
||||
return false
|
||||
}
|
||||
return out > 0
|
||||
}
|
||||
|
||||
//go:cgo_import_dynamic libc_sysctl sysctl "/usr/lib/libSystem.B.dylib"
|
||||
|
||||
var libc_sysctlbyname_trampoline_addr uintptr
|
||||
|
||||
// adapted from runtime/sys_darwin.go in the pattern of sysctl() above, as defined in x/sys/unix
|
||||
func sysctlbyname(name *byte, old *byte, oldlen *uintptr, new *byte, newlen uintptr) error {
|
||||
if _, _, err := syscall_syscall6(
|
||||
libc_sysctlbyname_trampoline_addr,
|
||||
uintptr(unsafe.Pointer(name)),
|
||||
uintptr(unsafe.Pointer(old)),
|
||||
uintptr(unsafe.Pointer(oldlen)),
|
||||
uintptr(unsafe.Pointer(new)),
|
||||
uintptr(newlen),
|
||||
0,
|
||||
); err != 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//go:cgo_import_dynamic libc_sysctlbyname sysctlbyname "/usr/lib/libSystem.B.dylib"
|
||||
|
||||
// Implemented in the runtime package (runtime/sys_darwin.go)
|
||||
func syscall_syscall6(fn, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err Errno)
|
||||
|
||||
//go:linkname syscall_syscall6 syscall.syscall6
|
||||
+1
-1
@@ -156,7 +156,7 @@ from the generated architecture-specific files listed below, and merge these
|
||||
into a common file for each OS.
|
||||
|
||||
The merge is performed in the following steps:
|
||||
1. Construct the set of common code that is identical in all architecture-specific files.
|
||||
1. Construct the set of common code that is idential in all architecture-specific files.
|
||||
2. Write this common code to the merged file.
|
||||
3. Remove the common code from all architecture-specific files.
|
||||
|
||||
|
||||
-96
@@ -58,102 +58,6 @@ func IoctlGetEthtoolDrvinfo(fd int, ifname string) (*EthtoolDrvinfo, error) {
|
||||
return &value, err
|
||||
}
|
||||
|
||||
// IoctlGetEthtoolTsInfo fetches ethtool timestamping and PHC
|
||||
// association for the network device specified by ifname.
|
||||
func IoctlGetEthtoolTsInfo(fd int, ifname string) (*EthtoolTsInfo, error) {
|
||||
ifr, err := NewIfreq(ifname)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
value := EthtoolTsInfo{Cmd: ETHTOOL_GET_TS_INFO}
|
||||
ifrd := ifr.withData(unsafe.Pointer(&value))
|
||||
|
||||
err = ioctlIfreqData(fd, SIOCETHTOOL, &ifrd)
|
||||
return &value, err
|
||||
}
|
||||
|
||||
// IoctlGetHwTstamp retrieves the hardware timestamping configuration
|
||||
// for the network device specified by ifname.
|
||||
func IoctlGetHwTstamp(fd int, ifname string) (*HwTstampConfig, error) {
|
||||
ifr, err := NewIfreq(ifname)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
value := HwTstampConfig{}
|
||||
ifrd := ifr.withData(unsafe.Pointer(&value))
|
||||
|
||||
err = ioctlIfreqData(fd, SIOCGHWTSTAMP, &ifrd)
|
||||
return &value, err
|
||||
}
|
||||
|
||||
// IoctlSetHwTstamp updates the hardware timestamping configuration for
|
||||
// the network device specified by ifname.
|
||||
func IoctlSetHwTstamp(fd int, ifname string, cfg *HwTstampConfig) error {
|
||||
ifr, err := NewIfreq(ifname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ifrd := ifr.withData(unsafe.Pointer(cfg))
|
||||
return ioctlIfreqData(fd, SIOCSHWTSTAMP, &ifrd)
|
||||
}
|
||||
|
||||
// FdToClockID derives the clock ID from the file descriptor number
|
||||
// - see clock_gettime(3), FD_TO_CLOCKID macros. The resulting ID is
|
||||
// suitable for system calls like ClockGettime.
|
||||
func FdToClockID(fd int) int32 { return int32((int(^fd) << 3) | 3) }
|
||||
|
||||
// IoctlPtpClockGetcaps returns the description of a given PTP device.
|
||||
func IoctlPtpClockGetcaps(fd int) (*PtpClockCaps, error) {
|
||||
var value PtpClockCaps
|
||||
err := ioctlPtr(fd, PTP_CLOCK_GETCAPS2, unsafe.Pointer(&value))
|
||||
return &value, err
|
||||
}
|
||||
|
||||
// IoctlPtpSysOffsetPrecise returns a description of the clock
|
||||
// offset compared to the system clock.
|
||||
func IoctlPtpSysOffsetPrecise(fd int) (*PtpSysOffsetPrecise, error) {
|
||||
var value PtpSysOffsetPrecise
|
||||
err := ioctlPtr(fd, PTP_SYS_OFFSET_PRECISE2, unsafe.Pointer(&value))
|
||||
return &value, err
|
||||
}
|
||||
|
||||
// IoctlPtpSysOffsetExtended returns an extended description of the
|
||||
// clock offset compared to the system clock. The samples parameter
|
||||
// specifies the desired number of measurements.
|
||||
func IoctlPtpSysOffsetExtended(fd int, samples uint) (*PtpSysOffsetExtended, error) {
|
||||
value := PtpSysOffsetExtended{Samples: uint32(samples)}
|
||||
err := ioctlPtr(fd, PTP_SYS_OFFSET_EXTENDED2, unsafe.Pointer(&value))
|
||||
return &value, err
|
||||
}
|
||||
|
||||
// IoctlPtpPinGetfunc returns the configuration of the specified
|
||||
// I/O pin on given PTP device.
|
||||
func IoctlPtpPinGetfunc(fd int, index uint) (*PtpPinDesc, error) {
|
||||
value := PtpPinDesc{Index: uint32(index)}
|
||||
err := ioctlPtr(fd, PTP_PIN_GETFUNC2, unsafe.Pointer(&value))
|
||||
return &value, err
|
||||
}
|
||||
|
||||
// IoctlPtpPinSetfunc updates configuration of the specified PTP
|
||||
// I/O pin.
|
||||
func IoctlPtpPinSetfunc(fd int, pd *PtpPinDesc) error {
|
||||
return ioctlPtr(fd, PTP_PIN_SETFUNC2, unsafe.Pointer(pd))
|
||||
}
|
||||
|
||||
// IoctlPtpPeroutRequest configures the periodic output mode of the
|
||||
// PTP I/O pins.
|
||||
func IoctlPtpPeroutRequest(fd int, r *PtpPeroutRequest) error {
|
||||
return ioctlPtr(fd, PTP_PEROUT_REQUEST2, unsafe.Pointer(r))
|
||||
}
|
||||
|
||||
// IoctlPtpExttsRequest configures the external timestamping mode
|
||||
// of the PTP I/O pins.
|
||||
func IoctlPtpExttsRequest(fd int, r *PtpExttsRequest) error {
|
||||
return ioctlPtr(fd, PTP_EXTTS_REQUEST2, unsafe.Pointer(r))
|
||||
}
|
||||
|
||||
// IoctlGetWatchdogInfo fetches information about a watchdog device from the
|
||||
// Linux watchdog API. For more information, see:
|
||||
// https://www.kernel.org/doc/html/latest/watchdog/watchdog-api.html.
|
||||
|
||||
+2
-14
@@ -158,16 +158,6 @@ includes_Linux='
|
||||
#endif
|
||||
#define _GNU_SOURCE
|
||||
|
||||
// See the description in unix/linux/types.go
|
||||
#if defined(__ARM_EABI__) || \
|
||||
(defined(__mips__) && (_MIPS_SIM == _ABIO32)) || \
|
||||
(defined(__powerpc__) && (!defined(__powerpc64__)))
|
||||
# ifdef _TIME_BITS
|
||||
# undef _TIME_BITS
|
||||
# endif
|
||||
# define _TIME_BITS 32
|
||||
#endif
|
||||
|
||||
// <sys/ioctl.h> is broken on powerpc64, as it fails to include definitions of
|
||||
// these structures. We just include them copied from <bits/termios.h>.
|
||||
#if defined(__powerpc__)
|
||||
@@ -266,7 +256,6 @@ struct ltchars {
|
||||
#include <linux/nsfs.h>
|
||||
#include <linux/perf_event.h>
|
||||
#include <linux/pps.h>
|
||||
#include <linux/ptp_clock.h>
|
||||
#include <linux/ptrace.h>
|
||||
#include <linux/random.h>
|
||||
#include <linux/reboot.h>
|
||||
@@ -538,7 +527,6 @@ ccflags="$@"
|
||||
$2 ~ /^(AF|SOCK|SO|SOL|IPPROTO|IP|IPV6|TCP|MCAST|EVFILT|NOTE|SHUT|PROT|MAP|MREMAP|MFD|T?PACKET|MSG|SCM|MCL|DT|MADV|PR|LOCAL|TCPOPT|UDP)_/ ||
|
||||
$2 ~ /^NFC_(GENL|PROTO|COMM|RF|SE|DIRECTION|LLCP|SOCKPROTO)_/ ||
|
||||
$2 ~ /^NFC_.*_(MAX)?SIZE$/ ||
|
||||
$2 ~ /^PTP_/ ||
|
||||
$2 ~ /^RAW_PAYLOAD_/ ||
|
||||
$2 ~ /^[US]F_/ ||
|
||||
$2 ~ /^TP_STATUS_/ ||
|
||||
@@ -668,7 +656,7 @@ errors=$(
|
||||
signals=$(
|
||||
echo '#include <signal.h>' | $CC -x c - -E -dM $ccflags |
|
||||
awk '$1=="#define" && $2 ~ /^SIG[A-Z0-9]+$/ { print $2 }' |
|
||||
grep -E -v '(SIGSTKSIZE|SIGSTKSZ|SIGRT|SIGMAX64)' |
|
||||
grep -v 'SIGSTKSIZE\|SIGSTKSZ\|SIGRT\|SIGMAX64' |
|
||||
sort
|
||||
)
|
||||
|
||||
@@ -678,7 +666,7 @@ echo '#include <errno.h>' | $CC -x c - -E -dM $ccflags |
|
||||
sort >_error.grep
|
||||
echo '#include <signal.h>' | $CC -x c - -E -dM $ccflags |
|
||||
awk '$1=="#define" && $2 ~ /^SIG[A-Z0-9]+$/ { print "^\t" $2 "[ \t]*=" }' |
|
||||
grep -E -v '(SIGSTKSIZE|SIGSTKSZ|SIGRT|SIGMAX64)' |
|
||||
grep -v 'SIGSTKSIZE\|SIGSTKSZ\|SIGRT\|SIGMAX64' |
|
||||
sort >_signal.grep
|
||||
|
||||
echo '// mkerrors.sh' "$@"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user