nanite/main.go

198 lines
3.6 KiB
Go

package main
import (
"bufio"
"context"
"fmt"
"math"
"net"
"os"
"path"
"strings"
"time"
"git.rhzm.org/lobo/nanite/widgets/pager"
"git.sr.ht/~rockorager/vaxis"
"git.sr.ht/~rockorager/vaxis/widgets/textinput"
)
type App struct {
ctx context.Context
stop context.CancelFunc
conn *Conn
stats string
nick string
last int
incoming chan IncomingEvent
outgoing chan OutgoingEvent
error chan error
scripts map[string]string
vx *vaxis.Vaxis
pager *pager.Model
input *textinput.Model
w struct {
log, title, input vaxis.Window
}
cfgHome string
}
type Conn struct {
net.Conn
*bufio.Scanner
host, port string
rate time.Duration
ticker *time.Ticker
ctx context.Context
stop context.CancelFunc
}
func (app *App) AppendMessage(data string) {
nick, _, found := strings.Cut(data, ": ")
style := vaxis.Style{}
if !found || strings.TrimSpace(nick) != nick {
style.Attribute = vaxis.AttrItalic
}
app.pager.Segments = append(app.pager.Segments,
vaxis.Segment{Text: data, Style: style},
vaxis.Segment{Text: "\n"},
)
app.pager.Offset = math.MaxInt
}
func (app *App) AppendSystemMessage(format string, args ...any) {
st := vaxis.Style{Attribute: vaxis.AttrDim}
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
}
func (app *App) SetNick(nick string) {
nick = strings.TrimSpace(nick)
app.input.SetPrompt(fmt.Sprintf("[%s] ", nick))
app.input.Prompt.Attribute = vaxis.AttrBold
app.nick = nick
}
func (app *App) EnsureConfigDir() error {
userCfg, err := os.UserConfigDir()
if err != nil {
return err
}
app.cfgHome = path.Join(userCfg, "nanite")
stat, err := os.Stat(app.cfgHome)
if err != nil {
if err := os.MkdirAll(app.cfgHome, 0o700); err != nil {
return err
}
} else {
if !stat.IsDir() {
return fmt.Errorf("expected %s to be directory", app.cfgHome)
}
}
return nil
}
func NewApp() *App {
app := &App{}
app.ctx, app.stop = context.WithCancel(context.Background())
app.incoming = make(chan IncomingEvent, 256)
app.outgoing = make(chan OutgoingEvent, 256)
app.error = make(chan error)
if err := app.EnsureConfigDir(); err != nil {
panic(err)
}
if err := app.RefreshScripts(); err != nil {
panic(err)
}
go func() {
for {
select {
case ev := <-app.outgoing:
_, dial := ev.(DialEvent)
if dial || app.conn != nil {
if err := ev.HandleOutgoing(app); err != nil {
app.error <- err
return
}
} else {
app.incoming <- SystemMessageEvent("not connected to any server")
}
case <-app.ctx.Done():
return
}
}
}()
app.InitUI()
return app
}
func (app *App) Loop() error {
for {
select {
case ev := <-app.vx.Events():
app.HandleTerminalEvent(ev)
case ev := <-app.incoming:
ev.HandleIncoming(app)
case err := <-app.error:
return err
case <-app.ctx.Done():
return nil
}
app.Redraw()
}
}
func (app *App) Finish() {
app.stop()
app.FinishUI()
if app.conn != nil {
HangupEvent{}.HandleOutgoing(app)
}
}
func init() {
initCommandMap()
}
func main() {
app := NewApp()
defer app.Finish()
args := os.Args
if len(args) == 2 || len(args) == 3 {
host := args[1]
port := "44322"
if len(args) == 3 {
port = args[2]
}
app.outgoing <- DialEvent{host, port}
}
if data, err := os.ReadFile(path.Join(app.cfgHome, "nick")); err == nil {
app.SetNick(string(data))
} else {
app.SetNick("someone")
}
app.AppendSystemMessage("welcome to nanite! :3")
if err := app.Loop(); err != nil {
app.FinishUI()
panic(err)
}
}