ronantakizawa/codereview-bench huggingface dataset:
Repository: derailed/k9s
File: internal/view/app.go (Go)

Code before:
func (a *App) helpCmd(evt *tcell.EventKey) *tcell.EventKey {
	if evt != nil && evt.Rune() == '?' && a.Prompt().InCmdMode() {
		return evt
	}

	top := a.Content.Top()
	if top != nil && top.Name() == "help" {
		a.Content.Pop()
		return nil
	}

	if err := a.inject(NewHelp(a), false); err != nil {
		a.Flash().Err(err)
	}

	a.Prompt().Deactivate()
	return nil
}

// previousView returns to the view prior to the current one in the history
func (a *App) previousView(evt *tcell.EventKey) *tcell.EventKey {
	if evt != nil && evt.Rune() == rune(ui.KeyB) && a.Prompt().InCmdMode() {
		return evt
	}
	cmds := a.cmdHistory.List()
	if !(len(cmds) > 1) {
		dialog.ShowError(
			a.Styles.Dialog(),
			a.Content.Pages,
			"Can't go back any further")
		return evt
	} else {
		previousCmd := cmds[1]
		a.cmdHistory.Pop()
		a.gotoResource(previousCmd, "", true)
		a.ResetCmd()
	}

	return nil
}

// lastView switches between the last view and this one a la `cd -`
func (a *App) lastView(evt *tcell.EventKey) *tcell.EventKey {
	if evt != nil && evt.Rune() == ui.KeyDash && a.Prompt().InCmdMode() {
		return evt
	}
	cmds := a.cmdHistory.List()
	if !(len(cmds) > 1) {
		dialog.ShowError(
			a.Styles.Dialog(),
			a.Content.Pages,

Review comment (suggestion):
A dialog here feels a bit heavy, I think best to use a flash for this instance.
Also not keen on !(len(cmds) > 1.  This is len(cmds) <=1 return. 
But... as noted below better to defer these calls to cmdHistory.

Code after:

func (a *App) helpCmd(evt *tcell.EventKey) *tcell.EventKey {
	if evt != nil && evt.Rune() == '?' && a.Prompt().InCmdMode() {
		return evt
	}

	top := a.Content.Top()
	if top != nil && top.Name() == "help" {
		a.Content.Pop()
		return nil
	}

	if err := a.inject(NewHelp(a), false); err != nil {
		a.Flash().Err(err)
	}

	a.Prompt().Deactivate()
	return nil
}

// previousCommand returns to the command prior to the current one in the history
func (a *App) previousCommand(evt *tcell.EventKey) *tcell.EventKey {
	if evt != nil && evt.Rune() == rune(ui.KeyLeftBracket) && a.Prompt().InCmdMode() {
		return evt
	}
	cmds := a.cmdHistory.List()
	if !a.cmdHistory.Back() {
		a.App.Flash().Warn("Can't go back any further")
		return evt
	}
	a.gotoResource(cmds[a.cmdHistory.CurrentIndex()], "", true, false)
	return nil
}

// nextCommand returns to the command subsequent to the current one in the history
func (a *App) nextCommand(evt *tcell.EventKey) *tcell.EventKey {
	if evt != nil && evt.Rune() == rune(ui.KeyRightBracket) && a.Prompt().InCmdMode() {
		return evt
	}
	cmds := a.cmdHistory.List()
	if !a.cmdHistory.Forward() {
		a.App.Flash().Warn("Can't go forward any further")
		return evt
	}
	// We go to the resource before updating the history so that
	// gotoResource doesn't add this command to the history
	a.gotoResource(cmds[a.cmdHistory.CurrentIndex()], "", true, false)
	return nil
}

// lastCommand switches between the last command and the current one a la `cd -`
Repository: derailed/k9s
File: internal/watch/factory.go (Go)

Code before:
	}
	if !wait || (wait && inf.Informer().HasSynced()) {
		return o, err
	}

	f.waitForCacheSync(ns)
	if client.IsClusterScoped(ns) {
		return inf.Lister().Get(n)
	}
	return inf.Lister().ByNamespace(ns).Get(n)
}

func (f *Factory) waitForCacheSync(ns string) {
	if client.IsClusterWide(ns) {
		ns = client.BlankNamespace
	}

	f.mx.RLock()
	defer f.mx.RUnlock()
	fac, ok := f.factories[ns]
	if !ok {
		return
	}

	// we must block until all started informers' caches were synced
	_ = fac.WaitForCacheSync(f.stopChan)
}

// WaitForCacheSync waits for all factories to update their cache.
func (f *Factory) WaitForCacheSync() {
	for ns, fac := range f.factories {
		m := fac.WaitForCacheSync(f.stopChan)
		for k, v := range m {
			log.Debug().Msgf("CACHE `%q Loaded %t:%s", ns, v, k)
		}
	}
}

// Client return the factory connection.
func (f *Factory) Client() client.Connection {
	return f.client
}

// FactoryFor returns a factory for a given namespace.
func (f *Factory) FactoryFor(ns string) di.DynamicSharedInformerFactory {
	return f.factories[ns]
}

// SetActiveNS sets the active namespace.
func (f *Factory) SetActiveNS(ns string) error {
	if f.isClusterWide() {

Review comment (bug):
The reason for blocking here is that `command` call the `internal/dao/registry::Meta.loadCRDs` method during initialization (and as part of the init of the whole app), and if it doesn't block here, it will result in an incorrect list of CRDs being read (and theoretically, other resources will have the same problem...).

But this also creates additional problems, in my cluster with a slightly higher load, a total of 96 CRDs takes about ~2s to synchronize.

I didn't think of any good way (or I'm missing something) to solve this problem, does anyone have any ideas?

Code after:
	}
	var o runtime.Object
	if client.IsClusterScoped(ns) {
		o, err = inf.Lister().Get(n)
	} else {
		o, err = inf.Lister().ByNamespace(ns).Get(n)
	}
	if !wait || (wait && inf.Informer().HasSynced()) {
		return o, err
	}

	f.waitForCacheSync(ns)
	if client.IsClusterScoped(ns) {
		return inf.Lister().Get(n)
	}

	return inf.Lister().ByNamespace(ns).Get(n)
}

func (f *Factory) waitForCacheSync(ns string) {
	if client.IsClusterWide(ns) {
		ns = client.BlankNamespace
	}

	f.mx.RLock()
	defer f.mx.RUnlock()
	fac, ok := f.factories[ns]
	if !ok {
		return
	}

	// Hang for a sec for the cache to refresh if still not done bail out!
	c := make(chan struct{})
	go func(c chan struct{}) {
		<-time.After(defaultWaitTime)
		close(c)
	}(c)
	_ = fac.WaitForCacheSync(c)
}

// WaitForCacheSync waits for all factories to update their cache.
func (f *Factory) WaitForCacheSync() {
	for ns, fac := range f.factories {
		m := fac.WaitForCacheSync(f.stopChan)
		for k, v := range m {
			log.Debug().Msgf("CACHE `%q Loaded %t:%s", ns, v, k)
		}
	}
}

// Client return the factory connection.
Repository: derailed/k9s
File: internal/render/pod.go (Go)

Code before:
		model1.HeaderColumn{Name: "AGE", Time: true},
	}
}

