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);
};