2018-08-14 11:05:26 +02:00
package gui
2018-05-26 13:23:39 +10:00
import (
2018-07-21 15:51:18 +10:00
"fmt"
"sort"
"strings"
2020-03-26 23:48:11 +11:00
"sync"
2018-05-26 13:23:39 +10:00
2018-07-21 15:51:18 +10:00
"github.com/jesseduffield/gocui"
2022-01-28 20:44:36 +11:00
"github.com/jesseduffield/lazygit/pkg/gui/types"
2018-08-19 07:20:50 -04:00
"github.com/jesseduffield/lazygit/pkg/utils"
2018-08-16 01:53:53 -04:00
"github.com/spkg/bom"
2018-05-26 13:23:39 +10:00
)
2020-08-16 13:58:29 +10:00
func ( gui * Gui ) getCyclableWindows ( ) [ ] string {
2020-05-17 21:54:51 +10:00
return [ ] string { "status" , "files" , "branches" , "commits" , "stash" }
}
2018-06-06 12:17:49 +10:00
2022-01-28 20:44:36 +11:00
func getScopeNames ( scopes [ ] types . RefreshableView ) [ ] string {
scopeNameMap := map [ types . RefreshableView ] string {
types . COMMITS : "commits" ,
types . BRANCHES : "branches" ,
types . FILES : "files" ,
types . SUBMODULES : "submodules" ,
types . STASH : "stash" ,
types . REFLOG : "reflog" ,
types . TAGS : "tags" ,
types . REMOTES : "remotes" ,
types . STATUS : "status" ,
types . BISECT_INFO : "bisect" ,
2020-09-26 10:23:10 +10:00
}
scopeNames := make ( [ ] string , len ( scopes ) )
for i , scope := range scopes {
scopeNames [ i ] = scopeNameMap [ scope ]
}
return scopeNames
}
2022-01-28 20:44:36 +11:00
func getModeName ( mode types . RefreshMode ) string {
2020-09-26 10:23:10 +10:00
switch mode {
2022-01-28 20:44:36 +11:00
case types . SYNC :
2020-09-26 10:23:10 +10:00
return "sync"
2022-01-28 20:44:36 +11:00
case types . ASYNC :
2020-09-26 10:23:10 +10:00
return "async"
2022-01-28 20:44:36 +11:00
case types . BLOCK_UI :
2020-09-26 10:23:10 +10:00
return "block-ui"
default :
return "unknown mode"
}
}
2022-01-28 20:44:36 +11:00
func arrToMap ( arr [ ] types . RefreshableView ) map [ types . RefreshableView ] bool {
output := map [ types . RefreshableView ] bool { }
2020-03-27 19:12:15 +11:00
for _ , el := range arr {
output [ el ] = true
}
return output
}
2022-01-16 14:46:53 +11:00
func ( gui * Gui ) Refresh ( options types . RefreshOptions ) error {
2022-01-28 20:44:36 +11:00
if options . Scope == nil {
2022-01-16 14:46:53 +11:00
gui . c . Log . Infof (
2020-09-26 10:23:10 +10:00
"refreshing all scopes in %s mode" ,
2022-01-28 20:44:36 +11:00
getModeName ( options . Mode ) ,
2020-09-26 10:23:10 +10:00
)
} else {
2022-01-16 14:46:53 +11:00
gui . c . Log . Infof (
2020-09-26 10:23:10 +10:00
"refreshing the following scopes in %s mode: %s" ,
2022-01-28 20:44:36 +11:00
getModeName ( options . Mode ) ,
strings . Join ( getScopeNames ( options . Scope ) , "," ) ,
2020-09-26 10:23:10 +10:00
)
}
2020-03-26 23:48:11 +11:00
wg := sync . WaitGroup { }
2020-03-27 19:12:15 +11:00
f := func ( ) {
2022-01-28 20:44:36 +11:00
var scopeMap map [ types . RefreshableView ] bool
if len ( options . Scope ) == 0 {
2022-01-16 14:46:53 +11:00
scopeMap = arrToMap ( [ ] types . RefreshableView {
types . COMMITS ,
types . BRANCHES ,
types . FILES ,
types . STASH ,
types . REFLOG ,
types . TAGS ,
types . REMOTES ,
types . STATUS ,
types . BISECT_INFO ,
} )
2020-03-27 19:12:15 +11:00
} else {
2022-01-28 20:44:36 +11:00
scopeMap = arrToMap ( options . Scope )
2020-03-27 19:12:15 +11:00
}
2022-01-16 14:46:53 +11:00
refresh := func ( f func ( ) ) {
2020-03-27 19:12:15 +11:00
wg . Add ( 1 )
func ( ) {
2022-01-28 20:44:36 +11:00
if options . Mode == types . ASYNC {
2022-01-16 14:46:53 +11:00
go utils . Safe ( f )
2020-03-27 19:12:15 +11:00
} else {
2022-01-16 14:46:53 +11:00
f ( )
2020-03-27 19:12:15 +11:00
}
wg . Done ( )
} ( )
}
2020-03-26 23:48:11 +11:00
2022-01-16 14:46:53 +11:00
if scopeMap [ types . COMMITS ] || scopeMap [ types . BRANCHES ] || scopeMap [ types . REFLOG ] || scopeMap [ types . BISECT_INFO ] {
refresh ( gui . refreshCommits )
} else if scopeMap [ types . REBASE_COMMITS ] {
// the above block handles rebase commits so we only need to call this one
// if we've asked specifically for rebase commits and not those other things
refresh ( func ( ) { _ = gui . refreshRebaseCommits ( ) } )
}
2022-01-28 20:44:36 +11:00
if scopeMap [ types . FILES ] || scopeMap [ types . SUBMODULES ] {
2022-01-16 14:46:53 +11:00
refresh ( func ( ) { _ = gui . refreshFilesAndSubmodules ( ) } )
2020-03-27 19:12:15 +11:00
}
2022-01-28 20:44:36 +11:00
if scopeMap [ types . STASH ] {
2022-01-16 14:46:53 +11:00
refresh ( func ( ) { _ = gui . refreshStashEntries ( ) } )
2020-03-27 19:12:15 +11:00
}
2020-03-26 23:48:11 +11:00
2022-01-28 20:44:36 +11:00
if scopeMap [ types . TAGS ] {
2022-01-16 14:46:53 +11:00
refresh ( func ( ) { _ = gui . refreshTags ( ) } )
2020-03-28 10:27:34 +11:00
}
2022-01-28 20:44:36 +11:00
if scopeMap [ types . REMOTES ] {
2022-01-16 14:46:53 +11:00
refresh ( func ( ) { _ = gui . refreshRemotes ( ) } )
2020-03-28 10:27:34 +11:00
}
2020-03-27 19:12:15 +11:00
wg . Wait ( )
2020-03-26 23:48:11 +11:00
2020-03-28 10:27:34 +11:00
gui . refreshStatus ( )
2022-01-28 20:44:36 +11:00
if options . Then != nil {
options . Then ( )
2020-03-27 19:12:15 +11:00
}
}
2022-01-28 20:44:36 +11:00
if options . Mode == types . BLOCK_UI {
2022-01-15 12:04:00 +11:00
gui . OnUIThread ( func ( ) error {
2020-03-27 19:12:15 +11:00
f ( )
return nil
} )
} else {
f ( )
}
2019-03-11 13:04:08 +11:00
2020-03-26 23:48:11 +11:00
return nil
2018-06-06 12:17:49 +10:00
}
2018-08-14 11:05:26 +02:00
func ( gui * Gui ) resetOrigin ( v * gocui . View ) error {
2019-04-25 21:37:19 +02:00
_ = v . SetCursor ( 0 , 0 )
2018-07-21 15:51:18 +10:00
return v . SetOrigin ( 0 , 0 )
2018-06-09 19:06:33 +10:00
}
2019-01-15 20:12:31 +11:00
func ( gui * Gui ) cleanString ( s string ) string {
2018-12-12 22:34:20 +11:00
output := string ( bom . Clean ( [ ] byte ( s ) ) )
2019-01-15 20:12:31 +11:00
return utils . NormalizeLinefeeds ( output )
}
2021-04-11 10:05:19 +10:00
func ( gui * Gui ) setViewContent ( v * gocui . View , s string ) {
2022-01-15 12:04:00 +11:00
v . SetContent ( gui . cleanString ( s ) )
2021-04-11 10:05:19 +10:00
}
2019-01-15 20:12:31 +11:00
// renderString resets the origin of a view and sets its content
2022-01-15 12:04:00 +11:00
func ( gui * Gui ) renderString ( view * gocui . View , s string ) error {
2021-04-05 00:31:52 +10:00
if err := view . SetOrigin ( 0 , 0 ) ; err != nil {
2020-08-16 18:25:08 +10:00
return err
}
2021-04-05 00:31:52 +10:00
if err := view . SetCursor ( 0 , 0 ) ; err != nil {
2020-08-16 18:25:08 +10:00
return err
}
2022-01-15 12:04:00 +11:00
gui . setViewContent ( view , s )
2020-08-16 18:25:08 +10:00
return nil
}
2018-08-14 11:05:26 +02:00
func ( gui * Gui ) optionsMapToString ( optionsMap map [ string ] string ) string {
2018-07-21 15:51:18 +10:00
optionsArray := make ( [ ] string , 0 )
for key , description := range optionsMap {
optionsArray = append ( optionsArray , key + ": " + description )
}
sort . Strings ( optionsArray )
return strings . Join ( optionsArray , ", " )
2018-06-09 19:06:33 +10:00
}
2020-08-23 10:50:27 +10:00
func ( gui * Gui ) renderOptionsMap ( optionsMap map [ string ] string ) {
2022-01-15 12:04:00 +11:00
_ = gui . renderString ( gui . Views . Options , gui . optionsMapToString ( optionsMap ) )
2018-06-09 19:06:33 +10:00
}
2018-07-22 12:07:36 +10:00
2019-02-25 22:11:35 +11:00
func ( gui * Gui ) currentViewName ( ) string {
currentView := gui . g . CurrentView ( )
2020-05-16 12:35:19 +10:00
if currentView == nil {
return ""
}
2018-08-14 11:05:26 +02:00
return currentView . Name ( )
}
2018-09-05 19:07:46 +10:00
2020-08-15 17:23:16 +10:00
func ( gui * Gui ) resizeCurrentPopupPanel ( ) error {
v := gui . g . CurrentView ( )
2020-08-18 22:41:14 +10:00
if v == nil {
return nil
}
2019-03-11 13:04:08 +11:00
if gui . isPopupPanel ( v . Name ( ) ) {
2021-10-17 13:00:44 +11:00
return gui . resizePopupPanel ( v , v . Buffer ( ) )
2018-09-05 19:07:46 +10:00
}
return nil
}
2021-10-17 13:00:44 +11:00
func ( gui * Gui ) resizePopupPanel ( v * gocui . View , content string ) error {
2018-09-05 19:07:46 +10:00
// If the confirmation panel is already displayed, just resize the width,
// otherwise continue
2020-08-15 17:23:16 +10:00
x0 , y0 , x1 , y1 := gui . getConfirmationPanelDimensions ( v . Wrap , content )
2018-09-05 19:07:46 +10:00
vx0 , vy0 , vx1 , vy1 := v . Dimensions ( )
if vx0 == x0 && vy0 == y0 && vx1 == x1 && vy1 == y1 {
return nil
}
2020-08-15 17:23:16 +10:00
_ , err := gui . g . SetView ( v . Name ( ) , x0 , y0 , x1 , y1 , 0 )
2018-09-05 19:07:46 +10:00
return err
}
2018-12-04 19:50:11 +11:00
2022-01-16 14:46:53 +11:00
func ( gui * Gui ) changeSelectedLine ( panelState types . IListPanelState , total int , change int ) {
2019-11-16 14:00:27 +11:00
// TODO: find out why we're doing this
2020-08-20 08:52:51 +10:00
line := panelState . GetSelectedLineIdx ( )
if line == - 1 {
2019-11-16 14:00:27 +11:00
return
}
2020-08-20 08:52:51 +10:00
var newLine int
if line + change < 0 {
newLine = 0
} else if line + change >= total {
newLine = total - 1
2018-12-04 19:50:11 +11:00
} else {
2020-08-20 08:52:51 +10:00
newLine = line + change
2018-12-04 19:50:11 +11:00
}
2020-08-20 08:52:51 +10:00
panelState . SetSelectedLineIdx ( newLine )
2018-12-04 19:50:11 +11:00
}
2022-01-16 14:46:53 +11:00
func ( gui * Gui ) refreshSelectedLine ( panelState types . IListPanelState , total int ) {
2020-08-20 08:52:51 +10:00
line := panelState . GetSelectedLineIdx ( )
if line == - 1 && total > 0 {
panelState . SetSelectedLineIdx ( 0 )
} else if total - 1 < line {
panelState . SetSelectedLineIdx ( total - 1 )
2018-12-04 19:50:11 +11:00
}
}
2018-12-07 18:52:31 +11:00
2020-02-25 20:11:07 +11:00
func ( gui * Gui ) renderDisplayStrings ( v * gocui . View , displayStrings [ ] [ ] string ) {
2021-11-01 09:35:54 +11:00
list := utils . RenderDisplayStrings ( displayStrings )
v . SetContent ( list )
2020-02-25 20:11:07 +11:00
}
2021-11-02 16:39:15 +11:00
func ( gui * Gui ) renderDisplayStringsAtPos ( v * gocui . View , y int , displayStrings [ ] [ ] string ) {
list := utils . RenderDisplayStrings ( displayStrings )
v . OverwriteLines ( y , list )
}
2020-08-23 10:50:27 +10:00
func ( gui * Gui ) globalOptionsMap ( ) map [ string ] string {
2022-01-16 14:46:53 +11:00
keybindingConfig := gui . c . UserConfig . Keybinding
2020-10-03 14:54:55 +10:00
2020-08-23 10:50:27 +10:00
return map [ string ] string {
2022-01-16 14:46:53 +11:00
fmt . Sprintf ( "%s/%s" , gui . getKeyDisplay ( keybindingConfig . Universal . ScrollUpMain ) , gui . getKeyDisplay ( keybindingConfig . Universal . ScrollDownMain ) ) : gui . c . Tr . LcScroll ,
fmt . Sprintf ( "%s %s %s %s" , gui . getKeyDisplay ( keybindingConfig . Universal . PrevBlock ) , gui . getKeyDisplay ( keybindingConfig . Universal . NextBlock ) , gui . getKeyDisplay ( keybindingConfig . Universal . PrevItem ) , gui . getKeyDisplay ( keybindingConfig . Universal . NextItem ) ) : gui . c . Tr . LcNavigate ,
gui . getKeyDisplay ( keybindingConfig . Universal . Return ) : gui . c . Tr . LcCancel ,
gui . getKeyDisplay ( keybindingConfig . Universal . Quit ) : gui . c . Tr . LcQuit ,
gui . getKeyDisplay ( keybindingConfig . Universal . OptionMenu ) : gui . c . Tr . LcMenu ,
fmt . Sprintf ( "%s-%s" , gui . getKeyDisplay ( keybindingConfig . Universal . JumpToBlock [ 0 ] ) , gui . getKeyDisplay ( keybindingConfig . Universal . JumpToBlock [ len ( keybindingConfig . Universal . JumpToBlock ) - 1 ] ) ) : gui . c . Tr . LcJump ,
fmt . Sprintf ( "%s/%s" , gui . getKeyDisplay ( keybindingConfig . Universal . ScrollLeft ) , gui . getKeyDisplay ( keybindingConfig . Universal . ScrollRight ) ) : gui . c . Tr . LcScrollLeftRight ,
2020-08-23 10:50:27 +10:00
}
2020-03-29 10:31:34 +11:00
}
2019-03-11 13:04:08 +11:00
func ( gui * Gui ) isPopupPanel ( viewName string ) bool {
return viewName == "commitMessage" || viewName == "credentials" || viewName == "confirmation" || viewName == "menu"
}
2019-02-25 22:11:35 +11:00
func ( gui * Gui ) popupPanelFocused ( ) bool {
2019-03-11 13:04:08 +11:00
return gui . isPopupPanel ( gui . currentViewName ( ) )
2019-02-25 22:11:35 +11:00
}
2019-11-10 16:20:35 +11:00
2020-05-18 22:00:07 +10:00
// secondaryViewFocused tells us whether it appears that the secondary view is focused. The view is actually never focused for real: we just swap the main and secondary views and then you're still focused on the main view so that we can give you access to all its keybindings for free. I will probably regret this design decision soon enough.
func ( gui * Gui ) secondaryViewFocused ( ) bool {
2020-10-08 08:01:04 +11:00
state := gui . State . Panels . LineByLine
return state != nil && state . SecondaryFocused
2020-05-18 22:00:07 +10:00
}
2020-08-17 21:58:30 +10:00
2020-08-19 19:31:58 +10:00
func ( gui * Gui ) onViewTabClick ( viewName string , tabIndex int ) error {
2022-01-16 14:46:53 +11:00
context := gui . State . ViewTabContextMap [ viewName ] [ tabIndex ] . Contexts [ 0 ]
2020-08-19 19:31:58 +10:00
2022-01-16 14:46:53 +11:00
return gui . c . PushContext ( context )
2020-08-19 19:31:58 +10:00
}
2021-04-02 19:20:40 +11:00
func ( gui * Gui ) handleNextTab ( ) error {
2021-06-06 12:41:55 +02:00
v := getTabbedView ( gui )
2021-04-02 20:51:52 +11:00
if v == nil {
2021-04-02 19:20:40 +11:00
return nil
}
2020-08-19 19:31:58 +10:00
return gui . onViewTabClick (
v . Name ( ) ,
utils . ModuloWithWrap ( v . TabIndex + 1 , len ( v . Tabs ) ) ,
)
}
2021-04-02 19:20:40 +11:00
func ( gui * Gui ) handlePrevTab ( ) error {
2021-06-06 12:41:55 +02:00
v := getTabbedView ( gui )
2021-04-02 20:51:52 +11:00
if v == nil {
2021-04-02 19:20:40 +11:00
return nil
}
2020-08-19 19:31:58 +10:00
return gui . onViewTabClick (
v . Name ( ) ,
utils . ModuloWithWrap ( v . TabIndex - 1 , len ( v . Tabs ) ) ,
)
}
2020-10-02 07:56:14 +10:00
// this is the distance we will move the cursor when paging up or down in a view
func ( gui * Gui ) pageDelta ( view * gocui . View ) int {
_ , height := view . Size ( )
delta := height - 1
if delta == 0 {
return 1
}
return delta
}
2021-06-06 12:41:55 +02:00
func getTabbedView ( gui * Gui ) * gocui . View {
2021-06-15 19:58:43 +02:00
// It safe assumption that only static contexts have tabs
context := gui . currentStaticContext ( )
view , _ := gui . g . View ( context . GetViewName ( ) )
return view
2021-06-06 12:41:55 +02:00
}
2021-11-01 09:35:54 +11:00
func ( gui * Gui ) render ( ) {
2022-01-15 12:04:00 +11:00
gui . OnUIThread ( func ( ) error { return nil } )
2021-11-01 09:35:54 +11:00
}