new object $help_ui: $user_interfaces; var $has_commands local = \ #[["@help", [["@help", "*", "@help <any>", 'help_cmd, #[[1, ['any, []]]]]]]]; var $has_commands shortcuts = #[["?*", ['help_cmd, ["?", 1]]]]; var $help_ui current = 1; var $help_ui history = [$help_coldcore]; var $help_ui index = 0; var $root created_on = 796268969; var $root fertile = 1; var $root flags = ['methods, 'code, 'fertile, 'variables, 'core]; var $root inited = 1; var $root managed = [$help_ui]; var $root manager = $help_ui; protected method ._back_help_node() { var pos; pos = current - 1; if (pos >= 1) { current = pos; return history[current]; } throw(~nonode, "You are at the start of your help node history, use \"??\" to list the history."); }; protected method ._forward_help_node() { var pos; pos = current + 1; if ((pos <= ($help_lib.history_cap())) && (pos <= (history.length()))) { current = pos; return history[current]; } throw(~nonode, "You are at the end of your help node history, use \"??\" to list the history."); }; protected method ._help_node_history() { var node, line; .tell("Help node history:"); for node in [1 .. history.length()] { line = " "; if (node == current) line = "=> "; catch any { .tell(line + ((history[node]).name())); } with { history = history.delete(node); .set_help_node(history[1]); .tell(line + ">> ERROR: INVALID NODE IN HISTORY <<"); } } }; protected method ._navigate_node_history() { arg str; var node, r, hist, match; while ((r = regexp(str, "^ *([<>]) *([^<>]*) *"))) { if (r[2]) { if ((r[1]) == "<") hist = (sublist(history, 1, current - 1).reverse()) + (sublist(history, current).reverse()); else hist = sublist(history, current + 1) + sublist(history, 1, current); r = r[2]; for node in (hist) { if (node.match_name(r)) { .set_help_node(node); match++; break; } } if (!match) throw(~nonode, ("There is no node \"" + r) + "\" in your history."); } else if ((r[1]) == "<") { (| .set_help_node(._back_help_node()) |); } else { (| .set_help_node(._forward_help_node()) |); } str = strsed(str, "^ *([<>]) *([^<>]*) *", ""); } return history[current]; }; protected method .clear_help_history() { (| clear_var('history) |); (| clear_var('current) |); }; public method .current_node() { return history[current]; }; public method .find_help_in_group() { arg str; var sibling, node; node = (.current_node()) || ($help_lib.default_node()); if (node.group()) { for sibling in (((node.parents())[1]).children()) { if ((sibling == node) || ((sibling.nolist()) || (sibling.holder()))) continue; if (sibling.match_name(str)) return sibling; } } throw(~nonode, ("Unable to find group node \"" + str) + "\"."); }; public method .find_help_in_index() { arg str; var indices, i, node, index, matches, len; // get from $help_lib to keep in a consistent prioritized order indices = $help_lib.indices(); // put the 'current' node at the end, and use it first if ((i = sender().help_index())) { indices = setremove(indices, i); indices = setadd(indices, i); } // loop through the indices backwards len = listlen(indices); matches = []; for i in [1 .. len] { i = (len - i) + 1; catch any { // return the first perfect match if ((node = (> (indices[i]).match_begin(str) <))) return node; } with { if (error() == ~ambig) matches += ((traceback()[1])[3]) || []; } } if (matches) throw(~ambig, "More than one match.", matches); throw(~nonode, ("Unable to find help on \"" + str) + "\"."); }; public method .find_help_in_links() { arg str; var links, node, index; links = ((| .current_node() |) || ($help_lib.default_node())).links(); for node in (links.keys()) { if (match_begin(node, str)) { node = links[node]; return node; } } throw(~nonode, ("Unable to find link \"" + str) + "\"."); }; public method .fmt_help_from() { arg from; var node, c, name; c = .task_connection(); for node in (from.traverse()) { name = (node.name()).word(1, "|"); c.write(["", name, "=" * strlen(name)]); if (!(node.holder())) c.write($parse_lib.filter_ctext(node.body(), #[['formatter, $flat_format]])); } }; protected method .help_cmd() { arg cmdstr, cmd, args; var o, opt, optval, way, node, links, i; (> .perms(caller(), 'command) <); o = #[["?", ["h?istory"]], ["<", ["b?ack"]], [">", ["f?orward"]], ["!", ["fix"]], ["#", ["l?inks"]], ["^", ["u?p"]]]; args = $parse_lib.opt(args, o.values()); opt = args[2]; args = args[1]; if (!opt) { if (!args) { if (cmd == "?") node = .current_node(); else node = $help_lib.default_node(); } else { args = args.join(); if ((args[1]) in (o.keys())) { opt = (o[args[1]])[1]; if ((args.length()) > 1) optval = args.subrange(2); } else if ((o = match_template(args, "* in *"))) { if (!(i = $help_index.match_children(o[3]))) return ("\"" + (o[3])) + "\" is not a help index."; if ((!(o[1])) || ((o[1]) == "#")) node = i; else if (!(node = (| i.match_begin(o[1]) |))) return ((("Unable to find help on \"" + (o[1])) + "\" in the ") + (i.name())) + " index."; } else { catch ~nonode, ~ambig { node = (> .parse_help_reference(args) <); } with { if (error() == ~ambig) return ([("Topic '" + args) + "' has multiple possibilities:", ""] + ((((traceback()[1])[3]).mmap('namef, 'ref)).prefix(" "))) + ["", "---"]; return (traceback()[1])[2]; } } } } else { // since all options override each other, just use the last one. optval = (opt[opt.length()])[4]; opt = (opt[opt.length()])[1]; } if (!node) { catch ~nonode { switch (opt) { case "u?p": o = ((.current_node()).parents())[1]; while ((o.is($help_node)) && (o.holder())) o = (o.parents())[1]; if ((!(o.is($help_node))) || (o.top_of_help_heirarchy())) return "You are at the top of this help node heirarchy."; node = o; case "h?istory": return ._help_node_history(); case "b?ack": if (!optval) optval = ""; node = (> ._navigate_node_history("<" + optval) <); case "f?orward": if (!optval) optval = ""; node = (> ._navigate_node_history(">" + optval) <); case "fix": .tell("Fixing your help history."); for node in (history) { if ((!valid(node)) || (!(node.has_ancestor($help_node)))) history = setremove(history, node); } current = listlen(history); return; case "l?inks": node = .current_node(); links = node.links(); if (!links) { .tell(("No links from " + (node.name())) + "."); } else { .tell(("Links from " + (node.name())) + ":"); .tell(map i in (links) to (strfmt("%30l %s", @i)).prefix(" ")); } if (node.group()) { links = ((node.parents())[1]).children(); links = filter o in (links) where ((!(o.nolist())) && (o != node)); if (!links) return ("No group nodes with " + (node.name())) + "."; .tell(("Group nodes with " + (node.name())) + ":"); .tell(map i in (links) to (strfmt("%30e %s", i.name(), i)).prefix(" ")); } return; } } with { return (traceback()[1])[2]; } } .set_help_node(node); .tell_help_node(node); }; public method .help_index() { return index; }; protected method .help_node_history() { return history; }; root method .init_help_ui() { history = [$help_lib.default_node()]; current = 1; }; protected method .last_visited() { (> .perms(sender()) <); return last_visited; }; protected method .parse_help_reference() { arg str; var node, cnode, current, indices, len, links, i, matches, m; if ((str[1]) == "$") { node = (| $object_lib.to_dbref(str) |); if (node && (node.help_node())) node = node.help_node(); else if ((!node) || (!(node.has_ancestor($help_node)))) throw(~nonode, ("\"" + str) + "\" is not a help node, and does not have a help node assigned to it."); return node; } if ((m = match_template(str, "*=*"))) { if (!(m[2])) throw(~nonode, ("Search in " + (m[1])) + " for nothing?"); else if (match_template(m[1], "g?roup")) return (> .find_help_in_group(m[2]) <); else if (match_template(m[1], "l?inks")) return (> .find_help_in_links(m[2]) <); else if (match_template(m[1], "i?ndex")) return (> .find_help_in_index(m[2]) <); if (!(i = $help_index.match_children(m[1]))) throw(~nonode, ("\"" + (m[1])) + "\" is not a help index."); if ((m[2]) == "#") return i; if (!(node = (| i.match_begin(m[2]) |))) throw(~nonode, ((("Unable to find help on \"" + (m[2])) + "\" in the ") + (i.name())) + " index."); return node; } if ((m = regexp(str, " *([a-z][0-9a-z_]+)\( *\)"))) { i = "help_func_" + (m[1]); if ((node = (| lookup(tosym(i)) |))) return node; else throw(~nonode, ("Unable to find help on function " + (m[1])) + "()"); } return (| .find_help_in_links(str) |) || ((| .find_help_in_group(str) |) || (> .find_help_in_index(str) <)); }; protected method .reset_help_history() { history = [$help_coldcore]; current = 1; }; protected method .set_help_node() { arg node, @navhist; var cur; if (node.index()) index = node.index(); cur = history[current]; if (node in history) { if ((history[current]) == node) return; if (navhist) return (current = node in history); else history = setremove(history, node); } else if (listlen(history) >= ($help_lib.history_cap())) { history = sublist(history, 1, $help_lib.history_cap()); } current = cur in history; if (navhist) { if (current) history = insert(history, current + 1, node); } else { history = (history ? sublist(history, 1, current) : []) + [node]; } current = node in history; }; protected method .tell_help_node() { arg node; var out, len, clen, line, n, name, changed, sibs, end, start, flen; // len = .linelen() % 2 ? .linelen() - 1 : .linelen(); len = .linelen(); name = node.node_name(); while (strlen(name) >= (len - 10)) { name = substr(name, (":" in name) + 1); changed++; } if (changed) name = ".." + name; line = strfmt("%*{-}c", len, (" " + name) + " "); .tell(line); .ptell(node.body(), #[['type, 'help], ['ctype, 'ctext]]); if (node.group()) { line = (start = (end = "")); sibs = filter n in (((node.parents())[1]).children()) where (!(n.nolist())); flen = (((strlen((sibs.mmap('small_name)).join()) + (listlen(sibs) * 3)) + 3) + strlen(end)) + strlen(start); while (flen > len) { if ((listlen(sibs) / 2) >= (node in sibs)) { sibs = sublist(sibs, 1, listlen(sibs) - 1); if (!end) end = ".. "; } else { sibs = sublist(sibs, 2); if (!start) start = ".. "; } flen = (((strlen((sibs.mmap('small_name)).join()) + (listlen(sibs) * 3)) + 3) + strlen(end)) + strlen(start); } if (sibs) { for n in (sibs) { if (n == node) line += ". "; else if (n.holder()) line += (((n.name()).word(1, "|")).word(1)) + " "; else line += ("[" + (((n.name()).word(1, "|")).word(1))) + "] "; } line = (((" " + start) + line) + end).center(len, "-", 'both); } else { line = "-" * len; } } else { line = "-" * len; } .tell(["", line]); }; root method .uninit_help_ui() { clear_var('history); clear_var('current); clear_var('index); };