// Copyright 2017-2025 WIT.COM Inc. All rights reserved. // Use of this source code is governed by the GPL 3.0 // all structures and variables are local (aka lowercase) // since the plugin should be isolated to access only // by functions() to insure everything here is run // inside a dedicated goroutine package main import ( "fmt" "reflect" "strconv" "strings" "sync" "github.com/awesome-gocui/gocui" log "go.wit.com/log" "go.wit.com/toolkits/tree" ) // It's probably a terrible idea to call this 'me' // 2025 note: doesn't seem terrible to call this 'me' anymore. notsure. var me config // todo: move all this to a protobuf. then, redo all this mess. // it got me here, but now it's time to clean it up for good // I can't get a GO plugins that use protobuf to load yet (versioning mismatch) type config struct { baseGui *gocui.Gui // the main gocui handle treeRoot *tree.Node // the base of the binary tree. it should have id == 0 myTree *tree.TreeInfo // ? ctrlDown *tree.Node // shown if you click the mouse when the ctrl key is pressed currentWindow *guiWidget // this is the current tab or window to show helpLabel *gocui.View // ? showHelp bool // toggle boolean for the help menu (deprecate?) // dropdownW *guiWidget // grab the dropdown choices from this widget FramePadW int `default:"1" dense:"0"` // When the widget has a frame, like a button, it adds 2 lines runes on each side FramePadH int `default:"1" dense:"0"` // When the widget has a frame, like a button, it adds 2 lines runes on each side PadW int `default:"1" dense:"0"` // pad spacing PadH int `default:"1" dense:"0"` // pad spacing WindowW int `default:"8" dense:"0"` // how far down to start Window or Tab headings WindowH int `default:"-1"` // how far down to start Window or Tab headings TabW int `default:"5" dense:"0"` // how far down to start Window or Tab headings TabH int `default:"1" dense:"0"` // how far down to start Window or Tab headings WindowPadW int `default:"8" dense:"0"` // additional amount of space to put between window & tab widgets TabPadW int `default:"4" dense:"0"` // additional amount of space to put between window & tab widgets GroupPadW int `default:"2" dense:"1"` // additional amount of space to indent on a group BoxPadW int `default:"2" dense:"1"` // additional amount of space to indent on a box GridPadW int `default:"2" dense:"1"` // additional amount of space to indent on a grid RawW int `default:"1"` // the raw beginning of each window (or tab) RawH int `default:"5"` // the raw beginning of each window (or tab) FakeW int `default:"20"` // offset for the hidden widgets padded bool // add space between things like buttons bookshelf bool // do you want things arranged in the box like a bookshelf or a stack? canvas bool // if set to true, the windows are a raw canvas menubar bool // for windows stretchy bool // expand things like buttons to the maximum size margin bool // add space around the frames of windows writeMutex sync.Mutex // writeMutex protects writes to *guiWidget (it's global right now maybe) ecount int // counts how many mouse and keyboard events have occurred supermouse bool // prints out every widget found while you move the mouse around depth int // used for listWidgets() debugging globalMouseDown bool // yep, mouse is pressed newWindowTrigger chan *guiWidget // work around hack to redraw windows a bit after NewWindow() stdout stdout // information for the STDOUT window showDebug bool // todo: move this into config struct dropdown dropdown // the dropdown menu allwin []*guiWidget // for tracking which window is next downW int // where the mouse was pressed down downH int // where the mouse was pressed down currentDrag *guiWidget // what widget is currently being moved around } // settings for the stdout window type stdout struct { tk *guiWidget // where to show STDOUT w int // the width h int // the height outputOnTop bool // is the STDOUT window on top? outputOffscreen bool // is the STDOUT window offscreen? lastW int // the last 'w' location (used to move from offscreen to onscreen) lastH int // the last 'h' location (used to move from offscreen to onscreen) mouseOffsetW int // the current 'w' offset mouseOffsetH int // the current 'h' offset init bool // moves the window offscreen on startup resize bool // user is resizing the window } // settings for the dropdown window type dropdown struct { tk *guiWidget // where to show STDOUT callerTK *guiWidget // which widget called the dropdown menu items []string // what is currently in the menu w int // the width h int // the height active bool // is the dropdown menu currently in use? init bool // moves the window offscreen on startup // dtoggle bool // is a dropdown or combobox currently active? } // this is the gocui way // corner starts at in the upper left corner type rectType struct { w0, h0, w1, h1 int // left top right bottom } func (r *rectType) Width() int { return r.w1 - r.w0 } func (r *rectType) Height() int { return r.h1 - r.h0 } // settings that are window specific type window struct { windowFrame *guiWidget // this is the frame for a window widget wasDragged bool // indicates the window was dragged. This keeps it from being rearranged dragW int // when dragging a window, this is the offset to the mouse position dragH int // when dragging a window, this is the offset to the mouse position hasTabs bool // does the window have tabs? currentTab bool // the visible tab selectedTab *tree.Node // for a window, this is currently selected tab active bool // means this window is the active one isBG bool // means this is the background widget. There is only one of these order int // what level the window is on resize bool // only set the title once } type guiWidget struct { v *gocui.View // this is nil if the widget is not displayed cuiName string // what gocui uses to reference the widget (usually "TK " parent *guiWidget // mirrors the binary node tree children []*guiWidget // mirrors the binary node tree node *tree.Node // the pointer back to the tree windowFrame *guiWidget // this is the frame for a window widget internal bool // indicates the widget is internal to gocui and should be treated differently dragW int // when dragging a window, this is the offset to the mouse position dragH int // when dragging a window, this is the offset to the mouse position hasTabs bool // does the window have tabs? currentTab bool // the visible tab window window // holds information specific only to Window widgets value string // ? checked bool // ? labelN string // the actual text to display in the console vals []string // dropdown menu items enable bool // ? defaultColor *colorT // store the color to go back to gocuiSize rectType // should mirror the real display size. todo: verify these are accurate. they are not yet full rectType // full size of children (used by widget.Window, etc) force rectType // force widget within these boundries (using this to debug window dragging) startW int // ? startH int // ? isFake bool // widget types like 'box' are 'false' widths map[int]int // how tall each row in the grid is heights map[int]int // how wide each column in the grid is tainted bool // ? frame bool // ? selectedTab *tree.Node // for a window, this is currently selected tab color *colorT // what color to use // resize bool // the window is currently being resized isBG bool // means this is the background widget. There is only one of these } // from the gocui devs: // Write appends a byte slice into the view's internal buffer. Because // View implements the io.Writer interface, it can be passed as parameter // of functions like fmt.Fprintf, fmt.Fprintln, io.Copy, etc. Clear must // be called to clear the view's buffer. func (w *guiWidget) Write(p []byte) (n int, err error) { w.tainted = true me.writeMutex.Lock() defer me.writeMutex.Unlock() // _, outputH := w.Size() outputH := 33 // special output length for "msg" window until I figure things out tk := me.stdout.tk if tk.v == nil { // optionally write the output to /tmp s := fmt.Sprint(string(p)) s = strings.TrimSuffix(s, "\n") fmt.Fprintln(outf, s) v, _ := me.baseGui.View("msg") if v != nil { // fmt.Fprintln(outf, "found msg") tk.v = v } } else { // display the output in the gocui window tk.v.Clear() s := fmt.Sprint(string(p)) s = strings.TrimSuffix(s, "\n") tmp := strings.Split(s, "\n") outputS = append(outputS, tmp...) if len(outputS) > outputH { l := len(outputS) - outputH outputS = outputS[l:] } fmt.Fprintln(tk.v, strings.Join(outputS, "\n")) } return len(p), nil } // THIS IS GO COMPILER MAGIC // this sets the `default` in the structs above on init() // this is cool code. thank the GO devs for this code and Alex Flint for explaining it to me func Set(ptr interface{}, tag string) error { if reflect.TypeOf(ptr).Kind() != reflect.Ptr { log.Log(ERROR, "Set() Not a pointer", ptr, "with tag =", tag) return fmt.Errorf("Not a pointer") } v := reflect.ValueOf(ptr).Elem() t := v.Type() for i := 0; i < t.NumField(); i++ { defaultVal := t.Field(i).Tag.Get(tag) name := t.Field(i).Name // log("Set() try name =", name, "defaultVal =", defaultVal) setField(v.Field(i), defaultVal, name) } return nil } func setField(field reflect.Value, defaultVal string, name string) error { if !field.CanSet() { // log("setField() Can't set value", field, defaultVal) return fmt.Errorf("Can't set value\n") } else { log.Log(NOW, "setField() Can set value", name, defaultVal) } switch field.Kind() { case reflect.Int: val, _ := strconv.Atoi(defaultVal) field.Set(reflect.ValueOf(int(val)).Convert(field.Type())) case reflect.String: field.Set(reflect.ValueOf(defaultVal).Convert(field.Type())) case reflect.Bool: if defaultVal == "true" { field.Set(reflect.ValueOf(true)) } else { field.Set(reflect.ValueOf(false)) } } return nil }