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

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


# Sam language taken from acme.
# This file was /appl/acme/ecmd.b, changed for o/x.
#
# Sam commands are implemented here.
# The main loop at edit.b, after parsing, calls
# cmdexec() to do the actual work.
#

implement Samcmd;
include "mods.m";
	elogterm, eloginsert, elogreplace, elogdelete, elogapply: import samlog;
	Addr, Address, BUFSIZE, Cmd: import Sam;
	aNo, aDot, aAll, editerror, cmdlookup, cmdtab, warnc,
	C_nl, C_a, C_b, C_c, C_d, C_B, C_D, C_e, C_f, C_g, C_i, C_k, 
	C_m, C_n, C_p, C_s, C_u, C_w, C_x, C_X, C_pipe, C_eq: 		import sam;
	NRange, Range, Rangeset: import Regx;
	rxcompile, rxexecute, rxbexecute: import regx;
	Edit, Tree, Etext, seled, trees, Esel: import oxedit;
	Xcmd, newedit, deledit, putedit, pipein, pipeout, findedit: import oxex;

none: Address;

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

	none.r.q0 = none.r.q1 = 0;
	none.f = nil;
}

skipbl(r : string) : string
{
	for(i := 0; i < len r && (r[i]==' ' || r[i]=='\t' || r[i]=='\n'); i++)
		;
	if (i == len r)
		return "";
	else
		return r[i:];
}

# executes a sam command (compound or not).
# builds the address and applies the command to it.
cmdexec(t: ref Edit, cp: ref Cmd): int
{
	i: int;
	ap: ref Addr;
	f: ref Edit;
	dot: Address;
	w: ref Panel;

	if(t == nil)
		w = nil;
	else
		w = t.body;
	if(w==nil && (cp.addr==nil || cp.addr.typex!='"') &&
	    strchr("bBnqUXY!", cp.cmdc) < 0&&
	    !(cp.cmdc=='D' && cp.text!=nil))
		editerror("no current panel");
	i = cmdlookup(cp.cmdc);	# will be -1 for '{' 
	f = nil;
	if(t!=nil && t.body!=nil){
		f = t;
		f.getedits();
	}
	if(i>=0 && cmdtab[i].defaddr != aNo){
		if((ap=cp.addr)==nil && cp.cmdc!='\n'){
			cp.addr = ap = ref Addr;
			ap.typex = '.';
			if(cmdtab[i].defaddr == aAll)
				ap.typex = '*';
		}else if(ap!=nil && ap.typex=='"' && ap.next==nil && cp.cmdc!='\n'){
			ap.next = ref Addr;
			ap.next.typex = '.';
			if(cmdtab[i].defaddr == aAll)
				ap.next.typex = '*';
		}
		if(cp.addr!=nil){	# may be false for '\n' (only)
			if(f!=nil){
				dot = mkaddr(f);
				addr = cmdaddress(ap, dot, 0);
			}else	# a "
				addr = cmdaddress(ap, none, 0);
			f = addr.f;
			f.getedits();
		}
	}
	case(cp.cmdc){
	'{' =>
		dot = mkaddr(f);
		if(cp.addr != nil)
			dot = cmdaddress(cp.addr, dot, 0);
		for(cp = cp.cmd; cp!=nil; cp = cp.next){
			f.q0 = dot.r.q0;
			f.q1 = dot.r.q1;
			cmdexec(f, cp);
		}
		break;
	* =>
		if(i < 0)
			editerror(sprint("unknown command %c in cmdexec", cp.cmdc));
		i = cmdtabexec(i, f, cp);
		return i;
	}
	return 1;
}

# Executes a command given its cmdtab index
cmdtabexec(i: int, t: ref Edit, cp: ref Cmd): int
{
	case (cmdtab[i].fnc){
		C_nl	=> i = nl_cmd(t, cp);
		C_a 	=> i = a_cmd(t, cp);
		C_c	=> i = c_cmd(t, cp);
		C_d	=> i = d_cmd(t, cp);
		C_g	=> i = g_cmd(t, cp);
		C_i	=> i = i_cmd(t, cp);
		C_m	=> i = m_cmd(t, cp);
		C_p	=> i = p_cmd(t, cp);
		C_s	=> i = s_cmd(t, cp);
		C_w	=> i = w_cmd(t, cp);
		C_x	=> i = x_cmd(t, cp);
		C_eq => i = eq_cmd(t, cp);
		C_B	=> i = B_cmd(t, cp);
		C_D	=> i = D_cmd(t, cp);
		C_X	=> i = X_cmd(t, cp);
		C_pipe	=> i = pipe_cmd(t, cp);
		* =>	error("bad case in cmdtabexec");
	}
	return i;
}

