Finish Event

This commit is contained in:
gizak 2015-09-18 11:41:44 -04:00
parent 3ea00a7476
commit e89b10ff4f
39 changed files with 425 additions and 55 deletions

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 443 KiB

After

Width:  |  Height:  |  Size: 443 KiB

View File

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

Before

Width:  |  Height:  |  Size: 782 KiB

After

Width:  |  Height:  |  Size: 782 KiB

View File

Before

Width:  |  Height:  |  Size: 136 KiB

After

Width:  |  Height:  |  Size: 136 KiB

View File

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

View File

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

View File

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 103 KiB

View File

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB

113
debug/debuger.go Normal file
View File

@ -0,0 +1,113 @@
package debug
import (
"fmt"
"net/http"
"golang.org/x/net/websocket"
)
type Server struct {
Port string
Addr string
Path string
Msg chan string
chs []chan string
}
type Client struct {
Port string
Addr string
Path string
ws *websocket.Conn
}
var defaultPort = ":8080"
func NewServer() *Server {
return &Server{
Port: defaultPort,
Addr: "localhost",
Path: "/echo",
Msg: make(chan string),
chs: make([]chan string, 0),
}
}
func NewClient() Client {
return Client{
Port: defaultPort,
Addr: "localhost",
Path: "/echo",
}
}
func (c Client) ConnectAndListen() error {
ws, err := websocket.Dial("ws://"+c.Addr+c.Port+c.Path, "", "http://"+c.Addr)
if err != nil {
return err
}
defer ws.Close()
var m string
for {
err := websocket.Message.Receive(ws, &m)
if err != nil {
fmt.Print(err)
return err
}
fmt.Print(m)
}
}
func (s *Server) ListenAndServe() error {
http.Handle(s.Path, websocket.Handler(func(ws *websocket.Conn) {
defer ws.Close()
mc := make(chan string)
s.chs = append(s.chs, mc)
for m := range mc {
websocket.Message.Send(ws, m)
}
}))
go func() {
for msg := range s.Msg {
for _, c := range s.chs {
go func(a chan string) {
a <- msg
}(c)
}
}
}()
return http.ListenAndServe(s.Port, nil)
}
func (s *Server) Log(msg string) {
go func() { s.Msg <- msg }()
}
func (s *Server) Logf(format string, a ...interface{}) {
s.Log(fmt.Sprintf(format, a...))
}
var DefaultServer = NewServer()
var DefaultClient = NewClient()
func ListenAndServe() error {
return DefaultServer.ListenAndServe()
}
func ConnectAndListen() error {
return DefaultClient.ConnectAndListen()
}
func Log(msg string) {
DefaultServer.Log(msg)
}
func Logf(format string, a ...interface{}) {
DefaultServer.Logf(format, a...)
}

251
events.go
View File

