# # Text and frame management needs a rewrite. # implement Pimpl; include "mods.m"; mods, debug, win, tree: import dat; frame: Framem; Menu: import menus; setcursor, Waiting, Arrow, getfont, Cpointer, cols, panelback, maxpt, cookclick, drawtag, BACK,TEXT, readsnarf, Inset, terminate, writesnarf, readfile, SET, CLEAR, SHAD, BORD, CMtriple, CMdouble : import gui; Ptag, Pline, Pedit, Pshown, Psync, Pdead, Pdirty, Ptbl, Predraw, intag, escape, unescape, nth, Panel: import wpanel; panelctl, panelkbd, panelmouse, tagmouse, Tree: import wtree; Frame: import frame; fixpos, dtxt, strstr, Maxline, Blks, strchr, Str: import tblks; Edit, Edits: import tundo; Ptext: import text; usage: import arg; panels: array of ref Ptext; textmenu: ref Menu; # To allow search from tag lines, anytime a text is selected it becomes # an argument for search, open, etc. The argument is void when they # keyboard is used or a single click is made. seltext: string; pimpl(p: ref Panel): ref Ptext { if (p.implid < 0 || p.implid > len panels || panels[p.implid] == nil) panic("draw: bug: no impl"); return panels[p.implid]; } init(d: Livedat): string { prefixes = list of {"text:", "button:", "label:", "tag:", "tbl:"}; dat = d; initmods(); frame = load Framem Framem->PATH; if (frame == nil) return sprint("loading %s: %r", Framem->PATH); dat = d; str = load String String->PATH; if (str == nil) return sprint("loading %s: %r", String->PATH); text = load Text Text->PATH; if (text == nil) return sprint("loading %s: %r", Text->PATH); tblks = load Tblks Tblks->PATH; if (tblks == nil) return sprint("loading %s: %r", Tblks->PATH); tundo= load Tundo Tundo->PATH; if (tundo == nil) return sprint("loading %s: %r", Tundo->PATH); frame->init(d); tblks->init(sys, str, err, debug['T']); tundo->init(sys, err, debug['T']); text->init(sys, str, frame, err, tblks, tundo, debug['T']); return nil; } settextsize(p: ref Panel) { pi := pimpl(p); pi.rect = p.rect; if (!(p.flags&Pline)){ pi.rect.min.y += 1; } p.maxsz.y = 0; s := pi.blks.pack(); if (p.flags&Pline){ p.minsz.y = p.font.height; # up/down margins if (!(p.flags&Pedit) && len s.s > 0) p.minsz.x = p.font.width(s.s); } else { p.minsz = Point(p.font.height*2,p.font.height*2); p.minsz.y += 1; if (pi.nlines == 0) p.maxsz.y = 0; else { n := pi.nlines + 1; if (n < 3) n = 3; p.maxsz.y = p.font.height * n; p.maxsz.y += 1; } } pi.nrows = pi.rect.dy() / p.font.height; pi.ncols = pi.rect.dx() / p.font.width("M"); } pinit(p: ref Panel) { if (tree == nil) tree = dat->tree; if (textmenu == nil) textmenu = Menu.new(array[] of {"Exec", "Find", "Put", "Apply", "Paste", "Cut", "Close", "Open"}); for (i := 0; i < len panels; i++) if (panels[i] == nil) break; if (i == len panels){ npanels := array[i+16] of ref Ptext; npanels[0:] = panels; panels = npanels; } p.implid = i; pi := panels[i] = Ptext.new(); pi.mlast = 0; # Set Exec by default p.flags &= ~(Pline|Pedit); pi.tab = 4; if (len p.name > 3 && p.name[0:3] == "tbl"){ p.flags |= Ptbl|Pedit; p.wants = Point(1,1); pi.tabtext = "[empty]"; pi.mlast = 5; # Open by default } else if (len p.name > 4 && p.name[0:4] == "text"){ p.flags |= Pedit; p.wants = Point(1,1); pi.mlast = 2; # Find } else if (len p.name > 3 && p.name[0:3] == "tag"){ p.flags |= Pline|Pedit; p.wants = Point(1,0); } else { p.flags |= Pline; } if (p.flags&Pline) p.font = getfont("B"); else p.font = getfont("R"); s := ""; if (p.flags&Pline){ (nil, n) := splitl(p.name, ":"); if (n != nil){ n = n[1:]; (s, nil) = splitl(n, "."); if (s == nil) s = nil; } } pi.blks = Blks.new(s); settextsize(p); } pterm(p: ref Panel) { if (p.implid != -1){ pi := pimpl(p); panels[p.implid] = nil; p.implid = -1; pi.term(); } } dirty(p: ref Panel, set: int) { if (set) p.flags |= Psync; else p.flags &= ~Psync; if (p.flags&Pline) # lines do not get dirty return; old := p.flags; if (set) p.flags |= Pdirty; else p.flags &= ~Pdirty; if (old != p.flags){ if (set) p.fsctl("dirty\n"); else p.fsctl("clean\n"); spawn tree.tags("/"); # we might get here called from tree; avoid deadlock. } } filltext(p: ref Panel) { nl := "\n"; pi := pimpl(p); (i, n) := pi.blks.seek(pi.froff + pi.f.nchars); b := pi.blks.b; for(; i < len b && b[i] != nil && !pi.f.lastlinefull; i++){ pi.f.insert(b[i].s[n:], pi.f.nchars); n = 0; } # Frame may not clear the right of the last line. BUG? # we force that by inserting a fake \n if (!pi.f.lastlinefull){ pi.f.insert(nl, pi.f.nchars); pi.f.delete(pi.f.nchars-1, pi.f.nchars); } # clear the unused part pt := pi.rect.min; pt.y += pi.f.nlines * p.font.height; wr := Rect(pt, pi.rect.max); cback := panelback(p); if (pi.f.ticked && pi.f.p0 == pi.f.p1 && pi.f.p0 == pi.f.nchars){ # avoid clearing the tick hidesel(p); win.image.draw(wr, cback, nil, (0,0)); drawsel(p); } else win.image.draw(wr, cback, nil, (0,0)); } hidesel(p: ref Panel) { pi := pimpl(p); pi.f.drawsel(pi.f.ptofchar(pi.f.p0), pi.f.p0, pi.f.p1, 0); } drawsel(p: ref Panel) { pi := pimpl(p); f := pi.f; ss := fixpos(f.nchars, pi.ss - pi.froff); se := fixpos(f.nchars, pi.se - pi.froff); if(f.ticked && ss == f.p0 && se == f.p1) # it's already there. return; # This code below was taken from acme's # if(f.p1<=ss || se<=f.p0 || ss==se || f.p1==f.p0){ # no overlap or too easy to bother trying if (f.p0 != f.p1 || !(p.flags&Pedit)) f.drawsel(f.ptofchar(f.p0), f.p0, f.p1, 0); if (ss != se || (p.flags&Pedit)) f.drawsel(f.ptofchar(ss), ss, se, 1); } else { if(ss < f.p0){ # extend selection backwards f.drawsel(f.ptofchar(ss), ss, f.p0, 1); }else if(ss > f.p0){ # trim first part of selection f.drawsel(f.ptofchar(f.p0), f.p0, ss, 0); } if(se > f.p1){ # extend selection forwards f.drawsel(f.ptofchar(f.p1), f.p1, se, 1); }else if(se < f.p1){ # trim last part of selection f.drawsel(f.ptofchar(se), se, f.p1, 0); } } f.p0 = ss; f.p1 = se; } tab(p: ref Panel) { pi := pimpl(p); (n, toks) := tokenize(pi.tabtext, "\t\n"); if (n <= 0) return; wl := array[n] of string; for (i := 0; i < n; i++){ wl[i] = hd toks; toks = tl toks; } mint := p.font.width("0"); maxtab := 3 * mint; maxt := maxtab; pi.f.maxtab = 3 * mint; colw := 0; for(i=0; i colw) colw = w; } ncol := 1; if (colw != 0) ncol = pi.rect.dx()/colw; if (ncol < 1) ncol = 1; nrow := (n+ncol-1)/ncol; ns := ""; for(i=0; i= n) break; w := p.font.width(wl[j]); if(maxt-w%maxt < mint){ ns += "\t"; w += mint; } do{ ns += "\t"; w += maxt-(w%maxt); }while(w < colw); } ns += "\n"; } pi.blks = Blks.new(ns); l := len ns; pi.s0 = fixpos(pi.s0, l); pi.ss = fixpos(pi.ss, l); pi.se = fixpos(pi.se, l); pi.froff = fixpos(pi.froff, l); pi.mark = fixpos(pi.mark, l); } drawmark(img: ref Image, ur: Rect, cback: ref Image) { lr := ur; # lr.max.x = lr.min.x + ur.dx()/3; # img.draw(lr, cback, nil, (0,0)); # lr.min.x = lr.max.x; lr.max.x = lr.min.x + ur.dx()/3; img.draw(lr, cols[TEXT], nil, (0,0)); lr.min.x = lr.max.x; lr.max.x = ur.max.x; img.draw(lr, cback, nil, (0,0)); } drawmarks(img: ref Image, pi: ref Ptext, ht: int, prect: Rect, cback: ref Image) { ur := prect; ur.max.y = ur.min.y+1; if (pi.froff == 0) drawmark(img, ur, cback); else img.draw(ur, cback, nil, (0,0)); if (pi.f.nlines + 1 < pi.f.maxlines){ # leave one line for typing dr := prect; dr.min.y += (pi.f.nlines+1)*ht + 2; dr.max.y = dr.min.y + 1; if (dr.max.y <= prect.max.y && dr.min.y > prect.min.y) drawmark(img, dr, cback); } } pdraw(p: ref Panel) { if (!(p.flags&Pshown)) return; w := dat->win; pi := pimpl(p); pi.rect = p.rect; if (!(p.flags&Pline)){ pi.rect.min.y += 1; } if (pi.f != nil){ hidesel(p); pi.f.clear(0); } else pi.f = Frame.new(); fcols := gui->cols[0:gui->NCOL]; cback := panelback(p); fcols[BACK] = cback; pi.f.init(pi.rect, p.font, w.image, fcols); fcols = nil; if (p.flags&Ptbl) tab(p); else pi.f.maxtab = pi.tab * p.font.width("0"); filltext(p); drawsel(p); settextsize(p); if (p.flags&Ptag) drawtag(p); if (!(p.flags&Pline)) drawmarks(win.image, pi, p.font.height, p.rect, cback); } stringlines(s: string): int { nc := n := 0; for (i := 0; i < len s; i++) if (s[i] == '\n' || nc == Maxline){ n++; nc = 0; } else nc++; return n; } # could put the update as a new del+ins event, but how would we # sync our current ongoing edit? # The safe thing to do is to discard any edits and start again. # The application knows best. pupdate(p: ref Panel, d: array of byte) { pi := pimpl(p); s := string d; pi.blks = Blks.new(s); pi.edits = Edits.new(); if (p.flags&Ptbl) pi.tabtext = s; pi.nlines = stringlines(s); pi.froff = fixpos(len s, pi.froff); pi.mark = fixpos(len s, pi.mark); pos := 0; if ( (p.flags&Pline) || !(p.flags&Pedit)) pos= len s; else pos = fixpos(len s, pi.s0); pi.ss = pi.se = pi.s0 = pos; p.flags |= Predraw; } syncchk(p: ref Panel) { if (debug['T']){ # double check that we are really synced with the FS. fname := p.path + "/data"; fd := open(fname, OREAD); data := readfile(fd); if (data != nil){ pi := pimpl(p); ftext := string data; s := pi.blks.pack().s; if (s != ftext){ pi.dump(); l := len s; if (l > len ftext) l = len ftext; for (i := 0; i < l - 1 && s[i] == ftext[i]; i++) ; if (i > 15) i -= 15; else i = 0; sj := len s - 1; fj := len ftext - 1; while(sj > 0 && fj > 0 && s[sj] == ftext[fj]){ sj--; fj--; } fprint(stderr, "after sync text differs: pos %d:\n", i); fprint(stderr, "text[%s]\n\nfile[%s]\n\n", s[i:sj], ftext[i:fj]); panic("insdel bug"); } } } } syncedits(p: ref Panel, edits: list of ref Edit) { ctlstr := ""; for(; edits != nil; edits = tl edits){ ev : string; pick e := hd edits { Ins => ev = sprint("ins %d %s\n", e.pos, e.s); Del => ev = sprint("del %d %d\n", e.pos, len e.s); } ctlstr += escape(ev); } # fsctl would scape our \n's, and we want a single write for the entire # set of updates, if feasible. if (ctlstr != ""){ fname := p.path + "/ctl"; fd := open(fname, OWRITE); if (fd != nil){ if (debug['E']) fprint(stderr, "fsctl: %s: [%s]\n", p.path, ctlstr); seek(fd, big 0, 2); data := array of byte ctlstr; write(fd, data, len data); } } p.flags &= ~Psync; # we're synced now. } applyedit(p: ref Panel, e: ref Edit): int { pi := pimpl(p); if (e != nil) pick ep := e { Ins => pi.ins(ep.s, ep.pos); pi.f.insert(ep.s, ep.pos-pi.froff); return ep.pos + len ep.s; Del => pi.del(len ep.s, ep.pos); pi.f.delete(ep.pos - pi.froff, ep.pos - pi.froff + len ep.s); filltext(p); return ep.pos; } return -1; } undo(p: ref Panel): int { pi := pimpl(p); e := pi.edits.undo(); return applyedit(p, e); } redo(p: ref Panel): int { pi := pimpl(p); e := pi.edits.redo(); return applyedit(p, e); } ins(p: ref Panel, s: string, pos: int) { pi := pimpl(p); pi.ins(s, pos); if (pi.edits.ins(s, pos) < 0){ syncedits(p, pi.edits.sync()); if (pi.edits.ins(s, pos) < 0){ pi.dump(); panic(sprint("text: ins [%s] %d failed", s, pos)); } } } del(p: ref Panel, n: int, pos: int): string { pi := pimpl(p); ds := pi.del(n, pos); if (pi.edits.del(ds, pos) < 0){ syncedits(p, pi.edits.sync()); if (pi.edits.del(ds, pos) < 0){ pi.dump(); panic(sprint("text: del [%s] %d failed", ds, pos)); } } return ds; } pctl(p: ref Panel, s: string) { pi := pimpl(p); odirty := p.flags&Pdirty; if (panelctl(tree, p, s) < 0){ (nargs, args) := tokenize(s, " \t\n"); if (nargs > 0) case hd args { "sel" => pi.ss = int nth(args, 1); pi.se = int nth(args, 2); if (pi.se < pi.ss) pi.se = pi.ss; pi.s0 = pi.ss; "mark" => pi.mark = int nth(args, 1); "tab" => pi.tab = int nth(args, 1); if (pi.tab < 3) pi.tab = 3; if (pi.tab > 10) pi.tab = 10; * => ; # ignore others } } if (odirty && !(p.flags&Pdirty)){ pi.edits.cpos = pi.edits.pos; # potential race here: User puts, editor issues a clean ctl, # and the user adds an edit in the mean while. # If this happens, we could just set Pdirty again. # But let see if the race is a real one. eds := pi.edits.sync(); if (eds != nil) panic("text pctl: clean while dirty edits"); } } # Any of: # o/mero: /path/to/panel ins pos str # o/mero: /path/to/panel del pos n pevent(p: ref Panel, ev: string) { pi := pimpl(p); n := strchr(ev, ' '); ev = ev[n+1:]; n = strchr(ev, ' '); ev = ev[n+1:]; # ins|del ... if (len ev < 5){ fprint(stderr, "o/live: pevent: bad event\n"); return; } op := ev[0:3]; ev = ev[3+1:]; # pos ... n = strchr(ev, ' '); if (n < 0){ fprint(stderr, "o/live: pevent: short event\n"); return; } pos := int ev[0:n]; ev = ev[n+1:]; # n|str if (len ev == 0) return; ev = ev[0:len ev -1]; # remove \n if (op == "del"){ n = int ev; del(p, n, pos); pi.f.delete(pos, pos+n); filltext(p); } else { s := unescape(ev); ins(p, s, pos); # pdraw should refill the frame. (?) if (p.flags&Pline) pos += len s; } pi.ss = pi.se = pi.s0 = pos; # that's the p.flags |= Pdirty; # best we can do. pdraw(p); } writepsel(p: ref Panel) { fd := open("/dev/sel", OWRITE|OTRUNC); if (fd != nil) fprint(fd, "%s\n", p.path); } movetoshow(p: ref Panel, pos: int): int { pi := pimpl(p); if (pos < pi.froff || (pos >= pi.froff + pi.f.nchars && pi.f.nlines == pi.f.maxlines)){ pi.gotopos(pos); pi.froff = pi.scroll(-pi.f.nlines/2); return 1; } return 0; } cut(p: ref Panel, putsnarf: int): int { pi := pimpl(p); if (pi.ss == pi.se) return 0; p0 := pi.ss - pi.froff; p1 := pi.se - pi.froff; s := del(p, pi.se - pi.ss, pi.ss); pi.f.delete(p0, p1); pi.se = pi.s0 = pi.ss; filltext(p); if (putsnarf){ writesnarf(s); writepsel(p); } return 1; } paste(p: ref Panel, pos: int): int { pi := pimpl(p); s := readsnarf(); if (len s > 0){ ins(p, s, pos); pi.f.insert(s, pos - pi.froff); pi.ss = pi.s0 = pos; pi.se = pos + len s; drawsel(p); writepsel(p); } return 1; } lastcmd: string; exec(p: ref Panel, pos: int) { pi := pimpl(p); s := pi.blks.pack(); (ws, we) := pi.wordat(pos, 1, 1); c := s.s[ws:we]; lastcmd = c; if (c == "Exit") terminate(); pi.ss = pi.s0 = ws; pi.se = we; drawsel(p); p.fsctl("exec " + c + "\n"); } look(p: ref Panel, pos: int) { pi := pimpl(p); s := pi.blks.pack(); if (seltext != nil){ p.fsctl("look " + seltext + "\n"); return; } (ws, we) := pi.wordat(pos, 1, 1); c := s.s[ws:we]; pi.ss = pi.s0 = ws; pi.se = we; drawsel(p); p.fsctl("look " + c + "\n"); } apply(p: ref Panel, nil: int) { if (lastcmd != nil){ writepsel(p); p.fsctl("apply " + lastcmd + "\n"); } } search(p: ref Panel, pos: int) { ws, we: int; txt: string; pi := pimpl(p); s := pi.blks.pack(); if (seltext == nil){ (ws, we) = pi.wordat(pos, 0, 1); txt = s.s[ws:we]; } else { we = pos+1; txt = seltext; } i := strstr(s.s[we:], txt); if (i < 0) i = strstr(s.s, txt); else i += we; if (i < 0) return; if (debug['T']) fprint(stderr, "search: %s: pos %d\n", dtxt(txt), i); pi.ss = pi.s0 = i; pi.se = pi.ss+ len txt; if (movetoshow(p, pi.ss)) pdraw(p); else drawsel(p); pt := pi.f.ptofchar(pi.f.p0); pt.add(pi.rect.min); pt.add((p.font.width(txt[0:1])-2, p.font.height -2)); win.wmctl("ptr " + string pt.x + " " + string pt.y); } frscroll(p: ref Panel, nlines: int) { pi := pimpl(p); if (nlines == 0){ return; } noff := pi.scroll(nlines); if (noff < pi.froff){ s := pi.blks.pack().s; s = s[noff:pi.froff]; pi.f.insert(s, 0); pi.froff = noff; } else if (noff > pi.froff){ if (pi.f.nchars > 0) if (noff < pi.froff + pi.f.nchars) pi.f.delete(0, noff - pi.froff); else if (pi.f.nchars > 0) pi.f.delete(0, pi.f.nchars - 1); pi.froff = noff; filltext(p); } } select(p: ref Panel, pos: int, m: ref Cpointer, mc: chan of ref Cpointer): ref Cpointer { pi := pimpl(p); b := m.buttons; s := pi.blks.pack(); if (m.flags&(CMdouble|CMtriple)){ long := m.flags&CMtriple; (ws, we) := pi.wordat(pos, long, 0); if (debug['T']) fprint(stderr, "getword: pos %d long %d: %d %d %s\n", pos, long, ws, we, dtxt(s.s[ws:we])); pi.ss = pi.s0 = ws; pi.se = we; writepsel(p); drawsel(p); do { m = <-mc; } while (m.buttons == b); } else { pi.s0 = pos; m = pi.f.select(m, mc, frscroll, p); if (pi.f.p0 + pi.froff < pi.s0){ pi.ss = pi.f.p0 + pi.froff; pi.se = pi.s0; } else { pi.ss = pi.s0; pi.se = pi.f.p1 + pi.froff; } if (!(p.flags&Pedit) && pi.ss == pi.se) hidesel(p); else drawsel(p); } if (pi.ss == pi.se) seltext = nil; else seltext = s.s[pi.ss:pi.se]; return m; } pmouse1(p: ref Panel, pos: int, m: ref Cpointer, mc: chan of ref Cpointer) { pi := pimpl(p); m = select(p, pos, m, mc); case m.buttons { 0 => return; 3 => if (cut(p, 1)) dirty(p, 1); 5 => if (cut(p, 0)) pos = pi.ss; if (paste(p, pos)) dirty(p, 1); } while(m.buttons) m = <-mc; } pmouse2(p: ref Panel, pos: int, m: ref Cpointer, mc: chan of ref Cpointer) { pi := pimpl(p); if (pos >= pi.ss && pos < pi.se){ m = <-mc; if (m.buttons == 0) exec(p, pos); } else { m = select(p, pos, m, mc); if (m.buttons == 0) exec(p, pos); } while(m.buttons) m = <-mc; } # To avoid blinking, we use double buffering during # scroll operations. This is because we redraw the whole frame, # and do not shift rectangles around. This makes it easier to draw # overlayed scroll bars. scrdraw(p: ref Panel, i: ref Image): ref Image { pi := pimpl(p); r := Rect((0,0), (p.rect.dx(), p.rect.dy())); if (i == nil) i = win.display.newimage(r, win.image.chans, 0, Draw->White); fcols := gui->cols[0:gui->NCOL]; cback := panelback(p); fcols[BACK] = cback; i.draw(i.r, cback, nil, (0,0)); pi.f.clear(0); fr := r; if (!(p.flags&Pline)){ fr.min.y += 1; } pi.f.init(fr, p.font, i, fcols); if (p.flags&Ptbl) pi.f.maxtab = 3 * p.font.width("0"); else pi.f.maxtab = pi.tab * p.font.width("0"); filltext(p); drawsel(p); if (!(p.flags&Pline)) drawmarks(i, pi, p.font.height, r, cback); drawscrollbar(p, i); win.image.draw(p.rect, i, nil, (0,0)); return i; } scrdone(p: ref Panel) { pi := pimpl(p); pi.f.clear(0); pi.f = nil; pdraw(p); } drawscrollbar(p: ref Panel, i: ref Image) { Barwid: con 25; Barht: con 120; pi := pimpl(p); bar, r: Rect; bar.max.x = r.max.x = i.r.max.x - Inset - 3; bar.min.x = r.min.x = r.max.x - Barwid; r.min.y = i.r.min.y + Inset; ysz := Barht; r.max.y = r.min.y + ysz; if (r.max.y + 3 > i.r.max.y){ r.max.y = i.r.max.y - 3; ysz = r.dy(); } i.draw(r.addpt((2,2)), cols[TEXT], cols[SHAD], (0,0)); i.draw(r, cols[CLEAR], cols[SHAD], (0,0)); l := len pi.blks.b[0].s; y0 := dy := 0; if (l > 0){ y0 = ysz * pi.froff / l; dy = ysz * pi.f.nchars / l; if (dy < 3) dy = 3; } else { y0 = 0; dy = r.dy(); } bar.min.y = r.min.y + y0; bar.max.y = bar.min.y + dy; if (bar.max.y > r.max.y) bar.max.y = r.max.y; if (bar.min.y > bar.max.y - 2) bar.min.y = bar.max.y - 2; i.draw(bar.addpt((3,3)), cols[TEXT], cols[SHAD], (0,0)); i.draw(bar, cols[SET], nil, (0,0)); i.border(r, 1, cols[BORD], (0,0)); } jumpscale(p: ref Panel, xy: Point): real { pi := pimpl(p); s := pi.blks.pack(); dy := xy.y - pi.rect.min.y; if (dy > pi.rect.max.y - xy.y) dy = pi.rect.max.y - xy.y; dc := len s.s - pi.froff; if (dc < pi.froff) dc = pi.froff; if (dy < 1) dy = 1; return (real dc) / (real dy); } pmouse3scrl(p: ref Panel, pos: int, m: ref Cpointer, mc: chan of ref Cpointer) { pi := pimpl(p); xy := m.xy; jfactor := real 0; jy := xy.y; old := pi.froff; i := scrdraw(p, nil); b := m.buttons; do { if (jfactor <= real 0.00001){ pos = pi.froff; jfactor = jumpscale(p, xy); } jpos := pos + int (real (xy.y - jy) * jfactor); if (jpos == old || !pi.gotopos(jpos)) ; # sys->sleep(10); else { old = jpos; scrdraw(p, i); } more := 0; m = <- mc; do { alt { m = <-mc => more=1; * => more = 0; } } while(more && m.buttons == b); xy = m.xy; } while(m.buttons); scrdone(p); } pmouse3(p: ref Panel, pos: int, m: ref Cpointer, mc: chan of ref Cpointer) { pi := pimpl(p); m = <- mc; case m.buttons { 0 => textmenu.last = pi.mlast; cmd := textmenu.run(m, mc); if (debug['T']) fprint(stderr, "pmouse3: cmd: %s at %d\n", cmd, pos); case cmd { "scroll" => pmouse3scrl(p, pos, m, mc); "Find" => search(p, pos); "Open" => look(p, pos); "Exec" => exec(p, pos); "Apply" => apply(p, pos); "Cut" => if (cut(p, 1)) dirty(p, 1); textmenu.last = 3; "Paste" => if (paste(p, pos)) dirty(p, 1); * => p.fsctl("exec " + cmd + "\n"); } pi.mlast = textmenu.last; 4 => pmouse3scrl(p, pos, m, mc); * => do m = <-mc; while (m.buttons); } } pmouse(p: ref Panel, m: ref Cpointer, mc: chan of ref Cpointer) { if ((p.flags&Ptag) && intag(p, m.xy)){ tagmouse(tree, p, m, mc); return; } pi := pimpl(p); # mouse movement syncs any pending edit. eds := pi.edits.sync(); if (eds != nil){ syncedits(p, eds); syncchk(p); } pos := pi.froff + pi.f.charofpt(m.xy); case m.buttons { 1 => # For tbls, this should probably allow drag&drop pmouse1(p, pos, m, mc); if (debug['T'] > 1) pi.dump(); 2 => pmouse2(p, pos, m, mc); if (debug['T'] > 1) pi.dump(); 4 => pmouse3(p, pos, m, mc); if (debug['T'] > 1) pi.dump(); } drawsel(p); } pkbd(p: ref Panel, k: int) { Killword: con 16r17; # C-w pi := pimpl(p); pos := pi.ss; s := ""; s[0] = k; if (!(p.flags&Pedit)){ p.fsctl("keys " + s + "\n"); return; } case k { '\b' => if (!cut(p, 1) && pos > 0){ pos--; if (movetoshow(p, pos)) pdraw(p); del(p, 1, pos); if (pos < pi.froff | pos > pi.froff + pi.f.nchars + 1) panic("pos del bug"); pi.f.delete(pos-pi.froff, pos-pi.froff+1); pi.ss = pi.se = pi.s0 = pos; } dirty(p, 1); filltext(p); Killword => (ws, we) := pi.wordat(pos, 1, 1); if (ws != we){ pos = ws; n := we - ws; if (movetoshow(p, pos)) pdraw(p); del(p, n, pos); if (pos < pi.froff | pos > pi.froff + pi.f.nchars + 1) panic("pos killword bug"); pi.f.delete(pos-pi.froff, pos+n-pi.froff); pi.ss = pi.se = pi.s0 = pos; } Keyboard->Del => p.fsctl("interrupt\n"); Keyboard->Left or Keyboard->Right=> if (k == Keyboard->Left) pos = undo(p); else pos = redo(p); if (pos >= 0){ dirty(p, pi.edits.pos != pi.edits.cpos); pi.ss = pi.se = pi.s0 = pos; if (movetoshow(p, pos)) pdraw(p); else drawsel(p); } Keyboard->Up or Keyboard->Down => if (p.flags&Pline) return; n := pi.f.nlines / 3; if (n < 2) n = 2; if (k == Keyboard->Up) n = -n; noff := pi.scroll(n); if (noff != pi.froff){ pi.froff = noff; pdraw(p); } Keyboard->Esc => if (pi.s0 < pos){ pi.ss = pi.s0; pi.se = pos; } else { pi.ss = pos; pi.se = pi.s0; } drawsel(p); * => if (k == '\n' && (p.flags&Pline)){ if (pi.s0 < pos){ pi.ss = pi.s0; pi.se = pos; } else { pi.ss = pos; pi.se = pi.s0; } drawsel(p); exec(p, pos); } else { cut(p, 1); if (!(p.flags&Pline) && movetoshow(p, pos)) pdraw(p); ins(p, s, pos); pi.f.insert(s, pos - pi.froff); pi.ss = pi.se = pos+1; if (pos >= pi.froff + pi.f.nchars - 2 && !(p.flags&Pline)){ noff := pi.scroll(pi.f.nlines/3); if (noff != pi.froff){ pi.froff = noff; pdraw(p); } } dirty(p, 1); if ((p.flags&Pedit) && (k == '\n' || (p.flags&Pline))) settextsize(p); } } if (debug['T'] > 1) pi.dump(); } psync(p: ref Panel) { fprint(stderr, "owptext: sync called\n"); pi := pimpl(p); fd := open(p.path + "/data", OWRITE|OTRUNC); if (fd == nil) return; s := pi.blks.pack(); data := array of byte s.s; if (write(fd, data, len data) != len data) fprint(stderr, "o/live: sync %s: %r\n", p.path); }