// Render renders a K8s resource to screen.
func (p Pod) Render(o interface{}, ns string, row *model1.Row) error {
	pwm, ok := o.(*PodWithMetrics)
	if !ok {
		return fmt.Errorf("expected PodWithMetrics, but got %T", o)
	}

	var po v1.Pod
	if err := runtime.DefaultUnstructuredConverter.FromUnstructured(pwm.Raw.Object, &po); err != nil {
		return err
	}

	ics := po.Status.InitContainerStatuses
	_, _, irc := p.Statuses(ics)
	cs := po.Status.ContainerStatuses
	cr, _, rc := p.Statuses(cs)

	var ccmx []mv1beta1.ContainerMetrics
	if pwm.MX != nil {
		ccmx = pwm.MX.Containers
	}
	cc := make([]v1.Container, 0, len(po.Spec.InitContainers)+len(po.Spec.Containers))
	cc = append(cc, filterRestartableInitCO(po.Spec.InitContainers)...)
	cc = append(cc, po.Spec.Containers...)	
	c, r := gatherCoMX(cc, ccmx)
	phase := p.Phase(&po)
	row.ID = client.MetaFQN(po.ObjectMeta)

	row.Fields = model1.Fields{
		po.Namespace,
		po.Name,
		computeVulScore(po.ObjectMeta, &po.Spec),
		"●",
		strconv.Itoa(cr) + "/" + strconv.Itoa(len(po.Spec.Containers)),
		phase,
		strconv.Itoa(rc + irc),
		toMc(c.cpu),
		toMi(c.mem),
		toMc(r.cpu) + ":" + toMc(r.lcpu),
		toMi(r.mem) + ":" + toMi(r.lmem),
		client.ToPercentageStr(c.cpu, r.cpu),
		client.ToPercentageStr(c.cpu, r.lcpu),
		client.ToPercentageStr(c.mem, r.mem),
		client.ToPercentageStr(c.mem, r.lmem),
		na(po.Status.PodIP),
		na(po.Spec.NodeName),
		asNominated(po.Status.NominatedNodeName),

Review comment (performance):
I think it might be best to include the filtering within this call ie gatherCoMx(po.Spec,  ccmx). This will hide the complexity from the call site and will allow us to tune further in future releases.

Code after:
		model1.HeaderColumn{Name: "AGE", Time: true},
	}
}