Glooping: int;
nest: int;
Enoname := "no file name given";

addr: Address;
menu: ref Edit;
sel: Rangeset;
collection: string;

resetxec()
{
	Glooping = nest = 0;
	collection = nil;
}

mkaddr(f: ref Edit): Address
{
	a: Address;

	a.r.q0 = f.q0;
	a.r.q1 = f.q1;
	a.f = f;
	return a;
}


edittext(f: ref Edit, q: int, r: string): string
{
	case(editing){
	Inactive =>
		return "permission denied";
	Inserting =>
		eloginsert(f, q, r);
		return nil;
	Collecting =>
		collection += r;
		return nil;
	* =>
		return "unknown state in edittext";
	}
}

filelist(t: ref Edit, r: string): list of string
{
	if(len r == 0)
		return nil;
	r = skipbl(r);
	l: list of string;
	if(len r == 0 || r[0] != '<')
		(nil, l) = tokenize(r, " \t\n");
	else {
		# use < command to collect text 
		collection = "";
		runpipe(t, '<', r[1:], Collecting);
		(nil, l) = tokenize(collection, " \t\n");
	}
	return l;
}

a_cmd(t: ref Edit, cp: ref Cmd): int
{
	return append(t, cp, addr.r.q1);
}

B_cmd(t: ref Edit, cp: ref Cmd): int
{
	tr := Tree.find(t.tid);
	l := filelist(t, cp.text);
	if(l == nil)
		editerror(Enoname);
	for(; l != nil; l = tl l)
		newedit(tr, hd l, 0, 0);
	collection = "";
	return 1;
}

c_cmd(t: ref Edit, cp: ref Cmd): int
{
	elogreplace(t, addr.r.q0, addr.r.q1, cp.text);
	return 1;
}

d_cmd(t: ref Edit, nil: ref Cmd): int
{
	if(addr.r.q1 > addr.r.q0)
		elogdelete(t, addr.r.q0, addr.r.q1);
	return 1;
}

D_cmd(t: ref Edit, cp: ref Cmd): int
{
	listx := filelist(t, cp.text);
	if(listx == nil){
		deledit(t);
		return 1;
	}
	for(; listx != nil; listx = tl listx){
		nm :=  names->rooted(hd listx, t.dir);
		nm = names->cleanname(nm);
		w := findedit(t, nm);
		deledit(w);
	}
	collection = "";
	return 1;
}

readloader(f: ref Edit, q0: int, r: string): int
{
	if(len r > 0)
		eloginsert(f, q0, r);
	return 0;
}

g_cmd(t: ref Edit, cp: ref Cmd): int
{
	ok: int;
	if(t != addr.f){
		error("internal error: g_cmd f!=addr.f\n");
		return 0;
	}
	if(rxcompile(cp.re) == 0)
		editerror(sprint("bad regexp '%s' in g command",cp.re));
	(ok, sel) = rxexecute(t.buf, nil, addr.r.q0, addr.r.q1);
	if(ok ^ cp.cmdc=='v'){
		t.q0 = addr.r.q0;
		t.q1 = addr.r.q1;
		return cmdexec(t, cp.cmd);
	}
	return 1;
}

i_cmd(t: ref Edit, cp: ref Cmd): int
{
	return append(t, cp, addr.r.q0);
}

copy(f: ref Edit, addr2: Address)
{
	n := addr.r.q1 - addr.r.q0;
	s := f.buf.gets(addr.r.q0, n);
	eloginsert(addr2.f, addr2.r.q1, s);
}

move(f: ref Edit, addr2: Address)
{
	if(addr.f!=addr2.f || addr.r.q1<=addr2.r.q0){
		elogdelete(f, addr.r.q0, addr.r.q1);
		copy(f, addr2);
	}else if(addr.r.q0 >= addr2.r.q1){
		copy(f, addr2);
		elogdelete(f, addr.r.q0, addr.r.q1);
	}else
		error("move overlaps itself");
}

