parent
5b707c656e
commit
67c0702bf2
|
|
@ -143,7 +143,7 @@ func (a *Aliases) Define(gvr string, aliases ...string) {
|
|||
func (a *Aliases) LoadAliases(path string) error {
|
||||
f, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msgf("No custom aliases found")
|
||||
log.Debug().Err(err).Msgf("No custom aliases found")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,120 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/sahilm/fuzzy"
|
||||
)
|
||||
|
||||
// TextListener represents a text model listener.
|
||||
type TextListener interface {
|
||||
// TextChanged notifies the model changed.
|
||||
TextChanged([]string)
|
||||
|
||||
// TextFiltered notifies when the filter changed.
|
||||
TextFiltered([]string, fuzzy.Matches)
|
||||
}
|
||||
|
||||
// Text represents a text model.
|
||||
type Text struct {
|
||||
lines []string
|
||||
listeners []TextListener
|
||||
query string
|
||||
}
|
||||
|
||||
// NewText returns a new model.
|
||||
func NewText() *Text {
|
||||
return &Text{}
|
||||
}
|
||||
|
||||
// Peek returns the current model state.
|
||||
func (t *Text) Peek() []string {
|
||||
return t.lines
|
||||
}
|
||||
|
||||
// ClearFilter clear out filter.
|
||||
func (t *Text) ClearFilter() {
|
||||
t.query = ""
|
||||
t.filterChanged(t.lines)
|
||||
}
|
||||
|
||||
// Filter filters out the text.
|
||||
func (t *Text) Filter(q string) {
|
||||
t.query = q
|
||||
t.filterChanged(t.lines)
|
||||
}
|
||||
|
||||
// SetText sets the current model content.
|
||||
func (t *Text) SetText(buff string) {
|
||||
t.lines = strings.Split(buff, "\n")
|
||||
t.fireTextChanged(t.lines)
|
||||
}
|
||||
|
||||
// AddListener adds a new model listener.
|
||||
func (t *Text) AddListener(listener TextListener) {
|
||||
t.listeners = append(t.listeners, listener)
|
||||
}
|
||||
|
||||
// RemoveListener delete a listener from the list.
|
||||
func (t *Text) RemoveListener(listener TextListener) {
|
||||
victim := -1
|
||||
for i, lis := range t.listeners {
|
||||
if lis == listener {
|
||||
victim = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if victim >= 0 {
|
||||
t.listeners = append(t.listeners[:victim], t.listeners[victim+1:]...)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Text) filterChanged(lines []string) {
|
||||
t.fireTextFiltered(lines, t.filter(t.query, lines))
|
||||
}
|
||||
|
||||
func (t *Text) fireTextChanged(lines []string) {
|
||||
for _, lis := range t.listeners {
|
||||
lis.TextChanged(lines)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Text) fireTextFiltered(lines []string, matches fuzzy.Matches) {
|
||||
for _, lis := range t.listeners {
|
||||
lis.TextFiltered(lines, matches)
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Helpers...
|
||||
|
||||
func (t *Text) filter(q string, lines []string) fuzzy.Matches {
|
||||
if q == "" {
|
||||
return nil
|
||||
}
|
||||
if isFuzzySelector(q) {
|
||||
return t.fuzzyFilter(strings.TrimSpace(q[2:]), lines)
|
||||
}
|
||||
return t.rxFilter(q, lines)
|
||||
}
|
||||
|
||||
func (*Text) fuzzyFilter(q string, lines []string) fuzzy.Matches {
|
||||
return fuzzy.Find(q, lines)
|
||||
}
|
||||
|
||||
func (*Text) rxFilter(q string, lines []string) fuzzy.Matches {
|
||||
rx, err := regexp.Compile(`(?i)` + q)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
matches := make(fuzzy.Matches, 0, len(lines))
|
||||
for i, l := range lines {
|
||||
if loc := rx.FindStringIndex(l); len(loc) == 2 {
|
||||
matches = append(matches, fuzzy.Match{Str: q, Index: i, MatchedIndexes: loc})
|
||||
}
|
||||
}
|
||||
|
||||
return matches
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
package model_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/derailed/k9s/internal/model"
|
||||
"github.com/sahilm/fuzzy"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewText(t *testing.T) {
|
||||
m := model.NewText()
|
||||
|
||||
lis := textLis{}
|
||||
m.AddListener(&lis)
|
||||
|
||||
m.SetText("Hello World\nBumbleBeeTuna")
|
||||
|
||||
assert.Equal(t, 1, lis.changed)
|
||||
assert.Equal(t, 2, lis.lines)
|
||||
assert.Equal(t, 0, lis.filtered)
|
||||
assert.Equal(t, 0, lis.matches)
|
||||
}
|
||||
|
||||
func TestTextFilterRXMatch(t *testing.T) {
|
||||
m := model.NewText()
|
||||
|
||||
lis := textLis{}
|
||||
m.AddListener(&lis)
|
||||
|
||||
m.SetText("Hello World\nBumbleBeeTuna")
|
||||
m.Filter("world")
|
||||
|
||||
assert.Equal(t, 1, lis.changed)
|
||||
assert.Equal(t, 2, lis.lines)
|
||||
assert.Equal(t, 1, lis.filtered)
|
||||
assert.Equal(t, 1, lis.matches)
|
||||
assert.Equal(t, 6, lis.index)
|
||||
}
|
||||
|
||||
func TestTextFilterFuzzyMatch(t *testing.T) {
|
||||
m := model.NewText()
|
||||
|
||||
lis := textLis{}
|
||||
m.AddListener(&lis)
|
||||
|
||||
m.SetText("Hello World\nBumbleBeeTuna")
|
||||
m.Filter("-f world")
|
||||
|
||||
assert.Equal(t, 1, lis.changed)
|
||||
assert.Equal(t, 2, lis.lines)
|
||||
assert.Equal(t, 1, lis.filtered)
|
||||
assert.Equal(t, 1, lis.matches)
|
||||
assert.Equal(t, 6, lis.index)
|
||||
}
|
||||
|
||||
func TestTextFilterNoMatch(t *testing.T) {
|
||||
m := model.NewText()
|
||||
|
||||
lis := textLis{}
|
||||
m.AddListener(&lis)
|
||||
|
||||
m.SetText("Hello World\nBumbleBeeTuna")
|
||||
m.Filter("blee")
|
||||
|
||||
assert.Equal(t, 1, lis.changed)
|
||||
assert.Equal(t, 2, lis.lines)
|
||||
assert.Equal(t, 1, lis.filtered)
|
||||
assert.Equal(t, 0, lis.matches)
|
||||
assert.Equal(t, 0, lis.index)
|
||||
}
|
||||
|
||||
// Helpers...
|
||||
|
||||
type textLis struct {
|
||||
changed, filtered, matches, lines, index int
|
||||
}
|
||||
|
||||
func (l *textLis) TextChanged(ll []string) {
|
||||
l.lines = len(ll)
|
||||
l.changed++
|
||||
}
|
||||
|
||||
func (l *textLis) TextFiltered(ll []string, mm fuzzy.Matches) {
|
||||
l.matches = len(mm)
|
||||
l.filtered++
|
||||
if len(mm) > 0 && len(mm[0].MatchedIndexes) > 0 {
|
||||
l.index = mm[0].MatchedIndexes[0]
|
||||
}
|
||||
}
|
||||
|
|
@ -175,7 +175,7 @@ func (a *App) keyboard(evt *tcell.EventKey) *tcell.EventKey {
|
|||
a.cmdBuff.Add(evt.Rune())
|
||||
return nil
|
||||
}
|
||||
key = asKey(evt)
|
||||
key = AsKey(evt)
|
||||
}
|
||||
|
||||
if a, ok := a.actions[key]; ok {
|
||||
|
|
@ -258,7 +258,7 @@ func (a *App) Menu() *Menu {
|
|||
// Helpers...
|
||||
|
||||
// AsKey converts rune to keyboard key.,
|
||||
func asKey(evt *tcell.EventKey) tcell.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()))
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ func (t *Table) keyboard(evt *tcell.EventKey) *tcell.EventKey {
|
|||
if t.filterInput(evt.Rune()) {
|
||||
return nil
|
||||
}
|
||||
key = asKey(evt)
|
||||
key = AsKey(evt)
|
||||
}
|
||||
|
||||
if a, ok := t.actions[key]; ok {
|
||||
|
|
@ -370,17 +370,17 @@ func (t *Table) styleTitle() string {
|
|||
}
|
||||
}
|
||||
|
||||
buff := t.cmdBuff.String()
|
||||
var title string
|
||||
if ns == client.ClusterScope {
|
||||
title = SkinTitle(fmt.Sprintf(TitleFmt, base, rc), t.styles.Frame())
|
||||
} else {
|
||||
title = SkinTitle(fmt.Sprintf(NSTitleFmt, base, ns, rc), t.styles.Frame())
|
||||
}
|
||||
|
||||
buff := t.cmdBuff.String()
|
||||
if buff == "" {
|
||||
return title
|
||||
}
|
||||
|
||||
if IsLabelSelector(buff) {
|
||||
buff = TrimLabelSelector(buff)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ func (b *Benchmark) viewBench(app *App, model ui.Tabular, gvr, path string) {
|
|||
return
|
||||
}
|
||||
|
||||
details := NewDetails(b.App(), "Benchmark", fileToSubject(path)).Update(data)
|
||||
details := NewDetails(b.App(), "Benchmark", fileToSubject(path), false).Update(data)
|
||||
if err := app.inject(details); err != nil {
|
||||
app.Flash().Err(err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -175,7 +175,7 @@ func (b *Browser) viewCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
return nil
|
||||
}
|
||||
|
||||
details := NewDetails(b.app, "YAML", path).Update(raw)
|
||||
details := NewDetails(b.app, "YAML", path, true).Update(raw)
|
||||
if err := b.App().inject(details); err != nil {
|
||||
b.App().Flash().Err(err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package view
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/atotto/clipboard"
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
|
|
@ -10,6 +11,7 @@ import (
|
|||
"github.com/derailed/k9s/internal/ui"
|
||||
"github.com/derailed/tview"
|
||||
"github.com/gdamore/tcell"
|
||||
"github.com/sahilm/fuzzy"
|
||||
)
|
||||
|
||||
const detailsTitleFmt = "[fg:bg:b] %s([hilite:bg:b]%s[fg:bg:-])[fg:bg:-] "
|
||||
|
|
@ -18,20 +20,26 @@ const detailsTitleFmt = "[fg:bg:b] %s([hilite:bg:b]%s[fg:bg:-])[fg:bg:-] "
|
|||
type Details struct {
|
||||
*tview.TextView
|
||||
|
||||
actions ui.KeyActions
|
||||
app *App
|
||||
title, subject string
|
||||
buff string
|
||||
actions ui.KeyActions
|
||||
app *App
|
||||
title, subject string
|
||||
cmdBuff *ui.CmdBuff
|
||||
model *model.Text
|
||||
currentRegion, maxRegions int
|
||||
searchable bool
|
||||
}
|
||||
|
||||
// NewDetails returns a details viewer.
|
||||
func NewDetails(app *App, title, subject string) *Details {
|
||||
func NewDetails(app *App, title, subject string, searchable bool) *Details {
|
||||
d := Details{
|
||||
TextView: tview.NewTextView(),
|
||||
app: app,
|
||||
title: title,
|
||||
subject: subject,
|
||||
actions: make(ui.KeyActions),
|
||||
TextView: tview.NewTextView(),
|
||||
app: app,
|
||||
title: title,
|
||||
subject: subject,
|
||||
actions: make(ui.KeyActions),
|
||||
cmdBuff: ui.NewCmdBuff('/', ui.FilterBuff),
|
||||
model: model.NewText(),
|
||||
searchable: searchable,
|
||||
}
|
||||
|
||||
return &d
|
||||
|
|
@ -42,37 +50,123 @@ func (d *Details) Init(_ context.Context) error {
|
|||
if d.title != "" {
|
||||
d.SetBorder(true)
|
||||
}
|
||||
d.SetScrollable(true)
|
||||
d.SetWrap(true)
|
||||
d.SetScrollable(true).SetWrap(true).SetRegions(true)
|
||||
d.SetDynamicColors(true)
|
||||
d.SetHighlightColor(tcell.ColorOrange)
|
||||
d.SetTitleColor(tcell.ColorAqua)
|
||||
d.SetInputCapture(d.keyboard)
|
||||
d.bindKeys()
|
||||
d.SetChangedFunc(func() {
|
||||
d.app.Draw()
|
||||
})
|
||||
d.updateTitle()
|
||||
|
||||
d.app.Styles.AddListener(d)
|
||||
d.StylesChanged(d.app.Styles)
|
||||
|
||||
d.cmdBuff.AddListener(d.app.Cmd())
|
||||
d.cmdBuff.AddListener(d)
|
||||
|
||||
d.bindKeys()
|
||||
d.SetInputCapture(d.keyboard)
|
||||
d.model.AddListener(d)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Details) TextChanged(lines []string) {
|
||||
d.SetText(colorizeYAML(d.app.Styles.Views().Yaml, strings.Join(lines, "\n")))
|
||||
d.ScrollToBeginning()
|
||||
}
|
||||
|
||||
func (d *Details) TextFiltered(lines []string, matches fuzzy.Matches) {
|
||||
d.currentRegion, d.maxRegions = 0, 0
|
||||
|
||||
ll := make([]string, len(lines))
|
||||
copy(ll, lines)
|
||||
for _, m := range matches {
|
||||
loc, line := m.MatchedIndexes, ll[m.Index]
|
||||
ll[m.Index] = line[:loc[0]] + fmt.Sprintf(`<<<"search_%d">>>`, d.maxRegions) + line[loc[0]:loc[1]] + `<<<"">>>` + line[loc[1]:]
|
||||
d.maxRegions++
|
||||
}
|
||||
|
||||
d.SetText(colorizeYAML(d.app.Styles.Views().Yaml, strings.Join(ll, "\n")))
|
||||
d.Highlight()
|
||||
if d.maxRegions > 0 {
|
||||
d.Highlight("search_0")
|
||||
d.ScrollToHighlight()
|
||||
}
|
||||
}
|
||||
|
||||
// BufferChanged indicates the buffer was changed.
|
||||
func (d *Details) BufferChanged(s string) {}
|
||||
|
||||
// BufferActive indicates the buff activity changed.
|
||||
func (d *Details) BufferActive(state bool, k ui.BufferKind) {
|
||||
d.app.BufferActive(state, k)
|
||||
}
|
||||
|
||||
func (d *Details) bindKeys() {
|
||||
d.actions.Set(ui.KeyActions{
|
||||
tcell.KeyEnter: ui.NewSharedKeyAction("Filter", d.filterCmd, false),
|
||||
tcell.KeyEscape: ui.NewKeyAction("Back", d.resetCmd, false),
|
||||
tcell.KeyCtrlS: ui.NewKeyAction("Save", d.saveCmd, false),
|
||||
ui.KeyC: ui.NewKeyAction("Copy", d.cpCmd, true),
|
||||
ui.KeyN: ui.NewKeyAction("Next Match", d.nextCmd, true),
|
||||
ui.KeyShiftN: ui.NewKeyAction("Prev Match", d.prevCmd, true),
|
||||
ui.KeySlash: ui.NewSharedKeyAction("Filter Mode", d.activateCmd, false),
|
||||
tcell.KeyCtrlU: ui.NewSharedKeyAction("Clear Filter", d.clearCmd, false),
|
||||
tcell.KeyBackspace2: ui.NewSharedKeyAction("Erase", d.eraseCmd, false),
|
||||
tcell.KeyBackspace: ui.NewSharedKeyAction("Erase", d.eraseCmd, false),
|
||||
tcell.KeyDelete: ui.NewSharedKeyAction("Erase", d.eraseCmd, false),
|
||||
})
|
||||
|
||||
if !d.searchable {
|
||||
d.actions.Delete(ui.KeyN, ui.KeyShiftN)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Details) keyboard(evt *tcell.EventKey) *tcell.EventKey {
|
||||
key := evt.Key()
|
||||
if key == tcell.KeyUp || key == tcell.KeyDown {
|
||||
return evt
|
||||
}
|
||||
|
||||
if key == tcell.KeyRune {
|
||||
if d.filterInput(evt.Rune()) {
|
||||
return nil
|
||||
}
|
||||
key = ui.AsKey(evt)
|
||||
}
|
||||
|
||||
if a, ok := d.actions[key]; ok {
|
||||
return a.Action(evt)
|
||||
}
|
||||
|
||||
return evt
|
||||
}
|
||||
|
||||
func (d *Details) filterInput(r rune) bool {
|
||||
if !d.cmdBuff.IsActive() {
|
||||
return false
|
||||
}
|
||||
d.cmdBuff.Add(r)
|
||||
d.updateTitle()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// StylesChanged notifies the skin changed.
|
||||
func (d *Details) StylesChanged(s *config.Styles) {
|
||||
d.SetBackgroundColor(d.app.Styles.BgColor())
|
||||
d.SetTextColor(d.app.Styles.FgColor())
|
||||
d.SetBorderFocusColor(config.AsColor(d.app.Styles.Frame().Border.FocusColor))
|
||||
|
||||
d.Update(d.buff)
|
||||
d.TextChanged(d.model.Peek())
|
||||
}
|
||||
|
||||
// Update updates the view content.
|
||||
func (d *Details) Update(buff string) *Details {
|
||||
d.buff = buff
|
||||
d.SetText(colorizeYAML(d.app.Styles.Views().Yaml, buff))
|
||||
d.ScrollToBeginning()
|
||||
d.model.SetText(buff)
|
||||
|
||||
return d
|
||||
}
|
||||
|
|
@ -108,24 +202,89 @@ func (d *Details) ExtraHints() map[string]string {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (d *Details) bindKeys() {
|
||||
d.actions.Set(ui.KeyActions{
|
||||
tcell.KeyEscape: ui.NewKeyAction("Back", d.app.PrevCmd, false),
|
||||
tcell.KeyCtrlS: ui.NewKeyAction("Save", d.saveCmd, false),
|
||||
ui.KeyC: ui.NewKeyAction("Copy", d.cpCmd, true),
|
||||
})
|
||||
func (d *Details) nextCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if d.cmdBuff.Empty() {
|
||||
return evt
|
||||
}
|
||||
|
||||
d.currentRegion++
|
||||
if d.currentRegion >= d.maxRegions {
|
||||
d.currentRegion = 0
|
||||
}
|
||||
d.Highlight(fmt.Sprintf("search_%d", d.currentRegion))
|
||||
d.ScrollToHighlight()
|
||||
d.updateTitle()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Details) keyboard(evt *tcell.EventKey) *tcell.EventKey {
|
||||
key := evt.Key()
|
||||
if key == tcell.KeyRune {
|
||||
key = tcell.Key(evt.Rune())
|
||||
}
|
||||
if a, ok := d.actions[key]; ok {
|
||||
return a.Action(evt)
|
||||
func (d *Details) prevCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if d.cmdBuff.Empty() {
|
||||
return evt
|
||||
}
|
||||
|
||||
return evt
|
||||
d.currentRegion--
|
||||
if d.currentRegion < 0 {
|
||||
d.currentRegion = d.maxRegions - 1
|
||||
}
|
||||
d.Highlight(fmt.Sprintf("search_%d", d.currentRegion))
|
||||
d.ScrollToHighlight()
|
||||
d.updateTitle()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Details) filterCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
d.model.Filter(d.cmdBuff.String())
|
||||
d.cmdBuff.SetActive(false)
|
||||
d.updateTitle()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Details) activateCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if d.app.InCmdMode() {
|
||||
return evt
|
||||
}
|
||||
d.app.Flash().Info("Filter mode activated.")
|
||||
d.cmdBuff.SetActive(true)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Details) clearCmd(*tcell.EventKey) *tcell.EventKey {
|
||||
if !d.app.InCmdMode() {
|
||||
return nil
|
||||
}
|
||||
d.cmdBuff.Clear()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Details) eraseCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if !d.cmdBuff.IsActive() {
|
||||
return nil
|
||||
}
|
||||
d.cmdBuff.Delete()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Details) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if !d.cmdBuff.InCmdMode() {
|
||||
d.cmdBuff.Reset()
|
||||
return d.app.PrevCmd(evt)
|
||||
}
|
||||
|
||||
if d.cmdBuff.String() != "" {
|
||||
d.model.ClearFilter()
|
||||
}
|
||||
d.app.Flash().Info("Clearing filter...")
|
||||
d.cmdBuff.SetActive(false)
|
||||
d.cmdBuff.Reset()
|
||||
d.updateTitle()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Details) saveCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
|
|
@ -149,6 +308,19 @@ func (d *Details) updateTitle() {
|
|||
if d.title == "" {
|
||||
return
|
||||
}
|
||||
title := ui.SkinTitle(fmt.Sprintf(detailsTitleFmt, d.title, d.subject), d.app.Styles.Frame())
|
||||
d.SetTitle(title)
|
||||
fmat := fmt.Sprintf(detailsTitleFmt, d.title, d.subject)
|
||||
|
||||
buff := d.cmdBuff.String()
|
||||
if buff == "" {
|
||||
d.SetTitle(ui.SkinTitle(fmat, d.app.Styles.Frame()))
|
||||
return
|
||||
}
|
||||
|
||||
search := d.cmdBuff.String()
|
||||
if d.maxRegions != 0 {
|
||||
search += fmt.Sprintf("[%d:%d]", d.currentRegion+1, d.maxRegions)
|
||||
}
|
||||
fmat += fmt.Sprintf(ui.SearchFmt, search)
|
||||
|
||||
d.SetTitle(ui.SkinTitle(fmat, d.app.Styles.Frame()))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ import (
|
|||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const shellCheck = `command -v bash >/dev/null && exec bash || exec sh`
|
||||
|
||||
func runK(clear bool, app *App, args ...string) bool {
|
||||
bin, err := exec.LookPath("kubectl")
|
||||
if err != nil {
|
||||
|
|
@ -73,7 +75,7 @@ func execute(clear bool, bin string, bg bool, args ...string) error {
|
|||
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
|
||||
err = cmd.Run()
|
||||
}
|
||||
log.Debug().Msgf("Command returned error?? %v", err)
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return errors.New("canceled by operator")
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ func describeResource(app *App, model ui.Tabular, gvr, path string) {
|
|||
return
|
||||
}
|
||||
|
||||
details := NewDetails(app, "Describe", path).Update(yaml)
|
||||
details := NewDetails(app, "Describe", path, true).Update(yaml)
|
||||
if err := app.inject(details); err != nil {
|
||||
app.Flash().Err(err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ func (l *Log) Init(ctx context.Context) (err error) {
|
|||
l.indicator = NewLogIndicator(l.app.Config, l.app.Styles)
|
||||
l.AddItem(l.indicator, 1, 1, false)
|
||||
|
||||
l.logs = NewDetails(l.app, "", "")
|
||||
l.logs = NewDetails(l.app, "", "", false)
|
||||
if err = l.logs.Init(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -173,7 +173,7 @@ func (l *Log) bindKeys() {
|
|||
ui.KeyW: ui.NewKeyAction("Toggle Wrap", l.textWrapCmd, true),
|
||||
tcell.KeyCtrlS: ui.NewKeyAction("Save", l.SaveCmd, true),
|
||||
ui.KeySlash: ui.NewSharedKeyAction("Filter Mode", l.activateCmd, false),
|
||||
tcell.KeyCtrlU: ui.NewSharedKeyAction("Clear Filter", l.clearCmd, false),
|
||||
tcell.KeyCtrlU: ui.NewSharedKeyAction("Clear Filter", l.resetCmd, false),
|
||||
tcell.KeyBackspace2: ui.NewSharedKeyAction("Erase", l.eraseCmd, false),
|
||||
tcell.KeyBackspace: ui.NewSharedKeyAction("Erase", l.eraseCmd, false),
|
||||
tcell.KeyDelete: ui.NewSharedKeyAction("Erase", l.eraseCmd, false),
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ func (n *Node) viewCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
return nil
|
||||
}
|
||||
|
||||
details := NewDetails(n.App(), "YAML", sel).Update(raw)
|
||||
details := NewDetails(n.App(), "YAML", sel, true).Update(raw)
|
||||
if err := n.App().inject(details); err != nil {
|
||||
n.App().Flash().Err(err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/derailed/k9s/internal"
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/dao"
|
||||
|
|
@ -18,8 +19,6 @@ import (
|
|||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
const shellCheck = "command -v bash >/dev/null && exec bash || exec sh"
|
||||
|
||||
// Pod represents a pod viewer.
|
||||
type Pod struct {
|
||||
ResourceViewer
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ func (s *Secret) decodeCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
return nil
|
||||
}
|
||||
|
||||
details := NewDetails(s.App(), "Secret Decoder", path).Update(string(raw))
|
||||
details := NewDetails(s.App(), "Secret Decoder", path, false).Update(string(raw))
|
||||
if err := s.App().inject(details); err != nil {
|
||||
s.App().Flash().Err(err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -269,7 +269,7 @@ func (x *Xray) viewCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
return nil
|
||||
}
|
||||
|
||||
details := NewDetails(x.app, "YAML", ref.Path).Update(raw)
|
||||
details := NewDetails(x.app, "YAML", ref.Path, true).Update(raw)
|
||||
if err := x.app.inject(details); err != nil {
|
||||
x.app.Flash().Err(err)
|
||||
}
|
||||
|
|
@ -321,7 +321,7 @@ func (x *Xray) describe(gvr, path string) {
|
|||
return
|
||||
}
|
||||
|
||||
details := NewDetails(x.app, "Describe", path).Update(yaml)
|
||||
details := NewDetails(x.app, "Describe", path, true).Update(yaml)
|
||||
if err := x.app.inject(details); err != nil {
|
||||
x.app.Flash().Err(err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,9 +8,8 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/derailed/tview"
|
||||
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/derailed/tview"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
|
|
@ -26,6 +25,7 @@ const (
|
|||
)
|
||||
|
||||
func colorizeYAML(style config.Yaml, raw string) string {
|
||||
// lines := strings.Split(raw, "\n")
|
||||
lines := strings.Split(tview.Escape(raw), "\n")
|
||||
|
||||
fullFmt := strings.Replace(yamlFullFmt, "[key", "["+style.KeyColor, 1)
|
||||
|
|
@ -41,22 +41,26 @@ func colorizeYAML(style config.Yaml, raw string) string {
|
|||
for _, l := range lines {
|
||||
res := keyValRX.FindStringSubmatch(l)
|
||||
if len(res) == 4 {
|
||||
buff = append(buff, fmt.Sprintf(fullFmt, res[1], res[2], res[3]))
|
||||
buff = append(buff, enableRegion(fmt.Sprintf(fullFmt, res[1], res[2], res[3])))
|
||||
continue
|
||||
}
|
||||
|
||||
res = keyRX.FindStringSubmatch(l)
|
||||
if len(res) == 3 {
|
||||
buff = append(buff, fmt.Sprintf(keyFmt, res[1], res[2]))
|
||||
buff = append(buff, enableRegion(fmt.Sprintf(keyFmt, res[1], res[2])))
|
||||
continue
|
||||
}
|
||||
|
||||
buff = append(buff, fmt.Sprintf(valFmt, l))
|
||||
buff = append(buff, enableRegion(fmt.Sprintf(valFmt, l)))
|
||||
}
|
||||
|
||||
return strings.Join(buff, "\n")
|
||||
}
|
||||
|
||||
func enableRegion(str string) string {
|
||||
return strings.ReplaceAll(strings.ReplaceAll(str, "<<<", "["), ">>>", "]")
|
||||
}
|
||||
|
||||
func saveYAML(cluster, name, data string) (string, error) {
|
||||
dir := filepath.Join(config.K9sDumpDir, cluster)
|
||||
if err := ensureDir(dir); err != nil {
|
||||
|
|
|
|||
|
|
@ -13,15 +13,21 @@ func TestYaml(t *testing.T) {
|
|||
}{
|
||||
{
|
||||
`api: fred
|
||||
version: v1`,
|
||||
version: v1`,
|
||||
`[steelblue::b]api[white::-]: [papayawhip::]fred
|
||||
[steelblue::b]version[white::-]: [papayawhip::]v1`,
|
||||
[steelblue::b]version[white::-]: [papayawhip::]v1`,
|
||||
},
|
||||
{
|
||||
`api: <<<"search_0">>>fred<<<"">>>
|
||||
version: v1`,
|
||||
`[steelblue::b]api[white::-]: [papayawhip::]["search_0"]fred[""]
|
||||
[steelblue::b]version[white::-]: [papayawhip::]v1`,
|
||||
},
|
||||
{
|
||||
`api:
|
||||
version: v1`,
|
||||
version: v1`,
|
||||
`[steelblue::b]api[white::-]:
|
||||
[steelblue::b]version[white::-]: [papayawhip::]v1`,
|
||||
[steelblue::b]version[white::-]: [papayawhip::]v1`,
|
||||
},
|
||||
{
|
||||
" fred:blee",
|
||||
|
|
|
|||
Loading…
Reference in New Issue