Plan 9 from Bell Labs’s /usr/web/sources/contrib/nemo/octopus/port/x/xedit.b

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


implement Oxedit;
include "mods.m";
	msg, newedit: import oxex;
tidgen := 0;
pidgen := 0;

init(d: Oxdat)
{
	initmods(d->mods);
}


Edit.text(ed: self ref Edit): string
{
	text: string;
	pick edp := ed {
	Dir =>
		text = sprint("d %s [%s]", ed.path, ed.dir);
	Msg =>
		text = sprint("m %s [%s]", ed.path, ed.dir);
	File =>
		text = sprint("f %s [%s]", ed.path, ed.dir);
		if (edp.dirty)
			text += " dirty";
		else
			text += " clean";
	}
	return text;
}


Tree.new(path: string): ref Tree
{
	tid := tidgen++;
	if (debug)
		fprint(stderr, "o/x: new tree %s id %d\n", path, tid);
	tr := ref Tree(tid, path, nil, nil, nil, nil);
	trees = tr::trees;
	return tr;
}

Tree.find(tid: int): ref Tree
{
	for (trl := trees; trl != nil; trl = tl trl){
		tr := hd trl;
		if (tr.tid == tid)
			return tr;
	}
	return nil;
}

Tree.close(tr: self ref Tree)
{
	edl := tr.eds;		# File.close will try to tr.deledit()
	tr.eds = nil;		# Avoid that while we are scanning that list.
	for (; edl != nil; edl = tl edl)
		(hd edl).close();
	if (tr.col != nil){
		tr.col.close();
		tr.col = nil;
	}
	if (tr.tag != nil){
		tr.tag.close();
		tr.tag = nil;
	}
	tr.tid = -1;	# posion
	nl: list of ref Tree;
	nl = nil;
	for (trl := trees; trl != nil; trl = tl trl)
		if (hd trl != tr)
			nl = hd trl :: nl;
	if (debug)
		fprint(stderr, "o/x: close tree %s\n", tr.path);
	trees = nl;
}

Tree.mk(tr: self ref Tree)
{
	id := ++pidgen;
	tr.col = ui.new("col:tree", id);
	if (tr.col == nil)
		error("o/x: ui.new");
	tr.tag = tr.col.new("tag:tree", id);
	if (tr.tag == nil)
		error("o/x: ui.new");
	fd := open(tr.tag.path+"/data", OWRITE|OTRUNC);
	if (fd == nil)
		error("o/x: tag open");
	text := sprint("Ox %d", pctl(0, nil));
	text += sprint(" %s End Dup ", tr.path);
	data := array of byte text;
	if (write(fd, data, len data) != len data)
		error("o/x: tag write");
	tr.xtag = tr.col.new("tag:cmds", id);
	if (tr.xtag == nil)
		error("o/x: ui.new");
	fd = open(tr.xtag.path+"/data", OWRITE|OTRUNC);
	if (fd == nil)
		error("o/x: tag open");
	data = array of byte "no cmds";
	if (write(fd, data, len data) != len data)
		error("o/x: tag write");
}

Tree.findedit(tr: self ref Tree, name: string): ref Edit
{
	for (edl := tr.eds; edl != nil; edl = tl edl){
		ed := hd edl;
		if (ed.path == name)
			return ed;
	}
	return nil;
}

lrul(edl: list of ref Edit, k: int): (ref Edit, int)
{
	led: ref Edit;
	led = nil;
	ltime := now();
	n := 0;
	for (; edl != nil; edl = tl edl){
		ed := hd edl;
		if (tagof(ed) == k){
			n++;
			if (!ed.keep && !ed.dirty && ed.lru < ltime){
				led = ed;
				ltime = ed.lru;
			}
		}
	}
	return (led, n);
}

# No more than 2 dirs in a tree, no more than 6 edits.
# For files, it would be nice to keep a global policy in
# the entire omero regarding when to close forgotten panels.
# Also, when to minimize them
Tree.lru(tr: self ref Tree)
{
	Ndirs: con 2;
	Neds: con 6;
	Nmsgs: con 2;

	(ed, n) := lrul(tr.eds, tagof(Edit.Dir));
	if (ed != nil && n > Ndirs)
		ed.close();
	(ed, n) = lrul(tr.eds, tagof(Edit.Msg));
	if (ed != nil && n > Nmsgs){
		ed.close();
		n--;
	}
	nm := n;
	(ed, n) = lrul(tr.eds, tagof(Edit.File));
	if (ed != nil && n + nm > Neds)
		ed.close();
}

Tree.addedit(tr: self ref Tree, ed: ref Edit)
{
	tr.lru();
	tr.eds = ed::tr.eds;
}

Tree.deledit(tr: self ref Tree, ed: ref Edit)
{
	nl: list of ref Edit;
	nl = nil;
	for (edl := tr.eds; edl != nil; edl = tl edl){
		if (hd edl != ed)
			nl = hd edl :: nl;
	}
	tr.eds = nl;
}

