// SPDX-License-Identifier: Apache-2.0 // Copyright Authors of K9s package dao import ( "bytes" "fmt" "regexp" "strings" "sync" "github.com/sahilm/fuzzy" ) var podPalette = []string{ "teal", "green", "purple", "lime", "blue", "yellow", "fushia", "aqua", } // LogItems represents a collection of log items. type LogItems struct { items []*LogItem podColors map[string]string mx sync.RWMutex } // NewLogItems returns a new instance. func NewLogItems() *LogItems { return &LogItems{ podColors: make(map[string]string), } } // Items returns the log items. func (l *LogItems) Items() []*LogItem { l.mx.RLock() defer l.mx.RUnlock() return l.items } // Len returns the items length. func (l *LogItems) Len() int { l.mx.RLock() defer l.mx.RUnlock() return len(l.items) } // Clear removes all items. func (l *LogItems) Clear() { l.mx.Lock() defer l.mx.Unlock() l.items = l.items[:0] for k := range l.podColors { delete(l.podColors, k) } } // Shift scrolls the lines by one. func (l *LogItems) Shift(i *LogItem) { l.mx.Lock() defer l.mx.Unlock() l.items = append(l.items[1:], i) } // Subset return a subset of logitems. func (l *LogItems) Subset(index int) *LogItems { l.mx.RLock() defer l.mx.RUnlock() return &LogItems{ items: l.items[index:], podColors: l.podColors, } } // Merge merges two logitems list. func (l *LogItems) Merge(n *LogItems) { l.mx.Lock() defer l.mx.Unlock() l.items = append(l.items, n.items...) for k, v := range n.podColors { l.podColors[k] = v } } // Add augments the items. func (l *LogItems) Add(ii ...*LogItem) { l.mx.Lock() defer l.mx.Unlock() l.items = append(l.items, ii...) } // Lines returns a collection of log lines. func (l *LogItems) Lines(index int, showTime bool, ll [][]byte) { l.mx.Lock() defer l.mx.Unlock() var colorIndex int for i, item := range l.items[index:] { id := item.ID() color, ok := l.podColors[id] if !ok { if colorIndex >= len(podPalette) { colorIndex = 0 } color = podPalette[colorIndex] l.podColors[id] = color colorIndex++ } bb := bytes.NewBuffer(make([]byte, 0, item.Size())) item.Render(color, showTime, bb) ll[i] = bb.Bytes() } } // StrLines returns a collection of log lines. func (l *LogItems) StrLines(index int, showTime bool) []string { l.mx.Lock() defer l.mx.Unlock() ll := make([]string, len(l.items[index:])) for i, item := range l.items[index:] { bb := bytes.NewBuffer(make([]byte, 0, item.Size())) item.Render("white", showTime, bb) ll[i] = bb.String() } return ll } // Render returns logs as a collection of strings. func (l *LogItems) Render(index int, showTime bool, ll [][]byte) { var colorIndex int for i, item := range l.items[index:] { id := item.ID() color, ok := l.podColors[id] if !ok { if colorIndex >= len(podPalette) { colorIndex = 0 } color = podPalette[colorIndex] l.podColors[id] = color colorIndex++ } bb := bytes.NewBuffer(make([]byte, 0, item.Size())) item.Render(color, showTime, bb) ll[i] = bb.Bytes() } } // DumpDebug for debugging. func (l *LogItems) DumpDebug(m string) { fmt.Println(m + strings.Repeat("-", 50)) for i, line := range l.items { fmt.Println(i, string(line.Bytes)) } } // Filter filters out log items based on given filter. func (l *LogItems) Filter(index int, q string, showTime bool) ([]int, [][]int, error) { if q == "" { return nil, nil, nil } if f, ok := HasFuzzySelector(q); ok { mm, ii := l.fuzzyFilter(index, f, showTime) return mm, ii, nil } matches, indices, err := l.filterLogs(index, q, showTime) if err != nil { return nil, nil, err } return matches, indices, nil } func (l *LogItems) fuzzyFilter(index int, q string, showTime bool) ([]int, [][]int) { q = strings.TrimSpace(q) matches, indices := make([]int, 0, len(l.items)), make([][]int, 0, 10) mm := fuzzy.Find(q, l.StrLines(index, showTime)) for _, m := range mm { matches = append(matches, m.Index) indices = append(indices, m.MatchedIndexes) } return matches, indices } func (l *LogItems) filterLogs(index int, q string, showTime bool) ([]int, [][]int, error) { var invert bool if IsInverseSelector(q) { invert = true q = q[1:] } rx, err := regexp.Compile(`(?i)` + q) if err != nil { return nil, nil, err } matches, indices := make([]int, 0, len(l.items)), make([][]int, 0, 10) ll := make([][]byte, len(l.items[index:])) l.Lines(index, showTime, ll) for i, line := range ll { locs := rx.FindIndex(line) if locs != nil && invert { continue } if locs == nil && !invert { continue } matches = append(matches, i) ii := make([]int, 0, 10) for i := 0; i < len(locs); i += 2 { for j := locs[i]; j < locs[i+1]; j++ { ii = append(ii, j) } } indices = append(indices, ii) } return matches, indices, nil }