summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--pkg/git/repository.go4
-rw-r--r--pkg/git/stash.go104
-rw-r--r--pkg/git/status.go67
-rw-r--r--pkg/gui/gui-navigate.go54
-rw-r--r--pkg/gui/gui-util.go78
-rw-r--r--pkg/gui/gui.go16
-rw-r--r--pkg/gui/keybindings.go100
-rw-r--r--pkg/gui/mainview.go44
-rw-r--r--pkg/gui/stagedview.go54
-rw-r--r--pkg/gui/stashview.go47
-rw-r--r--pkg/gui/statusview.go158
-rw-r--r--pkg/gui/unstagedview.go50
12 files changed, 682 insertions, 94 deletions
diff --git a/pkg/git/repository.go b/pkg/git/repository.go
index e75281c..7d1fd73 100644
--- a/pkg/git/repository.go
+++ b/pkg/git/repository.go
@@ -25,6 +25,7 @@ type RepoEntity struct {
Remotes []*Remote
Commit *Commit
Commits []*Commit
+ Stasheds []*StashedItem
State RepoState
}
@@ -85,6 +86,9 @@ func InitializeRepository(directory string) (entity *RepoEntity, err error) {
entity.loadRemotes()
// set the active branch to repositories HEAD
entity.Branch = entity.getActiveBranch()
+ if err = entity.loadStashedItems(); err != nil {
+ // TODO: fix here.
+ }
if len(entity.Remotes) > 0 {
// TODO: tend to take origin/master as default
entity.Remote = entity.Remotes[0]
diff --git a/pkg/git/stash.go b/pkg/git/stash.go
new file mode 100644
index 0000000..caaaf90
--- /dev/null
+++ b/pkg/git/stash.go
@@ -0,0 +1,104 @@
+package git
+
+import (
+ "regexp"
+ "strings"
+ "strconv"
+
+ log "github.com/sirupsen/logrus"
+)
+
+var stashCommand = "stash"
+
+type StashedItem struct {
+ StashID int
+ BranchName string
+ Hash string
+ Description string
+}
+
+// // StashOption is the option argument for git stash command
+// type StashOption string
+
+// var (
+// StashPop StashOption = "pop"
+// StashPush StashOption = "push"
+// StashDrop StashOption = "drop"
+// )
+
+// // Stash used when you want to record the current state of the working
+// // directory and the index, but want to go back to a clean working directory.
+// func Stash(entity *RepoEntity, option StashOption) error {
+// args := make([]string, 0)
+// args = append(args, stashCommand)
+
+// if err := GenericGitCommand(entity.AbsPath, args); err != nil {
+// log.Warn("Error while stashing")
+// return err
+// }
+// return nil
+// }
+
+func stashGet(entity *RepoEntity, option string) string {
+ args := make([]string, 0)
+ args = append(args, stashCommand)
+ args = append(args, option)
+ out, err := GenericGitCommandWithOutput(entity.AbsPath, args)
+ if err != nil {
+ log.Warn("Error while stash command")
+ return "?"
+ }
+ return out
+}
+
+func (entity *RepoEntity) loadStashedItems() error {
+ entity.Stasheds = make([]*StashedItem, 0)
+ output := stashGet(entity, "list")
+ stashIDRegex := regexp.MustCompile(`stash@{[\d]+}:`)
+ stashIDRegexInt := regexp.MustCompile(`[\d]+`)
+ stashBranchRegex := regexp.MustCompile(`[\w]+: `)
+ stashHashRegex := regexp.MustCompile(`[\w]{7}`)
+
+ stashlist := strings.Split(output, "\n")
+ for _, stashitem := range stashlist {
+ // find id
+ id := stashIDRegexInt.FindString(stashIDRegex.FindString(stashitem))
+ i, err := strconv.Atoi(id)
+ if err != nil {
+ // probably something isn't right let's continue over this iteration
+ log.Trace("cannot initiate stashed item")
+ continue
+ }
+ // trim id section
+ trimmed := stashIDRegex.Split(stashitem, 2)[1]
+
+ // find branch
+ stashBranchRegexMatch :=stashBranchRegex.FindString(trimmed)
+ branchName := stashBranchRegexMatch[:len(stashBranchRegexMatch)-2]
+
+ // trim branch section
+ trimmed = stashBranchRegex.Split(trimmed, 2)[1]
+ hash := stashHashRegex.FindString(trimmed)
+
+ // trim hash
+ desc := stashHashRegex.Split(trimmed, 2)[1][1:]
+
+ entity.Stasheds = append(entity.Stasheds, &StashedItem{
+ StashID: i,
+ BranchName: branchName,
+ Hash: hash,
+ Description: desc,
+ })
+ }
+ return nil
+}
+
+func (entity *RepoEntity) Stash() (error) {
+ args := make([]string, 0)
+ args = append(args, stashCommand)
+ if err := GenericGitCommand(entity.AbsPath, args); err != nil {
+ log.Warn("Error while stashing")
+ return err
+ }
+ return nil
+} \ No newline at end of file
diff --git a/pkg/git/status.go b/pkg/git/status.go
new file mode 100644
index 0000000..e1207ee
--- /dev/null
+++ b/pkg/git/status.go
@@ -0,0 +1,67 @@
+package git
+
+import (
+ "os"
+ "regexp"
+ "strings"
+
+ log "github.com/sirupsen/logrus"
+)
+
+var statusCommand = "status"
+
+type File struct {
+ Name string
+ AbsPath string
+ X FileStatus
+ Y FileStatus
+}
+
+type FileStatus rune
+
+var (
+ StatusNotupdated FileStatus = ' '
+ StatusModified FileStatus = 'M'
+ StatusAdded FileStatus = 'A'
+ StatusDeleted FileStatus = 'D'
+ StatusRenamed FileStatus = 'R'
+ StatusCopied FileStatus = 'C'
+ StatusUntracked FileStatus = '?'
+ StatusIgnored FileStatus = '!'
+)
+
+func shortStatus(entity *RepoEntity, option string) string {
+ args := make([]string, 0)
+ args = append(args, statusCommand)
+ args = append(args, option)
+ args = append(args, "--short")
+ out, err := GenericGitCommandWithOutput(entity.AbsPath, args)
+ if err != nil {
+ log.Warn("Error while status command")
+ return "?"
+ }
+ return out
+}
+
+func (entity *RepoEntity) LoadFiles() ([]*File, error) {
+ files := make([]*File, 0)
+ output := shortStatus(entity, "--untracked-files=all")
+ if len(output) == 0 {
+ return files, nil
+ }
+ fileslist := strings.Split(output, "\n")
+ for _, file := range fileslist {
+ x := rune(file[0])
+ y := rune(file[1])
+ relativePathRegex := regexp.MustCompile(`[(\w|/|.)]+`)
+ path := relativePathRegex.FindString(file[2:])
+
+ files = append(files, &File{
+ Name: path,
+ AbsPath: entity.AbsPath + string(os.PathSeparator) + path,
+ X: FileStatus(x),
+ Y: FileStatus(y),
+ })
+ }
+ return files, nil
+}
diff --git a/pkg/gui/gui-navigate.go b/pkg/gui/gui-navigate.go
deleted file mode 100644
index 56d96de..0000000
--- a/pkg/gui/gui-navigate.go
+++ /dev/null
@@ -1,54 +0,0 @@
-package gui
-
-import (
- log "github.com/sirupsen/logrus"
- "github.com/jroimartin/gocui"
-)
-
-// focus to next view
-func (gui *Gui) nextView(g *gocui.Gui, v *gocui.View) error {
- var focusedViewName string
- if v == nil || v.Name() == mainViews[len(mainViews)-1].Name {
- focusedViewName = mainViews[0].Name
- } else {
- for i := range mainViews {
- if v.Name() == mainViews[i].Name {
- focusedViewName = mainViews[i+1].Name
- break
- }
- if i == len(mainViews)-1 {
- return nil
- }
- }
- }
- if _, err := g.SetCurrentView(focusedViewName); err != nil {
- log.Warn("Loading view cannot be focused.")
- return nil
- }
- gui.updateKeyBindingsView(g, focusedViewName)
- return nil
-}
-
-// focus to previous view
-func (gui *Gui) previousView(g *gocui.Gui, v *gocui.View) error {
- var focusedViewName string
- if v == nil || v.Name() == mainViews[0].Name {
- focusedViewName = mainViews[len(mainViews)-1].Name
- } else {
- for i := range mainViews {
- if v.Name() == mainViews[i].Name {
- focusedViewName = mainViews[i-1].Name
- break
- }
- if i == len(mainViews)-1 {
- return nil
- }
- }
- }
- if _, err := g.SetCurrentView(focusedViewName); err != nil {
- log.Warn("Loading view cannot be focused.")
- return nil
- }
- gui.updateKeyBindingsView(g, focusedViewName)
- return nil
-}
diff --git a/pkg/gui/gui-util.go b/pkg/gui/gui-util.go
index 2ae8cae..2338bac 100644
--- a/pkg/gui/gui-util.go
+++ b/pkg/gui/gui-util.go
@@ -1,11 +1,10 @@
package gui
import (
- "sort"
-
"github.com/isacikgoz/gitbatch/pkg/git"
"github.com/isacikgoz/gitbatch/pkg/helpers"
"github.com/jroimartin/gocui"
+ log "github.com/sirupsen/logrus"
)
// refreshes the side views of the application for given git.RepoEntity struct
@@ -26,6 +25,58 @@ func (gui *Gui) refreshViews(g *gocui.Gui, entity *git.RepoEntity) error {
return err
}
+// focus to next view
+func (gui *Gui) nextViewOfGroup(g *gocui.Gui, v *gocui.View, group []viewFeature) error {
+ var focusedViewName string
+ if v == nil || v.Name() == group[len(group)-1].Name {
+ focusedViewName = group[0].Name
+ } else {
+ for i := range group {
+ if v.Name() == group[i].Name {
+ focusedViewName = group[i+1].Name
+ break
+ }
+ if i == len(group)-1 {
+ return nil
+ }
+ }
+ }
+ if _, err := g.SetCurrentView(focusedViewName); err != nil {
+ log.WithFields(log.Fields{
+ "view": focusedViewName,
+ }).Warn("View cannot be focused.")
+ return nil
+ }
+ gui.updateKeyBindingsView(g, focusedViewName)
+ return nil
+}
+
+// focus to previous view
+func (gui *Gui) previousViewOfGroup(g *gocui.Gui, v *gocui.View, group []viewFeature) error {
+ var focusedViewName string
+ if v == nil || v.Name() == group[0].Name {
+ focusedViewName = group[len(group)-1].Name
+ } else {
+ for i := range group {
+ if v.Name() == group[i].Name {
+ focusedViewName = group[i-1].Name
+ break
+ }
+ if i == len(group)-1 {
+ return nil
+ }
+ }
+ }
+ if _, err := g.SetCurrentView(focusedViewName); err != nil {
+ log.WithFields(log.Fields{
+ "view": focusedViewName,
+ }).Warn("View cannot be focused.")
+ return nil
+ }
+ gui.updateKeyBindingsView(g, focusedViewName)
+ return nil
+}
+
// siwtch the app mode
// TODO: switching can be made with conventional iteration
func (gui *Gui) switchMode(g *gocui.Gui, v *gocui.View) error {
@@ -110,29 +161,6 @@ func writeRightHandSide(v *gocui.View, text string, cx, cy int) error {
return nil
}
-// sortByName sorts the repositories by A to Z order
-func (gui *Gui) sortByName(g *gocui.Gui, v *gocui.View) error {
- sort.Sort(git.Alphabetical(gui.State.Repositories))
- gui.refreshAfterSort(g)
- return nil
-}
-
-// sortByMod sorts the repositories according to last modifed date
-// the top element will be the last modified
-func (gui *Gui) sortByMod(g *gocui.Gui, v *gocui.View) error {
- sort.Sort(git.LastModified(gui.State.Repositories))
- gui.refreshAfterSort(g)
- return nil
-}
-
-// utility function that refreshes main and side views after that
-func (gui *Gui) refreshAfterSort(g *gocui.Gui) error {
- gui.refreshMain(g)
- entity := gui.getSelectedRepository()
- gui.refreshViews(g, entity)
- return nil
-}
-
// cursor down acts like half-page down for faster scrolling
func (gui *Gui) fastCursorDown(g *gocui.Gui, v *gocui.View) error {
if v != nil {
diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go
index 0598383..5c32bc0 100644
--- a/pkg/gui/gui.go
+++ b/pkg/gui/gui.go
@@ -194,6 +194,22 @@ func (gui *Gui) layout(g *gocui.Gui) error {
return nil
}
+// focus to next view
+func (gui *Gui) nextMainView(g *gocui.Gui, v *gocui.View) error {
+ if err := gui.nextViewOfGroup(g, v, mainViews); err != nil {
+ return err
+ }
+ return nil
+}
+
+// focus to previous view
+func (gui *Gui) previousMainView(g *gocui.Gui, v *gocui.View) error {
+ if err := gui.previousViewOfGroup(g, v, mainViews); err != nil {
+ return err
+ }
+ return nil
+}
+
// quit from the gui and end its loop
func (gui *Gui) quit(g *gocui.Gui, v *gocui.View) error {
return gocui.ErrQuit
diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go
index b2622bc..55c86bd 100644
--- a/pkg/gui/keybindings.go
+++ b/pkg/gui/keybindings.go
@@ -43,7 +43,7 @@ func (gui *Gui) generateKeybindings() error {
View: view.Name,
Key: gocui.KeyArrowLeft,
Modifier: gocui.ModNone,
- Handler: gui.previousView,
+ Handler: gui.previousMainView,
Display: "←",
Description: "Previous Panel",
Vital: false,
@@ -51,7 +51,7 @@ func (gui *Gui) generateKeybindings() error {
View: view.Name,
Key: gocui.KeyArrowRight,
Modifier: gocui.ModNone,
- Handler: gui.nextView,
+ Handler: gui.nextMainView,
Display: "→",
Description: "Next Panel",
Vital: false,
@@ -59,7 +59,7 @@ func (gui *Gui) generateKeybindings() error {
View: view.Name,
Key: 'l',
Modifier: gocui.ModNone,
- Handler: gui.nextView,
+ Handler: gui.nextMainView,
Display: "l",
Description: "Previous Panel",
Vital: false,
@@ -67,7 +67,7 @@ func (gui *Gui) generateKeybindings() error {
View: view.Name,
Key: 'h',
Modifier: gocui.ModNone,
- Handler: gui.previousView,
+ Handler: gui.previousMainView,
Display: "h",
Description: "Next Panel",
Vital: false,
@@ -77,7 +77,89 @@ func (gui *Gui) generateKeybindings() error {
gui.KeyBindings = append(gui.KeyBindings, binding)
}
}
+ // Statusviews common keybindings
+ for _, view := range statusViews {
+ statusKeybindings := []*KeyBinding{
+ {
+ View: view.Name,
+ Key: 'c',
+ Modifier: gocui.ModNone,
+ Handler: gui.closeStatusView,
+ Display: "c",
+ Description: "Close/Cancel",
+ Vital: true,
+ }, {
+ View: view.Name,
+ Key: gocui.KeyArrowLeft,
+ Modifier: gocui.ModNone,
+ Handler: gui.previousStatusView,
+ Display: "←",
+ Description: "Previous Panel",
+ Vital: false,
+ }, {
+ View: view.Name,
+ Key: gocui.KeyArrowRight,
+ Modifier: gocui.ModNone,
+ Handler: gui.nextStatusView,
+ Display: "→",
+ Description: "Next Panel",
+ Vital: false,
+ }, {
+ View: view.Name,
+ Key: 'l',
+ Modifier: gocui.ModNone,
+ Handler: gui.nextStatusView,
+ Display: "l",
+ Description: "Previous Panel",
+ Vital: false,
+ }, {
+ View: view.Name,
+ Key: 'h',
+ Modifier: gocui.ModNone,
+ Handler: gui.previousStatusView,
+ Display: "h",
+ Description: "Next Panel",
+ Vital: false,
+ }, {
+ View: view.Name,
+ Key: gocui.KeyArrowUp,
+ Modifier: gocui.ModNone,
+ Handler: gui.statusCursorUp,
+ Display: "↑",
+ Description: "Up",
+ Vital: false,
+ }, {
+ View: view.Name,
+ Key: gocui.KeyArrowDown,
+ Modifier: gocui.ModNone,
+ Handler: gui.statusCursorDown,
+ Display: "↓",
+ Description: "Down",
+ Vital: false,
+ }, {
+ View: view.Name,
+ Key: 'k',
+ Modifier: gocui.ModNone,
+ Handler: gui.statusCursorUp,
+ Display: "k",
+ Description: "Up",
+ Vital: false,
+ }, {
+ View: view.Name,
+ Key: 'j',
+ Modifier: gocui.ModNone,
+ Handler: gui.statusCursorDown,
+ Display: "j",
+ Description: "Down",
+ Vital: false,
+ },
+ }
+ for _, binding := range statusKeybindings {
+ gui.KeyBindings = append(gui.KeyBindings, binding)
+ }
+ }
individualKeybindings := []*KeyBinding{
+ // Main view controls
{
View: mainViewFeature.Name,
Key: gocui.KeyArrowUp,
@@ -167,6 +249,14 @@ func (gui *Gui) generateKeybindings() error {
Description: "Sort repositories by Modification date",
Vital: false,
}, {
+ View: mainViewFeature.Name,
+ Key: 's',
+ Modifier: gocui.ModNone,
+ Handler: gui.openStatusView,
+ Display: "s",
+ Description: "Open Status",
+ Vital: true,
+ }, {
View: "",
Key: gocui.KeyCtrlC,
Modifier: gocui.ModNone,
@@ -412,7 +502,7 @@ func (gui *Gui) generateKeybindings() error {
Display: "c",
Description: "close/cancel",
Vital: true,
- },
+ },
}
for _, binding := range individualKeybindings {
gui.KeyBindings = append(gui.KeyBindings, binding)
diff --git a/pkg/gui/mainview.go b/pkg/gui/mainview.go
index 58f8422..f912382 100644
--- a/pkg/gui/mainview.go
+++ b/pkg/gui/mainview.go
@@ -2,6 +2,7 @@ package gui
import (
"fmt"
+ "sort"
"github.com/isacikgoz/gitbatch/pkg/git"
"github.com/isacikgoz/gitbatch/pkg/queue"
@@ -36,6 +37,19 @@ func (gui *Gui) fillMain(g *gocui.Gui) error {
return nil
}
+// refresh the main view and re-render the repository representations
+func (gui *Gui) refreshMain(g *gocui.Gui) error {
+ mainView, err := g.View(mainViewFeature.Name)
+ if err != nil {
+ return err
+ }
+ mainView.Clear()
+ for _, r := range gui.State.Repositories {
+ fmt.Fprintln(mainView, gui.displayString(r))
+ }
+ return nil
+}
+
// moves the cursor downwards for the main view and if it goes to bottom it
// prevents from going further
func (gui *Gui) cursorDown(g *gocui.Gui, v *gocui.View) error {
@@ -181,15 +195,25 @@ func (gui *Gui) unmarkAllRepositories(g *gocui.Gui, v *gocui.View) error {
return nil
}
-// refresh the main view and re-render the repository representations
-func (gui *Gui) refreshMain(g *gocui.Gui) error {
- mainView, err := g.View(mainViewFeature.Name)
- if err != nil {
- return err
- }
- mainView.Clear()
- for _, r := range gui.State.Repositories {
- fmt.Fprintln(mainView, gui.displayString(r))
- }
+// sortByName sorts the repositories by A to Z order
+func (gui *Gui) sortByName(g *gocui.Gui, v *gocui.View) error {
+ sort.Sort(git.Alphabetical(gui.State.Repositories))
+ gui.refreshAfterSort(g)
+ return nil
+}
+
+// sortByMod sorts the repositories according to last modifed date
+// the top element will be the last modified
+func (gui *Gui) sortByMod(g *gocui.Gui, v *gocui.View) error {
+ sort.Sort(git.LastModified(gui.State.Repositories))
+ gui.refreshAfterSort(g)
+ return nil
+}
+
+// utility function that refreshes main and side views after that
+func (gui *Gui) refreshAfterSort(g *gocui.Gui) error {
+ gui.refreshMain(g)
+ entity := gui.getSelectedRepository()
+ gui.refreshViews(g, entity)
return nil
}
diff --git a/pkg/gui/stagedview.go b/pkg/gui/stagedview.go
new file mode 100644
index 0000000..e3684f0
--- /dev/null
+++ b/pkg/gui/stagedview.go
@@ -0,0 +1,54 @@
+package gui
+
+import (
+ "fmt"
+
+ "github.com/isacikgoz/gitbatch/pkg/git"
+ "github.com/jroimartin/gocui"
+)
+
+// staged view
+func (gui *Gui) openStageView(g *gocui.Gui) error {
+ maxX, maxY := g.Size()
+
+ v, err := g.SetView(stageViewFeature.Name, 6, 5, maxX/2-1, int(0.75*float32(maxY))-1)
+ if err != nil {
+ if err != gocui.ErrUnknownView {
+ return err
+ }
+ v.Title = stageViewFeature.Title
+ v.Wrap = true
+ }
+ entity := gui.getSelectedRepository()
+ if err := refreshStagedView(g, entity); err != nil {
+ return err
+ }
+ gui.updateKeyBindingsView(g, stageViewFeature.Name)
+ if _, err := g.SetCurrentView(stageViewFeature.Name); err != nil {
+ return err
+ }
+ return nil
+}
+
+// refresh the main view and re-render the repository representations
+func refreshStagedView(g *gocui.Gui, entity *git.RepoEntity) error {
+ stageView, err := g.View(stageViewFeature.Name)
+ if err != nil {
+ return err
+ }
+ stageView.Clear()
+ _, cy := stageView.Cursor()
+ _, oy := stageView.Origin()
+ files, _, err := generateFileLists(entity)
+ if err != nil {
+ return err
+ }
+ for i, file := range files {
+ var prefix string
+ if i == cy+oy {
+ prefix = prefix + selectionIndicator
+ }
+ fmt.Fprintf(stageView, "%s%s%s %s\n", prefix, green.Sprint(string(file.X)), red.Sprint(string(file.Y)), file.Name)
+ }
+ return nil
+}
diff --git a/pkg/gui/stashview.go b/pkg/gui/stashview.go
new file mode 100644
index 0000000..8cdc46c
--- /dev/null
+++ b/pkg/gui/stashview.go
@@ -0,0 +1,47 @@
+package gui
+
+import (
+ "fmt"
+
+ "github.com/isacikgoz/gitbatch/pkg/git"
+ "github.com/jroimartin/gocui"
+)
+
+// stash view
+func (gui *Gui) openStashView(g *gocui.Gui) error {
+ maxX, maxY := g.Size()
+
+ v, err := g.SetView(stashViewFeature.Name, 6, int(0.75*float32(maxY)), maxX-6, maxY-3)
+ if err != nil {
+ if err != gocui.ErrUnknownView {
+ return err
+ }
+ v.Title = stashViewFeature.Title
+ v.Wrap = true
+ }
+ entity := gui.getSelectedRepository()
+ if err := refreshStashView(g, entity); err != nil {
+ return err
+ }
+ return nil
+}
+
+// refresh the main view and re-render the repository representations
+func refreshStashView(g *gocui.Gui, entity *git.RepoEntity) error {
+ stashView, err := g.View(stashViewFeature.Name)
+ if err != nil {
+ return err
+ }
+ stashView.Clear()
+ _, cy := stashView.Cursor()
+ _, oy := stashView.Origin()
+ stashedItems := entity.Stasheds
+ for i, stashedItem := range stashedItems {
+ var prefix string
+ if i == cy+oy {
+ prefix = prefix + selectionIndicator
+ }
+ fmt.Fprintf(stashView, "%s%d %s: %s (%s)\n", prefix, stashedItem.StashID, cyan.Sprint(stashedItem.BranchName), stashedItem.Description, stashedItem.Hash)
+ }
+ return nil
+} \ No newline at end of file
diff --git a/pkg/gui/statusview.go b/pkg/gui/statusview.go
new file mode 100644
index 0000000..b1873e8
--- /dev/null
+++ b/pkg/gui/statusview.go
@@ -0,0 +1,158 @@
+package gui
+
+import (
+ "fmt"
+
+ "github.com/isacikgoz/gitbatch/pkg/git"
+ "github.com/jroimartin/gocui"
+)
+
+var (
+ statusHeaderViewFeature = viewFeature{Name: "status-header", Title: " Status Header "}
+ // statusViewFeature = viewFeature{Name: "status", Title: " Status "}
+ stageViewFeature = viewFeature{Name: "staged", Title: " Staged "}
+ unstageViewFeature = viewFeature{Name: "unstaged", Title: " Unstaged "}
+ stashViewFeature = viewFeature{Name: "stash", Title: " Stash "}
+
+ statusViews = []viewFeature{stageViewFeature, unstageViewFeature, stashViewFeature}
+)
+
+// open the status layout
+func (gui *Gui) openStatusView(g *gocui.Gui, v *gocui.View) error {
+ gui.openStatusHeaderView(g)
+ gui.openStageView(g)
+ gui.openUnStagedView(g)
+ gui.openStashView(g)
+ return nil
+}
+
+// focus to next view
+func (gui *Gui) nextStatusView(g *gocui.Gui, v *gocui.View) error {
+ if err := gui.nextViewOfGroup(g, v, statusViews); err != nil {
+ return err
+ }
+ return nil
+}
+
+// focus to previous view
+func (gui *Gui) previousStatusView(g *gocui.Gui, v *gocui.View) error {
+ if err := gui.previousViewOfGroup(g, v, statusViews); err != nil {
+ return err
+ }
+ return nil
+}
+
+// moves the cursor downwards for the main view and if it goes to bottom it
+// prevents from going further
+func (gui *Gui) statusCursorDown(g *gocui.Gui, v *gocui.View) error {
+ if v != nil {
+ cx, cy := v.Cursor()
+ ox, oy := v.Origin()
+ ly := len(v.BufferLines()) - 2 // why magic number? have no idea
+
+ // if we are at the end we just return
+ if cy+oy == ly {
+ return nil
+ }
+ if err := v.SetCursor(cx, cy+1); err != nil {
+
+ if err := v.SetOrigin(ox, oy+1); err != nil {
+ return err
+ }
+ }
+ entity := gui.getSelectedRepository()
+ if err := refreshStatusView(v.Name(), g, entity); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// moves the cursor upwards for the main view
+func (gui *Gui) statusCursorUp(g *gocui.Gui, v *gocui.View) error {
+ if v != nil {
+ ox, oy := v.Origin()
+ cx, cy := v.Cursor()
+ if err := v.SetCursor(cx, cy-1); err != nil && oy > 0 {
+ if err := v.SetOrigin(ox, oy-1); err != nil {
+ return err
+ }
+ }
+ entity := gui.getSelectedRepository()
+ if err := refreshStatusView(v.Name(), g, entity); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// header og the status layout
+func (gui *Gui) openStatusHeaderView(g *gocui.Gui) error {
+ maxX, _ := g.Size()
+ entity := gui.getSelectedRepository()
+ v, err := g.SetView(statusHeaderViewFeature.Name, 6, 2, maxX-6, 4)
+ if err != nil {
+ if err != gocui.ErrUnknownView {
+ return err
+ }
+ fmt.Fprintln(v, entity.AbsPath)
+ // v.Frame = false
+ v.Wrap = true
+ }
+ return nil
+}
+
+// close the opened stat views
+func (gui *Gui) closeStatusView(g *gocui.Gui, v *gocui.View) error {
+ if err := g.DeleteView(stashViewFeature.Name); err != nil {
+ return err
+ }
+ if err := g.DeleteView(unstageViewFeature.Name); err != nil {
+ return err
+ }
+ if err := g.DeleteView(stageViewFeature.Name); err != nil {
+ return err
+ }
+ if err := g.DeleteView(statusHeaderViewFeature.Name); err != nil {
+ return err
+ }
+ if _, err := g.SetCurrentView(mainViewFeature.Name); err != nil {
+ return err
+ }
+ gui.updateKeyBindingsView(g, mainViewFeature.Name)
+ return nil
+}
+
+func generateFileLists(entity *git.RepoEntity) (staged, unstaged []*git.File, err error) {
+ files, err := entity.LoadFiles()
+ if err != nil {
+ return nil, nil, err
+ }
+ for _, file := range files {
+ if file.X != git.StatusNotupdated && file.X != git.StatusUntracked && file.X != git.StatusIgnored {
+ staged = append(staged, file)
+ }
+ if file.Y != git.StatusNotupdated {
+ unstaged = append(unstaged, file)
+ }
+ }
+ return staged, unstaged, err
+}
+
+func refreshStatusView(viewName string, g *gocui.Gui, entity *git.RepoEntity) error {
+ switch viewName {
+ case stageViewFeature.Name:
+ if err := refreshStagedView(g, entity); err != nil {
+ return err
+ }
+ case unstageViewFeature.Name:
+ if err := refreshUnstagedView(g, entity); err != nil {
+ return err
+ }
+ case stashViewFeature.Name:
+ if err := refreshStashView(g, entity); err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/pkg/gui/unstagedview.go b/pkg/gui/unstagedview.go
new file mode 100644
index 0000000..5ee25a7
--- /dev/null
+++ b/pkg/gui/unstagedview.go
@@ -0,0 +1,50 @@
+package gui
+
+import (
+ "fmt"
+
+ "github.com/isacikgoz/gitbatch/pkg/git"
+ "github.com/jroimartin/gocui"
+)
+
+// not staged view
+func (gui *Gui) openUnStagedView(g *gocui.Gui) error {
+ maxX, maxY := g.Size()
+
+ v, err := g.SetView(unstageViewFeature.Name, maxX/2+1, 5, maxX-6, int(0.75*float32(maxY))-1)
+ if err != nil {
+ if err != gocui.ErrUnknownView {
+ return err
+ }
+ v.Title = unstageViewFeature.Title
+ v.Wrap = true
+ }
+ entity := gui.getSelectedRepository()
+ if err := refreshUnstagedView(g, entity); err != nil {
+ return err
+ }
+ return nil
+}
+
+// refresh the main view and re-render the repository representations
+func refreshUnstagedView(g *gocui.Gui, entity *git.RepoEntity) error {
+ stageView, err := g.View(unstageViewFeature.Name)
+ if err != nil {
+ return err
+ }
+ stageView.Clear()
+ _, cy := stageView.Cursor()
+ _, oy := stageView.Origin()
+ _, files, err := generateFileLists(entity)
+ if err != nil {
+ return err
+ }
+ for i, file := range files {
+ var prefix string
+ if i == cy+oy {
+ prefix = prefix + selectionIndicator
+ }
+ fmt.Fprintf(stageView, "%s%s%s %s\n", prefix, green.Sprint(string(file.X)), red.Sprint(string(file.Y)), file.Name)
+ }
+ return nil
+} \ No newline at end of file