Tree.dump(tr: self ref Tree)
{
	fprint(stderr, "tree %s\n", tr.path);
	for (edl := tr.eds; edl != nil; edl = tl edl)
		fprint(stderr, "\t%s\n", (hd edl).text());
}

# Precaution. Copy the file being edited to /tmp, just in case we
# screw things up

safe(name: string)
{
	fname := "/tmp/" + names->basename(name, nil);
	fd := create(fname, OWRITE, 8r664);
	sfd := open(name, OREAD);
	if (fd == nil || sfd == nil || panels->copy(fd, sfd) < 0)
		fprint(stderr, "o/x: can't backup %s: %r\n", fname);
}

msgpath(n: string): string
{
	(p, nil) := splitl(n[1:], "] ");
	if (p == nil || p == "")
		p = n[1:];
	return p;
}


Edit.new(name: string, tid: int, msg: int): ref Edit
{
	ed: ref Edit;
	if (msg){
		path := name;
		if (name[0] == '[')
			path = msgpath(name);
		else
			name = sprint("[%s]", name);
		ed = ref Edit.Msg(name, path, tid, 0, 0, now(), nil, nil, nil, nil, nil, nil, 0, 0, 0);
	} else {
		(e, d) := stat(name);
		if (e < 0)
			return nil;
		if (d.qid.qtype&QTDIR)
			ed = ref Edit.Dir(name, name, tid, 0, 0, now(), nil, nil, nil, nil, nil, nil, 0, 0, 0, Qid(big 0, 0, QTDIR));
		else {
			safe(name);
			ed = ref Edit.File(name, names->dirname(name), tid, 0, 0, now(), nil, nil, nil, nil, nil, nil, 0, 0, 0, Qid(big 0, 0, 0), nil);
		}
	}
	if (debug)
		fprint(stderr, "o/x: new edit %s tid %d\n", name, tid);
	return ed;
}

Edit.close(ed: self ref Edit)
{
	tid := ed.tid;
	if (tid == -1) # already closed
		return;
	ed.tid = -1;
	if (ed.col != nil)
		ed.col.close();
	if (ed.tag != nil)
		ed.tag.close();
	if (ed.body != nil)
		ed.body.close();
	ed.col = ed.tag = ed.body = nil;
	if (debug)
		fprint(stderr, "o/x: close edit %s tid %d\n", ed.path, ed.tid);
	tr := Tree.find(tid);
	tr.deledit(ed);
}

showedit(ed: ref Edit)
{
	scr := hd panels->screens();
	col := hd tl panels->cols(scr);
	ed.col.ctl(sprint("copyto %s\n", col));
}

Edit.mk(ed: self ref Edit)
{
	col: ref Panel;
	tagtext := "";
	body :=  "text:file";
	id := ++pidgen;
	sed : ref Edit;
	sed = nil;
	pick edp := ed {
	File =>
		col = edp.col = ui.new("col:ox", id);
		sed = edp;
	Msg =>
		col = edp.col = ui.new("col:ox", id);
		sed = edp;
		if (edp.col == nil)
			error(sprint("o/x: ui.new: %r"));
	Dir =>
		tr := Tree.find(ed.tid);
		col = tr.col;
		tagtext += " Dup / ..";
		body = "tbl:body";
	}
	tagtext += " | ";
	if (col == nil)
		error(sprint("o/x: ui.new: %r"));
	ed.tag = col.new("tag:file", id);
	ed.body = col.new(body, id);
	if (ed.tag == nil || ed.body == nil)
		error(sprint("o/x: ui.new: %r"));
	fd := open(ed.tag.path+"/data", OWRITE|OTRUNC);
	if (fd == nil)
		error (sprint("o/x: new: %r\n"));
	data := array of byte sprint("%s %s", ed.path, tagtext);
	if (write(fd, data, len data) != len data)
		error (sprint("o/x: new: write: %r\n"));
	if (sed != nil)
		showedit(sed);
}

Edit.put(ed: self ref Edit, where: string): int
{
	ffd: ref FD;
	tr := Tree.find(ed.tid);
	pick edp := ed {
	File =>
		if (where != ed.path)
			ffd = create(where, OWRITE, 8r664);
		else {
			if (edp.dirty == 0)	# nothing to put
				return 0;
			ffd = open(where, OWRITE|OTRUNC);
		}
		if (ffd == nil){
			msg(tr, ed.dir, sprint("%s: %r\n", where));
			return -1;
		}
		fd := open(ed.body.path+"/data", OREAD);
		if (fd == nil){
			msg(tr, ed.dir, sprint("open %s/data: %r\n", ed.body.path));
			return -1;
		}
		if (panels->copy(ffd, fd) < 0){
			msg(tr, ed.dir, sprint("put %s: %r\n", where));
			return -1;
		}
		if (debug)
			fprint(stderr, "o/x: put %s\n", where);
		ffd = nil;
		fd = nil; 				# forze stat to see new qid
		if (where == ed.path){
			(e, d) := stat(ed.path);
			if (e < 0){
				msg(tr, ed.dir, sprint("stat %s: %r\n", ed.path));
				return -1;
			}
			edp.qid = d.qid;
			edp.dirty = 0;
			edp.body.ctl("clean");
		} else
			msg(tr, ed.dir, sprint("%s: new file\n", where));
	}
	return 0;
}