m_cmd(t: ref Edit, cp: ref Cmd): int
{
	dot := mkaddr(t);
	addr2 := cmdaddress(cp.mtaddr, dot, 0);
	if(cp.cmdc == 'm')
		move(t, addr2);
	else
		copy(t, addr2);
	return 1;
}

p_cmd(t: ref Edit, nil: ref Cmd): int
{
	return pdisplay(t);
}

s_cmd(t: ref Edit, cp: ref Cmd): int
{
	n := cp.num;
	op:= -1;
	if(rxcompile(cp.re) == 0)
		editerror("bad regexp in s command");
	nrp := 0;
	rp: array of Rangeset;
	rp = nil;
	delta := 0;
	didsub := 0;
	ok := 0;
	for(p1 := addr.r.q0; p1<=addr.r.q1; ){
		(ok, sel) = rxexecute(t.buf, nil, p1, addr.r.q1);
		if(!ok)
			break;
		if(sel[0].q0 == sel[0].q1){	# empty match?
			if(sel[0].q0 == op){
				p1++;
				continue;
			}
			p1 = sel[0].q1+1;
		}else
			p1 = sel[0].q1;
		op = sel[0].q1;
		if(--n>0)
			continue;
		nrp++;
		orp := rp;
		rp = array[nrp] of Rangeset;
		rp[0: ] = orp[0:nrp-1];
		rp[nrp-1] = copysel(sel);
		orp = nil;
	}
	buf := "";
	c: int;
	for(m:=0; m<nrp; m++){
		buf = "";
		sel = rp[m];
		for(i := 0; i<len cp.text; i++)
			if((c = cp.text[i])=='\\' && i < len cp.text -1){
				c = cp.text[++i];
				if('1'<=c && c<='9') {
					j := c-'0';
					if(sel[j].q1-sel[j].q0>BUFSIZE){
						editerror("replacement string too long");
						return 0;
					}
					buf += t.buf.gets(sel[j].q0, sel[j].q1-sel[j].q0);
				}else
				 	buf[len buf] = c;
			}else if(c!='&')
				buf[len buf] = c;
			else{
				if(sel[0].q1-sel[0].q0>BUFSIZE){
					editerror("right hand side too long in substitution");
					return 0;
				}
				buf += t.buf.gets(sel[0].q0, sel[0].q1-sel[0].q0);
			}
		elogreplace(t, sel[0].q0, sel[0].q1, buf);
		delta -= sel[0].q1-sel[0].q0;
		delta += len buf;
		didsub = 1;
		if(!cp.flag)
			break;
	}
	if(!didsub && nest==0)
		editerror("no substitution");
	t.edited |= Esel;
	t.q0 = addr.r.q0;
	t.q1 = addr.r.q1+delta;
	return 1;
}

w_cmd(t: ref Edit, cp: ref Cmd): int
{
	r := cmdname(t, cp.text);
	if(r == nil)
		editerror("no name specified for 'w' command");
	putedit(t, r);
	return 1;
}

x_cmd(t: ref Edit, cp: ref Cmd): int
{
	if(cp.re!=nil)
		looper(t, cp, cp.cmdc=='x');
	else
		linelooper(t, cp);
	return 1;
}

X_cmd(nil: ref Edit, cp: ref Cmd): int
{
	filelooper(cp, cp.cmdc=='X');
	return 1;
}

runpipe(t: ref Edit, cmd: int, cr: string, state: int)
{
	r := skipbl(cr);
	if(len r == 0)
		editerror("no command specified for >");
	if(state == Inserting){
		t.q0 = addr.r.q0;
		t.q1 = addr.r.q1;
		if(cmd == '<' || cmd=='|')
			elogdelete(t, t.q0, t.q1);
	}
	dir := ".";
	if(t != nil)
		dir = t.dir;
	if(dir == ".")
		dir = nil;
	editing = state;

	spawn run(t, cmd, cr, dir);

	editing = Inactive;
}

run(t: ref Edit, cmd: int, cr: string, dir: string)
{
	ifd, ofd: ref FD;
	ifd = ofd = nil;
	oc: chan of string;
	oc = nil;
	if (cmd == '<' || cmd == '|'){
		s := t.buf.gets(t.q0, t.q1-t.q0);
		ifd = pipein(s);
	}
	if (cmd== '>' || cmd == '|')
		(ofd, oc) = pipeout();
	Xcmd.new(cr, dir, ifd, ofd, t.tid);
	ifd = ofd = nil;
	if (oc != nil){
		out := <-oc;
		if (len out > 0)
			eloginsert(t, t.q0, out);
	}
	t.edited |= Etext|Esel;
}

