Add support for logs selection and copy to clipboard

This commit is contained in:
Berger Eugene 2022-12-08 22:29:32 +02:00
parent a835aa5f53
commit 636872f7d3
7 changed files with 150 additions and 70 deletions

2
go.mod
View File

@ -20,6 +20,7 @@ replace github.com/InVisionApp/go-health/v2 => github.com/f1bonacc1/go-health/v2
require (
github.com/InVisionApp/go-logger v1.0.1 // indirect
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/f1bonacc1/glippy v0.0.0-20221207220753-a53cdbf9bae7 // indirect
github.com/gdamore/encoding v1.0.0 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
@ -31,6 +32,7 @@ require (
github.com/go-playground/validator/v10 v10.11.1 // indirect
github.com/goccy/go-json v0.10.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jezek/xgb v1.1.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/leodido/go-urn v1.2.1 // indirect

4
go.sum
View File

@ -25,6 +25,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/f1bonacc1/glippy v0.0.0-20221207220753-a53cdbf9bae7 h1:4e3jb2TWZp24ME220mKFMLMPVb6/T6b5bdtXfQPnz68=
github.com/f1bonacc1/glippy v0.0.0-20221207220753-a53cdbf9bae7/go.mod h1:4FvlEkhBa/BJMEuMGVlocGYDJAvO7FwhJhHH9MY6vaM=
github.com/f1bonacc1/go-health/v2 v2.1.3 h1:UbiB5hSNpnqAAs7tsCZ8aA/ScdIpuGbDo2r7lhfIGkQ=
github.com/f1bonacc1/go-health/v2 v2.1.3/go.mod h1:Iz2FZRfK3sJecRvGCIgyBsKOjILdKTdLGiGFaO+JDYc=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
@ -82,6 +84,8 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jezek/xgb v1.1.0 h1:wnpxJzP1+rkbGclEkmwpVFQWpuE2PUGNUzP8SbfFobk=
github.com/jezek/xgb v1.1.0/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=

View File

@ -16,6 +16,7 @@ const (
ActionLogScreen = ActionName("log_screen")
ActionFollowLog = ActionName("log_follow")
ActionWrapLog = ActionName("log_wrap")
ActionLogSelection = ActionName("log_select")
ActionProcessStart = ActionName("process_start")
ActionProcessInfo = ActionName("process_info")
ActionProcessStop = ActionName("process_stop")
@ -28,6 +29,7 @@ var defaultShortcuts = map[ActionName]tcell.Key{
ActionLogScreen: tcell.KeyF4,
ActionFollowLog: tcell.KeyF5,
ActionWrapLog: tcell.KeyF6,
ActionLogSelection: tcell.KeyCtrlS,
ActionProcessInfo: tcell.KeyF3,
ActionProcessStart: tcell.KeyF7,
ActionProcessStop: tcell.KeyF9,
@ -162,6 +164,12 @@ func getDefaultActions() ShortCuts {
false: "Wrap Off",
},
},
ActionLogSelection: {
ToggleDescription: map[bool]string{
true: "Select On",
false: "Select Off",
},
},
ActionProcessInfo: {
Description: "Info",
},

View File

@ -1,64 +1,17 @@
package tui
import (
"github.com/f1bonacc1/process-compose/src/app"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
"strings"
)
func (pv *pcView) showProcInfo(info *app.ProcessConfig) {
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("Process " + info.Name + " Info")
addStringIfNotEmpty("Command:", info.Command, f)
addStringIfNotEmpty("Working Directory:", info.WorkingDir, f)
addStringIfNotEmpty("Log Location:", info.LogLocation, f)
addSliceIfNotEmpty("Environment:", info.Environment, f)
addSliceIfNotEmpty("Depends On:", mapKeysToSlice(info.DependsOn), f)
f.AddCheckbox("Is Disabled:", info.Disabled, nil)
f.AddCheckbox("Is Daemon:", info.IsDaemon, nil)
f.AddButton("Close", func() {
pv.pages.RemovePage(PageDialog)
})
f.SetFocus(f.GetFormItemCount())
pv.pages.AddPage(PageDialog, createPage(f, 0, 0), true, true)
func (pv *pcView) showDialog(primitive tview.Primitive) {
pv.pages.AddPage(PageDialog, createDialogPage(primitive, 0, 0), true, true)
pv.appView.SetFocus(primitive)
}
func createPage(p tview.Primitive, width, height int) tview.Primitive {
func createDialogPage(p tview.Primitive, width, height int) tview.Primitive {
return tview.NewGrid().
SetColumns(0, width, 0).
SetRows(0, height, 0).
AddItem(p, 1, 1, 1, 1, 0, 0, true)
}
func addStringIfNotEmpty(label, value string, f *tview.Form) {
if len(strings.TrimSpace(value)) > 0 {
f.AddInputField(label, value, 0, nil, nil)
}
}
func addSliceIfNotEmpty(label string, value []string, f *tview.Form) {
if len(value) > 0 {
f.AddDropDown(label, value, 0, nil)
}
}
// mapKeysToSlice extract keys of map as slice,
func mapKeysToSlice[K comparable, V any](m map[K]V) []K {
keys := make([]K, len(m))
i := 0
for k := range m {
keys[i] = k
i++
}
return keys
}

View File

@ -22,9 +22,12 @@ func NewLogView(maxLines int) *LogView {
l := &LogView{
isWrapOn: true,
TextView: *tview.NewTextView().SetDynamicColors(true).SetScrollable(true).SetMaxLines(maxLines),
buffer: &strings.Builder{},
useAnsi: false,
TextView: *tview.NewTextView().
SetDynamicColors(true).
SetScrollable(true).
SetMaxLines(maxLines),
buffer: &strings.Builder{},
useAnsi: false,
}
l.ansiWriter = tview.ANSIWriter(l)
l.SetBorder(true)

58
src/tui/proc-info-form.go Normal file
View File

@ -0,0 +1,58 @@
package tui
import (
"github.com/f1bonacc1/process-compose/src/app"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
"strings"
)
func (pv *pcView) createProcInfoForm(info *app.ProcessConfig) *tview.Form {
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("Process " + info.Name + " Info")
addStringIfNotEmpty("Command:", info.Command, f)
addStringIfNotEmpty("Working Directory:", info.WorkingDir, f)
addStringIfNotEmpty("Log Location:", info.LogLocation, f)
addSliceIfNotEmpty("Environment:", info.Environment, f)
addSliceIfNotEmpty("Depends On:", mapKeysToSlice(info.DependsOn), f)
f.AddCheckbox("Is Disabled:", info.Disabled, nil)
f.AddCheckbox("Is Daemon:", info.IsDaemon, nil)
f.AddButton("Close", func() {
pv.pages.RemovePage(PageDialog)
})
f.SetFocus(f.GetFormItemCount())
return f
}
func addStringIfNotEmpty(label, value string, f *tview.Form) {
if len(strings.TrimSpace(value)) > 0 {
f.AddInputField(label, value, 0, nil, nil)
}
}
func addSliceIfNotEmpty(label string, value []string, f *tview.Form) {
if len(value) > 0 {
f.AddDropDown(label, value, 0, nil)
}
}
// mapKeysToSlice extract keys of map as slice,
func mapKeysToSlice[K comparable, V any](m map[K]V) []K {
keys := make([]K, len(m))
i := 0
for k := range m {
keys[i] = k
i++
}
return keys
}

View File

@ -2,6 +2,7 @@ package tui
import (
"fmt"
"github.com/f1bonacc1/glippy"
"github.com/f1bonacc1/process-compose/src/config"
"github.com/f1bonacc1/process-compose/src/updater"
"os"
@ -43,10 +44,13 @@ type pcView struct {
pages *tview.Pages
procNames []string
logFollow bool
logSelect bool
fullScrState FullScrState
loggedProc string
shortcuts ShortCuts
procCountCell *tview.TableCell
mainGrid *tview.Grid
logsTextArea *tview.TextArea
}
func newPcView(logLength int) *pcView {
@ -62,13 +66,29 @@ func newPcView(logLength int) *pcView {
loggedProc: "",
shortcuts: getDefaultActions(),
procCountCell: tview.NewTableCell(""),
mainGrid: tview.NewGrid(),
logsTextArea: tview.NewTextArea(),
logSelect: false,
}
pv.loadShortcuts()
pv.procTable = pv.createProcTable()
pv.statTable = pv.createStatTable()
pv.updateHelpTextView()
pv.createGrid()
pv.logsTextArea.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
switch event.Key() {
case tcell.KeyCR:
text, start, _ := pv.logsTextArea.GetSelection()
glippy.Set(text)
pv.logsTextArea.Select(start, start)
case tcell.KeyEsc:
pv.toggleLogSelection()
pv.updateHelpTextView()
}
return nil
})
pv.pages = tview.NewPages().
AddPage(PageMain, pv.createGrid(pv.fullScrState), true, true)
AddPage(PageMain, pv.mainGrid, true, true)
pv.appView.SetRoot(pv.pages, true).EnableMouse(true).SetInputCapture(pv.onAppKey)
if len(pv.procNames) > 0 {
@ -97,20 +117,25 @@ func (pv *pcView) onAppKey(event *tcell.EventKey) *tcell.EventKey {
} else {
pv.fullScrState = LogFull
}
pv.appView.SetRoot(pv.createGrid(pv.fullScrState), true)
pv.redrawGrid()
pv.updateHelpTextView()
case pv.shortcuts.ShortCutKeys[ActionFollowLog].key:
pv.toggleLogFollow()
case pv.shortcuts.ShortCutKeys[ActionWrapLog].key:
pv.logsText.ToggleWrap()
pv.updateHelpTextView()
case pv.shortcuts.ShortCutKeys[ActionLogSelection].key:
pv.stopFollowLog()
pv.toggleLogSelection()
pv.appView.SetFocus(pv.logsTextArea)
pv.updateHelpTextView()
case pv.shortcuts.ShortCutKeys[ActionProcessScreen].key:
if pv.fullScrState == ProcFull {
pv.fullScrState = LogProcHalf
} else {
pv.fullScrState = ProcFull
}
pv.appView.SetRoot(pv.createGrid(pv.fullScrState), true)
pv.redrawGrid()
pv.onProcRowSpanChange()
pv.updateHelpTextView()
case tcell.KeyCtrlC:
@ -136,7 +161,7 @@ func (pv *pcView) terminateAppView() {
pv.pages.RemovePage(PageDialog)
})
// Display and focus the dialog
pv.pages.AddPage(PageDialog, createPage(m, 50, 50), true, true)
pv.pages.AddPage(PageDialog, createDialogPage(m, 50, 50), true, true)
}
func (pv *pcView) showError(errMessage string) {
@ -148,7 +173,7 @@ func (pv *pcView) showError(errMessage string) {
pv.pages.RemovePage(PageDialog)
})
pv.pages.AddPage(PageDialog, createPage(m, 50, 50), true, true)
pv.pages.AddPage(PageDialog, createDialogPage(m, 50, 50), true, true)
}
func (pv *pcView) showInfo() {
@ -158,7 +183,22 @@ func (pv *pcView) showInfo() {
pv.showError(err.Error())
return
}
pv.showProcInfo(info)
form := pv.createProcInfoForm(info)
pv.showDialog(form)
}
func (pv *pcView) toggleLogSelection() {
name := pv.getSelectedProcName()
pv.logSelect = !pv.logSelect
if pv.logSelect {
pv.logsTextArea.SetText(pv.logsText.GetText(true), true).
SetBorder(true).
SetTitle(name + " [Select & Press Enter to Copy]")
} else {
pv.logsTextArea.SetText("", false)
}
pv.redrawGrid()
}
func (pv *pcView) handleShutDown() {
@ -347,6 +387,7 @@ func (pv *pcView) updateHelpTextView() {
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)
fmt.Fprintf(pv.helpText, "%s ", "[lightskyblue::b]PROCESS:[-:-:-]")
pv.shortcuts.ShortCutKeys[ActionProcessInfo].writeButton(pv.helpText)
pv.shortcuts.ShortCutKeys[ActionProcessStart].writeButton(pv.helpText)
@ -356,27 +397,38 @@ func (pv *pcView) updateHelpTextView() {
pv.shortcuts.ShortCutKeys[ActionQuit].writeButton(pv.helpText)
}
func (pv *pcView) createGrid(fullScrState FullScrState) *tview.Grid {
grid := tview.NewGrid().
func (pv *pcView) createGrid() {
pv.mainGrid.Clear().
SetRows(3, 0, 0, 1).
//SetColumns(30, 0, 30).
SetBorders(true).
AddItem(pv.statTable, 0, 0, 1, 1, 0, 0, false).
AddItem(pv.helpText, 3, 0, 1, 1, 0, 0, false)
switch fullScrState {
var log tview.Primitive
if !pv.logSelect {
log = pv.logsText
} else {
log = pv.logsTextArea
}
switch pv.fullScrState {
case LogFull:
grid.AddItem(pv.logsText, 1, 0, 2, 1, 0, 0, true)
pv.mainGrid.AddItem(log, 1, 0, 2, 1, 0, 0, true)
case ProcFull:
grid.AddItem(pv.procTable, 1, 0, 2, 1, 0, 0, true)
pv.mainGrid.AddItem(pv.procTable, 1, 0, 2, 1, 0, 0, true)
case LogProcHalf:
grid.AddItem(pv.procTable, 1, 0, 1, 1, 0, 0, true)
grid.AddItem(pv.logsText, 2, 0, 1, 1, 0, 0, false)
pv.mainGrid.AddItem(pv.procTable, 1, 0, 1, 1, 0, 0, true)
pv.mainGrid.AddItem(log, 2, 0, 1, 1, 0, 0, false)
}
grid.SetTitle("Process Compose")
return grid
pv.mainGrid.SetTitle("Process Compose")
//pv.mainGrid = grid
}
func (pv *pcView) redrawGrid() {
go pv.appView.QueueUpdateDraw(func() {
pv.createGrid()
})
}
func (pv *pcView) updateTable() {