// Render renders a K8s resource to screen.
func (p Pod) Render(o interface{}, ns string, row *model1.Row) error {
	pwm, ok := o.(*PodWithMetrics)
	if !ok {
		return fmt.Errorf("expected PodWithMetrics, but got %T", o)
	}

	var po v1.Pod
	if err := runtime.DefaultUnstructuredConverter.FromUnstructured(pwm.Raw.Object, &po); err != nil {
		return err
	}

	ics := po.Status.InitContainerStatuses
	_, _, irc := p.Statuses(ics)
	cs := po.Status.ContainerStatuses
	cr, _, rc := p.Statuses(cs)

	var ccmx []mv1beta1.ContainerMetrics
	if pwm.MX != nil {
		ccmx = pwm.MX.Containers
	}
	c, r := gatherCoMX(&po.Spec, ccmx)
	phase := p.Phase(&po)
	row.ID = client.MetaFQN(po.ObjectMeta)

	row.Fields = model1.Fields{
		po.Namespace,
		po.Name,
		computeVulScore(po.ObjectMeta, &po.Spec),
		"●",
		strconv.Itoa(cr) + "/" + strconv.Itoa(len(po.Spec.Containers)),
		phase,
		strconv.Itoa(rc + irc),
		toMc(c.cpu),
		toMi(c.mem),
		toMc(r.cpu) + ":" + toMc(r.lcpu),
		toMi(r.mem) + ":" + toMi(r.lmem),
		client.ToPercentageStr(c.cpu, r.cpu),
		client.ToPercentageStr(c.cpu, r.lcpu),
		client.ToPercentageStr(c.mem, r.mem),
		client.ToPercentageStr(c.mem, r.lmem),
		na(po.Status.PodIP),
		na(po.Spec.NodeName),
		asNominated(po.Status.NominatedNodeName),
		asReadinessGate(po),
		p.mapQOS(po.Status.QOSClass),
		mapToStr(po.Labels),
Repository: derailed/k9s
File: internal/render/helpers.go (Go)

Code before:
}

// ToAge converts time to human duration.
func ToAge(t metav1.Time) string {
	if t.IsZero() {
		return UnknownValue
	}

	return duration.HumanDuration(time.Since(t.Time))
}

func toAgeHuman(s string) string {
	if len(s) == 0 {
		return UnknownValue
	}

	t, err := time.Parse(time.RFC3339, s)
	if err != nil {
		return NAValue
	}

	return duration.HumanDuration(time.Since(t))
}

// ToRestartAge converts pod last restart time to human duration.
func ToRestartAge(t metav1.Time) string {
	if t.IsZero() {
		return NAValue
	}
	
	return duration.HumanDuration(time.Since(t.Time))
}

// Truncate a string to the given l and suffix ellipsis if needed.
func Truncate(str string, width int) string {
	return runewidth.Truncate(str, width, string(tview.SemigraphicsHorizontalEllipsis))
}

func mapToStr(m map[string]string) string {
	if len(m) == 0 {
		return ""
	}

	kk := make([]string, 0, len(m))
	for k := range m {
		kk = append(kk, k)
	}
	sort.Strings(kk)

	bb := make([]byte, 0, 100)
	for i, k := range kk {

Review comment (suggestion):
Not sure I understand why we are not reusing ToAge here. Same implementation `<unknown>` is on par if we can not determine the last restart.

Code after:
}

// ToAge converts time to human duration.
func ToAge(t metav1.Time) string {
	if t.IsZero() {
		return UnknownValue
	}

	return duration.HumanDuration(time.Since(t.Time))
}

func toAgeHuman(s string) string {
	if len(s) == 0 {
		return UnknownValue
	}

	t, err := time.Parse(time.RFC3339, s)
	if err != nil {
		return NAValue
	}

	return duration.HumanDuration(time.Since(t))
}

// Truncate a string to the given l and suffix ellipsis if needed.
func Truncate(str string, width int) string {
	return runewidth.Truncate(str, width, string(tview.SemigraphicsHorizontalEllipsis))
}

func mapToStr(m map[string]string) string {
	if len(m) == 0 {
		return ""
	}

	kk := make([]string, 0, len(m))
	for k := range m {
		kk = append(kk, k)
	}
	sort.Strings(kk)

	bb := make([]byte, 0, 100)
	for i, k := range kk {
		bb = append(bb, k+"="+m[k]...)
		if i < len(kk)-1 {
			bb = append(bb, ' ')
		}
	}

	return string(bb)
}
Repository: derailed/k9s
File: internal/vul/scanner.go (Go)

Code before:
	s.opts.GenerateMissingCPEs = true

	var err error
	s.store, s.dbStatus, err = grype.LoadVulnerabilityDB(
		s.opts.DB.ToLegacyCuratorConfig(),
		s.opts.DB.AutoUpdate,
	)
	if err != nil {
		log.Error().Err(err).Msgf("VulDb load failed")
		return
	}

	if err := validateDBLoad(err, s.dbStatus); err != nil {
		log.Error().Err(err).Msgf("VulDb validate failed")
		return
	}

	s.initialized = true
}

// Stop closes scan database.
func (s *imageScanner) Stop() {
	s.mx.RLock()
	defer s.mx.RUnlock()

	if s.dbCloser != nil {
		(*s.dbCloser).Close()
		s.dbCloser = nil
	}
}

func (s *imageScanner) Score(ii ...string) string {
	var sc scorer
	for _, i := range ii {
		if scan, ok := s.GetScan(i); ok {
			sc = sc.Add(newScorer(scan.Tally))
		}
	}

	return sc.String()
}

func (s *imageScanner) isInitialized() bool {
	s.mx.RLock()
	defer s.mx.RUnlock()

	return s.initialized
}

func (s *imageScanner) Enqueue(ctx context.Context, images ...string) {
	if !s.isInitialized() {

Review comment (suggestion):
Based on the api changes. It looks like the new api no longer returns a db.Closer. 
So we might as well remove it

Code after:
	var err error
	s.store, s.dbStatus, err = grype.LoadVulnerabilityDB(
		s.opts.DB.ToLegacyCuratorConfig(),
		s.opts.DB.AutoUpdate,
	)
	if err != nil {
		log.Error().Err(err).Msgf("VulDb load failed")
		return
	}

	if err := validateDBLoad(err, s.dbStatus); err != nil {
		log.Error().Err(err).Msgf("VulDb validate failed")
		return
	}

	s.initialized = true
}

// Stop closes scan database.
func (s *imageScanner) Stop() {
	s.mx.RLock()
	defer s.mx.RUnlock()

	if s.store != nil {
		s.store.Close()
		s.store = nil
	}
}

func (s *imageScanner) Score(ii ...string) string {
	var sc scorer
	for _, i := range ii {
		if scan, ok := s.GetScan(i); ok {
			sc = sc.Add(newScorer(scan.Tally))
		}
	}

	return sc.String()
}

func (s *imageScanner) isInitialized() bool {
	s.mx.RLock()
	defer s.mx.RUnlock()

	return s.initialized
}

func (s *imageScanner) Enqueue(ctx context.Context, images ...string) {
	if !s.isInitialized() {
		return
	}
Repository: derailed/k9s
File: internal/ui/config.go (Go)

Code before:
			}
		}
	}()

	slog.Debug("ConfigWatcher watching", slogs.FileName, config.AppConfigFile)
	if err := w.Add(config.AppConfigFile); err != nil {
		return err
	}

	cl, ct, ok := c.activeConfig()
	if !ok {
		return nil
	}
	ctConfigFile := config.AppContextConfig(cl, ct)
	slog.Debug("ConfigWatcher watching", slogs.FileName, ctConfigFile)

	return w.Add(ctConfigFile)
}

