Code cleanup

This commit is contained in:
Caleb Bassi 2018-03-03 17:05:52 -08:00
parent e79f0e0a83
commit 47a98de44b
19 changed files with 202 additions and 184 deletions

View File

@ -69,16 +69,16 @@ Feel free to add a new one. You can use 256 colors, bold, underline, and reverse
- increase height of sparkline depending on widget size - increase height of sparkline depending on widget size
* Process List * Process List
- memory total goes above 100% - memory total goes above 100%
* general * Graphs
- command line option to set polling interval for CPU and mem
- command line option to only show processes, CPU, and mem
- zooming in and out of graphs - zooming in and out of graphs
- command line option to set polling interval for CPU and mem
* general
- command line option to only show processes, CPU, and mem
- gopsutil cross-compiling issue on linux_amd64 for darwin_i386 - gopsutil cross-compiling issue on linux_amd64 for darwin_i386
* code cleanup * try to get the drawille fork merged upstream
- more comments * termui code cleanup
- termui buffers should ignore characters set outside the widget area - termui buffers should ignore characters set outside the widget area
- ignore writes or give an error? - ignore writes or give an error?
- termui Blocks should be indexed at 0, and maybe change their X and Y variables too - termui Blocks should be indexed at 0, and maybe change their X and Y variables too
- try to get the drawille fork merged upstream
- draw borders and label after widget contents - draw borders and label after widget contents
- only merge buffers that are within the original's area - only merge buffers that are within the original's area

View File

@ -5,7 +5,7 @@ package colorschemes
-1 = clear -1 = clear
You can combine a color with 'Bold', 'Underline', or 'Reverse' by using bitwise OR ('|'). You can combine a color with 'Bold', 'Underline', or 'Reverse' by using bitwise OR ('|') and the name of the attribute.
For example, to get Bold red Labels, you would do 'Labels: 2 | Bold'. For example, to get Bold red Labels, you would do 'Labels: 2 | Bold'.
Once you've created a colorscheme, add an entry for it in the `handleColorscheme` function Once you've created a colorscheme, add an entry for it in the `handleColorscheme` function
@ -29,7 +29,7 @@ type Colorscheme struct {
BorderLabel int BorderLabel int
BorderLine int BorderLine int
// Try to add at least 8 // should add at least 8 here
CPULines []int CPULines []int
MainMem int MainMem int
@ -41,7 +41,7 @@ type Colorscheme struct {
DiskBar int DiskBar int
// Temperature colors depending on if it's over a certain threshold // colors the temperature number a different color if it's over a certain threshold
TempLow int TempLow int
TempHigh int TempHigh int
} }

View File

