<|start_filename|>hoc/console/err.go<|end_filename|>
package console

import (
	"fmt"

	"github.com/gdamore/tcell"
	"github.com/verdverm/tview"

	"github.com/verdverm/vermui"
	"github.com/verdverm/vermui/events"
)

type ErrConsoleWidget struct {
	*tview.TextView
}

func NewErrConsoleWidget() *ErrConsoleWidget {
	textView := tview.NewTextView()
	textView.
		SetTextColor(tcell.ColorMaroon).
		SetScrollable(true).
		SetChangedFunc(func() {
			vermui.Draw()
			textView.ScrollToEnd()
		})

	textView.SetTitle(" errors ").
		SetBorder(true).
		SetBorderColor(tcell.ColorRed)

	C := &ErrConsoleWidget{
		TextView: textView,
	}

	return C
}

func (C *ErrConsoleWidget) Mount(context map[string]interface{}) error {

	vermui.AddGlobalHandler("/user/error", func(evt events.Event) {
		str := evt.Data.(*events.EventCustom).Data()
		text := fmt.Sprintf("[%s] %v\n", evt.When().Format("2006-01-02 15:04:05"), str)
		fmt.Fprintf(C, "%s", text)
	})

	vermui.AddGlobalHandler("/sys/err", func(ev events.Event) {
		err := ev.Data.(*events.EventError)
		line := fmt.Sprintf("[%s] %v", ev.When().Format("2006-01-02 15:04:05"), err)
		fmt.Fprintf(C, "[red]SYSERR %v[white]\n", line)
	})

	return nil
}
func (C *ErrConsoleWidget) Unmount() error {
	vermui.RemoveWidgetHandler(C, "/user/error")
	vermui.RemoveWidgetHandler(C, "/sys/err")
	return nil
}


<|start_filename|>mux/handler.go<|end_filename|>
package mux

import (
	"github.com/verdverm/tview"
)

type HandlerFunc func(*Request) (tview.Primitive, *Request, error)

type Handler interface {
	Serve(*Request) (tview.Primitive, *Request, error)
}

type DefaultHandler struct {
	handler HandlerFunc
}

func NewDefaultHandler(handle HandlerFunc) *DefaultHandler {
	return &DefaultHandler{
		handler: handle,
	}
}

func (H *DefaultHandler) Serve(req *Request) (tview.Primitive, *Request, error) {
	return H.handler(req)
}


<|start_filename|>events/events.go<|end_filename|>
// Copyright 2017 <NAME> <<EMAIL>>. All rights reserved.
// Copyright 2018 <NAME> <<EMAIL>>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.

package events

import (
	"path"
	"strings"
	"sync"
	"time"

	"github.com/codemodus/kace"
	"github.com/gdamore/tcell"
	"github.com/verdverm/tview"
)

type Event struct {
	tcell.Event
	when time.Time

	Type string
	Path string

	From string
	To   string

	Data interface{}
}

func (E *Event) When() time.Time {
	if E.Event != nil {
		return E.Event.When()
	} else {
		return E.when
	}
}

type EventError struct {
	*tcell.EventError
}

type EventResize struct {
	*tcell.EventResize
}

type EventKey struct {
	*tcell.EventKey
	KeyStr string
}

type EventMouse struct {
	*tcell.EventMouse
	Press string
}

type EventInterrupt struct {
	*tcell.EventInterrupt
}

type EventCustom struct {
	*tcell.EventInterrupt
}

func NewSysEvtCh() chan Event {
	ec := make(chan Event, 0)
	sysEvtChs = append(sysEvtChs, ec)
	return ec
}

func SendCustomEvent(path string, data interface{}) {
	now := time.Now()
	c := &EventCustom{
		EventInterrupt: tcell.NewEventInterrupt(data),
	}
	e := Event{
		when: now,
		Type: "custom",
		From: "user",
		Path: path,
		Data: c,
	}

	customEventCh <- Event(e)
}

func hookEventsFromApp(app *tview.Application) {
	hook := func(e tcell.Event) tcell.Event {
		for _, c := range sysEvtChs {
			func(ch chan Event) {
				ch <- handleEvents(e)
			}(c)
		}
		return e
	}
	app.SetInputCapture(hook)
}

func handleEvents(e tcell.Event) Event {
	ne := Event{Event: e, From: "/sys"}

	switch t := e.(type) {
	case *tcell.EventError:
		err := EventError{t}
		ne.Type = "error"
		ne.Path = "/sys/err"
		ne.Data = err

	case *tcell.EventInterrupt:
		i := EventInterrupt{t}
		ne.Type = "interrupt"
		ne.Path = "/sys/interrupt"
		ne.Data = i

	case *tcell.EventResize:
		r := EventResize{t}
		ne.Type = "resize"
		ne.Path = "/sys/resize"
		ne.Data = r

	case *tcell.EventKey:
		k := eventKey(t)
		ne.Type = "keyboard"
		ne.Path = "/sys/key/" + k.KeyStr
		ne.Data = k

	case *tcell.EventMouse:
		m := eventMouse(t)
		ne.Type = "mouse"
		ne.Path = "/sys/mouse/" + m.Press
		ne.Data = m
	}
	return ne
}

func eventKey(e *tcell.EventKey) (tk EventKey) {
	tk.EventKey = e

	mods := eventMods(e.Modifiers())

	if e.Key() == tcell.KeyRune {
		k := string(e.Rune())
		tk.KeyStr = mods + k
		return tk
	}

	key := tcell.KeyNames[e.Key()]
	if strings.HasPrefix(key, "Ctrl-") {
		key = strings.TrimPrefix(key, "Ctrl-")
		key = kace.Kebab(key)
		if key == "space" {
			key = "<space>"
		}
	} else {
		key = "<" + kace.Kebab(key) + ">"
	}

	tk.KeyStr = mods + key

	return tk

}

func eventMouse(e *tcell.EventMouse) (te EventMouse) {
	te.EventMouse = e

	mods := eventMods(e.Modifiers())

	btn := ""
	switch B := e.Buttons(); B {
	case B & tcell.Button1:
		btn = "<left>"
	case B & tcell.Button2:
		btn = "<middle>"
	case B & tcell.Button3:
		btn = "<right>"

	case B & tcell.Button4:
		btn = "<button-4>"
	case B & tcell.Button5:
		btn = "<button-5>"

	case B & tcell.WheelUp:
		btn = "<wheel-up>"
	case B & tcell.WheelDown:
		btn = "<wheel-down>"
	case B & tcell.WheelLeft:
		btn = "<wheel-left>"
	case B & tcell.WheelRight:
		btn = "<wheel-right>"

	default:
		btn = "<unknown>"
	}

	te.Press = mods + btn

	return te
}

func eventMods(mods tcell.ModMask) string {
	m := ""
	if mods&tcell.ModCtrl == tcell.ModCtrl {
		m += "C-"
	}
	if mods&tcell.ModShift == tcell.ModShift {
		m += "S-"
	}
	if mods&tcell.ModAlt == tcell.ModAlt {
		m += "A-"
	}
	if mods&tcell.ModMeta == tcell.ModMeta {
		m += "M-"
	}
	return m
}

type EventStream struct {
	sync.RWMutex
	srcMap      map[string]chan Event
	stream      chan Event
	wg          sync.WaitGroup
	sigStopLoop chan Event
	Handlers    map[string]func(Event)
	hook        func(Event)
}

func NewEventStream() *EventStream {
	return &EventStream{
		srcMap:      make(map[string]chan Event),
		stream:      make(chan Event, 256),
		Handlers:    make(map[string]func(Event)),
		sigStopLoop: make(chan Event),
	}
}

func (es *EventStream) Init() {
	es.Merge("internal", es.sigStopLoop)
	go func() {
		es.wg.Wait()
		close(es.stream)
	}()
}

func (es *EventStream) Merge(name string, ec chan Event) {
	es.Lock()
	defer es.Unlock()

	es.wg.Add(1)
	es.srcMap[name] = ec

	go func(a chan Event) {
		for n := range a {
			n.From = name
			es.stream <- n
		}
		es.wg.Done()
	}(ec)
}

func (es *EventStream) Handle(path string, handler func(Event)) {
	es.Handlers[cleanPath(path)] = handler
}

func (es *EventStream) RemoveHandle(path string) {
	delete(es.Handlers, cleanPath(path))
}

// Remove all existing defined Handlers from the map
func (es *EventStream) ResetHandlers() {
	for Path := range es.Handlers {
		delete(es.Handlers, Path)
	}
	return
}

func (es *EventStream) match(path string) string {
	return findMatch(es.Handlers, path)
}

func (es *EventStream) Hook(f func(Event)) {
	es.hook = f
}

func (es *EventStream) Loop() {
	for e := range es.stream {
		switch e.Path {
		case "/sig/stoploop":
			return
		}
		func(a Event) {
			es.RLock()
			defer es.RUnlock()
			if pattern := es.match(a.Path); pattern != "" {
				es.Handlers[pattern](a)
			}
		}(e)

		if es.hook != nil {
			es.hook(e)
		}
	}

}

func (es *EventStream) StopLoop() {
	go func() {
		e := Event{
			Path: "/sig/stoploop",
		}
		es.sigStopLoop <- e
	}()
}

func findMatch(mux map[string]func(Event), path string) string {
	n := -1
	pattern := ""
	for m := range mux {
		if !isPathMatch(m, path) {
			continue
		}
		if len(m) > n {
			pattern = m
			n = len(m)
		}
	}
	return pattern

}

func cleanPath(p string) string {
	if p == "" {
		return "/"
	}
	if p[0] != '/' {
		p = "/" + p
	}
	return path.Clean(p)
}

func isPathMatch(pattern, path string) bool {
	if len(pattern) == 0 {
		return false
	}
	n := len(pattern)
	return len(path) >= n && path[0:n] == pattern
}


<|start_filename|>hoc/form/form.go<|end_filename|>
package form

import (
	"github.com/gdamore/tcell"
	"github.com/verdverm/tview"
)

