2018-05-26 05:23:39 +02:00
package main
import (
// "io"
// "io/ioutil"
// "strings"
2018-06-01 15:23:31 +02:00
"errors"
2018-05-26 05:23:39 +02:00
"strings"
"github.com/fatih/color"
2018-06-05 10:47:31 +02:00
"github.com/jesseduffield/gocui"
2018-05-26 05:23:39 +02:00
)
2018-06-01 15:23:31 +02:00
var (
// ErrNoFiles : when there are no modified files in the repo
ErrNoFiles = errors . New ( "No changed files" )
)
2018-05-26 05:23:39 +02:00
func stagedFiles ( files [ ] GitFile ) [ ] GitFile {
result := make ( [ ] GitFile , 0 )
for _ , file := range files {
if file . HasStagedChanges {
result = append ( result , file )
}
}
return result
}
func handleFilePress ( g * gocui . Gui , v * gocui . View ) error {
2018-06-09 11:06:33 +02:00
file , err := getSelectedFile ( g )
2018-06-01 15:23:31 +02:00
if err != nil {
return err
}
2018-05-26 05:23:39 +02:00
if file . HasUnstagedChanges {
stageFile ( file . Name )
} else {
unStageFile ( file . Name )
}
if err := refreshFiles ( g ) ; err != nil {
return err
}
if err := handleFileSelect ( g , v ) ; err != nil {
return err
}
return nil
}
2018-06-09 11:06:33 +02:00
func getSelectedFile ( g * gocui . Gui ) ( GitFile , error ) {
2018-05-26 05:23:39 +02:00
if len ( state . GitFiles ) == 0 {
2018-06-01 15:23:31 +02:00
return GitFile { } , ErrNoFiles
2018-05-26 05:23:39 +02:00
}
2018-06-09 11:06:33 +02:00
filesView , err := g . View ( "files" )
if err != nil {
panic ( err )
}
lineNumber := getItemPosition ( filesView )
2018-06-01 15:23:31 +02:00
return state . GitFiles [ lineNumber ] , nil
2018-05-26 05:23:39 +02:00
}
func handleFileRemove ( g * gocui . Gui , v * gocui . View ) error {
2018-06-09 11:06:33 +02:00
file , err := getSelectedFile ( g )
2018-06-01 15:23:31 +02:00
if err != nil {
return err
}
2018-05-26 05:23:39 +02:00
var deleteVerb string
if file . Tracked {
deleteVerb = "checkout"
} else {
deleteVerb = "delete"
}
2018-05-27 08:32:09 +02:00
return createConfirmationPanel ( g , v , strings . Title ( deleteVerb ) + " file" , "Are you sure you want to " + deleteVerb + " " + file . Name + " (you will lose your changes)? (y/n)" , func ( g * gocui . Gui , v * gocui . View ) error {
2018-05-26 05:23:39 +02:00
if err := removeFile ( file ) ; err != nil {
panic ( err )
}
return refreshFiles ( g )
} , nil )
}
2018-06-01 15:23:31 +02:00
func handleIgnoreFile ( g * gocui . Gui , v * gocui . View ) error {
2018-06-09 11:06:33 +02:00
file , err := getSelectedFile ( g )
2018-06-01 15:23:31 +02:00
if err != nil {
return err
}
if file . Tracked {
return createErrorPanel ( g , "Cannot ignore tracked files" )
}
gitIgnore ( file . Name )
return refreshFiles ( g )
}
2018-06-09 11:06:33 +02:00
func renderfilesOptions ( g * gocui . Gui , gitFile * GitFile ) error {
optionsMap := map [ string ] string {
"tab" : "next panel" ,
"S" : "stash files" ,
"c" : "commit changes" ,
"o" : "open" ,
"s" : "open in sublime" ,
"i" : "ignore" ,
"d" : "delete" ,
"space" : "toggle staged" ,
}
if state . HasMergeConflicts {
optionsMap [ "a" ] = "abort merge"
optionsMap [ "m" ] = "resolve merge conflicts"
}
if gitFile == nil {
return renderOptionsMap ( g , optionsMap )
}
if gitFile . Tracked {
optionsMap [ "d" ] = "checkout"
}
return renderOptionsMap ( g , optionsMap )
}
2018-05-26 05:23:39 +02:00
func handleFileSelect ( g * gocui . Gui , v * gocui . View ) error {
2018-06-09 11:06:33 +02:00
gitFile , err := getSelectedFile ( g )
2018-06-01 15:23:31 +02:00
if err != nil {
if err != ErrNoFiles {
return err
}
renderString ( g , "main" , "No changed files" )
colorLog ( color . FgRed , "error" )
2018-06-09 11:06:33 +02:00
return renderfilesOptions ( g , nil )
2018-06-01 15:23:31 +02:00
}
2018-06-09 11:06:33 +02:00
renderfilesOptions ( g , & gitFile )
var content string
if gitFile . HasMergeConflicts {
return refreshMergePanel ( g )
2018-05-26 05:23:39 +02:00
}
2018-06-09 11:06:33 +02:00
content = getDiff ( gitFile )
return renderString ( g , "main" , content )
2018-05-26 05:23:39 +02:00
}
2018-06-05 10:49:10 +02:00
func handleCommitPress ( g * gocui . Gui , filesView * gocui . View ) error {
2018-06-09 11:06:33 +02:00
if len ( stagedFiles ( state . GitFiles ) ) == 0 && ! state . HasMergeConflicts {
2018-06-05 10:49:10 +02:00
return createErrorPanel ( g , "There are no staged files to commit" )
}
createPromptPanel ( g , filesView , "Commit message" , func ( g * gocui . Gui , v * gocui . View ) error {
message := trimmedContent ( v )
if message == "" {
return createErrorPanel ( g , "You cannot commit without a commit message" )
}
if err := gitCommit ( message ) ; err != nil {
panic ( err )
}
refreshFiles ( g )
return refreshCommits ( g )
} )
return nil
}
2018-06-01 15:23:31 +02:00
func genericFileOpen ( g * gocui . Gui , v * gocui . View , open func ( string ) ( string , error ) ) error {
2018-06-09 11:06:33 +02:00
file , err := getSelectedFile ( g )
2018-06-01 15:23:31 +02:00
if err != nil {
return err
}
_ , err = open ( file . Name )
2018-05-26 05:23:39 +02:00
return err
}
2018-06-01 15:23:31 +02:00
func handleFileOpen ( g * gocui . Gui , v * gocui . View ) error {
return genericFileOpen ( g , v , openFile )
}
2018-05-26 05:23:39 +02:00
func handleSublimeFileOpen ( g * gocui . Gui , v * gocui . View ) error {
2018-06-01 15:23:31 +02:00
return genericFileOpen ( g , v , sublimeOpenFile )
2018-05-26 05:23:39 +02:00
}
2018-06-09 11:06:33 +02:00
func refreshStateGitFiles ( ) {
2018-05-26 05:23:39 +02:00
// get files to stage
gitFiles := getGitStatusFiles ( )
state . GitFiles = mergeGitStatusFiles ( state . GitFiles , gitFiles )
2018-06-09 11:06:33 +02:00
updateHasMergeConflictStatus ( )
}
2018-05-26 05:23:39 +02:00
2018-06-09 11:06:33 +02:00
func updateHasMergeConflictStatus ( ) error {
merging , err := isInMergeState ( )
if err != nil {
return err
}
state . HasMergeConflicts = merging
return nil
}
func renderGitFile ( gitFile GitFile , filesView * gocui . View ) {
// potentially inefficient to be instantiating these color
// objects with each render
2018-05-26 05:23:39 +02:00
red := color . New ( color . FgRed )
green := color . New ( color . FgGreen )
2018-06-09 11:06:33 +02:00
if ! gitFile . Tracked {
red . Fprintln ( filesView , gitFile . DisplayString )
return
}
green . Fprint ( filesView , gitFile . DisplayString [ 0 : 1 ] )
red . Fprint ( filesView , gitFile . DisplayString [ 1 : 3 ] )
if gitFile . HasUnstagedChanges {
red . Fprintln ( filesView , gitFile . Name )
} else {
green . Fprintln ( filesView , gitFile . Name )
}
}
func catSelectedFile ( g * gocui . Gui ) ( string , error ) {
item , err := getSelectedFile ( g )
if err != nil {
if err != ErrNoFiles {
return "" , err
2018-05-26 05:23:39 +02:00
}
2018-06-09 11:06:33 +02:00
return "" , renderString ( g , "main" , "No file to display" )
}
cat , err := catFile ( item . Name )
if err != nil {
panic ( err )
}
return cat , nil
}
func refreshFiles ( g * gocui . Gui ) error {
filesView , err := g . View ( "files" )
if err != nil {
return err
}
refreshStateGitFiles ( )
filesView . Clear ( )
for _ , gitFile := range state . GitFiles {
renderGitFile ( gitFile , filesView )
2018-05-26 05:23:39 +02:00
}
correctCursor ( filesView )
2018-06-05 10:49:10 +02:00
if filesView == g . CurrentView ( ) {
handleFileSelect ( g , filesView )
}
2018-05-26 05:23:39 +02:00
return nil
}
2018-05-26 07:44:44 +02:00
2018-05-27 08:32:09 +02:00
func pullFiles ( g * gocui . Gui , v * gocui . View ) error {
devLog ( "pulling..." )
2018-06-09 11:06:33 +02:00
createMessagePanel ( g , v , "" , "Pulling..." )
2018-05-27 08:32:09 +02:00
go func ( ) {
if output , err := gitPull ( ) ; err != nil {
2018-06-05 10:49:10 +02:00
createErrorPanel ( g , output )
2018-05-27 08:32:09 +02:00
} else {
closeConfirmationPrompt ( g )
2018-06-01 15:23:31 +02:00
refreshCommits ( g )
refreshFiles ( g )
refreshStatus ( g )
devLog ( "pulled." )
2018-05-27 08:32:09 +02:00
}
} ( )
2018-06-01 15:23:31 +02:00
return nil
2018-05-26 07:44:44 +02:00
}
2018-05-27 08:32:09 +02:00
func pushFiles ( g * gocui . Gui , v * gocui . View ) error {
devLog ( "pushing..." )
2018-06-09 11:06:33 +02:00
createMessagePanel ( g , v , "" , "Pushing..." )
2018-05-27 08:32:09 +02:00
go func ( ) {
if output , err := gitPush ( ) ; err != nil {
2018-06-05 10:49:10 +02:00
createErrorPanel ( g , output )
2018-05-27 08:32:09 +02:00
} else {
closeConfirmationPrompt ( g )
2018-06-01 15:23:31 +02:00
refreshCommits ( g )
refreshStatus ( g )
devLog ( "pushed." )
2018-05-27 08:32:09 +02:00
}
} ( )
return nil
}
2018-06-09 11:06:33 +02:00
func handleSwitchToMerge ( g * gocui . Gui , v * gocui . View ) error {
mergeView , err := g . View ( "main" )
if err != nil {
return err
}
file , err := getSelectedFile ( g )
if err != nil {
if err != ErrNoFiles {
return err
}
return nil
}
if ! file . HasMergeConflicts {
return nil
}
switchFocus ( g , v , mergeView )
return refreshMergePanel ( g )
}
func handleAbortMerge ( g * gocui . Gui , v * gocui . View ) error {
output , err := gitAbortMerge ( )
if err != nil {
return createErrorPanel ( g , output )
}
createMessagePanel ( g , v , "" , "Merge aborted" )
return refreshFiles ( g )
}