func (c *Configurator) activeSkin() (string, bool) {
	var skin string
	if c.Config == nil || c.Config.K9s == nil {
		return skin, false
	}

	if env_skin := os.Getenv("K9S_SKIN"); env_skin != "" {
		if _, err := os.Stat(config.SkinFileFromName(env_skin)); err == nil {
			skin = env_skin
			slog.Debug("Loading env skin", slogs.Skin, skin)
		}
	}

	if ct, err := c.Config.K9s.ActiveContext(); err == nil && skin == "" && ct.Skin != "" {
		if _, err := os.Stat(config.SkinFileFromName(ct.Skin)); err == nil {
			skin = ct.Skin
			slog.Debug("Loading context skin",
				slogs.Skin, skin,
				slogs.Context, c.Config.K9s.ActiveContextName(),
			)
		}
	}

	if sk := c.Config.K9s.UI.Skin; skin == "" && sk != "" {
		if _, err := os.Stat(config.SkinFileFromName(sk)); err == nil {
			skin = sk
			slog.Debug("Loading global skin", slogs.Skin, skin)
		}
	}

	return skin, skin != ""
}

Review comment (suggestion):
if the skin env var is set and valid I think we should just return since it overrides any configs skins anyway.

Code after:
			}
		}
	}()

	slog.Debug("ConfigWatcher watching", slogs.FileName, config.AppConfigFile)
	if err := w.Add(config.AppConfigFile); err != nil {
		return err
	}

	cl, ct, ok := c.activeConfig()
	if !ok {
		return nil
	}
	ctConfigFile := config.AppContextConfig(cl, ct)
	slog.Debug("ConfigWatcher watching", slogs.FileName, ctConfigFile)

	return w.Add(ctConfigFile)
}