type Form interface {
	tview.Primitive

	Name() string

	GetValues() (values map[string]interface{})
	SetValues(values map[string]interface{})

	AddItem(name string, item FormItem, taborder, proportion int)
	GetItem(name string) FormItem
	GetItems(name string) []FormItem

	AddButton(name string, button FormButton, taborder, proportion int)
	GetButton(name string) FormButton
	GetButtons(name string) []FormButton
}

type FormItem interface {
	tview.Primitive

	Name() string

	GetValues() (values map[string]interface{})
	SetValues(values map[string]interface{})

	SetFinishedFunction(handler func(key tcell.Key))
}

type FormButton interface {
	tview.Primitive

	Name() string

	OnSubmit()

	SetBlurFunction(handler func(key tcell.Key))
}

type FormBase struct {
	*Flex

	focusedElement int

	// An optional function which is called when the user hits Escape.
	cancel func()
}

func New(name string) *FormBase {
	L := &FormBase{
		Flex: NewFlex(name),
	}

	return L
}

func (f *FormBase) Focus(delegate func(p tview.Primitive)) {
	items := f.GetItems()
	buttons := f.GetButtons()

	if len(items)+len(buttons) == 0 {
		return
	}

	// Hand on the focus to one of our child elements.
	if f.focusedElement < 0 || f.focusedElement >= len(items)+len(buttons) {
		f.focusedElement = 0
	}
	handler := func(key tcell.Key) {
		switch key {
		case tcell.KeyTab, tcell.KeyEnter:
			f.focusedElement++
			f.Focus(delegate)
		case tcell.KeyBacktab:
			f.focusedElement--
			if f.focusedElement < 0 {
				f.focusedElement = len(items) + len(buttons) - 1
			}
			f.Focus(delegate)
		case tcell.KeyEscape:
			if f.cancel != nil {
				f.cancel()
			} else {
				f.focusedElement = 0
				f.Focus(delegate)
			}
		}
	}

	if f.focusedElement < len(items) {
		// We're selecting an item.
		item := items[f.focusedElement]
		item.SetFinishedFunction(handler)
		delegate(item)
	} else {
		// We're selecting a button.
		button := buttons[f.focusedElement-len(items)]
		button.SetBlurFunction(handler)
		delegate(button)
	}
}


<|start_filename|>events/run.go<|end_filename|>
package events

import (
	"sync"

	"github.com/verdverm/tview"
)

var defaultEventStream *EventStream
var defaultWgtMgr WgtMgr
var wgtMgrMuxtx sync.Mutex

var DefaultHandler = func(e Event) {}

var sysEvtChs []chan Event
var customEventCh = make(chan Event, 256)

func Init(app *tview.Application) error {
	sysEvtChs = make([]chan Event, 0)
	go hookEventsFromApp(app)

	defaultEventStream = NewEventStream()
	defaultEventStream.Init()
	defaultEventStream.Merge("tcell", NewSysEvtCh())
	defaultEventStream.Merge("custom", customEventCh)

	defaultWgtMgr = NewWgtMgr()
	defaultEventStream.Hook(defaultWgtMgr.WgtHandlersHook())

	return nil
}

func Start() error {
	defaultEventStream.Loop()
	return nil
}

func Stop() error {
	defaultEventStream.StopLoop()
	return nil
}

func Merge(name string, ec chan Event) {
	defaultEventStream.Merge(name, ec)
}

func AddGlobalHandler(path string, handler func(Event)) {
	defaultEventStream.Handle(path, handler)
}

func RemoveGlobalHandler(path string) {
	defaultEventStream.RemoveHandle(path)
}

func ClearGlobalHandlers() {
	defaultEventStream.ResetHandlers()
}

func AddWidgetHandler(wgt tview.Primitive, path string, handler func(Event)) {
	if _, ok := defaultWgtMgr[wgt.Id()]; !ok {
		defaultWgtMgr.AddWgt(wgt)
	}

	defaultWgtMgr.AddWgtHandler(wgt.Id(), path, handler)
}

func RemoveWidgetHandler(wgt tview.Primitive, path string) {
	_, ok := defaultWgtMgr[wgt.Id()]
	if !ok {
		return
	}

	defaultWgtMgr.RmWgtHandler(wgt.Id(), path)
}

func ClearWidgetHandlers(wgt tview.Primitive) {
	_, ok := defaultWgtMgr[wgt.Id()]
	if !ok {
		return
	}

	defaultWgtMgr.ClearWgtHandlers(wgt.Id())
}


<|start_filename|>mux/request.go<|end_filename|>
package mux

type Request struct {
	Path    string
	Queries map[string][]string

	Context map[string]interface{}
}


<|start_filename|>hoc/form/errors.go<|end_filename|>
package form

import "fmt"

type FormError interface {
	Field() string
	Message() string
	Value() interface{}
	String() string
}

type Error struct {
	field   string
	message string
	value   interface{}
}

func NewError(message, field string, value interface{}) *Error {
	return &Error{
		field:   field,
		message: message,
		value:   value,
	}
}

func (E *Error) Field() string {
	return E.field
}

func (E *Error) Message() string {
	return E.message
}

func (E *Error) Value() interface{} {
	return E.value
}

func (E *Error) String() string {
	return fmt.Sprintf("FormError: %s - field: %q value: %v", E.message, E.field, E.value)
}

type RequiredError struct {
	*Error
}

func NewRequiredError(field string) *RequiredError {
	return &RequiredError{
		Error: &Error{
			field:   field,
			message: "missing required field",
		},
	}
}

type ValidationError struct {
	*Error
}

func NewValidationError(field, validationMessage string, value interface{}) *ValidationError {
	return &ValidationError{
		Error: &Error{
			field:   field,
			message: fmt.Sprintf("field: %q has bad input %q with value: %v", field, validationMessage, value),
			value:   value,
		},
	}
}


<|start_filename|>hoc/console/dev.go<|end_filename|>
package console

import (
	"fmt"
	"strings"

	"github.com/verdverm/tview"

	"github.com/verdverm/vermui"
	"github.com/verdverm/vermui/events"
)

type DevConsoleWidget struct {
	*tview.TextView
}

func NewDevConsoleWidget() *DevConsoleWidget {
	textView := tview.NewTextView().
		SetScrollable(true).
		SetDynamicColors(true).
		SetRegions(true).
		SetChangedFunc(func() {
			vermui.Draw()
		})

	textView.SetTitle(" console ").SetBorder(true)

	C := &DevConsoleWidget{
		TextView: textView,
	}

	return C
}

func (C *DevConsoleWidget) Mount(context map[string]interface{}) error {
	vermui.AddGlobalHandler("/console", func(ev events.Event) {
		d := ev.Data
		switch t := ev.Data.(type) {
		case *events.EventCustom:
			d = t.Data()
		case *events.EventKey:
			d = t.KeyStr
		}
		line := fmt.Sprintf("[%s] %v", ev.When().Format("2006-01-02 15:04:05"), d)

		level := strings.TrimPrefix(ev.Path, "/console/")
		if len(level) > 6 && level[:6] == "color-" {
			color := level[6:]
			line = fmt.Sprintf("[%s]%.5s  %s[white]", color, color, line)
		} else {
			switch level {
			case "crit":
				line = fmt.Sprintf("[red]CRIT   %s[white]", line)
			case "error":
				line = fmt.Sprintf("[orange]ERROR  %s[white]", line)
			case "warn":
				line = fmt.Sprintf("[yellow]WARN   %s[white]", line)
			case "info":
				line = fmt.Sprintf("INFO  %s", line)
			case "debug":
				line = fmt.Sprintf("[green]DEBUG  %s[white]", line)
			case "trace":
				line = fmt.Sprintf("[aqua]TRACE  %s[white]", line)
			}
		}

		fmt.Fprintln(C, line)
		C.ScrollToEnd()
	})

	return nil
}

func (C *DevConsoleWidget) Unmount() error {
	vermui.RemoveWidgetHandler(C, "/console")
	return nil
}


<|start_filename|>hoc/cmdbox/index.go<|end_filename|>
package cmdbox

import (
	"fmt"
	"strings"

	"github.com/gdamore/tcell"
	"github.com/verdverm/tview"
	"github.com/verdverm/vermui"
	"github.com/verdverm/vermui/events"
)

const emptyMsg = "press 'Ctrl-<space>' to enter a command or '/path/to/something' to navigate"

type Command interface {
	CommandName() string
	CommandUsage() string
	CommandHelp() string

	CommandCallback(args []string, context map[string]interface{})
}

type DefaultCommand struct {
	Name  string
	Usage string
	Help  string

	Callback func(args []string, context map[string]interface{})
}

func (DC *DefaultCommand) CommandName() string {
	return DC.Name
}

func (DC *DefaultCommand) CommandHelp() string {
	return DC.Help
}

func (DC *DefaultCommand) CommandUsage() string {
	return DC.Usage
}

func (DC *DefaultCommand) CommandCallback(args []string, context map[string]interface{}) {
	DC.Callback(args, context)
}

type CmdBoxWidget struct {
	*tview.InputField

	commands map[string]Command

	curr    string   // current input (potentially partial)
	hIdx    int      // where we are in history
	history []string // command history
}

func New() *CmdBoxWidget {
	cb := &CmdBoxWidget{
		InputField: tview.NewInputField(),
		commands:   make(map[string]Command),
		history:    []string{},
	}

	cb.InputField.
		SetFieldBackgroundColor(tview.Styles.PrimitiveBackgroundColor).
		SetLabel(" ")

	return cb
}

func (CB *CmdBoxWidget) Id() string {
	return CB.InputField.Id()
}

func (CB *CmdBoxWidget) AddCommandCallback(command string, callback func([]string, map[string]interface{})) Command {
	CB.Lock()
	defer CB.Unlock()
	c := &DefaultCommand{
		Name:     command,
		Usage:    command,
		Help:     "no help for " + command,
		Callback: callback,
	}
	CB.commands[c.CommandName()] = c
	return c
}

