diff --git a/internal/config/style.go b/internal/config/style.go index cb276136..8f5175b1 100644 --- a/internal/config/style.go +++ b/internal/config/style.go @@ -99,6 +99,7 @@ type ( FgColor string `yaml:"fgColor"` BgColor string `yaml:"bgColor"` CursorColor string `yaml:"cursorColor"` + MarkColor string `yaml:"markColor"` Header TableHeader `yaml:"header"` } @@ -215,6 +216,7 @@ func newTable() Table { FgColor: "aqua", BgColor: "black", CursorColor: "aqua", + MarkColor: "darkgoldenrod", Header: newTableHeader(), } } diff --git a/internal/ui/menu.go b/internal/ui/menu.go index b09a3c4a..a45eafcc 100644 --- a/internal/ui/menu.go +++ b/internal/ui/menu.go @@ -204,6 +204,7 @@ const ( KeyHelp = 63 KeySlash = 47 KeyColon = 58 + KeySpace = 32 ) // Define Shift Keys @@ -253,6 +254,7 @@ var NumKeys = map[int]int32{ func initKeys() { tcell.KeyNames[tcell.Key(KeyHelp)] = "?" tcell.KeyNames[tcell.Key(KeySlash)] = "/" + tcell.KeyNames[tcell.Key(KeySpace)] = "space" initNumbKeys() initStdKeys() diff --git a/internal/ui/table.go b/internal/ui/table.go index 8ad49149..b1bab827 100644 --- a/internal/ui/table.go +++ b/internal/ui/table.go @@ -42,6 +42,7 @@ type Table struct { selectedRow int selectedFn func(string) string selListeners []SelectedRowFunc + marks map[string]bool } // NewTable returns a new table view. @@ -53,6 +54,7 @@ func NewTable(title string, styles *config.Styles) *Table { cmdBuff: NewCmdBuff('/', FilterBuff), baseTitle: title, sortCol: SortColumn{0, 0, true}, + marks: make(map[string]bool), } v.SetFixed(1, 0) @@ -169,6 +171,20 @@ func (v *Table) GetSelectedItem() string { return v.selectedItem } +// GetSelectedItems return currently marked or selected items names. +func (v *Table) GetSelectedItems() []string { + if len(v.marks) > 0 { + var items []string + for item, marked := range v.marks { + if marked { + items = append(items, item) + } + } + return items + } + return []string{v.GetSelectedItem()} +} + func (v *Table) keyboard(evt *tcell.EventKey) *tcell.EventKey { key := evt.Key() if key == tcell.KeyRune { @@ -323,6 +339,7 @@ func (v *Table) buildRow(row int, data resource.TableData, sk string, pads MaxyP if v.colorerFn != nil { f = v.colorerFn } + m := v.isMarked(sk) for col, field := range data.Rows[sk].Fields { header := data.Header[col] field, align := v.formatCell(data.NumCols[header], header, field+Deltas(data.Rows[sk].Deltas[col], field), pads[col]) @@ -331,6 +348,9 @@ func (v *Table) buildRow(row int, data resource.TableData, sk string, pads MaxyP c.SetExpansion(1) c.SetAlign(align) c.SetTextColor(f(data.Namespace, data.Rows[sk])) + if m { + c.SetBackgroundColor(config.AsColor(v.styles.Table().MarkColor)) + } } v.SetCell(row, col, c) } @@ -527,3 +547,12 @@ func (v *Table) SortInvertCmd(evt *tcell.EventKey) *tcell.EventKey { return nil } + +// ToggleMark toggles marked row +func (v *Table) ToggleMark() { + v.marks[v.GetSelectedItem()] = !v.marks[v.GetSelectedItem()] +} + +func (v *Table) isMarked(item string) bool { + return v.marks[item] +} diff --git a/internal/views/pod.go b/internal/views/pod.go index af2431ca..abe9455c 100644 --- a/internal/views/pod.go +++ b/internal/views/pod.go @@ -115,15 +115,17 @@ func (v *podView) killCmd(evt *tcell.EventKey) *tcell.EventKey { return evt } - sel := v.masterPage().GetSelectedItem() + sel := v.masterPage().GetSelectedItems() v.masterPage().ShowDeleted() - v.app.Flash().Infof("Delete resource %s %s", v.list.GetName(), sel) - if err := v.list.Resource().Delete(sel, true, false); err != nil { - v.app.Flash().Errf("Delete failed with %s", err) - } else { - deletePortForward(v.app.forwarders, sel) - v.refresh() + for _, res := range sel { + v.app.Flash().Infof("Delete resource %s %s", v.list.GetName(), res) + if err := v.list.Resource().Delete(res, true, false); err != nil { + v.app.Flash().Errf("Delete failed with %s", err) + } else { + deletePortForward(v.app.forwarders, res) + } } + v.refresh() return nil } diff --git a/internal/views/resource.go b/internal/views/resource.go index 5aa2a45c..d8d33cc5 100644 --- a/internal/views/resource.go +++ b/internal/views/resource.go @@ -192,23 +192,45 @@ func (v *resourceView) deleteCmd(evt *tcell.EventKey) *tcell.EventKey { return evt } - sel := v.masterPage().GetSelectedItem() - msg := fmt.Sprintf("Delete %s %s?", v.list.GetName(), sel) + sel := v.masterPage().GetSelectedItems() + var msg string + if len(sel) > 1 { + msg = fmt.Sprintf("Delete %d selected %s?", len(sel), v.list.GetName()) + } else { + msg = fmt.Sprintf("Delete %s %s?", v.list.GetName(), sel[0]) + } dialog.ShowDelete(v.Pages, msg, func(cascade, force bool) { v.masterPage().ShowDeleted() - v.app.Flash().Infof("Delete resource %s %s", v.list.GetName(), sel) - if err := v.list.Resource().Delete(sel, cascade, force); err != nil { - v.app.Flash().Errf("Delete failed with %s", err) + if len(sel) > 1 { + v.app.Flash().Infof("Delete %d selected %s", len(sel), v.list.GetName()) } else { - deletePortForward(v.app.forwarders, sel) - v.refresh() + v.app.Flash().Infof("Delete resource %s %s", v.list.GetName(), sel[0]) } + for _, res := range sel { + if err := v.list.Resource().Delete(res, cascade, force); err != nil { + v.app.Flash().Errf("Delete failed with %s", err) + } else { + deletePortForward(v.app.forwarders, res) + } + } + v.refresh() }, func() { v.switchPage("master") }) return nil } +func (v *resourceView) markCmd(evt *tcell.EventKey) *tcell.EventKey { + if !v.masterPage().RowSelected() { + return evt + } + + v.masterPage().ToggleMark() + v.refresh() + v.app.Draw() + return nil +} + func deletePortForward(ff map[string]forwarder, sel string) { for k, v := range ff { tokens := strings.Split(k, ":") @@ -375,6 +397,7 @@ func (v *resourceView) refreshActions() { tcell.KeyEnter: ui.NewKeyAction("Enter", v.enterCmd, false), tcell.KeyCtrlR: ui.NewKeyAction("Refresh", v.refreshCmd, false), } + aa[ui.KeySpace] = ui.NewKeyAction("Mark", v.markCmd, true) v.namespaceActions(aa) v.defaultActions(aa) diff --git a/skins/black_and_wtf.yml b/skins/black_and_wtf.yml index 4fe8393b..e8d91b1a 100644 --- a/skins/black_and_wtf.yml +++ b/skins/black_and_wtf.yml @@ -35,6 +35,7 @@ k9s: fgColor: white bgColor: black cursorColor: white + markColor: darkgoldenrod header: fgColor: darkgray bgColor: black diff --git a/skins/in_the_navy.yml b/skins/in_the_navy.yml index 0a7762d6..9dc0f062 100644 --- a/skins/in_the_navy.yml +++ b/skins/in_the_navy.yml @@ -37,6 +37,7 @@ k9s: fgColor: blue bgColor: darkblue cursorColor: aqua + markColor: mediumslateblue header: fgColor: white bgColor: darkblue diff --git a/skins/stock.yml b/skins/stock.yml index 6f42d942..be02aa01 100644 --- a/skins/stock.yml +++ b/skins/stock.yml @@ -35,6 +35,7 @@ k9s: fgColor: blue bgColor: black cursorColor: aqua + markColor: darkgoldenrod header: fgColor: white bgColor: black