func (c *Configurator) activeSkin() (string, bool) {
	var skin string
	if c.Config == nil || c.Config.K9s == nil {
		return skin, false
	}

	if env_skin := os.Getenv("K9S_SKIN"); env_skin != "" {
		if _, err := os.Stat(config.SkinFileFromName(env_skin)); err == nil {
			skin = env_skin
			slog.Debug("Loading env skin", slogs.Skin, skin)
			return skin, true
		}
	}

	if ct, err := c.Config.K9s.ActiveContext(); err == nil && ct.Skin != "" {
		if _, err := os.Stat(config.SkinFileFromName(ct.Skin)); err == nil {
			skin = ct.Skin
			slog.Debug("Loading context skin",
				slogs.Skin, skin,
				slogs.Context, c.Config.K9s.ActiveContextName(),
			)
			return skin, true
		}
	}

	if sk := c.Config.K9s.UI.Skin; sk != "" {
		if _, err := os.Stat(config.SkinFileFromName(sk)); err == nil {
			skin = sk
			slog.Debug("Loading global skin", slogs.Skin, skin)
			return skin, true
		}
	}
Repository: derailed/k9s
File: internal/view/context.go (Go)

Code before:

	m := tview.NewModalForm("<Rename>", f)
	m.SetText(fmt.Sprintf("Rename context %q?", name))
	m.SetDoneFunc(func(int, string) {
		app.Content.RemovePage(renamePage)
	})
	app.Content.AddPage(renamePage, m, false, false)
	app.Content.ShowPage(renamePage)

	for i := range f.GetButtonCount() {
		f.GetButton(i).
			SetBackgroundColorActivated(styles.ButtonFocusBgColor.Color()).
			SetLabelColorActivated(styles.ButtonFocusFgColor.Color())
	}
}

func (c *Context) deleteDialogCallback(contextName string) error {
	if err := c.App().factory.Client().Config().DelContext(contextName); err != nil {
		c.App().Flash().Err(err)
		return err
	}
	c.Refresh()
	return nil
}