func (CB *CmdBoxWidget) AddCommand(command Command) {
	CB.Lock()
	defer CB.Unlock()
	// go events.SendCustomEvent("/console/info", "adding command: "+command.CommandName())
	CB.commands[command.CommandName()] = command
}

func (CB *CmdBoxWidget) RemoveCommand(command Command) {
	delete(CB.commands, command.CommandName())
}

func (CB *CmdBoxWidget) Mount(context map[string]interface{}) error {
	vermui.AddWidgetHandler(CB, "/sys/key/C-<space>", func(e events.Event) {
		CB.Lock()
		CB.curr = ""
		CB.hIdx = len(CB.history)
		CB.Unlock()

		CB.SetText("")
		CB.SetFieldTextColor(tcell.ColorWhite)
		CB.SetBorderColor(tcell.Color69)

		vermui.SetFocus(CB.InputField)
	})

	CB.SetFinishedFunc(func(key tcell.Key) {
		switch key {
		case tcell.KeyEnter:
			input := CB.GetText()
			input = strings.TrimSpace(input)
			if input != "" {
				flds := strings.Fields(input)
				CB.Submit(flds[0], flds[1:])
				CB.SetText("")
				CB.SetBorderColor(tcell.Color27)
				vermui.Unfocus()
			}
		case tcell.KeyEscape:
			CB.SetText("")
			CB.SetBorderColor(tcell.Color27)
			vermui.Unfocus()
		case tcell.KeyTab:
		case tcell.KeyBacktab:
		default:
			go events.SendCustomEvent("/console/warn", fmt.Sprintf("cmdbox (fin-???-key): %v", key))

		}

	})

	return nil
}
func (CB *CmdBoxWidget) Unmount() error {
	vermui.RemoveWidgetHandler(CB, "/sys/key/C-space")

	return nil
}

func (CB *CmdBoxWidget) Submit(command string, args []string) {
	if len(command) == 0 {
		return
	}

	CB.Lock()
	if len(args) == 0 {
		CB.history = append(CB.history, command)
	} else {
		CB.history = append(CB.history, command+" "+strings.Join(args, " "))
	}
	CB.Unlock()

	command = strings.ToLower(command)
	if command[:1] == "/" {
		go events.SendCustomEvent("/router/dispatch", command)
		return
	}
	CB.Lock()
	cmd, ok := CB.commands[command]
	CB.Unlock()
	if !ok {
		vermui.Unfocus()
		// render for the user
		go events.SendCustomEvent("/user/error", fmt.Sprintf("unknown command %q", command))
		// log to console
		go events.SendCustomEvent("/console/warn", fmt.Sprintf("unknown command %q", command))
		return
	}

	go cmd.CommandCallback(args, nil)
}

// InputHandler returns the handler for this primitive.
func (CB *CmdBoxWidget) InputHandler() func(tcell.Event, func(tview.Primitive)) {
	return CB.WrapInputHandler(func(event tcell.Event, setFocus func(p tview.Primitive)) {
		handle := CB.InputField.InputHandler()
		switch evt := event.(type) {
		case *tcell.EventKey:

			dist := 1

			// Process key evt.
			switch key := evt.Key(); key {

			// Upwards, back in history
			case tcell.KeyHome:
				dist = len(CB.history)
				fallthrough
			case tcell.KeyPgUp:
				dist += 4
				fallthrough
			case tcell.KeyUp: // Regular character.
				if CB.hIdx == len(CB.history) {
					CB.curr = CB.GetText()
				}
				CB.hIdx -= dist
				if CB.hIdx < 0 {
					CB.hIdx = 0
				}
				if len(CB.history) > 0 {
					CB.SetText(CB.history[CB.hIdx])
				}

			// Downwards, more recent in history
			case tcell.KeyEnd:
				dist = len(CB.history)
				fallthrough
			case tcell.KeyPgDn:
				dist += 4
				fallthrough
			case tcell.KeyDown:
				CB.hIdx += dist
				if CB.hIdx > len(CB.history) {
					CB.hIdx = len(CB.history)
				}

				if CB.hIdx == len(CB.history) {
					CB.SetText(CB.curr)
					return
				}
				if len(CB.history) > 0 {
					CB.SetText(CB.history[CB.hIdx])
				}

			// Default is to pass through to InputField handler
			default:
				CB.hIdx = len(CB.history)
				handle(event, setFocus)

			}

		}
	})
}


<|start_filename|>hoc/stream-table/widget.go<|end_filename|>
package streamtable

import (
	"github.com/verdverm/tview"
	"github.com/verdverm/vermui"
)

type StreamTableSource func(chan string) chan interface{}
type StreamTableFormatter func(interface{}) [][]*tview.TableCell

type StreamTable struct {
	*tview.Table

	TableHeader   [][]*tview.TableCell
	DataSource    StreamTableSource
	DataFormatter StreamTableFormatter

	dataCommands chan string
	dataStreamer chan interface{}
	quitChan     chan int
}

func NewStreamTable(header [][]*tview.TableCell, source StreamTableSource, formatter StreamTableFormatter) *StreamTable {
	ST := &StreamTable{
		Table:         tview.NewTable(),
		TableHeader:   header,
		DataSource:    source,
		DataFormatter: formatter,
	}

	return ST
}

func (ST *StreamTable) StartStream() {
	ST.Lock()
	defer ST.Unlock()

	// already shown
	if ST.dataStreamer != nil {
		return
	}

	ST.quitChan = make(chan int)
	ST.dataCommands = make(chan string)
	ST.dataStreamer = ST.DataSource(ST.dataCommands)

	go func() {
		for {
			ST.Lock()
			ds := ST.dataStreamer
			ST.Unlock()
			select {
			case data := <-ds:
				ST.UpdateData(data)

			case <-ST.quitChan:
				ST.Lock()
				defer ST.Unlock()
				ST.dataCommands <- "quit"
				close(ST.dataCommands)
				close(ST.quitChan)
				ST.quitChan = nil
				ST.dataCommands = nil
				ST.dataStreamer = nil
				return
			}
		}
	}()
}
func (ST *StreamTable) StopStream() {
	ST.quitChan <- 1
}

func (ST *StreamTable) UpdateData(input interface{}) {

	ST.Lock()
	data := ST.DataFormatter(input)

	cells := [][]*tview.TableCell{}
	cells = append(cells, ST.TableHeader...)
	cells = append(cells, data...)

	ST.Unlock()

	ST.Table.SetCells(cells)

	vermui.Draw()
}


<|start_filename|>vermui.go<|end_filename|>
package vermui

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"runtime/debug"
	"sync"

	"github.com/maruel/panicparse/stack"
	"github.com/verdverm/tview"

	"github.com/verdverm/vermui/events"
)

var app *tview.Application
var appLock sync.RWMutex
var rootView tview.Primitive

// Init initializes vermui library. This function should be called before any others.
// After initialization, the library must be finalized by 'Close' function.
func Init() error {
	app = tview.NewApplication()

	err := events.Init(app)
	if err != nil {
		return err
	}

	events.AddGlobalHandler("/", events.DefaultHandler)

	events.AddGlobalHandler("/sys/redraw", func(e events.Event) {
		Draw()
	})

	return nil
}

// blocking call
func Start() error {

	// catch panics, clean up, format error
	defer func() {
		e := recover()
		if e != nil {
			Stop()
			// Print a formatted panic output
			fmt.Fprintf(os.Stderr, "Captured a panic(value=%v) lib.Start()... Exit vermui and clean terminal...\nPrint stack trace:\n\n", e)
			//debug.PrintStack()
			gs, err := stack.ParseDump(bytes.NewReader(debug.Stack()), os.Stderr)
			if err != nil {
				debug.PrintStack()
				os.Exit(1)
			}
			p := &stack.Palette{}
			buckets := stack.SortBuckets(stack.Bucketize(gs, stack.AnyValue))
			srcLen, pkgLen := stack.CalcLengths(buckets, false)
			for _, bucket := range buckets {
				io.WriteString(os.Stdout, p.BucketHeader(&bucket, false, len(buckets) > 1))
				io.WriteString(os.Stdout, p.StackLines(&bucket.Signature, srcLen, pkgLen, false))
			}
			panic(e)
		}
	}()

	// start the event engine
	go events.Start()

	err := rootView.Mount(nil)
	if err != nil {
		panic(err)
	}

	// blocking
	app.SetRoot(rootView, true)
	return app.Run()
}

// Close finalizes vermui library,
// should be called after successful initialization when vermui's functionality isn't required anymore.
func Stop() error {
	//appLock.Lock()
	//defer appLock.Unlock()

	app.Stop()
	err := events.Stop()
	if err != nil {
		return err
	}
	return nil
}

func Application() *tview.Application {
	return app
}

func Draw() {
	appLock.RLock()
	defer appLock.RUnlock()

	go app.Draw()
}

func Clear() {
	//appLock.Lock()
	//defer appLock.Unlock()

	if app == nil {
		// really shouldn't get here, but the event stream is still running
		return
	}
	screen := app.Screen()
	if screen != nil {
		screen.Clear()
		screen.Sync()
	}
}

func GetRootView() tview.Primitive {
	return rootView
}

func SetRootView(v tview.Primitive) {
	rootView = v
}

func GetFocus() (p tview.Primitive) {
	//appLock.RLock()
	//defer appLock.RLock()

	return app.GetFocus()
}

func SetFocus(p tview.Primitive) {
	//appLock.Lock()
	//defer appLock.Unlock()

	if app == nil {
		// really shouldn't get here, but the event stream is still running
		return
	}

	// go app.Screen().HideCursor()
	app.SetFocus(p)
	Draw()
}
func Unfocus() {
	//appLock.Lock()
	//defer appLock.Unlock()

	if app == nil {
		// really shouldn't get here, but the event stream is still running
		return
	}

	// go app.Screen().HideCursor()
	app.SetFocus(rootView)
	Draw()
}

func AddGlobalHandler(path string, handler func(events.Event)) {
	events.AddGlobalHandler(path, handler)
}

