Record types ============ conn - network: file-safe str - sock: socket - rdbuf: str - wrbuf: str - addrs: non-empty list of (host: str, port: int) (first entry is current) - nick: str - password: str | None - registered: bool - channels: dict of (channel: casemapped str) => (set of nick: casemapped str) - casemapper: function of (str -> casemapped str) - reconn_delay: int - count: int (number of times connected) - ping_ts: float (monotonic time of last sent ping) - pong_ts: float (monotonic time of last received pong) buf - name: str - parent: buf - title: str - vscroll: int - lines: list of str - num_read: int - at_end: bool buflist (singleton) - vscroll: int - width: int - selection: buf - cursor: buf - last: buf prompt (singleton) - chars: list of str - cursor: int - hscroll: int flags (singleton) - refresh - redraw - buflist_draw - buf_draw - prompt_draw - status_draw - quit - buflist - prompt - ping_draw Enumerations ============ kbd_state: ks_start ks_cx ks_esc ks_cseq ks_cs_intermed Variant types ============= message: (args all str unless noted) - m_privmsg(sender, msg) - m_notice(sender, msg) - m_join(sender, chan) - m_part(sender, chan, msg) - m_quit(sender, msg) - m_nick(sender, nick) - m_kick(sender, chan, name, msg) - m_kicked(sender, chan, msg) - m_topic(sender, topic: str | None) - m_chantopic(sender, chan, topic: str | None) - m_mode(sender, modes) - m_chanmode(sender, chan, modes) - m_names(sender, chan, names) - m_endnames(sender, chan) - m_error(sender, msg) - m_client(msg) - m_server(sender, msg) Quasiconstants ============== self_pipe_rd: int self_pipe_wr: int Global state ============ cur_buf: buf scr_height: int scr_width: int mono_last: float mono_offset: float out_buf: bytearray kbd_accum: bytearray kbd_state: function Collections =========== commands: (name: str) => function buffers: non-empty list of buf buffer_index: (name: str, parent_name: str) => buf opening_conns: (fileno: int) => conn open_conns: (fileno: int) => conn network_conns: (network: str) => conn : all enabled networks, whether TCP alive or not schedule: min-heap of (time: float) => thunk Functions ========= Excluding record constructors, accessors and basic mutators. Startup only: command(name: str, min_args=0: int, max_args=None: int | None, extended_arg=False: bool)(function) -> function check_command_dicts(keymap) make_casemapper(int) -> function of (str -> casemapped str) Boring: set_nonblock Pure functions: format_time(time_tuple) -> str is_ctrl(chr) -> bool ctrl(chr) -> chr is_meta(chr) -> bool meta(chr) -> chr variant_name(val) -> str variant_args(val) -> list matcher(vtype, cases: iterable of (constructor, receiver: function of (*args -> x))) -> function of (val: vtype -> x) sequence(*thunks) -> thunk flatten(iterable of iterable) -> iterable char_range(pair) -> str partition(list, pred) -> (left: list, right: list) split_pair(str, sep=' ': str) -> [str, str] make_encoder(function of chr -> str) -> function of (str -> str) asciify(str) -> str fs_encode(str) -> str casemap_ascii(str) -> str clip(min: comparable, max: comparable, comparable) -> comparable clip_to(list, int) -> int get_clipped(list, int) -> element of list clip_str(str, width: int) -> str pad_or_clip_str(str, width: int, pad=' ': chr) -> str wrap(line, width, indent=0) -> list of str is_digit(chr) -> bool parse_address(addr) -> (host: str, port: int) / ValueError format_address((host: str, port: int)) -> str int_of_bytes(str) -> int heap_peek(heap: list) -> (key, value) / IndexError safe_filename(name) -> bool config_lines(text) -> non-empty list of non-empty str | None format_buf_msg(val: message) -> str render_lines(lines: list of str, width: int, start: int, row_limit: int) -> (list of list, int) build_msg(prefix, cmd, params) -> str max_param_len(cmd, prefix=None) -> int parse_msg(msg) -> (prefix: str | None, cmd: str, params: list of str) / ProtocolError is_chan(str) -> bool valid_chan(str) -> bool valid_nick(str) -> bool valid_password(str) -> bool conn_nick_lc(conn) -> casemapped str sender_nick(str) -> str arg2 arg3 empty2 lterr / TypeError format_log_msg(val: message except (m_chantopic | m_mode | m_client)) -> str Pure I/O: write_all(blocking fd, str|bytearray) / EnvironmentError read_all(nonblocking fd) -> str / EOFError, EnvironmentError : may return empty Pure drawing: buf_draw(buf) prompt_draw draw_status(y: int) buflist_draw buflist_vline_draw place_cursor refresh_if_needed "Queries": find_buf(buf) -> int : find buf's index in buffers list #buf_network(buf) -> str buf_conn(buf) -> conn / CommandError buf_registered_conn(buf) -> conn / CommandError get_config(key, paths=(()), default=None) -> str | default Side effects on data: write_out(str) : stores for write to terminal flush_out : sends full terminal output buffer variant(vtype, name: str, nargs: int) -> constructor: function of (*args -> (tag, args)) rand_int(int) -> int heap_insert(heap: list, key: comparable, value) heap_extract(heap: list) -> (key, value) / IndexError run_command(line: str) -> * / CommandError buf_log_msg(buf, m: message) buf_privmsg(buf, msg: str) : buf_parent ; buf_registered_conn ; conn_privmsg / CommandError check_buf_at_end : buf_set_at_end(cur_buf) | buf_clr_at_end(cur_buf) is_child_of(buf) -> function of (buf -> bool) sort_buffers : sorts buffers & generates buffer_index get_buf(name, parent_name) -> buf : buffer_index lookup but creates if not found close_buf(buf) prompt_insert(chr) prompt_delete prompt_backspace prompt_submit info(msg: str, buf=None) : buf_log_msg to buf or buffers[0] error(msg_or_exc: str | Exception, buf=None) : buf_log_msg to buf or buffers[0] kaccum(str) kaccept(str) ktrans(kbd_state) ks_start(chr) ks_cx(chr) ks_esc(chr) ks_cseq(chr) ks_cs_intermed(chr) conn_run_in(conn, seconds: float, method: function of (conn), run_if_down=False) : run_in conn_log_msg(conn, venue: casemapped str | None, m: message) : get_buf ; buf_log_msg ; file_log_msg conn_info(conn, str) : uses conn_log_msg conn_error(conn, str) : uses conn_log_msg conn_start(conn) : starts connecting; bumps count; rotates addrs; adds to opening_conns and network_conns conn_write(conn, str) : stores for nonblocking write by main loop conn_send(conn, cmd: str, params: list of str, prefix=None: str) : build_msg ;conn_write (TODO check msg well-formedness) conn_handle_connected(conn) : if successful, moves c from opening_conns to open_conns & sends login conn_close(conn) : shuts down and removes from open_conns conn_handle_data(conn, data: str) : does input buffering; extracts messages and calls... conn_handle_msg(conn, msg: str) : mega-function for handling IRC commands conn_join(conn, chan: str, key=None) : conn_info ; conn_send conn_privmsg(conn, target: str, msg: str) : conn_log_msg ; conn_send conn_ping(conn) conn_timeout(conn) conn_reg_timeout(conn) file_log_msg(network: file-safe str, venue: casemapped str | None, m: message) handle_resize mono_time -> float run_in(seconds, thunk) Slash commands: quit_cmd connect_cmd disconnect_cmd join_cmd kick_cmd mode_cmd nick_cmd part_cmd send_cmd Lifecycle: main crash_handler MVP: finish commands close/close-net debug prefix case sensitivity in conn_handle_msg? TODO: nick fallback buflist vscroll modes date changes prompt history, kill ring tab completion (but how to focus buf/prompt?) username, realname logging bold nicks (generally: formatted wrap) search in scrollback channel key (config, persist across reconnect) buffer cleanup by category help scripting self-ping to find user@host to calc max privmsg length Proliferation of casemapping is bound to be buggy. Recognize WHOIS/WHOWAS/LIST responses bracketed paste Possible cleanups: more use of lambda/sequence for trivial functions rename buf* to wind* to match manual's terminology (nobody but emacs uses "buffer" to mean "window"...) move cur_buf global to a buflist attribute use the new scheduler for schedule_*; use delayed redraw to perform well under message floods