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
import (
"strconv"
"strings"
"sync"
"time"
@ -25,32 +26,125 @@ type Event struct {
Time int64
}
var sysevt struct {
chs []chan Event
var sysEvtChs []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()}
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
}
func hookSysEvt() {
sysevt.chs = make([]chan Event, 0)
type EvtWnd struct {
Width int
Height int
}
type EvtMouse struct {
X int
Y int
Press string
}
type EvtErr error
func hookTermboxEvt() {
for {
e := termbox.PollEvent()
for _, c := range sysevt.chs {
// shorten?
go func(ch chan Event, ev Event) { ch <- ev }(c, newSysEvtFromTb(e))
for _, c := range sysEvtChs {
go func(ch chan Event) {
ch <- crtTermboxEvt(e)
}(c)
}
}
}
func NewSysEvtCh() chan Event {
ec := make(chan Event)
sysevt.chs = append(sysevt.chs, ec)
sysEvtChs = append(sysEvtChs, ec)
return ec
}
var DefaultEvtStream = NewEvtStream()
/*
type evtCtl struct {
in chan Event
@ -73,22 +167,23 @@ func newEvtCtl() evtCtl {
*/
//
type EvtStream struct {
srcMap map[string]chan Event
stream chan Event
cache map[string][]func(Event)
wg sync.WaitGroup
Handlers map[string]func(Event)
srcMap map[string]chan Event
stream chan Event
wg sync.WaitGroup
sigStopLoop chan int
Handlers map[string]func(Event)
}
func NewEvtStream() *EvtStream {
return &EvtStream{
srcMap: make(map[string]chan Event),
stream: make(chan Event),
srcMap: make(map[string]chan Event),
stream: make(chan Event),
Handlers: make(map[string]func(Event)),
sigStopLoop: make(chan int),
}
}
func (es *EvtStream) Init() {
go func() {
es.wg.Wait()
close(es.stream)
@ -107,17 +202,38 @@ func (es *EvtStream) Init() {
// b: /
// score: 0
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 {
if i >= len(sb) {
break
break // exhaust b
}
if s != sb[i] {
return -1
return -1 // mismatch
}
score++
}
@ -125,23 +241,98 @@ func MatchScore(a, b string) int {
return score
}
func (es *EvtStream) Merge(ec chan Event) {
func (es *EvtStream) Merge(name string, ec chan Event) {
es.wg.Add(1)
es.srcMap[name] = ec
go func(a chan Event) {
for n := range ec {
for n := range a {
n.From = name
es.stream <- n
}
wg.Done()
es.wg.Done()
}(ec)
}
/*
func (es *EvtStream) hookup() {
func (es *EvtStream) Handle(path string, handler func(Event)) {
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
import (
"errors"
"testing"
import "testing"
termbox "github.com/nsf/termbox-go"
"github.com/stretchr/testify/assert"
)
var ps = []string{
"",
"/",
"/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
}
// Body corresponds to the entire terminal display region.
var Body *Grid

View File

@ -4,7 +4,11 @@
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.
type Bufferer interface {
@ -14,16 +18,24 @@ type Bufferer interface {
// Init initializes termui library. This function should be called before any others.
// After initialization, the library must be finalized by 'Close' function.
func Init() error {
Body = NewGrid()
Body.X = 0
Body.Y = 0
Body.BgColor = theme.BodyBg
if err := tm.Init(); err != nil {
return err
}
w, _ := tm.Size()
Body.Width = w
evtListen()
sysEvtChs = make([]chan Event, 0)
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
}
@ -64,3 +76,9 @@ func Render(bs ...Bufferer) {
// render
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()
}