func RemoveGlobalHandler(path string) {
	events.RemoveGlobalHandler(path)
}

func ClearGlobalHandlers() {
	events.ClearGlobalHandlers()
}

func AddWidgetHandler(widget tview.Primitive, path string, handler func(events.Event)) {
	events.AddWidgetHandler(widget, path, handler)
}

func RemoveWidgetHandler(widget tview.Primitive, path string) {
	events.RemoveWidgetHandler(widget, path)
}

func ClearWidgetHandlers(widget tview.Primitive) {
	events.ClearWidgetHandlers(widget)
}


<|start_filename|>mux/mux.go<|end_filename|>
// Copyright 2012 The Gorilla Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package mux

import (
	"fmt"
	"net/url"
	"path"
	"regexp"

	"github.com/pkg/errors"
	"github.com/verdverm/tview"
)

var (
	ErrMethodMismatch = errors.New("method is not allowed")
	ErrNotFound       = errors.New("no matching route was found")
)

// NewRouter returns a new router instance.
func NewRouter() *Router {
	return &Router{namedRoutes: make(map[string]*Route)}
}

// Router registers routes to be matched and dispatches a handler.
//
// It implements the http.Handler interface, so it can be registered to serve
// requests:
//
//     var router = mux.NewRouter()
//
//     func main() {
//         http.Handle("/", router)
//     }
//
// Or, for Google App Engine, register it in a init() function:
//
//     func init() {
//         http.Handle("/", router)
//     }
//
// This will send all incoming requests to the router.
type Router struct {
	// Configurable Handler to be used when no route matches.
	NotFoundHandler Handler

	// Parent route, if this is a subrouter.
	parent parentRoute
	// Routes to be matched, in order.
	routes []*Route
	// Routes by name for URL building.
	namedRoutes map[string]*Route
	// Slice of middlewares to be called after a match is found
	middlewares []middleware
}

// Match attempts to match the given request against the router's registered routes.
//
// If the request matches a route of this router or one of its subrouters the Route,
// Handler, and Vars fields of the the match argument are filled and this function
// returns true.
//
// If the request does not match any of this router's or its subrouters' routes
// then this function returns false. If available, a reason for the match failure
// will be filled in the match argument's MatchErr field. If the match failure type
// (eg: not found) has a registered handler, the handler is assigned to the Handler
// field of the match argument.
func (r *Router) Match(req *Request, match *RouteMatch) bool {
	for _, route := range r.routes {
		if route.Match(req, match) {
			return true
		}
	}

	// Closest match for a router (includes sub-routers)
	if r.NotFoundHandler != nil {
		match.Handler = r.NotFoundHandler
		match.MatchErr = ErrNotFound
		return true
	}

	match.MatchErr = ErrNotFound
	return false
}

// Dispatches the handler registered in the matched route.
//
// When there is a match, the route variables can be retrieved calling
// mux.Vars(request).
func (r *Router) Dispatch(fullpath string, context map[string]interface{}) (tview.Primitive, *Request, error) {
	u, err := url.Parse(fullpath)
	if err != nil {
		return nil, nil, errors.Wrapf(err, "in Router.Dispatch(path): %q\n", fullpath)
	}
	if context == nil {
		context = make(map[string]interface{})
	}
	req := &Request{
		Path:    u.Path,
		Queries: u.Query(),
		Context: context,
	}

	// Clean path to canonical form and redirect.
	if p := cleanPath(req.Path); p != req.Path {
		req.Path = p
	}

	return r.Serve(req)
}

func (r *Router) Serve(req *Request) (tview.Primitive, *Request, error) {
	var match RouteMatch
	var handler Handler
	if r.Match(req, &match) {
		handler = match.Handler
		req = setVars(req, match.Vars)
		req = setCurrentRoute(req, match.Route)
	}
	// fmt.Printf("\n\n\n\nMatch !!!\n\n%+v\n\n%+v\n\n", req, match)

	if handler == nil {
		handler = r.NotFoundHandler
	}

	// finalize dispatch by calling the handler
	return handler.Serve(req)
}

// Get returns a route registered with the given name.
func (r *Router) Get(name string) *Route {
	return r.getNamedRoutes()[name]
}

// GetRoute returns a route registered with the given name. This method
// was renamed to Get() and remains here for backwards compatibility.
func (r *Router) GetRoute(name string) *Route {
	return r.getNamedRoutes()[name]
}

// ----------------------------------------------------------------------------
// parentRoute
// ----------------------------------------------------------------------------

// getNamedRoutes returns the map where named routes are registered.
func (r *Router) getNamedRoutes() map[string]*Route {
	if r.namedRoutes == nil {
		if r.parent != nil {
			r.namedRoutes = r.parent.getNamedRoutes()
		} else {
			r.namedRoutes = make(map[string]*Route)
		}
	}
	return r.namedRoutes
}

// getRegexpGroup returns regexp definitions from the parent route, if any.
func (r *Router) getRegexpGroup() *routeRegexpGroup {
	if r.parent != nil {
		return r.parent.getRegexpGroup()
	}
	return nil
}

func (r *Router) buildVars(m map[string]string) map[string]string {
	if r.parent != nil {
		m = r.parent.buildVars(m)
	}
	return m
}

// ----------------------------------------------------------------------------
// Route factories
// ----------------------------------------------------------------------------

// NewRoute registers an empty route.
func (r *Router) NewRoute() *Route {
	route := &Route{parent: r}
	r.routes = append(r.routes, route)
	return route
}

// Handle registers a new route with a matcher for the URL path.
// See Route.Path() and Route.Handler().
func (r *Router) Handle(path string, handler Handler) *Route {
	return r.NewRoute().Path(path).Handler(handler)
}

// MatcherFunc registers a new route with a custom matcher function.
// See Route.MatcherFunc().
func (r *Router) MatcherFunc(f MatcherFunc) *Route {
	return r.NewRoute().MatcherFunc(f)
}

// Path registers a new route with a matcher for the URL path.
// See Route.Path().
func (r *Router) Path(tpl string) *Route {
	return r.NewRoute().Path(tpl)
}

// PathPrefix registers a new route with a matcher for the URL path prefix.
// See Route.PathPrefix().
func (r *Router) PathPrefix(tpl string) *Route {
	return r.NewRoute().PathPrefix(tpl)
}

// Queries registers a new route with a matcher for URL query values.
// See Route.Queries().
func (r *Router) Queries(pairs ...string) *Route {
	return r.NewRoute().Queries(pairs...)
}

// BuildVarsFunc registers a new route with a custom function for modifying
// route variables before building a URL.
func (r *Router) BuildVarsFunc(f BuildVarsFunc) *Route {
	return r.NewRoute().BuildVarsFunc(f)
}

// Walk walks the router and all its sub-routers, calling walkFn for each route
// in the tree. The routes are walked in the order they were added. Sub-routers
// are explored depth-first.
func (r *Router) Walk(walkFn WalkFunc) error {
	return r.walk(walkFn, []*Route{})
}

// SkipRouter is used as a return value from WalkFuncs to indicate that the
// router that walk is about to descend down to should be skipped.
var SkipRouter = errors.New("skip this router")

// WalkFunc is the type of the function called for each route visited by Walk.
// At every invocation, it is given the current route, and the current router,
// and a list of ancestor routes that lead to the current route.
type WalkFunc func(route *Route, router *Router, ancestors []*Route) error

func (r *Router) walk(walkFn WalkFunc, ancestors []*Route) error {
	for _, t := range r.routes {
		err := walkFn(t, r, ancestors)
		if err == SkipRouter {
			continue
		}
		if err != nil {
			return err
		}
		for _, sr := range t.matchers {
			if h, ok := sr.(*Router); ok {
				ancestors = append(ancestors, t)
				err := h.walk(walkFn, ancestors)
				if err != nil {
					return err
				}
				ancestors = ancestors[:len(ancestors)-1]
			}
		}
		if h, ok := t.handler.(*Router); ok {
			ancestors = append(ancestors, t)
			err := h.walk(walkFn, ancestors)
			if err != nil {
				return err
			}
			ancestors = ancestors[:len(ancestors)-1]
		}
	}
	return nil
}

// ----------------------------------------------------------------------------
// Context
// ----------------------------------------------------------------------------

// RouteMatch stores information about a matched route.
type RouteMatch struct {
	Route   *Route
	Handler Handler
	Vars    map[string]string

	// MatchErr is set to appropriate matching error
	// It is set to ErrMethodMismatch if there is a mismatch in
	// the request method and route method
	MatchErr error
}

type contextKey int

const (
	varsKey  = "vars"
	routeKey = "route"
)

// Vars returns the route variables for the current request, if any.
func Vars(r *Request) map[string]string {
	if rv := r.Context[varsKey]; rv != nil {
		return rv.(map[string]string)
	}
	return nil
}

// CurrentRoute returns the matched route for the current request, if any.
// This only works when called inside the handler of the matched route
// because the matched route is stored in the request context which is cleared
// after the handler returns, unless the KeepContext option is set on the
// Router.
func CurrentRoute(r *Request) *Route {
	if rv := r.Context[routeKey]; rv != nil {
		return rv.(*Route)
	}
	return nil
}

func setVars(r *Request, val interface{}) *Request {
	r.Context[varsKey] = val
	return r
}

func setCurrentRoute(r *Request, val interface{}) *Request {
	r.Context[routeKey] = val
	return r
}

// ----------------------------------------------------------------------------
// Helpers
// ----------------------------------------------------------------------------

// cleanPath returns the canonical path for p, eliminating . and .. elements.
// Borrowed from the net/http package.
func cleanPath(p string) string {
	if p == "" {
		return "/"
	}
	if p[0] != '/' {
		p = "/" + p
	}
	np := path.Clean(p)
	// path.Clean removes trailing slash except for root;
	// put the trailing slash back if necessary.
	if p[len(p)-1] == '/' && np != "/" {
		np += "/"
	}

	return np
}

