Merge remote-tracking branch 'rephorm/filter'
This commit is contained in:
commit
f850a47d91
|
@ -21,6 +21,7 @@ Bug fixes & pull requests
|
|||
- Merged pull request for README clean-ups (theverything:add-missing-option-to-readme)
|
||||
- Merge Nord color scheme (jrswab:nordColorScheme)
|
||||
- Merge support for multiple (and filtering) network interfaces (mattLLVW:feature/network_interface_list)
|
||||
- Merge filtering subprocesses by substring (rephorm:filter)
|
||||
|
||||
## [3.1.0] - 2020-02-13
|
||||
|
||||
|
|
|
@ -37,9 +37,9 @@ Unzip it and then move `gotop` into your `$PATH` somewhere. If you're on a Debi
|
|||
### Keybinds
|
||||
|
||||
- Quit: `q` or `<C-c>`
|
||||
- Process navigation
|
||||
- Process navigation:
|
||||
- `k` and `<Up>`: up
|
||||
- `j` and `<Down`: down
|
||||
- `j` and `<Down>`: down
|
||||
- `<C-u>`: half page up
|
||||
- `<C-d>`: half page down
|
||||
- `<C-b>`: full page up
|
||||
|
@ -55,6 +55,11 @@ Unzip it and then move `gotop` into your `$PATH` somewhere. If you're on a Debi
|
|||
- `c`: CPU
|
||||
- `m`: Mem
|
||||
- `p`: PID
|
||||
- Process filtering:
|
||||
- `/`: start editing filter
|
||||
- (while editing):
|
||||
- `<Enter>` accept filter
|
||||
- `<C-c>` and `<Escape>`: clear filter
|
||||
- CPU and Mem graph scaling:
|
||||
- `h`: scale in
|
||||
- `l`: scale out
|
||||
|
|
|
@ -221,6 +221,10 @@ func eventLoop(c gotop.Config, grid *layout.MyGrid) {
|
|||
}
|
||||
}
|
||||
case e := <-uiEvents:
|
||||
if grid.Proc != nil && grid.Proc.HandleEvent(e) {
|
||||
ui.Render(grid.Proc)
|
||||
break
|
||||
}
|
||||
switch e.ID {
|
||||
case "q", "<C-c>":
|
||||
return
|
||||
|
@ -354,6 +358,11 @@ func eventLoop(c gotop.Config, grid *layout.MyGrid) {
|
|||
grid.Proc.ChangeProcSortMethod(w.ProcSortMethod(e.ID))
|
||||
ui.Render(grid.Proc)
|
||||
}
|
||||
case "/":
|
||||
if grid.Proc != nil {
|
||||
grid.Proc.SetEditingFilter(true)
|
||||
ui.Render(grid.Proc)
|
||||
}
|
||||
}
|
||||
|
||||
if previousKey == e.ID {
|
||||
|
|
|
@ -14,7 +14,6 @@ import (
|
|||
// TODO: Merge #167 configuration file (jrswab:configFile111)
|
||||
// TODO: Merge #157 FreeBSD fixes & Nvidia GPU support (kraust:master)
|
||||
// TODO: Merge #156 Added temperatures for NVidia GPUs (azak-azkaran:master)
|
||||
// TODO: Merge #147 filtering subprocesses by substring (rephorm:filter)
|
||||
// TODO: Merge #140 color-related fix (Tazer:master)
|
||||
// TODO: Merge #135 linux console font (cmatsuoka:console-font)
|
||||
type Config struct {
|
||||
|
|
1
go.mod
1
go.mod
|
@ -6,6 +6,7 @@ require (
|
|||
github.com/docopt/docopt.go v0.0.0-20180111231733-ee0de3bc6815
|
||||
github.com/gizak/termui/v3 v3.0.0
|
||||
github.com/go-ole/go-ole v1.2.4 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.4
|
||||
github.com/shirou/gopsutil v2.18.11+incompatible
|
||||
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 // indirect
|
||||
github.com/stretchr/testify v1.4.0
|
||||
|
|
2
go.sum
2
go.sum
|
@ -20,6 +20,8 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
|||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/mattn/go-runewidth v0.0.2 h1:UnlwIPBGaTZfPQ6T1IGzPI0EkYAQmT9fAEJ/poFC63o=
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM=
|
||||
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
|
||||
github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d h1:x3S6kxmy49zXVVyhcnrFqxvNVCBPb2KZ9hV2RBdS840=
|
||||
|
|
113
termui/entry.go
Normal file
113
termui/entry.go
Normal file
|
@ -0,0 +1,113 @@
|
|||
package termui
|
||||
|
||||
import (
|
||||
"image"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
. "github.com/gizak/termui/v3"
|
||||
rw "github.com/mattn/go-runewidth"
|
||||
"github.com/xxxserxxx/gotop/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
ELLIPSIS = "…"
|
||||
CURSOR = " "
|
||||
)
|
||||
|
||||
type Entry struct {
|
||||
Block
|
||||
|
||||
Style Style
|
||||
|
||||
Label string
|
||||
Value string
|
||||
ShowWhenEmpty bool
|
||||
UpdateCallback func(string)
|
||||
|
||||
editing bool
|
||||
}
|
||||
|
||||
func (self *Entry) SetEditing(editing bool) {
|
||||
self.editing = editing
|
||||
}
|
||||
|
||||
func (self *Entry) update() {
|
||||
if self.UpdateCallback != nil {
|
||||
self.UpdateCallback(self.Value)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleEvent handles input events if the entry is being edited.
|
||||
// Returns true if the event was handled.
|
||||
func (self *Entry) HandleEvent(e Event) bool {
|
||||
if !self.editing {
|
||||
return false
|
||||
}
|
||||
if utf8.RuneCountInString(e.ID) == 1 {
|
||||
self.Value += e.ID
|
||||
self.update()
|
||||
return true
|
||||
}
|
||||
switch e.ID {
|
||||
case "<C-c>", "<Escape>":
|
||||
self.Value = ""
|
||||
self.editing = false
|
||||
self.update()
|
||||
case "<Enter>":
|
||||
self.editing = false
|
||||
case "<Backspace>":
|
||||
if self.Value != "" {
|
||||
r := []rune(self.Value)
|
||||
self.Value = string(r[:len(r)-1])
|
||||
self.update()
|
||||
}
|
||||
case "<Space>":
|
||||
self.Value += " "
|
||||
self.update()
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (self *Entry) Draw(buf *Buffer) {
|
||||
if self.Value == "" && !self.editing && !self.ShowWhenEmpty {
|
||||
return
|
||||
}
|
||||
|
||||
style := self.Style
|
||||
label := self.Label
|
||||
if self.editing {
|
||||
label += "["
|
||||
style = NewStyle(style.Fg, style.Bg, ModifierBold)
|
||||
}
|
||||
cursorStyle := NewStyle(style.Bg, style.Fg, ModifierClear)
|
||||
|
||||
p := image.Pt(self.Min.X, self.Min.Y)
|
||||
buf.SetString(label, style, p)
|
||||
p.X += rw.StringWidth(label)
|
||||
|
||||
tail := " "
|
||||
if self.editing {
|
||||
tail = "] "
|
||||
}
|
||||
|
||||
maxLen := self.Max.X - p.X - rw.StringWidth(tail)
|
||||
if self.editing {
|
||||
maxLen -= 1 // for cursor
|
||||
}
|
||||
value := utils.TruncateFront(self.Value, maxLen, ELLIPSIS)
|
||||
buf.SetString(value, self.Style, p)
|
||||
p.X += rw.StringWidth(value)
|
||||
|
||||
if self.editing {
|
||||
buf.SetString(CURSOR, cursorStyle, p)
|
||||
p.X += rw.StringWidth(CURSOR)
|
||||
if remaining := maxLen - rw.StringWidth(value); remaining > 0 {
|
||||
buf.SetString(strings.Repeat(" ", remaining), self.TitleStyle, p)
|
||||
p.X += remaining
|
||||
}
|
||||
}
|
||||
buf.SetString(tail, style, p)
|
||||
}
|
|
@ -131,6 +131,9 @@ func (self *Table) Draw(buf *Buffer) {
|
|||
func (self *Table) drawLocation(buf *Buffer) {
|
||||
total := len(self.Rows)
|
||||
topRow := self.TopRow + 1
|
||||
if topRow > total {
|
||||
topRow = total
|
||||
}
|
||||
bottomRow := self.TopRow + self.Inner.Dy() - 1
|
||||
if bottomRow > total {
|
||||
bottomRow = total
|
||||
|
|
24
utils/runes.go
Normal file
24
utils/runes.go
Normal file
|
@ -0,0 +1,24 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
rw "github.com/mattn/go-runewidth"
|
||||
)
|
||||
|
||||
func TruncateFront(s string, w int, prefix string) string {
|
||||
if rw.StringWidth(s) <= w {
|
||||
return s
|
||||
}
|
||||
r := []rune(s)
|
||||
pw := rw.StringWidth(prefix)
|
||||
w -= pw
|
||||
width := 0
|
||||
i := len(r) - 1
|
||||
for ; i >= 0; i-- {
|
||||
cw := rw.RuneWidth(r[i])
|
||||
width += cw
|
||||
if width > w {
|
||||
break
|
||||
}
|
||||
}
|
||||
return prefix + string(r[i+1:len(r)])
|
||||
}
|
50
utils/runes_test.go
Normal file
50
utils/runes_test.go
Normal file
|
@ -0,0 +1,50 @@
|
|||
package utils
|
||||
|
||||
import "testing"
|
||||
|
||||
const (
|
||||
ELLIPSIS = "…"
|
||||
)
|
||||
|
||||
func TestTruncateFront(t *testing.T) {
|
||||
tests := []struct {
|
||||
s string
|
||||
w int
|
||||
prefix string
|
||||
want string
|
||||
}{
|
||||
{"", 0, ELLIPSIS, ""},
|
||||
{"", 1, ELLIPSIS, ""},
|
||||
{"", 10, ELLIPSIS, ""},
|
||||
|
||||
{"abcdef", 0, ELLIPSIS, ELLIPSIS},
|
||||
{"abcdef", 1, ELLIPSIS, ELLIPSIS},
|
||||
{"abcdef", 2, ELLIPSIS, ELLIPSIS + "f"},
|
||||
{"abcdef", 5, ELLIPSIS, ELLIPSIS + "cdef"},
|
||||
{"abcdef", 6, ELLIPSIS, "abcdef"},
|
||||
{"abcdef", 10, ELLIPSIS, "abcdef"},
|
||||
|
||||
{"abcdef", 0, "...", "..."},
|
||||
{"abcdef", 1, "...", "..."},
|
||||
{"abcdef", 3, "...", "..."},
|
||||
{"abcdef", 4, "...", "...f"},
|
||||
{"abcdef", 5, "...", "...ef"},
|
||||
{"abcdef", 6, "...", "abcdef"},
|
||||
{"abcdef", 10, "...", "abcdef"},
|
||||
|
||||
{"⦅full~width⦆", 15, ".", "⦅full~width⦆"},
|
||||
{"⦅full~width⦆", 14, ".", ".full~width⦆"},
|
||||
{"⦅full~width⦆", 13, ".", ".ull~width⦆"},
|
||||
{"⦅full~width⦆", 10, ".", ".~width⦆"},
|
||||
{"⦅full~width⦆", 9, ".", ".width⦆"},
|
||||
{"⦅full~width⦆", 8, ".", ".width⦆"},
|
||||
{"⦅full~width⦆", 3, ".", ".⦆"},
|
||||
{"⦅full~width⦆", 2, ".", "."},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
if got := TruncateFront(test.s, test.w, test.prefix); got != test.want {
|
||||
t.Errorf("TruncateFront(%q, %d, %q) = %q; want %q", test.s, test.w, test.prefix, got, test.want)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,7 +10,7 @@ import (
|
|||
const KEYBINDS = `
|
||||
Quit: q or <C-c>
|
||||
|
||||
Process navigation
|
||||
Process navigation:
|
||||
- k and <Up>: up
|
||||
- j and <Down>: down
|
||||
- <C-u>: half page up
|
||||
|
@ -26,11 +26,17 @@ Process actions:
|
|||
- d3: kill selected process or group of processes with SIGQUIT (3)
|
||||
- d9: kill selected process or group of processes with SIGKILL (9)
|
||||
|
||||
Process sorting
|
||||
Process sorting:
|
||||
- c: CPU
|
||||
- m: Mem
|
||||
- p: PID
|
||||
|
||||
Process filtering:
|
||||
- /: start editing filter
|
||||
- (while editing):
|
||||
- <Enter>: accept filter
|
||||
- <C-c> and <Escape>: clear filter
|
||||
|
||||
CPU and Mem graph scaling:
|
||||
- h: scale in
|
||||
- l: scale out
|
||||
|
@ -47,12 +53,8 @@ func NewHelpMenu() *HelpMenu {
|
|||
}
|
||||
|
||||
func (self *HelpMenu) Resize(termWidth, termHeight int) {
|
||||
var textWidth = 0
|
||||
for _, line := range strings.Split(KEYBINDS, "\n") {
|
||||
textWidth = maxInt(len(line), textWidth)
|
||||
}
|
||||
textWidth += 2
|
||||
textHeight := 28
|
||||
textWidth := 53
|
||||
textHeight := strings.Count(KEYBINDS, "\n") + 1
|
||||
x := (termWidth - textWidth) / 2
|
||||
y := (termHeight - textHeight) / 2
|
||||
|
||||
|
|
|
@ -6,10 +6,12 @@ import (
|
|||
"os/exec"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
psCPU "github.com/shirou/gopsutil/cpu"
|
||||
|
||||
tui "github.com/gizak/termui/v3"
|
||||
ui "github.com/xxxserxxx/gotop/termui"
|
||||
"github.com/xxxserxxx/gotop/utils"
|
||||
)
|
||||
|
@ -37,9 +39,11 @@ type Proc struct {
|
|||
|
||||
type ProcWidget struct {
|
||||
*ui.Table
|
||||
entry *ui.Entry
|
||||
cpuCount int
|
||||
updateInterval time.Duration
|
||||
sortMethod ProcSortMethod
|
||||
filter string
|
||||
groupedProcs []Proc
|
||||
ungroupedProcs []Proc
|
||||
showGroupedProcs bool
|
||||
|
@ -56,6 +60,16 @@ func NewProcWidget() *ProcWidget {
|
|||
cpuCount: cpuCount,
|
||||
sortMethod: ProcSortCpu,
|
||||
showGroupedProcs: true,
|
||||
filter: "",
|
||||
}
|
||||
self.entry = &ui.Entry{
|
||||
Style: self.TitleStyle,
|
||||
Label: " Filter: ",
|
||||
Value: "",
|
||||
UpdateCallback: func(val string) {
|
||||
self.filter = val
|
||||
self.update()
|
||||
},
|
||||
}
|
||||
self.Title = " Processes "
|
||||
self.ShowCursor = true
|
||||
|
@ -86,6 +100,37 @@ func NewProcWidget() *ProcWidget {
|
|||
return self
|
||||
}
|
||||
|
||||
func (self *ProcWidget) SetEditingFilter(editing bool) {
|
||||
self.entry.SetEditing(editing)
|
||||
}
|
||||
|
||||
func (self *ProcWidget) HandleEvent(e tui.Event) bool {
|
||||
return self.entry.HandleEvent(e)
|
||||
}
|
||||
|
||||
func (self *ProcWidget) SetRect(x1, y1, x2, y2 int) {
|
||||
self.Table.SetRect(x1, y1, x2, y2)
|
||||
self.entry.SetRect(x1+2, y2-1, x2-2, y2)
|
||||
}
|
||||
|
||||
func (self *ProcWidget) Draw(buf *tui.Buffer) {
|
||||
self.Table.Draw(buf)
|
||||
self.entry.Draw(buf)
|
||||
}
|
||||
|
||||
func (self *ProcWidget) filterProcs(procs []Proc) []Proc {
|
||||
if self.filter == "" {
|
||||
return procs
|
||||
}
|
||||
var filtered []Proc
|
||||
for _, proc := range procs {
|
||||
if strings.Contains(proc.FullCommand, self.filter) || strings.Contains(fmt.Sprintf("%d", proc.Pid), self.filter) {
|
||||
filtered = append(filtered, proc)
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
func (self *ProcWidget) update() {
|
||||
procs, err := getProcs()
|
||||
if err != nil {
|
||||
|
@ -98,6 +143,7 @@ func (self *ProcWidget) update() {
|
|||
procs[i].Cpu /= float64(self.cpuCount)
|
||||
}
|
||||
|
||||
procs = self.filterProcs(procs)
|
||||
self.ungroupedProcs = procs
|
||||
self.groupedProcs = groupProcs(procs)
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user