ref: 02ac617541ca1a7bf82b1615fb5a58235469b5d3
dir: /appl/wm/unibrowse.b/
implement Unibrowse;
# unicode browser for inferno.
# roger peppe (rog@ohm.york.ac.uk)
include "sys.m";
sys: Sys;
stderr: ref Sys->FD;
include "draw.m";
draw: Draw;
include "tk.m";
tk: Tk;
include "tkclient.m";
tkclient: Tkclient;
include "dialog.m";
dialog: Dialog;
include "selectfile.m";
selectfile: Selectfile;
include "string.m";
str: String;
include "bufio.m";
bio: Bufio;
Unibrowse: module
{
init: fn(ctxt: ref Draw->Context, nil: list of string);
};
Widgetstack: adt {
stk: list of string; # list of widget names; bottom of list is left-most widget
name: string;
# init returns the widget name for the widgetstack;
# wn is the name of the frame holding the widget stack
new: fn(wn: string): ref Widgetstack;
push: fn(ws: self ref Widgetstack, w: string);
pop: fn(ws: self ref Widgetstack): string;
top: fn(ws: self ref Widgetstack): string;
};
Defaultwidth: con 30;
Defaultheight: con 1;
Tablerows: con 3;
Tablecols: con 8;
Element: adt {
name: string;
cmd: chan of string;
cmdname: string;
config: array of string;
doneinit: int;
};
# columns in unidata file
ud_VAL, ud_CHARNAME, ud_CATEG, ud_COMBINE, ud_BIDIRECT,
ud_DECOMP, ud_DECDIGIT, ud_DIGIT, ud_NUMERICVAL, ud_MIRRORED,
ud_OLDNAME, ud_COMMENT, ud_UPCASE, ud_LOWCASE, ud_TITLECASE: con iota;
# default font configurations within the application
DEFAULTFONT: con "";
UNICODEFONT: con "lucm/unicode.9";
TITLEFONT: con "misc/latin1.8x13";
DATAFONT: con "misc/latin1.8x13";
BUTTONFONT: con "misc/latin1.8x13";
currfont := "/fonts/" + UNICODEFONT + ".font";
MAINMENU, BYSEARCH, BYNUMBER, BYCATEGORY, BYFONT, TABLE: con iota;
elements := array[] of {
MAINMENU => Element(".main", nil, "maincmd", array[] of {
"frame .main",
"$listbox data .main.menu -height 6h",
"$button button .main.insp -text {Inspector} -command {send maincmd inspect}",
"$button button .main.font -text {Font} -command {send maincmd font}",
"$label unicode .fontlabel", # .fontlabel's font is currently chosen font
"pack .main.menu -side top",
"pack .main.insp .main.font -side left",
"bind .main.menu <ButtonRelease-1> +{send maincmd newselect}"
}, 0),
BYNUMBER => Element(".numfield", nil, "numcmd", array[] of {
"frame .numfield",
"$entry data .numfield.f -width 8w",
"bind .numfield.f <Key-\n> {send numcmd shownum}",
"$label title .numfield.l -text 'Hex unicode value",
"pack .numfield.l .numfield.f -side left"
}, 0),
TABLE => Element(".tbl", nil, "tblcmd", array[] of {
"frame .tbl",
"frame .tbl.tf",
"frame .tbl.buts",
"$button button .tbl.buts.forw -text {Next} -command {send tblcmd forw}",
"$button button .tbl.buts.backw -text {Prev} -command {send tblcmd backw}",
"pack .tbl.buts.forw .tbl.buts.backw -side left",
"pack .tbl.tf -side top",
"pack .tbl.buts -side left"
}, 0),
BYCATEGORY => Element(".cat", nil, "catcmd", array[] of {
"frame .cat",
"$listbox data .cat.menu -width 43w -height 130 -yscrollcommand {.cat.yscroll set}",
"scrollbar .cat.yscroll -width 18 -command {.cat.menu yview}",
"pack .cat.yscroll .cat.menu -side left -fill y",
"bind .cat.menu <ButtonRelease-1> +{send catcmd newselect}"
}, 0),
BYSEARCH => Element(".srch", nil, "searchcmd", array[] of {
"frame .srch",
"$listbox data .srch.menu -width 43w -height 130 -yscrollcommand {.srch.yscroll set}",
"scrollbar .srch.yscroll -width 18 -command {.srch.menu yview}",
"pack .srch.yscroll .srch.menu -side left -fill y",
"bind .srch.menu <ButtonRelease-1> +{send searchcmd search}"
}, 0),
BYFONT => Element(".font", nil, "fontcmd", array[] of {
"frame .font",
"$listbox data .font.menu -width 43w -height 130 -yscrollcommand {.font.yscroll set}",
"scrollbar .font.yscroll -width 18 -command {.font.menu yview}",
"pack .font.yscroll .font.menu -side left -fill y",
"bind .font.menu <ButtonRelease-1> +{send fontcmd newselect}"
}, 0),
};
entries := array[] of {
("By Category", BYCATEGORY),
("By number", BYNUMBER),
("Symbol wordsearch", BYSEARCH),
("Font information", BYFONT)
};
toplevelconfig := array[] of {
"pack .Wm_t .display -side top -fill x",
"image create bitmap waiting -file cursor.wait"
};
wmchan: chan of string; # from main window
inspchan: chan of string; # to inspector
ctxt: ref Draw->Context;
displ: ref Widgetstack;
top: ref Tk->Toplevel;
unidata: ref bio->Iobuf;
UNIDATA: con "/lib/unidata/unidata2.txt";
UNIINDEX: con "/lib/unidata/index2.txt";
UNIBLOCKS: con "/lib/unidata/blocks.txt";
notice(msg: string)
{
dialog->prompt(ctxt, top.image, "bomb.bit", "Notice", msg, 0, "OK"::nil);
}
init(drawctxt: ref Draw->Context, nil: list of string)
{
entrychan := chan of string;
ctxt = drawctxt;
config();
if ((unidata = bio->open(UNIDATA, bio->OREAD)) == nil) {
notice("Couldn't open unicode data file");
inspchan <-= "exit";
exit;
}
push(MAINMENU);
tkclient->onscreen(top, nil);
tkclient->startinput(top, "kbd"::"ptr"::nil);
currpos := 0;
for (;;) alt {
c := <-top.ctxt.kbd =>
tk->keyboard(top, c);
p := <-top.ctxt.ptr =>
tk->pointer(top, *p);
c := <-top.ctxt.ctl or
c = <-top.wreq or
c = <-wmchan =>
tkclient->wmctl(top, c);
c := <-elements[MAINMENU].cmd =>
case c {
"font" =>
font := choosefont(ctxt);
if (font != nil) {
currfont = font;
updatefont();
update(top);
}
"newselect" =>
sel := int cmd(top, ".main.menu curselection");
(nil, el) := entries[sel];
if (el == BYSEARCH) {
spawn sendentry(top, "Enter search string", entrychan);
break;
}
pop(MAINMENU);
push(el);
update(top);
"inspect" =>
inspchan <-= "raise";
}
c := <-entrychan =>
if (c != nil) {
pop(MAINMENU);
push(BYSEARCH);
update(top);
keywordsearch(c);
}
<-elements[BYNUMBER].cmd =>
txt := cmd(top, ".numfield.f get");
(n, nil) := str->toint(txt, 16);
pop(BYNUMBER);
push(TABLE);
setchar(0, n);
currpos = filltable(n);
update(top);
<-elements[BYCATEGORY].cmd =>
sel := cmd(top, ".cat.menu curselection");
(currpos, nil) = str->toint(cmd(top, ".cat.menu get "+sel), 16);
pop(BYCATEGORY);
push(TABLE);
currpos = filltable(currpos);
update(top);
c := <-elements[TABLE].cmd =>
case c {
"forw" => currpos = filltable(currpos + Tablerows * Tablecols);
update(top);
"backw" => currpos = filltable(currpos - Tablerows * Tablecols);
update(top);
* => # must be set <col> <row> <raise>
(nil, args) := sys->tokenize(c, " ");
setchar(int hd tl tl tl args, currpos + int hd tl args
+ int hd tl tl args * Tablecols);
}
<-elements[BYSEARCH].cmd =>
sel := cmd(top, ".srch.menu curselection");
(n, nil) := str->toint(cmd(top, ".srch.menu get "+sel), 16);
pop(BYSEARCH);
push(TABLE);
setchar(0, n);
currpos = filltable(n);
update(top);
<-elements[BYFONT].cmd =>
sel := cmd(top, ".font.menu curselection");
(currpos, nil) = str->toint(cmd(top, ".font.menu get "+sel), 16);
pop(BYFONT);
push(TABLE);
currpos = filltable(currpos);
update(top);
}
inspchan <-= "exit";
}
sendentry(t: ref Tk->Toplevel, msg: string, where: chan of string)
{
where <-= dialog->getstring(ctxt, t.image, msg);
exit;
}
setchar(raisei: int, c: int)
{
s := ""; s[0] = c;
if(raisei)
inspchan <-= "raise";
inspchan <-= s;
}
charconfig := array[] of {
"frame .chdata -borderwidth 5 -relief ridge",
"frame .chdata.f1",
"frame .chdata.f2",
"frame .chdata.chf -borderwidth 4 -relief raised",
"frame .chdata.chcf -borderwidth 3 -relief ridge",
"$label title .chdata.chf.title -text 'Glyph: ",
"$label unicode .chdata.ch",
"$label data .chdata.val -anchor e",
"$label title .chdata.name -anchor w",
"$label data .chdata.cat -anchor w",
"$label data .chdata.comm -anchor w",
"$button button .chdata.snarfbut -text {Snarf} -command {send charcmd snarf}",
"$button button .chdata.pastebut -text {Paste} -command {send charcmd paste}",
"pack .chdata.chf.title .chdata.chcf -in .chdata.chf -side left",
"pack .chdata.ch -in .chdata.chcf",
"pack .chdata.chf -in .chdata.f1 -side left -padx 1 -pady 1",
"pack .chdata.val -in .chdata.f1 -side right",
"pack .chdata.snarfbut .chdata.pastebut -in .chdata.f2 -side right",
"pack .chdata.f1 .chdata.name .chdata.cat .chdata.comm .chdata.f2 -fill x -side top",
"pack .Wm_t .chdata -side top -fill x",
};
inspector(ctxt: ref Draw->Context, cmdch: chan of string)
{
chtop: ref Tk->Toplevel;
kbd := chan of int;
ptr := chan of ref Draw->Pointer;
wreq := chan of string;
iwmchan := chan of string;
ctl := chan of string;
charcmd := chan of string;
currc := 'A';
for (;;) alt {
c := <-kbd =>
tk->keyboard(chtop, c);
p := <-ptr =>
tk->pointer(chtop, *p);
c := <-ctl or
c = <-wreq or
c = <-iwmchan =>
if (c != "exit" && chtop != nil)
tkclient->wmctl(chtop, c);
else
chtop = nil;
c := <-cmdch =>
case c {
"raise" =>
if (chtop != nil) {
cmd(chtop, "raise .");
break;
}
org := winorg(top);
org.y += int cmd(top, ". cget -actheight");
(chtop, iwmchan) = tkclient->toplevel(ctxt,
"-x "+string org.x+" -y "+string org.y,
"Character inspector", 0);
tk->namechan(chtop, charcmd, "charcmd");
runconfig(chtop, charconfig);
inspector_setchar(chtop, currc);
tkclient->onscreen(chtop, "onscreen");
tkclient->startinput(chtop, "ptr"::nil);
kbd = chtop.ctxt.kbd;
ptr = chtop.ctxt.ptr;
ctl = chtop.ctxt.ctl;
wreq = chtop.wreq;
"font" =>
if (chtop != nil) {
cmd(chtop, ".chdata.ch configure -font "+currfont);
update(chtop);
}
"exit" =>
exit;
* =>
if (len c == 1) {
currc = c[0];
inspector_setchar(chtop, currc);
} else {
sys->fprint(stderr, "unknown inspector cmd: '%s'\n", c);
}
}
c := <-charcmd =>
case c {
"snarf" =>
tkclient->snarfput(cmd(chtop, ".chdata.ch cget -text"));
"paste" =>
buf := tkclient->snarfget();
if (len buf > 0)
inspector_setchar(chtop, buf[0]);
}
}
}
inspector_setchar(t: ref Tk->Toplevel, c: int)
{
if(t == nil)
return;
line := look(unidata, ';', sys->sprint("%4.4X", c));
labelset(t, ".chdata.ch", sys->sprint("%c", c));
labelset(t, ".chdata.val", sys->sprint("%4.4X", c));
if (line == nil) {
labelset(t, ".chdata.name", "No entry found in unicode table");
labelset(t, ".chdata.cat", "");
labelset(t, ".chdata.comm", "");
} else {
flds := fields(line, ';');
labelset(t, ".chdata.name", fieldindex(flds, ud_CHARNAME));
labelset(t, ".chdata.cat", categname(fieldindex(flds, ud_CATEG)));
labelset(t, ".chdata.comm", fieldindex(flds, ud_OLDNAME));
}
update(t);
}
keywordsearch(key: string): int
{
data := bio->open(UNIINDEX, Sys->OREAD);
key = str->tolower(key);
busy();
cmd(top, ".srch.menu delete 0 end");
count := 0;
while ((l := bio->data.gets('\n')) != nil) {
l = str->tolower(l);
if (str->prefix(key, l)) {
if (len l > 1 && l[len l - 2] == '\r')
l = l[0:len l - 2];
else
l = l[0:len l - 1];
flds := fields(l, '\t');
cmd(top, ".srch.menu insert end '"
+fieldindex(flds, 1)+": "+fieldindex(flds, 0));
update(top);
count++;
}
}
notbusy();
if (count == 0) {
notice("No match");
return 0;
}
return 1;
}
nomodule(s: string)
{
sys->fprint(stderr, "couldn't load modules %s: %r\n", s);
raise "could not load modules";
}
config()
{
sys = load Sys Sys->PATH;
if(ctxt == nil){
sys->fprint(stderr, "unibrowse: window manager required\n");
raise "no wm";
}
sys->pctl(Sys->NEWPGRP, nil);
stderr = sys->fildes(2);
draw = load Draw Draw->PATH;
if (draw == nil) nomodule(Draw->PATH);
tk = load Tk Tk->PATH;
if (tk == nil) nomodule(Tk->PATH);
tkclient = load Tkclient Tkclient->PATH;
if (tkclient == nil) nomodule(Tkclient->PATH);
dialog = load Dialog Dialog->PATH;
if (dialog == nil) nomodule(Dialog->PATH);
selectfile = load Selectfile Selectfile->PATH;
if (selectfile == nil) nomodule(Selectfile->PATH);
str = load String String->PATH;
if (str == nil) nomodule(String->PATH);
bio = load Bufio Bufio->PATH;
if (bio == nil) nomodule(Bufio->PATH);
tkclient->init();
dialog->init();
selectfile->init();
ctxt = ctxt;
(top, wmchan) = tkclient->toplevel(ctxt, nil, "Unicode browser", Tkclient->Hide);
displ = Widgetstack.new(".display");
cmd(top, "pack .display");
for (i := 0; i < len elements; i++) {
elements[i].cmd = tkchan(elements[i].cmdname);
runconfig(top, elements[i].config);
}
runconfig(top, toplevelconfig);
inspchan = chan of string;
spawn inspector(ctxt, inspchan);
}
runconfig(top: ref Tk->Toplevel, cmds: array of string)
{
for (i := 0; i < len cmds; i++) {
ent := tkexpand(cmds[i]);
if (ent != nil) {
err := cmd(top, ent);
if (len err > 0 && err[0] == '!')
sys->fprint(stderr, "config err: %s on '%s'\n", err, ent);
}
}
}
update(top: ref Tk->Toplevel)
{ cmd(top, "update"); }
busy()
{ cmd(top, "cursor -image waiting"); }
notbusy()
{ cmd(top, "cursor -default"); }
initelement(el: int): int
# returns non-zero on success
{
if (!elements[el].doneinit) {
elements[el].doneinit = 1;
case el {
MAINMENU =>
for (e := entries; len e > 0; e = e[1:]) {
(text, nil) := e[0];
cmd(top, ".main.menu insert end '" + text);
}
BYCATEGORY =>
cats := getcategories();
if (cats == nil) {
notice("No categories found");
elements[el].doneinit = 0;
return 0;
}
while (cats != nil) {
cmd(top, ".cat.menu insert 0 '" + hd cats);
cats = tl cats;
}
BYFONT =>
elements[el].doneinit = 0; # do it each time
fonts := getfonts(currfont);
if (fonts == nil) {
notice("Can't find font information file");
return 0;
}
cmd(top, ".font.menu delete 0 end");
while (fonts != nil) {
cmd(top, ".font.menu insert 0 '" + hd fonts);
fonts = tl fonts;
}
TABLE =>
inittable();
}
}
return 1;
}
tablecharpath(col, row: int): string
{
return ".tbl.tf.c"+string row+"_"+string col;
}
inittable()
{
i: int;
for (i = 0; i < Tablerows; i++) {
cmd(top, tkexpand("$label title .tbl.tf.num" + string i));
cmd(top, sys->sprint("grid .tbl.tf.num%d -row %d", i, i));
# >>> could put entry here
for (j := 0; j < Tablecols; j++) {
cname := ".tbl.tf.c" + string i +"_" +string j;
cmd(top, tkexpand("$label unicode "+cname
+" -borderwidth 1 -relief raised"));
cmd(top, "bind "+cname+" <ButtonRelease-1>"
+" {send tblcmd set "+string j+" "+string i+" 0}");
cmd(top, "bind "+cname+" <Double-Button-1>"
+" {send tblcmd set "+string j+" "+string i+" 1}");
cmd(top, "grid "+cname+" -row "+string i+" -column "+string (j+1) +
" -sticky ews");
}
}
}
# fill table starting at n.
# return actual starting value.
filltable(n: int): int
{
if (n < 0)
n = 0;
if (n + Tablerows * Tablecols > 16rffff)
n = 16rffff - Tablerows * Tablecols;
n -= n % Tablecols;
for (i := 0; i < Tablerows; i++) {
cmd(top, ".tbl.tf.num" + string i +" configure -text '"
+ sys->sprint("%4.4X",n+i*Tablecols));
for (j := 0; j < Tablecols; j++) {
cname := tablecharpath(j, i);
cmd(top, cname + " configure -text '"
+sys->sprint("%c", n + i * Tablecols + j));
}
}
return n;
}
cnumtoint(s: string): int
{
if (len s == 0)
return 0;
if (s[0] == '0' && len s > 1) {
n: int;
if (s[1] == 'x' || s[1] == 'X') {
if (len s < 3)
return 0;
(n, nil) = str->toint(s[2:], 16);
} else
(n, nil) = str->toint(s, 8);
return n;
}
return int s;
}
getfonts(font: string): list of string
{
f := bio->open(font, bio->OREAD);
if (f == nil)
return nil;
# ignore header
if (bio->f.gets('\n') == nil)
return nil;
ret: list of string;
while ((s := bio->f.gets('\n')) != nil) {
(count, wds) := sys->tokenize(s, " \t");
if (count < 3 || count > 4)
continue; # ignore malformed lines
first := cnumtoint(hd wds);
wds = tl wds;
last := cnumtoint(hd wds);
wds = tl wds;
if (tl wds != nil) # if optional third field exists
wds = tl wds; # ignore it
name := hd wds;
if (name != "" && name[len name - 1] == '\n')
name = name[0:len name - 1];
ret = sys->sprint("%.4X-%.4X: %s", first, last, name) :: ret;
}
return ret;
}
getcategories(): list of string
{
f := bio->open(UNIBLOCKS, bio->OREAD);
if (f == nil)
return nil;
ret: list of string;
while ((s := bio->f.gets('\n')) != nil) {
if (s[0] == '#')
continue;
(s, nil) = str->splitr(s, "^\n\r");
if (len s > 0) {
start, end: string;
(start, s) = str->splitl(s, ";");
s = str->drop(s, "; ");
(end, s) = str->splitl(s, ";");
s = str->drop(s, "; ");
ret = start+"-"+end+": "+s :: ret;
}
}
return ret;
}
tkexpand(s: string): string
{
if (len s == 0 || s[0] != '$')
return s;
cmd, tp, name: string;
(cmd, s) = str->splitl(s, " \t");
cmd = cmd[1:];
s = str->drop(s, " \t");
(tp, s) = str->splitl(s, " \t");
s = str->drop(s, " \t");
(name, s) = str->splitl(s, " \t");
s = str->drop(s, " \t");
font := "";
case tp {
"deflt" => font = DEFAULTFONT;
"title" => font = TITLEFONT;
"data" => font = DATAFONT;
"button" => font = BUTTONFONT;
"unicode" => font = currfont;
}
if (font != nil) {
if (font[0] != '/')
font = "/fonts/"+font+".font";
font = "-font "+font;
}
ret := cmd+" "+name+" "+font+" "+s;
return ret;
}
categname(s: string): string
{
r := "Unknown category";
case s {
"Mn" => r = "Mark, Non-Spacing ";
"Mc" => r = "Mark, Combining";
"Nd" => r = "Number, Decimal Digit";
"No" => r = "Number, Other";
"Zs" => r = "Separator, Space";
"Zl" => r = "Separator, Line";
"Zp" => r = "Separator, Paragraph";
"Cc" => r = "Other, Control or Format";
"Co" => r = "Other, Private Use";
"Cn" => r = "Other, Not Assigned";
"Lu" => r = "Letter, Uppercase";
"Ll" => r = "Letter, Lowercase";
"Lt" => r = "Letter, Titlecase ";
"Lm" => r = "Letter, Modifier";
"Lo" => r = "Letter, Other ";
"Pd" => r = "Punctuation, Dash";
"Ps" => r = "Punctuation, Open";
"Pe" => r = "Punctuation, Close";
"Po" => r = "Punctuation, Other";
"Sm" => r = "Symbol, Math";
"Sc" => r = "Symbol, Currency";
"So" => r = "Symbol, Other";
}
return r;
}
fields(s: string, sep: int): list of string
# seperator can't be '^' (see string(2))
{
cl := ""; cl[0] = sep;
ret: list of string;
do {
(l, r) := str->splitr(s, cl);
ret = r :: ret;
if (len l > 0)
s = l[0:len l - 1];
else
s = nil;
} while (s != nil);
return ret;
}
fieldindex(sl: list of string, n: int): string
{
for (; sl != nil; sl = tl sl) {
if (n == 0)
return hd sl;
n--;
}
return nil;
}
push(el: int)
{
if (initelement(el)) {
displ.push(elements[el].name);
}
}
pop(el: int)
# pop elements until we encounter one matching el.
{
while (displ.top() != elements[el].name)
displ.pop();
}
tkchan(nm: string): chan of string
{
c := chan of string;
tk->namechan(top, c, nm);
return c;
}
cmd(top: ref Tk->Toplevel, s: string): string
{
# sys->print("%s\n", s);
e := tk->cmd(top, s);
if (e != nil && e[0] == '!')
sys->fprint(sys->fildes(2), "tk error on '%s': %s\n", s, e);
return e;
}
labelset(t: ref Tk->Toplevel, name: string, val: string)
{
cmd(t, name+" configure -text '"+val);
}
choosefont(ctxt: ref Draw->Context): string
{
font := selectfile->filename(ctxt, top.image, "Select a font", "*.font" :: nil, "/fonts");
if (font != nil) {
ret := cmd(top, ".fontlabel configure"+" -font "+font);
if (len ret > 0 && ret[0] == '!') {
font = nil;
notice("Bad font: "+ret[1:]);
}
}
return font;
}
updatefont()
{
if (elements[TABLE].doneinit) # only if table is being displayed
for (i := 0; i < Tablerows; i++)
for (j := 0; j < Tablecols; j++)
cmd(top, tablecharpath(j, i) + " configure -font "+currfont);
# update the font display table if it's being displayed
for (el := displ.stk; el != nil; el = tl el) {
if (hd el == elements[BYFONT].name) {
initelement(BYFONT);
}
}
inspchan <-= "font";
}
winorg(t: ref Tk->Toplevel): Draw->Point
{
return Draw->Point(int cmd(t, ". cget -x"), int cmd(t, ". cget -y"));
}
Widgetstack.new(wn: string): ref Widgetstack
{
cmd(top, "frame "+wn+" -borderwidth 4 -relief ridge");
return ref Widgetstack(nil, wn);
}
Widgetstack.push(ws: self ref Widgetstack, w: string)
{
if (w == nil)
return;
opts: con " -fill y -side left";
if (ws.stk == nil) {
cmd(top, "pack "+w+" -in "+ws.name+" "+opts);
} else {
cmd(top, "pack "+w+" -after "+hd ws.stk+" "+opts);
}
ws.stk = w :: ws.stk;
}
Widgetstack.pop(ws: self ref Widgetstack): string
{
if (ws.stk == nil) {
sys->fprint(stderr, "widget stack underflow!\n");
exit;
}
old := hd ws.stk;
ws.stk = tl ws.stk;
cmd(top, "pack forget "+old);
return old;
}
Widgetstack.top(ws: self ref Widgetstack): string
{
if (ws.stk == nil)
return nil;
return hd ws.stk;
}
# binary search for key in f.
# code converted from bsd source without permission.
look(f: ref bio->Iobuf, sep: int, key: string): string
{
bot := mid := big 0;
ktop := bio->f.seek(big 0, Sys->SEEKEND);
key = canon(key, sep);
for (;;) {
mid = (ktop + bot) / big 2;
bio->f.seek(mid, Sys->SEEKSTART);
c: int;
do {
c = bio->f.getb();
mid++;
} while (c != bio->EOF && c != bio->ERROR && c != '\n');
(entry, eof) := getword(f);
if (entry == nil && eof)
break;
entry = canon(entry, sep);
case comparewords(key, entry) {
-2 or -1 or 0 =>
if (ktop <= mid)
break;
ktop = mid;
continue;
1 or 2 =>
bot = mid;
continue;
}
break;
}
bio->f.seek(bot, Sys->SEEKSTART);
while (bio->f.seek(big 0, Sys->SEEKRELA) < ktop) {
(entry, eof) := getword(f);
if (entry == nil && eof)
return nil;
word := canon(entry, sep);
case comparewords(key, word) {
-2 =>
return nil;
-1 or 0 =>
return entry;
1 or 2 =>
continue;
}
break;
}
for (;;) {
(entry, eof) := getword(f);
if (entry == nil && eof)
return nil;
word := canon(entry, sep);
case comparewords(key, word) {
-1 or 0 =>
return entry;
}
break;
}
return nil;
}
comparewords(s, t: string): int
{
if (s == t)
return 0;
i := 0;
for (; i < len s && i < len t && s[i] == t[i]; i++)
;
if (i >= len s)
return -1;
if (i >= len t)
return 1;
if (s[i] < t[i])
return -2;
return 2;
}
getword(f: ref bio->Iobuf): (string, int)
{
ret := "";
for (;;) {
c := bio->f.getc();
if (c == bio->EOF || c == bio->ERROR)
return (ret, 0);
if (c == '\n')
break;
ret[len ret] = c;
}
return (ret, 1);
}
canon(s: string, sep: int): string
{
if (sep < 0)
return s;
i := 0;
for (; i < len s; i++)
if (s[i] == sep)
break;
return s[0:i];
}