// uniqueVars returns an error if two slices contain duplicated strings.
func uniqueVars(s1, s2 []string) error {
	for _, v1 := range s1 {
		for _, v2 := range s2 {
			if v1 == v2 {
				return fmt.Errorf("mux: duplicated route variable %q", v2)
			}
		}
	}
	return nil
}

// checkPairs returns the count of strings passed in, and an error if
// the count is not an even number.
func checkPairs(pairs ...string) (int, error) {
	length := len(pairs)
	if length%2 != 0 {
		return length, fmt.Errorf(
			"mux: number of parameters must be multiple of 2, got %v", pairs)
	}
	return length, nil
}

// mapFromPairsToString converts variadic string parameters to a
// string to string map.
func mapFromPairsToString(pairs ...string) (map[string]string, error) {
	length, err := checkPairs(pairs...)
	if err != nil {
		return nil, err
	}
	m := make(map[string]string, length/2)
	for i := 0; i < length; i += 2 {
		m[pairs[i]] = pairs[i+1]
	}
	return m, nil
}

// mapFromPairsToRegex converts variadic string parameters to a
// string to regex map.
func mapFromPairsToRegex(pairs ...string) (map[string]*regexp.Regexp, error) {
	length, err := checkPairs(pairs...)
	if err != nil {
		return nil, err
	}
	m := make(map[string]*regexp.Regexp, length/2)
	for i := 0; i < length; i += 2 {
		regex, err := regexp.Compile(pairs[i+1])
		if err != nil {
			return nil, err
		}
		m[pairs[i]] = regex
	}
	return m, nil
}

// matchInArray returns true if the given string value is in the array.
func matchInArray(arr []string, value string) bool {
	for _, v := range arr {
		if v == value {
			return true
		}
	}
	return false
}

// matchMapWithString returns true if the given key/value pairs exist in a given map.
func matchMapWithString(toCheck map[string]string, toMatch map[string][]string, canonicalKey bool) bool {
	for k, v := range toCheck {

		if values := toMatch[k]; values == nil {
			return false
		} else if v != "" {
			// If value was defined as an empty string we only check that the
			// key exists. Otherwise we also check for equality.
			valueExists := false
			for _, value := range values {
				if v == value {
					valueExists = true
					break
				}
			}
			if !valueExists {
				return false
			}
		}
	}
	return true
}

// matchMapWithRegex returns true if the given key/value pairs exist in a given map compiled against
// the given regex
func matchMapWithRegex(toCheck map[string]*regexp.Regexp, toMatch map[string][]string, canonicalKey bool) bool {
	for k, v := range toCheck {
		if values := toMatch[k]; values == nil {
			return false
		} else if v != nil {
			// If value was defined as an empty string we only check that the
			// key exists. Otherwise we also check for equality.
			valueExists := false
			for _, value := range values {
				if v.MatchString(value) {
					valueExists = true
					break
				}
			}
			if !valueExists {
				return false
			}
		}
	}
	return true
}


<|start_filename|>hoc/layouts/panels/layout.go<|end_filename|>
// Panels Layout is a Flex widget with
// hidable panels and a main content.
// Can be vert or horz oriendted throught the Flex widget.
// Panels can be jumped to with <focuskey> and hidden with <shift>-<focuskey>
// Recommend making the <focuskey> an: '<alt>-<key>' and hidden will be '<shift>-<alt>-<key>'
// Normal movement and interaction keys within the focussed panel.
//
// main (middle) panel, can be anything, including...
// - the router (when this is the root view)
// - another DashAndPanels
// - a pager, grid, or any other primitive
package panels

import (
	"fmt"
	"sort"

	"github.com/verdverm/tview"
	"github.com/verdverm/vermui"
	"github.com/verdverm/vermui/events"
)

type Panel struct {
	Name       string
	Item       tview.Primitive
	FixedSize  int
	Proportion int
	Focus      int
	FocusKey   string
	Hidden     bool
	HiddenKey  string
}

type Layout struct {
	*tview.Flex

	// first (left/top) panels, can be almost anything and hidden.
	fPanels map[string]*Panel

	// main (middle) panel, can be anything, I think.
	mPanel *Panel

	// last (right/bottom) panels, can be almost anything and hidden.
	lPanels map[string]*Panel
}

func New() *Layout {
	L := &Layout{
		Flex:    tview.NewFlex(),
		fPanels: map[string]*Panel{},
		lPanels: map[string]*Panel{},
	}

	return L
}

// AddFirstPanel adds a Panel to the left or top, depending on orientation.
func (L *Layout) AddFirstPanel(name string, item tview.Primitive, fixedSize, proportion,
	focus int, focuskey string, hidden bool, hiddenkey string) {
	panel := &Panel{
		Name:       name,
		Item:       item,
		FixedSize:  fixedSize,
		Proportion: proportion,
		Focus:      focus,
		FocusKey:   focuskey,
		Hidden:     hidden,
		HiddenKey:  hiddenkey,
	}

	L.fPanels[name] = panel
}

// AddLastPanel adds a Panel to the right or bottom, depending on orientation.
func (L *Layout) AddLastPanel(name string, item tview.Primitive, fixedSize, proportion,
	focus int, focuskey string, hidden bool, hiddenkey string) {
	panel := &Panel{
		Name:       name,
		Item:       item,
		FixedSize:  fixedSize,
		Proportion: proportion,
		Focus:      focus,
		FocusKey:   focuskey,
		Hidden:     hidden,
		HiddenKey:  hiddenkey,
	}

	L.lPanels[name] = panel
}

func (L *Layout) SetMainPanel(name string, item tview.Primitive, fixedSize, proportion, focus int, focuskey string) {
	panel := &Panel{
		Name:       name,
		Item:       item,
		FixedSize:  fixedSize,
		Proportion: proportion,
		Focus:      focus,
		FocusKey:   focuskey,
	}

	L.mPanel = panel
}

func (L *Layout) Mount(context map[string]interface{}) error {
	err := L.build()
	if err != nil {
		return err
	}

	// Setup focuskeys
	for _, panel := range L.fPanels {
		panel.Item.Mount(context)
		if panel.FocusKey != "" {
			localPanel := panel
			vermui.AddWidgetHandler(L, "/sys/key/"+localPanel.FocusKey, func(e events.Event) {
				go events.SendCustomEvent("/console/trace", "Focus: "+localPanel.Name)
				vermui.SetFocus(localPanel.Item)
			})
		}
		if panel.HiddenKey != "" {
			localPanel := panel
			vermui.AddWidgetHandler(L, "/sys/key/"+localPanel.HiddenKey, func(e events.Event) {
				localPanel.Hidden = !localPanel.Hidden
				go events.SendCustomEvent("/console/trace", fmt.Sprintf("Hidden: %s (%v)", localPanel.Name, localPanel.Hidden))
				L.build()
				if localPanel.Hidden {
					vermui.SetFocus(L.mPanel.Item)
				} else {
					vermui.SetFocus(localPanel.Item)
				}
				vermui.Draw()
			})
		}
	}
	if L.mPanel.FocusKey != "" {
		L.mPanel.Item.Mount(context)
		localPanel := L.mPanel
		vermui.AddWidgetHandler(L, "/sys/key/"+localPanel.FocusKey, func(e events.Event) {
			go events.SendCustomEvent("/console/trace", "Focus: "+localPanel.Name)
			vermui.SetFocus(localPanel.Item)
		})
	}
	for _, panel := range L.lPanels {
		panel.Item.Mount(context)
		if panel.FocusKey != "" {
			localPanel := panel
			vermui.AddWidgetHandler(L, "/sys/key/"+localPanel.FocusKey, func(e events.Event) {
				go events.SendCustomEvent("/console/trace", "Focus: "+localPanel.Name)
				vermui.SetFocus(localPanel.Item)
			})
		}
		if panel.HiddenKey != "" {
			localPanel := panel
			vermui.AddWidgetHandler(L, "/sys/key/"+localPanel.HiddenKey, func(e events.Event) {
				localPanel.Hidden = !localPanel.Hidden
				go events.SendCustomEvent("/console/trace", fmt.Sprintf("Hidden: %s (%v)", localPanel.Name, localPanel.Hidden))
				L.build()
				if localPanel.Hidden {
					vermui.SetFocus(L.mPanel.Item)
				} else {
					vermui.SetFocus(localPanel.Item)
				}
				vermui.Draw()
			})
		}
	}

	return nil
}

func (L *Layout) build() error {
	// get and order the fPanels
	fPs := []*Panel{}
	for _, panel := range L.fPanels {
		if panel.Hidden {
			continue
		}
		fPs = append(fPs, panel)
	}
	sort.Slice(fPs, func(i, j int) bool {
		return fPs[i].Focus < fPs[j].Focus
	})

	// get and order the lPanels
	lPs := []*Panel{}
	for _, panel := range L.lPanels {
		if panel.Hidden {
			continue
		}
		lPs = append(lPs, panel)
	}
	sort.Slice(lPs, func(i, j int) bool {
		return lPs[i].Focus < lPs[j].Focus
	})

	// Start a fresh Flex item
	orient := L.GetDirection()
	L.Flex = tview.NewFlex().SetDirection(orient)

	for _, p := range fPs {
		L.AddItem(p.Item, p.FixedSize, p.Proportion, false)
	}

	p := L.mPanel
	L.AddItem(p.Item, p.FixedSize, p.Proportion, true)

	for _, p := range lPs {
		L.AddItem(p.Item, p.FixedSize, p.Proportion, false)
	}

	return nil
}


<|start_filename|>mux/test_helpers.go<|end_filename|>
// Copyright 2012 The Gorilla Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package mux

// SetURLVars sets the URL variables for the given request, to be accessed via
// mux.Vars for testing route behaviour.
//
// This API should only be used for testing purposes; it provides a way to
// inject variables into the request context. Alternatively, URL variables
// can be set by making a route that captures the required variables,
// starting a server and sending the request to that server.
func SetURLVars(r *Request, val map[string]string) *Request {
	return setVars(r, val)
}


<|start_filename|>hoc/router/router.go<|end_filename|>
package router

