summaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
authorIbrahim Serdar Acikgoz <serdaracikgoz86@gmail.com>2018-12-08 03:12:52 +0300
committerIbrahim Serdar Acikgoz <serdaracikgoz86@gmail.com>2018-12-08 03:12:52 +0300
commit46e22a8968f72895622c56b1e7fa554bb501371c (patch)
tree61e5a168cadccebfc9e994613be1aeb8a1ac7542 /pkg
parentreduced coupling in view navigation logic and minor refactor (diff)
downloadgitbatch-46e22a8968f72895622c56b1e7fa554bb501371c.tar.gz
status implementation cntd.. added display and navigation
Diffstat (limited to 'pkg')
-rw-r--r--pkg/git/status.go67
-rw-r--r--pkg/gui/keybindings.go58
-rw-r--r--pkg/gui/stagedview.go54
-rw-r--r--pkg/gui/stashview.go47
-rw-r--r--pkg/gui/statusview.go119
-rw-r--r--pkg/gui/unstagedview.go50
6 files changed, 332 insertions, 63 deletions
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/keybindings.go b/pkg/gui/keybindings.go
index 26df3fa..55c86bd 100644
--- a/pkg/gui/keybindings.go
+++ b/pkg/gui/keybindings.go
@@ -77,6 +77,7 @@ func (gui *Gui) generateKeybindings() error {
gui.KeyBindings = append(gui.KeyBindings, binding)
}
}
+ // Statusviews common keybindings
for _, view := range statusViews {
statusKeybindings := []*KeyBinding{
{
@@ -119,6 +120,38 @@ func (gui *Gui) generateKeybindings() error {
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 {
@@ -126,6 +159,7 @@ func (gui *Gui) generateKeybindings() error {
}
}
individualKeybindings := []*KeyBinding{
+ // Main view controls
{
View: mainViewFeature.Name,
Key: gocui.KeyArrowUp,
@@ -215,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,
@@ -460,22 +502,6 @@ func (gui *Gui) generateKeybindings() error {
Display: "c",
Description: "close/cancel",
Vital: true,
- }, {
- View: statusHeaderViewFeature.Name,
- Key: 'c',
- Modifier: gocui.ModNone,
- Handler: gui.closeStatusView,
- Display: "c",
- Description: "close/cancel",
- Vital: true,
- }, {
- View: mainViewFeature.Name,
- Key: 's',
- Modifier: gocui.ModNone,
- Handler: gui.openStatusView,
- Display: "s",
- Description: "Open Status",
- Vital: true,
},
}
for _, binding := range individualKeybindings {
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
index f9ab29d..b1873e8 100644
--- a/pkg/gui/statusview.go
+++ b/pkg/gui/statusview.go
@@ -3,6 +3,7 @@ package gui
import (
"fmt"
+ "github.com/isacikgoz/gitbatch/pkg/git"
"github.com/jroimartin/gocui"
)
@@ -41,72 +42,62 @@ func (gui *Gui) previousStatusView(g *gocui.Gui, v *gocui.View) error {
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
-}
+// 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
-// staged view
-func (gui *Gui) openStageView(g *gocui.Gui) error {
- maxX, maxY := g.Size()
+ // if we are at the end we just return
+ if cy+oy == ly {
+ return nil
+ }
+ if err := v.SetCursor(cx, cy+1); err != nil {
- v, err := g.SetView(stageViewFeature.Name, 6, 5, maxX/2-1, int(0.75*float32(maxY))-1)
- if err != nil {
- if err != gocui.ErrUnknownView {
+ 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
}
- v.Title = stageViewFeature.Title
- v.Wrap = true
- }
- gui.updateKeyBindingsView(g, stageViewFeature.Name)
- if _, err := g.SetCurrentView(stageViewFeature.Name); err != nil {
- return err
}
return nil
}
-// 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 {
+// 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
}
- v.Title = unstageViewFeature.Title
- v.Wrap = true
}
return nil
}
-// 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)
+// 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
}
- v.Title = stashViewFeature.Title
+ fmt.Fprintln(v, entity.AbsPath)
+ // v.Frame = false
v.Wrap = true
- entity := gui.getSelectedRepository()
- stashedItems := entity.Stasheds
- for _, stashedItem := range stashedItems {
- fmt.Fprintln(v, stashedItem)
- }
}
return nil
}
@@ -131,3 +122,37 @@ func (gui *Gui) closeStatusView(g *gocui.Gui, v *gocui.View) error {
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