diff --git a/layout.go b/layout.go index b6f81a4..8dafb54 100644 --- a/layout.go +++ b/layout.go @@ -8,27 +8,43 @@ import ( "github.com/mkchoi212/fac/conflict" ) +// Following constants define the string literal names of 5 views +// that are instantiated via gocui const ( Current = "current" Foreign = "foreign" Panel = "panel" Prompt = "prompt" - Input = "input prompt" + Input = "input" +) - Up = 3 - Down = 4 +// `Up` and `Down` represent scrolling directions +// `Horizontal` and `Vertical` represent current code view orientation +// Notice how both pairs of directionality are `not`s of each other +const ( + Up = 1 + Down = ^1 - Horizontal = 5 - Vertical = -6 + Horizontal = 2 + Vertical = ^2 ) -var ( - ViewOrientation = Vertical - inputHeight = 2 +// Following constants define input panel's dimensions +const ( + inputHeight = 2 + inputCursorPos = 17 + promptWidth = 21 ) +func printLines(v *gocui.View, lines []string) { + for _, line := range lines { + fmt.Fprint(v, line) + } +} + +// layout is used as fac's main gocui.Gui manager func layout(g *gocui.Gui) (err error) { - if err = makePanels(g); err != nil { + if err = makeCodePanels(g); err != nil { return } @@ -43,7 +59,10 @@ func layout(g *gocui.Gui) (err error) { return } -func makePanels(g *gocui.Gui) error { +// makeCodePanels draws the two panels representing "local" and "incoming" lines of code +// `viewOrientation` is taken into consideration as the panels can either be +// `Vertical` or `Horizontal` +func makeCodePanels(g *gocui.Gui) error { maxX, maxY := g.Size() viewHeight := maxY - inputHeight branchViewWidth := (maxX / 5) * 2 @@ -52,7 +71,7 @@ func makePanels(g *gocui.Gui) error { var x0, x1, y0, y1 int var x2, x3, y2, y3 int - if ViewOrientation == Horizontal { + if viewOrientation == Horizontal { x0, x1 = 0, branchViewWidth y0, y1 = 0, viewHeight x2, x3 = branchViewWidth, branchViewWidth*2 @@ -87,6 +106,8 @@ func makePanels(g *gocui.Gui) error { return nil } +// makeOverviewPanel draws the panel on the right-side of the CUI +// listing all the conflicts that need to be resolved func makeOverviewPanel(g *gocui.Gui) error { maxX, maxY := g.Size() viewHeight := maxY - inputHeight @@ -101,13 +122,15 @@ func makeOverviewPanel(g *gocui.Gui) error { return nil } +// makePrompt draws two panels on the bottom of the CUI +// A "prompt view" which prompts the user for available keybindings and +// a "user input view" which is an area where the user can type in queries func makePrompt(g *gocui.Gui) error { maxX, maxY := g.Size() - inputHeight := 2 viewHeight := maxY - inputHeight // Prompt view - if v, err := g.SetView(Prompt, 0, viewHeight, 21, viewHeight+inputHeight); err != nil { + if v, err := g.SetView(Prompt, 0, viewHeight, promptWidth, viewHeight+inputHeight); err != nil { if err != gocui.ErrUnknownView { return err } @@ -116,7 +139,7 @@ func makePrompt(g *gocui.Gui) error { } // User input view - if v, err := g.SetView(Input, 17, viewHeight, maxX, viewHeight+inputHeight); err != nil { + if v, err := g.SetView(Input, inputCursorPos, viewHeight, maxX, viewHeight+inputHeight); err != nil { if err != gocui.ErrUnknownView { return err } @@ -130,7 +153,9 @@ func makePrompt(g *gocui.Gui) error { return nil } -func Select(c *conflict.Conflict, g *gocui.Gui, showHelp bool) error { +// Select selects conflict `c` as the current conflict displayed on the screen +// When selecting a conflict, it updates the side panel, and the code view +func Select(g *gocui.Gui, c *conflict.Conflict, showHelp bool) error { // Update side panel g.Update(func(g *gocui.Gui) error { v, err := g.View(Panel) @@ -155,7 +180,7 @@ func Select(c *conflict.Conflict, g *gocui.Gui, showHelp bool) error { } if showHelp { - printHelp(v, &binding) + PrintHelp(v, &binding) } return nil }) @@ -197,20 +222,23 @@ func Select(c *conflict.Conflict, g *gocui.Gui, showHelp bool) error { return nil } -func Resolve(c *conflict.Conflict, g *gocui.Gui, v *gocui.View, version int) error { +// Resolve resolves the provided conflict and moves to the next conflict +// in the queue +func Resolve(g *gocui.Gui, v *gocui.View, c *conflict.Conflict, version int) error { g.Update(func(g *gocui.Gui) error { c.Choice = version - MoveToItem(Down, g, v) + Move(g, v, Down) return nil }) return nil } -func MoveToItem(dir int, g *gocui.Gui, v *gocui.View) error { +// Move goes to the next conflict in the list in the provided `direction` +func Move(g *gocui.Gui, v *gocui.View, direction int) error { originalCur := cur for { - if dir == Up { + if direction == Up { cur-- } else { cur++ @@ -227,14 +255,16 @@ func MoveToItem(dir int, g *gocui.Gui, v *gocui.View) error { } } + // Quit application if all items are resolved if originalCur == cur && conflicts[cur].Choice != 0 { globalQuit(g) } - Select(conflicts[cur], g, false) + Select(g, conflicts[cur], false) return nil } +// Scroll scrolls the two code view panels in `direction` by one line func Scroll(g *gocui.Gui, c *conflict.Conflict, direction int) { if direction == Up { c.TopPeek-- @@ -244,5 +274,5 @@ func Scroll(g *gocui.Gui, c *conflict.Conflict, direction int) { return } - Select(c, g, false) + Select(g, c, false) } diff --git a/main.go b/main.go index 202c898..90420e7 100644 --- a/main.go +++ b/main.go @@ -13,28 +13,28 @@ import ( ) var ( - conflicts = []*conflict.Conflict{} - cur = 0 - consecutiveError = 0 - binding = key.Binding{} + viewOrientation = Vertical + conflicts = []*conflict.Conflict{} + binding = key.Binding{} + cur = 0 + consecutiveErrCnt = 0 ) -func printLines(v *gocui.View, lines []string) { - for _, line := range lines { - fmt.Fprint(v, line) - } -} - +// globalQuit is invoked when the user quits the contact and or +// when all conflicts have been resolved func globalQuit(g *gocui.Gui) { g.Update(func(g *gocui.Gui) error { return gocui.ErrQuit }) } +// quit is invoked when the user presses "Ctrl+C" func quit(g *gocui.Gui, v *gocui.View) error { return gocui.ErrQuit } +// parseInput is invoked when the user presses "Enter" +// It `evaluate`s the user's query and reflects the state on the UI func parseInput(g *gocui.Gui, v *gocui.View) error { in := strings.TrimSuffix(v.Buffer(), "\n") v.Clear() @@ -42,78 +42,84 @@ func parseInput(g *gocui.Gui, v *gocui.View) error { if err := Evaluate(g, v, conflicts[cur], in); err != nil { if err == ErrUnknownCmd { - consecutiveError++ - if consecutiveError > 3 { - Select(conflicts[cur], g, true) + consecutiveErrCnt++ + if consecutiveErrCnt > 3 { + Select(g, conflicts[cur], true) } } else { return err } } else { - consecutiveError = 0 + consecutiveErrCnt = 0 } PrintPrompt(g) return nil } -// Start initializes, configures, and starts a fresh instance of gocui -func Start() (err error) { - g, err := gocui.NewGui(gocui.OutputNormal) +// findConflicts looks at the current directory and returns an +// array of `File`s that contain merge conflicts +// It returns an error if it fails to parse the conflicts +func findConflicts() (files []conflict.File, err error) { + cwd, err := os.Getwd() if err != nil { return } - defer g.Close() - g.SetManagerFunc(layout) - g.Cursor = true - - if err = g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil { - return - } - if err = g.SetKeybinding("", gocui.KeyEnter, gocui.ModNone, parseInput); err != nil { + if files, err = conflict.Find(cwd); err != nil { return } - Select(conflicts[cur], g, false) - - if err = g.MainLoop(); err != nil { - return + for i := range files { + file := &files[i] + for j := range file.Conflicts { + conflicts = append(conflicts, &file.Conflicts[j]) + } } return } -func findConflicts() (files []conflict.File, err error) { - cwd, err := os.Getwd() +// runUI initializes, configures, and starts a fresh instance of gocui +func runUI() (err error) { + g, err := gocui.NewGui(gocui.OutputNormal) if err != nil { return } - if files, err = conflict.Find(cwd); err != nil { + defer g.Close() + g.SetManagerFunc(layout) + g.Cursor = true + + if err = g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil { + return + } + if err = g.SetKeybinding("", gocui.KeyEnter, gocui.ModNone, parseInput); err != nil { return } - for i := range files { - file := &files[i] - for j := range file.Conflicts { - conflicts = append(conflicts, &file.Conflicts[j]) - } + Select(g, conflicts[cur], false) + + if err = g.MainLoop(); err != nil { + return } return } -func runUI() error { +// mainLoop manages how the main instances of gocui are created and destroyed +func mainLoop() error { for { - if err := Start(); err != nil { + if err := runUI(); err != nil { + // Instantiates a fresh instance of gocui + // when user opens an editor because screen is dirty if err == ErrOpenEditor { newLines, err := editor.Open(conflicts[cur]) if err != nil { return err } if err = conflicts[cur].Update(newLines); err != nil { - consecutiveError++ + consecutiveErrCnt++ } } else if err == gocui.ErrQuit { break @@ -137,7 +143,6 @@ func main() { die(err) } - // Find and parse conflicts files, err := findConflicts() if err != nil { die(err) @@ -148,7 +153,7 @@ func main() { os.Exit(0) } - if err = runUI(); err != nil { + if err = mainLoop(); err != nil { die(err) } @@ -158,5 +163,5 @@ func main() { } } - printSummary(conflicts) + PrintSummary(conflicts) } diff --git a/prompt.go b/prompt.go index b56892d..10aca71 100644 --- a/prompt.go +++ b/prompt.go @@ -32,7 +32,7 @@ func PrintPrompt(g *gocui.Gui) { v.Clear() v.MoveCursor(0, 0, true) - if consecutiveError == 0 { + if consecutiveErrCnt == 0 { fmt.Fprintf(v, color.Green(color.Regular, promptString)) } else { fmt.Fprintf(v, color.Red(color.Regular, promptString)) @@ -53,25 +53,25 @@ func Evaluate(g *gocui.Gui, v *gocui.View, conf *conflict.Conflict, input string Scroll(g, conflicts[cur], Down) case binding[key.ShowLinesUp]: conflicts[cur].TopPeek++ - Select(conflicts[cur], g, false) + Select(g, conflicts[cur], false) case binding[key.ShowLinesDown]: conflicts[cur].BottomPeek++ - Select(conflicts[cur], g, false) + Select(g, conflicts[cur], false) case binding[key.SelectLocal]: - Resolve(conflicts[cur], g, v, conflict.Local) + Resolve(g, v, conflicts[cur], conflict.Local) case binding[key.SelectIncoming]: - Resolve(conflicts[cur], g, v, conflict.Incoming) + Resolve(g, v, conflicts[cur], conflict.Incoming) case binding[key.NextConflict]: - MoveToItem(Down, g, v) + Move(g, v, Down) case binding[key.PreviousConflict]: - MoveToItem(Up, g, v) + Move(g, v, Up) case binding[key.ToggleViewOrientation]: - ViewOrientation = ^ViewOrientation + viewOrientation = ^viewOrientation layout(g) case binding[key.EditCode]: return ErrOpenEditor case binding[key.ShowHelp], "?": - Select(conflicts[cur], g, true) + Select(g, conflicts[cur], true) case binding[key.QuitApplication]: globalQuit(g) default: diff --git a/summary.go b/summary.go index 1e321cb..7be121d 100644 --- a/summary.go +++ b/summary.go @@ -10,11 +10,14 @@ import ( "github.com/mkchoi212/fac/key" ) -func printHelp(v io.Writer, binding *key.Binding) { +// PrintHelp prints the current key binding rules in the side panel +func PrintHelp(v io.Writer, binding *key.Binding) { fmt.Fprintf(v, color.Blue(color.Regular, binding.Help())) } -func printSummary(conflicts []*conflict.Conflict) { +// PrintSummary prints the summary of the fac session after the user +// either quits the program or has resolved all conflicts +func PrintSummary(conflicts []*conflict.Conflict) { resolvedCnt := 0 var line string