Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/keybindings/Keybindings_de.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct

<pre>
<kbd>esc</kbd>: zurück
<kbd>g</kbd>: scroll to top
<kbd>G</kbd>: scroll to bottom
</pre>

## Global
Expand Down
2 changes: 2 additions & 0 deletions docs/keybindings/Keybindings_en.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct

<pre>
<kbd>esc</kbd>: return
<kbd>g</kbd>: scroll to top
<kbd>G</kbd>: scroll to bottom
</pre>

## Global
Expand Down
2 changes: 2 additions & 0 deletions docs/keybindings/Keybindings_es.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct

<pre>
<kbd>esc</kbd>: regresar
<kbd>g</kbd>: scroll to top
<kbd>G</kbd>: scroll to bottom
</pre>

## Global
Expand Down
2 changes: 2 additions & 0 deletions docs/keybindings/Keybindings_fr.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct

<pre>
<kbd>esc</kbd>: retour
<kbd>g</kbd>: scroll to top
<kbd>G</kbd>: scroll to bottom
</pre>

## Global
Expand Down
2 changes: 2 additions & 0 deletions docs/keybindings/Keybindings_nl.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct

<pre>
<kbd>esc</kbd>: terug
<kbd>g</kbd>: scroll to top
<kbd>G</kbd>: scroll to bottom
</pre>

## Globaal
Expand Down
2 changes: 2 additions & 0 deletions docs/keybindings/Keybindings_pl.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct

<pre>
<kbd>esc</kbd>: powrót
<kbd>g</kbd>: scroll to top
<kbd>G</kbd>: scroll to bottom
</pre>

## Globalne
Expand Down
2 changes: 2 additions & 0 deletions docs/keybindings/Keybindings_pt.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct

<pre>
<kbd>esc</kbd>: retornar
<kbd>g</kbd>: scroll to top
<kbd>G</kbd>: scroll to bottom
</pre>

## Global
Expand Down
2 changes: 2 additions & 0 deletions docs/keybindings/Keybindings_tr.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct

<pre>
<kbd>esc</kbd>: dönüş
<kbd>g</kbd>: scroll to top
<kbd>G</kbd>: scroll to bottom
</pre>

## Global
Expand Down
2 changes: 2 additions & 0 deletions docs/keybindings/Keybindings_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct

<pre>
<kbd>esc</kbd>: 返回
<kbd>g</kbd>: scroll to top
<kbd>G</kbd>: scroll to bottom
</pre>

## 全局
Expand Down
14 changes: 14 additions & 0 deletions pkg/gui/gui.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ type guiState struct {
// Maintains the state of manual filtering i.e. typing in a substring
// to filter on in the current panel.
Filter filterState

// Timestamp of the last 'g' key press in the main panel, used to detect
// the vim-style "gg" double press that jumps to the top.
lastGPressedAt time.Time
}

type filterState struct {
Expand Down Expand Up @@ -167,6 +171,16 @@ func (gui *Gui) renderGlobalOptions() error {
})
}

func (gui *Gui) renderMainOptions() error {
return gui.renderOptionsMap(map[string]string{
"PgUp/PgDn": gui.Tr.Scroll,
"← → ↑ ↓": gui.Tr.Navigate,
"gg": gui.Tr.GotoTop,
"G": gui.Tr.GotoBottom,
"esc": gui.Tr.Return,
})
}

