package main import ( "bufio" "context" "errors" "fmt" "math" "net" "os" "strings" "time" "codeberg.org/lobo/nanite/widgets/pager" "git.sr.ht/~rockorager/vaxis" "git.sr.ht/~rockorager/vaxis/widgets/textinput" ) var ( ErrAlreadyConnected = errors.New("already connected") ) var CommandMap = map[string]func(*App, string){ "nick": func(app *App, rest string) { args := strings.Fields(rest) switch len(args) { case 0: app.AppendSystemMessage("your nickname is %s", app.nick) default: app.SetNick(args[0]) app.AppendSystemMessage("your nickname is now %s", app.nick) } }, "me": func(app *App, rest string) { msg := fmt.Sprintf("%s %s", app.nick, rest) app.AppendMessage(msg) app.outgoing <- Message(msg) }, "quit": func(app *App, rest string) { app.stop() }, "q": func(app *App, rest string) { app.stop() }, } type App struct { ctx context.Context stop context.CancelFunc conn net.Conn scanner *bufio.Scanner host, port string stats string nick string last int ticker *time.Ticker incoming chan IncomingEvent outgoing chan OutgoingEvent error chan error vx *vaxis.Vaxis w struct { log, title, status, input vaxis.Window } pager *pager.Model input *textinput.Model dirty bool } func (app *App) Connect(host, port string) (err error) { if app.conn != nil { return ErrAlreadyConnected } app.host = host app.port = port app.conn, err = net.Dial("tcp", net.JoinHostPort(host, port)) if err != nil { return err } app.scanner = bufio.NewScanner(app.conn) app.incoming = make(chan IncomingEvent) app.outgoing = make(chan OutgoingEvent) app.error = make(chan error) app.ticker = time.NewTicker(1 * time.Second) go func() { app.Last(50) for { select { case ev := <-app.outgoing: if err := ev.HandleOutgoing(app); err != nil { app.error <- err return } case <-app.ctx.Done(): return } } }() return nil } func (app *App) Disconnect() { if app.conn != nil { app.conn.Write([]byte("QUIT\n")) app.conn.Close() app.conn = nil } if app.ticker != nil { app.ticker.Stop() app.ticker = nil } if app.incoming != nil { close(app.incoming) app.incoming = nil } if app.outgoing != nil { close(app.outgoing) app.outgoing = nil } } func (app *App) AppendMessage(data string) { app.pager.Segments = append(app.pager.Segments, vaxis.Segment{Text: data}, vaxis.Segment{Text: "\n"}, ) app.last += 1 app.pager.Offset = math.MaxInt app.dirty = true } func (app *App) AppendSystemMessage(format string, args ...any) { st := vaxis.Style{Attribute: vaxis.AttrDim | vaxis.AttrItalic} app.pager.Segments = append(app.pager.Segments, vaxis.Segment{Text: "* ", Style: st}, vaxis.Segment{Text: fmt.Sprintf(format, args...), Style: st}, vaxis.Segment{Text: "\n"}, ) app.pager.Offset = math.MaxInt app.dirty = true } func (app *App) SetNick(nick string) { app.input.SetPrompt(fmt.Sprintf("%s: ", nick)) app.nick = nick } func main() { args := os.Args if len(args) < 2 || len(args) > 3 { fmt.Printf("usage: %s host [port]\n", args[0]) os.Exit(1) } port := "44322" if len(args) == 3 { port = args[2] } app := App{} app.ctx, app.stop = context.WithCancel(context.Background()) defer app.stop() if err := app.Connect(args[1], port); err != nil { panic(err) } defer app.Disconnect() app.InitUI() defer app.FinishUI() app.SetNick("wolfdog") for { select { case ev := <-app.vx.Events(): app.HandleTerminalEvent(ev) case ev := <-app.incoming: ev.HandleIncoming(&app) case <-app.ticker.C: app.outgoing <- Poll(app.last) case err := <-app.error: app.FinishUI() panic(err) case <-app.ctx.Done(): return } app.Redraw() } }