pipe_cmd(t: ref Edit, cp: ref Cmd): int
{
	runpipe(t, cp.cmdc, cp.text, Inserting);
	return 1;
}

nlcount(t: ref Edit, q0: int, q1: int): int
{
	nl := 0;
	l := t.buf.blen();
	for (i := q0; i < q1 && i < l; i++)
		if (t.buf.getc(i) == '\n')
			nl++;
	return nl;
}

printposn(t: ref Edit, charsonly: int)
{
	s := t.path + ": ";
	if(!charsonly){
		l1 := 1+nlcount(t, 0, addr.r.q0);
		l2 := l1+nlcount(t, addr.r.q0, addr.r.q1);
		# check if addr ends with '\n' 
		if(addr.r.q1>0 && addr.r.q1>addr.r.q0 && t.buf.getc(addr.r.q1-1)=='\n')
			--l2;
		s += sprint("%ud", l1);
		if(l2 != l1)
			s += sprint(",%ud", l2);
		warnc <-= s + "\n";
		return;
	}
	s += sprint("#%d", addr.r.q0);
	if(addr.r.q1 != addr.r.q0)
		s += sprint(",#%d", addr.r.q1);
	warnc <-= s + "\n";
}

eq_cmd(t: ref Edit, cp: ref Cmd): int
{
	charsonly := 0;
	case(len cp.text){
	0 =>
		break;
	1 =>
		if(cp.text[0] == '#'){
			charsonly = 1;
			break;
		}
	* =>
		charsonly = 1;
		editerror("newline expected");
	}
	printposn(t, charsonly);
	return 1;
}

nl_cmd(t: ref Edit, cp: ref Cmd): int
{
	if(cp.addr == nil){
		# First put it on newline boundaries
		a := mkaddr(t);
		addr = lineaddr(0, a, -1);
		a = lineaddr(0, a, 1);
		addr.r.q1 = a.r.q1;
		if(addr.r.q0==t.q0 && addr.r.q1==t.q1){
			a = mkaddr(t);
			addr = lineaddr(1, a, 1);
		}
	}
	t.q0 = addr.r.q0;
	t.q1 = addr.r.q1;
	t.edited |= Esel;
	return 1;
}

append(f: ref Edit, cp: ref Cmd, p: int): int
{
	if(len cp.text > 0)
		eloginsert(f, p, cp.text);
	return 1;
}

pdisplay(f: ref Edit): int
{
	p1 := addr.r.q0;
	p2 := addr.r.q1;
	if(p2 > f.buf.blen())
		p2 = f.buf.blen();
	if(p1 < p2)
		warnc <-= f.buf.gets(p1, p2 - p1);
	f.q0 = addr.r.q0;
	f.q1 = addr.r.q1;
	f.edited |= Esel;
	return 1;
}

filename(f: ref Edit): string
{
	dirt := cur := ' ';
	if (f.dirty)
		dirt = '\'';
	if (seled == f)
		cur = '.';
	return sprint("%c+%c %s\n", dirt, cur, f.path);
}

loopcmd(f: ref Edit, cp: ref Cmd, rp: array of Range)
{
	for(i:=0; i<len rp; i++){
		f.q0 = rp[i].q0;
		f.q1 = rp[i].q1;
		cmdexec(f, cp);
	}
}

looper(f: ref Edit, cp: ref Cmd, xy: int)
{
	p, op, nrp, ok: int;
	r, tr: Range;
	rp: array of  Range;

	r = addr.r;
	if(xy)
		op = -1;
	else
		op = r.q0;
	nest++;
	if(rxcompile(cp.re) == 0)
		editerror(sprint("bad regexp in '%c' command", cp.cmdc));
	nrp = 0;
	rp = nil;
	for(p = r.q0; p<=r.q1; ){
		(ok, sel) = rxexecute(f.buf, nil, p, r.q1);
		if(!ok){ # no match, but y should still run
			if(xy || op>r.q1)
				break;
			tr.q0 = op;
			tr.q1 = r.q1;
			p = r.q1+1;	# exit next loop
		}else{
			if(sel[0].q0==sel[0].q1){	# empty match?
				if(sel[0].q0==op){
					p++;
					continue;
				}
				p = sel[0].q1+1;
			}else
				p = sel[0].q1;
			if(xy)
				tr = sel[0];
			else{
				tr.q0 = op;
				tr.q1 = sel[0].q0;
			}
		}
		op = sel[0].q1;
		nrp++;
		orp := rp;
		rp = array[nrp] of Range;
		rp[0: ] = orp[0: nrp-1];
		rp[nrp-1] = tr;
		orp = nil;
	}
	loopcmd(f, cp.cmd, rp);
	rp = nil;
	--nest;
}

