k9s/internal/ui/app.go

271 lines
5.6 KiB
Go

package ui
import (
"context"
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/k8s"
"github.com/derailed/tview"
"github.com/gdamore/tcell"
"github.com/rs/zerolog/log"
)
// Igniter represents an initializable view.
type Igniter interface {
tview.Primitive
// Init initializes the view.
Init(ctx context.Context, ns string)
}
type (
keyHandler interface {
keyboard(evt *tcell.EventKey) *tcell.EventKey
}
// ActionsFunc augments Keybindings.
ActionsFunc func(KeyActions)
// Configurator represents an application configurations.
Configurator struct {
HasSkins bool
Config *config.Config
Styles *config.Styles
Bench *config.Bench
}
// App represents an application.
App struct {
*tview.Application
Configurator
actions KeyActions
pages *tview.Pages
content *tview.Pages
views map[string]tview.Primitive
cmdBuff *CmdBuff
}
)
// NewApp returns a new app.
func NewApp() *App {
s := App{
Application: tview.NewApplication(),
actions: make(KeyActions),
pages: tview.NewPages(),
content: tview.NewPages(),
cmdBuff: NewCmdBuff(':'),
}
s.RefreshStyles()
s.views = map[string]tview.Primitive{
"menu": NewMenuView(s.Styles),
"logo": NewLogoView(s.Styles),
"cmd": NewCmdView(s.Styles, '🐶'),
"crumbs": NewCrumbsView(s.Styles),
}
return &s
}
// Main returns main app frame.
func (a *App) Main() *tview.Pages {
return a.pages
}
// Frame returns main app content frame.
func (a *App) Frame() *tview.Pages {
return a.content
}
// Conn returns an api server connection.
func (a *App) Conn() k8s.Connection {
return a.Config.GetConnection()
}
// Init initializes the application.
func (a *App) Init() {
a.bindKeys()
a.SetInputCapture(a.keyboard)
a.cmdBuff.AddListener(a.Cmd())
a.SetRoot(a.pages, true)
}
func (a *App) bindKeys() {
a.actions = KeyActions{
KeyColon: NewKeyAction("Cmd", a.activateCmd, false),
tcell.KeyCtrlR: NewKeyAction("Redraw", a.redrawCmd, false),
tcell.KeyCtrlC: NewKeyAction("Quit", a.quitCmd, false),
tcell.KeyEscape: NewKeyAction("Escape", a.escapeCmd, false),
tcell.KeyBackspace2: NewKeyAction("Erase", a.eraseCmd, false),
tcell.KeyBackspace: NewKeyAction("Erase", a.eraseCmd, false),
tcell.KeyDelete: NewKeyAction("Erase", a.eraseCmd, false),
}
}
// BailOut exists the application.
func (a *App) BailOut() {
a.Stop()
}
// ResetCmd clear out user command.
func (a *App) ResetCmd() {
a.cmdBuff.Reset()
}
// ActivateCmd toggle command mode.
func (a *App) ActivateCmd(b bool) {
a.cmdBuff.SetActive(b)
}
// GetCmd retrieves user command.
func (a *App) GetCmd() string {
return a.cmdBuff.String()
}
// GetCmdBuff returns a cmd buffer.
func (a *App) GetCmdBuff() *CmdBuff {
return a.cmdBuff
}
// HasCmd check if cmd buffer is active and has a command.
func (a *App) HasCmd() bool {
return a.cmdBuff.IsActive() && !a.cmdBuff.Empty()
}
func (a *App) quitCmd(evt *tcell.EventKey) *tcell.EventKey {
if a.InCmdMode() {
return evt
}
a.BailOut()
return nil
}
// InCmdMode check if command mode is active.
func (a *App) InCmdMode() bool {
return a.Cmd().InCmdMode()
}
// GetActions returns a collection of actions.
func (a *App) GetActions() KeyActions {
return a.actions
}
// AddActions returns the application actions.
func (a *App) AddActions(aa KeyActions) {
for k, v := range aa {
a.actions[k] = v
}
}
// Views return the application root views.
func (a *App) Views() map[string]tview.Primitive {
return a.views
}
func (a *App) keyboard(evt *tcell.EventKey) *tcell.EventKey {
key := evt.Key()
if key == tcell.KeyRune {
if a.cmdBuff.IsActive() && evt.Modifiers() == tcell.ModNone {
a.cmdBuff.Add(evt.Rune())
return nil
}
key = asKey(evt)
}
if a, ok := a.actions[key]; ok {
log.Debug().Msgf(">> App handled key: %s", tcell.KeyNames[key])
return a.Action(evt)
}
return evt
}
func (a *App) activateCmd(evt *tcell.EventKey) *tcell.EventKey {
if a.InCmdMode() {
return evt
}
a.Flash().Info("Command mode activated.")
a.cmdBuff.SetActive(true)
a.cmdBuff.Clear()
return nil
}
// EraseCmd removes the last char from a command.
func (a *App) eraseCmd(evt *tcell.EventKey) *tcell.EventKey {
if a.cmdBuff.IsActive() {
a.cmdBuff.Delete()
return nil
}
return evt
}
// EscapeCmd dismiss cmd mode.
func (a *App) escapeCmd(evt *tcell.EventKey) *tcell.EventKey {
if a.cmdBuff.IsActive() {
a.cmdBuff.Reset()
}
return evt
}
// RedrawCmd forces a redraw.
func (a *App) redrawCmd(evt *tcell.EventKey) *tcell.EventKey {
a.Draw()
return evt
}
// ActiveView returns the currently active view.
func (a *App) ActiveView() Igniter {
return a.content.GetPrimitive("main").(Igniter)
}
// SetHints updates menu hints.
func (a *App) SetHints(h Hints) {
a.views["menu"].(*MenuView).HydrateMenu(h)
}
// StatusReset reset log back to normal.
func (a *App) StatusReset() {
a.Logo().Reset()
a.Draw()
}
// View Accessors...
// Crumbs return app crumbs.
func (a *App) Crumbs() *CrumbsView {
return a.views["crumbs"].(*CrumbsView)
}
// Logo return the app logo.
func (a *App) Logo() *LogoView {
return a.views["logo"].(*LogoView)
}
// Flash returns app flash.
func (a *App) Flash() *FlashView {
return a.views["flash"].(*FlashView)
}
// Cmd returns app cmd.
func (a *App) Cmd() *CmdView {
return a.views["cmd"].(*CmdView)
}
// Menu returns app menu.
func (a *App) Menu() *MenuView {
return a.views["menu"].(*MenuView)
}
// AsKey converts rune to keyboard key.,
func asKey(evt *tcell.EventKey) tcell.Key {
key := tcell.Key(evt.Rune())
if evt.Modifiers() == tcell.ModAlt {
key = tcell.Key(int16(evt.Rune()) * int16(evt.Modifiers()))
}
return key
}