diff --git a/README.md b/README.md index 9e4d866..b7d4838 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,32 @@ # nanite -it ony a [nanochat] client! -wrote this while simultaneously learning how to use [vaxis] and it sure was an experience +`nanite` is a terminal [Nanochat] client. -## use it +## build ``` $ go build . +``` + +## usage + +``` +$ ./nanite +usage: ./nanite host [port] $ ./nanite very.real-server.com ``` -[nanochat]: https://git.phial.org/d6/nanochat -[vaxis]: https://git.sr.ht/~rockorager/vaxis +keybindings: + +- `Ctrl+C`: quit +- `Ctrl+L`: refresh screen +- `Ctrl+P`: poll + +commands: + +- `/q`, `/quit`: quit +- `/nick [nickname]`: change nick, if no arguments, show current nick +- `/me [is listening to music]`: IRC `/me` alike +- `/poll [n]`: change polling interval, if no arguments, poll manually + +[Nanochat]: https://git.phial.org/d6/nanochat diff --git a/command.go b/command.go index 47ac666..d4a3eed 100644 --- a/command.go +++ b/command.go @@ -3,8 +3,26 @@ package main import ( "fmt" "strconv" + "strings" ) +func (app *App) Stat() (res string, err error) { + if _, err := app.conn.Write([]byte("STAT\n")); err != nil { + return "", err + } + + var str strings.Builder + for range 3 { + if !app.scanner.Scan() { + return "", app.scanner.Err() + } + str.Write(app.scanner.Bytes()) + str.WriteRune(' ') + } + + return str.String(), nil +} + func (app *App) Send(data string) (num int, err error) { if _, err := fmt.Fprintf(app.conn, "SEND %s\n", data); err != nil { return 0, err @@ -37,29 +55,29 @@ func (app *App) Poll(since int) (num int, err error) { return num, nil } -func (app *App) Last(n int) (err error) { +func (app *App) Last(n int) (num int, err error) { if n == 0 { - return nil + return 0, nil } if _, err := fmt.Fprintf(app.conn, "LAST %d\n", n); err != nil { - return err + return 0, err } var nsrv int if !app.scanner.Scan() { - return app.scanner.Err() + return 0, app.scanner.Err() } nsrvRaw := app.scanner.Text() nsrv, err = strconv.Atoi(nsrvRaw) if err != nil { - return err + return 0, err } if nsrv != 0 { for range nsrv { if !app.scanner.Scan() { - return app.scanner.Err() + return 0, app.scanner.Err() } app.incoming <- Message(app.scanner.Text()) } @@ -67,14 +85,14 @@ func (app *App) Last(n int) (err error) { var last int if !app.scanner.Scan() { - return app.scanner.Err() + return 0, app.scanner.Err() } lastRaw := app.scanner.Text() last, err = strconv.Atoi(lastRaw) if err != nil { - return err + return 0, err } - app.incoming <- SetLast(last) + app.incoming <- Last(last) - return nil + return nsrv, nil } diff --git a/display.go b/display.go index 1235585..d056431 100644 --- a/display.go +++ b/display.go @@ -43,50 +43,43 @@ func (app *App) Redraw() { return } app.dirty = false - - // set window title and draw titlebar app.w.title.Clear() + titlebarStyle := vaxis.Style{Attribute: vaxis.AttrBold} + delimiterStyle := vaxis.Style{Attribute: vaxis.AttrDim} if app.conn != nil { - app.vx.SetTitle(fmt.Sprintf("%s:%s", app.host, app.port)) - rateString := "n/a" + titleString := fmt.Sprintf("%s:%s", app.host, app.port) + app.vx.SetTitle(titleString) + + rateString := "manual" if app.rate != 0 { rateString = app.rate.String() } - app.w.title.PrintTruncate(0, - vaxis.Segment{ - Text: "• ", - Style: titlebarStyle, - }, - vaxis.Segment{ - Text: app.host, - Style: titlebarStyle, - }, - vaxis.Segment{ - Text: ":", - Style: titlebarStyle, - }, - vaxis.Segment{ - Text: app.port, - Style: titlebarStyle, - }, - vaxis.Segment{ - Text: fmt.Sprintf(" |  %s", rateString), - }, - ) - // let the widgets draw themselves + segments := []vaxis.Segment{ + {Text: "• "}, + {Text: titleString, Style: titlebarStyle}, + {Text: " │ ", Style: delimiterStyle}, + {Text: fmt.Sprintf("↻ %s", rateString)}, + } + + if app.stats != "" { + segments = append(segments, + vaxis.Segment{Text: " │ ", Style: delimiterStyle}, + vaxis.Segment{Text: app.stats}, + ) + } + + app.w.title.PrintTruncate(0, segments...) app.pager.Layout() app.pager.Draw(app.w.log) app.input.Draw(app.w.input) } else { app.vx.SetTitle("nanite (disconnected)") app.w.title.PrintTruncate(0, - vaxis.Segment{ - Text: "✖ (disconnected)", - Style: titlebarStyle, - }, + vaxis.Segment{Text: "✕ "}, + vaxis.Segment{Text: "disconnected", Style: titlebarStyle}, ) } app.vx.Render() @@ -115,8 +108,7 @@ func (app *App) HandleTerminalEvent(ev vaxis.Event) { case ev.MatchString("down"): app.pager.ScrollDown() case ev.MatchString("ctrl+p"): - app.AppendSystemMessage("polling for new messages") - app.outgoing <- Poll(app.last) + app.outgoing <- ManualPoll(app.last) case ev.MatchString("ctrl+l"): app.Redraw() app.vx.Refresh() @@ -136,6 +128,7 @@ func (app *App) HandleTerminalEvent(ev vaxis.Event) { message := fmt.Sprintf("%s: %s", app.nick, app.input.String()) app.AppendMessage(message) app.outgoing <- Message(message) + app.outgoing <- Stat("") } app.input.SetContent("") diff --git a/event.go b/event.go index ab4b1f3..3657cbc 100644 --- a/event.go +++ b/event.go @@ -18,7 +18,7 @@ func (m Message) HandleOutgoing(app *App) error { if err != nil { return err } - app.incoming <- SetLast(num) + app.incoming <- Last(num) return nil } @@ -30,15 +30,59 @@ func (p Poll) HandleOutgoing(app *App) error { if err != nil { return err } - err = app.Last(num) + num, err = app.Last(num) if err != nil { return err } + if num != 0 { + app.outgoing <- Stat("") + } return nil } -type SetLast int +type ManualPoll int -func (m SetLast) HandleIncoming(app *App) { +func (p ManualPoll) HandleOutgoing(app *App) error { + num, err := app.Poll(int(p)) + if err != nil { + return err + } + app.incoming <- ManualPoll(num) + num, err = app.Last(num) + if err != nil { + return err + } + if num != 0 { + app.outgoing <- Stat("") + } + return nil +} + +func (p ManualPoll) HandleIncoming(app *App) { + if int(p) == 0 { + app.AppendSystemMessage("poll: no new messages") + } else { + app.AppendSystemMessage("poll: retrieving %d messages", p) + } +} + +type Last int + +func (m Last) HandleIncoming(app *App) { app.last = int(m) } + +type Stat string + +func (data Stat) HandleIncoming(app *App) { + app.stats = string(data) + app.dirty = true +} +func (_ Stat) HandleOutgoing(app *App) error { + res, err := app.Stat() + if err != nil { + return err + } + app.incoming <- Stat(res) + return nil +} diff --git a/main.go b/main.go index 8ef195a..919e436 100644 --- a/main.go +++ b/main.go @@ -26,30 +26,29 @@ var CommandMap = map[string]func(*App, string){ args := strings.Fields(rest) switch len(args) { case 0: - app.AppendSystemMessage("your nickname is %s", app.nick) + app.AppendSystemMessage("nick: your nickname is %s", app.nick) default: app.SetNick(args[0]) - app.AppendSystemMessage("your nickname is now %s", app.nick) + app.AppendSystemMessage("nick: your nickname is now %s", app.nick) } }, "poll": func(app *App, rest string) { if rest == "" { - app.AppendSystemMessage("polling for new messages") - app.outgoing <- Poll(app.last) + app.outgoing <- ManualPoll(app.last) } else { num, err := strconv.Atoi(rest) if err != nil { - app.AppendSystemMessage("invalid number %s", rest) + app.AppendSystemMessage("poll: invalid number %s", rest) } else { if num == 0 { app.ticker.Stop() app.rate = 0 - app.AppendSystemMessage("disabled automatic polling") + app.AppendSystemMessage("poll: disabled automatic polling") } else { app.rate = time.Second * time.Duration(num) app.ticker.Stop() app.ticker = time.NewTicker(app.rate) - app.AppendSystemMessage("polling every %s", app.rate.String()) + app.AppendSystemMessage("poll: polling every %s", app.rate.String()) } } } @@ -123,7 +122,9 @@ func (app *App) Connect(host, port string) (err error) { app.ticker = time.NewTicker(delta) go func() { + app.outgoing <- Stat("") app.Last(20) + for { select { case ev := <-app.outgoing: