Skip to content
This repository has been archived by the owner on Aug 29, 2020. It is now read-only.

Add support for filtering processes by substring. #147

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ snap connect gotop-cjbassi:system-observe
### Keybinds

- Quit: `q` or `<C-c>`
- Process navigation
- Process navigation:
- `k` and `<Up>`: up
- `j` and `<Down`: down
- `<C-u>`: half page up
Expand All @@ -83,10 +83,15 @@ snap connect gotop-cjbassi:system-observe
- Process actions:
- `<Tab>`: toggle process grouping
- `dd`: kill selected process or group of processes
- Process sorting
- Process sorting:
- `c`: CPU
- `m`: Mem
- `p`: PID
- Process filtering:
- /: start editing filter
- (while editing):
- <Enter> accept filter
- <C-c>: clear filter
rephorm marked this conversation as resolved.
Show resolved Hide resolved
- CPU and Mem graph scaling:
- `h`: scale in
- `l`: scale out
Expand Down
62 changes: 45 additions & 17 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"strconv"
"syscall"
"time"
"unicode/utf8"

docopt "github.com/docopt/docopt.go"
ui "github.com/gizak/termui/v3"
Expand Down Expand Up @@ -296,12 +297,8 @@ func eventLoop() {
}
}
case e := <-uiEvents:
switch e.ID {
case "q", "<C-c>":
return
case "?":
helpVisible = !helpVisible
case "<Resize>":
// Handle resize event always.
if e.ID == "<Resize>" {
payload := e.Payload.(ui.Resize)
termWidth, termHeight := payload.Width, payload.Height
if statusbar {
Expand All @@ -312,23 +309,55 @@ func eventLoop() {
}
help.Resize(payload.Width, payload.Height)
ui.Clear()

if helpVisible {
ui.Render(help)
} else {
ui.Render(grid)
if statusbar {
ui.Render(bar)
}
}
}

if helpVisible {
if proc.EditingFilter() {
if utf8.RuneCountInString(e.ID) == 1 {
proc.SetFilter(proc.Filter() + e.ID)
ui.Render(proc)
}
switch e.ID {
case "<C-c>":
proc.SetFilter("")
proc.SetEditingFilter(false)
ui.Render(proc)
case "<Enter>":
proc.SetEditingFilter(false)
ui.Render(proc)
case "<Backspace>":
if filter := proc.Filter(); filter != "" {
proc.SetFilter(filter[:len(filter)-1])
}
ui.Render(proc)
}
} else if helpVisible {
switch e.ID {
case "q", "<C-c>":
return
case "?":
ui.Clear()
ui.Render(help)
helpVisible = false
ui.Render(grid)
case "<Escape>":
helpVisible = false
ui.Render(grid)
case "<Resize>":
ui.Render(help)
}
} else {
switch e.ID {
case "q", "<C-c>":
return
case "?":
ui.Render(grid)
helpVisible = true
ui.Clear()
ui.Render(help)
case "h":
graphHorizontalScale += graphHorizontalScaleDelta
cpu.HorizontalScale = graphHorizontalScale
Expand All @@ -341,11 +370,6 @@ func eventLoop() {
mem.HorizontalScale = graphHorizontalScale
ui.Render(cpu, mem)
}
case "<Resize>":
ui.Render(grid)
if statusbar {
ui.Render(bar)
}
case "<MouseLeft>":
payload := e.Payload.(ui.Mouse)
proc.HandleClick(payload.X, payload.Y)
Expand Down Expand Up @@ -389,6 +413,10 @@ func eventLoop() {
case "m", "c", "p":
proc.ChangeProcSortMethod(w.ProcSortMethod(e.ID))
ui.Render(proc)
case "/":
proc.SetFilter("")
proc.SetEditingFilter(true)
ui.Render(proc)
}

if previousKey == e.ID {
Expand Down
12 changes: 9 additions & 3 deletions src/widgets/help.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -24,11 +24,17 @@ Process actions:
- <Tab>: toggle process grouping
- dd: kill selected process or group of processes

Process sorting
Process sorting:
- c: CPU
- m: Mem
- p: PID

Process filtering:
- /: start editing filter
- (while editing):
- <Enter>: accept filter
- <C-c> clear filter

CPU and Mem graph scaling:
- h: scale in
- l: scale out
Expand All @@ -46,7 +52,7 @@ func NewHelpMenu() *HelpMenu {

func (self *HelpMenu) Resize(termWidth, termHeight int) {
textWidth := 53
textHeight := 22
textHeight := strings.Count(KEYBINDS, "\n") + 1
x := (termWidth - textWidth) / 2
y := (termHeight - textHeight) / 2

Expand Down
75 changes: 75 additions & 0 deletions src/widgets/proc.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,26 @@ package widgets

import (
"fmt"
"image"
"log"
"os/exec"
"sort"
"strconv"
"strings"
"time"
"unicode/utf8"

psCPU "github.com/shirou/gopsutil/cpu"

ui "github.com/cjbassi/gotop/src/termui"
"github.com/cjbassi/gotop/src/utils"
tui "github.com/gizak/termui/v3"
)

const (
UP_ARROW = "▲"
DOWN_ARROW = "▼"
ELLIPSIS = "…"
)

type ProcSortMethod string
Expand All @@ -40,6 +45,8 @@ type ProcWidget struct {
cpuCount int
updateInterval time.Duration
sortMethod ProcSortMethod
filter string
editingFilter bool
groupedProcs []Proc
ungroupedProcs []Proc
showGroupedProcs bool
Expand All @@ -56,6 +63,8 @@ func NewProcWidget() *ProcWidget {
cpuCount: cpuCount,
sortMethod: ProcSortCpu,
showGroupedProcs: true,
filter: "",
editingFilter: false,
}
self.Title = " Processes "
self.ShowCursor = true
Expand Down Expand Up @@ -86,6 +95,71 @@ func NewProcWidget() *ProcWidget {
return self
}

func (self *ProcWidget) Filter() string {
return self.filter
}

func (self *ProcWidget) SetFilter(filter string) {
self.filter = filter
}

func (self *ProcWidget) EditingFilter() bool {
return self.editingFilter
}

func (self *ProcWidget) SetEditingFilter(editing bool) {
self.editingFilter = editing
if !editing {
self.update()
}
}

func (self *ProcWidget) Draw(buf *tui.Buffer) {
self.Table.Draw(buf)
if self.filter != "" || self.editingFilter {
self.drawFilter(buf)
}
}

func (self *ProcWidget) drawFilter(buf *tui.Buffer) {
style := self.TitleStyle
label := "Filter: "
if self.editingFilter {
label = "[ Filter: "
style = tui.NewStyle(style.Fg, style.Bg, tui.ModifierBold)
}

p := image.Pt(self.Min.X+2, self.Max.Y-1)
buf.SetString(label, style, p)
p.X += utf8.RuneCountInString(label)

maxLen := self.Max.X - p.X - 4
filter := self.filter
if l := utf8.RuneCountInString(filter); l > maxLen {
filter = ELLIPSIS + filter[l-maxLen+1:]
}
buf.SetString(filter, self.TitleStyle, p)
p.X += utf8.RuneCountInString(filter)

if self.editingFilter {
remaining := self.Max.X - 2 - p.X
buf.SetString(fmt.Sprintf("%*s", remaining, "]"), style, p)
}
}

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 {
Expand All @@ -98,6 +172,7 @@ func (self *ProcWidget) update() {
procs[i].Cpu /= float64(self.cpuCount)
}

procs = self.filterProcs(procs)
self.ungroupedProcs = procs
self.groupedProcs = groupProcs(procs)

Expand Down