func (gui *Gui) goEvery(interval time.Duration, function func() error) {
_ = function() // time.Tick doesn't run immediately so we'll do that here // TODO: maybe change
go func() {
Expand Down
14 changes: 14 additions & 0 deletions pkg/gui/keybindings.go
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,20 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Modifier: gocui.ModNone,
Handler: gui.scrollRightMain,
},
{
ViewName: "main",
Key: 'g',
Modifier: gocui.ModNone,
Handler: gui.gotoTopMain,
Description: gui.Tr.GotoTop,
},
{
ViewName: "main",
Key: 'G',
Modifier: gocui.ModNone,
Handler: gui.gotoBottomMain,
Description: gui.Tr.GotoBottom,
},
{
ViewName: "filter",
Key: gocui.KeyEnter,
Expand Down
50 changes: 50 additions & 0 deletions pkg/gui/main_panel.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,33 @@ package gui

import (
"math"
"time"

"github.com/jesseduffield/gocui"
)

// gPressTimeout is the window within which two consecutive 'g' presses are
// treated as the vim-style "gg" command to jump to the top of the main panel.
const gPressTimeout = 250 * time.Millisecond

// isDoublePress reports whether `now` follows `previous` closely enough to be
// considered the second half of a double key press.
func isDoublePress(previous, now time.Time, timeout time.Duration) bool {
return !previous.IsZero() && now.Sub(previous) <= timeout
}

// gotoBottomOriginY returns the vertical origin that scrolls the main panel to
// its final line. When scrollPastBottom is false the last page of content is
// kept on screen rather than scrolling it off the top.
func gotoBottomOriginY(totalLines, viewHeight int, scrollPastBottom bool) int {
reservedLines := 0
if !scrollPastBottom {
reservedLines = viewHeight
}

return int(math.Max(0, float64(totalLines-reservedLines)))
}

func (gui *Gui) scrollUpMain() error {
mainView := gui.Views.Main
mainView.Autoscroll = false
Expand Down Expand Up @@ -73,6 +96,33 @@ func (gui *Gui) jumpToTopMain(g *gocui.Gui, v *gocui.View) error {
return nil
}

// gotoTopMain implements the vim-style "gg" command: the first 'g' arms the
// sequence and the second 'g' pressed within gPressTimeout jumps to the top of
// the main panel.
func (gui *Gui) gotoTopMain(g *gocui.Gui, v *gocui.View) error {
now := time.Now()
if !isDoublePress(gui.State.lastGPressedAt, now, gPressTimeout) {
gui.State.lastGPressedAt = now
return nil
}

gui.State.lastGPressedAt = time.Time{}
return gui.jumpToTopMain(g, v)
}

// gotoBottomMain implements the vim-style "G" command, jumping to the bottom of
// the main panel.
func (gui *Gui) gotoBottomMain(g *gocui.Gui, v *gocui.View) error {
mainView := gui.Views.Main
mainView.Autoscroll = false

_, viewHeight := mainView.Size()
ox, _ := mainView.Origin()
newOy := gotoBottomOriginY(mainView.ViewLinesHeight(), viewHeight, gui.Config.UserConfig.Gui.ScrollPastBottom)

return mainView.SetOrigin(ox, newOy)
}

func (gui *Gui) onMainTabClick(tabIndex int) error {
gui.Log.Warn(tabIndex)

Expand Down
100 changes: 100 additions & 0 deletions pkg/gui/main_panel_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package gui

import (
"testing"
"time"

"github.com/stretchr/testify/assert"
)

func TestGotoBottomOriginY(t *testing.T) {
type scenario struct {
name string
totalLines int
viewHeight int
scrollPastBottom bool
expected int
}

scenarios := []scenario{
{
name: "keeps the last page on screen when not scrolling past bottom",
totalLines: 100,
viewHeight: 20,
scrollPastBottom: false,
expected: 80,
},
{
name: "scrolls all content off the top when scrolling past bottom",
totalLines: 100,
viewHeight: 20,
scrollPastBottom: true,
expected: 100,
},
{
name: "never returns a negative origin when content fits the view",
totalLines: 5,
viewHeight: 20,
scrollPastBottom: false,
expected: 0,
},
{
name: "handles empty content",
totalLines: 0,
viewHeight: 20,
scrollPastBottom: true,
expected: 0,
},
}

for _, s := range scenarios {
t.Run(s.name, func(t *testing.T) {
assert.Equal(t, s.expected, gotoBottomOriginY(s.totalLines, s.viewHeight, s.scrollPastBottom))
})
}
}

func TestIsDoublePress(t *testing.T) {
now := time.Unix(1000, 0)
timeout := 250 * time.Millisecond

type scenario struct {
name string
previous time.Time
now time.Time
expected bool
}

scenarios := []scenario{
{
name: "no previous press is not a double press",
previous: time.Time{},
now: now,
expected: false,
},
{
name: "second press within the timeout is a double press",
previous: now.Add(-100 * time.Millisecond),
now: now,
expected: true,
},
{
name: "second press exactly on the timeout is a double press",
previous: now.Add(-timeout),
now: now,
expected: true,
},
{
name: "second press after the timeout is not a double press",
previous: now.Add(-timeout - time.Millisecond),
now: now,
expected: false,
},
}

for _, s := range scenarios {
t.Run(s.name, func(t *testing.T) {
assert.Equal(t, s.expected, isDoublePress(s.previous, s.now, timeout))
})
}
}
2 changes: 2 additions & 0 deletions pkg/gui/view_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,8 @@ func (gui *Gui) renderPanelOptions() error {
return gui.renderMenuOptions()
case "confirmation":
return gui.renderConfirmationOptions()
case "main":
return gui.renderMainOptions()
}
return gui.renderGlobalOptions()
}
Expand Down
4 changes: 4 additions & 0 deletions pkg/i18n/english.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ type TranslationSet struct {
Recreate string
PreviousContext string
NextContext string
GotoTop string
GotoBottom string
Attach string
ViewLogs string
UpProject string
Expand Down Expand Up @@ -194,6 +196,8 @@ func englishSet() TranslationSet {
Recreate: "recreate",
PreviousContext: "previous tab",
NextContext: "next tab",
GotoTop: "scroll to top",
GotoBottom: "scroll to bottom",
Attach: "attach",
ViewLogs: "view logs",
UpProject: "up project",
Expand Down