func (c *Context) showDeleteConfirmation(name string, callback func(string) error) {
	app := c.App()
	styles := app.Styles.Dialog()

	f := tview.NewForm().
		SetItemPadding(0).
		SetButtonsAlign(tview.AlignCenter).
		SetButtonBackgroundColor(styles.ButtonBgColor.Color()).
		SetButtonTextColor(styles.ButtonFgColor.Color()).
		SetLabelColor(styles.LabelFgColor.Color()).
		SetFieldTextColor(styles.FieldFgColor.Color())
	f.AddButton("OK", func() {
		if err := callback(name); err != nil {
			app.Flash().Err(err)
			return
		}
		app.Content.Pages.RemovePage(deletePage)
	}).
		AddButton("Cancel", func() {
			app.Content.RemovePage(deletePage)
		})

	m := tview.NewModalForm("<Delete>", f)
	m.SetText(fmt.Sprintf("Delete context %q?", name))
	m.SetDoneFunc(func(int, string) {
		app.Content.RemovePage(deletePage)

Review comment (suggestion):
You should use the built-in dialog for this `dialog.ShowConfirm`

Code after:
		SetLabelColor(styles.LabelFgColor.Color()).
		SetFieldTextColor(styles.FieldFgColor.Color())
	f.AddInputField(inputField, name, 0, nil, nil).
		AddButton("OK", func() {
			if err := ok(f, name); err != nil {
				app.Flash().Err(err)
				return
			}
			app.Content.Pages.RemovePage(renamePage)
		}).
		AddButton("Cancel", func() {
			app.Content.RemovePage(renamePage)
		})

	m := tview.NewModalForm("<Rename>", f)
	m.SetText(fmt.Sprintf("Rename context %q?", name))
	m.SetDoneFunc(func(int, string) {
		app.Content.RemovePage(renamePage)
	})
	app.Content.AddPage(renamePage, m, false, false)
	app.Content.ShowPage(renamePage)

	for i := range f.GetButtonCount() {
		f.GetButton(i).
			SetBackgroundColorActivated(styles.ButtonFocusBgColor.Color()).
			SetLabelColorActivated(styles.ButtonFocusFgColor.Color())
	}
}

func (c *Context) useCtx(app *App, _ ui.Tabular, gvr *client.GVR, path string) {
	slog.Debug("Using context",
		slogs.GVR, gvr,
		slogs.FQN, path,
	)
	if err := useContext(app, path); err != nil {
		app.Flash().Err(err)
		return
	}
	c.Refresh()
	c.GetTable().Select(1, 0)
}

func useContext(app *App, name string) error {
	if app.Content.Top() != nil {
		app.Content.Top().Stop()
	}
	res, err := dao.AccessorFor(app.factory, client.CtGVR)
	if err != nil {
		return err
	}
	switcher, ok := res.(dao.Switchable)
Repository: derailed/k9s
File: internal/view/context.go (Go)

Code before:
const (
	renamePage = "rename"
	deletePage = "delete"
	inputField = "New name:"
)

// Context presents a context viewer.
type Context struct {
	ResourceViewer
}

// NewContext returns a new viewer.
func NewContext(gvr *client.GVR) ResourceViewer {
	c := Context{
		ResourceViewer: NewBrowser(gvr),
	}
	c.GetTable().SetEnterFn(c.useCtx)
	c.AddBindKeysFn(c.bindKeys)

	return &c
}

func (c *Context) bindKeys(aa *ui.KeyActions) {
	aa.Delete(ui.KeyShiftA, tcell.KeyCtrlSpace, ui.KeySpace)
	aa.Add(ui.KeyR, ui.NewKeyAction("Rename", c.renameCmd, true))
	aa.Add(ui.KeyD, ui.NewKeyAction("Delete", c.deleteCmd, true))
}

func (c *Context) renameCmd(evt *tcell.EventKey) *tcell.EventKey {
	contextName := c.GetTable().GetSelectedItem()
	if contextName == "" {
		return evt
	}

	c.showRenameModal(contextName, c.renameDialogCallback)

	return nil
}

func (c *Context) deleteCmd(evt *tcell.EventKey) *tcell.EventKey {
	contextName := c.GetTable().GetSelectedItem()
	if contextName == "" {
		return evt
	}

	c.showDeleteConfirmation(contextName, c.deleteDialogCallback)

	return nil
}

func (c *Context) renameDialogCallback(form *tview.Form, contextName string) error {

Review comment (suggestion):
This should use the standard CtrlD mnemonic. Also we should check for readonly setting and only bind these keys is not enabled.

Code after:

const (
	renamePage = "rename"
	inputField = "New name:"
)

// Context presents a context viewer.
type Context struct {
	ResourceViewer
}

// NewContext returns a new viewer.
func NewContext(gvr *client.GVR) ResourceViewer {
	c := Context{
		ResourceViewer: NewBrowser(gvr),
	}
	c.GetTable().SetEnterFn(c.useCtx)
	c.AddBindKeysFn(c.bindKeys)

	return &c
}

func (c *Context) bindKeys(aa *ui.KeyActions) {
	aa.Delete(ui.KeyShiftA, tcell.KeyCtrlSpace, ui.KeySpace)
	if !c.App().Config.IsReadOnly() {
		c.bindDangerousKeys(aa)
	}
}

func (c *Context) bindDangerousKeys(aa *ui.KeyActions) {
	aa.Add(ui.KeyR, ui.NewKeyAction("Rename", c.renameCmd, true))
	aa.Add(tcell.KeyCtrlD, ui.NewKeyAction("Delete", c.deleteCmd, true))
}

func (c *Context) renameCmd(evt *tcell.EventKey) *tcell.EventKey {
	contextName := c.GetTable().GetSelectedItem()
	if contextName == "" {
		return evt
	}

	c.showRenameModal(contextName, c.renameDialogCallback)

	return nil
}

func (c *Context) deleteCmd(evt *tcell.EventKey) *tcell.EventKey {
	contextName := c.GetTable().GetSelectedItem()
	if contextName == "" {
		return evt
	}
Repository: derailed/k9s
File: internal/dao/pod.go (Go)

Code before:
	go func() {
		defer wg.Done()
		podOpts := opts.ToPodLogOptions()

		for range logRetryCount {
			// Check if we should stop retrying based on pod status
			if pod, ok := logger.(*Pod); ok && pod.shouldStopRetrying(opts.Path) {
				slog.Debug("Stopping log retry - pod is terminating or deleted",
					slogs.Container, opts.Info(),
				)
				return
			}

			req, err := logger.Logs(opts.Path, podOpts)
			if err != nil {
				slog.Error("Log request failed",
					slogs.Container, opts.Info(),
					slogs.Error, err,
				)
				select {
				case <-ctx.Done():
					return
				default:
					// Don't send request errors to user - they will be retried
				}
				time.Sleep(logRetryWait)
				continue
			}

			stream, e := req.Stream(ctx)
			if e != nil {
				slog.Error("Stream logs failed",
					slogs.Error, e,
					slogs.Container, opts.Info(),
				)
				select {
				case <-ctx.Done():
					return
				default:
					// Don't send stream errors to user - they will be retried
				}
				time.Sleep(logRetryWait)
				continue
			}

			// Process logs until completion
			result := readLogs(ctx, stream, out, opts)
			switch result {
			case streamEOF:
				slog.Debug("Log stream ended cleanly",
					slogs.Container, opts.Info(),

Review comment (suggestion):
I think we should leverage exponential backoff so we don't keep hammering on the api server. Check out backoff pkg usage in the repo

Code after:

	wg.Add(1)
	go func() {
		defer wg.Done()
		podOpts := opts.ToPodLogOptions()

		// Setup exponential backoff following project pattern
		bf := backoff.NewExponentialBackOff()
		bf.InitialInterval = logBackoffInitial
		bf.MaxElapsedTime = 0
		bf.MaxInterval = logBackoffMax / 2
		backoffCtx := backoff.WithContext(bf, ctx)
		delay := logBackoffInitial

		for range logRetryCount {
			// Check if we should stop retrying based on pod status
			if pod, ok := logger.(*Pod); ok && pod.shouldStopRetrying(opts.Path) {
				slog.Debug("Stopping log retry - pod is terminating or deleted",
					slogs.Container, opts.Info(),
				)
				return
			}

			req, err := logger.Logs(opts.Path, podOpts)
			if err != nil {
				slog.Error("Log request failed",
					slogs.Container, opts.Info(),
					slogs.Error, err,
				)
				select {
				case <-ctx.Done():
					return
				case <-time.After(delay):
					if delay = backoffCtx.NextBackOff(); delay == backoff.Stop {
						return
					}
				}
				continue
			}

			stream, e := req.Stream(ctx)
			if e != nil {
				slog.Error("Stream logs failed",
					slogs.Error, e,
					slogs.Container, opts.Info(),
				)
				select {
				case <-ctx.Done():
					return
				case <-time.After(delay):
					if delay = backoffCtx.NextBackOff(); delay == backoff.Stop {