@ -9,6 +9,7 @@
package termui package termui
import ( import (
"strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -25,32 +26,125 @@ type Event struct {
Time int64 Time int64
} }
var sysevt struct { var sysEvtChs []chan Event
chs []chan Event
type EvtKbd struct {
KeyStr string
} }
func newSysEvtFromTb(e termbox.Event) Event { func evtKbd(e termbox.Event) EvtKbd {
ek := EvtKbd{}
k := string(e.Ch)
pre := ""
mod := ""
if e.Mod == termbox.ModAlt {
mod = "M-"
}
if e.Ch == 0 {
if e.Key > 0xFFFF-12 {
k = "<f" + strconv.Itoa(0xFFFF-int(e.Key)+1) + ">"
} else if e.Key > 0xFFFF-25 {
ks := []string{"<insert>", "<delete>", "<home>", "<end>", "<previous>", "<next>", "<up>", "<down>", "<left>", "<right>"}
k = ks[0xFFFF-int(e.Key)-12]
}
if e.Key <= 0x7F {
pre = "C-"
k = string('a' - 1 + int(e.Key))
kmap := map[termbox.Key][2]string{
termbox.KeyCtrlSpace: {"C-", "<space>"},
termbox.KeyBackspace: {"", "<backspace>"},
termbox.KeyTab: {"", "<tab>"},
termbox.KeyEnter: {"", "<enter>"},
termbox.KeyEsc: {"", "<escape>"},
termbox.KeyCtrlBackslash: {"C-", "\\"},
termbox.KeyCtrlSlash: {"C-", "/"},
termbox.KeySpace: {"", "<space>"},
termbox.KeyCtrl8: {"C-", "8"},
}
if sk, ok := kmap[e.Key]; ok {
pre = sk[0]
k = sk[1]
}
}
}
ek.KeyStr = pre + mod + k
return ek
}
func crtTermboxEvt(e termbox.Event) Event {
systypemap := map[termbox.EventType]string{
termbox.EventKey: "keyboard",
termbox.EventResize: "window",
termbox.EventMouse: "mouse",
termbox.EventError: "error",
termbox.EventInterrupt: "interrupt",
}
ne := Event{From: "/sys", Time: time.Now().Unix()} ne := Event{From: "/sys", Time: time.Now().Unix()}
typ := e.Type
ne.Type = systypemap[typ]
switch typ {
case termbox.EventKey:
kbd := evtKbd(e)
ne.Path = "/sys/kbd/" + kbd.KeyStr
ne.Data = kbd
case termbox.EventResize:
wnd := EvtWnd{}
wnd.Width = e.Width
wnd.Height = e.Height
ne.Path = "/sys/wnd/resize"
ne.Data = wnd
case termbox.EventError:
err := EvtErr(e.Err)
ne.Path = "/sys/err"
ne.Data = err
case termbox.EventMouse:
m := EvtMouse{}
m.X = e.MouseX
m.Y = e.MouseY
ne.Path = "/sys/mouse"
ne.Data = m
}
return ne return ne
} }
func hookSysEvt() { type EvtWnd struct {
sysevt.chs = make([]chan Event, 0) Width int
Height int
}
type EvtMouse struct {
X int
Y int
Press string
}
type EvtErr error
func hookTermboxEvt() {
for { for {
e := termbox.PollEvent() e := termbox.PollEvent()
for _, c := range sysevt.chs {
// shorten? for _, c := range sysEvtChs {
go func(ch chan Event, ev Event) { ch <- ev }(c, newSysEvtFromTb(e)) go func(ch chan Event) {
ch <- crtTermboxEvt(e)
}(c)
} }
} }
} }
func NewSysEvtCh() chan Event { func NewSysEvtCh() chan Event {
ec := make(chan Event) ec := make(chan Event)
sysevt.chs = append(sysevt.chs, ec) sysEvtChs = append(sysEvtChs, ec)
return ec return ec
} }
var DefaultEvtStream = NewEvtStream()
/* /*
type evtCtl struct { type evtCtl struct {
in chan Event in chan Event
@ -73,22 +167,23 @@ func newEvtCtl() evtCtl {
*/ */
// //
type EvtStream struct { type EvtStream struct {
srcMap map[string]chan Event srcMap map[string]chan Event
stream chan Event stream chan Event
cache map[string][]func(Event) wg sync.WaitGroup
wg sync.WaitGroup sigStopLoop chan int
Handlers map[string]func(Event) Handlers map[string]func(Event)
} }
func NewEvtStream() *EvtStream { func NewEvtStream() *EvtStream {
return &EvtStream{ return &EvtStream{
srcMap: make(map[string]chan Event), srcMap: make(map[string]chan Event),
stream: make(chan Event), stream: make(chan Event),
Handlers: make(map[string]func(Event)),
sigStopLoop: make(chan int),
} }
} }
func (es *EvtStream) Init() { func (es *EvtStream) Init() {
go func() { go func() {
es.wg.Wait() es.wg.Wait()
close(es.stream) close(es.stream)
@ -107,17 +202,38 @@ func (es *EvtStream) Init() {
// b: / // b: /
// score: 0 // score: 0
func MatchScore(a, b string) int { func MatchScore(a, b string) int {
sa := strings.Split(a, "/")
sb := strings.Split(b, "/")
score := -1 // divide by "" and rm heading ""
sliced := func(s string) []string {
ss := strings.Split(s, "/")
i := 0
for j := range ss {
if ss[j] == "" {
i++
} else {
break
}
}
return ss[i:]
}
sa := sliced(a)
sb := sliced(b)
score := 0
if len(sb) > len(sa) {
return -1 // sb couldnt be more deeper than sa
}
for i, s := range sa { for i, s := range sa {
if i >= len(sb) { if i >= len(sb) {
break break // exhaust b
} }
if s != sb[i] { if s != sb[i] {
return -1 return -1 // mismatch
} }
score++ score++
} }
@ -125,23 +241,98 @@ func MatchScore(a, b string) int {
return score return score
} }
func (es *EvtStream) Merge(ec chan Event) { func (es *EvtStream) Merge(name string, ec chan Event) {
es.wg.Add(1) es.wg.Add(1)
es.srcMap[name] = ec
go func(a chan Event) { go func(a chan Event) {
for n := range ec { for n := range a {
n.From = name
es.stream <- n es.stream <- n
} }
wg.Done() es.wg.Done()
}(ec) }(ec)
} }
/* func (es *EvtStream) Handle(path string, handler func(Event)) {
func (es *EvtStream) hookup() { es.Handlers[path] = handler
} }
func (es EvtStream) Subscribe(uri string) chan Event { func (es *EvtStream) match(path string) string {
n := 0
pattern := ""
for m := range es.Handlers {
if MatchScore(path, m) < 0 {
continue
}
if pattern == "" || len(m) > n {
pattern = m
}
}
return pattern
}
func (es *EvtStream) Loop() {
for {
select {
case e := <-es.stream:
if pattern := es.match(e.Path); pattern != "" {
es.Handlers[pattern](e)
}
case <-es.sigStopLoop:
return
}
}
}
func (es *EvtStream) StopLoop() {
go func() { es.sigStopLoop <- 1 }()
}
func Merge(name string, ec chan Event) {
DefaultEvtStream.Merge(name, ec)
}
func Handle(path string, handler func(Event)) {
DefaultEvtStream.Handle(path, handler)
}
func Loop() {
DefaultEvtStream.Loop()
}
func StopLoop() {
DefaultEvtStream.StopLoop()
}
type EvtTimer struct {
Duration time.Duration
Count uint64
}
func NewTimerCh(du time.Duration) chan Event {
t := make(chan Event)
go func(a chan Event) {
n := uint64(0)
for {
n++
time.Sleep(du)
e := Event{}
e.From = "timer"
e.Type = "timer"
e.Path = "/timer/" + du.String()
e.Time = time.Now().Unix()
e.Data = EvtTimer{
Duration: du,
Count: n,
}
t <- e
}
}(t)
return t
}
var DefualtHandler = func(e Event) {
} }
*/

View File

@ -8,21 +8,34 @@
package termui package termui
import ( import "testing"
"errors"
"testing"
termbox "github.com/nsf/termbox-go" var ps = []string{
"github.com/stretchr/testify/assert" "",
) "/",
"/a",
"/b",
"/a/c",
"/a/b",
"/a/b/c",
"/a/b/c/d",
"/a/b/c/d/"}
type boxEvent termbox.Event func TestMatchScore(t *testing.T) {
chk := func(a, b string, s int) {
if c := MatchScore(a, b); c != s {
t.Errorf("\na:%s\nb:%s\nshould:%d\nscore:%d", a, b, s, c)
}
}
chk(ps[1], ps[1], 0)
chk(ps[1], ps[2], -1)
chk(ps[2], ps[1], 0)
chk(ps[4], ps[1], 0)
chk(ps[6], ps[2], 1)
chk(ps[4], ps[5], -1)
}
func TestCrtEvt(t *testing.T) {
func TestUiEvt(t *testing.T) {
err := errors.New("This is a mock error")
event := boxEvent{3, 5, 2, 'H', 200, 500, err, 50, 30, 2}
expetced := Event{3, 5, 2, 'H', 200, 500, err, 50, 30, 2}
// We need to do that ugly casting so that vet does not complain
assert.Equal(t, uiEvt(termbox.Event(event)), expetced)
} }

View File

@ -275,6 +275,3 @@ func (g Grid) Buffer() Buffer {
} }
return buf return buf
} }
// Body corresponds to the entire terminal display region.
var Body *Grid

View File

@ -4,7 +4,11 @@
package termui package termui
import tm "github.com/nsf/termbox-go" import (
"time"
tm "github.com/nsf/termbox-go"
)
// Bufferer should be implemented by all renderable components. // Bufferer should be implemented by all renderable components.
type Bufferer interface { type Bufferer interface {
@ -14,16 +18,24 @@ type Bufferer interface {
// Init initializes termui library. This function should be called before any others. // Init initializes termui library. This function should be called before any others.
// After initialization, the library must be finalized by 'Close' function. // After initialization, the library must be finalized by 'Close' function.
func Init() error { func Init() error {
Body = NewGrid()
Body.X = 0
Body.Y = 0
Body.BgColor = theme.BodyBg
if err := tm.Init(); err != nil { if err := tm.Init(); err != nil {
return err return err
} }
w, _ := tm.Size()
Body.Width = w sysEvtChs = make([]chan Event, 0)
evtListen() go hookTermboxEvt()
renderJobs = make(chan []Bufferer)
go func() {
for bs := range renderJobs {
Render(bs...)
}
}()
DefaultEvtStream.Init()
DefaultEvtStream.Merge("termbox", NewSysEvtCh())
DefaultEvtStream.Merge("timer", NewTimerCh(time.Second))
DefaultEvtStream.Handle("/", DefualtHandler)
return nil return nil
} }
@ -64,3 +76,9 @@ func Render(bs ...Bufferer) {
// render // render
tm.Flush() tm.Flush()
} }
var renderJobs chan []Bufferer
func SendBufferToRender(bs ...Bufferer) {
go func() { renderJobs <- bs }()
}

38
test/runtest.go Normal file
View File

@ -0,0 +1,38 @@
package main
import (
"fmt"
"os"
"github.com/gizak/termui"
"github.com/gizak/termui/debug"
)
func main() {
// run as client
if len(os.Args) > 1 {
fmt.Print(debug.ConnectAndListen())
return
}
// run as server
go func() { panic(debug.ListenAndServe()) }()
if err := termui.Init(); err != nil {
panic(err)
}
defer termui.Close()
termui.Handle("/sys", func(e termui.Event) {
k, ok := e.Data.(termui.EvtKbd)
debug.Logf("-->%v\n", e)
if ok && k.KeyStr == "q" {
termui.StopLoop()
}
})
termui.Handle("/timer", func(e termui.Event) {
//debug.Logf("-->%v\n", e)
})
termui.Loop()
}