@ -16,13 +16,10 @@ import (
const VERSION = "1.0.1" const VERSION = "1.0.1"
var ( var (
// for when terminal is resized termResized = make(chan bool, 1)
resized = make(chan bool, 1)
// for when help menu is toggled
helpToggled = make(chan bool, 1) helpToggled = make(chan bool, 1)
// whether help menu is toggled helpVisible = false
helpStatus = false
// proc widget takes longer to load, wait to render until it loads data // proc widget takes longer to load, wait to render until it loads data
procLoaded = make(chan bool, 1) procLoaded = make(chan bool, 1)
@ -41,7 +38,6 @@ var (
help *w.HelpMenu help *w.HelpMenu
) )
// Sets up docopt which is a command line argument parser
func cliArguments() { func cliArguments() {
usage := ` usage := `
Usage: gotop [options] Usage: gotop [options]
@ -101,13 +97,13 @@ func keyBinds() {
// toggles help menu // toggles help menu
ui.On("?", func(e ui.Event) { ui.On("?", func(e ui.Event) {
helpToggled <- true helpToggled <- true
helpStatus = !helpStatus helpVisible = !helpVisible
}) })
// hides help menu // hides help menu
ui.On("<escape>", func(e ui.Event) { ui.On("<escape>", func(e ui.Event) {
if helpStatus { if helpVisible {
helpToggled <- true helpToggled <- true
helpStatus = false helpVisible = false
} }
}) })
} }
@ -126,11 +122,9 @@ func termuiColors() {
} }
func widgetColors() { func widgetColors() {
// memory widget colors
mem.LineColor["Main"] = ui.Color(colorscheme.MainMem) mem.LineColor["Main"] = ui.Color(colorscheme.MainMem)
mem.LineColor["Swap"] = ui.Color(colorscheme.SwapMem) mem.LineColor["Swap"] = ui.Color(colorscheme.SwapMem)
// cpu widget colors
LineColor := make(map[string]ui.Color) LineColor := make(map[string]ui.Color)
for i := 0; i < len(cpu.Data); i++ { for i := 0; i < len(cpu.Data); i++ {
LineColor[fmt.Sprintf("CPU%d", i+1)] = ui.Color(colorscheme.CPULines[i]) LineColor[fmt.Sprintf("CPU%d", i+1)] = ui.Color(colorscheme.CPULines[i])
@ -158,7 +152,6 @@ func main() {
widgetColors() widgetColors()
// blocks till loaded
<-procLoaded <-procLoaded
// inits termui // inits termui
@ -180,43 +173,43 @@ func main() {
help.XOffset = (ui.Body.Width - help.X) / 2 help.XOffset = (ui.Body.Width - help.X) / 2
help.YOffset = (ui.Body.Height - help.Y) / 2 help.YOffset = (ui.Body.Height - help.Y) / 2
resized <- true termResized <- true
}) })
// All rendering done here // all rendering done here
go func() { go func() {
ui.Render(ui.Body) ui.Render(ui.Body)
drawTick := time.NewTicker(time.Second) drawTick := time.NewTicker(time.Second)
for { for {
select { select {
case <-helpToggled: case <-helpToggled:
if helpStatus { if helpVisible {
ui.Clear() ui.Clear()
ui.Render(help) ui.Render(help)
} else { } else {
ui.Render(ui.Body) ui.Render(ui.Body)
} }
case <-resized: case <-termResized:
if !helpStatus { if !helpVisible {
ui.Clear() ui.Clear()
ui.Render(ui.Body) ui.Render(ui.Body)
} else if helpStatus { } else if helpVisible {
ui.Clear() ui.Clear()
ui.Render(help) ui.Render(help)
} }
case <-keyPressed: case <-keyPressed:
if !helpStatus { if !helpVisible {
ui.Render(proc) ui.Render(proc)
} }
case <-drawTick.C: case <-drawTick.C:
if !helpStatus { if !helpVisible {
ui.Render(ui.Body) ui.Render(ui.Body)
} }
} }
} }
}() }()
// handles kill signal // handles os kill signal
c := make(chan os.Signal, 2) c := make(chan os.Signal, 2)
signal.Notify(c, os.Interrupt, syscall.SIGTERM) signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() { go func() {

View File

@ -77,10 +77,12 @@ func (b *Block) SetGrid(c0, r0, c1, r1 int) {
b.Grid = image.Rect(c0, r0, c1, r1) b.Grid = image.Rect(c0, r0, c1, r1)
} }
// GetXOffset implements Bufferer interface.
func (b *Block) GetXOffset() int { func (b *Block) GetXOffset() int {
return b.XOffset return b.XOffset
} }
// GetYOffset implements Bufferer interface.
func (b *Block) GetYOffset() int { func (b *Block) GetYOffset() int {
return b.YOffset return b.YOffset
} }

View File

@ -17,14 +17,17 @@ type Buffer struct {
CellMap map[image.Point]Cell CellMap map[image.Point]Cell
} }
// NewCell returne a new Cell given all necessary fields.
func NewCell(ch rune, Fg, Bg Color) Cell { func NewCell(ch rune, Fg, Bg Color) Cell {
return Cell{ch, Fg, Bg} return Cell{ch, Fg, Bg}
} }
// NewBuffer returns a new empty Buffer.
func NewBuffer() *Buffer { func NewBuffer() *Buffer {
return &Buffer{ return &Buffer{
CellMap: make(map[image.Point]Cell), CellMap: make(map[image.Point]Cell),
Area: image.Rectangle{}} Area: image.Rectangle{},
}
} }
// NewFilledBuffer returns a new Buffer filled with the given Cell. // NewFilledBuffer returns a new Buffer filled with the given Cell.
@ -32,16 +35,11 @@ func NewFilledBuffer(x0, y0, x1, y1 int, c Cell) *Buffer {
buf := NewBuffer() buf := NewBuffer()
buf.Area.Min = image.Pt(x0, y0) buf.Area.Min = image.Pt(x0, y0)
buf.Area.Max = image.Pt(x1, y1) buf.Area.Max = image.Pt(x1, y1)
buf.Fill(c)
for x := buf.Area.Min.X; x < buf.Area.Max.X; x++ {
for y := buf.Area.Min.Y; y < buf.Area.Max.Y; y++ {
buf.SetCell(x, y, c)
}
}
return buf return buf
} }
// Set assigns a Cell to (x,y). // SetCell assigns a Cell to (x,y).
func (b *Buffer) SetCell(x, y int, c Cell) { func (b *Buffer) SetCell(x, y int, c Cell) {
b.CellMap[image.Pt(x, y)] = c b.CellMap[image.Pt(x, y)] = c
} }
@ -82,7 +80,7 @@ func (b *Buffer) Merge(bs ...*Buffer) {
} }
} }
// MergeWithOffset merges the given buffer at a certain position on the given buffer. // MergeWithOffset merges a Buffer onto another with an offset.
func (b *Buffer) MergeWithOffset(buf *Buffer, xOffset, yOffset int) { func (b *Buffer) MergeWithOffset(buf *Buffer, xOffset, yOffset int) {
for p, c := range buf.CellMap { for p, c := range buf.CellMap {
b.SetCell(p.X+xOffset, p.Y+yOffset, c) b.SetCell(p.X+xOffset, p.Y+yOffset, c)

View File

@ -1,21 +1,22 @@
package termui package termui
// Color is an integer in the range -1 to 255 // Color is an integer in the range -1 to 255.
type Color int type Color int
// ColorDefault = clear // ColorDefault = clear
const ColorDefault = -1 const ColorDefault = -1
// Copied from termbox // Copied from termbox. Attributes that can be bitwise OR'ed with a color.
const ( const (
AttrBold Color = 1 << (iota + 9) AttrBold Color = 1 << (iota + 9)
AttrUnderline AttrUnderline
AttrReverse AttrReverse
) )
// Theme is assigned to the current theme // Theme is assigned to the current theme.
var Theme = DefaultTheme var Theme = DefaultTheme
// DefaultTheme implements a generic set of colors to use by default.
var DefaultTheme = Colorscheme{ var DefaultTheme = Colorscheme{
Fg: 7, Fg: 7,
Bg: -1, Bg: -1,

View File

@ -20,7 +20,7 @@ type EventStream struct {
eventQueue chan tb.Event // list of events from termbox eventQueue chan tb.Event // list of events from termbox
} }
// Event includes only the termbox.Event attributes we need. // Event is a copy of termbox.Event that only contains the fields we need.
type Event struct { type Event struct {
Key string Key string
Width int Width int
@ -76,7 +76,7 @@ func Loop() {
} }
} }
// StopLoop stops the events Loop // StopLoop stops the event loop.
func StopLoop() { func StopLoop() {
eventStream.stopLoop <- true eventStream.stopLoop <- true
} }
@ -156,21 +156,27 @@ func convertTermboxMouseValue(e tb.Event) string {
return "" return ""
} }
// convertTermboxEvent turns a termbox event into a termui event // convertTermboxEvent turns a termbox event into a termui event.
func convertTermboxEvent(e tb.Event) Event { func convertTermboxEvent(e tb.Event) Event {
ne := Event{} // new event var ne Event
switch e.Type { switch e.Type {
case tb.EventKey: case tb.EventKey:
ne.Key = convertTermboxKeyValue(e) ne = Event{
Key: convertTermboxKeyValue(e),
}
case tb.EventMouse: case tb.EventMouse:
ne.Key = convertTermboxMouseValue(e) ne = Event{
ne.MouseX = e.MouseX Key: convertTermboxMouseValue(e),
ne.MouseY = e.MouseY MouseX: e.MouseX,
MouseY: e.MouseY,
}
case tb.EventResize: case tb.EventResize:
ne.Key = "resize" ne = Event{
ne.Width = e.Width Key: "resize",
ne.Height = e.Height Width: e.Width,
Height: e.Height,
}
} }
return ne return ne

View File

@ -35,10 +35,8 @@ func (g *Gauge) Buffer() *Buffer {
// plot percentage // plot percentage
s := strconv.Itoa(g.Percent) + "%" + g.Description s := strconv.Itoa(g.Percent) + "%" + g.Description
s = MaxString(s, g.X) s = MaxString(s, g.X)
y := (g.Y + 1) / 2 y := (g.Y + 1) / 2
x := ((g.X - len(s)) + 1) / 2 x := ((g.X - len(s)) + 1) / 2
for i, char := range s { for i, char := range s {
bg := g.Bg bg := g.Bg
fg := g.Fg fg := g.Fg

View File

@ -19,11 +19,12 @@ type Grid struct {
Rows int Rows int
} }
// NewGrid creates an empty Grid.
func NewGrid() *Grid { func NewGrid() *Grid {
return &Grid{} return &Grid{}
} }
// Set takes a widget along with it's grid dimensions to be controlled by the grid. // Set assigns a widget and its grid dimensions to Grid.
func (g *Grid) Set(x0, y0, x1, y1 int, widget GridBufferer) { func (g *Grid) Set(x0, y0, x1, y1 int, widget GridBufferer) {
if widget == nil { if widget == nil {
return return
@ -38,14 +39,14 @@ func (g *Grid) Set(x0, y0, x1, y1 int, widget GridBufferer) {
g.Widgets = append(g.Widgets, widget) g.Widgets = append(g.Widgets, widget)
} }
// Resize resizes each widget in the grid's control. // Resize resizes each widget in the grid.
func (g *Grid) Resize() { func (g *Grid) Resize() {
for _, w := range g.Widgets { for _, w := range g.Widgets {
w.Resize(g.Width, g.Height, g.Cols, g.Rows) w.Resize(g.Width, g.Height, g.Cols, g.Rows)
} }
} }
// Buffer implements Bufferer interface and merges each widget into one buffer. // Buffer implements the Bufferer interface by merging each widget in Grid into one buffer.
func (g *Grid) Buffer() *Buffer { func (g *Grid) Buffer() *Buffer {
buf := NewFilledBuffer(0, 0, g.Width, g.Height, Cell{' ', ColorDefault, Theme.Bg}) buf := NewFilledBuffer(0, 0, g.Width, g.Height, Cell{' ', ColorDefault, Theme.Bg})
for _, w := range g.Widgets { for _, w := range g.Widgets {
@ -54,10 +55,12 @@ func (g *Grid) Buffer() *Buffer {
return buf return buf
} }
// GetXOffset implements Bufferer interface.
func (g *Grid) GetXOffset() int { func (g *Grid) GetXOffset() int {
return 0 return 0
} }
// GetYOffset implements Bufferer interface.
func (g *Grid) GetYOffset() int { func (g *Grid) GetYOffset() int {
return 0 return 0
} }

View File

@ -7,7 +7,7 @@ import (
drawille "github.com/cjbassi/drawille-go" drawille "github.com/cjbassi/drawille-go"
) )
// LineGraph implements a graph of data points. // LineGraph implements a line graph of data points.
type LineGraph struct { type LineGraph struct {
*Block *Block
Data map[string][]float64 Data map[string][]float64
@ -27,17 +27,19 @@ func NewLineGraph() *LineGraph {
} }
} }
// renderPoints plots and interpolates data points. // Buffer implements Bufferer interface.
func (lc *LineGraph) Buffer() *Buffer { func (lc *LineGraph) Buffer() *Buffer {
buf := lc.Block.Buffer() buf := lc.Block.Buffer()
// we render each data point on to the canvas then copy over the braille to the buffer at the end
// fyi braille characters have 2x4 dots for each character
c := drawille.NewCanvas() c := drawille.NewCanvas()
// used to keep track of colors but not write them to the buffer until the end // used to keep track of the braille colors until the end when we render the braille to the buffer
colors := make([][]Color, lc.X+2) colors := make([][]Color, lc.X+2)
for i := range colors { for i := range colors {
colors[i] = make([]Color, lc.Y+2) colors[i] = make([]Color, lc.Y+2)
} }
// Sort the series so that overlapping data will overlap the same way each time // sort the series so that overlapping data will overlap the same way each time
seriesList := make([]string, len(lc.Data)) seriesList := make([]string, len(lc.Data))
i := 0 i := 0
for seriesName := range lc.Data { for seriesName := range lc.Data {
@ -46,7 +48,7 @@ func (lc *LineGraph) Buffer() *Buffer {
} }
sort.Strings(seriesList) sort.Strings(seriesList)
// draw lines in reverse order so the first one is on top // draw lines in reverse order so that the first color defined in the colorscheme is on top
for i := len(seriesList) - 1; i >= 0; i-- { for i := len(seriesList) - 1; i >= 0; i-- {
seriesName := seriesList[i] seriesName := seriesList[i]
seriesData := lc.Data[seriesName] seriesData := lc.Data[seriesName]
@ -61,13 +63,12 @@ func (lc *LineGraph) Buffer() *Buffer {
for i := len(seriesData) - 1; i >= 0; i-- { for i := len(seriesData) - 1; i >= 0; i-- {
x := ((lc.X + 1) * 2) - 1 - (((len(seriesData) - 1) - i) * 5) x := ((lc.X + 1) * 2) - 1 - (((len(seriesData) - 1) - i) * 5)
y := ((lc.Y + 1) * 4) - 1 - int((float64((lc.Y)*4)-1)*(seriesData[i]/100)) y := ((lc.Y + 1) * 4) - 1 - int((float64((lc.Y)*4)-1)*(seriesData[i]/100))
// stop rendering at the left-most wall if x < 0 { // stop rendering at the left-most wall
if x < 0 {
break break
} }
if lastY == -1 { // if this is the first point if lastY == -1 { // if this is the first point
c.Set(x, y) c.Set(x, y)
colors[x/2][y/4] = seriesLineColor // divide by 2 and 4 due to 2x4 dots in braille characters colors[x/2][y/4] = seriesLineColor
} else { } else {
c.DrawLine(lastX, lastY, x, y) c.DrawLine(lastX, lastY, x, y)
for _, p := range drawille.Line(lastX, lastY, x, y) { for _, p := range drawille.Line(lastX, lastY, x, y) {
@ -91,14 +92,16 @@ func (lc *LineGraph) Buffer() *Buffer {
} }
} }
// renders key ontop
for j, seriesName := range seriesList { for j, seriesName := range seriesList {
// sorts lines again
seriesData := lc.Data[seriesName] seriesData := lc.Data[seriesName]
seriesLineColor, ok := lc.LineColor[seriesName] seriesLineColor, ok := lc.LineColor[seriesName]
if !ok { if !ok {
seriesLineColor = lc.DefaultLineColor seriesLineColor = lc.DefaultLineColor
} }
// Render key ontop, but let braille be drawn between words // render key ontop, but let braille be drawn over space characters
str := fmt.Sprintf("%s %3.0f%%", seriesName, seriesData[len(seriesData)-1]) str := fmt.Sprintf("%s %3.0f%%", seriesName, seriesData[len(seriesData)-1])
for k, char := range str { for k, char := range str {
if char != ' ' { if char != ' ' {

View File

@ -17,12 +17,12 @@ type Sparklines struct {
Lines []*Sparkline Lines []*Sparkline
} }
// Add appends a given Sparkline to s *Sparklines. // Add appends a given Sparkline to the *Sparklines.
func (s *Sparklines) Add(sl Sparkline) { func (s *Sparklines) Add(sl Sparkline) {
s.Lines = append(s.Lines, &sl) s.Lines = append(s.Lines, &sl)
} }
// NewSparkline returns a unrenderable single sparkline that intended to be added into Sparklines. // NewSparkline returns an unrenderable single sparkline that intended to be added into a Sparklines.
func NewSparkline() *Sparkline { func NewSparkline() *Sparkline {
return &Sparkline{ return &Sparkline{
TitleColor: Theme.Fg, TitleColor: Theme.Fg,
@ -30,7 +30,7 @@ func NewSparkline() *Sparkline {
} }
} }
// NewSparklines return a new *Sparklines with given Sparkline(s), you can always add a new Sparkline later. // NewSparklines return a new *Sparklines with given Sparklines, you can always add a new Sparkline later.
func NewSparklines(ss ...*Sparkline) *Sparklines { func NewSparklines(ss ...*Sparkline) *Sparklines {
return &Sparklines{ return &Sparklines{
Block: NewBlock(), Block: NewBlock(),
@ -44,19 +44,18 @@ func (sl *Sparklines) Buffer() *Buffer {
lc := len(sl.Lines) // lineCount lc := len(sl.Lines) // lineCount
// for each line // renders each sparkline and its titles
for i, line := range sl.Lines { for i, line := range sl.Lines {
// prints titles
title1Y := 2 + (sl.Y/lc)*i title1Y := 2 + (sl.Y/lc)*i
title2Y := (2 + (sl.Y/lc)*i) + 1 title2Y := (2 + (sl.Y/lc)*i) + 1
title1 := MaxString(line.Title1, sl.X) title1 := MaxString(line.Title1, sl.X)
title2 := MaxString(line.Title2, sl.X) title2 := MaxString(line.Title2, sl.X)
buf.SetString(1, title1Y, title1, line.TitleColor|AttrBold, sl.Bg) buf.SetString(1, title1Y, title1, line.TitleColor|AttrBold, sl.Bg)
buf.SetString(1, title2Y, title2, line.TitleColor|AttrBold, sl.Bg) buf.SetString(1, title2Y, title2, line.TitleColor|AttrBold, sl.Bg)
sparkY := (sl.Y / lc) * (i + 1) sparkY := (sl.Y / lc) * (i + 1)
// finds max data in current view used for relative heights // finds max data in current view used for relative heights
max := 1 max := 1
for i := len(line.Data) - 1; i >= 0 && sl.X-((len(line.Data)-1)-i) >= 1; i-- { for i := len(line.Data) - 1; i >= 0 && sl.X-((len(line.Data)-1)-i) >= 1; i-- {

View File

@ -10,7 +10,7 @@ type Table struct {
Header []string Header []string
Rows [][]string Rows [][]string
ColWidths []int ColWidths []int
Cp []int // column position CellXPos []int // column position
Gap int // gap between columns Gap int // gap between columns
Cursor Color Cursor Color
UniqueCol int // the column used to identify the selected item UniqueCol int // the column used to identify the selected item
@ -34,6 +34,7 @@ func NewTable() *Table {
} }
// ColResize is the default column resizer, but can be overriden. // ColResize is the default column resizer, but can be overriden.
// ColResize calculates the width of each column.
func (t *Table) ColResize() { func (t *Table) ColResize() {
// calculate gap size based on total width // calculate gap size based on total width
t.Gap = 3 t.Gap = 3
@ -46,7 +47,7 @@ func (t *Table) ColResize() {
cur := 0 cur := 0
for _, w := range t.ColWidths { for _, w := range t.ColWidths {
cur += t.Gap cur += t.Gap
t.Cp = append(t.Cp, cur) t.CellXPos = append(t.CellXPos, cur)
cur += w cur += w
} }
} }
@ -55,21 +56,20 @@ func (t *Table) ColResize() {
func (t *Table) Buffer() *Buffer { func (t *Table) Buffer() *Buffer {
buf := t.Block.Buffer() buf := t.Block.Buffer()
// makes sure there isn't a gap at the bottom of the table view // removes gap at the bottom of the current view if there is one
if t.TopRow > len(t.Rows)-(t.Y-1) { if t.TopRow > len(t.Rows)-(t.Y-1) {
t.TopRow = len(t.Rows) - (t.Y - 1) t.TopRow = len(t.Rows) - (t.Y - 1)
} }
t.ColResizer() t.ColResizer()
// print header // prints header
// for i := 0; i < render; i++ {
for i, width := range t.ColWidths { for i, width := range t.ColWidths {
if width == 0 { if width == 0 {
break break
} }
r := MaxString(t.Header[i], t.X-6) r := MaxString(t.Header[i], t.X-6)
buf.SetString(t.Cp[i], 1, r, t.Fg|AttrBold, t.Bg) buf.SetString(t.CellXPos[i], 1, r, t.Fg|AttrBold, t.Bg)
} }
// prints each row // prints each row
@ -77,7 +77,7 @@ func (t *Table) Buffer() *Buffer {
row := t.Rows[rowNum] row := t.Rows[rowNum]
y := (rowNum + 2) - t.TopRow y := (rowNum + 2) - t.TopRow
// cursor // prints cursor
bg := t.Bg bg := t.Bg
if (t.SelectedItem == "" && rowNum == t.SelectedRow) || (t.SelectedItem != "" && t.SelectedItem == row[t.UniqueCol]) { if (t.SelectedItem == "" && rowNum == t.SelectedRow) || (t.SelectedItem != "" && t.SelectedItem == row[t.UniqueCol]) {
bg = t.Cursor bg = t.Cursor
@ -97,7 +97,7 @@ func (t *Table) Buffer() *Buffer {
break break
} }
r := MaxString(row[i], t.X-6) r := MaxString(row[i], t.X-6)
buf.SetString(t.Cp[i], y, r, t.Fg, bg) buf.SetString(t.CellXPos[i], y, r, t.Fg, bg)
} }
} }
@ -108,7 +108,7 @@ func (t *Table) Buffer() *Buffer {
// Cursor Movement // // Cursor Movement //
///////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////
// calcPos is used to calculate the cursor position and where in the process list we are located. // calcPos is used to calculate the cursor position and the current view.
func (t *Table) calcPos() { func (t *Table) calcPos() {
t.SelectedItem = "" t.SelectedItem = ""
@ -147,6 +147,8 @@ func (t *Table) Bottom() {
t.calcPos() t.calcPos()
} }
// The number of lines in a page is equal to the height of the widget.
func (t *Table) HalfPageUp() { func (t *Table) HalfPageUp() {
t.SelectedRow = t.SelectedRow - (t.Y-2)/2 t.SelectedRow = t.SelectedRow - (t.Y-2)/2
t.calcPos() t.calcPos()

View File

@ -5,7 +5,7 @@ import (
"time" "time"
ui "github.com/cjbassi/gotop/termui" ui "github.com/cjbassi/gotop/termui"
cpu "github.com/shirou/gopsutil/cpu" psCPU "github.com/shirou/gopsutil/cpu"
) )
type CPU struct { type CPU struct {
@ -15,8 +15,12 @@ type CPU struct {
} }
func NewCPU() *CPU { func NewCPU() *CPU {
count, _ := cpu.Counts(false) count, _ := psCPU.Counts(false)
c := &CPU{ui.NewLineGraph(), count, time.Second} c := &CPU{
LineGraph: ui.NewLineGraph(),
count: count,
interval: time.Second,
}
c.Label = "CPU Usage" c.Label = "CPU Usage"
for i := 0; i < c.count; i++ { for i := 0; i < c.count; i++ {
key := "CPU" + strconv.Itoa(i+1) key := "CPU" + strconv.Itoa(i+1)
@ -37,7 +41,7 @@ func NewCPU() *CPU {
func (c *CPU) update() { func (c *CPU) update() {
// psutil calculates the CPU usage over a 1 second interval, therefore it blocks for 1 second // psutil calculates the CPU usage over a 1 second interval, therefore it blocks for 1 second
// `true` makes it so psutil doesn't group CPU usage percentages // `true` makes it so psutil doesn't group CPU usage percentages
percent, _ := cpu.Percent(time.Second, true) percent, _ := psCPU.Percent(time.Second, true)
for i := 0; i < c.count; i++ { for i := 0; i < c.count; i++ {
key := "CPU" + strconv.Itoa(i+1) key := "CPU" + strconv.Itoa(i+1)
c.Data[key] = append(c.Data[key], percent[i]) c.Data[key] = append(c.Data[key], percent[i])

View File

@ -6,7 +6,7 @@ import (
ui "github.com/cjbassi/gotop/termui" ui "github.com/cjbassi/gotop/termui"
"github.com/cjbassi/gotop/utils" "github.com/cjbassi/gotop/utils"
disk "github.com/shirou/gopsutil/disk" psDisk "github.com/shirou/gopsutil/disk"
) )
type Disk struct { type Disk struct {
@ -16,8 +16,11 @@ type Disk struct {
} }
func NewDisk() *Disk { func NewDisk() *Disk {
// get root filesystem usage d := &Disk{
d := &Disk{ui.NewGauge(), "/", time.Second * 5} Gauge: ui.NewGauge(),
fs: "/",
interval: time.Second * 5,
}
d.Label = "Disk Usage" d.Label = "Disk Usage"
go d.update() go d.update()
@ -32,7 +35,7 @@ func NewDisk() *Disk {
} }
func (d *Disk) update() { func (d *Disk) update() {
disk, _ := disk.Usage(d.fs) usage, _ := psDisk.Usage(d.fs)
d.Percent = int(disk.UsedPercent) d.Percent = int(usage.UsedPercent)
d.Description = fmt.Sprintf(" (%dGB free)", int(utils.BytesToGB(disk.Free))) d.Description = fmt.Sprintf(" (%dGB free)", int(utils.BytesToGB(usage.Free)))
} }

View File

@ -30,8 +30,8 @@ type HelpMenu struct {
func NewHelpMenu() *HelpMenu { func NewHelpMenu() *HelpMenu {
block := ui.NewBlock() block := ui.NewBlock()
block.X = 48 // width block.X = 48 // width - 1
block.Y = 15 // height block.Y = 15 // height - 1
block.XOffset = (ui.Body.Width - block.X) / 2 // X coordinate block.XOffset = (ui.Body.Width - block.X) / 2 // X coordinate
block.YOffset = (ui.Body.Height - block.Y) / 2 // Y coordinate block.YOffset = (ui.Body.Height - block.Y) / 2 // Y coordinate
return &HelpMenu{block} return &HelpMenu{block}

View File

@ -4,7 +4,7 @@ import (
"time" "time"
ui "github.com/cjbassi/gotop/termui" ui "github.com/cjbassi/gotop/termui"
mem "github.com/shirou/gopsutil/mem" psMem "github.com/shirou/gopsutil/mem"
) )
type Mem struct { type Mem struct {
@ -13,7 +13,10 @@ type Mem struct {
} }
func NewMem() *Mem { func NewMem() *Mem {
m := &Mem{ui.NewLineGraph(), time.Second} m := &Mem{
LineGraph: ui.NewLineGraph(),
interval: time.Second,
}
m.Label = "Memory Usage" m.Label = "Memory Usage"
m.Data["Main"] = []float64{0} m.Data["Main"] = []float64{0}
m.Data["Swap"] = []float64{0} m.Data["Swap"] = []float64{0}
@ -30,8 +33,8 @@ func NewMem() *Mem {
} }
func (m *Mem) update() { func (m *Mem) update() {
main, _ := mem.VirtualMemory() main, _ := psMem.VirtualMemory()
swap, _ := mem.SwapMemory() swap, _ := psMem.SwapMemory()
m.Data["Main"] = append(m.Data["Main"], main.UsedPercent) m.Data["Main"] = append(m.Data["Main"], main.UsedPercent)
m.Data["Swap"] = append(m.Data["Swap"], swap.UsedPercent) m.Data["Swap"] = append(m.Data["Swap"], swap.UsedPercent)
} }

View File

@ -6,7 +6,7 @@ import (
ui "github.com/cjbassi/gotop/termui" ui "github.com/cjbassi/gotop/termui"
"github.com/cjbassi/gotop/utils" "github.com/cjbassi/gotop/utils"
net "github.com/shirou/gopsutil/net" psNet "github.com/shirou/gopsutil/net"
) )
type Net struct { type Net struct {
@ -25,7 +25,10 @@ func NewNet() *Net {
sent.Data = []int{0} sent.Data = []int{0}
spark := ui.NewSparklines(recv, sent) spark := ui.NewSparklines(recv, sent)
n := &Net{spark, time.Second, 0, 0} n := &Net{
Sparklines: spark,
interval: time.Second,
}
n.Label = "Network Usage" n.Label = "Network Usage"
go n.update() go n.update()
@ -41,56 +44,55 @@ func NewNet() *Net {
func (n *Net) update() { func (n *Net) update() {
// `false` causes psutil to group all network activity // `false` causes psutil to group all network activity
interfaces, _ := net.IOCounters(false) interfaces, _ := psNet.IOCounters(false)
recv := interfaces[0].BytesRecv recvTotal := interfaces[0].BytesRecv
sent := interfaces[0].BytesSent sentTotal := interfaces[0].BytesSent
if n.recvTotal != 0 { // if this isn't the first update if n.recvTotal != 0 { // if this isn't the first update
curRecv := recv - n.recvTotal recvRecent := recvTotal - n.recvTotal
curSent := sent - n.sentTotal sentRecent := sentTotal - n.sentTotal
n.Lines[0].Data = append(n.Lines[0].Data, int(curRecv)) n.Lines[0].Data = append(n.Lines[0].Data, int(recvRecent))
n.Lines[1].Data = append(n.Lines[1].Data, int(curSent)) n.Lines[1].Data = append(n.Lines[1].Data, int(sentRecent))
} }
// used for later calls to update // used in later calls to update
n.recvTotal = recv n.recvTotal = recvTotal
n.sentTotal = sent n.sentTotal = sentTotal
// renders net widget titles // renders net widget titles
for i := 0; i < 2; i++ { for i := 0; i < 2; i++ {
var method string // either 'Rx' or 'Tx' var method string // either 'Rx' or 'Tx'
var total uint64 var total float64
cur := n.Lines[i].Data[len(n.Lines[i].Data)-1] recent := n.Lines[i].Data[len(n.Lines[i].Data)-1]
totalUnit := "B" unitTotal := "B"
curUnit := "B" unitRecent := "B"
if i == 0 { if i == 0 {
total = recv total = float64(recvTotal)
method = "Rx" method = "Rx"
} else { } else {
total = sent total = float64(sentTotal)
method = "Tx" method = "Tx"
} }
if cur >= 1000000 { if recent >= 1000000 {
cur = int(utils.BytesToMB(uint64(cur))) recent = int(utils.BytesToMB(uint64(recent)))
curUnit = "MB" unitRecent = "MB"
} else if cur >= 1000 { } else if recent >= 1000 {
cur = int(utils.BytesToKB(uint64(cur))) recent = int(utils.BytesToKB(uint64(recent)))
curUnit = "kB" unitRecent = "kB"
} }
var totalCvrt float64
if total >= 1000000000 { if total >= 1000000000 {
totalCvrt = utils.BytesToGB(total) total = utils.BytesToGB(uint64(total))
totalUnit = "GB" unitTotal = "GB"
} else if total >= 1000000 { } else if total >= 1000000 {
totalCvrt = utils.BytesToMB(total) total = utils.BytesToMB(uint64(total))
totalUnit = "MB" unitTotal = "MB"
} }
n.Lines[i].Title1 = fmt.Sprintf(" Total %s: %5.1f %s", method, totalCvrt, totalUnit) n.Lines[i].Title1 = fmt.Sprintf(" Total %s: %5.1f %s", method, total, unitTotal)
n.Lines[i].Title2 = fmt.Sprintf(" %s/s: %9d %2s/s", method, cur, curUnit) n.Lines[i].Title2 = fmt.Sprintf(" %s/s: %9d %2s/s", method, recent, unitRecent)
} }
} }

View File

@ -7,13 +7,13 @@ import (
"time" "time"
ui "github.com/cjbassi/gotop/termui" ui "github.com/cjbassi/gotop/termui"
cpu "github.com/shirou/gopsutil/cpu" psCPU "github.com/shirou/gopsutil/cpu"
proc "github.com/shirou/gopsutil/process" psProc "github.com/shirou/gopsutil/process"
) )
const ( const (
DOWN = "▼"
UP = "▲" UP = "▲"
DOWN = "▼"
) )
// Process represents each process. // Process represents each process.
@ -24,7 +24,6 @@ type Process struct {
Mem float32 Mem float32
} }
// Proc widget.
type Proc struct { type Proc struct {
*ui.Table *ui.Table
cpuCount int cpuCount int
@ -36,9 +35,8 @@ type Proc struct {
KeyPressed chan bool KeyPressed chan bool
} }
// NewProc creates a new Proc widget.
func NewProc(loaded, keyPressed chan bool) *Proc { func NewProc(loaded, keyPressed chan bool) *Proc {
cpuCount, _ := cpu.Counts(false) cpuCount, _ := psCPU.Counts(false)
p := &Proc{ p := &Proc{
Table: ui.NewTable(), Table: ui.NewTable(),
interval: time.Second, interval: time.Second,
@ -73,15 +71,14 @@ func NewProc(loaded, keyPressed chan bool) *Proc {
return p return p
} }
// update updates proc widget.
func (p *Proc) update() { func (p *Proc) update() {
psProcs, _ := proc.Processes() psProcesses, _ := psProc.Processes()
processes := make([]Process, len(psProcs)) processes := make([]Process, len(psProcesses))
for i, pr := range psProcs { for i, psProcess := range psProcesses {
pid := pr.Pid pid := psProcess.Pid
command, _ := pr.Name() command, _ := psProcess.Name()
cpu, _ := pr.CPUPercent() cpu, _ := psProcess.CPUPercent()
mem, _ := pr.MemoryPercent() mem, _ := psProcess.MemoryPercent()
processes[i] = Process{ processes[i] = Process{
pid, pid,
@ -139,21 +136,21 @@ func (p *Proc) ColResize() {
p.Gap = 2 p.Gap = 2
} }
p.Cp = []int{ p.CellXPos = []int{
p.Gap, p.Gap,
p.Gap + p.ColWidths[0] + p.Gap, p.Gap + p.ColWidths[0] + p.Gap,
p.X - p.Gap - p.ColWidths[3] - p.Gap - p.ColWidths[2], p.X - p.Gap - p.ColWidths[3] - p.Gap - p.ColWidths[2],
p.X - p.Gap - p.ColWidths[3], p.X - p.Gap - p.ColWidths[3],
} }
contentWidth := p.Gap + p.ColWidths[0] + p.Gap + p.ColWidths[1] + p.Gap + p.ColWidths[2] + p.Gap + p.ColWidths[3] + p.Gap rowWidth := p.Gap + p.ColWidths[0] + p.Gap + p.ColWidths[1] + p.Gap + p.ColWidths[2] + p.Gap + p.ColWidths[3] + p.Gap
// only renders a column if it fits // only renders a column if it fits
if p.X < (contentWidth - p.Gap - p.ColWidths[3]) { if p.X < (rowWidth - p.Gap - p.ColWidths[3]) {
p.ColWidths[2] = 0 p.ColWidths[2] = 0
p.ColWidths[3] = 0 p.ColWidths[3] = 0
} else if p.X < contentWidth { } else if p.X < rowWidth {
p.Cp[2] = p.Cp[3] p.CellXPos[2] = p.CellXPos[3]
p.ColWidths[3] = 0 p.ColWidths[3] = 0
} }
} }
@ -238,33 +235,33 @@ func (p *Proc) keyBinds() {
// The first field changes from PID to count. // The first field changes from PID to count.
// CPU and Mem are added together for each Process. // CPU and Mem are added together for each Process.
func Group(P []Process) []Process { func Group(P []Process) []Process {
groupMap := make(map[string]Process) groupedP := make(map[string]Process)
for _, p := range P { for _, process := range P {
val, ok := groupMap[p.Command] val, ok := groupedP[process.Command]
if ok { if ok {
newP := Process{ groupedP[process.Command] = Process{
val.PID + 1, val.PID + 1,
val.Command, val.Command,
val.CPU + p.CPU, val.CPU + process.CPU,
val.Mem + p.Mem, val.Mem + process.Mem,
} }
groupMap[p.Command] = newP
} else { } else {
newP := Process{ groupedP[process.Command] = Process{
1, 1,
p.Command, process.Command,
p.CPU, process.CPU,
p.Mem, process.Mem,
} }
groupMap[p.Command] = newP
} }
} }
groupList := make([]Process, len(groupMap))
i := 0 groupList := make([]Process, len(groupedP))
for _, val := range groupMap { var i int
for _, val := range groupedP {
groupList[i] = val groupList[i] = val
i++ i++
} }
return groupList return groupList
} }

View File

@ -5,28 +5,29 @@ package widgets
import ( import (
"fmt" "fmt"
"sort"
"strings" "strings"
"time" "time"
ui "github.com/cjbassi/gotop/termui" ui "github.com/cjbassi/gotop/termui"
ps "github.com/shirou/gopsutil/host" psHost "github.com/shirou/gopsutil/host"
) )
type Temp struct { type Temp struct {
*ui.Block *ui.Block
interval time.Duration interval time.Duration
Data []int Data map[string]int
DataLabels []string Threshold int
Threshold int TempLow ui.Color
TempLow ui.Color TempHigh ui.Color
TempHigh ui.Color
} }
func NewTemp() *Temp { func NewTemp() *Temp {
t := &Temp{ t := &Temp{
Block: ui.NewBlock(), Block: ui.NewBlock(),
interval: time.Second * 5, interval: time.Second * 5,
Threshold: 80, // temp at which color should change to red Data: make(map[string]int),
Threshold: 80, // temp at which color should change
} }
t.Label = "Temperatures" t.Label = "Temperatures"
@ -42,38 +43,41 @@ func NewTemp() *Temp {
} }
func (t *Temp) update() { func (t *Temp) update() {
sensors, _ := ps.SensorsTemperatures() sensors, _ := psHost.SensorsTemperatures()
temps := []int{} for _, sensor := range sensors {
labels := []string{}
for _, temp := range sensors {
// only sensors with input in their name are giving us live temp info // only sensors with input in their name are giving us live temp info
if strings.Contains(temp.SensorKey, "input") { if strings.Contains(sensor.SensorKey, "input") {
temps = append(temps, int(temp.Temperature))
// removes '_input' from the end of the sensor name // removes '_input' from the end of the sensor name
labels = append(labels, temp.SensorKey[:strings.Index(temp.SensorKey, "_input")]) label := sensor.SensorKey[:strings.Index(sensor.SensorKey, "_input")]
t.Data[label] = int(sensor.Temperature)
} }
} }
t.Data = temps
t.DataLabels = labels
} }
// Buffer implements ui.Bufferer interface. // Buffer implements ui.Bufferer interface.
func (t *Temp) Buffer() *ui.Buffer { func (t *Temp) Buffer() *ui.Buffer {
buf := t.Block.Buffer() buf := t.Block.Buffer()
for y, text := range t.DataLabels { var keys []string
for k := range t.Data {
keys = append(keys, k)
}
sort.Strings(keys)
for y, key := range keys {
if y+1 > t.Y { if y+1 > t.Y {
break break
} }
fg := t.TempLow fg := t.TempLow
if t.Data[y] >= t.Threshold { if t.Data[key] >= t.Threshold {
fg = t.TempHigh fg = t.TempHigh
} }
s := ui.MaxString(text, (t.X - 4)) s := ui.MaxString(key, (t.X - 4))
buf.SetString(1, y+1, s, t.Fg, t.Bg) buf.SetString(1, y+1, s, t.Fg, t.Bg)
buf.SetString(t.X-2, y+1, fmt.Sprintf("%dC", t.Data[y]), fg, t.Bg) buf.SetString(t.X-2, y+1, fmt.Sprintf("%dC", t.Data[key]), fg, t.Bg)
} }
return buf return buf