checkpoint

mine
derailed 2019-11-15 19:20:10 -07:00
parent 5b0fd805ce
commit ef0aab3f4c
16 changed files with 138 additions and 192 deletions

View File

@ -28,6 +28,20 @@ func NewKeyAction(d string, a ActionHandler, display bool) KeyAction {
return KeyAction{Description: d, Action: a, Visible: display}
}
// Add sets up keyboard action listener.
func (a KeyActions) AddActions(aa KeyActions) {
for k, v := range aa {
a[k] = v
}
}
// Remove delete a keyed action.
func (a KeyActions) RmActions(kk ...tcell.Key) {
for _, k := range kk {
delete(a, k)
}
}
// Hints returns a collection of hints.
func (a KeyActions) Hints() model.MenuHints {
kk := make([]int, 0, len(a))

View File

@ -3,19 +3,12 @@ package ui
import (
"context"
"errors"
"fmt"
"regexp"
"strings"
"time"
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/model"
"github.com/derailed/k9s/internal/resource"
"github.com/derailed/tview"
"github.com/gdamore/tcell"
"github.com/rs/zerolog/log"
"github.com/sahilm/fuzzy"
"k8s.io/apimachinery/pkg/util/duration"
)
type (
@ -29,10 +22,10 @@ type (
// Table represents tabular data.
type Table struct {
*SelectTable
KeyActions
baseTitle string
BaseTitle string
Data resource.TableData
actions KeyActions
cmdBuff *CmdBuff
styles *config.Styles
colorerFn ColorerFunc
@ -47,10 +40,10 @@ func NewTable(title string) *Table {
Table: tview.NewTable(),
marks: make(map[string]bool),
},
actions: make(KeyActions),
cmdBuff: NewCmdBuff('/', FilterBuff),
baseTitle: title,
sortCol: SortColumn{0, 0, true},
KeyActions: make(KeyActions),
cmdBuff: NewCmdBuff('/', FilterBuff),
BaseTitle: title,
sortCol: SortColumn{0, 0, true},
}
}
@ -94,7 +87,7 @@ func (t *Table) keyboard(evt *tcell.EventKey) *tcell.EventKey {
key = asKey(evt)
}
if a, ok := t.actions[key]; ok {
if a, ok := t.KeyActions[key]; ok {
return a.Action(evt)
}
@ -106,16 +99,6 @@ func (t *Table) GetFilteredData() resource.TableData {
return t.filtered()
}
// SetBaseTitle set the table title.
func (t *Table) SetBaseTitle(s string) {
t.baseTitle = s
}
// GetBaseTitle fetch the current title.
func (t *Table) GetBaseTitle() string {
return t.baseTitle
}
// SetColorerFn set the row colorer.
func (t *Table) SetColorerFn(f ColorerFunc) {
t.colorerFn = f
@ -141,9 +124,9 @@ func (t *Table) Update(data resource.TableData) {
func (t *Table) doUpdate(data resource.TableData) {
t.ActiveNS = data.Namespace
if t.ActiveNS == resource.AllNamespaces && t.ActiveNS != "*" {
t.actions[KeyShiftP] = NewKeyAction("Sort Namespace", t.SortColCmd(-2), false)
t.KeyActions[KeyShiftP] = NewKeyAction("Sort Namespace", t.SortColCmd(-2), false)
} else {
delete(t.actions, KeyShiftP)
t.KeyActions.RmActions(KeyShiftP)
}
t.Clear()
@ -229,7 +212,7 @@ func (t *Table) buildRow(row int, data resource.TableData, sk string, pads MaxyP
m := t.IsMarked(sk)
for col, field := range data.Rows[sk].Fields {
header := data.Header[col]
cell, align := t.formatCell(data.NumCols[header], header, field+Deltas(data.Rows[sk].Deltas[col], field), pads[col])
cell, align := formatCell(data.NumCols[header], header, field+Deltas(data.Rows[sk].Deltas[col], field), pads[col])
c := tview.NewTableCell(cell)
{
c.SetExpansion(1)
@ -243,26 +226,6 @@ func (t *Table) buildRow(row int, data resource.TableData, sk string, pads MaxyP
}
}
func (t *Table) formatCell(numerical bool, header, field string, padding int) (string, int) {
if header == "AGE" {
dur, err := time.ParseDuration(field)
if err == nil {
field = duration.HumanDuration(dur)
}
}
if numerical || cpuRX.MatchString(header) || memRX.MatchString(header) {
return field, tview.AlignRight
}
align := tview.AlignLeft
if IsASCII(field) {
return Pad(field, padding), align
}
return field, align
}
func (t *Table) ClearMarks() {
t.marks = map[string]bool{}
t.Refresh()
@ -299,58 +262,17 @@ func (t *Table) filtered() resource.TableData {
q := t.cmdBuff.String()
if isFuzzySelector(q) {
return t.fuzzyFilter(q[2:])
return fuzzyFilter(q[2:], t.NameColIndex(), t.Data)
}
return t.rxFilter()
}
func (t *Table) rxFilter() resource.TableData {
rx, err := regexp.Compile(`(?i)` + t.cmdBuff.String())
data, err := rxFilter(t.cmdBuff.String(), t.Data)
if err != nil {
log.Error().Err(errors.New("Invalid filter expression")).Msg("Regexp")
t.cmdBuff.Clear()
return t.Data
}
filtered := resource.TableData{
Header: t.Data.Header,
Rows: resource.RowEvents{},
Namespace: t.Data.Namespace,
}
for k, row := range t.Data.Rows {
f := strings.Join(row.Fields, " ")
if rx.MatchString(f) {
filtered.Rows[k] = row
}
}
return filtered
}
func (t *Table) fuzzyFilter(q string) resource.TableData {
var ss, kk []string
for k, row := range t.Data.Rows {
ss = append(ss, row.Fields[t.NameColIndex()])
kk = append(kk, k)
}
filtered := resource.TableData{
Header: t.Data.Header,
Rows: resource.RowEvents{},
Namespace: t.Data.Namespace,
}
mm := fuzzy.Find(q, ss)
for _, m := range mm {
filtered.Rows[kk[m.Index]] = t.Data.Rows[kk[m.Index]]
}
return filtered
}
// KeyBindings returns the bounded keys.
func (t *Table) KeyBindings() KeyActions {
return t.actions
return data
}
// SearchBuff returns the associated command buffer.
@ -367,56 +289,9 @@ func (t *Table) ShowDeleted() {
}
}
// SetActions sets up keyboard action listener.
func (t *Table) AddActions(aa KeyActions) {
for k, a := range aa {
t.actions[k] = a
}
}
// RmAction delete a keyed action.
func (t *Table) RmAction(kk ...tcell.Key) {
for _, k := range kk {
delete(t.actions, k)
}
}
// Hints options
func (t *Table) Hints() model.MenuHints {
if t.actions != nil {
return t.actions.Hints()
}
return nil
}
// UpdateTitle refreshes the table title.
func (t *Table) UpdateTitle() {
var title string
rc := t.GetRowCount()
if rc > 0 {
rc--
}
switch t.ActiveNS {
case resource.NotNamespaced, "*":
title = skinTitle(fmt.Sprintf(titleFmt, t.baseTitle, rc), t.styles.Frame())
default:
ns := t.ActiveNS
if ns == resource.AllNamespaces {
ns = resource.AllNamespace
}
title = skinTitle(fmt.Sprintf(nsTitleFmt, t.baseTitle, ns, rc), t.styles.Frame())
}
if !t.cmdBuff.Empty() {
cmd := t.cmdBuff.String()
if IsLabelSelector(cmd) {
cmd = TrimLabelSelector(cmd)
}
title += skinTitle(fmt.Sprintf(SearchFmt, cmd), t.styles.Frame())
}
t.SetTitle(title)
t.SetTitle(styleTitle(t.GetRowCount(), t.ActiveNS, t.BaseTitle, t.cmdBuff.String(), t.styles))
}
// SortInvertCmd reverses sorting order.

View File

@ -6,10 +6,14 @@ import (
"regexp"
"sort"
"strings"
"time"
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/resource"
"github.com/derailed/tview"
"github.com/rs/zerolog/log"
"github.com/sahilm/fuzzy"
"k8s.io/apimachinery/pkg/util/duration"
)
const (
@ -77,7 +81,7 @@ func TrimLabelSelector(s string) string {
return strings.TrimSpace(s[2:])
}
func skinTitle(fmat string, style config.Frame) string {
func SkinTitle(fmat string, style config.Frame) string {
fmat = strings.Replace(fmat, "[fg:bg", "["+style.Title.FgColor+":"+style.Title.BgColor, -1)
fmat = strings.Replace(fmat, "[hilite", "["+style.Title.HighlightColor, 1)
fmat = strings.Replace(fmat, "[key", "["+style.Menu.NumKeyColor, 1)
@ -137,3 +141,91 @@ func sortIndicator(col SortColumn, style config.Table, index int, name string) s
}
return fmt.Sprintf("%s[%s::]%s[::]", name, style.Header.SorterColor, order)
}
func formatCell(numerical bool, header, field string, padding int) (string, int) {
if header == "AGE" {
dur, err := time.ParseDuration(field)
if err == nil {
field = duration.HumanDuration(dur)
}
}
if numerical || cpuRX.MatchString(header) || memRX.MatchString(header) {
return field, tview.AlignRight
}
align := tview.AlignLeft
if IsASCII(field) {
return Pad(field, padding), align
}
return field, align
}
func rxFilter(q string, data resource.TableData) (resource.TableData, error) {
rx, err := regexp.Compile(`(?i)` + q)
if err != nil {
return data, err
}
filtered := resource.TableData{
Header: data.Header,
Rows: resource.RowEvents{},
Namespace: data.Namespace,
}
for k, row := range data.Rows {
f := strings.Join(row.Fields, " ")
if rx.MatchString(f) {
filtered.Rows[k] = row
}
}
return filtered, nil
}
func fuzzyFilter(q string, index int, data resource.TableData) resource.TableData {
var ss, kk []string
for k, row := range data.Rows {
ss = append(ss, row.Fields[index])
kk = append(kk, k)
}
filtered := resource.TableData{
Header: data.Header,
Rows: resource.RowEvents{},
Namespace: data.Namespace,
}
mm := fuzzy.Find(q, ss)
for _, m := range mm {
filtered.Rows[kk[m.Index]] = data.Rows[kk[m.Index]]
}
return filtered
}
// UpdateTitle refreshes the table title.
func styleTitle(rc int, ns, base, buff string, styles *config.Styles) string {
var title string
if rc > 0 {
rc--
}
switch ns {
case resource.NotNamespaced, "*":
title = SkinTitle(fmt.Sprintf(titleFmt, base, rc), styles.Frame())
default:
if ns == resource.AllNamespaces {
ns = resource.AllNamespace
}
title = SkinTitle(fmt.Sprintf(nsTitleFmt, base, ns, rc), styles.Frame())
}
if buff != "" {
if IsLabelSelector(buff) {
buff = TrimLabelSelector(buff)
}
title += SkinTitle(fmt.Sprintf(SearchFmt, buff), styles.Frame())
}
return title
}

View File

@ -17,11 +17,7 @@ func TestTableNew(t *testing.T) {
ctx := context.WithValue(context.Background(), ui.KeyStyles, s)
v.Init(ctx)
assert.Equal(t, "fred", v.GetBaseTitle())
v.SetBaseTitle("bozo")
assert.Equal(t, "bozo", v.GetBaseTitle())
assert.Equal(t, "fred", v.BaseTitle)
}
func TestTableUpdate(t *testing.T) {

View File

@ -50,12 +50,7 @@ func (a *Alias) Start() {}
func (a *Alias) Stop() {}
func (a *Alias) registerActions() {
a.RmAction(ui.KeyShiftA)
a.RmAction(ui.KeyShiftN)
a.RmAction(tcell.KeyCtrlS)
a.RmAction(tcell.KeyCtrlSpace)
a.RmAction(ui.KeySpace)
a.RmActions(ui.KeyShiftA, ui.KeyShiftN, tcell.KeyCtrlS, tcell.KeyCtrlSpace, ui.KeySpace)
a.AddActions(ui.KeyActions{
tcell.KeyEnter: ui.NewKeyAction("Goto Resource", a.gotoCmd, true),
tcell.KeyEscape: ui.NewKeyAction("Reset", a.resetCmd, false),

View File

@ -52,8 +52,7 @@ func (c *Container) Name() string { return "containers" }
func (c *Container) extraActions(aa ui.KeyActions) {
c.LogResource.extraActions(aa)
c.masterPage().RmAction(tcell.KeyCtrlSpace)
c.masterPage().RmAction(ui.KeySpace)
c.masterPage().RmActions(tcell.KeyCtrlSpace, ui.KeySpace)
aa[ui.KeyShiftF] = ui.NewKeyAction("PortForward", c.portFwdCmd, true)
aa[ui.KeyShiftL] = ui.NewKeyAction("Logs Previous", c.prevLogsCmd, true)

View File

@ -31,9 +31,7 @@ func (c *Context) Init(ctx context.Context) {
}
func (c *Context) extraActions(aa ui.KeyActions) {
c.masterPage().RmAction(ui.KeyShiftA)
c.masterPage().RmAction(tcell.KeyCtrlSpace)
c.masterPage().RmAction(ui.KeySpace)
c.masterPage().RmActions(ui.KeyShiftA, tcell.KeyCtrlSpace, ui.KeySpace)
}
func (c *Context) useCtx(app *App, _, res, sel string) {

View File

@ -204,9 +204,9 @@ func (d *Details) refreshTitle() {
func (d *Details) setTitle(t string) {
d.title = t
title := skinTitle(fmt.Sprintf(detailsTitleFmt, d.category, t), d.app.Styles.Frame())
title := ui.SkinTitle(fmt.Sprintf(detailsTitleFmt, d.category, t), d.app.Styles.Frame())
if !d.cmdBuff.Empty() {
title += skinTitle(fmt.Sprintf(ui.SearchFmt, d.cmdBuff.String()), d.app.Styles.Frame())
title += ui.SkinTitle(fmt.Sprintf(ui.SearchFmt, d.cmdBuff.String()), d.app.Styles.Frame())
}
d.SetTitle(title)
}

View File

@ -58,8 +58,7 @@ func (v *Help) Hints() model.MenuHints {
}
func (v *Help) bindKeys() {
v.RmAction(tcell.KeyCtrlSpace)
v.RmAction(ui.KeySpace)
v.RmActions(tcell.KeyCtrlSpace, ui.KeySpace)
v.actions = ui.KeyActions{
tcell.KeyEsc: ui.NewKeyAction("Back", v.backCmd, true),

View File

@ -90,9 +90,9 @@ func (l *Log) bindKeys() {
func (l *Log) setTitle(path, co string) {
var fmat string
if co == "" {
fmat = skinTitle(fmt.Sprintf(logFmt, path), l.app.Styles.Frame())
fmat = ui.SkinTitle(fmt.Sprintf(logFmt, path), l.app.Styles.Frame())
} else {
fmat = skinTitle(fmt.Sprintf(logCoFmt, path, co), l.app.Styles.Frame())
fmat = ui.SkinTitle(fmt.Sprintf(logCoFmt, path, co), l.app.Styles.Frame())
}
l.path = path
l.SetTitle(fmat)

View File

@ -87,7 +87,7 @@ func (p *Pod) listContainers(app *App, _, res, sel string) {
log.Fatal().Msg("Expecting a valid pod")
}
list := resource.NewContainerList(app.Conn(), pod)
title := skinTitle(fmt.Sprintf(containerFmt, "Container", sel), app.Styles.Frame())
title := ui.SkinTitle(fmt.Sprintf(containerFmt, "Container", sel), app.Styles.Frame())
// Stop my updater
if p.cancelFn != nil {

View File

@ -87,10 +87,7 @@ func (p *Policy) Stop() {
}
func (p *Policy) bindKeys() {
p.RmAction(ui.KeyShiftA)
p.RmAction(tcell.KeyCtrlSpace)
p.RmAction(ui.KeySpace)
p.RmActions(ui.KeyShiftA, tcell.KeyCtrlSpace, ui.KeySpace)
p.AddActions(ui.KeyActions{
tcell.KeyEscape: ui.NewKeyAction("Back", p.resetCmd, false),
ui.KeySlash: ui.NewKeyAction("Filter", p.activateCmd, false),

View File

@ -130,10 +130,7 @@ func (r *Rbac) Name() string {
}
func (r *Rbac) bindKeys() {
r.RmAction(ui.KeyShiftA)
r.RmAction(tcell.KeyCtrlSpace)
r.RmAction(ui.KeySpace)
r.RmActions(ui.KeyShiftA, tcell.KeyCtrlSpace, ui.KeySpace)
r.AddActions(ui.KeyActions{
tcell.KeyEscape: ui.NewKeyAction("Reset", r.resetCmd, false),
ui.KeySlash: ui.NewKeyAction("Filter", r.activateCmd, false),
@ -142,7 +139,7 @@ func (r *Rbac) bindKeys() {
}
func (r *Rbac) getTitle() string {
return skinTitle(fmt.Sprintf(rbacTitleFmt, rbacTitle, r.roleName), r.app.Styles.Frame())
return ui.SkinTitle(fmt.Sprintf(rbacTitleFmt, rbacTitle, r.roleName), r.app.Styles.Frame())
}
func (r *Rbac) refresh() {

View File

@ -48,7 +48,7 @@ func (s *Subject) Init(ctx context.Context) {
s.bindKeys()
s.SetSortCol(1, len(rbacHeader), true)
s.subjectKind = mapCmdSubject(s.app.Config.K9s.ActiveCluster().View.Active)
s.SetBaseTitle(s.subjectKind)
s.BaseTitle = s.subjectKind
s.SelectRow(1, true)
s.refresh()
@ -89,11 +89,7 @@ func (s *Subject) masterPage() *Table {
}
func (s *Subject) bindKeys() {
s.RmAction(ui.KeyShiftA)
s.RmAction(ui.KeyShiftP)
s.RmAction(tcell.KeyCtrlSpace)
s.RmAction(ui.KeySpace)
s.RmActions(ui.KeyShiftA, ui.KeyShiftP, tcell.KeyCtrlSpace, ui.KeySpace)
s.AddActions(ui.KeyActions{
tcell.KeyEnter: ui.NewKeyAction("Policies", s.policyCmd, true),
tcell.KeyEscape: ui.NewKeyAction("Back", s.resetCmd, false),

View File

@ -33,7 +33,7 @@ func (t *Table) Init(ctx context.Context) {
func (t *Table) Start() {}
func (t *Table) Stop() {}
func (t *Table) Name() string { return t.GetBaseTitle() }
func (t *Table) Name() string { return t.BaseTitle }
// BufferChanged indicates the buffer was changed.
func (t *Table) BufferChanged(s string) {}
@ -44,7 +44,7 @@ func (t *Table) BufferActive(state bool, k ui.BufferKind) {
}
func (t *Table) saveCmd(evt *tcell.EventKey) *tcell.EventKey {
if path, err := saveTable(t.app.Config.K9s.CurrentCluster, t.GetBaseTitle(), t.GetFilteredData()); err != nil {
if path, err := saveTable(t.app.Config.K9s.CurrentCluster, t.BaseTitle, t.GetFilteredData()); err != nil {
t.app.Flash().Err(err)
} else {
t.app.Flash().Infof("File %s saved successfully!", path)

View File

@ -5,7 +5,6 @@ import (
"fmt"
"os"
"path/filepath"
"strings"
"time"
"github.com/derailed/k9s/internal/config"
@ -61,14 +60,3 @@ func saveTable(cluster, name string, data resource.TableData) (string, error) {
return path, nil
}
func skinTitle(fmat string, style config.Frame) string {
fmat = strings.Replace(fmat, "[fg:bg", "["+style.Title.FgColor+":"+style.Title.BgColor, -1)
fmat = strings.Replace(fmat, "[hilite", "["+style.Title.HighlightColor, 1)
fmat = strings.Replace(fmat, "[key", "["+style.Menu.NumKeyColor, 1)
fmat = strings.Replace(fmat, "[filter", "["+style.Title.FilterColor, 1)
fmat = strings.Replace(fmat, "[count", "["+style.Title.CounterColor, 1)
fmat = strings.Replace(fmat, ":bg:", ":"+style.Title.BgColor+":", -1)
return fmat
}