mirror of
https://github.com/MichaelMure/git-bug.git
synced 2024-12-14 08:45:30 +03:00
318 lines
7.0 KiB
Go
318 lines
7.0 KiB
Go
package termui
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/awesome-gocui/gocui"
|
|
|
|
"github.com/MichaelMure/git-bug/cache"
|
|
"github.com/MichaelMure/git-bug/entities/bug"
|
|
)
|
|
|
|
const labelSelectView = "labelSelectView"
|
|
const labelSelectInstructionsView = "labelSelectInstructionsView"
|
|
|
|
var labelSelectHelp = helpBar{
|
|
{"q", "Save and close"},
|
|
{"↓↑,jk", "Nav"},
|
|
{"a", "Add item"},
|
|
}
|
|
|
|
type labelSelect struct {
|
|
cache *cache.RepoCache
|
|
bug *cache.BugCache
|
|
labels []bug.Label
|
|
labelSelect []bool
|
|
selected int
|
|
scroll int
|
|
childViews []string
|
|
}
|
|
|
|
func newLabelSelect() *labelSelect {
|
|
return &labelSelect{}
|
|
}
|
|
|
|
func (ls *labelSelect) SetBug(cache *cache.RepoCache, bug *cache.BugCache) {
|
|
ls.cache = cache
|
|
ls.bug = bug
|
|
ls.labels = cache.ValidLabels()
|
|
|
|
// Find which labels are currently applied to the bug
|
|
bugLabels := bug.Snapshot().Labels
|
|
labelSelect := make([]bool, len(ls.labels))
|
|
for i, label := range ls.labels {
|
|
for _, bugLabel := range bugLabels {
|
|
if label == bugLabel {
|
|
labelSelect[i] = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
ls.labelSelect = labelSelect
|
|
if len(labelSelect) > 0 {
|
|
ls.selected = 0
|
|
} else {
|
|
ls.selected = -1
|
|
}
|
|
|
|
ls.scroll = 0
|
|
}
|
|
|
|
func (ls *labelSelect) keybindings(g *gocui.Gui) error {
|
|
// Abort
|
|
if err := g.SetKeybinding(labelSelectView, gocui.KeyEsc, gocui.ModNone, ls.abort); err != nil {
|
|
return err
|
|
}
|
|
// Save and return
|
|
if err := g.SetKeybinding(labelSelectView, 'q', gocui.ModNone, ls.saveAndReturn); err != nil {
|
|
return err
|
|
}
|
|
// Up
|
|
if err := g.SetKeybinding(labelSelectView, gocui.KeyArrowUp, gocui.ModNone, ls.selectPrevious); err != nil {
|
|
return err
|
|
}
|
|
if err := g.SetKeybinding(labelSelectView, 'k', gocui.ModNone, ls.selectPrevious); err != nil {
|
|
return err
|
|
}
|
|
// Down
|
|
if err := g.SetKeybinding(labelSelectView, gocui.KeyArrowDown, gocui.ModNone, ls.selectNext); err != nil {
|
|
return err
|
|
}
|
|
if err := g.SetKeybinding(labelSelectView, 'j', gocui.ModNone, ls.selectNext); err != nil {
|
|
return err
|
|
}
|
|
// Select
|
|
if err := g.SetKeybinding(labelSelectView, gocui.KeySpace, gocui.ModNone, ls.selectItem); err != nil {
|
|
return err
|
|
}
|
|
if err := g.SetKeybinding(labelSelectView, 'x', gocui.ModNone, ls.selectItem); err != nil {
|
|
return err
|
|
}
|
|
if err := g.SetKeybinding(labelSelectView, gocui.KeyEnter, gocui.ModNone, ls.selectItem); err != nil {
|
|
return err
|
|
}
|
|
// Add
|
|
if err := g.SetKeybinding(labelSelectView, 'a', gocui.ModNone, ls.addItem); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (ls *labelSelect) layout(g *gocui.Gui) error {
|
|
maxX, maxY := g.Size()
|
|
ls.childViews = nil
|
|
|
|
width := 5
|
|
for _, label := range ls.labels {
|
|
width = maxInt(width, len(label.String()))
|
|
}
|
|
width += 10
|
|
x0 := 1
|
|
y0 := 0 - ls.scroll
|
|
|
|
v, err := g.SetView(labelSelectView, x0, 0, x0+width, maxY-2, 0)
|
|
if err != nil {
|
|
if !errors.Is(err, gocui.ErrUnknownView) {
|
|
return err
|
|
}
|
|
|
|
v.Frame = false
|
|
}
|
|
|
|
for i, label := range ls.labels {
|
|
viewname := fmt.Sprintf("labeledit%d", i)
|
|
v, err := g.SetView(viewname, x0+2, y0, x0+width+2, y0+2, 0)
|
|
if err != nil && !errors.Is(err, gocui.ErrUnknownView) {
|
|
return err
|
|
}
|
|
ls.childViews = append(ls.childViews, viewname)
|
|
v.Frame = i == ls.selected
|
|
v.Clear()
|
|
selectBox := " [ ] "
|
|
if ls.labelSelect[i] {
|
|
selectBox = " [x] "
|
|
}
|
|
|
|
lc := label.Color()
|
|
lc256 := lc.Term256()
|
|
labelStr := lc256.Escape() + "◼ " + lc256.Unescape() + label.String()
|
|
_, _ = fmt.Fprint(v, selectBox, labelStr)
|
|
|
|
y0 += 2
|
|
}
|
|
|
|
v, err = g.SetView(labelSelectInstructionsView, -1, maxY-2, maxX, maxY, 0)
|
|
ls.childViews = append(ls.childViews, labelSelectInstructionsView)
|
|
if err != nil {
|
|
if !errors.Is(err, gocui.ErrUnknownView) {
|
|
return err
|
|
}
|
|
v.Frame = false
|
|
v.FgColor = gocui.ColorWhite
|
|
}
|
|
v.Clear()
|
|
_, _ = fmt.Fprint(v, labelSelectHelp.Render(maxX))
|
|
if _, err = g.SetViewOnTop(labelSelectInstructionsView); err != nil {
|
|
return err
|
|
}
|
|
if _, err := g.SetCurrentView(labelSelectView); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (ls *labelSelect) disable(g *gocui.Gui) error {
|
|
for _, view := range ls.childViews {
|
|
if err := g.DeleteView(view); err != nil && !errors.Is(err, gocui.ErrUnknownView) {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (ls *labelSelect) focusView(g *gocui.Gui) error {
|
|
if ls.selected < 0 {
|
|
return nil
|
|
}
|
|
|
|
_, lsy0, _, lsy1, err := g.ViewPosition(labelSelectView)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, vy0, _, vy1, err := g.ViewPosition(fmt.Sprintf("labeledit%d", ls.selected))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Below bottom of frame
|
|
if vy1 > lsy1 {
|
|
ls.scroll += vy1 - lsy1
|
|
return nil
|
|
}
|
|
|
|
// Above top of frame
|
|
if vy0 < lsy0 {
|
|
ls.scroll -= lsy0 - vy0
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (ls *labelSelect) selectPrevious(g *gocui.Gui, v *gocui.View) error {
|
|
if ls.selected < 0 {
|
|
return nil
|
|
}
|
|
|
|
ls.selected = maxInt(0, ls.selected-1)
|
|
return ls.focusView(g)
|
|
}
|
|
|
|
func (ls *labelSelect) selectNext(g *gocui.Gui, v *gocui.View) error {
|
|
if ls.selected < 0 {
|
|
return nil
|
|
}
|
|
|
|
ls.selected = minInt(len(ls.labels)-1, ls.selected+1)
|
|
return ls.focusView(g)
|
|
}
|
|
|
|
func (ls *labelSelect) selectItem(g *gocui.Gui, v *gocui.View) error {
|
|
if ls.selected < 0 {
|
|
return nil
|
|
}
|
|
|
|
ls.labelSelect[ls.selected] = !ls.labelSelect[ls.selected]
|
|
return nil
|
|
}
|
|
|
|
func (ls *labelSelect) addItem(g *gocui.Gui, v *gocui.View) error {
|
|
c := ui.inputPopup.Activate("Add a new label")
|
|
|
|
go func() {
|
|
input := <-c
|
|
|
|
// Standardize label format
|
|
input = strings.TrimSuffix(input, "\n")
|
|
input = strings.Replace(input, " ", "-", -1)
|
|
|
|
// Check if label already exists
|
|
for i, label := range ls.labels {
|
|
if input == label.String() {
|
|
ls.labelSelect[i] = true
|
|
ls.selected = i
|
|
|
|
g.Update(func(gui *gocui.Gui) error {
|
|
return ls.focusView(g)
|
|
})
|
|
|
|
return
|
|
}
|
|
}
|
|
|
|
// Add new label, make it selected, and focus
|
|
ls.labels = append(ls.labels, bug.Label(input))
|
|
ls.labelSelect = append(ls.labelSelect, true)
|
|
ls.selected = len(ls.labels) - 1
|
|
|
|
g.Update(func(g *gocui.Gui) error {
|
|
return nil
|
|
})
|
|
}()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (ls *labelSelect) abort(g *gocui.Gui, v *gocui.View) error {
|
|
return ui.activateWindow(ui.showBug)
|
|
}
|
|
|
|
func (ls *labelSelect) saveAndReturn(g *gocui.Gui, v *gocui.View) error {
|
|
bugLabels := ls.bug.Snapshot().Labels
|
|
var selectedLabels []bug.Label
|
|
for i, label := range ls.labels {
|
|
if ls.labelSelect[i] {
|
|
selectedLabels = append(selectedLabels, label)
|
|
}
|
|
}
|
|
|
|
// Find the new and removed labels. This could be implemented more efficiently...
|
|
var newLabels []string
|
|
var rmLabels []string
|
|
|
|
for _, selectedLabel := range selectedLabels {
|
|
found := false
|
|
for _, bugLabel := range bugLabels {
|
|
if selectedLabel == bugLabel {
|
|
found = true
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
newLabels = append(newLabels, string(selectedLabel))
|
|
}
|
|
}
|
|
|
|
for _, bugLabel := range bugLabels {
|
|
found := false
|
|
for _, selectedLabel := range selectedLabels {
|
|
if bugLabel == selectedLabel {
|
|
found = true
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
rmLabels = append(rmLabels, string(bugLabel))
|
|
}
|
|
}
|
|
|
|
if _, _, err := ls.bug.ChangeLabels(newLabels, rmLabels); err != nil {
|
|
ui.msgPopup.Activate(msgPopupErrorTitle, err.Error())
|
|
}
|
|
|
|
return ui.activateWindow(ui.showBug)
|
|
}
|