From b055d28fb1e9e6f3f7d9253b368eb39d6c2958b0 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Thu, 23 Jul 2026 09:39:09 +0200 Subject: [PATCH] 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. --- pkg/gui/context/local_commits_context.go | 44 +++++++++++++++++++ pkg/gui/context/local_commits_context_test.go | 29 ++++++++++++ pkg/i18n/english.go | 2 + 3 files changed, 75 insertions(+) create mode 100644 pkg/gui/context/local_commits_context_test.go diff --git a/pkg/gui/context/local_commits_context.go b/pkg/gui/context/local_commits_context.go index 034b2434e..5c01de973 100644 --- a/pkg/gui/context/local_commits_context.go +++ b/pkg/gui/context/local_commits_context.go @@ -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] diff --git a/pkg/gui/context/local_commits_context_test.go b/pkg/gui/context/local_commits_context_test.go new file mode 100644 index 000000000..67597ff04 --- /dev/null +++ b/pkg/gui/context/local_commits_context_test.go @@ -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)) +} diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 2e83fed9b..826752b53 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -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",