Edit.get(ed: self ref Edit): int
{
	if (tagof(ed) == tagof(Edit.Msg))
		return 0;
	tr := Tree.find(ed.tid);
	fd := open(ed.body.path+"/data", OWRITE|OTRUNC);
	if (fd == nil){
		msg(tr, ed.dir, sprint("open: %s/data: %r\n", ed.body.path));
		return -1;
	}
	pick edp := ed {
	File =>
		ffd := open(ed.path, OREAD);
		if (panels->copy(fd, ffd) < 0){
			msg(tr, ed.dir, sprint("get %s: %r\n", ed.path));
			return -1;
		}
		(e, d) := fstat(ffd);
		if (e < 0){
			msg(tr, ed.dir, sprint("fstat %s: %r\n", ed.path));
			return -1;
		}
		edp.qid = d.qid;
		edp.dirty = 0;
		edp.body.ctl("clean");
	Dir =>
		(dirs, n) := readdir->init(ed.path, NAME);
		if (n < 0){
			msg(tr, ed.dir, sprint("open: %s: %r\n", ed.path));
			return -1;
		} else if (n == 0)
			fprint(fd, "none\n");
		else {
			text := "";
			for (i := 0; i < n; i++)
				text += dirs[i].name + "\n";
			data := array of byte text;
			if (write(fd, data, len data) != len data){
				msg(tr, ed.dir, sprint("write: %s/data: %r\n", ed.body.path));
				return -1;
			}
		}
	}
	if (debug)
		fprint(stderr, "o/x: get %s\n", ed.path);
	return 0;
}

Edit.getedits(ed: self ref Edit)
{
	if (ed == nil || ed.buf != nil)
		return;
	fd := open(ed.body.path + "/data", OREAD);
	if (fd != nil && (s := readfile(fd)) != nil)
		ed.buf = Blks.new(string s);
	else
		ed.buf = Blks.new("");
#	getsel really belongs to panel.b in lib. getattrs() should read and
#	parse the contents of ctl.
#	(ed.q0, ed.q1) = getsel(ed.body);
	ed.edited = 0;
}

Edit.clredits(ed: self ref Edit)
{
	ed.buf = nil;
	ed.edited = 0;
}

Edit.putedits(ed: self ref Edit)
{
	if (tagof(ed) == tagof(Edit.File) || tagof(ed) == tagof(Edit.Msg)){
		ctl := "";
		if (ed.edited&Etext){
			fd := open(ed.body.path+"/data", OWRITE|OTRUNC);
			if (fd != nil){
				s := ed.buf.pack();
				d := array of  byte s.s;
				write(fd, d, len d);
			}
			if (!ed.dirty){
				ed.dirty = 1;
				ctl = "dirty\n";
			}
		}
		if (ed.edited&Esel)
			ctl += sprint("sel %d %d\n", ed.q0, ed.q1);
		if (ctl != "")
			ed.body.ctl(ctl);
	}
	ed.clredits();
}

findpanel(id: int): (ref Tree, ref Edit)
{
	for (trl := trees; trl != nil; trl = tl trl){
		tr := hd trl;
		for (edl := tr.eds; edl != nil; edl = tl edl){
			ed := hd edl;
			if (ed.tag != nil && ed.tag.id == id)
				return (tr, ed);
		}
		if (tr.col != nil && tr.col.id == id)
			return (tr, nil);
	}
	return (nil, nil);
}

Edit.cleanto(ed: self ref Edit, cmd: string, arg: string): string
{
	pick edp := ed {
	File =>
		if (edp.lastcmd == cmd)
			return nil;
		edp.lastcmd = cmd;
		case cmd {
		"Put" =>
			(e, d) := stat(ed.path);
			if (e < 0)
				return sprint("stat: %r");
			if (d.qid.path != edp.qid.path || d.qid.vers != edp.qid.vers)
				return sprint("file changed by %s", d.muid);
		"New" =>
			(e, nil) := stat(arg);
			if (e >= 0)
				return sprint("%s: file exists", arg);
		* =>
			if (edp.dirty)
				return "put changes first";
		}
	}
	return nil;
}

Elogbuf.new(): ref Elogbuf
{
	return ref Elogbuf(array[0] of ref Elog, 0);
}

Elogbuf.push(e: self ref Elogbuf, b: ref Elog)
{
	if (e.n == len e.b){
		nb := array[len e.b + 16] of ref Elog;
		nb[0:] = e.b[0:len e.b];
		for (i := len e.b; i < len nb; i++)
			nb[i] = nil;
		e.b = nb;
	}
	e.b[e.n++] = b;
}

Elogbuf.pop(e: self ref Elogbuf): ref Elog
{
	if (e.n == 0)
		return nil;
	b := e.b[--e.n];
	e.b[e.n] = nil;		# poison
	return b;
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.