Skip to content

Commit 6bf3f0e

Browse files
authored
Merge pull request #9 from metaspartan/development
mactop v2.0.3
2 parents e59a4ef + adb795f commit 6bf3f0e

File tree

15 files changed

+750
-219
lines changed

15 files changed

+750
-219
lines changed

README.md

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,18 @@
3333
- Easy-to-read terminal UI
3434
- **15 Layouts**: (`L` to cycle layouts)
3535
- **Persistent Settings**: Remembers your Layout and Theme choice across restarts
36-
- Customizable UI color (green, red, blue, skyblue, magenta, yellow, gold, silver, white, lime, orange, violet, pink, and more) (`C` to cycle colors)
37-
- Customizable background color (`B` to cycle colors)
36+
- Customizable UI color (green, red, blue, skyblue, magenta, yellow, gold, silver, white, lime, orange, violet, pink, and more) (`c` to cycle colors)
37+
- Customizable background color (`b` to cycle colors)
3838
- Customizable update interval (default is 1000ms) (`-` or `=` to speed up, `+` to slow down)
3939
- Process list matching htop format (VIRT in GB, CPU normalized by core count)
40-
- **Process Management**: Kill processes directly from the UI (F9). List pauses while selecting.
40+
- **Process Management**: Kill processes directly from the UI (F9) with safe confirmation.
41+
- **Process Filter**: Search and filter processes by name (`/`)
42+
- **Navigation**: Enhanced Vim-like navigation (`g` top, `G` bottom, `j`/`k` scroll)
4143
- **Headless Mode**: Output JSON metrics to stdout for scripting/logging (`--headless`)
4244
- **JSON Formatting**: Pretty print JSON output (`--pretty`) or set collection count (`--count <n>`)
43-
- Party Mode (Randomly cycles through colors) (P to toggle)
45+
- **Output Formats**: JSON (default), YAML, XML, CSV, and [TOON](https://github.com/toon-format/toon) (`--format <format>`)
46+
- **Freeze**: Pause/Resume process list updates (`f`)
47+
- Party Mode (Randomly cycles through colors) (`p` to toggle)
4448
- Optional Prometheus Metrics server (default is disabled) (`-p <port>` or `--prometheus <port>`)
4549
- Support for all Apple Silicon models
4650
- **Auto-detect Light/Dark Mode**: Automatically adjusts UI colors based on your terminal's background color or system theme.
@@ -109,11 +113,15 @@ mactop --headless --count 1
109113

110114
# Run continuously with pretty printing
111115
mactop --headless --pretty
116+
117+
# Run with different output formats (json, yaml, xml, toon)
118+
mactop --headless --format toon
112119
```
113120

114121
## mactop Flags
115122

116-
- `--headless`: Run in headless mode (no TUI, output JSON to stdout).
123+
- `--headless`: Run in headless mode (no TUI, output to stdout).
124+
- `--format`: Output format for headless mode (json, yaml, xml, toon). Default is json.
117125
- `--count`: Number of samples to collect in headless mode (0 = infinite).
118126
- `--pretty`: Pretty print JSON output in headless mode.
119127
- `--interval` or `-i`: Set the update interval in milliseconds. Default is 1000.
@@ -140,6 +148,8 @@ Use the following keys to interact with the application while its running:
140148
- `-`: Decrease update interval (faster updates).
141149
- `F9`: Kill the currently selected process (pauses updates while selecting).
142150
- `Arrow Keys` or `h/j/k/l`: Navigate the process list and select columns.
151+
- `g` / `G`: Jump to the top or bottom of the process list.
152+
- `/`: Search/Filter the process list by name (Esc to clear).
143153
- `Enter` or `Space`: Sort by the selected column.
144154
- `h` or `?`: Toggle the help menu.
145155

go.mod

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ module github.com/metaspartan/mactop/v2
33
go 1.25.4
44

55
require (
6-
github.com/metaspartan/gotui/v4 v4.2.4
6+
github.com/metaspartan/gotui/v5 v5.0.0
77
github.com/prometheus/client_golang v1.23.2
88
github.com/shirou/gopsutil/v4 v4.25.11
9+
github.com/toon-format/toon-go v0.0.0-20251202084852-7ca0e27c4e8c
910
golang.org/x/term v0.38.0
11+
gopkg.in/yaml.v3 v3.0.1
1012
)
1113

1214
require (
@@ -15,7 +17,7 @@ require (
1517
github.com/clipperhouse/uax29/v2 v2.2.0 // indirect
1618
github.com/ebitengine/purego v0.9.1 // indirect
1719
github.com/gdamore/encoding v1.0.1 // indirect
18-
github.com/gdamore/tcell/v2 v2.13.4 // indirect
20+
github.com/gdamore/tcell/v3 v3.0.5 // indirect
1921
github.com/go-ole/go-ole v1.2.6 // indirect
2022
github.com/kr/text v0.2.0 // indirect
2123
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect

go.sum

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s
1111
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
1212
github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw=
1313
github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=
14-
github.com/gdamore/tcell/v2 v2.13.4 h1:k4fdtdHGvLsLr2RttPnWEGTZEkEuTaL+rL6AOVFyRWU=
15-
github.com/gdamore/tcell/v2 v2.13.4/go.mod h1:+Wfe208WDdB7INEtCsNrAN6O2m+wsTPk1RAovjaILlo=
14+
github.com/gdamore/tcell/v3 v3.0.5 h1:odOxRkUnMNFN0+kbx2bW7SxBAVUgAwVCVRwMJ92gzxE=
15+
github.com/gdamore/tcell/v3 v3.0.5/go.mod h1:hPfjyFARu5K9vLzjN5TrYAoK/D9dZmqRbQkevGvK7oQ=
1616
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
1717
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
1818
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
@@ -32,8 +32,8 @@ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ
3232
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
3333
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
3434
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
35-
github.com/metaspartan/gotui/v4 v4.2.4 h1:kEweCQdvL04+V9AunVZmQzxLbVohflrbWugBWUeC4X0=
36-
github.com/metaspartan/gotui/v4 v4.2.4/go.mod h1:V8E8/v0XdvOUvvmTpuwwX7OkgOXVI+41wl1+Eq8vecE=
35+
github.com/metaspartan/gotui/v5 v5.0.0 h1:m03sEApHyycxHs9DzAjA+R2l/9VURTtjGMMk2yPjD9I=
36+
github.com/metaspartan/gotui/v5 v5.0.0/go.mod h1:PasNHuk3/HQ2vQTZOmWqnAKLQtOaP0paN4yHPqDJ4q8=
3737
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
3838
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
3939
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
@@ -62,6 +62,8 @@ github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYI
6262
github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
6363
github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
6464
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
65+
github.com/toon-format/toon-go v0.0.0-20251202084852-7ca0e27c4e8c h1:D8lDFovBMZywze1eh9iwMLcYor5f11mHBocLhO7cBe8=
66+
github.com/toon-format/toon-go v0.0.0-20251202084852-7ca0e27c4e8c/go.mod h1:j/BOnpF2ihnz4lELs99h9mwGJBx/zdleOUCnLLRPCsc=
6567
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
6668
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
6769
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=

internal/app/app.go

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ import (
1616

1717
"sync"
1818

19-
ui "github.com/metaspartan/gotui/v4"
20-
w "github.com/metaspartan/gotui/v4/widgets"
19+
ui "github.com/metaspartan/gotui/v5"
20+
w "github.com/metaspartan/gotui/v5/widgets"
2121
)
2222

2323
var renderMutex sync.Mutex
@@ -151,6 +151,23 @@ func setupUI() {
151151
eCoreCount,
152152
pCoreCount,
153153
)
154+
155+
confirmModal = w.NewModal("CONFIRM KILL")
156+
confirmModal.Title = " CONFIRM "
157+
confirmModal.Border = true
158+
confirmModal.BorderRounded = true
159+
confirmModal.BorderStyle.Fg = ui.ColorRed
160+
confirmModal.BorderStyle.Bg = ui.ColorBlack
161+
confirmModal.TextStyle.Fg = ui.ColorWhite
162+
confirmModal.TextStyle.Bg = ui.ColorBlack
163+
confirmModal.ActiveButtonIndex = 1 // Default to No (Safe)
164+
165+
_ = confirmModal.AddButton("Yes", func() {
166+
// Callback logic will be handled elsewhere or reused
167+
})
168+
_ = confirmModal.AddButton("No", func() {
169+
// Callback logic
170+
})
154171
}
155172

156173
func updateModelText() {
@@ -219,17 +236,22 @@ func updateHelpText() {
219236
"- c: Cycle through UI color themes\n"+
220237
"- p: Toggle party mode (color cycling)\n"+
221238
"- l: Cycle through the 6 available layouts\n"+
239+
"- F9: Kill selected process (y/n confirm)\n"+ // Updated text
240+
"- /: Search process list\n"+ // Added help
241+
"- g/G: Jump to top/bottom of process list\n"+ // Added help
222242
"- + or -: Adjust update interval (faster/slower)\n"+
223-
"- F9: Kill selected process\n"+
224243
"- h or ?: Toggle this help menu\n"+
225244
"- q or <C-c>: Quit the application\n\n"+
226245
"Start Flags:\n"+
227246
"--help, -h: Show this help menu\n"+
228247
"--version, -v: Show the version of mactop\n"+
229248
"--interval, -i: Set the update interval in milliseconds. Default is 1000.\n"+
230249
"--prometheus, -p: Set and enable a Prometheus metrics port. Default is none. (e.g. --prometheus=9090)\n"+
231-
"--headless: Run in headless mode (no TUI, output JSON to stdout)\n"+
250+
"--headless: Run in headless mode (no TUI, output to stdout)\n"+
251+
"--format: Output format for headless mode (json, yaml, xml, csv, toon). Default is json.\n"+
252+
"--pretty: Pretty print output in headless mode\n"+
232253
"--count: Number of samples to collect in headless mode (0 = infinite)\n"+
254+
"--dump-ioreport, -d: Dump all available IOReport channels and exit\n"+
233255
"--unit-network: Network unit: auto, byte, kb, mb, gb (default: auto)\n"+
234256
"--unit-disk: Disk unit: auto, byte, kb, mb, gb (default: auto)\n"+
235257
"--unit-temp: Temperature unit: celsius, fahrenheit (default: celsius)\n"+
@@ -313,7 +335,11 @@ func renderUI() {
313335
defer renderMutex.Unlock()
314336
w, h := ui.TerminalDimensions()
315337
if w > 2 && h > 2 {
316-
ui.Render(mainBlock, grid)
338+
if killPending {
339+
ui.Render(mainBlock, grid, confirmModal) // Render on top
340+
} else {
341+
ui.Render(mainBlock, grid)
342+
}
317343
} else {
318344
ui.Render(mainBlock)
319345
}
@@ -346,8 +372,9 @@ func Run() {
346372
flag.StringVar(&prometheusPort, "prometheus", "", "Port to run Prometheus metrics server on (e.g. :9090)")
347373
flag.StringVar(&prometheusPort, "p", "", "Port to run Prometheus metrics server on (e.g. :9090)")
348374
flag.BoolVar(&headless, "headless", false, "Run in headless mode (no TUI, output JSON to stdout)")
349-
flag.BoolVar(&headlessPretty, "pretty", false, "Pretty print JSON output in headless mode")
375+
flag.BoolVar(&headlessPretty, "pretty", false, "Pretty print output in headless mode")
350376
flag.IntVar(&headlessCount, "count", 0, "Number of samples to collect in headless mode (0 = infinite)")
377+
flag.StringVar(&headlessFormat, "format", "json", "Output format for headless mode: json, yaml, xml, csv, toon")
351378
flag.IntVar(&updateInterval, "interval", 1000, "Update interval in milliseconds")
352379
flag.IntVar(&updateInterval, "i", 1000, "Update interval in milliseconds")
353380
flag.Bool("d", false, "Dump all available IOReport channels and exit")

internal/app/catppuccin.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package app
22

33
import (
4-
ui "github.com/metaspartan/gotui/v4"
4+
ui "github.com/metaspartan/gotui/v5"
55
)
66

77
type CatppuccinPalette struct {

internal/app/events.go

Lines changed: 29 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import (
44
"os"
55
"time"
66

7-
ui "github.com/metaspartan/gotui/v4"
7+
ui "github.com/metaspartan/gotui/v5"
88
)
99

1010
func startBackgroundUpdates(done chan struct{}) {
@@ -53,8 +53,11 @@ func startBackgroundUpdates(done chan struct{}) {
5353
select {
5454
case processes := <-processMetricsChan:
5555
renderMutex.Lock()
56-
if processList.SelectedRow == 0 {
56+
if !isFrozen && !killPending {
5757
lastProcesses = processes
58+
if searchText != "" {
59+
refreshFilteredProcesses()
60+
}
5861
updateProcessList()
5962
}
6063
renderMutex.Unlock()
@@ -85,7 +88,11 @@ func updateLayout(w, h int) {
8588
func drawScreen(w, h int) {
8689
ui.Clear()
8790
if w > 2 && h > 2 {
88-
ui.Render(mainBlock, grid)
91+
if killPending {
92+
ui.Render(mainBlock, grid, confirmModal)
93+
} else {
94+
ui.Render(mainBlock, grid)
95+
}
8996
} else {
9097
ui.Render(mainBlock)
9198
}
@@ -115,62 +122,17 @@ func handleModeKeys(key string, done chan struct{}) {
115122
case "p":
116123
togglePartyMode()
117124
case "c":
118-
renderMutex.Lock()
119-
w, h := ui.TerminalDimensions()
120-
updateLayout(w, h)
121-
cycleTheme()
122-
renderMutex.Unlock()
123-
renderMutex.Lock()
124-
updateProcessList()
125-
w, h = ui.TerminalDimensions()
126-
drawScreen(w, h)
127-
renderMutex.Unlock()
125+
handleThemeCycle()
128126
case "l":
129-
renderMutex.Lock()
130-
cycleLayout()
131-
renderMutex.Unlock()
132-
saveConfig()
133-
renderMutex.Lock()
134-
w, h := ui.TerminalDimensions()
135-
drawScreen(w, h)
136-
renderMutex.Unlock()
127+
handleLayoutCycle()
137128
case "h", "?":
138129
toggleHelpMenu()
139130
case "i":
140-
141-
renderMutex.Lock()
142-
if currentConfig.DefaultLayout == LayoutInfo {
143-
if lastActiveLayout != "" {
144-
currentConfig.DefaultLayout = lastActiveLayout
145-
} else {
146-
currentConfig.DefaultLayout = LayoutDefault
147-
}
148-
for i, layout := range layoutOrder {
149-
if layout == currentConfig.DefaultLayout {
150-
currentLayoutNum = i
151-
break
152-
}
153-
}
154-
} else {
155-
lastActiveLayout = currentConfig.DefaultLayout
156-
currentConfig.DefaultLayout = LayoutInfo
157-
for i, layout := range layoutOrder {
158-
if layout == LayoutInfo {
159-
currentLayoutNum = i
160-
break
161-
}
162-
}
163-
}
164-
applyLayout(currentConfig.DefaultLayout)
165-
w, h := ui.TerminalDimensions()
166-
drawScreen(w, h)
167-
renderMutex.Unlock()
131+
toggleInfoLayout()
168132
case "b":
169-
renderMutex.Lock()
170-
cycleBackground()
171-
w, h := ui.TerminalDimensions()
172-
drawScreen(w, h)
173-
renderMutex.Unlock()
133+
handleBackgroundCycle()
134+
case "f":
135+
toggleFreeze()
174136
}
175137
}
176138

@@ -206,20 +168,25 @@ func handleIntervalKeys(key string) {
206168

207169
func handleKeyboardEvent(e ui.Event, done chan struct{}) {
208170
key := e.ID
209-
fakeEvent := ui.Event{Type: ui.KeyboardEvent, ID: key}
171+
172+
// Delegate to process list events (handles search/modal/navigation)
210173
renderMutex.Lock()
211-
handleProcessListEvents(fakeEvent)
174+
handleProcessListEvents(e)
175+
176+
if killPending || searchMode {
177+
w, h := ui.TerminalDimensions()
178+
drawScreen(w, h)
179+
renderMutex.Unlock()
180+
return
181+
}
182+
212183
ui.Clear()
213184
w, h := ui.TerminalDimensions()
214-
if w > 2 && h > 2 {
215-
ui.Render(mainBlock, grid)
216-
} else {
217-
ui.Render(mainBlock)
218-
}
185+
drawScreen(w, h)
219186
renderMutex.Unlock()
220187

221188
switch key {
222-
case "q", "<C-c>", "r", "p", "c", "l", "h", "?", "i", "b":
189+
case "q", "<C-c>", "r", "p", "c", "l", "h", "?", "i", "b", "f":
223190
handleModeKeys(key, done)
224191
case "-", "_", "+", "=":
225192
handleIntervalKeys(key)

0 commit comments

Comments
 (0)