diff --git a/README.md b/README.md index bd6a7bb7..11f89f9d 100644 --- a/README.md +++ b/README.md @@ -208,6 +208,7 @@ K9s uses aliases to navigate most K8s resources. | View a Kubernetes resource using singular/plural or short-name | `:`po⏎ | accepts singular, plural, short-name or alias ie pod or pods | | View a Kubernetes resource in a given namespace | `:`alias namespace⏎ | | | Filter out a resource view given a filter | `/`filter⏎ | Regex2 supported ie `fred|blee` to filter resources named fred or blee | +| Inverse regex filer | `/`! filter⏎ | Keep everything that *doesn't* match. Not implemented for logs. | | Filter resource view by labels | `/`-l label-selector⏎ | | | Fuzzy find a resource given a filter | `/`-f filter⏎ | | | Bails out of view/command/filter mode | `` | | @@ -220,6 +221,7 @@ K9s uses aliases to navigate most K8s resources. | To kill a resource (no confirmation dialog!) | `ctrl-k` | | | Launch pulses view | `:`pulses or pu⏎ | | | Launch XRay view | `:`xray RESOURCE [NAMESPACE]⏎ | RESOURCE can be one of po, svc, dp, rs, sts, ds, NAMESPACE is optional | +| Launch Popeye view | `:`popeye or pop⏎ | See https://popeyecli.io | --- diff --git a/internal/ui/table.go b/internal/ui/table.go index 4de4adf4..b3a5018a 100644 --- a/internal/ui/table.go +++ b/internal/ui/table.go @@ -380,7 +380,7 @@ func (t *Table) filtered(data render.TableData) render.TableData { return fuzzyFilter(q[2:], filtered) } - filtered, err := rxFilter(t.cmdBuff.GetText(), filtered) + filtered, err := rxFilter(q, IsInverseSelector(q), filtered) if err != nil { log.Error().Err(errors.New("Invalid filter expression")).Msg("Regexp") t.cmdBuff.ClearText(true) diff --git a/internal/ui/table_helper.go b/internal/ui/table_helper.go index 2e3498fa..5d1ec389 100644 --- a/internal/ui/table_helper.go +++ b/internal/ui/table_helper.go @@ -37,8 +37,10 @@ const ( ) var ( - // LableRx identifies a label query - LableRx = regexp.MustCompile(`\A\-l`) + // LabelRx identifies a label query + LabelRx = regexp.MustCompile(`\A\-l`) + + inverseRx = regexp.MustCompile(`\A\!`) fuzzyRx = regexp.MustCompile(`\A\-f`) ) @@ -66,7 +68,7 @@ func IsLabelSelector(s string) bool { if s == "" { return false } - return LableRx.MatchString(s) + return LabelRx.MatchString(s) } // IsFuzzySelector checks if query is fuzzy. @@ -77,6 +79,13 @@ func IsFuzzySelector(s string) bool { return fuzzyRx.MatchString(s) } +func IsInverseSelector(s string) bool { + if s == "" { + return false + } + return inverseRx.MatchString(s) +} + // TrimLabelSelector extracts label query. func TrimLabelSelector(s string) string { return strings.TrimSpace(s[2:]) @@ -137,7 +146,10 @@ func filterToast(data render.TableData) render.TableData { return toast } -func rxFilter(q string, data render.TableData) (render.TableData, error) { +func rxFilter(q string, inverse bool, data render.TableData) (render.TableData, error) { + if inverse { + q = q[1:] + } rx, err := regexp.Compile(`(?i)(` + q + `)`) if err != nil { return data, err @@ -150,7 +162,8 @@ func rxFilter(q string, data render.TableData) (render.TableData, error) { } for _, re := range data.RowEvents { fields := strings.Join(re.Row.Fields, " ") - if rx.MatchString(fields) { + if (inverse && ! rx.MatchString(fields)) || + ((! inverse) && rx.MatchString(fields)) { filtered.RowEvents = append(filtered.RowEvents, re) } } diff --git a/internal/view/sanitizer.go b/internal/view/sanitizer.go index 58f2a07e..922ca502 100644 --- a/internal/view/sanitizer.go +++ b/internal/view/sanitizer.go @@ -235,6 +235,10 @@ func (s *Sanitizer) filter(root *xray.TreeNode) *xray.TreeNode { return root.Filter(q, fuzzyFilter) } + if ui.IsInverseSelector(q) { + return root.Filter(q, rxInverseFilter) + } + return root.Filter(q, rxFilter) } diff --git a/internal/view/xray.go b/internal/view/xray.go index a8ae529d..0fb7936b 100644 --- a/internal/view/xray.go +++ b/internal/view/xray.go @@ -466,6 +466,10 @@ func (x *Xray) filter(root *xray.TreeNode) *xray.TreeNode { return root.Filter(q, fuzzyFilter) } + if ui.IsInverseSelector(q) { + return root.Filter(q, rxInverseFilter) + } + return root.Filter(q, rxFilter) } @@ -687,6 +691,19 @@ func rxFilter(q, path string) bool { return false } +func rxInverseFilter(q, path string) bool { + q = strings.TrimSpace(q[1:]) + rx := regexp.MustCompile(`(?i)` + q) + tokens := strings.Split(path, xray.PathSeparator) + for _, t := range tokens { + if rx.MatchString(t) { + return false + } + } + + return true +} + func makeTreeNode(node *xray.TreeNode, expanded bool, showIcons bool, styles *config.Styles) *tview.TreeNode { n := tview.NewTreeNode("No data...") if node != nil {