linelooper(f: ref Edit, cp: ref Cmd)
{
	nrp, p: int;
	r, linesel: Range;
	a, a3: Address;
	rp: array of Range;

	nest++;
	nrp = 0;
	rp = nil;
	r = addr.r;
	a3.f = f;
	a3.r.q0 = a3.r.q1 = r.q0;
	a = lineaddr(0, a3, 1);
	linesel = a.r;
	for(p = r.q0; p<r.q1; p = a3.r.q1){
		a3.r.q0 = a3.r.q1;
		if(p!=r.q0 || linesel.q1==p){
			a = lineaddr(1, a3, 1);
			linesel = a.r;
		}
		if(linesel.q0 >= r.q1)
			break;
		if(linesel.q1 >= r.q1)
			linesel.q1 = r.q1;
		if(linesel.q1 > linesel.q0)
			if(linesel.q0>=a3.r.q1 && linesel.q1>a3.r.q1){
				a3.r = linesel;
				nrp++;
				orp := rp;
				rp = array[nrp] of Range;
				rp[0: ] = orp[0: nrp-1];
				rp[nrp-1] = linesel;
				orp = nil;
				continue;
			}
		break;
	}
	loopcmd(f, cp.cmd, rp);
	rp = nil;
	--nest;
}

matchfiles(re: string): list of ref Edit
{
	edl: list of ref Edit;
	edl = nil;
	for(trl := trees; trl != nil; trl = tl trl)
		for (eds := (hd trl).eds; eds != nil; eds = tl eds)
			if (filematch(hd eds, re))
				edl = hd eds::edl;
	return edl;
}

filelooper(cp: ref Cmd, XY: int)
{
	if(Glooping++)
		editerror(sprint("can't nest %c command", "YX"[XY]));
	nest++;

	for(edl := matchfiles(cp.re); edl != nil; edl = tl edl)
		cmdexec(hd edl, cp.cmd);

	--Glooping;
	--nest;
}

nextmatch(f: ref Edit, r: string, p: int, sign: int)
{
	ok: int;

	if(rxcompile(r) == 0)
		editerror("bad regexp in command address");
	if(sign >= 0){
		(ok, sel) = rxexecute(f.buf, nil, p, 16r7FFFFFFF);
		if(!ok)
			editerror("no match for regexp");
		if(sel[0].q0==sel[0].q1 && sel[0].q0==p){
			if(++p>f.buf.blen())
				p = 0;
			(ok, sel) = rxexecute(f.buf, nil, p, 16r7FFFFFFF);
			if(!ok)
				editerror("address");
		}
	}else{
		(ok, sel) = rxbexecute(f.buf, p);
		if(!ok)
			editerror("no match for regexp");
		if(sel[0].q0==sel[0].q1 && sel[0].q1==p){
			if(--p<0)
				p = f.buf.blen();
			(ok, sel) = rxbexecute(f.buf, p);
			if(!ok)
				editerror("address");
		}
	}
}

