Show commit insertion points during a drag

Render the insertion point of a commit drag as a non-model item in the
commits list. It must be inserted at the right position relative to
the section headers, because the list renderer assumes non-model items
are ordered by their model index.

Not used yet, we'll hook it up to the drag gesture in the next commit.
This commit is contained in:
Stefan Haller 2026-07-23 09:39:09 +02:00
parent e756511042
commit b055d28fb1
3 changed files with 75 additions and 0 deletions

View file

@ -2,6 +2,7 @@ package context
import (
"log"
"slices"
"strings"
"sync/atomic"
"time"
@ -9,6 +10,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/samber/lo"
)
@ -17,6 +19,12 @@ type LocalCommitsContext struct {
*LocalCommitsViewModel
*ListContextTrait
*SearchTrait
dropIndicator *commitDropIndicator
}
type commitDropIndicator struct {
insertionIndex int
}
var (
@ -26,6 +34,7 @@ var (
)
func NewLocalCommitsContext(c *ContextCommon) *LocalCommitsContext {
dropIndicator := &commitDropIndicator{insertionIndex: -1}
viewModel := NewLocalCommitsViewModel(
func() []*models.Commit { return c.Model().Commits },
c,
@ -94,6 +103,8 @@ func NewLocalCommitsContext(c *ContextCommon) *LocalCommitsContext {
})
}
result = addCommitDropIndicator(result, dropIndicator, c.Tr.MoveCommitsHere)
_, firstRealCommit, found := lo.FindIndexOf(
c.Model().Commits, func(c *models.Commit) bool {
return !c.IsTODO()
@ -105,6 +116,8 @@ func NewLocalCommitsContext(c *ContextCommon) *LocalCommitsContext {
Index: firstRealCommit,
Content: formatListSectionHeader(c.Tr.CommitsSectionHeader),
})
} else {
result = addCommitDropIndicator(result, dropIndicator, c.Tr.MoveCommitsHere)
}
return result
@ -113,6 +126,7 @@ func NewLocalCommitsContext(c *ContextCommon) *LocalCommitsContext {
ctx := &LocalCommitsContext{
LocalCommitsViewModel: viewModel,
SearchTrait: NewSearchTrait(c),
dropIndicator: dropIndicator,
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
View: c.Views().Commits,
@ -137,6 +151,36 @@ func NewLocalCommitsContext(c *ContextCommon) *LocalCommitsContext {
return ctx
}
func addCommitDropIndicator(
items []*NonModelItem, indicator *commitDropIndicator, label string,
) []*NonModelItem {
if indicator.insertionIndex < 0 {
return items
}
insertAt := len(items)
for i, item := range items {
if item.Index > indicator.insertionIndex {
insertAt = i
break
}
}
return slices.Insert(items, insertAt, &NonModelItem{
Index: indicator.insertionIndex,
Content: style.FgCyan.SetBold().Sprintf("━━━━━━ %s ━━━━━━", label),
Column: 6, // align with the commit subject
})
}
func (self *LocalCommitsContext) SetDropInsertionIndex(index int) {
self.dropIndicator.insertionIndex = index
}
func (self *LocalCommitsContext) ClearDropInsertionIndex() {
self.dropIndicator.insertionIndex = -1
}
type LocalCommitsViewModel struct {
*ListViewModel[*models.Commit]

View file

@ -0,0 +1,29 @@
package context
import (
"testing"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/stretchr/testify/assert"
)
func TestAddCommitDropIndicator(t *testing.T) {
pendingHeader := &NonModelItem{Index: 0, Content: "pending"}
commitsHeader := &NonModelItem{Index: 3, Content: "commits"}
indicator := &commitDropIndicator{insertionIndex: 3}
items := addCommitDropIndicator([]*NonModelItem{pendingHeader}, indicator, "drop here")
items = append(items, commitsHeader)
assert.Equal(t, []*NonModelItem{
pendingHeader,
{
Index: 3,
Content: style.FgCyan.SetBold().Sprint("━━━━━━ drop here ━━━━━━"),
Column: 6,
},
commitsHeader,
}, items)
assert.Equal(t, 6, modelIndexToViewIndex(4, items, 3))
assert.Equal(t, 3, viewIndexToModelIndex(4, items, 4))
}

View file

@ -373,6 +373,7 @@ type TranslationSet struct {
PendingCherryPicksSectionHeader string
PendingRevertsSectionHeader string
CommitsSectionHeader string
MoveCommitsHere string
YouDied string
RewordNotSupported string
ChangingThisActionIsNotAllowed string
@ -1523,6 +1524,7 @@ func EnglishTranslationSet() *TranslationSet {
PendingCherryPicksSectionHeader: "Pending cherry-picks",
PendingRevertsSectionHeader: "Pending reverts",
CommitsSectionHeader: "Commits",
MoveCommitsHere: "drop here",
YouDied: "YOU DIED!",
RewordNotSupported: "Rewording commits while interactively rebasing is not currently supported",
ChangingThisActionIsNotAllowed: "Changing this kind of rebase todo entry is not allowed",