import (
	"github.com/pkg/errors"
	"github.com/verdverm/tview"

	"github.com/verdverm/vermui"
	"github.com/verdverm/vermui/events"
	"github.com/verdverm/vermui/mux"
)

type RoutePair struct {
	Path  string
	Thing interface{}
}

type Routable interface {
	Routings() []RoutePair
}

type Router struct {
	*tview.Pages

	// internal router
	iRouter *mux.Router
}

func New() *Router {
	r := &Router{
		Pages:   tview.NewPages(),
		iRouter: mux.NewRouter(),
	}

	vermui.AddWidgetHandler(r.Pages, "/router/dispatch", func(ev events.Event) {
		path := ev.Data.(*events.EventCustom).Data().(string)
		context := map[string]interface{}{
			"activation": "dispatch",
			"path":       path,
			"data":       ev.Data.(*events.EventCustom).Data(),
			"event":      ev,
		}
		r.SetActive(path, context)
	})

	return r
}

func (R *Router) SetNotFound(layout tview.Primitive) {
	handler := func(req *mux.Request) (tview.Primitive, *mux.Request, error) {
		return layout, req, nil
	}
	R.iRouter.NotFoundHandler = mux.NewDefaultHandler(handler)
	R.AddPage(layout.Id(), layout, true, false)
}

func (R *Router) AddRoute(path string, thing interface{}) error {

	switch t := thing.(type) {
	case tview.Primitive:
		R.AddRouteLayout(path, t)

	case mux.HandlerFunc:
		R.AddRouteHandlerFunc(path, t)

	case mux.Handler:
		R.AddRouteHandler(path, t)

	default:
		return errors.New("Unknown thing to be routed to...")
	}

	return nil
}

func (R *Router) AddRouteLayout(path string, layout tview.Primitive) error {
	R.AddPage(layout.Id(), layout, true, false)
	handler := func(req *mux.Request) (tview.Primitive, *mux.Request, error) {
		return layout, req, nil
	}
	R.iRouter.Handle(path, mux.NewDefaultHandler(handler))
	return nil
}

func (R *Router) AddRouteHandlerFunc(path string, handler mux.HandlerFunc) error {
	R.iRouter.Handle(path, mux.NewDefaultHandler(handler))
	return nil
}

func (R *Router) AddRouteHandler(path string, handler mux.Handler) error {
	R.iRouter.Handle(path, handler)
	return nil
}

func (R *Router) SetActive(path string, context map[string]interface{}) {
	layout, req, err := R.iRouter.Dispatch(path, context)
	if err != nil {
		go events.SendCustomEvent("/console/error", errors.Wrap(err, "in dispatch handler"))
	}
	if layout != nil {
		ctx := req.Context
		req.Context = nil
		ctx["req"] = req
		R.setActive(layout, ctx)
	} else {
		go events.SendCustomEvent("/console/error", "nil layout in dispatch handler")
	}
}

func (R *Router) setActive(layout tview.Primitive, context map[string]interface{}) {
	R.Pages.SwitchToPage(layout.Id(), context)
	vermui.Draw()
}


<|start_filename|>mux/route.go<|end_filename|>
// Copyright 2012 The Gorilla Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package mux

import (
	"fmt"
	"strings"

	"github.com/pkg/errors"
)

// Route stores information to match a request and build URLs.
type Route struct {
	// Parent where the route was registered (a Router).
	parent parentRoute
	// Request handler for the route.
	handler Handler
	// List of matchers.
	matchers []matcher
	// Manager for the variables from host and path.
	regexp *routeRegexpGroup
	// If true, this route never matches: it is only used to build URLs.
	buildOnly bool
	// The name used to build URLs.
	name string
	// Error resulted from building a route.
	err error

	buildVarsFunc BuildVarsFunc
}

// Match matches the route against the request.
func (r *Route) Match(req *Request, match *RouteMatch) bool {
	if r.buildOnly || r.err != nil {
		return false
	}

	var matchErr error

	// Match everything.
	for _, m := range r.matchers {
		if matched := m.Match(req, match); !matched {
			matchErr = nil
			return false
		}
	}

	if matchErr != nil {
		match.MatchErr = matchErr
		return false
	}

	if match.MatchErr == ErrMethodMismatch {
		// We found a route which matches request method, clear MatchErr
		match.MatchErr = nil
		// Then override the mis-matched handler
		match.Handler = r.handler
	}

	// Yay, we have a match. Let's collect some info about it.
	if match.Route == nil {
		match.Route = r
	}
	if match.Handler == nil {
		match.Handler = r.handler
	}
	if match.Vars == nil {
		match.Vars = make(map[string]string)
	}

	// Set variables.
	if r.regexp != nil {
		r.regexp.setMatch(req, match, r)
	}
	return true
}

// ----------------------------------------------------------------------------
// Route attributes
// ----------------------------------------------------------------------------

// GetError returns an error resulted from building the route, if any.
func (r *Route) GetError() error {
	return r.err
}

// BuildOnly sets the route to never match: it is only used to build URLs.
func (r *Route) BuildOnly() *Route {
	r.buildOnly = true
	return r
}

// Handler --------------------------------------------------------------------

func (R *Route) HandlerFunc(HandlerFunc) {

}

// Handler sets a handler for the route.
func (r *Route) Handler(handler Handler) *Route {
	if r.err == nil {
		r.handler = handler
	}
	return r
}

// GetHandler returns the handler for the route, if any.
func (r *Route) GetHandler() Handler {
	return r.handler
}

// Name -----------------------------------------------------------------------

// Name sets the name for the route, used to build URLs.
// If the name was registered already it will be overwritten.
func (r *Route) Name(name string) *Route {
	if r.name != "" {
		r.err = fmt.Errorf("mux: route already has name %q, can't set %q",
			r.name, name)
	}
	if r.err == nil {
		r.name = name
		r.getNamedRoutes()[name] = r
	}
	return r
}

// GetName returns the name for the route, if any.
func (r *Route) GetName() string {
	return r.name
}

// ----------------------------------------------------------------------------
// Matchers
// ----------------------------------------------------------------------------

// matcher types try to match a request.
type matcher interface {
	Match(*Request, *RouteMatch) bool
}

// addMatcher adds a matcher to the route.
func (r *Route) addMatcher(m matcher) *Route {
	if r.err == nil {
		r.matchers = append(r.matchers, m)
	}
	return r
}

// addRegexpMatcher adds a host or path matcher and builder to a route.
func (r *Route) addRegexpMatcher(tpl string, typ regexpType) error {
	if r.err != nil {
		return r.err
	}
	r.regexp = r.getRegexpGroup()
	if typ == regexpTypePath || typ == regexpTypePrefix {
		if len(tpl) > 0 && tpl[0] != '/' {
			return fmt.Errorf("mux: path must start with a slash, got %q", tpl)
		}
		if r.regexp.path != nil {
			tpl = strings.TrimRight(r.regexp.path.template, "/") + tpl
		}
	}
	rr, err := newRouteRegexp(tpl, typ, routeRegexpOptions{})
	if err != nil {
		return err
	}
	for _, q := range r.regexp.queries {
		if err = uniqueVars(rr.varsN, q.varsN); err != nil {
			return err
		}
	}
	if typ == regexpTypeQuery {
		r.regexp.queries = append(r.regexp.queries, rr)
	} else {
		r.regexp.path = rr
	}

	r.addMatcher(rr)
	return nil
}

// MatcherFunc ----------------------------------------------------------------

// MatcherFunc is the function signature used by custom matchers.
type MatcherFunc func(*Request, *RouteMatch) bool

// Match returns the match for a given request.
func (m MatcherFunc) Match(r *Request, match *RouteMatch) bool {
	return m(r, match)
}

// MatcherFunc adds a custom function to be used as request matcher.
func (r *Route) MatcherFunc(f MatcherFunc) *Route {
	return r.addMatcher(f)
}

// Path -----------------------------------------------------------------------

// Path adds a matcher for the URL path.
// It accepts a template with zero or more URL variables enclosed by {}. The
// template must start with a "/".
// Variables can define an optional regexp pattern to be matched:
//
// - {name} matches anything until the next slash.
//
// - {name:pattern} matches the given regexp pattern.
//
// For example:
//
//     r := mux.NewRouter()
//     r.Path("/products/").Handler(ProductsHandler)
//     r.Path("/products/{key}").Handler(ProductsHandler)
//     r.Path("/articles/{category}/{id:[0-9]+}").
//       Handler(ArticleHandler)
//
// Variable names must be unique in a given route. They can be retrieved
// calling mux.Vars(request).
func (r *Route) Path(tpl string) *Route {
	r.err = r.addRegexpMatcher(tpl, regexpTypePath)
	return r
}

// PathPrefix -----------------------------------------------------------------

// PathPrefix adds a matcher for the URL path prefix. This matches if the given
// template is a prefix of the full URL path. See Route.Path() for details on
// the tpl argument.
//
// Note that it does not treat slashes specially ("/foobar/" will be matched by
// the prefix "/foo") so you may want to use a trailing slash here.
//
// Also note that the setting of Router.StrictSlash() has no effect on routes
// with a PathPrefix matcher.
func (r *Route) PathPrefix(tpl string) *Route {
	r.err = r.addRegexpMatcher(tpl, regexpTypePrefix)
	return r
}

// Query ----------------------------------------------------------------------

// Queries adds a matcher for URL query values.
// It accepts a sequence of key/value pairs. Values may define variables.
// For example:
//
//     r := mux.NewRouter()
//     r.Queries("foo", "bar", "id", "{id:[0-9]+}")
//
// The above route will only match if the URL contains the defined queries
// values, e.g.: ?foo=bar&id=42.
//
// It the value is an empty string, it will match any value if the key is set.
//
// Variables can define an optional regexp pattern to be matched:
//
// - {name} matches anything until the next slash.
//
// - {name:pattern} matches the given regexp pattern.
func (r *Route) Queries(pairs ...string) *Route {
	length := len(pairs)
	if length%2 != 0 {
		r.err = fmt.Errorf(
			"mux: number of parameters must be multiple of 2, got %v", pairs)
		return nil
	}
	for i := 0; i < length; i += 2 {
		if r.err = r.addRegexpMatcher(pairs[i]+"="+pairs[i+1], regexpTypeQuery); r.err != nil {
			return r
		}
	}

	return r
}

