summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStefan Haller <stefan@haller-berlin.de>2023-09-22 16:09:02 +0200
committerStefan Haller <stefan@haller-berlin.de>2023-10-08 18:45:36 +0200
commit7075b66bc6dde80be5e0bc626db1988a9f47e2ca (patch)
tree24dbb9819070b4ce9351f79b8a5a0db6ada876f9
parent9d55d71fdd310886cc178a4416a9d743d8f34a03 (diff)
Add WithInlineStatus helper function
Very similar to WithWaitingStatus, except that the status is shown in a view next to the affected item, rather than in the status bar. Not used by anything yet; again, committing separately to get smaller commits.
-rw-r--r--pkg/gui/controllers.go1
-rw-r--r--pkg/gui/controllers/helpers/helpers.go2
-rw-r--r--pkg/gui/controllers/helpers/inline_status_helper.go129
-rw-r--r--pkg/gui/gui_common.go6
-rw-r--r--pkg/gui/types/common.go6
5 files changed, 144 insertions, 0 deletions
diff --git a/pkg/gui/controllers.go b/pkg/gui/controllers.go
index 40a482f30..43b226b58 100644
--- a/pkg/gui/controllers.go
+++ b/pkg/gui/controllers.go
@@ -112,6 +112,7 @@ func (gui *Gui) resetHelpersAndControllers() {
Confirmation: helpers.NewConfirmationHelper(helperCommon),
Mode: modeHelper,
AppStatus: appStatusHelper,
+ InlineStatus: helpers.NewInlineStatusHelper(helperCommon),
WindowArrangement: helpers.NewWindowArrangementHelper(
gui.c,
windowHelper,
diff --git a/pkg/gui/controllers/helpers/helpers.go b/pkg/gui/controllers/helpers/helpers.go
index 06c5cc973..9e05ba163 100644
--- a/pkg/gui/controllers/helpers/helpers.go
+++ b/pkg/gui/controllers/helpers/helpers.go
@@ -46,6 +46,7 @@ type Helpers struct {
Confirmation *ConfirmationHelper
Mode *ModeHelper
AppStatus *AppStatusHelper
+ InlineStatus *InlineStatusHelper
WindowArrangement *WindowArrangementHelper
Search *SearchHelper
Worktree *WorktreeHelper
@@ -81,6 +82,7 @@ func NewStubHelpers() *Helpers {
Confirmation: &ConfirmationHelper{},
Mode: &ModeHelper{},
AppStatus: &AppStatusHelper{},
+ InlineStatus: &InlineStatusHelper{},
WindowArrangement: &WindowArrangementHelper{},
Search: &SearchHelper{},
Worktree: &WorktreeHelper{},
diff --git a/pkg/gui/controllers/helpers/inline_status_helper.go b/pkg/gui/controllers/helpers/inline_status_helper.go
new file mode 100644
index 000000000..d4435e5b9
--- /dev/null
+++ b/pkg/gui/controllers/helpers/inline_status_helper.go
@@ -0,0 +1,129 @@
+package helpers
+
+import (
+ "time"
+
+ "github.com/jesseduffield/gocui"
+ "github.com/jesseduffield/lazygit/pkg/gui/types"
+ "github.com/jesseduffield/lazygit/pkg/utils"
+ "github.com/sasha-s/go-deadlock"
+)
+
+type InlineStatusHelper struct {
+ c *HelperCommon
+
+ contextsWithInlineStatus map[types.ContextKey]*inlineStatusInfo
+ mutex *deadlock.Mutex
+}
+
+func NewInlineStatusHelper(c *HelperCommon) *InlineStatusHelper {
+ return &InlineStatusHelper{
+ c: c,
+ contextsWithInlineStatus: make(map[types.ContextKey]*inlineStatusInfo),
+ mutex: &deadlock.Mutex{},
+ }
+}
+
+type InlineStatusOpts struct {
+ Item types.HasUrn
+ Operation types.ItemOperation
+ ContextKey types.ContextKey
+}
+
+type inlineStatusInfo struct {
+ refCount int
+ stop chan struct{}
+}
+
+// A custom task for WithInlineStatus calls; it wraps the original one and
+// hides the status whenever the task is paused, and shows it again when
+// continued.
+type inlineStatusHelperTask struct {
+ gocui.Task
+
+ inlineStatusHelper *InlineStatusHelper
+ opts InlineStatusOpts
+}
+
+// poor man's version of explicitly saying that struct X implements interface Y
+var _ gocui.Task = inlineStatusHelperTask{}
+
+func (self inlineStatusHelperTask) Pause() {
+ self.inlineStatusHelper.stop(self.opts)
+ self.Task.Pause()
+
+ self.inlineStatusHelper.renderContext(self.opts.ContextKey)
+}
+
+func (self inlineStatusHelperTask) Continue() {
+ self.Task.Continue()
+ self.inlineStatusHelper.start(self.opts)
+}
+
+func (self *InlineStatusHelper) WithInlineStatus(opts InlineStatusOpts, f func(gocui.Task) error) {
+ self.c.OnWorker(func(task gocui.Task) {
+ self.start(opts)
+
+ err := f(inlineStatusHelperTask{task, self, opts})
+ if err != nil {
+ self.c.OnUIThread(func() error {
+ return self.c.Error(err)
+ })
+ }
+
+ self.stop(opts)
+ })
+}
+
+func (self *InlineStatusHelper) start(opts InlineStatusOpts) {
+ self.c.State().SetItemOperation(opts.Item, opts.Operation)
+
+ self.mutex.Lock()
+ defer self.mutex.Unlock()
+
+ info := self.contextsWithInlineStatus[opts.ContextKey]
+ if info == nil {
+ info = &inlineStatusInfo{refCount: 0, stop: make(chan struct{})}
+ self.contextsWithInlineStatus[opts.ContextKey] = info
+
+ go utils.Safe(func() {
+ ticker := time.NewTicker(time.Millisecond * utils.LoaderAnimationInterval)
+ defer ticker.Stop()
+ outer:
+ for {
+ select {
+ case <-ticker.C:
+ self.renderContext(opts.ContextKey)
+ case <-info.stop:
+ break outer
+ }
+ }
+ })
+ }
+
+ info.refCount++
+}
+
+func (self *InlineStatusHelper) stop(opts InlineStatusOpts) {
+ self.mutex.Lock()
+
+ if info := self.contextsWithInlineStatus[opts.ContextKey]; info != nil {
+ info.refCount--
+ if info.refCount <= 0 {
+ info.stop <- struct{}{}
+ delete(self.contextsWithInlineStatus, opts.ContextKey)
+ }
+
+ }
+
+ self.mutex.Unlock()
+
+ self.c.State().ClearItemOperation(opts.Item)
+}
+
+func (self *InlineStatusHelper) renderContext(contextKey types.ContextKey) {
+ self.c.OnUIThread(func() error {
+ _ = self.c.ContextForKey(contextKey).HandleRender()
+ return nil
+ })
+}
diff --git a/pkg/gui/gui_common.go b/pkg/gui/gui_common.go
index 0abe94f4c..4b9d3dc37 100644
--- a/pkg/gui/gui_common.go
+++ b/pkg/gui/gui_common.go
@@ -5,6 +5,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/config"
+ "github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
@@ -199,3 +200,8 @@ func (self *guiCommon) RunningIntegrationTest() bool {
func (self *guiCommon) InDemo() bool {
return self.gui.integrationTest != nil && self.gui.integrationTest.IsDemo()
}
+
+func (self *guiCommon) WithInlineStatus(item types.HasUrn, operation types.ItemOperation, contextKey types.ContextKey, f func(gocui.Task) error) error {
+ self.gui.helpers.InlineStatus.WithInlineStatus(helpers.InlineStatusOpts{Item: item, Operation: operation, ContextKey: contextKey}, f)
+ return nil
+}
diff --git a/pkg/gui/types/common.go b/pkg/gui/types/common.go
index f4fde41c7..e9f9fc266 100644
--- a/pkg/gui/types/common.go
+++ b/pkg/gui/types/common.go
@@ -87,6 +87,12 @@ type IGuiCommon interface {
// resized, if in accordion mode.
AfterLayout(f func() error)
+ // Wraps a function, attaching the given operation to the given item while
+ // the function is executing, and also causes the given context to be
+ // redrawn periodically. This allows the operation to be visualized with a
+ // spinning loader animation (e.g. when a branch is being pushed).
+ WithInlineStatus(item HasUrn, operation ItemOperation, contextKey ContextKey, f func(gocui.Task) error) error
+
// returns the gocui Gui struct. There is a good chance you don't actually want to use
// this struct and instead want to use another method above
GocuiGui() *gocui.Gui