246 lines
4.9 KiB
Go
246 lines
4.9 KiB
Go
// SPDX-License-Identifier: Apache-2.0
|
|
// Copyright Authors of K9s
|
|
|
|
package model
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
maxBuff = 10
|
|
|
|
keyEntryDelay = 100 * time.Millisecond
|
|
|
|
// CommandBuffer represents a command buffer.
|
|
CommandBuffer BufferKind = 1 << iota
|
|
// FilterBuffer represents a filter buffer.
|
|
FilterBuffer
|
|
)
|
|
|
|
type (
|
|
// BufferKind indicates a buffer type.
|
|
BufferKind int8
|
|
|
|
// BuffWatcher represents a command buffer listener.
|
|
BuffWatcher interface {
|
|
// BufferCompleted indicates input was accepted.
|
|
BufferCompleted(text, suggestion string)
|
|
|
|
// BufferChanged indicates the buffer was changed.
|
|
BufferChanged(text, suggestion string)
|
|
|
|
// BufferActive indicates the buff activity changed.
|
|
BufferActive(state bool, kind BufferKind)
|
|
}
|
|
)
|
|
|
|
// CmdBuff represents user command input.
|
|
type CmdBuff struct {
|
|
buff []rune
|
|
suggestion string
|
|
listeners map[BuffWatcher]struct{}
|
|
hotKey rune
|
|
kind BufferKind
|
|
active bool
|
|
cancel context.CancelFunc
|
|
mx sync.RWMutex
|
|
}
|
|
|
|
// NewCmdBuff returns a new command buffer.
|
|
func NewCmdBuff(key rune, kind BufferKind) *CmdBuff {
|
|
return &CmdBuff{
|
|
hotKey: key,
|
|
kind: kind,
|
|
buff: make([]rune, 0, maxBuff),
|
|
listeners: make(map[BuffWatcher]struct{}),
|
|
}
|
|
}
|
|
|
|
// InCmdMode checks if a command exists and the buffer is active.
|
|
func (c *CmdBuff) InCmdMode() bool {
|
|
c.mx.RLock()
|
|
defer c.mx.RUnlock()
|
|
|
|
if !c.active {
|
|
return false
|
|
}
|
|
|
|
return len(c.buff) > 0
|
|
}
|
|
|
|
// IsActive checks if command buffer is active.
|
|
func (c *CmdBuff) IsActive() bool {
|
|
c.mx.RLock()
|
|
defer c.mx.RUnlock()
|
|
|
|
return c.active
|
|
}
|
|
|
|
// SetActive toggles cmd buffer active state.
|
|
func (c *CmdBuff) SetActive(b bool) {
|
|
c.mx.Lock()
|
|
c.active = b
|
|
c.mx.Unlock()
|
|
|
|
c.fireActive(c.active)
|
|
}
|
|
|
|
// GetText returns the current text.
|
|
func (c *CmdBuff) GetText() string {
|
|
c.mx.RLock()
|
|
defer c.mx.RUnlock()
|
|
|
|
return string(c.buff)
|
|
}
|
|
|
|
// GetKind returns the buffer kind.
|
|
func (c *CmdBuff) GetKind() BufferKind {
|
|
c.mx.RLock()
|
|
defer c.mx.RUnlock()
|
|
|
|
return c.kind
|
|
}
|
|
|
|
// GetSuggestion returns the current suggestion.
|
|
func (c *CmdBuff) GetSuggestion() string {
|
|
c.mx.RLock()
|
|
defer c.mx.RUnlock()
|
|
|
|
return c.suggestion
|
|
}
|
|
|
|
func (c *CmdBuff) hasCancel() bool {
|
|
c.mx.RLock()
|
|
defer c.mx.RUnlock()
|
|
|
|
return c.cancel != nil
|
|
}
|
|
|
|
func (c *CmdBuff) setCancel(f context.CancelFunc) {
|
|
c.mx.Lock()
|
|
c.cancel = f
|
|
c.mx.Unlock()
|
|
}
|
|
|
|
func (c *CmdBuff) resetCancel() {
|
|
c.mx.Lock()
|
|
c.cancel = nil
|
|
c.mx.Unlock()
|
|
}
|
|
|
|
// SetText initializes the buffer with a command.
|
|
func (c *CmdBuff) SetText(text, suggestion string, wipe bool) {
|
|
c.mx.Lock()
|
|
if wipe {
|
|
c.buff, c.suggestion = []rune(text), suggestion
|
|
} else {
|
|
c.buff, c.suggestion = append(c.buff, []rune(text)...), suggestion
|
|
}
|
|
c.mx.Unlock()
|
|
c.fireBufferCompleted(c.GetText(), c.GetSuggestion())
|
|
}
|
|
|
|
// Add adds a new character to the buffer.
|
|
func (c *CmdBuff) Add(r rune) {
|
|
c.mx.Lock()
|
|
c.buff = append(c.buff, r)
|
|
c.mx.Unlock()
|
|
c.fireBufferChanged(c.GetText(), c.GetSuggestion())
|
|
if c.hasCancel() {
|
|
return
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), keyEntryDelay)
|
|
c.setCancel(cancel)
|
|
|
|
go func() {
|
|
<-ctx.Done()
|
|
c.fireBufferCompleted(c.GetText(), c.GetSuggestion())
|
|
c.resetCancel()
|
|
}()
|
|
}
|
|
|
|
// Delete removes the last character from the buffer.
|
|
func (c *CmdBuff) Delete() {
|
|
if c.Empty() {
|
|
return
|
|
}
|
|
c.SetText(string(c.buff[:len(c.buff)-1]), "", true)
|
|
c.fireBufferChanged(c.GetText(), c.GetSuggestion())
|
|
if c.hasCancel() {
|
|
return
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 800*time.Millisecond)
|
|
c.setCancel(cancel)
|
|
|
|
go func() {
|
|
<-ctx.Done()
|
|
c.fireBufferCompleted(c.GetText(), c.GetSuggestion())
|
|
c.resetCancel()
|
|
}()
|
|
}
|
|
|
|
// ClearText clears out command buffer.
|
|
func (c *CmdBuff) ClearText(fire bool) {
|
|
c.mx.Lock()
|
|
c.buff, c.suggestion = c.buff[:0], ""
|
|
c.mx.Unlock()
|
|
|
|
if fire {
|
|
c.fireBufferCompleted(c.GetText(), c.GetSuggestion())
|
|
}
|
|
}
|
|
|
|
// Reset clears out the command buffer and deactivates it.
|
|
func (c *CmdBuff) Reset() {
|
|
c.ClearText(true)
|
|
c.SetActive(false)
|
|
c.fireBufferCompleted(c.GetText(), c.GetSuggestion())
|
|
}
|
|
|
|
// Empty returns true if no cmd, false otherwise.
|
|
func (c *CmdBuff) Empty() bool {
|
|
c.mx.RLock()
|
|
defer c.mx.RUnlock()
|
|
|
|
return len(c.buff) == 0
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Event Listeners...
|
|
|
|
// AddListener registers a cmd buffer listener.
|
|
func (c *CmdBuff) AddListener(w BuffWatcher) {
|
|
c.mx.Lock()
|
|
c.listeners[w] = struct{}{}
|
|
c.mx.Unlock()
|
|
}
|
|
|
|
// RemoveListener removes a listener.
|
|
func (c *CmdBuff) RemoveListener(l BuffWatcher) {
|
|
c.mx.Lock()
|
|
delete(c.listeners, l)
|
|
c.mx.Unlock()
|
|
}
|
|
|
|
func (c *CmdBuff) fireBufferCompleted(t, s string) {
|
|
for l := range c.listeners {
|
|
l.BufferCompleted(t, s)
|
|
}
|
|
}
|
|
|
|
func (c *CmdBuff) fireBufferChanged(t, s string) {
|
|
for l := range c.listeners {
|
|
l.BufferChanged(t, s)
|
|
}
|
|
}
|
|
|
|
func (c *CmdBuff) fireActive(b bool) {
|
|
for l := range c.listeners {
|
|
l.BufferActive(b, c.GetKind())
|
|
}
|
|
}
|