// BuildVarsFunc --------------------------------------------------------------

// BuildVarsFunc is the function signature used by custom build variable
// functions (which can modify route variables before a route's URL is built).
type BuildVarsFunc func(map[string]string) map[string]string

// BuildVarsFunc adds a custom function to be used to modify build variables
// before a route's URL is built.
func (r *Route) BuildVarsFunc(f BuildVarsFunc) *Route {
	r.buildVarsFunc = f
	return r
}

// Subrouter ------------------------------------------------------------------

// Subrouter creates a subrouter for the route.
//
// It will test the inner routes only if the parent route matched. For example:
//
//     r := mux.NewRouter()
//     s := r.Host("www.example.com").Subrouter()
//     s.HandleFunc("/products/", ProductsHandler)
//     s.HandleFunc("/products/{key}", ProductHandler)
//     s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)
//
// Here, the routes registered in the subrouter won't be tested if the host
// doesn't match.
func (r *Route) Subrouter() *Router {
	router := &Router{parent: r}
	r.addMatcher(router)
	return router
}

// ----------------------------------------------------------------------------
// URL building
// ----------------------------------------------------------------------------

// Substitute builds the full path from the template for the route.
//
// It accepts a sequence of key/value pairs for the route variables. For
// example, given this route:
//
//     r := mux.NewRouter()
//     r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
//       Name("article")
//
// ...a Substitution for it can be built using:
//
//     url, err := r.Get("article").Substitute("category", "technology", "id", "42")
//
// ...which will return an url.URL with the following path:
//
//     "/articles/technology/42"
//
// This also works for host variables:
//
//     r := mux.NewRouter()
//     r.HandleFunc("/articles/{category}/{id:[0-9]+}?edit={edit}", ArticleHandler).
//       Name("article")
//
//     // url.String() will be "/articles/technology/42?edit=true"
//     url, err := r.Get("article")
//                  .Substitute("category", "technology",
//                              "id", "42",
//                              "edit", "true")
//
// All variables defined in the route are required, and their values must
// conform to the corresponding patterns.
func (r *Route) Substitute(pairs ...string) (string, error) {
	if r.err != nil {
		return "", r.err
	}
	if r.regexp == nil {
		return "", errors.New("mux: route doesn't have a path")
	}
	values, err := r.prepareVars(pairs...)
	if err != nil {
		return "", err
	}
	var path string
	if r.regexp.path != nil {
		if path, err = r.regexp.path.url(values); err != nil {
			return "", err
		}
	}
	queries := make([]string, 0, len(r.regexp.queries))
	for _, q := range r.regexp.queries {
		var query string
		if query, err = q.url(values); err != nil {
			return "", err
		}
		queries = append(queries, query)
	}

	queryStr := strings.Join(queries, "&")
	fullPath := path + "?" + queryStr

	return fullPath, nil
}

// GetPathTemplate returns the template used to build the
// route match.
// This is useful for building simple REST API documentation and for instrumentation
// against third-party services.
// An error will be returned if the route does not define a path.
func (r *Route) GetPathTemplate() (string, error) {
	if r.err != nil {
		return "", r.err
	}
	if r.regexp == nil || r.regexp.path == nil {
		return "", errors.New("mux: route doesn't have a path")
	}
	return r.regexp.path.template, nil
}

// GetPathRegexp returns the expanded regular expression used to match route path.
// This is useful for building simple REST API documentation and for instrumentation
// against third-party services.
// An error will be returned if the route does not define a path.
func (r *Route) GetPathRegexp() (string, error) {
	if r.err != nil {
		return "", r.err
	}
	if r.regexp == nil || r.regexp.path == nil {
		return "", errors.New("mux: route does not have a path")
	}
	return r.regexp.path.regexp.String(), nil
}

// GetQueriesRegexp returns the expanded regular expressions used to match the
// route queries.
// This is useful for building simple REST API documentation and for instrumentation
// against third-party services.
// An empty list will be returned if the route does not have queries.
func (r *Route) GetQueriesRegexp() ([]string, error) {
	if r.err != nil {
		return nil, r.err
	}
	if r.regexp == nil || r.regexp.queries == nil {
		return nil, errors.New("mux: route doesn't have queries")
	}
	var queries []string
	for _, query := range r.regexp.queries {
		queries = append(queries, query.regexp.String())
	}
	return queries, nil
}

// GetQueriesTemplates returns the templates used to build the
// query matching.
// This is useful for building simple REST API documentation and for instrumentation
// against third-party services.
// An empty list will be returned if the route does not define queries.
func (r *Route) GetQueriesTemplates() ([]string, error) {
	if r.err != nil {
		return nil, r.err
	}
	if r.regexp == nil || r.regexp.queries == nil {
		return nil, errors.New("mux: route doesn't have queries")
	}
	var queries []string
	for _, query := range r.regexp.queries {
		queries = append(queries, query.template)
	}
	return queries, nil
}

// prepareVars converts the route variable pairs into a map. If the route has a
// BuildVarsFunc, it is invoked.
func (r *Route) prepareVars(pairs ...string) (map[string]string, error) {
	m, err := mapFromPairsToString(pairs...)
	if err != nil {
		return nil, err
	}
	return r.buildVars(m), nil
}

// ----------------------------------------------------------------------------
// parentRoute
// ----------------------------------------------------------------------------

// parentRoute allows routes to know about parent host and path definitions.
type parentRoute interface {
	getNamedRoutes() map[string]*Route
	getRegexpGroup() *routeRegexpGroup
	buildVars(map[string]string) map[string]string
}

// getNamedRoutes returns the map where named routes are registered.
func (r *Route) getNamedRoutes() map[string]*Route {
	if r.parent == nil {
		// During tests router is not always set.
		r.parent = NewRouter()
	}
	return r.parent.getNamedRoutes()
}

// getRegexpGroup returns regexp definitions from this route.
func (r *Route) getRegexpGroup() *routeRegexpGroup {
	if r.regexp == nil {
		if r.parent == nil {
			// During tests router is not always set.
			r.parent = NewRouter()
		}
		regexp := r.parent.getRegexpGroup()
		if regexp == nil {
			r.regexp = new(routeRegexpGroup)
		} else {
			// Copy.
			r.regexp = &routeRegexpGroup{
				path:    regexp.path,
				queries: regexp.queries,
			}
		}
	}
	return r.regexp
}

func (r *Route) buildVars(m map[string]string) map[string]string {
	if r.parent != nil {
		m = r.parent.buildVars(m)
	}
	if r.buildVarsFunc != nil {
		m = r.buildVarsFunc(m)
	}
	return m
}


<|start_filename|>hoc/statusbar/statusbar.go<|end_filename|>
package statusbar

import (
	"fmt"
	"time"

	"github.com/gdamore/tcell"
	"github.com/verdverm/tview"
	"github.com/verdverm/vermui"
	"github.com/verdverm/vermui/events"
)

const emptyMsg = "press 'Ctrl-<space>' to enter a command or '/path/to/something' to navigate"

type StatusBar struct {
	*tview.TextView

	curr    string   // current input (potentially partial)
	hIdx    int      // where we are in history
	history []string // command history
}

func New() *StatusBar {
	S := &StatusBar{
		history: []string{},
	}

	status := tview.NewTextView().SetDynamicColors(true).SetTextAlign(tview.AlignLeft)
	status.SetTitle(" Status ").SetTitleAlign(tview.AlignRight).SetBorder(true).SetBorderPadding(0, 0, 1, 0)

	S.TextView = status

	return S
}

func (S *StatusBar) Mount(context map[string]interface{}) error {
	vermui.AddWidgetHandler(S, "/sys/key/C-s", func(e events.Event) {
		S.SetBorderColor(tcell.ColorFuchsia)
		vermui.SetFocus(S.TextView)
	})
	S.SetDoneFunc(func(key tcell.Key) {
		S.SetBorderColor(tcell.ColorWhite)
		vermui.Unfocus()
	})

	vermui.AddWidgetHandler(S, "/user/error", func(evt events.Event) {
		str := fmt.Sprintf("[red]%v[white]", evt.Data.(*events.EventCustom).Data())

		S.Clear()
		fmt.Fprint(S, str)
		vermui.Draw()

		go func() {
			time.Sleep(time.Second * 6)
			text := S.GetText()
			if text == str {
				S.Clear()
				fmt.Fprint(S, "[lime]ok[white]")
				vermui.Draw()
			}
		}()
	})

	vermui.AddWidgetHandler(S, "/status/message", func(evt events.Event) {
		str := evt.Data.(*events.EventCustom).Data().(string)
		S.history = append(S.history, str)

		S.Clear()
		fmt.Fprint(S, str)
		vermui.Draw()

		go func() {
			time.Sleep(time.Second * 6)
			text := S.GetText()
			if text == str {
				S.Clear()
				fmt.Fprint(S, "[lime]ok[white]")
				vermui.Draw()
			}
		}()
	})

	return nil
}
func (S *StatusBar) Unmount() error {

	vermui.RemoveWidgetHandler(S, "/sys/key/C-s")
	vermui.RemoveWidgetHandler(S, "/user/error")
	vermui.RemoveWidgetHandler(S, "/status/message")

	return nil
}

