diff --git a/.gitattributes b/.gitattributes index 6387d6aee78f25ab200efb7a8fe9d7f7f7d45a60..5bd3ff54cffa2a644ddc01c397d76f394ab67679 100644 --- a/.gitattributes +++ b/.gitattributes @@ -74,6 +74,8 @@ testfont binary /lib/modules/Sql.pmod/sql.pike foreign_ident /lib/modules/Sql.pmod/sql_result.pike foreign_ident /lib/modules/Stdio.pmod foreign_ident +/lib/modules/Stdio.pmod/Readline.pmod foreign_ident +/lib/modules/Stdio.pmod/Terminfo.pmod foreign_ident /lib/modules/Stdio.pmod/module.pmod foreign_ident /lib/modules/Yabu.pmod/module.pmod foreign_ident /lib/modules/error.pmod foreign_ident diff --git a/lib/modules/Stdio.pmod/Readline.pmod b/lib/modules/Stdio.pmod/Readline.pmod new file mode 100644 index 0000000000000000000000000000000000000000..e8d9069c4dbcef5c3c02d7c2eb1f097d8f6f44a9 --- /dev/null +++ b/lib/modules/Stdio.pmod/Readline.pmod @@ -0,0 +1,964 @@ +// $Id: Readline.pmod,v 1.1 1999/03/13 01:12:37 marcus Exp $ + +class OutputController +{ + static private object outfd, term; + static private int xpos = 0, columns = 0; + + void check_columns() + { + catch { + int c = outfd->tcgetattr()->columns; + if(c) + columns = c; + }; + if(!columns) + columns = term->tgetnum("co") || 80; + } + + int get_number_of_columns() + { + return columns; + } + + static string escapify(string s) + { + for(int i=0; i<strlen(s); i++) + if(s[i]<' ') + s = s[..i-1]+sprintf("^%c", s[i]+'@')+s[i+1..]; + else if(s[i]==127) + s = s[..i-1]+"^?"+s[i+1..]; + else if(s[i]>=128 && s[i]<160) + s = s[..i-1]+sprintf("~%c", s[i]-128+'@')+s[i+1..]; + return s; + } + + static int width(string s) + { + return strlen(s); + } + + static int escapified_width(string s) + { + return width(escapify(s)); + } + + void low_write(string s) + { + int n = width(s); + if(!n) + return; + while(xpos+n>=columns) { + int l = columns-xpos; + outfd->write(s[..l-1]); + s = s[l..]; + n -= l; + xpos = 0; + if(!term->tgetflag("am")) + outfd->write("\r\n"); + } + if(xpos==0 && term->tgetflag("am")) + outfd->write(" "+term->put("le")); + if(n>0) { + outfd->write(s); + xpos += n; + } + } + + void write(string s) + { + low_write(escapify(s)); + } + + void low_move_downward(int n) + { + if(n<=0) + return; + outfd->write(term->put("DO", n) || (term->put("do")||"")*n); + } + + void low_move_upward(int n) + { + if(n<=0) + return; + outfd->write(term->put("UP", n) || (term->put("up")||"")*n); + } + + void low_move_forward(int n) + { + if(n<=0) + return; + if(xpos+n<columns) { + outfd->write(term->put("RI", n) || (term->put("ri")||"")*n); + xpos += n; + } else { + int l = (xpos+n)/columns; + low_move_downward(l); + n -= l*columns; + if(n<0) + low_move_backward(-n); + else if(n>0) + low_move_forward(n); + } + } + + void low_move_backward(int n) + { + if(n<=0) + return; + if(xpos-n>=0) { + outfd->write(term->put("LE", n) || (term->put("le")||"")*n); + xpos -= n; + } else { + int l = 1+(n-xpos-1)/columns; + low_move_upward(l); + n -= l*columns; + if(n<0) + low_move_forward(-n); + else if(n>0) + low_move_backward(n); + } + } + + void low_erase(int n) + { + string e = term->put("ec", n); + if (e) + outfd->write(e); + else + { + low_write(" "*n); + low_move_backward(n); + } + } + + void move_forward(string s) + { + low_move_forward(escapified_width(s)); + } + + void move_backward(string s) + { + low_move_backward(escapified_width(s)); + } + + void erase(string s) + { + low_erase(escapified_width(s)); + } + + void newline() + { + outfd->write("\r\n"); + xpos = 0; + } + + void bol() + { + outfd->write("\r"); + xpos = 0; + } + + void clear(int|void partial) + { + string s; + if(!partial && (s = term->put("cl"))) { + outfd->write(s); + xpos = 0; + return; + } + if(!partial) { + outfd->write(term->put("ho")||term->put("cm", 0, 0)||"\f"); + xpos = 0; + } + outfd->write(term->put("cd")||""); + } + + void create(object|void _outfd, object|string|void _term) + { + outfd = _outfd || Stdio.File("stdout"); + term = objectp(_term)? _term : .Terminfo.getTerm(_term); + check_columns(); + } + +} + +class InputController +{ + static private object infd, term; + static private int enabled = -1; + static private function(:int) close_callback = 0; + static private string prefix=""; + static private mapping(int:function|mapping(string:function)) bindings=([]); + static private function grab_binding = 0; + static private mapping oldattrs = 0; + + void destroy() + { + catch{ infd->set_blocking(); }; + catch{ if(oldattrs) infd->tcsetattr(oldattrs); }; + catch{ infd->tcsetattr((["ECHO":1,"ICANON":1])); }; + } + + static private string process_input(string s) + { + int i; + + for (i=0; i<sizeof(s); i++) + { + if (!enabled) + return s[i..]; + function|mapping(string:function) b = grab_binding || bindings[s[i]]; + grab_binding = 0; + if (!b) + /* do nothing */; + else if(mappingp(b)) { + int ml = 0, l = sizeof(s)-i; + string m; + foreach (indices(b), string k) + { + if (sizeof(k)>l && k[..l-1]==s[i..]) + /* Valid prefix, need more data */ + return s[i..]; + else if (sizeof(k) > ml && s[i..i+sizeof(k)-1] == k) + { + /* New longest match found */ + ml = sizeof(m = k); + } + } + if (ml) + { + i += ml-1; + b[m](m); + } + } else + b(s[i..i]); + } + return ""; + } + + static private void read_cb(mixed _, string s) + { + if (!s || s=="") + return; + if (sizeof(prefix)) + { + s = prefix+s; + prefix = ""; + } + prefix = process_input(s); + } + + static private void close_cb() + { + if (close_callback && close_callback()) + return; + destruct(this_object()); + } + + static private int set_enabled(int e) + { + if (e != enabled) + { + enabled = e; + if (enabled) + { + string oldprefix = prefix; + prefix = ""; + prefix = process_input(oldprefix); + infd->set_nonblocking(read_cb, 0, close_cb); + } + else + infd->set_blocking(); + return !enabled; + } + else + return enabled; + } + + int isenabled() + { + return enabled; + } + + int enable(int ... e) + { + if (sizeof(e)) + return set_enabled(!!e[0]); + else + return set_enabled(1); + } + + int disable() + { + return set_enabled(0); + } + + void run_blocking() + { + disable(); + string data = prefix; + prefix = ""; + enabled = 1; + for (;;) + { + prefix = process_input(data); + if (!enabled) + return; + data = prefix+infd->read(1024, 1); + prefix = ""; + } + } + + void set_close_callback(function (:int) ccb) + { + close_callback = ccb; + } + + void nullbindings() + { + bindings = ([]); + } + + void grabnextkey(function g) + { + if(functionp(g)) + grab_binding = g; + } + + function bindstr(string str, function f) + { + function oldf = 0; + if (mappingp(f)) + f = 0; // Paranoia + switch (sizeof(str||"")) + { + case 0: + break; + case 1: + if (mappingp(bindings[str[0]])) + { + oldf = bindings[str[0]][str]; + if (f) + bindings[str[0]][str] = f; + else + m_delete(bindings[str[0]], str); + } else { + oldf = bindings[str[0]]; + if (f) + bindings[str[0]] = f; + else + m_delete(bindings, str[0]); + } + break; + default: + if (mappingp(bindings[str[0]])) + oldf = bindings[str[0]][str]; + else + bindings[str[0]] = + bindings[str[0]]? ([str[0..0]:bindings[str[0]]]) : ([]); + if (f) + bindings[str[0]][str] = f; + else { + m_delete(bindings[str[0]], str); + if (!sizeof(bindings[str[0]])) + m_delete(bindings, str[0]); + else if(sizeof(bindings[str[0]])==1 && bindings[str[0]][str[0..0]]) + bindings[str[0]] = bindings[str[0]][str[0..0]]; + } + break; + } + return oldf; + } + + function unbindstr(string str) + { + return bindstr(str, 0); + } + + function getbindingstr(string str) + { + switch (sizeof(str||"")) + { + case 0: + return 0; + case 1: + return mappingp(bindings[str[0]])? + bindings[str[0]][str] : bindings[str[0]]; + default: + return mappingp(bindings[str[0]]) && bindings[str[0]][str]; + } + } + + function bindtc(string cap, function f) + { + return bindstr(term->tgetstr(cap), f); + } + + function unbindtc(string cap) + { + return unbindstr(term->tgetstr(cap)); + } + + function getbindingtc(string cap) + { + return getbindingstr(term->tgetstr(cap)); + } + + string parsekey(string k) + { + if (k[..1]=="\\!") + k = term->tgetstr(k[2..]); + else for(int i=0; i<sizeof(k); i++) + switch(k[i]) + { + case '\\': + if(i<sizeof(k)-1) + switch(k[i+1]) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + int n, l; + if (2<sscanf(k[i+1..], "%o%n", n, l)) + { + n = k[i+1]-'0'; + l = 1; + } + k = k[..i-1]+sprintf("%c", n)+k[i+1+l..]; + break; + case 'e': + case 'E': + k = k[..i-1]+"\033"+k[i+2..]; + break; + case 'n': + k = k[..i-1]+"\n"+k[i+2..]; + break; + case 'r': + k = k[..i-1]+"\r"+k[i+2..]; + break; + case 't': + k = k[..i-1]+"\t"+k[i+2..]; + break; + case 'b': + k = k[..i-1]+"\b"+k[i+2..]; + break; + case 'f': + k = k[..i-1]+"\f"+k[i+2..]; + break; + default: + k = k[..i-1]+k[i+1..]; + break; + } + break; + case '^': + if(i<sizeof(k)-1 && k[i+1]>='?' && k[i+1]<='_') + k = k[..i-1]+(k[i+1]=='?'? "\177":sprintf("%c",k[i+1]-'@'))+k[i+2..]; + break; + } + return k; + } + + function bind(string k, function f) + { + return bindstr(parsekey(k), f); + } + + function unbind(string k) + { + return unbindstr(parsekey(k)); + } + + function getbinding(string k, string cap) + { + return getbindingstr(parsekey(k)); + } + + mapping(string:function) getbindings() + { + mapping(int:function) bb = bindings-Array.filter(bindings, mappingp); + return `|(mkmapping(Array.map(indices(bb), lambda(int n) { + return sprintf("%c", n); + }), values(bb)), + @Array.filter(values(bindings), mappingp)); + } + + void create(object|void _infd, object|string|void _term) + { + infd = _infd || Stdio.File("stdin"); + term = objectp(_term)? _term : .Terminfo.getTerm(_term); + disable(); + catch { oldattrs = infd->tcgetattr(); }; + catch { infd->tcsetattr((["ECHO":0,"ICANON":0,"VMIN":1,"VTIME":0, + "VLNEXT":0])); }; + } + +} + +class DefaultEditKeys +{ + + static object _readline; + + void self_insert_command(string str) + { + _readline->insert(str, _readline->getcursorpos()); + } + + void quoted_insert() + { + _readline->get_input_controller()->grabnextkey(self_insert_command); + } + + void newline() + { + _readline->newline(); + } + + void up_history() + { + _readline->delta_history(-1); + } + + void down_history() + { + _readline->delta_history(1); + } + + void backward_delete_char() + { + int p = _readline->getcursorpos(); + _readline->delete(p-1,p); + } + + void delete_char_or_eof() + { + int p = _readline->getcursorpos(); + if (p<strlen(_readline->gettext())) + _readline->delete(p,p+1); + else if(!strlen(_readline->gettext())) + _readline->eof(); + } + + void forward_char() + { + _readline->setcursorpos(_readline->getcursorpos()+1); + } + + void backward_char() + { + _readline->setcursorpos(_readline->getcursorpos()-1); + } + + void beginning_of_line() + { + _readline->setcursorpos(0); + } + + void end_of_line() + { + _readline->setcursorpos(strlen(_readline->gettext())); + } + + void transpose_chars() + { + int p = _readline->getcursorpos(); + if (p<0 || p>=strlen(_readline->gettext())) + return; + string c = _readline->gettext()[p-1..p]; + _readline->delete(p-1, p+1); + _readline->insert(reverse(c), p-1); + } + + void kill_line() + { + _readline->delete(_readline->getcursorpos(), strlen(_readline->gettext())); + } + + void kill_whole_line() + { + _readline->delete(0, strlen(_readline->gettext())); + } + + void redisplay() + { + _readline->redisplay(0); + } + + void clear_screen() + { + _readline->redisplay(1); + } + + static array(array(string|function)) default_bindings = ({ + ({ "^[[A", up_history }), + ({ "^[[B", down_history }), + ({ "^[[C", forward_char }), + ({ "^[[D", backward_char }), + ({ "^A", beginning_of_line }), + ({ "^B", backward_char }), + ({ "^D", delete_char_or_eof }), + ({ "^E", end_of_line }), + ({ "^F", forward_char }), + ({ "^H", backward_delete_char }), + ({ "^J", newline }), + ({ "^K", kill_line }), + ({ "^L", clear_screen }), + ({ "^M", newline }), + ({ "^N", down_history }), + ({ "^P", up_history }), + ({ "^R", redisplay }), + ({ "^T", transpose_chars }), + ({ "^U", kill_whole_line }), + ({ "^V", quoted_insert }), + ({ "^?", backward_delete_char }), + ({ "\\!ku", up_history }), + ({ "\\!kd", down_history }), + ({ "\\!kr", forward_char }), + ({ "\\!kl", backward_char }) + }); + + static void set_default_bindings() + { + object ic = _readline->get_input_controller(); + ic->nullbindings(); + for(int i=' '; i<'\177'; i++) + ic->bindstr(sprintf("%c", i), self_insert_command); + for(int i='\240'; i<='\377'; i++) + ic->bindstr(sprintf("%c", i), self_insert_command); + foreach(default_bindings, array(string|function) b) + ic->bind(@b); + } + + void create(object readline) + { + _readline = readline; + set_default_bindings(); + } + +} + +class History +{ + static private array(string) historylist; + static private int minhistory, maxhistory, historynum; + + int get_history_num() + { + return historynum; + } + + string history(int n, string text) + { + if (n<minhistory) + n = minhistory; + else if (n-minhistory>=sizeof(historylist)) + n = sizeof(historylist)+minhistory-1; + historylist[historynum-minhistory]=text; + return historylist[(historynum=n)-minhistory]; + } + + void initline() + { + if (sizeof(historylist)==0 || historylist[-1]!="") { + historylist += ({ "" }); + if (maxhistory && sizeof(historylist)>maxhistory) + { + int n = sizeof(historylist)-maxhistory; + historylist = historylist[n..]; + minhistory += n; + } + } + historynum = sizeof(historylist)-1+minhistory; + } + + void finishline(string text) + { + historylist[historynum-minhistory]=text; + } + + void set_max_history(int maxhist) + { + maxhistory = maxhist; + } + + void create(int maxhist) + { + historylist = ({ "" }); + minhistory = historynum = 0; + maxhistory = maxhist; + } + +} + +class Readline +{ + static private object(OutputController) output_controller; + static private object(InputController) input_controller; + static private string prompt=""; + static private string text="", readtext; + static private function(string:void) newline_func; + static private int cursorpos = 0; + static private object(History) historyobj = 0; + + object(OutputController) get_output_controller() + { + return output_controller; + } + + object(InputController) get_input_controller() + { + return input_controller; + } + + string get_prompt() + { + return prompt; + } + + string set_prompt(string newp) + { + string oldp = prompt; + prompt = newp; + return oldp; + } + + string gettext() + { + return text; + } + + int getcursorpos() + { + return cursorpos; + } + + int setcursorpos(int p) + { + if (p<0) + p = 0; + if (p>strlen(text)) + p = strlen(text); + if (p<cursorpos) + { + output_controller->move_backward(text[p..cursorpos-1]); + cursorpos = p; + } + else if (p>cursorpos) + { + output_controller->move_forward(text[cursorpos..p-1]); + cursorpos = p; + } + return cursorpos; + } + + void insert(string s, int p) + { + if (p<0) + p = 0; + if (p>strlen(text)) + p = strlen(text); + setcursorpos(p); + output_controller->write(s); + cursorpos += strlen(s); + string rest = text[p..]; + if (strlen(rest)) + { + output_controller->write(rest); + output_controller->move_backward(rest); + } + text = text[..p-1]+s+rest; + } + + void delete(int p1, int p2) + { + if (p1<0) + p1 = 0; + if (p2>strlen(text)) + p2 = strlen(text); + setcursorpos(p1); + if (p1>=p2) + return; + output_controller->write(text[p2..]); + output_controller->erase(text[p1..p2-1]); + text = text[..p1-1]+text[p2..]; + cursorpos = strlen(text); + setcursorpos(p1); + } + + void history(int n) + { + if(historyobj) { + string h = historyobj->history(n, text); + delete(0, sizeof(text)); + insert(h, 0); + } + } + + void delta_history(int d) + { + if(historyobj) + history(historyobj->get_history_num()+d); + } + + void redisplay(int clear, int|void nobackup) + { + int p = cursorpos; + if(clear) + output_controller->clear(); + else if(!nobackup) { + setcursorpos(0); + output_controller->bol(); + output_controller->clear(1); + } + output_controller->check_columns(); + if(newline_func == read_newline) + output_controller->write(prompt); + output_controller->write(text); + cursorpos = sizeof(text); + setcursorpos(p); + } + + static private void initline() + { + text = ""; + cursorpos = 0; + if (historyobj) + historyobj->initline(); + } + + string newline() + { + setcursorpos(sizeof(text)); + output_controller->newline(); + string data = text; + if (historyobj) + historyobj->finishline(text); + initline(); + if(newline_func) + newline_func(data); + } + + void eof() + { + if (historyobj) + historyobj->finishline(text); + initline(); + if(newline_func) + newline_func(0); + } + + static private void read_newline(string s) + { + input_controller->disable(); + readtext = s; + } + + void set_nonblocking(function f) + { + if (newline_func = f) + input_controller->enable(); + else + input_controller->disable(); + } + + void set_blocking() + { + set_nonblocking(0); + } + + string read() + { + if(newline_func == read_newline) + return 0; + function oldnl = newline_func; + output_controller->write(prompt); + initline(); + newline_func = read_newline; + readtext = ""; + input_controller->run_blocking(); + set_nonblocking(oldnl); + return readtext; + } + + void enable_history(object(History)|int hist) + { + if (objectp(hist)) + historyobj = hist; + else if(!hist) + historyobj = 0; + else if(historyobj) + historyobj->set_max_history(hist); + else + historyobj = History(hist); + } + + void destroy() + { + destruct(input_controller); + destruct(output_controller); + } + + void create(object|void infd, object|string|void interm, + object|void outfd, object|string|void outterm) + { + output_controller = OutputController(outfd || infd, outterm || interm); + input_controller = InputController(infd, interm); + DefaultEditKeys(this_object()); + } +} + + + +/* Emulation of old readline() function. Don't use in new code. */ + +static private object(History) readline_history = History(512); + +string readline(string prompt, function|void complete_callback) +{ + object rl = Readline(); + rl->enable_history(readline_history); + rl->set_prompt(prompt); + if(complete_callback) + rl->get_input_controller()-> + bind("^I", lambda() { + array(string) compl = ({ }); + string c, buf = rl->gettext(); + int st = 0, point = rl->getcursorpos(); + int wordstart = search(replace(reverse(buf), + ({"\t","\r","\n"}), + ({" "," "," "})), + " ", sizeof(buf)-point); + string word = buf[(wordstart>=0 && sizeof(buf)-wordstart).. + point-1]; + while((c = complete_callback(word, st++, buf, point))) + compl += ({ c }); + switch(sizeof(compl)) { + case 0: + break; + case 1: + rl->delete(point-sizeof(word), point); + rl->insert(compl[0], point-sizeof(word)); + rl->setcursorpos(point-sizeof(word)+sizeof(compl[0])); + break; + default: + rl->setcursorpos(strlen(rl->gettext())); + rl->get_output_controller()->newline(); + foreach(sprintf("%-"+rl->get_output_controller()-> + get_number_of_columns()+"#s", compl*"\n") + /"\n", string l) { + rl->get_output_controller()->write(l); + rl->get_output_controller()->newline(); + } + rl->redisplay(0, 1); + rl->setcursorpos(point); + } + }); + string res = rl->read(); + destruct(rl); + return res; +} diff --git a/lib/modules/Stdio.pmod/Terminfo.pmod b/lib/modules/Stdio.pmod/Terminfo.pmod new file mode 100644 index 0000000000000000000000000000000000000000..e22ac5dfa22376eca46c7e50f7221c25a89cb600 --- /dev/null +++ b/lib/modules/Stdio.pmod/Terminfo.pmod @@ -0,0 +1,756 @@ +// $Id: Terminfo.pmod,v 1.1 1999/03/13 01:12:37 marcus Exp $ + + +#if constant(thread_create) +#define LOCK object m_key = mutex->lock() +#define UNLOCK destruct(m_key) +#define MUTEX static private object mutex = Thread.Mutex(); +#else +#define LOCK +#define UNLOCK +#define MUTEX +#endif + +MUTEX + +static private array ctrlcharsfrom = + Array.map(indices(allocate(32)), + lambda(int z) { return sprintf("^%c",z+64); })+ + Array.map(indices(allocate(32)), + lambda(int z) { return sprintf("^%c",z+96); }); +static private array ctrlcharsto = + Array.map(indices(allocate(32)), + lambda(int z) { return sprintf("%c",z); })+ + Array.map(indices(allocate(32)), + lambda(int z) { return sprintf("%c",z); }); + + +static private class TermMachine { + + mapping(string:string|int) map = ([]); + + int tgetflag(string id) + { + return map[id]==1; + } + + int tgetnum(string id) + { + return intp(map[id]) && map[id]; + } + + string tgetstr(string id) + { + return stringp(map[id]) && map[id]; + } + + string tparam(string f, mixed ... args) + { + array fmt=f/"%"; + string res=fmt[0]; + string tmp; + int z; + mapping var=([]); + array args0=args; + +#define POP (z=args[0],args=args[1..],z) +#define PUSH(x) (args=({x})+args) + + while ( (fmt=fmt[1..])!=({}) ) + if (fmt[0]=="") res+="%"; + else + { + switch (fmt[0][0]) + { + case 'd': res+=sprintf("%d%s",POP,fmt[0][1..]); break; + case 'x': res+=sprintf("%x%s",POP,fmt[0][1..]); break; + case '0': case '2': case '3': + sscanf(fmt[0],"%[0-9]%s",tmp,fmt[0]); + res+=sprintf("%"+tmp+fmt[0][..0]+"%s",POP,fmt[0][1..]); + break; + case 'c': res+=sprintf("%c%s",POP,fmt[0][1..]); break; + case 's': res+=sprintf("%s%s",POP,fmt[0][1..]); break; + + case '\'': + sscanf(fmt[0],"'%s'%s",tmp,fmt[0]); + if (tmp=="") tmp="\0"; + if (tmp[0]=='\\') tmp=sprintf("%c",(int)("0"+tmp[1..])); + PUSH(tmp[0]); + res+=fmt[0]; + break; + case '{': + sscanf(fmt[0],"{%d}%s",z,fmt[0]); + res+=fmt[0]; + PUSH(z); + break; + case 'p': + PUSH(args0[fmt[0][1]-'1']); + res+=fmt[0][2..]; + break; + case 'P': + var[fmt[0][1]]=POP; + res+=fmt[0][2..]; + break; + case 'g': + PUSH(var[fmt[0][1]]); + res+=fmt[0][2..]; + break; + case 'i': + args[0]+=1; + args[1]+=1; + break; + case '+': PUSH(POP+POP); res+=fmt[0][1..]; break; + case '-': PUSH(POP-POP); res+=fmt[0][1..]; break; + case '*': PUSH(POP*POP); res+=fmt[0][1..]; break; + case '/': PUSH(POP/POP); res+=fmt[0][1..]; break; + case 'm': PUSH(POP%POP); res+=fmt[0][1..]; break; + case '&': PUSH(POP&POP); res+=fmt[0][1..]; break; + case '|': PUSH(POP|POP); res+=fmt[0][1..]; break; + case '^': PUSH(POP^POP); res+=fmt[0][1..]; break; + case '=': PUSH(POP==POP); res+=fmt[0][1..]; break; + case '>': PUSH(POP>POP); res+=fmt[0][1..]; break; + case '<': PUSH(POP<POP); res+=fmt[0][1..]; break; + case 'A': PUSH(POP && POP); res+=fmt[0][1..]; break; + case 'O': PUSH(POP || POP); res+=fmt[0][1..]; break; + case '!': PUSH(!POP); res+=fmt[0][1..]; break; + case '~': PUSH(~POP); res+=fmt[0][1..]; break; + case '?': + error("Sorry, Terminal can't handle if-else's\n"); + default: + error("Unknown opcode: %%%s\n",fmt[0][..0]); + } + } + return res; + } + + string tgoto(string cap, int col, int row) + { + return tparam(cap, col, row); + } + + string tputs(string s) + { + return s; + } + + string put(string cap, mixed ... args) + { + string str = tgetstr(cap); + string tstr = str && tparam(str, @args); + return tstr && tputs(tstr); + } + +} + +class Termcap { + + inherit TermMachine; + + array(string) aliases; + object parent; + + string tputs(string s) + { + // Delay stuff completely ignored... + sscanf(s, "%*d%s", s); + return s; + } + + private static multiset(string) load_cap(string en) + { + string br=":"; + int i=search(en,":"); + int j=search(en,","); + multiset(string) clears = (<>); + + if (i==-1) { i=j; br=","; } + else if (j!=-1) { i=min(i,j); if (i==j) br=","; } + if (i<1) + error("Termcap: Unparsable entry\n"); + aliases=en[..i-1]/"|"; + en=en[i..]; + + while (en!="") + { + string name; + string data; + sscanf(en,"%*[ \t]%[a-zA-Z_0-9&]%s"+br+"%s",name,data,en); + + if (data=="") // boolean + { + if (name!="") map[name]=1; + } + else if (data[0]=='@') // boolean off + { + clears[name]=1; + } + else if (data[0]=='#') // number + { + int z; + sscanf(data,"#%d",z); + map[name]=z; + } + else if (data[0]=='=') // string + { + data=data[1..]; + while (data[-1]=="\\") + { + string add; + if (sscanf(en,"%s"+br+"%s",add,en)<2) break; + data+="\\"+add; + } + + data=replace(data,"\\^","\\*"); + + if (search(data,"^")!=-1) + data=replace(data,ctrlcharsfrom,ctrlcharsto); + + data = replace(data, + ({"\\E","\\e","\\n","\\r","\\t","\\b","\\f", + "\\*","\\\\","\\,","\\:","#", + "\\0","\\1","\\2","\\3","\\4","\\5","\\6","\\7"}), + ({"\033","\033","\n","\r","\t","\b","\f", + "^","\\",",",":","#!", + "#0","#1","#2","#3","#4","#5","#6","#7"})); + + array(string) parts = data/"#"; + data = parts[0]; + foreach (parts[1..], string p) + if (sizeof(p) && p[0]=='!') + data += "#"+p[1..]; + else + { + int n; + string x; + if(2==sscanf(p[..2], "%o%s", n, x)) + data += sprintf("%c%s%s", n, x, p[3..]); + else + data += p; + } + + map[name]=data; + + } + else // wierd + { + // ignore + } + } + + return clears; + } + + void create(string cap, object|void tcdb, int|void maxrecurse) + { + int i=0; + while((i=search(cap, "\\\n", i))>=0) { + string capr; + if(2!=sscanf(cap[i..], "\\\n%*[ \t\r]%s", capr)) + break; + cap = cap[..i-1]+capr; + } + multiset(string) clears = load_cap(cap); + if(map->tc) { + if(maxrecurse==1) + error("Termcap: maximum inheritance depth exceeded (loop?)\n"); + parent = (tcdb||defaultTermcapDB())-> + load(map->tc, maxrecurse? (maxrecurse-1):25); + if(!parent) + error("Termcap: can't find parent terminal \"%s\"\n", map->tc); + map = parent->map | map; + } + map |= mkmapping(indices(clears), allocate(sizeof(clears))); + } +} + + + +class Terminfo { + + inherit TermMachine; + + array(string) aliases; + + static private constant boolnames = + ({ "bw","am","xb","xs","xn","eo","gn","hc","km","hs","in","da","db","mi", + "ms","os","es","xt","hz","ul","xo","nx","5i","HC","NR","NP","ND","cc", + "ut","hl","YA","YB","YC","YD","YE","YF","YG" }); + static private constant numnames = + ({ "co","it","li","lm","sg","pb","vt","ws","Nl","lh","lw","ma","MW","Co", + "pa","NC","Ya","Yb","Yc","Yd","Ye","Yf","Yg","Yh","Yi","Yj","Yk","Yl", + "Ym","Yn","BT","Yo","Yp" }); + static private constant strnames = + ({ "bt","bl","cr","cs","ct","cl","ce","cd","ch","CC","cm","do","ho","vi", + "le","CM","ve","nd","ll","up","vs","dc","dl","ds","hd","as","mb","md", + "ti","dm","mh","im","mk","mp","mr","so","us","ec","ae","me","te","ed", + "ei","se","ue","vb","ff","fs","i1","is","i3","if","ic","al","ip","kb", + "ka","kC","kt","kD","kL","kd","kM","kE","kS","k0","k1","k;","k2","k3", + "k4","k5","k6","k7","k8","k9","kh","kI","kA","kl","kH","kN","kP","kr", + "kF","kR","kT","ku","ke","ks","l0","l1","la","l2","l3","l4","l5","l6", + "l7","l8","l9","mo","mm","nw","pc","DC","DL","DO","IC","SF","AL","LE", + "RI","SR","UP","pk","pl","px","ps","pf","po","rp","r1","r2","r3","rf", + "rc","cv","sc","sf","sr","sa","st","wi","ta","ts","uc","hu","iP","K1", + "K3","K2","K4","K5","pO","rP","ac","pn","kB","SX","RX","SA","RA","XN", + "XF","eA","LO","LF","@1","@2","@3","@4","@5","@6","@7","@8","@9","@0", + "%1","%2","%3","%4","%5","%6","%7","%8","%9","%0","&1","&2","&3","&4", + "&5","&6","&7","&8","&9","&0","*1","*2","*3","*4","*5","*6","*7","*8", + "*9","*0","#1","#2","#3","#4","%a","%b","%c","%d","%e","%f","%g","%h", + "%i","%j","!1","!2","!3","RF","F1","F2","F3","F4","F5","F6","F7","F8", + "F9","FA","FB","FC","FD","FE","FF","FG","FH","FI","FJ","FK","FL","FM", + "FN","FO","FP","FQ","FR","FS","FT","FU","FV","FW","FX","FY","FZ","Fa", + "Fb","Fc","Fd","Fe","Ff","Fg","Fh","Fi","Fj","Fk","Fl","Fm","Fn","Fo", + "Fp","Fq","Fr","cb","MC","ML","MR","Lf","SC","DK","RC","CW","WG","HU", + "DI","QD","TO","PU","fh","PA","WA","u0","u1","u2","u3","u4","u5","u6", + "u7","u8","u9","op","oc","Ic","Ip","sp","Sf","Sb","ZA","ZB","ZC","ZD", + "ZE","ZF","ZG","ZH","ZI","ZJ","ZK","ZL","ZM","ZN","ZO","ZP","ZQ","ZR", + "ZS","ZT","ZU","ZV","ZW","ZX","ZY","ZZ","Za","Zb","Zc","Zd","Ze","Zf", + "Zg","Zh","Zi","Zj","Zk","Zl","Zm","Zn","Zo","Zp","Zq","Zr","Zs","Zt", + "Zu","Zv","Zw","Zx","Zy","Km","Mi","RQ","Gm","AF","AB","xl","dv","ci", + "s0","s1","s2","s3","ML","MT","Xy","Zz","Yv","Yw","Yx","Yy","Yz","YZ", + "S1","S2","S3","S4","S5","S6","S7","S8","Xh","Xl","Xo","Xr","Xt","Xv", + "sA","sL" }); + + string tputs(string s) + { + // Delay stuff completely ignored... + string pre, post; + while (3==sscanf(s, "%s$<%*[0-9.]>%s", pre, post)) + s = pre+post; + return s; + } + + static private string swab(string s) + { + return Array.map(s/2, reverse)*""; + } + + static private int load_cap(object f) + { + int magic, sname, nbool, nnum, nstr, sstr; + + if (6!=sscanf(swab(f->read(12)), "%2c%2c%2c%2c%2c%2c", + magic, sname, nbool, nnum, nstr, sstr) || + magic != 0432) + return 0; + aliases = (f->read(sname)-"\0")/"|"; + { + array(int) bools = values(f->read(nbool+(nbool&1))[..nbool-1]); + if (sizeof(bools)>sizeof(boolnames)) + bools = bools[..sizeof(boolnames)-1]; + map = mkmapping(boolnames[..sizeof(bools)-1], bools); + } + { + array(int) nums = array_sscanf(swab(f->read(nnum*2)), "%2c"*nnum); + if (sizeof(nums)>sizeof(numnames)) + nums = nums[..sizeof(numnames)-1]; + mapping(string:int) tmp = mkmapping(numnames[..sizeof(nums)-1], nums); + foreach (numnames[..sizeof(nums)-1], string name) + if (tmp[name]>=0xfffe) + m_delete(tmp, name); + map += tmp; + } + { + string stroffs = swab(f->read(nstr*2)); + string strbuf = f->read(sstr); + if(strlen(strbuf)!=sstr) + return 0; + array(string) strarr = Array.map(array_sscanf(stroffs, "%2c"*nstr), + lambda(int offs, string buf) { + return offs<0xfffe && + buf[offs.. + search(buf, "\0", offs)-1]; + }, strbuf+"\0"); + if (sizeof(strarr)>sizeof(strnames)) + strarr = strarr[..sizeof(strnames)-1]; + mapping(string:string) tmp = mkmapping(strnames[..sizeof(strarr)-1], + strarr); + foreach (strnames[..sizeof(strarr)-1], string name) + if (!tmp[name]) + m_delete(tmp, name); + map += tmp; + } + return 1; + } + + void create(string filename) + { + object f = Stdio.File(); + if (!f->open(filename, "r")) + error("Terminfo: unable to open terminfo file \"%s\"\n", filename); + int r = load_cap(f); + f->close(); + if (!r) + error("Terminfo: unparsable terminfo file \"%s\"\n", filename); + } +} + +class TermcapDB { + + MUTEX + + static private inherit Stdio.File; + + static private string buf=""; + static private mapping(string:int|object) cache=([]); + static private int complete_index=0; + + void create(string|void filename) + { + if (!filename) { + string tce = getenv("TERMCAP"); + if (tce && strlen(tce) && tce[0]=='/') + filename = tce; + else + filename = "/etc/termcap"; + } + if (!::open(filename, "r")) + error("failed to open termcap file %O\n", filename); + } + + static private void rewind(int|void pos) + { + ::seek(pos); + buf=""; + } + + static private int more_data() + { + string q; + q=::read(8192); + if (q=="" || !q) return 0; + buf+=q; + return 1; + } + + static private array(string) get_names(string cap) + { + sscanf(cap, "%s:", cap); + sscanf(cap, "%s,", cap); + return cap/"|"; + } + + static private string read() + { + int i, st; + string res=""; + for (;;) + { + if (buf=="" && !more_data()) return 0; // eof + + sscanf(buf,"%*[ \t\r\n]%s",buf); + if (buf=="") continue; + + if (buf[0]=='#') // comment, scan to newline + { + while ((i=search(buf,"\n"))<0) + { + // The rest of the buffer is comment, toss it and read more + buf=""; + if(!more_data()) return 0; // eof + } + buf=buf[i+1..]; + continue; + } + + break; + } + + st = ::tell()-sizeof(buf); + + while ((i=search(buf, "\n"))<0) + { + if (!more_data()) return 0; // eof + } + + while (buf[i-1]=='\\') + { + res+=buf[..i-2]; + buf=buf[i+1..]; + while (sscanf(buf,"%*[ \t\r]%s",buf)<2 || !sizeof(buf)) + if (!more_data()) { + buf = ""; + return res; // eof, or illegal... wierd + } + while ((i=search(buf, "\n"))<0) + { + if (!more_data()) return 0; // eof + } + } + + res+=buf[..i-1]; + buf=buf[i+1..]; + + foreach(get_names(res), string name) + if(!objectp(cache[name])) + cache[name]=st; + + return res; + } + + static private string readat(int pos) + { + rewind(pos); + return read(); + } + + array(string) _indices() + { + LOCK; + if(!complete_index) { + rewind(); + while(read()); + complete_index = 1; + } + UNLOCK; + return sort(indices(cache)); + } + + array(object) _values() + { + array(object|int) res = ({}); + mapping(int:string) extra = ([]); + LOCK; + if (complete_index) + res = Array.map(sort(indices(cache)), + lambda(string name, mapping(int:string) extra) { + if (!objectp(cache[name]) && !extra[cache[name]]) + extra[cache[name]] = readat(cache[name]); + return cache[name]; + }, extra); + else { + array(string) resi = ({}); + string cap; + int i = 1; + rewind(); + while ((cap = read())) { + array(string) names = get_names(cap); + object|int o = objectp(cache[names[0]]) && cache[names[0]]; + if (!o) + { + o = i++; + extra[o] = cap; + } + res += ({ o }) * sizeof(names); + resi += names; + } + sort(resi, res); + complete_index = 1; + } + UNLOCK; + return Array.map(res, lambda(int|object x, mapping(int:object) y) { + return objectp(x)? x : y[x]; + }, + mkmapping(indices(extra), + Array.map(values(extra), + Termcap, this_object()))); + } + + static private string read_next(string find) // quick search + { + for (;;) + { + int i, j; + + if (buf=="" && !more_data()) return 0; // eof + + i=search(buf,find); + if (i!=-1) + { + int j=i; + while (j>=0 && buf[j]!='\n') j--; // find backwards + + if (buf!="" && buf[j+1]!='#') // skip comments + { + buf=buf[j+1..]; + return read(); + } + + while ((i=search(buf,"\n",j+1))<0) + if (!more_data()) return 0; // eof + + buf = buf[i+1..]; + + continue; + } + for(j=-1; (i=search(buf,"\n",j+1))>=0; j=i); + buf = buf[j+1..]; + if (!more_data()) return 0; // eof + } + } + + object load(string term, int|void maxrecurse) + { + int|string|object cap; + + LOCK; + if (zero_type(cache[term])) + { + if (!complete_index) + { + rewind(); + do + cap = read_next(term); + while(cap && search(get_names(cap), term)<0); + } + } + else if (intp(cap=cache[term])) { + rewind(cap); + cap = read(); + } + UNLOCK; + if (stringp(cap)) + { + array(string) names = get_names(cap); + if ((cap = Termcap(cap, this_object(), maxrecurse))) + { + LOCK; + foreach(names, string name) + cache[name] = cap; + UNLOCK; + } + } + return objectp(cap) && cap; + } + + object `[](string name) + { + return load(name); + } +} + + +class TerminfoDB { + + MUTEX + + static private string dir; + static private mapping(string:object) cache = ([]); + static private int complete_index=0; + + void create(string|void dirname) + { + if (!dirname) + { + foreach (({"/usr/share/lib/terminfo", "/usr/share/termcap", + "/usr/lib/terminfo", "/usr/share/misc/terminfo"}), string dn) + { + array(int) s = file_stat(dn); + if (arrayp(s) && sizeof(s)>1 && s[1]==-2) + { + dirname = dn; + break; + } + } + if (!dirname) { + destruct(this_object()); + return; + } + } + + if(sizeof(dirname)<1 || dirname[-1]!='/') + dirname += "/"; + + if (!get_dir(dir = dirname)) + error("failed to read terminfo dir %O\n", dirname); + } + + array(string) _indices() + { + LOCK; + if (!complete_index) { + array(string) files; + foreach (get_dir(dir), string a) + if (sizeof(a) == 1) + foreach (get_dir(dir+a), string b) + if(zero_type(cache[b])) + cache[b] = 0; + complete_index = 1; + } + UNLOCK; + return sort(indices(cache)); + } + + array(object) _values() + { + return Array.map(_indices(), + lambda(string name) { + return cache[name] || + Terminfo(dir+name[..0]+"/"+name); + }); + } + + object load(string term) + { + object ti; + + if (!strlen(term)) + return 0; + LOCK; + if (!(ti = cache[term])) + { + if (file_stat(dir+term[..0]+"/"+term)) + ti = Terminfo(dir+term[..0]+"/"+term); + if (ti) + cache[term] = ti; + } + UNLOCK; + return ti; + } + + object `[](string name) + { + return load(name); + } + +} + +static private object defterm, deftermcap, defterminfo; + +object defaultTermcapDB() +{ + object tcdb; + LOCK; + tcdb = deftermcap || (deftermcap = TermcapDB()); + UNLOCK; + return tcdb; +} + +object defaultTerminfoDB() +{ + object tidb; + LOCK; + tidb = defterminfo || (defterminfo = TerminfoDB()); + UNLOCK; + return tidb; +} + +object getTermcap(string term) +{ + object tcdb = defaultTermcapDB(); + return tcdb && tcdb[term]; +} + +object getTerminfo(string term) +{ + object tidb = defaultTerminfoDB(); + return tidb && tidb[term]; +} + +object getTerm(string|void term) +{ + if (!term) { + object t = defterm; + if (!t) + { + string tc = getenv("TERMCAP"); + t = (tc && sizeof(tc) && tc[0]!='/'? + Termcap(tc) : getTerm(getenv("TERM")||"dumb")); + LOCK; + if (!defterm) + defterm = t; + UNLOCK; + } + return t; + } + return getTerminfo(term) || getTermcap(term); +}