Skip to content

Commit

Permalink
Showing 6 changed files with 224 additions and 13 deletions.
20 changes: 20 additions & 0 deletions src/tui/actions.go
Original file line number Diff line number Diff line change
@@ -23,6 +23,10 @@ const (
ActionProcessRestart = ActionName("process_restart")
ActionProcessScreen = ActionName("process_screen")
ActionQuit = ActionName("quit")
ActionLogFind = ActionName("find")
ActionLogFindNext = ActionName("find_next")
ActionLogFindPrev = ActionName("find_prev")
ActionLogFindExit = ActionName("find_exit")
)

var defaultShortcuts = map[ActionName]tcell.Key{
@@ -36,6 +40,10 @@ var defaultShortcuts = map[ActionName]tcell.Key{
ActionProcessRestart: tcell.KeyCtrlR,
ActionProcessScreen: tcell.KeyF8,
ActionQuit: tcell.KeyF10,
ActionLogFind: tcell.KeyCtrlF,
ActionLogFindNext: tcell.KeyCtrlN,
ActionLogFindPrev: tcell.KeyCtrlP,
ActionLogFindExit: tcell.KeyEsc,
}

type ShortCuts struct {
@@ -191,6 +199,18 @@ func getDefaultActions() ShortCuts {
ActionQuit: {
Description: "Quit",
},
ActionLogFind: {
Description: "Find",
},
ActionLogFindNext: {
Description: "Next",
},
ActionLogFindPrev: {
Description: "Previous",
},
ActionLogFindExit: {
Description: "Exit Search",
},
},
}
for k, v := range sc.ShortCutKeys {
14 changes: 13 additions & 1 deletion src/tui/log-operations.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package tui

import (
"fmt"
"github.com/f1bonacc1/glippy"
"github.com/f1bonacc1/process-compose/src/app"
"github.com/f1bonacc1/process-compose/src/types"
@@ -12,9 +13,11 @@ func (pv *pcView) toggleLogSelection() {
name := pv.getSelectedProcName()
pv.logSelect = !pv.logSelect
if pv.logSelect {
pv.logsTextArea.SetText(pv.logsText.GetText(true), true).
row, col := pv.logsText.GetScrollOffset()
pv.logsTextArea.SetText(pv.logsText.GetText(true), false).
SetBorder(true).
SetTitle(name + " [Select & Press Enter to Copy]")
pv.logsTextArea.SetOffset(row, col)
} else {
pv.logsTextArea.SetText("", false)
}
@@ -32,6 +35,7 @@ func (pv *pcView) toggleLogFollow() {
}

func (pv *pcView) startFollowLog(name string) {
pv.exitSearch()
pv.logFollow = true
pv.followLog(name)
go pv.updateLogs()
@@ -87,3 +91,11 @@ func (pv *pcView) createLogSelectionTextArea() {
return nil
})
}

func (pv *pcView) getLogTitle(name string) string {
if pv.logsText.isSearchActive() {
return fmt.Sprintf("Find: %s [%d of %d] - %s", pv.logsText.getSearchTerm(), pv.logsText.getCurrentSearchIndex()+1, pv.logsText.getTotalSearchCount(), name)
} else {
return name
}
}
121 changes: 109 additions & 12 deletions src/tui/log-viewer.go
Original file line number Diff line number Diff line change
@@ -1,24 +1,35 @@
package tui

import (
"bytes"
"fmt"
"github.com/f1bonacc1/process-compose/src/pclog"
"github.com/rivo/tview"
"io"
"math"
"regexp"
"strconv"
"strings"
"sync"
)

"github.com/rivo/tview"
var (
regionPattern = regexp.MustCompile(`\["([a-zA-Z0-9_,;: \-\.]*)"\]`)
)

type LogView struct {
tview.TextView
isWrapOn bool
buffer *strings.Builder
ansiWriter io.Writer
mx sync.Mutex
useAnsi bool
uniqueId string
isWrapOn bool
buffer *bytes.Buffer
ansiWriter io.Writer
mx sync.Mutex
useAnsi bool
uniqueId string
searchCurrentSelection int
isSearching bool
searchTerm string
searchIndex int
totalSearchCount int
}

func NewLogView(maxLines int) *LogView {
@@ -28,10 +39,12 @@ func NewLogView(maxLines int) *LogView {
TextView: *tview.NewTextView().
SetDynamicColors(true).
SetScrollable(true).
SetRegions(true).
SetMaxLines(maxLines),
buffer: &strings.Builder{},
useAnsi: false,
uniqueId: pclog.GenerateUniqueID(10),
buffer: &bytes.Buffer{},
useAnsi: false,
uniqueId: pclog.GenerateUniqueID(10),
searchCurrentSelection: 0,
}
l.ansiWriter = tview.ANSIWriter(l)
l.SetBorder(true)
@@ -84,10 +97,94 @@ func (l *LogView) Flush() {
l.mx.Lock()
defer l.mx.Unlock()
if l.useAnsi {
l.ansiWriter.Write([]byte(l.buffer.String()))
l.ansiWriter.Write(l.buffer.Bytes())
} else {
l.Write([]byte(l.buffer.String()))
l.Write(l.buffer.Bytes())
}

l.buffer.Reset()
}

func (l *LogView) addRegions(regex *regexp.Regexp, text string) string {
newText := regex.ReplaceAllStringFunc(text, func(match string) string {
region := fmt.Sprintf(`["%d"]%s[""]`, l.totalSearchCount, match)
l.totalSearchCount++
return region
})

return newText
}

func (l *LogView) removeRegions() {
text := regionPattern.ReplaceAllString(l.GetText(false), "")
l.SetText(text)
}

func (l *LogView) searchString(search string, isRegex, caseSensitive bool) error {
if search == "" {
return nil
}
l.resetSearch()
searchRegexString := search
if !isRegex {
searchRegexString = regexp.QuoteMeta(searchRegexString)
}
if !caseSensitive {
searchRegexString = "(?i)" + searchRegexString
}
searchRegex, err := regexp.Compile(searchRegexString)
if err != nil {
return err
}
log := l.GetText(false)
l.SetText(l.addRegions(searchRegex, strings.TrimSpace(log)))
if l.totalSearchCount > 0 {
l.Highlight("0").ScrollToHighlight()
}
l.isSearching = true
l.searchTerm = search
return nil
}

func (l *LogView) SearchNext() {
if l.totalSearchCount > 0 {
l.searchIndex = (l.searchIndex + 1) % l.totalSearchCount
l.Highlight(strconv.Itoa(l.searchIndex)).ScrollToHighlight()
}
}

func (l *LogView) SearchPrev() {
if l.totalSearchCount > 0 {
l.searchIndex = (l.searchIndex - 1 + l.totalSearchCount) % l.totalSearchCount
l.Highlight(strconv.Itoa(l.searchIndex)).ScrollToHighlight()
}
}

func (l *LogView) isSearchActive() bool {
return l.isSearching
}

func (l *LogView) resetSearch() {
if l.isSearching {
l.isSearching = false
l.searchIndex = 0
l.totalSearchCount = 0
l.Highlight()
l.removeRegions()
}
}

func (l *LogView) getSearchTerm() string {
return l.searchTerm
}

func (l *LogView) getCurrentSearchIndex() int {
if l.totalSearchCount == 0 {
return -1
}
return l.searchIndex
}

func (l *LogView) getTotalSearchCount() int {
return l.totalSearchCount
}
2 changes: 2 additions & 0 deletions src/tui/proc-table.go
Original file line number Diff line number Diff line change
@@ -40,6 +40,8 @@ func (pv *pcView) onTableSelectionChange(row, column int) {
if len(name) == 0 {
return
}
pv.logsText.resetSearch()
pv.updateHelpTextView()
pv.logsText.SetBorder(true).SetTitle(name)
pv.unFollowLog()
pv.followLog(name)
54 changes: 54 additions & 0 deletions src/tui/search-form.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package tui

import (
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
)

func (pv *pcView) showSearch() {
f := tview.NewForm()
f.SetCancelFunc(func() {
pv.pages.RemovePage(PageDialog)
})
f.SetItemPadding(1)
f.SetBorder(true)
f.SetFieldBackgroundColor(tcell.ColorLightSkyBlue)
f.SetFieldTextColor(tcell.ColorBlack)
f.SetButtonsAlign(tview.AlignCenter)
f.SetTitle("Search Log")
f.AddInputField("Search For", pv.logsText.getSearchTerm(), 50, nil, nil)
f.AddCheckbox("Case Sensitive", false, nil)
f.AddCheckbox("Regex", false, nil)
searchFunc := func() {
searchTerm := f.GetFormItem(0).(*tview.InputField).GetText()
caseSensitive := f.GetFormItem(1).(*tview.Checkbox).IsChecked()
isRegex := f.GetFormItem(2).(*tview.Checkbox).IsChecked()
pv.stopFollowLog()
if err := pv.logsText.searchString(searchTerm, isRegex, caseSensitive); err != nil {
f.SetTitle(err.Error())
return
}
pv.pages.RemovePage(PageDialog)
pv.logsText.SetTitle(pv.getLogTitle(pv.getSelectedProcName()))
pv.updateHelpTextView()
}
f.AddButton("Search", searchFunc)
f.AddButton("Cancel", func() {
pv.pages.RemovePage(PageDialog)
})
f.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
switch event.Key() {
case tcell.KeyEnter:
searchFunc()
case tcell.KeyEsc:
pv.pages.RemovePage(PageDialog)
default:
return event
}
return nil
})
f.SetFocus(0)
// Display and focus the dialog
pv.pages.AddPage(PageDialog, createDialogPage(f, 60, 11), true, true)
pv.appView.SetFocus(f)
}
26 changes: 26 additions & 0 deletions src/tui/view.go
Original file line number Diff line number Diff line change
@@ -122,12 +122,29 @@ func (pv *pcView) onAppKey(event *tcell.EventKey) *tcell.EventKey {
pv.terminateAppView()
case pv.shortcuts.ShortCutKeys[ActionProcessInfo].key:
pv.showInfo()
case pv.shortcuts.ShortCutKeys[ActionLogFind].key:
pv.showSearch()
case pv.shortcuts.ShortCutKeys[ActionLogFindNext].key:
pv.logsText.SearchNext()
pv.logsText.SetTitle(pv.getLogTitle(pv.getSelectedProcName()))
case pv.shortcuts.ShortCutKeys[ActionLogFindPrev].key:
pv.logsText.SearchPrev()
pv.logsText.SetTitle(pv.getLogTitle(pv.getSelectedProcName()))
case pv.shortcuts.ShortCutKeys[ActionLogFindExit].key:
pv.exitSearch()

default:
return event
}
return nil
}

func (pv *pcView) exitSearch() {
pv.logsText.resetSearch()
pv.logsText.SetTitle(pv.getLogTitle(pv.getSelectedProcName()))
pv.updateHelpTextView()
}

func (pv *pcView) terminateAppView() {

m := tview.NewModal().
@@ -201,11 +218,20 @@ func (pv *pcView) updateHelpTextView() {
logScrBool := pv.fullScrState != LogFull
procScrBool := pv.fullScrState != ProcFull
pv.helpText.Clear()
if pv.logsText.isSearchActive() {
pv.shortcuts.ShortCutKeys[ActionLogFind].writeButton(pv.helpText)
pv.shortcuts.ShortCutKeys[ActionLogFindNext].writeButton(pv.helpText)
pv.shortcuts.ShortCutKeys[ActionLogFindPrev].writeButton(pv.helpText)
pv.shortcuts.ShortCutKeys[ActionLogSelection].writeToggleButton(pv.helpText, !pv.logSelect)
pv.shortcuts.ShortCutKeys[ActionLogFindExit].writeButton(pv.helpText)
return
}
fmt.Fprintf(pv.helpText, "%s ", "[lightskyblue:]LOGS:[-:-:-]")
pv.shortcuts.ShortCutKeys[ActionLogScreen].writeToggleButton(pv.helpText, logScrBool)
pv.shortcuts.ShortCutKeys[ActionFollowLog].writeToggleButton(pv.helpText, !pv.logFollow)
pv.shortcuts.ShortCutKeys[ActionWrapLog].writeToggleButton(pv.helpText, !pv.logsText.IsWrapOn())
pv.shortcuts.ShortCutKeys[ActionLogSelection].writeToggleButton(pv.helpText, !pv.logSelect)
pv.shortcuts.ShortCutKeys[ActionLogFind].writeButton(pv.helpText)
fmt.Fprintf(pv.helpText, "%s ", "[lightskyblue::b]PROCESS:[-:-:-]")
pv.shortcuts.ShortCutKeys[ActionProcessInfo].writeButton(pv.helpText)
pv.shortcuts.ShortCutKeys[ActionProcessStart].writeButton(pv.helpText)

0 comments on commit 6c005d7

Please sign in to comment.