// InputHandler returns the handler for this primitive.
func (S *StatusBar) InputHandler() func(tcell.Event, func(tview.Primitive)) {
	return S.WrapInputHandler(func(event tcell.Event, setFocus func(p tview.Primitive)) {
		switch evt := event.(type) {
		case *tcell.EventKey:

			dist := 1

			// Process key evt.
			switch key := evt.Key(); key {

			// Upwards, back in history
			case tcell.KeyHome:
				dist = len(S.history)
				fallthrough
			case tcell.KeyPgUp:
				dist += 4
				fallthrough
			case tcell.KeyUp: // Regular character.
				S.hIdx -= dist
				if S.hIdx < 0 {
					S.hIdx = 0
				}

			// Downwards, more recent in history
			case tcell.KeyEnd:
				dist = len(S.history)
				fallthrough
			case tcell.KeyPgDn:
				dist += 4
				fallthrough
			case tcell.KeyDown:
				S.hIdx += dist
				if S.hIdx >= len(S.history) {
					S.hIdx = len(S.history) - 1
				}

			}

		}
		str := ""
		if len(S.history) > 0 {
			str = S.history[S.hIdx]
		}
		S.Clear()
		fmt.Fprint(S, str)
		vermui.Draw()
	})
}


<|start_filename|>hoc/form/flex.go<|end_filename|>
package form

import (
	"github.com/gdamore/tcell"
	"github.com/verdverm/tview"
)

type Flex struct {
	*tview.Flex

	name    string
	items   []FormItem
	buttons []FormButton
}

func NewFlex(name string) *Flex {
	F := &Flex{
		Flex:    tview.NewFlex(),
		name:    name,
		items:   []FormItem{},
		buttons: []FormButton{},
	}

	return F
}

func (F *Flex) Name() string {
	return F.name
}

func (F *Flex) SetFinishedFunction(handler func(key tcell.Key)) {}

func (F *Flex) GetValues() (values map[string]interface{}) {
	values = make(map[string]interface{})
	for _, item := range F.items {
		vals := item.GetValues()
		for field, value := range vals {
			values[field] = value
		}
	}
	return values
}

func (F *Flex) SetValues(values map[string]interface{}) {
	for _, item := range F.items {
		item.SetValues(values)
	}
}

func (F *Flex) GetItem(name string) FormItem {
	for _, item := range F.GetItems() {
		if item.Name() == name {
			return item
		}
	}

	return nil
}

func (F *Flex) GetItems() []FormItem {
	items := []FormItem{}
	for _, item := range F.items {
		switch typ := item.(type) {
		case *Flex:
			itms := typ.GetItems()
			items = append(items, itms...)
		default:
			items = append(items, item)
		}
	}

	return items
}

func (F *Flex) AddItem(item FormItem, fixedSize, proportion int) {
	F.items = append(F.items, item)

	F.Flex.AddItem(item, fixedSize, proportion, true)
}

func (F *Flex) GetButton(name string) FormButton {
	for _, item := range F.GetButtons() {
		if item.Name() == name {
			return item
		}
	}
	return nil
}

func (F *Flex) GetButtons() []FormButton {
	buttons := []FormButton{}
	for _, button := range F.buttons {
		buttons = append(buttons, button)
	}
	for _, item := range F.items {
		switch typ := item.(type) {
		case *Flex:
			btns := typ.GetButtons()
			buttons = append(buttons, btns...)
		}
	}
	return buttons
}

func (F *Flex) AddButton(button FormButton, fixedSize, proportion int) {
	F.buttons = append(F.buttons, button)
	F.Flex.AddItem(button, fixedSize, proportion, true)
}


<|start_filename|>hoc/graphics/image.go<|end_filename|>
// Copyright 2017 <NAME> <<EMAIL>>. All rights reserved.
// Copyright 2018 <NAME> <<EMAIL>>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.

package vermui

import (
	"image"
	"image/color"
)

// Image is an image widget.
type Image struct {
	Block
	Image               image.Image
	Monochrome          bool
	MonochromeThreshold uint8
	MonochromeInvert    bool
}

// NewImage returns a new image widget.
func NewImage(img image.Image) *Image {
	im := &Image{
		Block:               *NewBlock(),
		MonochromeThreshold: 128,
		Image:               img,
	}
	im.Width = 64
	im.Height = 48
	return im
}

// Image implements Bufferer.
func (im *Image) Buffer() Buffer {
	buf := im.Block.Buffer()
	bufWidth := im.innerArea.Dx()
	bufHeight := im.innerArea.Dy()
	for bx := 0; bx < bufWidth; bx++ {
		for by := 0; by < bufHeight; by++ {
			buf.Set(im.innerArea.Min.X+bx, im.innerArea.Min.Y+by, Cell{
				Ch: ' ',
				Fg: ColorDefault,
				Bg: ColorDefault,
			})
		}
	}
	if im.Image == nil {
		return buf
	}
	imageWidth := im.Image.Bounds().Dx()
	imageHeight := im.Image.Bounds().Dy()
	if im.Monochrome {
		if bufWidth > imageWidth/2 {
			bufWidth = imageWidth / 2
		}
		if bufHeight > imageHeight/2 {
			bufHeight = imageHeight / 2
		}
		for bx := 0; bx < bufWidth; bx++ {
			for by := 0; by < bufHeight; by++ {
				ul := im.colorAverage(2*bx*imageWidth/bufWidth/2, (2*bx+1)*imageWidth/bufWidth/2, 2*by*imageHeight/bufHeight/2, (2*by+1)*imageHeight/bufHeight/2)
				ur := im.colorAverage((2*bx+1)*imageWidth/bufWidth/2, (2*bx+2)*imageWidth/bufWidth/2, 2*by*imageHeight/bufHeight/2, (2*by+1)*imageHeight/bufHeight/2)
				ll := im.colorAverage(2*bx*imageWidth/bufWidth/2, (2*bx+1)*imageWidth/bufWidth/2, (2*by+1)*imageHeight/bufHeight/2, (2*by+2)*imageHeight/bufHeight/2)
				lr := im.colorAverage((2*bx+1)*imageWidth/bufWidth/2, (2*bx+2)*imageWidth/bufWidth/2, (2*by+1)*imageHeight/bufHeight/2, (2*by+2)*imageHeight/bufHeight/2)
				buf.Set(im.innerArea.Min.X+bx, im.innerArea.Min.Y+by, Cell{
					Ch: blocksChar(ul, ur, ll, lr, im.MonochromeThreshold, im.MonochromeInvert),
				})
			}
		}
	} else {
		if bufWidth > imageWidth {
			bufWidth = imageWidth
		}
		if bufHeight > imageHeight {
			bufHeight = imageHeight
		}
		for bx := 0; bx < bufWidth; bx++ {
			for by := 0; by < bufHeight; by++ {
				c := im.colorAverage(bx*imageWidth/bufWidth, (bx+1)*imageWidth/bufWidth, by*imageHeight/bufHeight, (by+1)*imageHeight/bufHeight)
				buf.Set(im.innerArea.Min.X+bx, im.innerArea.Min.Y+by, Cell{
					Ch: c.ch(),
					Fg: c.fgColor(),
					Bg: ColorBlack,
				})
			}
		}
	}
	return buf
}

func (im *Image) colorAverage(x0, x1, y0, y1 int) colorAverager {
	var c colorAverager
	for x := x0; x < x1; x++ {
		for y := y0; y < y1; y++ {
			c = c.add(im.Image.At(x+im.Image.Bounds().Min.X, y+im.Image.Bounds().Min.Y))
		}
	}
	return c
}

type colorAverager struct {
	rsum, gsum, bsum, asum, count uint64
}

func (c colorAverager) add(col color.Color) colorAverager {
	r, g, b, a := col.RGBA()
	return colorAverager{
		rsum:  c.rsum + uint64(r),
		gsum:  c.gsum + uint64(g),
		bsum:  c.bsum + uint64(b),
		asum:  c.asum + uint64(a),
		count: c.count + 1,
	}
}

func (c colorAverager) RGBA() (uint32, uint32, uint32, uint32) {
	if c.count == 0 {
		return 0, 0, 0, 0
	} else {
		return uint32(c.rsum/c.count) & 0xffff,
			uint32(c.gsum/c.count) & 0xffff,
			uint32(c.bsum/c.count) & 0xffff,
			uint32(c.asum/c.count) & 0xffff
	}
}

func (c colorAverager) fgColor() Attribute {
	return palette.Convert(c).(paletteColor).attribute
}

func (c colorAverager) ch() rune {
	gray := color.GrayModel.Convert(c).(color.Gray).Y
	switch {
	case gray < 51:
		return ' '
	case gray < 102:
		return '░'
	case gray < 153:
		return '▒'
	case gray < 204:
		return '▓'
	default:
		return '█'
	}
}

func (c colorAverager) monochrome(threshold uint8, invert bool) bool {
	return c.count != 0 && (color.GrayModel.Convert(c).(color.Gray).Y < threshold != invert)
}

type paletteColor struct {
	rgba      color.RGBA
	attribute Attribute
}

func (c paletteColor) RGBA() (uint32, uint32, uint32, uint32) {
	return c.rgba.RGBA()
}

var palette = color.Palette([]color.Color{
	paletteColor{color.RGBA{0, 0, 0, 255}, ColorBlack},
	paletteColor{color.RGBA{255, 0, 0, 255}, ColorRed},
	paletteColor{color.RGBA{0, 255, 0, 255}, ColorGreen},
	paletteColor{color.RGBA{255, 255, 0, 255}, ColorYellow},
	paletteColor{color.RGBA{0, 0, 255, 255}, ColorBlue},
	paletteColor{color.RGBA{255, 0, 255, 255}, ColorMagenta},
	paletteColor{color.RGBA{0, 255, 255, 255}, ColorCyan},
	paletteColor{color.RGBA{255, 255, 255, 255}, ColorWhite},
})

var blocks = [...]rune{
	' ', '▘', '▝', '▀', '▖', '▌', '▞', '▛',
	'▗', '▚', '▐', '▜', '▄', '▙', '▟', '█',
}

func blocksChar(ul, ur, ll, lr colorAverager, threshold uint8, invert bool) rune {
	index := 0
	if ul.monochrome(threshold, invert) {
		index |= 1
	}
	if ur.monochrome(threshold, invert) {
		index |= 2
	}
	if ll.monochrome(threshold, invert) {
		index |= 4
	}
	if lr.monochrome(threshold, invert) {
		index |= 8
	}
	return blocks[index]
}
