Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Filter on process list #136

Merged
merged 12 commits into from
Feb 9, 2024
67 changes: 67 additions & 0 deletions src/tui/proc-filter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package tui

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

func (pv *pcView) showProcFilter() {
const fieldWidth = 50
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 Process")

f.AddInputField("Search For", "", fieldWidth, nil, nil)
f.AddCheckbox("Case Sensitive", false, nil)
f.AddCheckbox("Regex", false, nil)

searchFunc := func(searchTerm string) {
caseSensitive := f.GetFormItem(1).(*tview.Checkbox).IsChecked()
isRegex := f.GetFormItem(2).(*tview.Checkbox).IsChecked()

pv.searchProcess(searchTerm, isRegex, caseSensitive)
}
f.GetFormItem(0).(*tview.InputField).SetChangedFunc(func(text string) {
searchFunc(text)
})

f.GetFormItem(0).(*tview.InputField).SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
if event.Key() == tcell.KeyEnter {
pv.pages.RemovePage(PageDialog)
return nil
}

return event
})

f.AddButton("Search", func() {
pv.pages.RemovePage(PageDialog)
})
f.AddButton("Cancel", func() {
pv.resetProcessSearch()
pv.pages.RemovePage(PageDialog)
})

f.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
switch event.Key() {
case tcell.KeyEsc:
pv.resetProcessSearch()
pv.pages.RemovePage(PageDialog)
default:
return event
}
return nil
})
f.SetFocus(0)
pv.resetProcessSearch()
// Display and focus the dialog
pv.pages.AddPage(PageDialog, createDialogPage(f, fieldWidth+20, 11), true, true)
pv.appView.SetFocus(f)
}
60 changes: 60 additions & 0 deletions src/tui/proc-table.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package tui
import (
"context"
"fmt"
"regexp"
"github.com/f1bonacc1/process-compose/src/types"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
Expand Down Expand Up @@ -46,13 +47,24 @@ func (pv *pcView) fillTableData() {
pv.procTable.RemoveRow(row)
continue
}

if !pv.matchProcRegex(state.Name) {
pv.procTable.RemoveRow(row)
continue
}

rowVals := getTableRowValues(state)
setRowValues(pv.procTable, row, rowVals)
if state.IsRunning {
runningProcCount += 1
}
row += 1
}
if row == 1 {
pv.procTable.SetSelectable(false, false)
} else {
pv.procTable.SetSelectable(true, false)
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you encountered my bug, which I already fixed in my env.
Instead of this block, can you try:

if pv.procTable.GetRowCount()-1 > row - 1{

Notice the row - 1
In the line below.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this block as a workaround for the issue in tview. It seems to break if the currently selected row is removed from the table. Which i was also able to replicate in isolation.

At one point i tried to fix it by calling table.clear() before rendering the rows, which means it didn't have to clean up the remaining rows. And it still had the same issue (with the lines you mentioned removed).

I tried it with this change and run into the same issue. (to test it)

  • enter a filter that results in no processes
  • press j or k

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's strange. I couldn't replicate it in my environment by, for example, hiding a selected namespace Ctrl+G.
Even if it's the last row, leaving me with 0 rows (except the title row).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. How did you filter all rows using namespaces? Doesn't every namespace in the list have at least one project?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am cheating :) I also have the implementation of #133
I have 2 processes:

  1. ns: aaa, disabled
  2. ns: default
    I select the aaa namespace and toggle disabled procs. Leaving me with an empty list:
    image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes sorry, that's what i ment by "crashing" , actually the background processes also keep running fine. (As seen by the logs updating by looking at the logfile directly)

Copy link
Owner

@F1bonacc1 F1bonacc1 Feb 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I attached a goroutine profiler. It looks like the table is stuck in this loop:
https://github.com/rivo/tview/blob/861aa94d61c899b32a1304d61b840d0178ece408/table.go#L1373

#	0x65ae14	github.com/rivo/tview.(*Table).InputHandler.func1.2+0x54			/home/eugene/go/pkg/mod/github.com/rivo/tview@v0.0.0-20240101144852-b3bd1aa5e9f2/table.go:1375
#	0x65aca3	github.com/rivo/tview.(*Table).InputHandler.func1.5+0xa3			/home/eugene/go/pkg/mod/github.com/rivo/tview@v0.0.0-20240101144852-b3bd1aa5e9f2/table.go:1440
#	0x65a5a4	github.com/rivo/tview.(*Table).InputHandler.func1+0x4e4				/home/eugene/go/pkg/mod/github.com/rivo/tview@v0.0.0-20240101144852-b3bd1aa5e9f2/table.go:1598
#	0x65a08f	github.com/rivo/tview.(*Table).InputHandler.(*Box).WrapInputHandler.func2+0x4f	/home/eugene/go/pkg/mod/github.com/rivo/tview@v0.0.0-20240101144852-b3bd1aa5e9f2/box.go:167
#	0x63bb16	github.com/rivo/tview.(*Grid).InputHandler.func1+0x1b6				/home/eugene/go/pkg/mod/github.com/rivo/tview@v0.0.0-20240101144852-b3bd1aa5e9f2/grid.go:277
#	0x63b92f	github.com/rivo/tview.(*Grid).InputHandler.(*Box).WrapInputHandler.func2+0x4f	/home/eugene/go/pkg/mod/github.com/rivo/tview@v0.0.0-20240101144852-b3bd1aa5e9f2/box.go:167
#	0x650801	github.com/rivo/tview.(*Pages).InputHandler.func1+0xa1				/home/eugene/go/pkg/mod/github.com/rivo/tview@v0.0.0-20240101144852-b3bd1aa5e9f2/pages.go:311
#	0x65072f	github.com/rivo/tview.(*Pages).InputHandler.(*Box).WrapInputHandler.func2+0x4f	/home/eugene/go/pkg/mod/github.com/rivo/tview@v0.0.0-20240101144852-b3bd1aa5e9f2/box.go:167
#	0x627737	github.com/rivo/tview.(*Application).Run+0x557					/home/eugene/go/pkg/mod/github.com/rivo/tview@v0.0.0-20240101144852-b3bd1aa5e9f2/application.go:344
#	0xb3a3fd	github.com/f1bonacc1/process-compose/src/tui.SetupTui+0x13d			/home/eugene/projects/go/process-compose/src/tui/view.go:474
#	0xb7ebb8	github.com/f1bonacc1/process-compose/src/cmd.startTui+0x178			/home/eugene/projects/go/process-compose/src/cmd/project_runner.go:92

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given this remaining issue, is there anything else in this PR that still needs to be addressed?
Do you think this workaround should be used for now? GIven that #133 will have a similar issue? Or wait for an upstream fix?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I already merged your PR locally.
I believe I will release a new version over the weekend.
Does this timeline work for you?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome! no rush, i was just wondering if there were any more changes needed 👌


// remove unnecessary rows, don't forget the title row (-1)
if pv.procTable.GetRowCount()-1 > row {
Expand Down Expand Up @@ -209,6 +221,54 @@ func (pv *pcView) setTableSorter(sortBy ColumnID) {
}
}

func (pv *pcView) setProcRegex(reg *regexp.Regexp) {
pv.procRegexMtx.Lock()
defer pv.procRegexMtx.Unlock()
pv.procRegex = reg
}

func (pv *pcView) matchProcRegex(procName string) bool {
pv.procRegexMtx.Lock()
defer pv.procRegexMtx.Unlock()
if pv.procRegex != nil {
return pv.procRegex.MatchString(procName)
}
// if no search string is present, match everything
return true
}

func (pv *pcView) resetProcessSearch() {
pv.setProcRegex(nil)
go pv.appView.QueueUpdateDraw(func() {
pv.fillTableData()
})
}

func (pv *pcView) searchProcess(search string, isRegex, caseSensitive bool) error {
if search == "" {
pv.setProcRegex(nil)
return nil
}
searchRegexString := search
if !isRegex {
searchRegexString = regexp.QuoteMeta(searchRegexString)
}
if !caseSensitive {
searchRegexString = "(?i)" + searchRegexString
}
searchRegex, err := regexp.Compile(searchRegexString)
if err != nil {
return err
}

pv.setProcRegex(searchRegex)
pv.procTable.Select(1, 1)
go pv.appView.QueueUpdateDraw(func() {
pv.fillTableData()
})
return nil
}

func (pv *pcView) getTableSorter() StateSorter {
pv.sortMtx.Lock()
defer pv.sortMtx.Unlock()
Expand Down
10 changes: 10 additions & 0 deletions src/tui/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/rs/zerolog/log"
"os"
"os/signal"
"regexp"
"strconv"
"sync"
"sync/atomic"
Expand Down Expand Up @@ -58,6 +59,8 @@ type pcView struct {
project app.IProject
sortMtx sync.Mutex
stateSorter StateSorter
procRegex *regexp.Regexp
procRegexMtx sync.Mutex
procColumns map[ColumnID]string
refreshRate time.Duration
cancelFn context.CancelFunc
Expand Down Expand Up @@ -192,8 +195,15 @@ func (pv *pcView) onMainGridKey(event *tcell.EventKey) *tcell.EventKey {
pv.logsText.SetTitle(pv.getLogTitle(pv.getSelectedProcName()))
case pv.shortcuts.ShortCutKeys[ActionLogFindExit].key:
pv.exitSearch()
pv.resetProcessSearch()
case pv.shortcuts.ShortCutKeys[ActionNsFilter].key:
pv.showNsFilter()
case tcell.KeyRune:
if event.Rune() == '/' {
pv.showProcFilter()
} else {
return event
}
default:
return event
}
Expand Down
Loading