cmdaddress(ap: ref Addr, a: Address, sign: int): Address
{
	f := a.f;
	a1, a2: Address;

	do{
		case(ap.typex){
		'l' or
		'#' =>
			if(ap.typex == '#')
				a = charaddr(ap.num, a, sign);
			else
				a = lineaddr(ap.num, a, sign);
			break;

		'.' =>
			a = mkaddr(f);
			break;

		'$' =>
			a.r.q0 = a.r.q1 = f.buf.blen();
			break;

		'\'' =>
editerror("can't handle '");
#			a.r = f.mark;
			break;

		'?' =>
			sign = -sign;
			if(sign == 0)
				sign = -1;
			if(sign >= 0)
				v := a.r.q1;
			else
				v = a.r.q0;
			nextmatch(f, ap.re, v, sign);
			a.r = sel[0];
			break;

		'/' =>
			if(sign >= 0)
				v := a.r.q1;
			else
				v = a.r.q0;
			nextmatch(f, ap.re, v, sign);
			a.r = sel[0];
			break;

		'"' =>
			eds := matchfiles(ap.re);
			if (eds == nil)
				editerror("no file matches");
			f = hd eds;
			a = mkaddr(f);
			break;

		'*' =>
			a.r.q0 = 0;
			a.r.q1 = f.buf.blen();
			return a;

		',' or
		';' =>
			if(ap.left!=nil)
				a1 = cmdaddress(ap.left, a, 0);
			else{
				a1.f = a.f;
				a1.r.q0 = a1.r.q1 = 0;
			}
			if(ap.typex == ';'){
				f = a1.f;
				a = a1;
				f.q0 = a1.r.q0;
				f.q1 = a1.r.q1;
			}
			if(ap.next!=nil)
				a2 = cmdaddress(ap.next, a, 0);
			else{
				a2.f = a.f;
				a2.r.q0 = a2.r.q1 = f.buf.blen();
			}
			if(a1.f != a2.f)
				editerror("addresses in different files");
			a.f = a1.f;
			a.r.q0 = a1.r.q0;
			a.r.q1 = a2.r.q1;
			if(a.r.q1 < a.r.q0)
				editerror("addresses out of order");
			return a;

		'+' or
		'-' =>
			sign = 1;
			if(ap.typex == '-')
				sign = -1;
			if(ap.next==nil || ap.next.typex=='+' || ap.next.typex=='-')
				a = lineaddr(1, a, sign);
			break;
		* =>
			error("cmdaddress");
			return a;
		}
	}while((ap = ap.next)!=nil);	# assign =
	return a;
}

filematch(f: ref Edit, r: string): int
{
	if(rxcompile(r) == 0)
		editerror("bad regexp in file match");
	buf := filename(f);
	(match, nil) := rxexecute(nil, buf, 0, len buf);
	return match;
}

charaddr(l: int, addr: Address, sign: int): Address
{
	if(sign == 0)
		addr.r.q0 = addr.r.q1 = l;
	else if(sign < 0)
		addr.r.q1 = addr.r.q0 -= l;
	else if(sign > 0)
		addr.r.q0 = addr.r.q1 += l;
	if(addr.r.q0<0 || addr.r.q1>addr.f.buf.blen())
		editerror("address out of range");
	return addr;
}

lineaddr(l: int, addr: Address, sign: int): Address
{
	n: int;
	c: int;
	f := addr.f;
	a: Address;
	p: int;

	a.f = f;
	if(sign >= 0){
		bl := f.buf.blen();
		if(l == 0){
			if(sign==0 || addr.r.q1==0){
				a.r.q0 = a.r.q1 = 0;
				return a;
			}
			a.r.q0 = addr.r.q1;
			p = addr.r.q1-1;
		}else{
			if(sign==0 || addr.r.q1==0){
				p = 0;
				n = 1;
			}else{
				p = addr.r.q1-1;
				n = f.buf.getc(p++)=='\n';
			}
			while(n < l){
				if(p >= bl)
					editerror("address out of range");
				if(f.buf.getc(p++) == '\n')
					n++;
			}
			a.r.q0 = p;
		}
		while(p < bl && f.buf.getc(p++)!='\n')
			;
		a.r.q1 = p;
	}else{
		p = addr.r.q0;
		if(l == 0)
			a.r.q1 = addr.r.q0;
		else{
			for(n = 0; n<l; ){	# always runs once
				if(p == 0){
					if(++n != l)
						editerror("address out of range");
				}else{
					c = f.buf.getc(p-1);
					if(c != '\n' || ++n != l)
						p--;
				}
			}
			a.r.q1 = p;
			if(p > 0)
				p--;
		}
		while(p > 0 && f.buf.getc(p-1)!='\n')	# lines start after a newline
			p--;
		a.r.q0 = p;
	}
	return a;
}

cmdname(f: ref Edit, str: string): string
{
	r := string nil;
	if(len str == 0){
		# no name; use existing
		return f.path;
	}
	str = skipbl(str);
	if(len str > 0){
		if(str[0] == '/')
			r = str;
		else
			r = f.dir;
	}
	return r;
}

copysel(rs: Rangeset): Rangeset
{
	nrs := array[NRange] of Range;
	for(i := 0; i < NRange; i++)
		nrs[i] = rs[i];
	return nrs;
}

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.