implement WmMash; include "sys.m"; sys: Sys; FileIO: import sys; include "draw.m"; draw: Draw; Context: import draw; include "tk.m"; tk: Tk; include "tkclient.m"; tkclient: Tkclient; include "plumbmsg.m"; plumbmsg: Plumbmsg; Msg: import plumbmsg; include "workdir.m"; workdir: Workdir; WmMash: module { init: fn(ctxt: ref Draw->Context, args: list of string); }; Command: module { tkinit: fn(ctxt: ref Draw->Context, t: ref Tk->Toplevel, args: list of string); }; BS: con 8; # ^h backspace character BSW: con 23; # ^w bacspace word BSL: con 21; # ^u backspace line EOT: con 4; # ^d end of file ESC: con 27; # hold mode HIWAT: con 2000; # maximum number of lines in transcript LOWAT: con 1500; # amount to reduce to after high water Name: con "Mash"; Rdreq: adt { off: big; nbytes: int; fid: int; rc: chan of (array of byte, string); }; shwin_cfg := array[] of { "menu .m", ".m add command -text Cut -command {send edit cut}", ".m add command -text Paste -command {send edit paste}", ".m add command -text Snarf -command {send edit snarf}", ".m add command -text Send -command {send edit send}", "frame .b -bd 1 -relief ridge", "frame .ft -bd 0", "scrollbar .ft.scroll -width 14 -bd 0 -relief ridge -command {.ft.t yview}", "text .ft.t -bd 1 -relief flat -width 520 -height 7c -yscrollcommand {.ft.scroll set}", "pack .ft.scroll -side left -fill y", "pack .ft.t -fill both -expand 1", "pack .Wm_t -fill x", "pack .b -anchor w -fill x", "pack .ft -fill both -expand 1", "pack propagate . 0", "focus .ft.t", "bind .ft.t {send keys {%A}}", "bind .ft.t {send keys {%A}}", "bind .ft.t {send keys {%A}}", "bind .ft.t +{grab set .ft.t; send but1 pressed}", "bind .ft.t +{grab set .ft.t; send but1 pressed}", "bind .ft.t +{grab release .ft.t; send but1 released}", "bind .ft.t {send but2 %X %Y}", "bind .ft.t {}", "bind .ft.t {}", "bind .ft.t {send but3 pressed}", "bind .ft.t {send but3 released %x %y}", "bind .ft.t {}", "bind .ft.t {}", "bind .ft.t {}", "bind .ft.t {}", "update" }; rdreq: list of Rdreq; menuindex := "0"; holding := 0; plumbed := 0; rawon := 0; rawinput := ""; init(ctxt: ref Context, argv: list of string) { s: string; sys = load Sys Sys->PATH; if (ctxt == nil) { sys->fprint(sys->fildes(2), "mash: no window context\n"); raise "fail:bad context"; } draw = load Draw Draw->PATH; tk = load Tk Tk->PATH; tkclient = load Tkclient Tkclient->PATH; plumbmsg = load Plumbmsg Plumbmsg->PATH; sys->pctl(Sys->FORKNS | Sys->NEWPGRP, nil); tkclient->init(); if(plumbmsg->init(1, nil, 0) >= 0){ plumbed = 1; workdir = load Workdir Workdir->PATH; } argv = tl argv; # strip off command name (t, titlectl) := tkclient->toplevel(ctxt, "", Name, Tkclient->Appl); edit := chan of string; tk->namechan(t, edit, "edit"); # mash := chan of string; # tk->namechan(t, mash, "mash"); tkcmds(t, shwin_cfg); tkclient->onscreen(t, nil); tkclient->startinput(t, "kbd"::"ptr"::nil); ioc := chan of (int, ref FileIO, ref FileIO, string); spawn newsh(ctxt, t, ioc, argv); (pid, file, filectl, consfile) := <-ioc; if(file == nil || filectl == nil) { sys->print("newsh: %r\n"); return; } keys := chan of string; tk->namechan(t, keys, "keys"); but1 := chan of string; tk->namechan(t, but1, "but1"); but2 := chan of string; tk->namechan(t, but2, "but2"); but3 := chan of string; tk->namechan(t, but3, "but3"); button1 := 0; button3 := 0; rdrpc: Rdreq; # outpoint is place in text to insert characters printed by programs tk->cmd(t, ".ft.t mark set outpoint end; .ft.t mark gravity outpoint left"); for(;;) alt { c := <-t.ctxt.kbd => tk->keyboard(t, c); c := <-t.ctxt.ptr => tk->pointer(t, *c); c := <-t.ctxt.ctl or c = <-t.wreq => tkclient->wmctl(t, c); menu := <-titlectl => if(menu == "exit") { kill(pid); return; } tkclient->wmctl(t, menu); tk->cmd(t, "focus .ft.t"); ecmd := <-edit => editor(t, ecmd); sendinput(t); tk->cmd(t, "focus .ft.t"); c := <-keys => cut(t, 1); if(rawon) { rawinput += c[1:2]; rawinput = sendraw(rawinput); break; } char := c[1]; if(char == '\\') char = c[2]; update := ";.ft.t see insert;update"; case char { * => tk->cmd(t, ".ft.t insert insert "+c+update); '\n' or EOT => tk->cmd(t, ".ft.t insert insert "+c+update); sendinput(t); BS => tk->cmd(t, ".ft.t tkTextDelIns -c"+update); BSL => tk->cmd(t, ".ft.t tkTextDelIns -l"+update); BSW => tk->cmd(t, ".ft.t tkTextDelIns -w"+update); ESC => holding ^= 1; color := "blue"; if(!holding){ color = "black"; tkclient->settitle(t, Name); sendinput(t); }else tkclient->settitle(t, Name+" (holding)"); tk->cmd(t, ".ft.t configure -foreground "+color+update); } c := <-but1 => button1 = (c == "pressed"); button3 = 0; # abort any pending button 3 action c := <-but2 => if(button1){ cut(t, 1); tk->cmd(t, "update"); break; } (nil, l) := sys->tokenize(c, " "); x := int hd l - 50; y := int hd tl l - int tk->cmd(t, ".m yposition "+menuindex) - 10; tk->cmd(t, ".m activate "+menuindex+"; .m post "+string x+" "+string y+ "; grab set .m; update"); button3 = 0; # abort any pending button 3 action c := <-but3 => if(c == "pressed"){ button3 = 1; if(button1){ paste(t); tk->cmd(t, "update"); } break; } if(plumbed == 0 || button3 == 0 || button1 != 0) break; button3 = 0; # Plumb message triggered by release of button 3 (nil, l) := sys->tokenize(c, " "); x := int hd tl l; y := int hd tl tl l; index := tk->cmd(t, ".ft.t index @"+string x+","+string y); selindex := tk->cmd(t, ".ft.t tag ranges sel"); if(selindex != "") insel := tk->cmd(t, ".ft.t compare sel.first <= "+index)=="1" && tk->cmd(t, ".ft.t compare sel.last >= "+index)=="1"; else insel = 0; attr := ""; if(insel) text := tk->cmd(t, ".ft.t get sel.first sel.last"); else{ # have line with text in it # now extract whitespace-bounded string around click (nil, w) := sys->tokenize(index, "."); charno := int hd tl w; left := tk->cmd(t, ".ft.t index {"+index+" linestart}"); right := tk->cmd(t, ".ft.t index {"+index+" lineend}"); line := tk->cmd(t, ".ft.t get "+left+" "+right); for(i:=charno; i>0; --i) if(line[i-1]==' ' || line[i-1]=='\t') break; for(j:=charno; jinit(), "text", attr, array of byte text); if(msg.send() < 0) sys->fprint(sys->fildes(2), "sh: plumbing write error: %r\n"); rdrpc = <-filectl.read => if(rdrpc.rc == nil) continue; rdrpc.rc <-= ( nil, "not allowed" ); (nil, data, nil, wc) := <-filectl.write => if(wc == nil) { # consctl closed - revert to cooked mode rawon = 0; continue; } (nc, cmdlst) := sys->tokenize(string data, " \n"); if(nc == 1) { case hd cmdlst { "rawon" => rawon = 1; rawinput = ""; # discard previous input advance := string (len tk->cmd(t, ".ft.t get outpoint end") +1); tk->cmd(t, ".ft.t mark set outpoint outpoint+" + advance + "chars"); "rawoff" => rawon = 0; * => wc <-= (0, "unknown consctl request"); continue; } wc <-= (len data, nil); continue; } wc <-= (0, "unknown consctl request"); rdrpc = <-file.read => if(rdrpc.rc == nil) { (ok, nil) := sys->stat(consfile); if (ok < 0) return; continue; } append(rdrpc); sendinput(t); (off, data, fid, wc) := <-file.write => if(wc == nil) { (ok, nil) := sys->stat(consfile); if (ok < 0) return; continue; } cdata := stripbs(t, string data); ncdata := string len cdata + "chars;"; moveins := insat(t, "outpoint"); tk->cmd(t, ".ft.t insert outpoint '"+ cdata); wc <-= (len data, nil); data = nil; s = ".ft.t mark set outpoint outpoint+" + ncdata; s += ".ft.t see outpoint;"; if(moveins) s += ".ft.t mark set insert insert+" + ncdata; s += "update"; tk->cmd(t, s); nlines := int tk->cmd(t, ".ft.t index end"); if(nlines > HIWAT){ s = ".ft.t delete 1.0 "+ string (nlines-LOWAT) +".0;update"; tk->cmd(t, s); } } } RPCread: type (big, int, int, chan of (array of byte, string)); append(r: RPCread) { t := r :: nil; while(rdreq != nil) { t = hd rdreq :: t; rdreq = tl rdreq; } rdreq = t; } insat(t: ref Tk->Toplevel, mark: string): int { return tk->cmd(t, ".ft.t compare insert == "+mark) == "1"; } insininput(t: ref Tk->Toplevel): int { if(tk->cmd(t, ".ft.t compare insert >= outpoint") != "1") return 0; return tk->cmd(t, ".ft.t compare {insert linestart} == {outpoint linestart}") == "1"; } isalnum(s: string): int { if(s == "") return 0; c := s[0]; if('a' <= c && c <= 'z') return 1; if('A' <= c && c <= 'Z') return 1; if('0' <= c && c <= '9') return 1; if(c == '_') return 1; if(c > 16rA0) return 1; return 0; } stripbs(t: ref Tk->Toplevel, s: string): string { l := len s; for(i := 0; i < l; i++) if(s[i] == '\b') { pre := ""; rem := ""; if(i + 1 < l) rem = s[i+1:]; if(i == 0) { # erase existing character in line if(tk->cmd(t, ".ft.t get " + "{outpoint linestart} outpoint") != "") tk->cmd(t, ".ft.t delete outpoint-1char"); } else { if(s[i-1] != '\n') # don't erase newlines i--; if(i) pre = s[:i]; } s = pre + rem; l = len s; i = len pre - 1; } return s; } editor(t: ref Tk->Toplevel, ecmd: string) { s, snarf: string; case ecmd { "cut" => menuindex = "0"; cut(t, 1); "paste" => menuindex = "1"; paste(t); "snarf" => menuindex = "2"; if(tk->cmd(t, ".ft.t tag ranges sel") == "") break; snarf = tk->cmd(t, ".ft.t get sel.first sel.last"); tkclient->snarfput(snarf); "send" => menuindex = "3"; if(tk->cmd(t, ".ft.t tag ranges sel") != ""){ snarf = tk->cmd(t, ".ft.t get sel.first sel.last"); tkclient->snarfput(snarf); }else snarf = tkclient->snarfget(); if(snarf != "") s = snarf; else return; if(s[len s-1] != '\n' && s[len s-1] != EOT) s[len s] = '\n'; tk->cmd(t, ".ft.t see end; .ft.t insert end '"+s); tk->cmd(t, ".ft.t mark set insert end"); tk->cmd(t, ".ft.t tag remove sel sel.first sel.last"); } tk->cmd(t, "update"); } cut(t: ref Tk->Toplevel, snarfit: int) { if(tk->cmd(t, ".ft.t tag ranges sel") == "") return; if(snarfit) tkclient->snarfput(tk->cmd(t, ".ft.t get sel.first sel.last")); tk->cmd(t, ".ft.t delete sel.first sel.last"); } paste(t: ref Tk->Toplevel) { snarf := tkclient->snarfget(); if(snarf == "") return; cut(t, 0); tk->cmd(t, ".ft.t insert insert '"+snarf); tk->cmd(t, ".ft.t tag add sel insert-"+string len snarf+"chars insert"); sendinput(t); } sendinput(t: ref Tk->Toplevel) { if(holding) return; input := tk->cmd(t, ".ft.t get outpoint end"); slen := len input; if(slen == 0 || rdreq == nil) return; r := hd rdreq; for(i := 0; i < slen; i++) if(input[i] == '\n' || input[i] == EOT) break; if(i >= slen && slen < r.nbytes) return; if(i >= r.nbytes) i = r.nbytes-1; advance := string (i+1); if(input[i] == EOT) input = input[0:i]; else input = input[0:i+1]; rdreq = tl rdreq; alt { r.rc <-= (array of byte input, "") => tk->cmd(t, ".ft.t mark set outpoint outpoint+" + advance + "chars"); * => # requester has disappeared; ignore his request and try again sendinput(t); } } sendraw(input : string) : string { i := len input; if(i == 0 || rdreq == nil) return input; r := hd rdreq; rdreq = tl rdreq; if(i > r.nbytes) i = r.nbytes; alt { r.rc <-= (array of byte input[0:i], "") => input = input[i:]; * => ;# requester has disappeared; ignore his request and try again } return input; } newsh(ctxt: ref Context, t: ref Tk->Toplevel, ioc: chan of (int, ref FileIO, ref FileIO, string), args: list of string) { pid := sys->pctl(sys->NEWFD, nil); sh := load Command "/dis/mash.dis"; if(sh == nil) { ioc <-= (0, nil, nil, nil); return; } tty := "cons."+string pid; sys->bind("#s","/chan",sys->MBEFORE); fio := sys->file2chan("/chan", tty); fioctl := sys->file2chan("/chan", tty + "ctl"); ioc <-= (pid, fio, fioctl, "/chan/"+tty); if(fio == nil || fioctl == nil) return; sys->bind("/chan/"+tty, "/dev/cons", sys->MREPL); sys->bind("/chan/"+tty+"ctl", "/dev/consctl", sys->MREPL); fd0 := sys->open("/dev/cons", sys->OREAD|sys->ORCLOSE); fd1 := sys->open("/dev/cons", sys->OWRITE); fd2 := sys->open("/dev/cons", sys->OWRITE); sh->tkinit(ctxt, t, "mash" :: args); } kill(pid: int) { fd := sys->open("#p/"+string pid+"/ctl", sys->OWRITE); if(fd != nil) sys->fprint(fd, "killgrp"); } tkcmds(t: ref Tk->Toplevel, cfg: array of string) { for(i := 0; i < len cfg; i++) tk->cmd(t, cfg[i]); }