mirror of
https://github.com/jesseduffield/lazydocker.git
synced 2026-07-25 08:31:03 +00:00
Merge 5ef52132a9 into 7e7aadc207
This commit is contained in:
commit
76e3ed5dd3
15 changed files with 202 additions and 0 deletions
|
|
@ -98,6 +98,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
<kbd>esc</kbd>: zurück
|
<kbd>esc</kbd>: zurück
|
||||||
|
<kbd>g</kbd>: scroll to top
|
||||||
|
<kbd>G</kbd>: scroll to bottom
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
## Global
|
## Global
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
<kbd>esc</kbd>: return
|
<kbd>esc</kbd>: return
|
||||||
|
<kbd>g</kbd>: scroll to top
|
||||||
|
<kbd>G</kbd>: scroll to bottom
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
## Global
|
## Global
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
<kbd>esc</kbd>: regresar
|
<kbd>esc</kbd>: regresar
|
||||||
|
<kbd>g</kbd>: scroll to top
|
||||||
|
<kbd>G</kbd>: scroll to bottom
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
## Global
|
## Global
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
<kbd>esc</kbd>: retour
|
<kbd>esc</kbd>: retour
|
||||||
|
<kbd>g</kbd>: scroll to top
|
||||||
|
<kbd>G</kbd>: scroll to bottom
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
## Global
|
## Global
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
<kbd>esc</kbd>: terug
|
<kbd>esc</kbd>: terug
|
||||||
|
<kbd>g</kbd>: scroll to top
|
||||||
|
<kbd>G</kbd>: scroll to bottom
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
## Globaal
|
## Globaal
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
<kbd>esc</kbd>: powrót
|
<kbd>esc</kbd>: powrót
|
||||||
|
<kbd>g</kbd>: scroll to top
|
||||||
|
<kbd>G</kbd>: scroll to bottom
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
## Globalne
|
## Globalne
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
<kbd>esc</kbd>: retornar
|
<kbd>esc</kbd>: retornar
|
||||||
|
<kbd>g</kbd>: scroll to top
|
||||||
|
<kbd>G</kbd>: scroll to bottom
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
## Global
|
## Global
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
<kbd>esc</kbd>: dönüş
|
<kbd>esc</kbd>: dönüş
|
||||||
|
<kbd>g</kbd>: scroll to top
|
||||||
|
<kbd>G</kbd>: scroll to bottom
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
## Global
|
## Global
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
<kbd>esc</kbd>: 返回
|
<kbd>esc</kbd>: 返回
|
||||||
|
<kbd>g</kbd>: scroll to top
|
||||||
|
<kbd>G</kbd>: scroll to bottom
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
## 全局
|
## 全局
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,10 @@ type guiState struct {
|
||||||
// Maintains the state of manual filtering i.e. typing in a substring
|
// Maintains the state of manual filtering i.e. typing in a substring
|
||||||
// to filter on in the current panel.
|
// to filter on in the current panel.
|
||||||
Filter filterState
|
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 {
|
type filterState struct {
|
||||||
|
|
@ -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) {
|
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
|
_ = function() // time.Tick doesn't run immediately so we'll do that here // TODO: maybe change
|
||||||
go func() {
|
go func() {
|
||||||
|
|
|
||||||
|
|
@ -461,6 +461,20 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: gui.scrollRightMain,
|
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",
|
ViewName: "filter",
|
||||||
Key: gocui.KeyEnter,
|
Key: gocui.KeyEnter,
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,33 @@ package gui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/jesseduffield/gocui"
|
"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 {
|
func (gui *Gui) scrollUpMain() error {
|
||||||
mainView := gui.Views.Main
|
mainView := gui.Views.Main
|
||||||
mainView.Autoscroll = false
|
mainView.Autoscroll = false
|
||||||
|
|
@ -73,6 +96,33 @@ func (gui *Gui) jumpToTopMain(g *gocui.Gui, v *gocui.View) error {
|
||||||
return nil
|
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 {
|
func (gui *Gui) onMainTabClick(tabIndex int) error {
|
||||||
gui.Log.Warn(tabIndex)
|
gui.Log.Warn(tabIndex)
|
||||||
|
|
||||||
|
|
|
||||||
100
pkg/gui/main_panel_test.go
Normal file
100
pkg/gui/main_panel_test.go
Normal file
|
|
@ -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))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -230,6 +230,8 @@ func (gui *Gui) renderPanelOptions() error {
|
||||||
return gui.renderMenuOptions()
|
return gui.renderMenuOptions()
|
||||||
case "confirmation":
|
case "confirmation":
|
||||||
return gui.renderConfirmationOptions()
|
return gui.renderConfirmationOptions()
|
||||||
|
case "main":
|
||||||
|
return gui.renderMainOptions()
|
||||||
}
|
}
|
||||||
return gui.renderGlobalOptions()
|
return gui.renderGlobalOptions()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,8 @@ type TranslationSet struct {
|
||||||
Recreate string
|
Recreate string
|
||||||
PreviousContext string
|
PreviousContext string
|
||||||
NextContext string
|
NextContext string
|
||||||
|
GotoTop string
|
||||||
|
GotoBottom string
|
||||||
Attach string
|
Attach string
|
||||||
ViewLogs string
|
ViewLogs string
|
||||||
UpProject string
|
UpProject string
|
||||||
|
|
@ -194,6 +196,8 @@ func englishSet() TranslationSet {
|
||||||
Recreate: "recreate",
|
Recreate: "recreate",
|
||||||
PreviousContext: "previous tab",
|
PreviousContext: "previous tab",
|
||||||
NextContext: "next tab",
|
NextContext: "next tab",
|
||||||
|
GotoTop: "scroll to top",
|
||||||
|
GotoBottom: "scroll to bottom",
|
||||||
Attach: "attach",
|
Attach: "attach",
|
||||||
ViewLogs: "view logs",
|
ViewLogs: "view logs",
|
||||||
UpProject: "up project",
|
UpProject: "up project",
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue