ref: c62a976e77e3fa7f12a2f105c4fc1adcf40fb194
parent: 1e548b7fd5c731682ad49d01bb834037e987c9cf
author: Ori Bernstein <ori@eigenstate.org>
date: Thu Jan 4 05:43:13 EST 2018
Split client into multiple files.
--- a/bld.proj
+++ b/bld.proj
@@ -1,2 +1,8 @@
-bin irc.myr = main.myr ;;
+bin irc.myr =
+ types.myr
+ main.myr
+ irc.myr
+ draw.myr
+;;
+
man = irc.myr.1 ;;
--- /dev/null
+++ b/draw.myr
@@ -1,0 +1,272 @@
+use std
+use date
+use iter
+use termdraw
+
+use "types"
+use "irc"
+
+pkg =
+ const redraw : (irc : irc# -> void)
+;;
+
+const redraw = {irc+ var c, x, y, w, h
+
+ if irc.term.width < 10 || irc.term.height < 5
+ -> void
+ ;;
+
+ x = 0
+ y = 0
+ w = irc.term.width
+ h = irc.term.height
+ c = curchan(irc)
+ if irc.chandirty
+ drawbanner(irc, 0, 0, w, 1, c)
+ drawtext(irc, 0, 1, w, h - 2, c)
+ drawlist(irc, 0, h - 2, w, h - 1, c)
+ irc.chandirty = false
+ ;;
+ if irc.cmddirty
+ drawinput(irc, 0, h - 1, w, h, c)
+ irc.cmddirty = false
+ ;;
+ termdraw.flush(irc.term)
+}
+
+const drawbanner = {irc, x0, y0, x1, y1, c+ var t
+
+ t = irc.term
+ termdraw.setbg(t, termdraw.Blue)
+ termdraw.clear(t, x0, y0, x1, y1)
+ termdraw.move(t, x0, y0)
+ termdraw.put(t, "{}", c.topic)+}
+
+const drawtext = {irc, x0, y0, x1, y1, c+ var height, margin, width, off
+ var t, dx, dy
+ var count
+ var x, y
+
+ t = irc.term
+ termdraw.setbg(t, termdraw.Default)
+ termdraw.clear(t, x0, y0, x1, y1)
+ termdraw.move(t, x0, y0)
+
+ dx = x1 - x0
+ dy = y1 - y0
+ count = 0
+ height = 0
+ width = dx
+ off = 0
+ /* nothing worth drawing... */
+ if dx <= c.gutter
+ -> void
+ ;;
+ for (tm, ent) : iter.byreverse(c.hist[:c.hist.len - c.scroll])
+ margin = 0
+ match ent
+ | `Msg (m, ln):
+ margin = strwidth(x0, c.gutter, " | ") + c.gutter
+ width = strwidth(x0, margin, ln)
+ | `Act (m, ln):
+ margin = strwidth(x0, 0, " *")
+ width = strwidth(x0, margin, m)
+ width += strwidth(x0, margin, " ")
+ width += strwidth(x0, margin, ln)
+ | `Join user:
+ margin = strwidth(x0, 0, "#joined: ")
+ width = strwidth(x0, margin, user)
+ | `Part user:
+ margin = strwidth(x0, 0, "#parted: ")
+ width = strwidth(x0, margin, user)
+ | `Status msg:
+ margin = strwidth(x0, 0, "! ")
+ width = strwidth(x0, margin, msg)
+ ;;
+ /* no point in drawing if all we draw is gutter */
+ if dx - margin <= 0
+ -> void
+ ;;
+ height++
+ if width > 0
+ height += width / (dx - margin)
+ ;;
+ count++
+ if height == dy
+ break
+ elif height > dy
+ off = (dx - margin)*(height - dy)
+ break
+ ;;
+ ;;
+
+ x = x0
+ y = y0
+ for (tm, h) : c.hist[c.hist.len - c.scroll - count:]
+ match h
+ | `Msg (m, ln):
+ termdraw.setattr(t, termdraw.Bold)
+ x = std.clamp(x0 + c.gutter - strwidth(x0, 0, m), 0, dx)
+ termdraw.move(t, x0 + c.gutter, y)
+ (x, y) = draw(t, m, x, y, x1, y1)
+ (x, y) = draw(t, " | ", x, y, x1, y1)
+ termdraw.setattr(t, lineattr(irc, ln[off:]))
+ (x, y) = draw(t, ln[off:], x, y, x1, y1)
+ termdraw.setattr(t, termdraw.Normal)
+ | `Act (m, ln):
+ termdraw.setattr(t, termdraw.Bold)
+ (x, y) = draw(t, " *", x0, y, x1, y1)
+ (x, y) = draw(t, m, x, y, x1, y1)
+ termdraw.setattr(t, lineattr(irc, ln[off:]))
+ (x, y) = draw(t, " ", x, y, x1, y1)
+ (x, y) = draw(t, ln[off:], x, y, x1, y1)
+ termdraw.setattr(t, termdraw.Normal)
+ | `Join user:
+ (x, y) = draw(t, "#joined ", x0, y, x1, y1)
+ (x, y) = draw(t, user[off:], x, y, x1, y1)
+ | `Part user:
+ (x, y) = draw(t, "#parted ", x0, y, x1, y1)
+ (x, y) = draw(t, user[off:], x, y, x1, y1)
+ | `Status msg:
+ (x, y) = draw(t, "! ", x0, y, x1, y1)
+ (x, y) = draw(t, msg[off:], x, y, x1, y1)
+ ;;
+ y++
+ off = 0
+ if y >= y1
+ break
+ ;;
+ ;;
+}
+
+const strwidth = {x0, margin, str+ var x
+
+ x = x0 + margin
+ for c : std.bychar(str)
+ match c
+ | '\t': x = (x / 8 + 1) * 8
+ | _: x += (std.cellwidth(c) : int)
+ ;;
+ ;;
+ -> x - (x0 + margin)
+}
+
+const draw = {t, msg, x0, y0, x1, y1+ var x, y
+
+ x = x0
+ y = y0
+ for l : std.bychar(msg)
+ match l
+ | '\t':
+ x = (x / 8 + 1)*8
+ | '\n':
+ termdraw.put(t, "\\n")
+ x += 2
+ | chr:
+ if x < x1
+ termdraw.move(t, x, y)
+ termdraw.putc(t, chr)
+ x++
+ ;;
+ ;;
+ if x >= x1
+ x = x0
+ y++
+ ;;
+ ;;
+ -> (x, y)
+}
+
+const drawlist = {irc, x0, y0, x1, y1, c+ var t
+
+ t = irc.term
+ termdraw.setbg(t, termdraw.Blue)
+ termdraw.clear(t, x0, y0, x1, y1)
+ termdraw.move(t, x0, y0)
+ termdraw.putc(t, '|')
+ termdraw.put(t, "[{}]", irc.self.name)+ for s : irc.srv
+ for ch : s.chan
+ if ch.flagged
+ termdraw.setattr(t, termdraw.Bold)
+ termdraw.put(t, "[@{}]", ch.name)+ termdraw.setattr(t, termdraw.Normal)
+ elif ch.stale
+ termdraw.put(t, "[*{}]", ch.name)+ else
+ termdraw.put(t, "[{}]", ch.name)+ ;;
+ ;;
+ termdraw.setattr(t, termdraw.Normal)
+ termdraw.putc(t, '|')
+ ;;
+}
+
+const drawinput = {irc, x0, y0, x1, y1, c+ var t, chan, ln, cx, dx
+
+ dx = x1 - x0
+ t = irc.term
+ chan = std.fmt("[{}] ", c.name)+ ln = inputstr(irc, chan, dx)
+ cx = x0 + (std.strcellwidth(chan) + irc.off : int)
+ if cx > dx
+ cx = dx
+ ;;
+ termdraw.setbg(t, termdraw.Default)
+ termdraw.move(t, x0, y0)
+ termdraw.clear(t, x0, y0, x1, y1)
+ termdraw.put(t, "{}{}", chan, ln)+ termdraw.cursorpos(t, cx, y0)
+ std.slfree(chan)
+ std.slfree(ln)
+}
+
+const lineattr = {irc, ln+ match cursrv(irc)
+ | `std.None: -> termdraw.Normal
+ | `std.Some srv:
+ match std.strfind(ln, srv.nick)
+ | `std.Some _: -> termdraw.Italic
+ | `std.None: -> termdraw.Normal
+ ;;
+ ;;
+}
+
+const inputstr = {irc, chan, dx+ var w, s, o
+ var start, end
+
+ s = irc.cmd
+ o = (irc.off : int)
+ dx -= strwidth(0, 0, chan)
+ w = 0
+ start = 0
+ end = 0
+ for var i = 0; i < s.len; i++
+ w += std.cellwidth(s[i])
+ if w >= dx
+ break
+ ;;
+ end++
+ ;;
+ while end < s.len && irc.off >= (w : std.size)
+ start++
+ while std.cellwidth(s[end]) == 0
+ start++
+ ;;
+ w += std.cellwidth(s[end])
+ end++
+ while end < s.len && std.cellwidth(s[end]) == 0
+ end++
+ ;;
+ ;;
+ -> getstr(s[start:end])
+}
--- /dev/null
+++ b/irc.myr
@@ -1,0 +1,830 @@
+use std
+use sys
+use bio
+use date
+use iter
+use termdraw
+use fileutil /* for homedir() */
+
+use "types"
+
+pkg =
+ const io : (irc : irc#, fd : std.fd -> void)
+ const curchan : (irc : irc# -> chan#)
+ const cursrv : (irc : irc# -> std.option(server#))
+ const send : (irc : irc#, srv : server#, fmt : byte[:], args : ... -> void)
+ const status : (irc : irc#, chan : chan#, fmt : byte[:], args : ... -> void)
+ const chancycle : (irc : irc#, delta : std.size -> void)
+ const handshake : (irc : irc#, srv : server# -> bool)
+ const message : (irc : irc#, ln : byte[:] -> void)
+ const cmd : (irc : irc#, ln : byte[:] -> void)
+ const getstr : (chars : char[:] -> byte[:])
+ const puthist : (irc : irc#, chan : chan#, ent : (std.time, hist) -> void)
+;;
+
+const io = {irc, fd+ var src, cmd, args
+ var srv, ln
+
+ srv = fd2srv(irc, fd)
+
+ match std.read(srv.fd, srv.buf[srv.nbuf:])
+ | `std.Ok 0: closed(irc, srv, "eof")
+ | `std.Ok n: srv.nbuf += n
+ | `std.Err e: closed(irc, srv, "error reading")
+ ;;
+
+ while true
+ match nextln(irc, srv)
+ | `std.Some l: ln = l
+ | `std.None: -> void
+ ;;
+ (src, cmd, args) = parse(ln)
+ uppercase(cmd)
+ match cmd
+ | "001": srvmsg(irc, args)
+ | "002": srvmsg(irc, args)
+ | "003": srvmsg(irc, args)
+ | "004": srvmsg(irc, args)
+ | "005": srvmsg(irc, args)
+ | "250": srvmsg(irc, args)
+ | "251": srvmsg(irc, args)
+ | "254": srvmsg(irc, args)
+ | "255": srvmsg(irc, args)
+ | "265": srvmsg(irc, args)
+ | "266": srvmsg(irc, args)
+ | "375": srvmsg(irc, args)
+ | "372": srvmsg(irc, args)
+ | "376": srvmsg(irc, args)
+ | "NOTICE": srvmsg(irc, args)
+ | "332": topic(irc, srv, args)
+ | "353": addusers(irc, srv, args)
+ | "QUIT": deluser(irc, srv, src)
+ | "PART": delchanuser(irc, srv, src, args)
+ | "366": shownames(irc, srv, args, 1)
+ | "PRIVMSG": recievemsg(irc, srv, src, args)
+ | "PING": send(irc, srv, "PONG :{}\r\n", args[0])+ | "JOIN": joined(irc, srv, src, args)
+ | "NICK": renamed(irc, srv, src, args)
+ | c: status(irc, irc.self, "unknown server command {}", ln)+ ;;
+ std.slfree(args)
+ std.slfree(ln)
+ ;;
+}
+
+const renamed = {irc, srv, src, args+ var a, b
+ if args.len > 0
+ for c : srv.chan
+ a = displayname(src)
+ b = displayname(args[0])
+ match std.lsearch(c.users, a, std.strcmp)
+ | `std.None: /* skip */
+ | `std.Some i:
+ std.slfree(c.users[i])
+ c.users[i] = std.sldup(b)
+ status(irc, c, "changed nick: {} => {}", a, b)+ ;;
+ ;;
+ ;;
+}
+
+const joined = {irc, srv, src, args+ var c, name
+
+ if args.len == 1
+ c = name2chan(irc, srv, args[0])
+ name = displayname(src)
+ c.gutter = std.max(c.gutter, std.cellwidth(name.len))
+ match std.lsearch(c.users, name, std.strcmp)
+ | `std.None: std.slpush(&c.users, std.sldup(name))
+ | `std.Some _: /* ignore */
+ ;;
+ puthist(irc, c, (std.now(), `Join std.sldup(name)))
+ irc.chandirty = true
+ irc.cmddirty = true
+ ;;
+}
+
+const topic = {irc, srv, args+ var c
+
+ if args.len >= 3
+ c = name2chan(irc, srv, args[1])
+ std.slfree(c.topic)
+ c.topic = std.sldup(args[2])
+ ;;
+}
+
+const recievemsg = {irc, srv, src, args+ var c
+
+ if std.sleq(args[0], srv.nick)
+ c = name2chan(irc, srv, displayname(src))
+ else
+ c = name2chan(irc, srv, args[0])
+ ;;
+ if args.len > 1 && isctcp(args[1])
+ std.put("got CTCP {e}\n", args[1])+ ctcpmsg(irc, srv, c, src, args[1][1:args[1].len-1])
+ else
+ chanmsg(irc, srv, c, src, args[1])
+ ;;
+}
+
+const isctcp = {m+ -> std.hasprefix(m, "\x01") && std.hassuffix(m, "\x01")
+}
+
+const addusers = {irc, srv, args+ var c
+
+ if args.len != 4
+ -> void
+ ;;
+ c = name2chan(irc, srv, args[2])
+ for n : std.bysplit(args[3], " ")
+ n = std.strstrip(n)
+ match std.lsearch(c.users, n, std.strcmp)
+ | `std.None: std.slpush(&c.users, std.sldup(n))
+ | `std.Some _: /* ignore */
+ ;;
+ c.gutter = std.max(c.gutter, n.len)
+ ;;
+}
+
+const deluser = {irc, srv, user+ for c : srv.chan
+ user = displayname(user)
+ match std.lsearch(c.users, user, std.strcmp)
+ | `std.None:
+ continue
+ | `std.Some i:
+ puthist(irc, c, (std.now(), `Part std.sldup(user)))
+ std.slfree(c.users[i])
+ std.sldel(&c.users, i)
+ ;;
+ ;;
+}
+
+const delchanuser = {irc, srv, user, args+ var c
+
+ if args.len == 0
+ -> void
+ ;;
+ c = name2chan(irc, srv, args[0])
+ user = displayname(user)
+ match std.lsearch(c.users, user, std.strcmp)
+ | `std.None: /* ignore */
+ | `std.Some i:
+ std.slfree(c.users[i])
+ std.sldel(&c.users, i)
+ puthist(irc, c, (std.now(), `Part std.sldup(user)))
+ ;;
+}
+
+const fd2srv = {irc, fd+ for s : irc.srv
+ if s.fd == fd
+ -> s
+ ;;
+ ;;
+ std.fatal("missing srv for fd {}", fd)+}
+
+const name2chan = {irc, srv, chan+ var new
+
+ for c : srv.chan
+ if std.sleq(c.name, chan)
+ -> c
+ ;;
+ ;;
+
+ new = mkchan(irc, srv.ds, chan, "")
+ std.slpush(&srv.chan, new)
+ if srv.focus == -1
+ srv.focus = srv.chan.len - 1
+ ;;
+ status(irc, new, "joined on {}", date.now("local"))+ -> new
+}
+
+const mkchan = {irc, srvname, name, topic+ var logpath, logfd
+
+ logpath = std.pathjoin([irc.logdir, srvname, name][:])
+ std.mkpath(std.dirname(logpath))
+ match std.openmode(logpath, std.Ordwr | std.Ocreat | std.Oappend, 0o666)
+ | `std.Ok fd: logfd = fd
+ | `std.Err _: logfd = -1
+ ;;
+ -> std.mk([
+ .log = logfd,
+ .hist = [][:], /* FIXME: read back from log */
+ .name = std.sldup(name),
+ .topic = std.sldup(topic),
+ .gutter = 8
+ ])
+}
+
+const mksrv = {irc, fd, ds -> server#+ -> std.mk([
+ .fd=fd,
+ .ds=std.sldup(ds),
+ .chan=[][:],
+ .focus=-1,
+ .user=std.sldup(irc.user),
+ .nick=std.sldup(irc.nick),
+ ])
+}
+
+const nextln = {irc, srv+ var r
+
+ match std.strfind(srv.buf[:srv.nbuf], "\r\n")
+ | `std.Some i:
+ r = std.sldup(srv.buf[:i])
+ std.slcp(srv.buf[:srv.nbuf - i - 2], srv.buf[i + 2:srv.nbuf])
+ srv.nbuf = srv.nbuf - i - 2
+ -> `std.Some r
+ | `std.None:
+ -> `std.None
+ ;;
+}
+
+const closed = {irc, srv, msg+ if srv.death == 0
+ srv.death = std.now()
+ status(irc, irc.self, "{} closed: {}", srv.ds, msg)+ ;;
+}
+
+
+const displayname = {src+ match std.strfind(src, "!")
+ | `std.Some i: -> src[:i]
+ | `std.None: -> src
+ ;;
+}
+
+const freechan = {chan+ std.slfree(chan.name)
+ std.slfree(chan.topic)
+ std.free(chan)
+}
+
+const getstr = {chars+ var sb = std.mksb()
+ for c : chars
+ std.sbputc(sb, c)
+ ;;
+ -> std.sbfin(sb)
+}
+
+const cmd = {irc, text+ var sp
+
+ sp = std.strtok(text)
+ if sp.len == 0
+ -> void
+ ;;
+ match sp[0]
+ | "/connect": connect(irc, sp[1:])
+ | "/join": join(irc, sp[1:])
+ | "/leave": leave(irc, sp[1:])
+ | "/chan": chanswitch(irc, sp[1:])
+ | "/win": chanswitch(irc, sp[1:])
+ | "/quit": quit(irc, sp[1:])
+ | "/help": help(irc, sp[1:])
+ | "/nick": changenick(irc, sp[1:])
+ | "/user": changeuser(irc, sp[1:])
+ | "/srv": changeserver(irc, sp[1:])
+ | "/names": listnames(irc, sp[1:])
+ | "/msg": privmsg(irc, sp[1:])
+ | "/me": action(irc, sp[1:])
+ | "/": message(irc, std.strfstrip(text[1:]))
+ | c: status(irc, irc.self, "unknown command: /{}", text)+ ;;
+ std.slfree(sp)
+}
+
+
+const changenick = {irc, args+ if args.len != 1
+ status(irc, irc.self, "/nick: invalid args {j= }", args)+ -> void
+ ;;
+
+ match cursrv(irc)
+ | `std.None:
+ std.slfree(irc.nick)
+ irc.nick = std.sldup(args[0])
+ status(irc, irc.self, "default nick changed: {}", args[0])+ | `std.Some srv:
+ std.slfree(irc.nick)
+ srv.nick = std.sldup(args[0])
+ send(irc, srv, "NICK {}\r\n", srv.nick)+ status(irc, irc.self, "nick changed for {}: {}", srv.ds, args[0])+ ;;
+}
+
+const changeuser = {irc, args+ if args.len != 1
+ status(irc, irc.self, "/nick: invalid args {j= }", args)+ -> void
+ ;;
+
+ match cursrv(irc)
+ | `std.None:
+ std.slfree(irc.user)
+ irc.user = std.sldup(args[0])
+ | `std.Some srv:
+ std.slfree(irc.user)
+ srv.user = std.sldup(args[0])
+ send(irc, srv, "NICK {}\r\n", srv.user)+ ;;
+}
+
+const changeserver = {irc, args+ if args.len != 1
+ status(irc, irc.self, "/nick: invalid args {j= }", args)+ -> void
+ ;;
+ match findsrv(irc, args[0])
+ | `std.Some s: irc.focus = s.id
+ | `std.None: status(irc, irc.self, "no server '{}", args[0])+ ;;
+}
+
+const connect = {irc, args+ var ds, srv
+
+ ds = ""
+ match args.len
+ | 1: ds = std.netaddr(args[0], "tcp", "ircd")
+ | 0: status(irc, irc.self, "join: missing server")
+ | _: status(irc, irc.self, "join: invalid arguments '{j= }'", args)+ ;;
+ if ds.len == 0
+ -> void
+ ;;
+
+ for s : irc.srv
+ if std.sleq(s.ds, ds)
+ status(irc, irc.self, "already connected to {j= }", args)+ -> void
+ ;;
+ ;;
+
+ status(irc, irc.self, "dialing {}", ds)+ match std.dial(ds)
+ | `std.Ok fd:
+ srv = mksrv(irc, fd, ds)
+ if handshake(irc, srv)
+ srv.id = irc.srv.len
+ std.slpush(&irc.srv, srv)
+ if irc.focus == -1
+ irc.focus = irc.srv.len - 1
+ srv.death = 0
+ ;;
+ status(irc, irc.self, "connected to {}", ds)+ else
+ status(irc, irc.self, "could not handshake with {j= }", args)+ std.close(srv.fd)
+ std.free(srv)
+ ;;
+ | `std.Err e:
+ status(irc, irc.self, "failed to connect: {}", e)+ ;;
+}
+
+const listnames = {irc, args+ match cursrv(irc)
+ | `std.Some s: shownames(irc, s, args, 0)
+ | `std.None: /* nothing */
+ ;;
+}
+
+const shownames = {irc, srv, args, idx+ var c
+
+ if args.len > idx && irc.focus >= 0
+ c = name2chan(irc, srv, args[idx])
+ else
+ c = curchan(irc)
+ ;;
+ if c != irc.self
+ status(irc, c, "{j= }", c.users)+ ;;
+}
+
+const handshake = {irc, srv+ send(irc, srv, "NICK {}\r\n", srv.nick)+ send(irc, srv, "USER {} 8 * :{}\r\n", srv.user, srv.user)+ -> true
+}
+
+const join = {irc, args+ if args.len == 2
+ match findsrv(irc, args[1])
+ | `std.Some s: send(irc, s, "JOIN {}\r\n", args[0])+ | `std.None: status(irc, irc.self, "no server '{}", args[1])+ ;;
+ elif args.len == 1
+ match cursrv(irc)
+ | `std.Some s: send(irc, s, "JOIN {}\r\n", args[0])+ | `std.None: status(irc, irc.self, "no server focused")
+ ;;
+ else
+ -> status(irc, irc.self, "invalid arguments: {j= }", args)+ ;;
+}
+
+const leave = {irc, args+ var name, c, srv
+
+ if irc.focus < 0
+ -> void
+ ;;
+ if args.len == 0
+ c = curchan(irc)
+ if c == irc.self
+ -> void
+ ;;
+ name = c.name
+ elif args.len == 1
+ name = args[0]
+ else
+ status(irc, irc.self, "leave: invalid arguments: {j= }", args)+ -> void
+ ;;
+
+ srv = irc.srv[irc.focus]
+ for var i = 0; i < srv.chan.len; i++
+ c = srv.chan[i]
+ if std.sleq(name, c.name)
+ send(irc, srv, "PART {}\r\n", name)+ std.sldel(&srv.chan, i)
+ if i == srv.focus
+ irc.focus = -1
+ ;;
+ freechan(c)
+ break
+ ;;
+
+ ;;
+}
+
+const chancycle = {irc, delta+ var srv
+
+ if irc.srv.len == 0
+ -> void
+ ;;
+
+ irc.chandirty = true
+ irc.cmddirty = true
+ for var i = 0; i < irc.srv.len; i++
+ if irc.focus < 0 || irc.focus >= irc.srv.len
+ irc.focus = 0
+ ;;
+
+ srv = irc.srv[irc.focus]
+ srv.focus += delta
+ if srv.focus < 0 || srv.focus >= srv.chan.len
+ irc.focus += delta
+ if irc.focus < 0
+ irc.focus += irc.srv.len
+ ;;
+ irc.focus %= irc.srv.len
+ irc.srv[irc.focus].focus = 0
+ ;;
+ srv = irc.srv[irc.focus]
+ if srv.chan.len > 0
+ srv.chan[srv.focus].stale = false
+ srv.chan[srv.focus].flagged = false
+ break
+ ;;
+ ;;
+}
+
+const chanswitch = {irc, args+ /* full, end, internal */
+ var matches = [(-1, -1), (-1, -1), (-1, -1)]
+ var srv
+
+ if args.len != 1
+ status(irc, irc.self, "invalid arguments: {j= }", args)+ else
+ irc.chandirty = true
+ irc.cmddirty = true
+ match matchrank(irc.self.name, args[0])
+ | `std.None: /* ok */
+ | `std.Some rank: matches[rank] = (0, -1)
+ ;;
+ for var i = 0; i < irc.srv.len; i++
+ srv = irc.srv[i]
+ for var j = 0; j < srv.chan.len; j++
+ match matchrank(srv.chan[j].name, args[0])
+ | `std.None: /* ok */
+ | `std.Some rank:
+ matches[rank] = (j, i)
+ ;;
+ ;;
+ ;;
+
+ for (chanidx, srvidx) : matches[:]
+ if chanidx < 0
+ continue
+ ;;
+ irc.focus = srvidx
+ if srvidx >= 0
+ irc.srv[srvidx].focus = chanidx
+ irc.srv[srvidx].chan[chanidx].stale = false
+ irc.srv[srvidx].chan[chanidx].flagged = false
+ ;;
+ break
+ ;;
+ ;;
+}
+
+const matchrank = {str, name+ match std.strfind(str, name)
+ | `std.None:
+ -> `std.None
+ | `std.Some idx:
+ match (idx, idx + name.len == str.len)
+ | (0, true): -> `std.Some 0
+ | (_, true): -> `std.Some 1
+ | (0, false): -> `std.Some 1
+ | (_, _): -> `std.Some 2
+ ;;
+ ;;
+}
+
+const quit = {irc, args+ if args.len != 0
+ status(irc, irc.self, "invalid argument for quit: {j= }", args)+ -> void
+ ;;
+ termdraw.free(irc.term)
+ std.exit(0)
+}
+
+const help = {irc, args+ status(irc, irc.self, "irc.myr commands")
+ status(irc, irc.self, "\t/connect dialstr: connect to server")
+ status(irc, irc.self, "\t/help [cmd...]: get help [on cmd...]")
+ status(irc, irc.self, "\t/join chan [srv]: join channel on server")
+ status(irc, irc.self, "\t/leave [chan]: leave current channel")
+ status(irc, irc.self, "\t/chan name: switch to channnel 'name'")
+ status(irc, irc.self, "\t/win name: switch to channnel 'name'")
+ status(irc, irc.self, "\t/names: list nicks in current channel")
+ status(irc, irc.self, "\t/quit: exit irc.myr")
+}
+
+const send = {irc, srv, fmt, args+ var s, ap
+
+ ap = std.vastart(&args)
+ s = std.fmtv(fmt, &ap)
+ if !writeall(srv.fd, s)
+ closed(irc, srv, "failed writing")
+ ;;
+ std.slfree(s)
+}
+
+const message = {irc, msg+ var c
+
+ match cursrv(irc)
+ | `std.Some s:
+ c = curchan(irc)
+ if c == irc.self
+ status(irc, irc.self, "can't send to status channel")
+ else
+ send(irc, s, "PRIVMSG {} :{}\r\n", c.name, msg)+ chanmsg(irc, s, c, s.nick, msg)
+ ;;
+ | `std.None:
+ status(irc, irc.self, "no connected server")
+ ;;
+}
+
+const action = {irc, msg+ var b, buf : byte[512]
+ var c
+
+ match cursrv(irc)
+ | `std.Some s:
+ b = std.bfmt(buf[:], "{j= }", msg)+ c = curchan(irc)
+ puthist(irc, c, (std.now(), `Act (s.nick, std.sldup(b))))
+ send(irc, s, "PRIVMSG {} :\x01ACTION {}\x01\r\n", c.name, b)+ | `std.None:
+ status(irc, irc.self, "no connected server")
+ ;;
+}
+
+const privmsg = {irc, msg+ var srv, chan, txt
+
+ if msg.len <= 1
+ status(irc, irc.self, "missing message in /msg")
+ -> void
+ elif msg.len > 3 && std.eq(msg[1], "-srv")
+ match findsrv(irc, msg[2])
+ | `std.Some s: srv = s
+ | `std.None: -> status(irc, irc.self, "can't message: no server '{}", msg[1])+ ;;
+ txt = std.fmt("{j= }", msg[3:])+ else
+ match cursrv(irc)
+ | `std.Some s: srv = s
+ | `std.None: -> status(irc, irc.self, "cant message: no server\n")
+ ;;
+ txt = std.fmt("{j= }", msg[1:])+ ;;
+ chan = name2chan(irc, srv, msg[0])
+ send(irc, srv, "PRIVMSG {} :{}\r\n", chan.name, txt)+ chanmsg(irc, srv, chan, srv.nick, txt)
+ std.slfree(txt)
+}
+
+const parse = {msg+ var w, hdr, cmd, args
+
+ match std.charstep(msg)
+ | (':', tl): (hdr, msg) = word(tl)+ | (_, _): hdr = ""
+ ;;
+ (cmd, msg) = word(msg)
+
+ args = [][:]
+ msg = std.strfstrip(msg)
+ while msg.len != 0
+ match std.charstep(msg)
+ | (':', tl):+ std.slpush(&args, tl)
+ break
+ | _:
+ (w, msg) = word(msg)
+ std.slpush(&args, w)
+ ;;
+ ;;
+
+ -> (hdr, cmd, args)
+}
+
+const word = {s+ for var i = 0; i < s.len; i++
+ if std.isspace(std.decode(s[i:]))
+ -> (s[:i], std.strfstrip(s[i:]))
+ ;;
+ ;;
+ -> (s, "")
+
+}
+
+
+const srvmsg = {irc, args+ if args.len >= 2
+ status(irc, irc.self, "{}", args[1])+ ;;
+}
+
+const status = {irc, chan, fmt, args : ...+ var s, ap
+
+ ap = std.vastart(&args)
+ s = std.fmtv(fmt, &ap)
+ puthist(irc, chan, (std.now(), `Status s))
+}
+
+const curchan = {irc+ var s
+
+ if irc.focus < 0 || irc.focus >= irc.srv.len
+ -> irc.self
+ else
+ s = irc.srv[irc.focus]
+ if s.focus < 0 || s.focus >= s.chan.len
+ -> irc.self
+ ;;
+ -> s.chan[s.focus]
+ ;;
+}
+
+const cursrv = {irc+ if irc.focus < 0
+ -> `std.None
+ else
+ -> `std.Some irc.srv[irc.focus]
+ ;;
+}
+
+const findsrv = {irc, name+ var idx
+
+ idx = -1
+ for var i = 0; i < irc.srv.len; i++
+ match std.strfind(irc.srv[i].ds, name)
+ | `std.Some o:
+ if idx != -1
+ status(irc, irc.self, "ambiguous server name {}", name)+ -> `std.None
+ else
+ idx = i
+ ;;
+ | `std.None:
+ ;;
+ ;;
+ if idx != -1
+ -> `std.Some irc.srv[idx]
+ else
+ -> `std.None
+ ;;
+}
+
+const ctcpmsg = {irc, srv, chan, nick, msg+ var n
+
+ match word(msg)
+ | ("ACTION", tl):+ n = std.sldup(displayname(nick))
+ puthist(irc, chan, (std.now(), `Act (n, std.sldup(tl))))
+ | ("PING", tl): + send(irc, srv, "PRIVMSG {} :\x01PING {}\x01\r\n", nick, tl)+ | ("TIME", tl):+ send(irc, srv, "PRIVMSG {} :\x01TIME {}\x01\r\n", nick, date.utcnow())+ | ("VERSION", tl):+ send(irc, srv, "PRIVMSG {} :\x01VERSION irc.myr\x01\r\n", nick)+ | ("CLIENTINFO", tl):+ send(irc, srv,
+ "PRIVMSG {} :\x01ACTION PING TIME VERSION CLIENTINFO\x01\r\n", nick)+ | (unknown, tl):
+ status(irc, irc.self, "unknown CTCP message {}\n", msg)+ ;;
+}
+
+const chanmsg = {irc, srv, chan, nick, msg + nick = std.sldup(displayname(nick))
+ puthist(irc, chan, (std.now(), `Msg (nick, std.fmt("{e}", msg))))+ match std.strfind(msg, srv.nick)
+ | `std.None: /* nothing */
+ | `std.Some _:
+ if curchan(irc) != chan
+ chan.flagged = true
+ ;;
+ ;;
+}
+
+const puthist = {irc, chan, entry+ var tm, d, contents
+
+ std.slpush(&chan.hist, entry)
+ (tm, contents) = entry
+ if chan.log != -1
+ d = date.mkinstant(tm, "local")
+ match contents
+ | `Msg (u, ln):
+ std.fput(chan.log, "{} {} | {}\n", d, u, ln)+ if chan != curchan(irc)
+ chan.stale = true
+ ;;
+ | _:
+ /* nothing */
+ ;;
+ ;;
+ if chan == curchan(irc)
+ irc.chandirty = true
+ ;;
+ if chan.scroll != 0
+ chan.scroll++
+ ;;
+}
+
+const writeall = {fd, buf+ var o
+
+ o = 0
+ while o != buf.len
+ match std.write(fd, buf[o:])
+ | `std.Err e: -> false
+ | `std.Ok 0: break
+ | `std.Ok n: o += n
+ ;;
+ ;;
+ -> o == buf.len
+}
+
+const uppercase = {cmd+ for var i = 0; i < cmd.len; i++
+ if cmd[i] >= ('a' : byte) && cmd[i] <= ('z' : byte)+ cmd[i] = cmd[i] - ('a' : byte) + ('A' : byte)+ ;;
+ ;;
+}
--- a/main.myr
+++ b/main.myr
@@ -6,63 +6,10 @@
use termdraw
use fileutil /* for homedir() */
-pkg =
- const send : (irc : irc#, srv : server#, fmt : byte[:], args : ... -> void)
- const status : (irc : irc#, chan : chan#, fmt : byte[:], args : ... -> void)
-;;
+use "types"
+use "draw"
+use "irc"
-type irc = struct
- srv : server#[:]
- logdir : byte[:]
- self : chan#
- focus : std.size
- term : termdraw.term#
- user : byte[:]
- nick : byte[:]
-
- /* input editing */
- off : std.size
- cmd : char[:]
- yank : char[:]
-
- chandirty : bool
- cmddirty : bool
-;;
-
-type server = struct
- id : std.size
- fd : std.fd
- user : byte[:]
- nick : byte[:]
- ds : byte[:]
- buf : byte[512]
- nbuf : std.size
- chan : chan#[:]
- focus : std.size
- death : std.time
-;;
-
-type chan = struct
- log : std.fd
- hist : (std.time, hist)[:]
- histoff : std.size /* scrolling */
- name : byte[:]
- topic : byte[:]
- users : byte[:][:]
- stale : bool
- flagged : bool
- gutter : int
- scroll : int
-;;
-
-type hist = union
- `Msg (byte[:], byte[:])
- `Act (byte[:], byte[:])
- `Join byte[:]
- `Part byte[:]
- `Status byte[:]
-;;
-
const main = {argsvar irc : irc#
var home, rcfile, logdir
@@ -121,225 +68,40 @@
;;
}
-const io = {irc, fd- var src, cmd, args
- var srv, ln
+const poll = {irc+ var pfd, fd, start, i
+ var out
- srv = fd2srv(irc, fd)
-
- match std.read(srv.fd, srv.buf[srv.nbuf:])
- | `std.Ok 0: closed(irc, srv, "eof")
- | `std.Ok n: srv.nbuf += n
- | `std.Err e: closed(irc, srv, "error reading")
+ fd = -1
+ pfd = [][:]
+ std.slpush(&pfd, [
+ .fd=(std.In : sys.fd),
+ .events=sys.Pollin,
+ .revents=0,
+ ])
+ for s : irc.srv
+ std.slpush(&pfd, [
+ .fd=(s.fd : sys.fd),
+ .events=sys.Pollin,
+ .revents=0,
+ ])
;;
-
- while true
- match nextln(irc, srv)
- | `std.Some l: ln = l
- | `std.None: -> void
- ;;
- (src, cmd, args) = parse(ln)
- uppercase(cmd)
- match cmd
- | "001": srvmsg(irc, args)
- | "002": srvmsg(irc, args)
- | "003": srvmsg(irc, args)
- | "004": srvmsg(irc, args)
- | "005": srvmsg(irc, args)
- | "250": srvmsg(irc, args)
- | "251": srvmsg(irc, args)
- | "254": srvmsg(irc, args)
- | "255": srvmsg(irc, args)
- | "265": srvmsg(irc, args)
- | "266": srvmsg(irc, args)
- | "375": srvmsg(irc, args)
- | "372": srvmsg(irc, args)
- | "376": srvmsg(irc, args)
- | "NOTICE": srvmsg(irc, args)
- | "332": topic(irc, srv, args)
- | "353": addusers(irc, srv, args)
- | "QUIT": deluser(irc, srv, src)
- | "PART": delchanuser(irc, srv, src, args)
- | "366": shownames(irc, srv, args, 1)
- | "PRIVMSG": recievemsg(irc, srv, src, args)
- | "PING": send(irc, srv, "PONG :{}\r\n", args[0])- | "JOIN": joined(irc, srv, src, args)
- | "NICK": renamed(irc, srv, src, args)
- | c: status(irc, irc.self, "unknown server command {}", ln)- ;;
- std.slfree(args)
- std.slfree(ln)
- ;;
-}
-
-const renamed = {irc, srv, src, args- var a, b
- if args.len > 0
- for c : srv.chan
- a = displayname(src)
- b = displayname(args[0])
- match std.lsearch(c.users, a, std.strcmp)
- | `std.None: /* skip */
- | `std.Some i:
- std.slfree(c.users[i])
- c.users[i] = std.sldup(b)
- status(irc, c, "changed nick: {} => {}", a, b)+ if sys.poll(pfd, -1) >= 0
+ /* randomize so that we try to cover all fds evenly */
+ start = std.rand(0, pfd.len)
+ for var j = 0; j != pfd.len; j++
+ i = (j + start) % pfd.len
+ if pfd[i].revents & sys.Pollin != 0
+ fd = (pfd[i].fd : std.fd)
+ out = false
;;
;;
;;
-}
+ std.slfree(pfd)
-const joined = {irc, srv, src, args- var c, name
-
- if args.len == 1
- c = name2chan(irc, srv, args[0])
- name = displayname(src)
- c.gutter = std.max(c.gutter, std.cellwidth(name.len))
- match std.lsearch(c.users, name, std.strcmp)
- | `std.None: std.slpush(&c.users, std.sldup(name))
- | `std.Some _: /* ignore */
- ;;
- puthist(irc, c, (std.now(), `Join std.sldup(name)))
- irc.chandirty = true
- irc.cmddirty = true
- ;;
+ -> fd
}
-const topic = {irc, srv, args- var c
-
- if args.len >= 3
- c = name2chan(irc, srv, args[1])
- std.slfree(c.topic)
- c.topic = std.sldup(args[2])
- ;;
-}
-
-const recievemsg = {irc, srv, src, args- var c
-
- if std.sleq(args[0], srv.nick)
- c = name2chan(irc, srv, displayname(src))
- else
- c = name2chan(irc, srv, args[0])
- ;;
- if args.len > 1 && isctcp(args[1])
- std.put("got CTCP {e}\n", args[1])- ctcpmsg(irc, srv, c, src, args[1][1:args[1].len-1])
- else
- chanmsg(irc, srv, c, src, args[1])
- ;;
-}
-
-const isctcp = {m- -> std.hasprefix(m, "\x01") && std.hassuffix(m, "\x01")
-}
-
-const addusers = {irc, srv, args- var c
-
- if args.len != 4
- -> void
- ;;
- c = name2chan(irc, srv, args[2])
- for n : std.bysplit(args[3], " ")
- n = std.strstrip(n)
- match std.lsearch(c.users, n, std.strcmp)
- | `std.None: std.slpush(&c.users, std.sldup(n))
- | `std.Some _: /* ignore */
- ;;
- c.gutter = std.max(c.gutter, n.len)
- ;;
-}
-
-const deluser = {irc, srv, user- for c : srv.chan
- user = displayname(user)
- match std.lsearch(c.users, user, std.strcmp)
- | `std.None:
- continue
- | `std.Some i:
- puthist(irc, c, (std.now(), `Part std.sldup(user)))
- std.slfree(c.users[i])
- std.sldel(&c.users, i)
- ;;
- ;;
-}
-
-const delchanuser = {irc, srv, user, args- var c
-
- if args.len == 0
- -> void
- ;;
- c = name2chan(irc, srv, args[0])
- user = displayname(user)
- match std.lsearch(c.users, user, std.strcmp)
- | `std.None: /* ignore */
- | `std.Some i:
- std.slfree(c.users[i])
- std.sldel(&c.users, i)
- puthist(irc, c, (std.now(), `Part std.sldup(user)))
- ;;
-}
-
-const fd2srv = {irc, fd- for s : irc.srv
- if s.fd == fd
- -> s
- ;;
- ;;
- std.fatal("missing srv for fd {}", fd)-}
-
-const name2chan = {irc, srv, chan- var new
-
- for c : srv.chan
- if std.sleq(c.name, chan)
- -> c
- ;;
- ;;
-
- new = mkchan(irc, srv.ds, chan, "")
- std.slpush(&srv.chan, new)
- if srv.focus == -1
- srv.focus = srv.chan.len - 1
- ;;
- status(irc, new, "joined on {}", date.now("local"))- -> new
-}
-
-const nextln = {irc, srv- var r
-
- match std.strfind(srv.buf[:srv.nbuf], "\r\n")
- | `std.Some i:
- r = std.sldup(srv.buf[:i])
- std.slcp(srv.buf[:srv.nbuf - i - 2], srv.buf[i + 2:srv.nbuf])
- srv.nbuf = srv.nbuf - i - 2
- -> `std.Some r
- | `std.None:
- -> `std.None
- ;;
-}
-
-const closed = {irc, srv, msg- if srv.death == 0
- srv.death = std.now()
- status(irc, irc.self, "{} closed: {}", srv.ds, msg)- ;;
-}
-
-
-const displayname = {src- match std.strfind(src, "!")
- | `std.Some i: -> src[:i]
- | `std.None: -> src
- ;;
-}
-
const mkirc = {rcfile, logdirvar irc, term
var nick, user
@@ -372,17 +134,6 @@
-> irc
}
-const mksrv = {irc, fd, ds -> server#- -> std.mk([
- .fd=fd,
- .ds=std.sldup(ds),
- .chan=[][:],
- .focus=-1,
- .user=std.sldup(irc.user),
- .nick=std.sldup(irc.nick),
- ])
-}
-
const mkchan = {irc, srvname, name, topicvar logpath, logfd
@@ -401,12 +152,6 @@
])
}
-const freechan = {chan- std.slfree(chan.name)
- std.slfree(chan.topic)
- std.free(chan)
-}
-
const do = {irc, lnif ln.len == 0
-> void
@@ -422,419 +167,6 @@
;;
}
-const getstr = {chars- var sb = std.mksb()
- for c : chars
- std.sbputc(sb, c)
- ;;
- -> std.sbfin(sb)
-}
-
-const cmd = {irc, text- var sp
-
- sp = std.strtok(text)
- if sp.len == 0
- -> void
- ;;
- match sp[0]
- | "/connect": connect(irc, sp[1:])
- | "/join": join(irc, sp[1:])
- | "/leave": leave(irc, sp[1:])
- | "/chan": chanswitch(irc, sp[1:])
- | "/win": chanswitch(irc, sp[1:])
- | "/quit": quit(irc, sp[1:])
- | "/help": help(irc, sp[1:])
- | "/nick": changenick(irc, sp[1:])
- | "/user": changeuser(irc, sp[1:])
- | "/srv": changeserver(irc, sp[1:])
- | "/names": listnames(irc, sp[1:])
- | "/msg": privmsg(irc, sp[1:])
- | "/me": action(irc, sp[1:])
- | "/": message(irc, std.strfstrip(text[1:]))
- | c: status(irc, irc.self, "unknown command: /{}", text)- ;;
- std.slfree(sp)
-}
-
-
-const changenick = {irc, args- if args.len != 1
- status(irc, irc.self, "/nick: invalid args {j= }", args)- -> void
- ;;
-
- match cursrv(irc)
- | `std.None:
- std.slfree(irc.nick)
- irc.nick = std.sldup(args[0])
- status(irc, irc.self, "default nick changed: {}", args[0])- | `std.Some srv:
- std.slfree(irc.nick)
- srv.nick = std.sldup(args[0])
- send(irc, srv, "NICK {}\r\n", srv.nick)- status(irc, irc.self, "nick changed for {}: {}", srv.ds, args[0])- ;;
-}
-
-const changeuser = {irc, args- if args.len != 1
- status(irc, irc.self, "/nick: invalid args {j= }", args)- -> void
- ;;
-
- match cursrv(irc)
- | `std.None:
- std.slfree(irc.user)
- irc.user = std.sldup(args[0])
- | `std.Some srv:
- std.slfree(irc.user)
- srv.user = std.sldup(args[0])
- send(irc, srv, "NICK {}\r\n", srv.user)- ;;
-}
-
-const changeserver = {irc, args- if args.len != 1
- status(irc, irc.self, "/nick: invalid args {j= }", args)- -> void
- ;;
- match findsrv(irc, args[0])
- | `std.Some s: irc.focus = s.id
- | `std.None: status(irc, irc.self, "no server '{}", args[0])- ;;
-}
-
-const connect = {irc, args- var ds, srv
-
- ds = ""
- match args.len
- | 1: ds = std.netaddr(args[0], "tcp", "ircd")
- | 0: status(irc, irc.self, "join: missing server")
- | _: status(irc, irc.self, "join: invalid arguments '{j= }'", args)- ;;
- if ds.len == 0
- -> void
- ;;
-
- for s : irc.srv
- if std.sleq(s.ds, ds)
- status(irc, irc.self, "already connected to {j= }", args)- -> void
- ;;
- ;;
-
- status(irc, irc.self, "dialing {}", ds)- match std.dial(ds)
- | `std.Ok fd:
- srv = mksrv(irc, fd, ds)
- if handshake(irc, srv)
- srv.id = irc.srv.len
- std.slpush(&irc.srv, srv)
- if irc.focus == -1
- irc.focus = irc.srv.len - 1
- srv.death = 0
- ;;
- status(irc, irc.self, "connected to {}", ds)- else
- status(irc, irc.self, "could not handshake with {j= }", args)- std.close(srv.fd)
- std.free(srv)
- ;;
- | `std.Err e:
- status(irc, irc.self, "failed to connect: {}", e)- ;;
-}
-
-const listnames = {irc, args- match cursrv(irc)
- | `std.Some s: shownames(irc, s, args, 0)
- | `std.None: /* nothing */
- ;;
-}
-
-const shownames = {irc, srv, args, idx- var c
-
- if args.len > idx && irc.focus >= 0
- c = name2chan(irc, srv, args[idx])
- else
- c = curchan(irc)
- ;;
- if c != irc.self
- status(irc, c, "{j= }", c.users)- ;;
-}
-
-const handshake = {irc, srv- send(irc, srv, "NICK {}\r\n", srv.nick)- send(irc, srv, "USER {} 8 * :{}\r\n", srv.user, srv.user)- -> true
-}
-
-const join = {irc, args- if args.len == 2
- match findsrv(irc, args[1])
- | `std.Some s: send(irc, s, "JOIN {}\r\n", args[0])- | `std.None: status(irc, irc.self, "no server '{}", args[1])- ;;
- elif args.len == 1
- match cursrv(irc)
- | `std.Some s: send(irc, s, "JOIN {}\r\n", args[0])- | `std.None: status(irc, irc.self, "no server focused")
- ;;
- else
- -> status(irc, irc.self, "invalid arguments: {j= }", args)- ;;
-}
-
-const leave = {irc, args- var name, c, srv
-
- if irc.focus < 0
- -> void
- ;;
- if args.len == 0
- c = curchan(irc)
- if c == irc.self
- -> void
- ;;
- name = c.name
- elif args.len == 1
- name = args[0]
- else
- status(irc, irc.self, "leave: invalid arguments: {j= }", args)- -> void
- ;;
-
- srv = irc.srv[irc.focus]
- for var i = 0; i < srv.chan.len; i++
- c = srv.chan[i]
- if std.sleq(name, c.name)
- send(irc, srv, "PART {}\r\n", name)- std.sldel(&srv.chan, i)
- if i == srv.focus
- irc.focus = -1
- ;;
- freechan(c)
- break
- ;;
-
- ;;
-}
-
-const chancycle = {irc, delta- var srv
-
- if irc.srv.len == 0
- -> void
- ;;
-
- irc.chandirty = true
- irc.cmddirty = true
- for var i = 0; i < irc.srv.len; i++
- if irc.focus < 0 || irc.focus >= irc.srv.len
- irc.focus = 0
- ;;
-
- srv = irc.srv[irc.focus]
- srv.focus += delta
- if srv.focus < 0 || srv.focus >= srv.chan.len
- irc.focus += delta
- if irc.focus < 0
- irc.focus += irc.srv.len
- ;;
- irc.focus %= irc.srv.len
- irc.srv[irc.focus].focus = 0
- ;;
- srv = irc.srv[irc.focus]
- if srv.chan.len > 0
- srv.chan[srv.focus].stale = false
- srv.chan[srv.focus].flagged = false
- break
- ;;
- ;;
-}
-
-const chanswitch = {irc, args- /* full, end, internal */
- var matches = [(-1, -1), (-1, -1), (-1, -1)]
- var srv
-
- if args.len != 1
- status(irc, irc.self, "invalid arguments: {j= }", args)- else
- irc.chandirty = true
- irc.cmddirty = true
- match matchrank(irc.self.name, args[0])
- | `std.None: /* ok */
- | `std.Some rank: matches[rank] = (0, -1)
- ;;
- for var i = 0; i < irc.srv.len; i++
- srv = irc.srv[i]
- for var j = 0; j < srv.chan.len; j++
- match matchrank(srv.chan[j].name, args[0])
- | `std.None: /* ok */
- | `std.Some rank:
- matches[rank] = (j, i)
- ;;
- ;;
- ;;
-
- for (chanidx, srvidx) : matches[:]
- if chanidx < 0
- continue
- ;;
- irc.focus = srvidx
- if srvidx >= 0
- irc.srv[srvidx].focus = chanidx
- irc.srv[srvidx].chan[chanidx].stale = false
- irc.srv[srvidx].chan[chanidx].flagged = false
- ;;
- break
- ;;
- ;;
-}
-
-const matchrank = {str, name- match std.strfind(str, name)
- | `std.None:
- -> `std.None
- | `std.Some idx:
- match (idx, idx + name.len == str.len)
- | (0, true): -> `std.Some 0
- | (_, true): -> `std.Some 1
- | (0, false): -> `std.Some 1
- | (_, _): -> `std.Some 2
- ;;
- ;;
-}
-
-const quit = {irc, args- if args.len != 0
- status(irc, irc.self, "invalid argument for quit: {j= }", args)- -> void
- ;;
- termdraw.free(irc.term)
- std.exit(0)
-}
-
-const help = {irc, args- status(irc, irc.self, "irc.myr commands")
- status(irc, irc.self, "\t/connect dialstr: connect to server")
- status(irc, irc.self, "\t/help [cmd...]: get help [on cmd...]")
- status(irc, irc.self, "\t/join chan [srv]: join channel on server")
- status(irc, irc.self, "\t/leave [chan]: leave current channel")
- status(irc, irc.self, "\t/chan name: switch to channnel 'name'")
- status(irc, irc.self, "\t/win name: switch to channnel 'name'")
- status(irc, irc.self, "\t/names: list nicks in current channel")
- status(irc, irc.self, "\t/quit: exit irc.myr")
-}
-
-const send = {irc, srv, fmt, args- var s, ap
-
- ap = std.vastart(&args)
- s = std.fmtv(fmt, &ap)
- if !writeall(srv.fd, s)
- closed(irc, srv, "failed writing")
- ;;
- std.slfree(s)
-}
-
-const message = {irc, msg- var c
-
- match cursrv(irc)
- | `std.Some s:
- c = curchan(irc)
- if c == irc.self
- status(irc, irc.self, "can't send to status channel")
- else
- send(irc, s, "PRIVMSG {} :{}\r\n", c.name, msg)- chanmsg(irc, s, c, s.nick, msg)
- ;;
- | `std.None:
- status(irc, irc.self, "no connected server")
- ;;
-}
-
-const action = {irc, msg- var b, buf : byte[512]
- var c
-
- match cursrv(irc)
- | `std.Some s:
- b = std.bfmt(buf[:], "{j= }", msg)- c = curchan(irc)
- puthist(irc, c, (std.now(), `Act (s.nick, std.sldup(b))))
- send(irc, s, "PRIVMSG {} :\x01ACTION {}\x01\r\n", c.name, b)- | `std.None:
- status(irc, irc.self, "no connected server")
- ;;
-}
-
-const privmsg = {irc, msg- var srv, chan, txt
-
- if msg.len <= 1
- status(irc, irc.self, "missing message in /msg")
- -> void
- elif msg.len > 3 && std.eq(msg[1], "-srv")
- match findsrv(irc, msg[2])
- | `std.Some s: srv = s
- | `std.None: -> status(irc, irc.self, "can't message: no server '{}", msg[1])- ;;
- txt = std.fmt("{j= }", msg[3:])- else
- match cursrv(irc)
- | `std.Some s: srv = s
- | `std.None: -> status(irc, irc.self, "cant message: no server\n")
- ;;
- txt = std.fmt("{j= }", msg[1:])- ;;
- chan = name2chan(irc, srv, msg[0])
- send(irc, srv, "PRIVMSG {} :{}\r\n", chan.name, txt)- chanmsg(irc, srv, chan, srv.nick, txt)
- std.slfree(txt)
-}
-
-const parse = {msg- var w, hdr, cmd, args
-
- match std.charstep(msg)
- | (':', tl): (hdr, msg) = word(tl)- | (_, _): hdr = ""
- ;;
- (cmd, msg) = word(msg)
-
- args = [][:]
- msg = std.strfstrip(msg)
- while msg.len != 0
- match std.charstep(msg)
- | (':', tl):- std.slpush(&args, tl)
- break
- | _:
- (w, msg) = word(msg)
- std.slpush(&args, w)
- ;;
- ;;
-
- -> (hdr, cmd, args)
-}
-
-const word = {s- for var i = 0; i < s.len; i++
- if std.isspace(std.decode(s[i:]))
- -> (s[:i], std.strfstrip(s[i:]))
- ;;
- ;;
- -> (s, "")
-
-}
-
const terminput = {irc, chanvar ln
@@ -958,6 +290,7 @@
elif matches.len > 1
status(irc, chan, "completions: {j= }", matches);;
+ std.slfree(matches)
std.slfree(pfx)
}
@@ -974,436 +307,3 @@
irc.chandirty = true
}
-const redraw = {irc- var c, x, y, w, h
-
- if irc.term.width < 10 || irc.term.height < 5
- -> void
- ;;
-
- x = 0
- y = 0
- w = irc.term.width
- h = irc.term.height
- c = curchan(irc)
- if irc.chandirty
- drawbanner(irc, 0, 0, w, 1, c)
- drawtext(irc, 0, 1, w, h - 2, c)
- drawlist(irc, 0, h - 2, w, h - 1, c)
- irc.chandirty = false
- ;;
- if irc.cmddirty
- drawinput(irc, 0, h - 1, w, h, c)
- irc.cmddirty = false
- ;;
- termdraw.flush(irc.term)
-}
-
-const drawbanner = {irc, x0, y0, x1, y1, c- var t
-
- t = irc.term
- termdraw.setbg(t, termdraw.Blue)
- termdraw.clear(t, x0, y0, x1, y1)
- termdraw.move(t, x0, y0)
- termdraw.put(t, "{}", c.topic)-}
-
-const drawtext = {irc, x0, y0, x1, y1, c- var height, margin, width, off
- var t, dx, dy
- var count
- var x, y
-
- t = irc.term
- termdraw.setbg(t, termdraw.Default)
- termdraw.clear(t, x0, y0, x1, y1)
- termdraw.move(t, x0, y0)
-
- dx = x1 - x0
- dy = y1 - y0
- count = 0
- height = 0
- width = dx
- off = 0
- /* nothing worth drawing... */
- if dx <= c.gutter
- -> void
- ;;
- for (tm, ent) : iter.byreverse(c.hist[:c.hist.len - c.scroll])
- margin = 0
- match ent
- | `Msg (m, ln):
- margin = strwidth(x0, c.gutter, " | ") + c.gutter
- width = strwidth(x0, margin, ln)
- | `Act (m, ln):
- margin = strwidth(x0, 0, " *")
- width = strwidth(x0, margin, m)
- width += strwidth(x0, margin, " ")
- width += strwidth(x0, margin, ln)
- | `Join user:
- margin = strwidth(x0, 0, "#joined: ")
- width = strwidth(x0, margin, user)
- | `Part user:
- margin = strwidth(x0, 0, "#parted: ")
- width = strwidth(x0, margin, user)
- | `Status msg:
- margin = strwidth(x0, 0, "! ")
- width = strwidth(x0, margin, msg)
- ;;
- /* no point in drawing if all we draw is gutter */
- if dx - margin <= 0
- -> void
- ;;
- height++
- if width > 0
- height += width / (dx - margin)
- ;;
- count++
- if height == dy
- break
- elif height > dy
- off = (dx - margin)*(height - dy)
- break
- ;;
- ;;
-
- x = x0
- y = y0
- for (tm, h) : c.hist[c.hist.len - c.scroll - count:]
- match h
- | `Msg (m, ln):
- termdraw.setattr(t, termdraw.Bold)
- x = std.clamp(x0 + c.gutter - strwidth(x0, 0, m), 0, dx)
- termdraw.move(t, x0 + c.gutter, y)
- (x, y) = draw(t, m, x, y, x1, y1)
- (x, y) = draw(t, " | ", x, y, x1, y1)
- termdraw.setattr(t, lineattr(irc, ln[off:]))
- (x, y) = draw(t, ln[off:], x, y, x1, y1)
- termdraw.setattr(t, termdraw.Normal)
- | `Act (m, ln):
- termdraw.setattr(t, termdraw.Bold)
- (x, y) = draw(t, " *", x0, y, x1, y1)
- (x, y) = draw(t, m, x, y, x1, y1)
- termdraw.setattr(t, lineattr(irc, ln[off:]))
- (x, y) = draw(t, " ", x, y, x1, y1)
- (x, y) = draw(t, ln[off:], x, y, x1, y1)
- termdraw.setattr(t, termdraw.Normal)
- | `Join user:
- (x, y) = draw(t, "#joined ", x0, y, x1, y1)
- (x, y) = draw(t, user[off:], x, y, x1, y1)
- | `Part user:
- (x, y) = draw(t, "#parted ", x0, y, x1, y1)
- (x, y) = draw(t, user[off:], x, y, x1, y1)
- | `Status msg:
- (x, y) = draw(t, "! ", x0, y, x1, y1)
- (x, y) = draw(t, msg[off:], x, y, x1, y1)
- ;;
- y++
- off = 0
- if y >= y1
- break
- ;;
- ;;
-}
-
-const strwidth = {x0, margin, str- var x
-
- x = x0 + margin
- for c : std.bychar(str)
- match c
- | '\t': x = (x / 8 + 1) * 8
- | _: x += (std.cellwidth(c) : int)
- ;;
- ;;
- -> x - (x0 + margin)
-}
-
-const draw = {t, msg, x0, y0, x1, y1- var x, y
-
- x = x0
- y = y0
- for l : std.bychar(msg)
- match l
- | '\t':
- x = (x / 8 + 1)*8
- | '\n':
- termdraw.put(t, "\\n")
- x += 2
- | chr:
- if x < x1
- termdraw.move(t, x, y)
- termdraw.putc(t, chr)
- x++
- ;;
- ;;
- if x >= x1
- x = x0
- y++
- ;;
- ;;
- -> (x, y)
-}
-
-const drawlist = {irc, x0, y0, x1, y1, c- var t
-
- t = irc.term
- termdraw.setbg(t, termdraw.Blue)
- termdraw.clear(t, x0, y0, x1, y1)
- termdraw.move(t, x0, y0)
- termdraw.putc(t, '|')
- termdraw.put(t, "[{}]", irc.self.name)- for s : irc.srv
- for ch : s.chan
- if ch.flagged
- termdraw.setattr(t, termdraw.Bold)
- termdraw.put(t, "[@{}]", ch.name)- termdraw.setattr(t, termdraw.Normal)
- elif ch.stale
- termdraw.put(t, "[*{}]", ch.name)- else
- termdraw.put(t, "[{}]", ch.name)- ;;
- ;;
- termdraw.setattr(t, termdraw.Normal)
- termdraw.putc(t, '|')
- ;;
-}
-
-const drawinput = {irc, x0, y0, x1, y1, c- var t, chan, ln, cx, dx
-
- dx = x1 - x0
- t = irc.term
- chan = std.fmt("[{}] ", c.name)- ln = inputstr(irc, chan, dx)
- cx = x0 + (std.strcellwidth(chan) + irc.off : int)
- if cx > dx
- cx = dx
- ;;
- termdraw.setbg(t, termdraw.Default)
- termdraw.move(t, x0, y0)
- termdraw.clear(t, x0, y0, x1, y1)
- termdraw.put(t, "{}{}", chan, ln)- termdraw.cursorpos(t, cx, y0)
- std.slfree(chan)
- std.slfree(ln)
-}
-
-const lineattr = {irc, ln- match cursrv(irc)
- | `std.None: -> termdraw.Normal
- | `std.Some srv:
- match std.strfind(ln, srv.nick)
- | `std.Some _: -> termdraw.Italic
- | `std.None: -> termdraw.Normal
- ;;
- ;;
-}
-
-const inputstr = {irc, chan, dx- var w, s, o
- var start, end
-
- s = irc.cmd
- o = (irc.off : int)
- dx -= strwidth(0, 0, chan)
- w = 0
- start = 0
- end = 0
- for var i = 0; i < s.len; i++
- w += std.cellwidth(s[i])
- if w >= dx
- break
- ;;
- end++
- ;;
- while end < s.len && irc.off >= (w : std.size)
- start++
- while std.cellwidth(s[end]) == 0
- start++
- ;;
- w += std.cellwidth(s[end])
- end++
- while end < s.len && std.cellwidth(s[end]) == 0
- end++
- ;;
- ;;
- -> getstr(s[start:end])
-}
-
-const srvmsg = {irc, args- if args.len >= 2
- status(irc, irc.self, "{}", args[1])- ;;
-}
-
-const status = {irc, chan, fmt, args : ...- var s, ap
-
- ap = std.vastart(&args)
- s = std.fmtv(fmt, &ap)
- puthist(irc, chan, (std.now(), `Status s))
-}
-
-const curchan = {irc- var s
-
- if irc.focus < 0 || irc.focus >= irc.srv.len
- -> irc.self
- else
- s = irc.srv[irc.focus]
- if s.focus < 0 || s.focus >= s.chan.len
- -> irc.self
- ;;
- -> s.chan[s.focus]
- ;;
-}
-
-const cursrv = {irc- if irc.focus < 0
- -> `std.None
- else
- -> `std.Some irc.srv[irc.focus]
- ;;
-}
-
-const findsrv = {irc, name- var idx
-
- idx = -1
- for var i = 0; i < irc.srv.len; i++
- match std.strfind(irc.srv[i].ds, name)
- | `std.Some o:
- if idx != -1
- status(irc, irc.self, "ambiguous server name {}", name)- -> `std.None
- else
- idx = i
- ;;
- | `std.None:
- ;;
- ;;
- if idx != -1
- -> `std.Some irc.srv[idx]
- else
- -> `std.None
- ;;
-}
-
-const poll = {irc- var pfd, fd, start, i
- var out
-
- fd = -1
- pfd = [][:]
- std.slpush(&pfd, [
- .fd=(std.In : sys.fd),
- .events=sys.Pollin,
- .revents=0,
- ])
- for s : irc.srv
- std.slpush(&pfd, [
- .fd=(s.fd : sys.fd),
- .events=sys.Pollin,
- .revents=0,
- ])
- ;;
- if sys.poll(pfd, -1) >= 0
- /* randomize so that we try to cover all fds evenly */
- start = std.rand(0, pfd.len)
- for var j = 0; j != pfd.len; j++
- i = (j + start) % pfd.len
- if pfd[i].revents & sys.Pollin != 0
- fd = (pfd[i].fd : std.fd)
- out = false
- ;;
- ;;
- ;;
- std.slfree(pfd)
-
- -> fd
-}
-
-const ctcpmsg = {irc, srv, chan, nick, msg- var n
-
- match word(msg)
- | ("ACTION", tl):- n = std.sldup(displayname(nick))
- puthist(irc, chan, (std.now(), `Act (n, std.sldup(tl))))
- | ("PING", tl): - send(irc, srv, "PRIVMSG {} :\x01PING {}\x01\r\n", nick, tl)- | ("TIME", tl):- send(irc, srv, "PRIVMSG {} :\x01TIME {}\x01\r\n", nick, date.utcnow())- | ("VERSION", tl):- send(irc, srv, "PRIVMSG {} :\x01VERSION irc.myr\x01\r\n", nick)- | ("CLIENTINFO", tl):- send(irc, srv,
- "PRIVMSG {} :\x01ACTION PING TIME VERSION CLIENTINFO\x01\r\n", nick)- | (unknown, tl):
- status(irc, irc.self, "unknown CTCP message {}\n", msg)- ;;
-}
-
-const chanmsg = {irc, srv, chan, nick, msg - nick = std.sldup(displayname(nick))
- puthist(irc, chan, (std.now(), `Msg (nick, std.fmt("{e}", msg))))- match std.strfind(msg, srv.nick)
- | `std.None: /* nothing */
- | `std.Some _:
- if curchan(irc) != chan
- chan.flagged = true
- ;;
- ;;
-}
-
-const puthist = {irc, chan, entry- var tm, d, contents
-
- std.slpush(&chan.hist, entry)
- (tm, contents) = entry
- if chan.log != -1
- d = date.mkinstant(tm, "local")
- match contents
- | `Msg (u, ln):
- std.fput(chan.log, "{} {} | {}\n", d, u, ln)- if chan != curchan(irc)
- chan.stale = true
- ;;
- | _:
- /* nothing */
- ;;
- ;;
- if chan == curchan(irc)
- irc.chandirty = true
- ;;
- if chan.scroll != 0
- chan.scroll++
- ;;
-}
-
-const writeall = {fd, buf- var o
-
- o = 0
- while o != buf.len
- match std.write(fd, buf[o:])
- | `std.Err e: -> false
- | `std.Ok 0: break
- | `std.Ok n: o += n
- ;;
- ;;
- -> o == buf.len
-}
-
-const uppercase = {cmd- for var i = 0; i < cmd.len; i++
- if cmd[i] >= ('a' : byte) && cmd[i] <= ('z' : byte)- cmd[i] = cmd[i] - ('a' : byte) + ('A' : byte)- ;;
- ;;
-}
--- /dev/null
+++ b/types.myr
@@ -1,0 +1,56 @@
+use std
+use termdraw
+
+pkg =
+ type irc = struct
+ srv : server#[:]
+ logdir : byte[:]
+ self : chan#
+ focus : std.size
+ term : termdraw.term#
+ user : byte[:]
+ nick : byte[:]
+
+ /* input editing */
+ off : std.size
+ cmd : char[:]
+ yank : char[:]
+
+ chandirty : bool
+ cmddirty : bool
+ ;;
+
+ type server = struct
+ id : std.size
+ fd : std.fd
+ user : byte[:]
+ nick : byte[:]
+ ds : byte[:]
+ buf : byte[512]
+ nbuf : std.size
+ chan : chan#[:]
+ focus : std.size
+ death : std.time
+ ;;
+
+ type chan = struct
+ log : std.fd
+ hist : (std.time, hist)[:]
+ histoff : std.size /* scrolling */
+ name : byte[:]
+ topic : byte[:]
+ users : byte[:][:]
+ stale : bool
+ flagged : bool
+ gutter : int
+ scroll : int
+ ;;
+
+ type hist = union
+ `Msg (byte[:], byte[:])
+ `Act (byte[:], byte[:])
+ `Join byte[:]
+ `Part byte[:]
+ `Status byte[:]
+ ;;
+;;
--
⑨