diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index 05b966e5d..708eae1f5 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -57,13 +57,6 @@ func (gui *Gui) selectFile(alreadySelected bool) error { } if !alreadySelected { - // TODO: pull into update task interface - if err := gui.resetOrigin(gui.Views.Main); err != nil { - return err - } - if err := gui.resetOrigin(gui.Views.Secondary); err != nil { - return err - } gui.takeOverMergeConflictScrolling() } diff --git a/pkg/gui/list_context.go b/pkg/gui/list_context.go index dc9425d20..a8ed12372 100644 --- a/pkg/gui/list_context.go +++ b/pkg/gui/list_context.go @@ -11,8 +11,7 @@ type ListContext struct { SelectedItem func() (ListItem, bool) GetPanelState func() IListPanelState - Gui *Gui - ResetMainViewOriginOnFocus bool + Gui *Gui *BasicContext } @@ -79,15 +78,6 @@ func (lc *ListContext) HandleFocus() error { view.FocusPoint(0, lc.GetPanelState().GetSelectedLineIdx()) - if lc.ResetMainViewOriginOnFocus { - if err := lc.Gui.resetOrigin(lc.Gui.Views.Main); err != nil { - return err - } - if err := lc.Gui.resetOrigin(lc.Gui.Views.Secondary); err != nil { - return err - } - } - if lc.Gui.State.Modes.Diffing.Active() { return lc.Gui.renderDiff() } diff --git a/pkg/gui/list_context_config.go b/pkg/gui/list_context_config.go index dab3c35e7..0d591531e 100644 --- a/pkg/gui/list_context_config.go +++ b/pkg/gui/list_context_config.go @@ -14,12 +14,11 @@ func (gui *Gui) menuListContext() *ListContext { Kind: PERSISTENT_POPUP, OnGetOptionsMap: gui.getMenuOptions, }, - GetItemsLength: func() int { return gui.Views.Menu.LinesHeight() }, - GetPanelState: func() IListPanelState { return gui.State.Panels.Menu }, - OnFocus: gui.handleMenuSelect, - OnClickSelectedItem: gui.onMenuPress, - Gui: gui, - ResetMainViewOriginOnFocus: false, + GetItemsLength: func() int { return gui.Views.Menu.LinesHeight() }, + GetPanelState: func() IListPanelState { return gui.State.Panels.Menu }, + OnFocus: gui.handleMenuSelect, + OnClickSelectedItem: gui.onMenuPress, + Gui: gui, // no GetDisplayStrings field because we do a custom render on menu creation } @@ -33,12 +32,11 @@ func (gui *Gui) filesListContext() *ListContext { Key: FILES_CONTEXT_KEY, Kind: SIDE_CONTEXT, }, - GetItemsLength: func() int { return gui.State.FileManager.GetItemsLength() }, - GetPanelState: func() IListPanelState { return gui.State.Panels.Files }, - OnFocus: gui.focusAndSelectFile, - OnClickSelectedItem: gui.handleFilePress, - Gui: gui, - ResetMainViewOriginOnFocus: false, + GetItemsLength: func() int { return gui.State.FileManager.GetItemsLength() }, + GetPanelState: func() IListPanelState { return gui.State.Panels.Files }, + OnFocus: gui.focusAndSelectFile, + OnClickSelectedItem: gui.handleFilePress, + Gui: gui, GetDisplayStrings: func() [][]string { lines := gui.State.FileManager.Render(gui.State.Modes.Diffing.Ref, gui.State.Submodules) mappedLines := make([][]string, len(lines)) @@ -63,11 +61,10 @@ func (gui *Gui) branchesListContext() *ListContext { Key: LOCAL_BRANCHES_CONTEXT_KEY, Kind: SIDE_CONTEXT, }, - GetItemsLength: func() int { return len(gui.State.Branches) }, - GetPanelState: func() IListPanelState { return gui.State.Panels.Branches }, - OnFocus: gui.handleBranchSelect, - Gui: gui, - ResetMainViewOriginOnFocus: true, + GetItemsLength: func() int { return len(gui.State.Branches) }, + GetPanelState: func() IListPanelState { return gui.State.Panels.Branches }, + OnFocus: gui.handleBranchSelect, + Gui: gui, GetDisplayStrings: func() [][]string { return presentation.GetBranchListDisplayStrings(gui.State.Branches, gui.State.ScreenMode != SCREEN_NORMAL, gui.State.Modes.Diffing.Ref) }, @@ -86,12 +83,11 @@ func (gui *Gui) remotesListContext() *ListContext { Key: REMOTES_CONTEXT_KEY, Kind: SIDE_CONTEXT, }, - GetItemsLength: func() int { return len(gui.State.Remotes) }, - GetPanelState: func() IListPanelState { return gui.State.Panels.Remotes }, - OnFocus: gui.handleRemoteSelect, - OnClickSelectedItem: gui.handleRemoteEnter, - Gui: gui, - ResetMainViewOriginOnFocus: true, + GetItemsLength: func() int { return len(gui.State.Remotes) }, + GetPanelState: func() IListPanelState { return gui.State.Panels.Remotes }, + OnFocus: gui.handleRemoteSelect, + OnClickSelectedItem: gui.handleRemoteEnter, + Gui: gui, GetDisplayStrings: func() [][]string { return presentation.GetRemoteListDisplayStrings(gui.State.Remotes, gui.State.Modes.Diffing.Ref) }, @@ -110,11 +106,10 @@ func (gui *Gui) remoteBranchesListContext() *ListContext { Key: REMOTE_BRANCHES_CONTEXT_KEY, Kind: SIDE_CONTEXT, }, - GetItemsLength: func() int { return len(gui.State.RemoteBranches) }, - GetPanelState: func() IListPanelState { return gui.State.Panels.RemoteBranches }, - OnFocus: gui.handleRemoteBranchSelect, - Gui: gui, - ResetMainViewOriginOnFocus: true, + GetItemsLength: func() int { return len(gui.State.RemoteBranches) }, + GetPanelState: func() IListPanelState { return gui.State.Panels.RemoteBranches }, + OnFocus: gui.handleRemoteBranchSelect, + Gui: gui, GetDisplayStrings: func() [][]string { return presentation.GetRemoteBranchListDisplayStrings(gui.State.RemoteBranches, gui.State.Modes.Diffing.Ref) }, @@ -133,11 +128,10 @@ func (gui *Gui) tagsListContext() *ListContext { Key: TAGS_CONTEXT_KEY, Kind: SIDE_CONTEXT, }, - GetItemsLength: func() int { return len(gui.State.Tags) }, - GetPanelState: func() IListPanelState { return gui.State.Panels.Tags }, - OnFocus: gui.handleTagSelect, - Gui: gui, - ResetMainViewOriginOnFocus: true, + GetItemsLength: func() int { return len(gui.State.Tags) }, + GetPanelState: func() IListPanelState { return gui.State.Panels.Tags }, + OnFocus: gui.handleTagSelect, + Gui: gui, GetDisplayStrings: func() [][]string { return presentation.GetTagListDisplayStrings(gui.State.Tags, gui.State.Modes.Diffing.Ref) }, @@ -157,12 +151,11 @@ func (gui *Gui) branchCommitsListContext() *ListContext { Key: BRANCH_COMMITS_CONTEXT_KEY, Kind: SIDE_CONTEXT, }, - GetItemsLength: func() int { return len(gui.State.Commits) }, - GetPanelState: func() IListPanelState { return gui.State.Panels.Commits }, - OnFocus: gui.handleCommitSelect, - OnClickSelectedItem: gui.handleViewCommitFiles, - Gui: gui, - ResetMainViewOriginOnFocus: true, + GetItemsLength: func() int { return len(gui.State.Commits) }, + GetPanelState: func() IListPanelState { return gui.State.Panels.Commits }, + OnFocus: gui.handleCommitSelect, + OnClickSelectedItem: gui.handleViewCommitFiles, + Gui: gui, GetDisplayStrings: func() [][]string { return presentation.GetCommitListDisplayStrings( gui.State.Commits, @@ -188,11 +181,10 @@ func (gui *Gui) reflogCommitsListContext() *ListContext { Key: REFLOG_COMMITS_CONTEXT_KEY, Kind: SIDE_CONTEXT, }, - GetItemsLength: func() int { return len(gui.State.FilteredReflogCommits) }, - GetPanelState: func() IListPanelState { return gui.State.Panels.ReflogCommits }, - OnFocus: gui.handleReflogCommitSelect, - Gui: gui, - ResetMainViewOriginOnFocus: true, + GetItemsLength: func() int { return len(gui.State.FilteredReflogCommits) }, + GetPanelState: func() IListPanelState { return gui.State.Panels.ReflogCommits }, + OnFocus: gui.handleReflogCommitSelect, + Gui: gui, GetDisplayStrings: func() [][]string { return presentation.GetReflogCommitListDisplayStrings( gui.State.FilteredReflogCommits, @@ -218,11 +210,10 @@ func (gui *Gui) subCommitsListContext() *ListContext { Key: SUB_COMMITS_CONTEXT_KEY, Kind: SIDE_CONTEXT, }, - GetItemsLength: func() int { return len(gui.State.SubCommits) }, - GetPanelState: func() IListPanelState { return gui.State.Panels.SubCommits }, - OnFocus: gui.handleSubCommitSelect, - Gui: gui, - ResetMainViewOriginOnFocus: true, + GetItemsLength: func() int { return len(gui.State.SubCommits) }, + GetPanelState: func() IListPanelState { return gui.State.Panels.SubCommits }, + OnFocus: gui.handleSubCommitSelect, + Gui: gui, GetDisplayStrings: func() [][]string { return presentation.GetCommitListDisplayStrings( gui.State.SubCommits, @@ -247,11 +238,10 @@ func (gui *Gui) stashListContext() *ListContext { Key: STASH_CONTEXT_KEY, Kind: SIDE_CONTEXT, }, - GetItemsLength: func() int { return len(gui.State.StashEntries) }, - GetPanelState: func() IListPanelState { return gui.State.Panels.Stash }, - OnFocus: gui.handleStashEntrySelect, - Gui: gui, - ResetMainViewOriginOnFocus: true, + GetItemsLength: func() int { return len(gui.State.StashEntries) }, + GetPanelState: func() IListPanelState { return gui.State.Panels.Stash }, + OnFocus: gui.handleStashEntrySelect, + Gui: gui, GetDisplayStrings: func() [][]string { return presentation.GetStashEntryListDisplayStrings(gui.State.StashEntries, gui.State.Modes.Diffing.Ref) }, @@ -270,11 +260,10 @@ func (gui *Gui) commitFilesListContext() *ListContext { Key: COMMIT_FILES_CONTEXT_KEY, Kind: SIDE_CONTEXT, }, - GetItemsLength: func() int { return gui.State.CommitFileManager.GetItemsLength() }, - GetPanelState: func() IListPanelState { return gui.State.Panels.CommitFiles }, - OnFocus: gui.handleCommitFileSelect, - Gui: gui, - ResetMainViewOriginOnFocus: true, + GetItemsLength: func() int { return gui.State.CommitFileManager.GetItemsLength() }, + GetPanelState: func() IListPanelState { return gui.State.Panels.CommitFiles }, + OnFocus: gui.handleCommitFileSelect, + Gui: gui, GetDisplayStrings: func() [][]string { if gui.State.CommitFileManager.GetItemsLength() == 0 { return [][]string{{style.FgRed.Sprint("(none)")}} @@ -303,11 +292,10 @@ func (gui *Gui) submodulesListContext() *ListContext { Key: SUBMODULES_CONTEXT_KEY, Kind: SIDE_CONTEXT, }, - GetItemsLength: func() int { return len(gui.State.Submodules) }, - GetPanelState: func() IListPanelState { return gui.State.Panels.Submodules }, - OnFocus: gui.handleSubmoduleSelect, - Gui: gui, - ResetMainViewOriginOnFocus: true, + GetItemsLength: func() int { return len(gui.State.Submodules) }, + GetPanelState: func() IListPanelState { return gui.State.Panels.Submodules }, + OnFocus: gui.handleSubmoduleSelect, + Gui: gui, GetDisplayStrings: func() [][]string { return presentation.GetSubmoduleListDisplayStrings(gui.State.Submodules) }, @@ -326,11 +314,10 @@ func (gui *Gui) suggestionsListContext() *ListContext { Key: SUGGESTIONS_CONTEXT_KEY, Kind: PERSISTENT_POPUP, }, - GetItemsLength: func() int { return len(gui.State.Suggestions) }, - GetPanelState: func() IListPanelState { return gui.State.Panels.Suggestions }, - OnFocus: func() error { return nil }, - Gui: gui, - ResetMainViewOriginOnFocus: false, + GetItemsLength: func() int { return len(gui.State.Suggestions) }, + GetPanelState: func() IListPanelState { return gui.State.Panels.Suggestions }, + OnFocus: func() error { return nil }, + Gui: gui, GetDisplayStrings: func() [][]string { return presentation.GetSuggestionListDisplayStrings(gui.State.Suggestions) }, diff --git a/pkg/gui/main_panels.go b/pkg/gui/main_panels.go index 389a9d783..72af94b1d 100644 --- a/pkg/gui/main_panels.go +++ b/pkg/gui/main_panels.go @@ -29,7 +29,6 @@ type TaskKind int const ( RENDER_STRING TaskKind = iota RENDER_STRING_WITHOUT_SCROLL - RUN_FUNCTION RUN_COMMAND RUN_PTY ) @@ -97,19 +96,6 @@ func NewRunPtyTask(cmd *exec.Cmd) *runPtyTask { // return &runPtyTask{cmd: cmd, prefix: prefix} // } -type runFunctionTask struct { - f func(chan struct{}) error -} - -func (t *runFunctionTask) GetKind() TaskKind { - return RUN_FUNCTION -} - -// currently unused -// func (gui *Gui) createRunFunctionTask(f func(chan struct{}) error) *runFunctionTask { -// return &runFunctionTask{f: f} -// } - func (gui *Gui) runTaskForView(view *gocui.View, task updateTask) error { switch task.GetKind() { case RENDER_STRING: @@ -120,10 +106,6 @@ func (gui *Gui) runTaskForView(view *gocui.View, task updateTask) error { specificTask := task.(*renderStringWithoutScrollTask) return gui.newStringTaskWithoutScroll(view, specificTask.str) - case RUN_FUNCTION: - specificTask := task.(*runFunctionTask) - return gui.newTask(view, specificTask.f) - case RUN_COMMAND: specificTask := task.(*runCommandTask) return gui.newCmdTask(view, specificTask.cmd, specificTask.prefix) diff --git a/pkg/gui/pty.go b/pkg/gui/pty.go index f53904835..ff6892d73 100644 --- a/pkg/gui/pty.go +++ b/pkg/gui/pty.go @@ -5,6 +5,7 @@ package gui import ( "os/exec" + "strings" "github.com/creack/pty" "github.com/jesseduffield/gocui" @@ -40,6 +41,8 @@ func (gui *Gui) newPtyTask(view *gocui.View, cmd *exec.Cmd, prefix string) error return gui.newCmdTask(view, cmd, prefix) } + cmdStr := strings.Join(cmd.Args, " ") + cmd.Env = append(cmd.Env, "GIT_PAGER="+pager) _, height := view.Size() @@ -62,7 +65,7 @@ func (gui *Gui) newPtyTask(view *gocui.View, cmd *exec.Cmd, prefix string) error return err } - if err := manager.NewTask(manager.NewCmdTask(ptmx, cmd, prefix, height+oy+10, onClose)); err != nil { + if err := manager.NewTask(manager.NewCmdTask(ptmx, cmd, prefix, height+oy+10, onClose), cmdStr); err != nil { return err } diff --git a/pkg/gui/tasks_adapter.go b/pkg/gui/tasks_adapter.go index 71cde5b00..8d1f289db 100644 --- a/pkg/gui/tasks_adapter.go +++ b/pkg/gui/tasks_adapter.go @@ -9,9 +9,10 @@ import ( ) func (gui *Gui) newCmdTask(view *gocui.View, cmd *exec.Cmd, prefix string) error { + cmdStr := strings.Join(cmd.Args, " ") gui.Log.WithField( "command", - strings.Join(cmd.Args, " "), + cmdStr, ).Debug("RunCommand") _, height := view.Size() @@ -29,17 +30,7 @@ func (gui *Gui) newCmdTask(view *gocui.View, cmd *exec.Cmd, prefix string) error return err } - if err := manager.NewTask(manager.NewCmdTask(r, cmd, prefix, height+oy+10, nil)); err != nil { - return err - } - - return nil -} - -func (gui *Gui) newTask(view *gocui.View, f func(chan struct{}) error) error { - manager := gui.getManager(view) - - if err := manager.NewTask(f); err != nil { + if err := manager.NewTask(manager.NewCmdTask(r, cmd, prefix, height+oy+10, nil), cmdStr); err != nil { return err } @@ -47,6 +38,16 @@ func (gui *Gui) newTask(view *gocui.View, f func(chan struct{}) error) error { } func (gui *Gui) newStringTask(view *gocui.View, str string) error { + // using str so that if rendering the exact same thing we don't reset the origin + return gui.newStringTaskWithKey(view, str, str) +} + +func (gui *Gui) newStringTaskWithoutScroll(view *gocui.View, str string) error { + // using empty key so that on subsequent calls we won't reset the view's origin + return gui.newStringTaskWithKey(view, str, "") +} + +func (gui *Gui) newStringTaskWithKey(view *gocui.View, str string, key string) error { manager := gui.getManager(view) f := func(stop chan struct{}) error { @@ -54,22 +55,7 @@ func (gui *Gui) newStringTask(view *gocui.View, str string) error { return nil } - if err := manager.NewTask(f); err != nil { - return err - } - - return nil -} - -func (gui *Gui) newStringTaskWithoutScroll(view *gocui.View, str string) error { - manager := gui.getManager(view) - - f := func(stop chan struct{}) error { - gui.setViewContent(view, str) - return nil - } - - if err := manager.NewTask(f); err != nil { + if err := manager.NewTask(f, key); err != nil { return err } @@ -97,8 +83,29 @@ func (gui *Gui) getManager(view *gocui.View) *tasks.ViewBufferManager { }) }, func() { + // Need to check if the content of the view is well past the origin. + // It would be better to use .ViewLinesHeight here (given it considers + // wrapping) but when this function is called they haven't been written to yet. + linesHeight := view.LinesHeight() + _, height := view.Size() + _, originY := view.Origin() + if linesHeight < originY { + newOriginY := linesHeight - height + if newOriginY < 0 { + newOriginY = 0 + } + err := view.SetOrigin(0, newOriginY) + if err != nil { + panic(err) + } + + } + view.FlushStaleCells() }, + func() { + _ = view.SetOrigin(0, 0) + }, ) gui.viewBufferManagerMap[view.Name()] = manager } diff --git a/pkg/tasks/tasks.go b/pkg/tasks/tasks.go index 8287b3927..ba86317ac 100644 --- a/pkg/tasks/tasks.go +++ b/pkg/tasks/tasks.go @@ -31,15 +31,36 @@ type ViewBufferManager struct { Log *logrus.Entry newTaskId int readLines chan int + taskKey string + onNewKey func() // beforeStart is the function that is called before starting a new task - beforeStart func() - refreshView func() - flushStaleCells func() + beforeStart func() + refreshView func() + onEndOfInput func() } -func NewViewBufferManager(log *logrus.Entry, writer io.Writer, beforeStart func(), refreshView func(), flushStaleCells func()) *ViewBufferManager { - return &ViewBufferManager{Log: log, writer: writer, beforeStart: beforeStart, refreshView: refreshView, flushStaleCells: flushStaleCells, readLines: make(chan int, 1024)} +func (m *ViewBufferManager) GetTaskKey() string { + return m.taskKey +} + +func NewViewBufferManager( + log *logrus.Entry, + writer io.Writer, + beforeStart func(), + refreshView func(), + onEndOfInput func(), + onNewKey func(), +) *ViewBufferManager { + return &ViewBufferManager{ + Log: log, + writer: writer, + beforeStart: beforeStart, + refreshView: refreshView, + onEndOfInput: onEndOfInput, + readLines: make(chan int, 1024), + onNewKey: onNewKey, + } } func (m *ViewBufferManager) ReadLines(n int) { @@ -117,11 +138,11 @@ func (m *ViewBufferManager) NewCmdTask(r io.Reader, cmd *exec.Cmd, prefix string if !ok { // if we're here then there's nothing left to scan from the source // so we're at the EOF and can flush the stale content - m.flushStaleCells() + m.onEndOfInput() m.refreshView() break outer } - _, _ = m.writer.Write(append(scanner.Bytes(), []byte("\n")...)) + _, _ = m.writer.Write(append(scanner.Bytes(), '\n')) } m.refreshView() case <-stop: @@ -179,11 +200,17 @@ func (t *ViewBufferManager) Close() { // 1) command based, where the manager can be asked to read more lines, but the command can be killed // 2) string based, where the manager can also be asked to read more lines -func (m *ViewBufferManager) NewTask(f func(stop chan struct{}) error) error { +func (m *ViewBufferManager) NewTask(f func(stop chan struct{}) error, key string) error { go utils.Safe(func() { m.taskIDMutex.Lock() m.newTaskId++ taskID := m.newTaskId + + if m.GetTaskKey() != key && m.onNewKey != nil { + m.onNewKey() + } + m.taskKey = key + m.taskIDMutex.Unlock() m.waitingMutex.Lock()