Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 0 additions & 100 deletions .github/workflows/auto-tag.yml

This file was deleted.

5 changes: 0 additions & 5 deletions CHANGELOG.md

This file was deleted.

4 changes: 2 additions & 2 deletions cmd/kubeutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ package cmd

import (
"fmt"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
"os"
"path/filepath"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
)

func kubeconfigPath() string {
Expand Down
2 changes: 1 addition & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ var rootCmd = &cobra.Command{
`,
RunE: func(cmd *cobra.Command, args []string) error {
if flagVersion {
fmt.Println("kdebug v0.1.1")
fmt.Println("kdebug v0.1.0")
return nil
}
return runInteractive()
Expand Down
121 changes: 29 additions & 92 deletions cmd/tui_picker.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,33 +14,29 @@ import (
"k8s.io/client-go/kubernetes"
)

type focusArea int

const (
focusTable focusArea = iota
focusFilter
)
type podRow struct {
ns, name, ready, status, restarts, age string
}

type pickModel struct {
allPods []corev1.Pod
table table.Model
filter textinput.Model
selected *corev1.Pod
termWidth, termHt int
focus focusArea
}

func newPickModel(pods []corev1.Pod) pickModel {
m := pickModel{allPods: pods, focus: focusTable}
m := pickModel{allPods: pods}

// Filter input (ASCII prompt avoids width quirks)
// ASCII prompt avoids width quirks in some fonts
ti := textinput.New()
ti.Placeholder = "Press / to filter… (Enter applies; Enter again selects; Esc cancels)"
ti.Placeholder = "Press / to filter… (Enter to select, Esc to cancel)"
ti.Prompt = "> "
ti.Blur()
m.filter = ti

// Seed columns; will be resized on first WindowSizeMsg
// Seed; real sizing happens on first WindowSizeMsg
cols := []table.Column{
{Title: "NAMESPACE", Width: 12},
{Title: "NAME", Width: 24},
Expand All @@ -57,12 +53,13 @@ func newPickModel(pods []corev1.Pod) pickModel {
table.WithHeight(10),
)

// Compact styles (no vertical padding so rows are tight)
// ZERO padding/margins/borders so headers align perfectly
st := table.Styles{
Header: lipgloss.NewStyle().Bold(true),
Cell: lipgloss.NewStyle(),
// Selected row style—no extra padding
Selected: lipgloss.NewStyle().
Background(lipgloss.Color("57")). // purple
Background(lipgloss.Color("57")). // tweak if you like
Foreground(lipgloss.Color("230")),
}
t.SetStyles(st)
Expand Down Expand Up @@ -119,81 +116,46 @@ func (m pickModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case tea.KeyMsg:
switch k.String() {
case "/":
m.focus = focusFilter
m.filter.Focus()
return m, nil

case "tab":
// Toggle focus
if m.focus == focusFilter {
m.filter.Blur()
m.focus = focusTable
} else {
m.focus = focusFilter
m.filter.Focus()
}
return m, nil

case "esc":
// If filtering, blur (and keep current filtered rows)
if m.focus == focusFilter {
if m.filter.Focused() {
m.filter.Blur()
m.focus = focusTable
m.filter.SetValue("")
m.applyFilter("")
return m, nil
}
return m, tea.Quit

case "enter", "ctrl+j":
if m.focus == focusFilter {
// Apply filter first
if m.filter.Focused() {
m.applyFilter(m.filter.Value())

// If exactly one row remains, auto-select it and quit
if len(m.table.Rows()) == 1 {
r := m.table.Rows()[0]
if pod, ok := m.findPod(r); ok {
m.selected = &pod
return m, tea.Quit
}
}

// Otherwise blur filter and return focus to table;
// user can press Enter again to select the highlighted row.
m.filter.Blur()
m.focus = focusTable
return m, nil
}

// Table-focused: select highlighted row
if len(m.table.Rows()) == 0 {
return m, nil
}
r := m.table.SelectedRow()
if pod, ok := m.findPod(r); ok {
m.selected = &pod
ns, name := r[0], r[1]
for i := range m.allPods {
if m.allPods[i].Namespace == ns && m.allPods[i].Name == name {
m.selected = &m.allPods[i]
break
}
}
return m, tea.Quit
}
}

// Default updates
var cmds []tea.Cmd

// Update table always (so arrows work even when filter focused off)
var tcmd tea.Cmd
m.table, tcmd = m.table.Update(msg)
cmds = append(cmds, tcmd)
var cmd tea.Cmd
m.table, cmd = m.table.Update(msg)

// Update filter only when focused
if m.focus == focusFilter {
if m.filter.Focused() {
var fcmd tea.Cmd
m.filter, fcmd = m.filter.Update(msg)
// Live filtering: update rows as user types
m.applyFilter(m.filter.Value())
cmds = append(cmds, fcmd)
return m, tea.Batch(cmd, fcmd)
}

return m, tea.Batch(cmds...)
return m, cmd
}

func (m *pickModel) resize() {
Expand All @@ -208,7 +170,7 @@ func (m *pickModel) resize() {
m.table.SetHeight(tableHeight)
m.table.SetWidth(m.termWidth)

// Column widths: fixed for most, NAME grows/shrinks
// Fixed columns; give remainder to NAME.
wNS := 16
wReady := 5
wStatus := 18
Expand All @@ -220,7 +182,6 @@ func (m *pickModel) resize() {
if wName < 10 {
wName = 10
}

m.table.SetColumns([]table.Column{
{Title: "NAMESPACE", Width: wNS},
{Title: "NAME", Width: wName},
Expand All @@ -235,47 +196,23 @@ func (m *pickModel) applyFilter(q string) {
q = strings.TrimSpace(strings.ToLower(q))
if q == "" {
m.table.SetRows(rowsFromPods(m.allPods))
// Keep cursor in-bounds
if len(m.table.Rows()) > 0 && m.table.Cursor() >= len(m.table.Rows()) {
m.table.SetCursor(len(m.table.Rows()) - 1)
}
return
}
filtered := make([]corev1.Pod, 0, len(m.allPods))
for _, p := range m.allPods {
s := strings.ToLower(strings.Join([]string{
p.Namespace,
p.Name,
podStatus(&p),
fmt.Sprintf("%v", p.Labels),
p.Namespace, p.Name, podStatus(&p), fmt.Sprintf("%v", p.Labels),
}, " "))
if strings.Contains(s, q) {
filtered = append(filtered, p)
}
}
m.table.SetRows(rowsFromPods(filtered))
// Reset cursor to first result when the list changes
if len(m.table.Rows()) > 0 {
m.table.SetCursor(0)
}
}

func (m pickModel) findPod(r table.Row) (corev1.Pod, bool) {
if len(r) < 2 {
return corev1.Pod{}, false
}
ns, name := r[0], r[1]
for i := range m.allPods {
if m.allPods[i].Namespace == ns && m.allPods[i].Name == name {
return m.allPods[i], true
}
}
return corev1.Pod{}, false
}

func (m pickModel) View() string {
title := lipgloss.NewStyle().Bold(true).
Render("Select a pod (↑/↓ or j/k move, / filter, Enter selects, Tab switch, Esc cancel)")
// No extra blank lines; keeps header tight to first row
title := lipgloss.NewStyle().Bold(true).Render("Select a pod (↑/↓ or j/k to move, / to filter, Enter to select, Esc to cancel)")
return title + "\n" + m.table.View() + "\n" + m.filter.View()
}

Expand Down