ref: 8d6c1b01c6e17894cb1054d954d18272e71e5aae
dir: /main.myr/
use std
use sys
use bio
use date
use iter
use termdraw
use fileutil /* for homedir() */
use "types"
use "draw"
use "irc"
const main = {args
var irc : irc#
var home, rcfile, logdir
var c
var fd
var cmd
std.nopipe()
home = fileutil.homedir()
rcfile = std.pathcat(home, ".ircrc")
logdir = std.pathcat(home, ".irclogs")
cmd = std.optparse(args, &[
.maxargs=-1,
.opts=[
[.opt='c', .arg="cfg",
.desc="use config file 'cfg'",
.dest=`std.Some &rcfile],
[.opt='l', .arg="log",
.desc="use log dir 'log'",
.dest=`std.Some &logdir],
][:]
])
irc = mkirc(rcfile, logdir)
while true
c = curchan(irc)
c.stale = false
c.flagged = false
redraw(irc)
fd = poll(irc)
/* we can get interrupted by sigwinch */
if fd <= 0
terminput(irc, c)
elif fd >= 0
io(irc, fd)
;;
ping(irc)
redial(irc)
;;
}
const ping = {irc
for s : irc.srv
if s.death == 0 && (std.now() - s.ping) > 60*std.Sec
send(irc, s, "PING irc.myr\r\n")
/* let's not flood */
s.ping = std.now()
;;
;;
}
const redial = {irc
var pingtime, droptime
for s : irc.srv
droptime = std.now() - s.death
pingtime = std.now() - s.ping
if s.death != 0 && droptime >= 60*std.Sec
status(irc, irc.self, "{}: connection dropped\n", s.ds)
elif pingtime >= 300*std.Sec
status(irc, irc.self, "{}: ping timed out\n", s.ds)
else
continue
;;
std.close(s.fd)
s.fd = -1
match std.dial(s.ds)
| `std.Ok fd:
s.death = 0
s.fd = fd
handshake(irc, s)
for c : irc.onconn
do(irc, c)
redraw(irc)
;;
for c : s.chan
send(irc, s, "JOIN {}\r\n", c.name)
redraw(irc)
std.usleep(std.Sec)
;;
| `std.Err e:
/* push back our next reconnection attempt */
s.death = std.now()
;;
;;
}
const poll = {irc
var pfd, fd, start, i
fd = -1
pfd = [][:]
std.slpush(&pfd, [
.fd=(std.In : sys.fd),
.events=sys.Pollin,
.revents=0,
])
for s : irc.srv
if s.fd == -1
continue
;;
std.slpush(&pfd, [
.fd=(s.fd : sys.fd),
.events=sys.Pollin,
.revents=0,
])
;;
if sys.poll(pfd, 60_000) >= 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|sys.Pollerr) != 0
fd = (pfd[i].fd : std.fd)
break
;;
;;
;;
std.slfree(pfd)
-> fd
}
const mkirc = {rcfile, logdir
var irc, term
var nick, user
term = termdraw.mk(std.In)
termdraw.raw(term)
termdraw.cursoron(term)
user = std.getenvv("user", std.getenvv("USER", "user"))
nick = user
irc = std.mk([
.srv = [][:],
.user = std.sldup(user),
.nick = std.sldup(nick),
.focus = -1,
.term = term,
.logdir = logdir,
.chandirty = true,
.cmddirty = true,
])
irc.self = mkchan(irc, "status", "status", "status")
match bio.open(rcfile, bio.Rd)
| `std.Err e: /* nothing */
| `std.Ok cfg:
for ln : bio.byline(cfg)
std.usleep(300*std.Msec);
do(irc, ln)
redraw(irc)
;;
status(irc, irc.self, "doing {}", irc.onconn)
for c : irc.onconn
std.usleep(300*std.Msec);
do(irc, c)
redraw(irc)
;;
;;
-> irc
}
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 do = {irc, ln
if ln.len == 0
-> void
;;
match std.charstep(std.strfstrip(ln))
| ('/', rest):
match std.charstep(ln)
| (' ', m): message(irc, m)
| (_, _): cmd(irc, ln)
;;
| _:
message(irc, ln)
;;
}
const terminput = {irc, chan
var ln
while true
match termdraw.poll(irc.term)
| `std.None:
break
| `std.Some `termdraw.Winsz:
irc.chandirty = true
irc.cmddirty = true
| `std.Some `termdraw.Kc '\t':
complete(irc, chan)
| `std.Some `termdraw.Kc '\n':
ln = getstr(irc.cmd)
do(irc, ln)
std.slfree(ln)
std.slfree(irc.cmd)
irc.cmd = [][:]
irc.off = 0
| `std.Some `termdraw.Ctrl 'n':
chancycle(irc, 1)
| `std.Some `termdraw.Ctrl 'p':
chancycle(irc, -1)
| `std.Some `termdraw.Ctrl 'l':
termdraw.cls(irc.term)
irc.chandirty = true
irc.cmddirty = true
| `std.Some `termdraw.Kup: scroll(irc, chan, 1)
| `std.Some `termdraw.Kdown: scroll(irc, chan, -1)
| `std.Some `termdraw.Kpgup: scroll(irc, chan, std.max(1, irc.term.height - 7))
| `std.Some `termdraw.Kpgdn: scroll(irc, chan, -std.max(1, irc.term.height - 7))
| `std.Some ev:
if !input(irc, ev)
puthist(irc, irc.self, (std.now(), `Status std.fmt("{}", ev)))
;;
;;
irc.cmddirty = true
redraw(irc)
;;
}
const input = {irc, ev
var b
match ev
| `termdraw.Khome: irc.off = 0
| `termdraw.Ctrl 'a': irc.off = 0
| `termdraw.Kend: irc.off = irc.cmd.len
| `termdraw.Ctrl 'e': irc.off = irc.cmd.len
| `termdraw.Kc c:
std.slput(&irc.cmd, irc.off, c)
irc.off++
| `termdraw.Kleft:
if irc.off > 0
irc.off--
;;
| `termdraw.Kright:
if irc.off < irc.cmd.len
irc.off++
;;
| `termdraw.Ctrl 'u':
std.slfree(irc.yank)
irc.yank = irc.cmd
irc.off = 0
irc.cmd = [][:]
| `termdraw.Ctrl 'y':
b = [][:]
std.sljoin(&b, irc.cmd[:irc.off])
std.sljoin(&b, irc.yank)
std.sljoin(&b, irc.cmd[irc.off:])
std.slfree(irc.cmd)
irc.cmd = b
irc.off += irc.yank.len
| `termdraw.Ctrl 'w':
deltext(irc, true)
deltext(irc, false)
| `termdraw.Kbksp:
if irc.off > 0
irc.off--
std.sldel(&irc.cmd, irc.off)
;;
| _:
-> false
;;
-> true
}
const deltext = {irc, isspace
while irc.off > 0
if isspace != std.isspace(irc.cmd[irc.off - 1])
break
;;
std.sldel(&irc.cmd, irc.off - 1)
irc.off--
;;
}
const complete = {irc, chan
var pfx, off, matches
off = irc.off
while off > 0
if std.isspace(irc.cmd[off - 1])
break
;;
off--
;;
pfx = getstr(irc.cmd[off:irc.off])
matches = [][:]
for var i = 0; i < chan.users.len; i++
if matchcompletion(chan.users[i], pfx)
std.slpush(&matches, chan.users[i])
;;
;;
if matches.len == 1
for c : std.bychar(matches[0][pfx.len:])
std.slput(&irc.cmd, irc.off, c)
irc.off++
;;
elif matches.len > 1
status(irc, chan, "completions: {j= }", matches)
;;
std.slfree(matches)
std.slfree(pfx)
}
const matchcompletion = {user, pfx
match (user[0] : char)
| '@': -> std.hasprefix(user[1:], pfx)
| '+': -> std.hasprefix(user[1:], pfx)
| _: -> std.hasprefix(user, pfx)
;;
}
const scroll = {irc, chan, delta
chan.scroll = std.clamp(chan.scroll + delta, 0, chan.hist.len)
irc.chandirty = true
}