home *** CD-ROM | disk | FTP | other *** search
/ Beijing Paradise BBS Backup / PARADISE.ISO / software / BBSDOORW / AAEMA95A.ZIP / EM.CC < prev    next >
Encoding:
C/C++ Source or Header  |  1995-10-10  |  45.1 KB  |  858 lines

  1. /* This file is EM.CC */
  2. #include "em.h"
  3. /*-----*/
  4. char CW[strsize],disp[256]; jmp_buf *bad=0; /* FILE *debug; */
  5. /*----- val==string? */
  6. int val::operator==(reg char*t){if(!t?:!s) return 0; reg int i,m=n;
  7. reg char*u=s; for(i=0;i<m;i++) if(u[i]!=t[i]) return 0; return t[m]==0;}
  8. /*----- is char in string? */
  9. int val::operator>>(reg char c){reg char*S=s,*T;
  10. for(T=S+n;T>S;) if(c==*--T) return 1; return 0;}
  11. /*-----*/
  12. Text killring[16]; short nkill=0;
  13. gp_cur cursor(0,0); /* the cursor */
  14. char *Moan=0,*Display=0; line*dustbin=0;
  15. /*----- if line is blank */
  16. int line::blank(){reg int i,m=n; char*t=s,j;
  17. for(i=0;i<m;i++) if((j=t[i])==' '?0:j!=9) return 0; return 1;};
  18. /*----- new line with specified text */
  19. line::line(char*s){nullline(); *this=val(s,strlen(s));};
  20. line::line(char*s,int n){nullline(); *this=val(s,n);};
  21. line::line(line*L){nullline(); *this=val(L->s,L->n); no_cr(L->no_cr());};
  22. /*-----*/
  23. line *line0,*modeline; /* dummy lines */
  24. screenline Sl[256];
  25. /*----- initialize buffer */
  26. void buffer::initbuffer(){next=0; nrows=ncols=0; row1=lastrow=refresh=0; name=0;
  27. sortcol=truncated=invisible=readonly=changed=oldch=lastrow=stback=0;
  28. rmargin=71; startc=0; bound=val(); dotcc=dotcc2=-1; col.init();
  29. tabtype=Case=1; twod=overlay=wrap=longlines=skip_after_word=0;
  30. text.line(); text.next=text.prev=new line();
  31. start=mark(text.next,0); dot=start; dot1=start; dot2=start; olddot=start;
  32. start.prev=0; start^olddot; olddot^dot1; dot1^dot2; dot2^dot; dot.next=0;}
  33. /*-----*/
  34. buffer *buf0, *bhead=0, *B=0; int bcount=0;    /* B -> current buffer */
  35. line *T; /* entry point of circle of lines currently being handled */
  36. char T1w[magicsize]={0},T2w[magicsize]={0}; val T1t(T1w,0),T2t(T2w,0);
  37. int needswrap=0,obtype,prevobtype=ob_other; region lastyank;
  38. buffer*window[16]; int nwindows=1,currentwindow=0; byte ccc;
  39. /*---------- interface with screen */
  40. long _ax,_bx,_cx,_dx,_si,_di,_bp,_es; short _flags; int nextch=0;
  41. byte gp_Mode,gp_Rows,gp_Cols;
  42. short gp_Attr=White,gp_Attr_def=White; /* on black */
  43. /*-----*/
  44. void int10() {interrupt(10);}
  45. void int16() {interrupt(16);}
  46. void int21() {interrupt(21);}
  47. void int31() {interrupt(31);}
  48. void int33() {interrupt(33);}
  49. /*----- is a key waiting to be read? */
  50. int typahead() {if(nextch) return 1; _ax=0xb00; int21(); return _ax&255;}
  51. /*----- swop foreground and background colours at a screen position */
  52. void counterchange(c_short x){uns short y=x.val();
  53. x=(y&0x88ff)|((y&0x0700)<<4)|((y&0x7000)>>4);}
  54. /*-----*/
  55. byte get__key(){_ax=0x700; int21(); return _ax&255;}
  56. /*-----*/
  57. int get_key(){return get__key()?:-get__key();}
  58. /*-----*/
  59. int loseip(){A: _ax=0xb00; int21(); if(_ax&255) {get__key(); goto A;} return 1;}
  60. /*-----*//* Put a char into DOS's input buffer. Returns 0 if fail */
  61. int inject(int ch){_ax=0x500;
  62. _cx=ch<0?((-ch)&0xff)<<8:((ch&0xff)<<8)+(ch&0xff); int16(); return !(_ax&1);}
  63. /*-----*/
  64. int getkey(){int i; uns short s; c_short_addr t,u=scr(cursor.r,cursor.c).adr();
  65. if(nextch) {i=nextch; nextch=0; return i;}
  66. if(B) {t=scr(B->lastrow+1,-1).adr(); s=(*t).val();
  67.     if(Jerry.mc) *t=sch('m',Orange+(White<<4)); counterchange(*t);}
  68. if(ccc) counterchange(*u);
  69. A: i=get_key(); if(!Jerry.mc) if(i==-mousemove) goto A;
  70. if(ccc) counterchange(*u);
  71. if(B) *t=s;
  72. if(B) if(i) {line*L=B->dot.r,*M; while(M=L->prev) L=M;
  73.     if(L!=B->text.next) Moan="BUG: buffer.dot.r pointing to wrong buffer";}
  74. return i;}
  75. /*-----*/
  76. int getkey_nowait(){return typahead()?getkey():0;}
  77. /*----- print char on screen, obeying ctrl chars */
  78. void printch(char c) {if(c=='C'-64) return;
  79. if(c==LF) {_ax=0x200; _dx=CR; int21();} _ax=0x200; _dx=c; int21();}
  80. /*-----*/
  81. void wr(char*s){while(*s) printch(*s++);}
  82. /*-----*/
  83. int beep(){_ax=0x0200; _dx=7; int21(); return 0;}
  84. /*----- = current screen mode */
  85. int gp_mode(void) {_ax=0xf00; int10(); return _ax&255;}
  86. /*----- screen mode := m */
  87. void gp_mode(char m) {_ax=m&255; int10(); gp_Attr=gp_Attr_def;
  88.   if(m!=gp_mode()) ps("\007error: this computer has no mode 0x%1x\n",m);}
  89. /*----- move cursor to c */
  90. void gp_cursor(gp_cur c) {_ax=0x200; _bx=0; _dx=*((short*)&c); int10();}
  91. /*----- = where cursor is on screen */
  92. gp_cur gp_cursor(void) {_ax=0x300; _bx=0; int10(); return *((gp_cur*)&_dx);}
  93. /*----- clear screen line i cols j to J */
  94. void gp_clear(int i,int j/*=0*/,reg int J/*=gp_Cols-1*/){
  95.   reg int k; c_short_addr L=Sl[i].sa; for(k=j;k<=J;k++) L[k]=sch(' ',White);}
  96. /*----- video-reverse from gp_Attr */
  97. inline byte revattr(char a){return ((a&7)<<4)|((a>>4)&7);}
  98. /*----------*//* error exit */
  99. volatile void MOAN(char *m) {beep(); gp_Attr=White; Moan=m; longjmp(*bad,1);}
  100. /*----- get current drive */
  101. byte drive(){_ax=0x1900; int21(); return _ax+'a';}
  102. /*----- get current directory for drive c ('@' = current drive, here) */
  103. void getcurrentdir(char c,char*s) {_dx=c&31; _si=(long)s; _ax=0x4700; int21();
  104. if(_carry)MOAN("no such drive, or bad drive, or no good floppy on this drive");}
  105. /*-----make a full pathname, replace / by \, convert to lowercase */
  106. char* fullfilename(char*full,char*name) {int i,j,k,l,m,n,p,q,net; byte c;
  107. char *s,*t,Dir[strsize],cw[strsize],*pf[128];
  108. if(!name[0]) {full[0]=0; return full;}
  109. if(name[0]=='(') {strcpy(full,name); return full;}
  110. for(s=cw,t=name;c=*t;t++) if(c>32) if(c<'A' ?0: c<='Z') *s++=c+32; else *s++=c;
  111. *s=0; n=strlen(cw); /**** relies on ctrl chars=0-31, space=32, lc=uc+32 **/
  112. s=cw; i=*s; j=s[1];
  113. if(net=i=='\\'?j=='\\':0) Dir[0]=0; /* net drive */
  114. else {if(i<'a'?0:i>'z'?0:j==':') s+=2; else i=drive();
  115.     Dir[0]=i; Dir[1]=':';
  116.     if(*s=='/'?:*s=='\\') Dir[2]=0;
  117.     else {Dir[2]='\\'; for(j=3;j<256+2;j++) Dir[j]=0; getcurrentdir(i,Dir+3);}}
  118. i=strlen(Dir); if(net) s=cw; else Dir[i++]='\\'; strcpy(Dir+i,s);
  119. for(pf[0]=s=Dir,q=1;*s;s++) if(*s=='/'?1:*s=='\\') {*s=0; pf[q++]=s+1;}
  120. for(i=0;i<q;i++) if(!(s=pf[i])[0] ?1: (s[0]!='.'?0:!s[1])) pf[i]=0; /* \\ \.\ */
  121. SQUEEZE: for(p=i=0;i<q;i++) {if(s=pf[i]) pf[p++]=s; /* remove nulls */} q=p;
  122. for(i=0;i<q;i++) if((s=pf[i])[0]=='.') if(s[1]=='.') if(!s[2]) { /* \..\ */
  123.     pf[i]=0; if(i>1) pf[i-1]=0; goto SQUEEZE;}
  124. for(i=1;i<q;i++) { /* check each name segment, but not drive or server name */
  125.     k=m=-1; l=strlen(s=pf[i]);
  126.     if(s[0]=='.') continue; /* starts with dot, illegal on PC */
  127.     for(j=0;j<l;j++) if(s[j]=='.') {k=j; break;} /* find first dot */
  128.     if(k<0) {if(l>8) s[8]=0; continue;} /* no dot: shorten to 8 chars */
  129.     for(j++;j<l;j++) if(s[j]=='.') {m=j; break;} /* find second dot */
  130.     if(m>0) continue; /* >1 dots, illegal on PC */
  131.     if(l>k+4) s[l=k+4]=0; /* shorten suffix to 3 chars */
  132.     if(k>8) for(m=k-8,j=k;j<=l;j++) s[j-m]=s[j];} /* shorten stem to 8 chars */
  133. /*for(i=0;i<q;i++){DEBUG;fprintf(debug,"pf[%1d]='%s'\n",i,pf[i]?:"(null)");GUBED;}*/
  134. if(net) strcpy(full,"\\\\"); strcpy(full+(net?2:0),pf[0]); for(i=1;i<q;i++) {
  135.     n=strlen(full); full[n]='\\'; strcpy(full+n+1,pf[i]);}
  136. return full;}
  137. /*----- compare n chars from t to s */
  138. int comptext(reg char*s,reg char*t,int n,int nocase/*=0*/){reg char*u;
  139. if(!n) return 1; if(!s) return 0; if(!t) return 0;
  140. if(nocase) {for(u=s+n;s<u;) if(to_upper(*s++)!=to_upper(*t++)) return 0;}
  141. else {for(u=s+n;s<u;) if(*s++!=*t++) return 0;} return 1;}
  142. /*---------- <marks> */
  143. /*----- how many marks currently defined on this buffer */
  144. int buffer::nmarks(){
  145.     reg mark *M; reg int n=0; for(M=dot.next;M;M=M->next,n++); return n;}
  146. /*----- find mark N. -ve N = count from bottom of stack */
  147. mark&buffer::Mark(int N){reg mark *M; reg int i; if(!N) N=1;
  148. if(N==999) return dot2;
  149. if(N>0) {for(M=B->dot.next  ,i= 1;i<N;i++,M=M->next) if(!M) break;}
  150. else    {for(M=B->start.prev,i=-1;i>N;i--,M=M->prev) if(!M) break;}
  151. if(!M) if(N>0) if(N==i) if(N<17) {B->dot.hsup(); return *B->start.prev;}
  152. if(!M) {pr(CW,"mark %d not defined yet",N); MOAN(CW);} return *M;}
  153. /*----- pushmark */
  154. void mark::push(){reg mark *M,*P; M=new mark(*this); M->prev=0;
  155. if(!(P=B->dot.next)) {B->dot.next=B->start.prev=M; M->next=0;}
  156. else {*M^*P; B->dot.next=M;}}
  157. /*----- push mark onto bottom of mark stack */
  158. void mark::hsup(){reg mark *M,*P; M=new mark(*this); M->next=0;
  159. if(!(P=B->start.prev)) {B->dot.next=B->start.prev=M; M->prev=0;}
  160. else {*P^*M; B->start.prev=M;}}
  161. /*----- popmark */
  162. void mark::pop(){reg mark *M;
  163. if(!(M=B->dot.next)) MOAN("this buffer's mark stack is already empty");
  164. *this=*M; if(B->dot.next=M->next) M->next->prev=0; else B->start.prev=0;
  165. delete M; return;}
  166. /*----- remove a mark */
  167. void buffer::rem1mark(){reg mark *M;
  168. if(!(M=dot.next)) MOAN("this buffer's mark stack is already empty");
  169. if(dot.next=M->next) M->next->prev=0; else start.prev=0; delete M; return;}
  170. /*---------- <lines> */
  171. /*----- count lines from start to this line */
  172. int line::lineno(){reg int i; reg line *L;
  173. for(i=0,L=this;L;i++,L=L->prev); return i;}
  174. /*----- insert line after L */
  175. line* operator/(line &L,val t) {reg line *N,*X,*T=&B->text; N=new line();
  176. X=L.next?:T; L-*N; *N-*X; T->next->prev=T->prev->next=0; *N=t; return N;}
  177. /*----- insert line before L */
  178. line* operator/(val t,line &L) {reg line *N,*X,*T=&B->text; N=new line();
  179. X=L.prev?:T; *X-*N; *N-L; T->next->prev=T->prev->next=0; *N=t; return N;}
  180. /*----- how to delete a line */
  181. void line::del(){if(dustbin) *this-*dustbin; else next=0; dustbin=this;}
  182. line::~line() {if(s) delete s;}
  183. void emptydustbin(){reg line *J,*K;
  184.     for(J=dustbin;J;J=K) {K=J->next; delete J;} dustbin=0;}
  185. /*----- line=val : line gets new text */
  186. void line::operator=(val t){mark *M; int j; char *ss;
  187. if(!t.n) {delete s; s=0; n=0; la(1); return;}
  188. ss=new char[j=roundup(t.n)]; la(1); delete s; s=ss; n=t.n;
  189. memcpy(ss,t.s,t.n); forallmarks(M) if(M->r==this) M->c<?=t.n;}
  190. /*----- line=int : change line.n */
  191. void line::operator=(int nn) {mark *M; int i,j; char *t;
  192. nn>?=0; if(n==nn) return; la(1);
  193. if(nn<n) forallmarks(M) if(M->r==this) M->c<?=nn;
  194. if(!nn) {empty(); return;}
  195. if((i=roundup(nn))!=roundup(n)) {t=new char[i]; j=n<?nn;
  196.     if(!t) {pr(CW,"needs a line to be too long (%d bytes)",nn); MOAN(CW);}
  197.     if(s) {if(j) memcpy(t,s,j); delete s;} s=t;}
  198. n=nn; /* if line lengthened, added part not initialized */}
  199. /*----- line+=int : lengthen line */
  200. inline void line::operator+=(reg int nn) {*this=n+nn;}
  201. /*----- line-=int : shorten line */
  202. inline void line::operator-=(reg int nn) {*this=n-nn;}
  203. /*----- line+=char : append char to line */
  204. void line::operator+=(reg char C){*this+=1; s[n-1]=C; la(1);}
  205. /*----- line+=line2 : append text of line2 to line */
  206. void line::operator+=(reg line&L) {int nn=n; *this=nn+L.n;
  207. memcpy(s+nn,L.s,L.n); la(1); return;}
  208. /*----- -line : remove eol at start of this line */
  209. void line::operator-(){
  210. if(!prev) MOAN("chain lines: no previous line"); --*prev;}
  211. /*----- line-- : remove eol at end of this line */
  212. void line::operator--(){reg int i; reg mark *m; reg line *L=next,*M;
  213. if(!L) MOAN("chain lines: no next line"); i=n; *this+=*next; M=L->next;
  214. forallmarks(m) if(m->r==L) {m->r=this; m->c+=i;} no_cr(L->no_cr());
  215. if(M) *this-*M; else {next=0; B->text.prev=this;} L->del();}
  216. /*----- line-Text : remove eol at end of this line, in the Text */
  217. void line::operator-(reg Text&K){reg line *L=next,*M; if(!L) return;
  218. *this+=*next; M=L->next; if(M) *this-*M; else {next=0; K.end=this;} L->del();}
  219. /*----- mark>>=int : move rest of line right n chars */
  220. void mark::operator>>=(reg int n){reg char*s,*t; if(n<0) *this<<=-n;
  221. else if(n) for(s=r->s+r->n-1-n,t=r->s+c;s>=t;s--) *(s+n)=*s;}
  222. /*----- mark<<=int : move rest of line left n chars */
  223. void mark::operator<<=(reg int n){reg char*s,*t; if(n<0) *this>>=-n;
  224. else if(n) for(s=r->s+r->n-n,t=r->s+c;t<s;t++) *t=*(t+n);}
  225. /*----- !mark : delete char at mark */
  226. void mark::operator!(){reg mark*M; c<?=r->n; r->la(1); B->changed=1;
  227. if(c==r->n) --*r;
  228. else {*this<<=1; *r-=1; forallmarks(M) if(M->r==r) if(M->c>c) M->c--;}}
  229. /*----- backspace-delete char at mark */
  230. void mark::bs() {reg mark*M; c<?=r->n; B->changed=1;
  231. if(!c) -(*r);
  232. else {c--; *this<<=1; *r-=1; r->la(1);
  233.     forallmarks(M) if(M->r==r) if(M->c>c) M->c--;}}
  234. /*----- mark=char : replace char at mark */
  235. void mark::operator=(reg char C) {c<?=r->n; r->la(1); B->changed=1;
  236. if(C==LF) *this/1;
  237. else if(c>=r->n) *r+=C;
  238. else r->s[c]=C;}
  239. /*----- mark+=char : insert char at mark */
  240. void mark::operator+=(reg char C){reg mark *M; c<?=r->n; r->la(1); B->changed=1;
  241. if(C==LF) *this/1;
  242. else if(c>=r->n) {*r+=C; c++;}
  243. else {*r+=1; (*this)>>=1; r->s[c]=C;
  244.     forallmarks(M) if(M->r==r) if(M->c>=c) M->c++;}}
  245. /*----- mark+=char* : insert string at mark */
  246. void mark::operator+=(reg char *C) {reg int i; reg char j;
  247. for(i=0;j=C[i];i++) (*this)+=j;}
  248. /*----- mark/int : insert eol. If i==0, mark at cut stays at eol before cut */
  249. line* mark::operator/(int i) {reg int cut,j; reg line *L,*N; L=r; reg mark *M;
  250. c<?=L->n; cut=c; N=*L/val(L->s+c,L->n-c); B->changed=1;
  251. j=N->no_cr(); N->no_cr(L->no_cr()); L->no_cr(j);
  252. if(i) {forallmarks(M) if(M->r==L) if(M->c>=cut) {M->r=N; M->c-=cut;}}
  253. else  {forallmarks(M) if(M->r==L) if(M->c> cut) {M->r=N; M->c-=cut;}}
  254. if(i!=2) if(B->start.r==N) B->start.r=L; *L=cut; L->la(1); return N;}
  255. /*----- line[int] : nth char in line */
  256. char line::operator[](reg int nn){return nn<0?' ':nn<n?s[nn]:LF;}
  257. /*----- change color of jth char of ith screen line */
  258. void recolor(reg int i,reg int j,byte col){c_short_addr SL=Sl[i].sa;
  259. if(i>=0) if(i<gp_Rows) if(j>=0) if(j<gp_Cols) (*SL).color()=col;}
  260. /*-----*/
  261. void line::empty(){delete s; s=0; n=0; la(1);}
  262. /*-----*/
  263. char chwidth[256]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  264. 0,0,5,3,5,5,5,6,6,3,4,4,5,5,3,5,3,5,5,5,5,5,5,5,5,5,5,5,3,3,5,5,5,5,6,6,6,6,6,6,
  265. 6,6,6,4,5,6,6,7,6,6,6,6,6,6,6,7,6,7,6,6,5,4,5,4,5,5,3,5,6,5,6,5,4,6,6,3,4,6,3,7,
  266. 6,5,6,6,5,5,4,6,6,7,5,6,5,4,3,4,5,0,6,6,5,5,5,5,5,5,5,5,5,3,3,3,6,6,6,7,7,5,5,5,
  267. 6,6,6,6,7,5,5,6,7,5,5,3,5,6,6,6,5,5,5,5,5,5,5,5,5,5,-6,-6,-6,-6,-6,-6,-6,-6,-6,
  268. -6,-6,-6,-6,-6,-6,-6,-6,-6,-6,-6,-6,-6,-6,-6,-6,-6,-6,-6,-6,-6,-6,-6,-6,-6,-6,
  269. -6,-6,-6,-6,-6,-6,-6,-6,-6,-6,-6,-6,-6,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,-6,-6,-6,
  270. -6,-6,-6,-6,-6,-6,-6,-6,-6,-6,-6,-6,0};  /* -6 = width=30 always */
  271. /* width = this*6 normal, this*4 if subscript/superscript. 0 = not printing */
  272. /* for LQ1050+: normal/condensed chars/line: 10cpi 136/233; 12cpi 164/272, 15cpi
  273.  204/NA, proportional 4890/9780 units as above (= 136/272 'n''s) */
  274. /*----- convert char # to screen col # */
  275. int line::to_tabexp(reg int c,int proportional/*=0*/) {
  276. reg int i,j,J,k; reg char*S=s;
  277. if(!proportional) {c<?=n; if(!B->tabtype) return c;
  278.     for(j=k=0;k<c;) if(S[k++]==9) j=totab(j); else j++; return j;}
  279. else {for(J=j=k=0;k<c;)
  280.     if((i=S[k++])==9) {i=j; j=totab(j); J+=(j-i)*chwidth[' '];}
  281.     else {j++; J+=abs(chwidth[i&127]);} return j;}}
  282. /*-----*/
  283. int line::to_scrcolno(int c) {return to_tabexp(c)-B->startc;}
  284. /*----- convert screen col # to char # */
  285. int line::from_tabexp(int p,int proportional/*=0*/) {
  286. reg int i,j,J,k; reg char *S=s;
  287. if(!proportional) {if(!B->tabtype) return p;
  288.     for(j=k=0;k<n?j<p:0;) if(S[k++]==9) j=totab(j); else j++; return k;}
  289. else {reg int P=p*6; for(J=j=k=0;k<n;) {if(J>=P) return k>0?k-1:0;
  290.     if((i=S[k++])==9) {i=j; j=totab(j);  J+=(j-i)*chwidth[' '];}
  291.     else {j++; J+=abs(chwidth[i&127]);}} return k;}}
  292. /*-----*/
  293. int line::from_scrcolno(int p) {return from_tabexp(p+B->startc);}
  294. /*----- correlate screen cursor with buffer cursor positions in line */
  295. void line::dotty() {
  296. if(B->dotcc<0) {B->dot.c<?=B->dot.r->n; B->dotcc=to_scrcolno(B->dot.c);}
  297. else B->dot.c=from_scrcolno(B->dotcc);}
  298. /*----- clear whole screen */
  299. void clearscreen(){reg short int i,c=sch(' ',White),n=gp_Cols*gp_Rows;
  300.     for(i=0;i<n;i++) screen[i]=c; for(i=0;i<gp_Rows;i++) Sl[i].ok=0;}
  301. /*----- clear this buffer's window only */
  302. void buffer::clearscreen(){reg int i; for(i=row1;i<=lastrow;i++) gp_clear(i);}
  303. /*----- buffer[int]: nth line in buffer */
  304. line* buffer::operator[](reg int n){reg int i; reg line *L;
  305. for(i=0,L=text.next;L;L=L->next,i++) if(i==n) return L; return 0;}
  306. /*----- go to this buffer */
  307. void buffer::go_to(){
  308. if(B==this) return; if(nrows) MOAN("this buffer already has a window");
  309. row1=B->row1; nrows=B->nrows; lastrow=B->lastrow; B->row1=B->nrows=B->lastrow=0;
  310. window[currentwindow]=B=this; B->redraw_info();}
  311. /*----- split window with this buffer, old window keeps N screen lines */
  312. void buffer::split_window_with(int N){reg int i,P=B->nrows-N;
  313. if(P<2 ?1: N<2) MOAN("window must have at least 2 lines");
  314. if(nrows) MOAN("this buffer already has a window");
  315. for(i=B->row1;i<=B->lastrow;i++) Sl[i].ok=0;
  316. B->nrows=N; nrows=P; lastrow=B->lastrow; B->lastrow-=P; row1=B->row1+N;
  317. for(i=nwindows++;i>currentwindow;i--) window[i]=window[i-1];
  318. for(i=row1;i<=lastrow;i++) Sl[i].buf=this; window[++currentwindow]=B=this;}
  319. /*----- read a Text from file */
  320. void Text::read(char*f){int F,b=0,i,j,k,m,n=1,z=0; line*K,*L=new line(),*N;K=L;
  321. if((F=open(f,0x8001))<0) {pr(disp,"new buffer '%s'",f); Display=disp; goto Z;}
  322. BLOCK: if((m=::read(F,CW,strsize))<0) {close(F); MOAN("file fault on reading");}
  323. if(!m) goto Y; z+=m; i=-1;
  324. LINE: i++; if(i) n++; for(k=i;i<m;i++) if(CW[i]==LF) break;
  325. if(!(n%50)) {pr(disp,"%5d lines = %6d bytes of %s read",n,b,f);
  326.     display(disp,B->lastrow,0,Blue+8);}
  327. N=new line(); *L-*N; j=i; *N=val(CW+k,j-k); L=N;
  328. N->la(!k); if(i<m) {b+=N->n+1; goto LINE;} goto BLOCK;
  329. Y: pr(disp,"file '%s' read, %1d lines, %1d bytes",f,n,z); Display=disp;
  330. Z: if(F>=0) close(F); beg=K; end=L; beg->la(1);
  331. for(L=beg;K=L->next;L=K) if(K->la()) {(*L)-(*this); K=L;} else K->la(1);
  332. for(L=beg;L->next;L=L->next)
  333.   if(L->n?L->s[L->n-1]==CR:0) {if(!(--L->n)) {delete L->s; L->s=0;}}
  334.   else L->no_cr(1);}
  335. /*-----*/
  336. val Text::asstring(){line*L; int c=0,i,n=0; for(L=beg;L;L=L->next) c+=L->n+1;
  337. char*s; val v; v.n=(c-1)>?0; v.s=new char[c>?1];
  338. for(L=beg;L;L=L->next) {s=L->s;
  339.     for(i=0;i<L->n;i++) v.s[n++]=s[i];
  340.     v.s[n++]=LF;}
  341. v.s[(--n)>?0]=0; return v;}
  342. /*----- read buffer from file */ /*** also moan if >1 buffers on this file ***/
  343. void buffer::read(){Text T;
  344. if(this!=B) MOAN("BUG: tried to read non-current buffer");
  345. if(!name) MOAN("BUG: buffer has no file"); T.read(name);
  346. changed=0; text.next=T.beg; text.prev=T.end; dot=bof();}
  347. /*----- write buffer to file */
  348. void buffer::write(){register int i; int F; line *L;
  349. if(name[0]=='(') MOAN("error in savebuffer: buffer has no file");
  350. if(attrib(name)==3*256) MOAN("bad filename, or can't find file's directory");
  351. char crlf[2]={CR,LF};
  352. pr(CW,"shall I write to file '%s'?",name); if(!yesno(CW)) goto AA;
  353. for(L=text.next;L;i++,L=L->next) if(L->n) if(!L->s) MOAN("BUG: corrupted line");
  354. if((F=open(name,0x8302))<0) MOAN("can't open file");
  355. for(i=1,L=text.next;L;i++,L=L->next) {
  356.     if(L->n) if(::write(F,L->s,L->n)<0) goto FAIL;
  357.     if(!L->next) break;
  358.     if(!(i%50)){pr(CW,"%5d lines written",i);::display(CW,B->lastrow,0,Blue+8);}
  359.     if((L->no_cr()?::write(F,crlf+1,1): ::write(F,crlf,2))<0) goto FAIL;}
  360. ::close(F); changed=0; AA: Sl[lastrow].ok=0; return;
  361. FAIL: ::close(F); MOAN("file fault on writing");}
  362. /*----- empty this buffer */
  363. void buffer::deltext(){text.prev->del(); dustbin=text.next;
  364. text.next=text.prev=new line(); /* leave one empty line */}
  365. /*-----*/
  366. int buffer::exists(){buffer*BB;
  367. for(BB=bhead;BB;BB=BB->next) if(BB==this) return 1; return 0;}
  368. /*-----*/
  369. buffer::~buffer(){while(dot.next) rem1mark(); delete name; del_every(val(this));
  370. deltext(); if(bound.n) delete bound.s; buffer**I;
  371. for(I=&bhead;*I;I=&((*I)->next)) if(*I==this) {*I=(*I)->next; break;}}
  372. /*----- put buffer into buffer chain */
  373. buffer*buffer::linkin(){line *L=new line(); text.next=text.prev=L;
  374. next=bhead; bhead=this; dot=mark(text.next,0); start=dot; return this;}
  375. /*----- move dot one in each direction */
  376. void buffer::left(){reg line *L; if(dot.c>0) {--dot.c; return;}
  377.     if(B->twod) MOAN("hit start of line");
  378.     if(L=dot.r->prev) dot.c=(dot.r=L)->n; else MOAN("hit start of buffer");}
  379. /*-----*/
  380. void buffer::right(){reg line *L; if(dot.c<dot.r->n) {++dot.c; return;}
  381.     if(B->twod) {(*dot.r)+=' '; dot.c++; B->changed=1; return;}
  382.     if(L=dot.r->next) {dot.c=0; dot.r=L;} else MOAN("hit end of buffer");}
  383. /*-----*/
  384. void buffer::up   (){if(!(dot.Up   ())) MOAN("hit start of buffer");}
  385. /*-----*/
  386. void buffer::down (){if(!(dot.Down ())) MOAN("hit end of buffer")  ;}
  387. /*----- move mark one in each direction */
  388. int mark::Left() {reg line *L; if(c>0   ) {--c; return 1;}
  389.     if(B->twod) return 0;
  390.     if(L=r->prev) {c=(r=L)->n; return 1;} return 0;}
  391. /*-----*/
  392. int mark::Right(){reg line *L; if(c<r->n) {++c; return 1;}
  393.     if(B->twod) {(*r)+=' '; c++; B->changed=1; return 1;}
  394.     if(L=r->next) {c=0; r=L; return 1;} return 0;}
  395. /*-----*//*** beware of dot left off end of line after up and down */
  396. int mark::Up()   {reg line *L=r->prev; if(!L) return 0; r=L; return 1;}
  397. /*-----*/
  398. int mark::Down() {reg line *L=r->next; if(!L) return 0; r=L; return 1;}
  399. /*----- copy a Text and all of its text */
  400. Text Text::copy(){line *J,*L,*M,*N,*Z=end;
  401. jmp_buf *oldbad=bad,killos; bad=&killos; if(setjmp(killos)) {
  402.     clear(); bad=oldbad; MOAN("memory got full in copying kill");}
  403. for(L=0,M=beg;M;M=M->next) {
  404.     N=new line(M); if(L) *L-*N; else J=N; L=N; if(M==Z) break;}
  405. J->prev=N->next=0; bad=oldbad; return Text(J,N);}
  406. /*----- empty a Text */
  407. void Text::clear(){if(beg) if(end) {end->del(); dustbin=beg;} beg=end=0;}
  408. /*----- mark1<mark2 : find what text order marks are in */
  409. int mark::operator<(mark&N){return r==N.r?c<N.c:r->lineno()<N.r->lineno();}
  410. /*----- print a Text to debug file */
  411. /* void Text::print(){line *L; int i;
  412. for(L=beg;L;L=L->next) {
  413.     for(i=0;i<L->n;i++) putc(L->s[i],debug); putc('¬',debug);}
  414. putc(LF,debug);} */
  415. /*----- tell display to print this text color f on color b */
  416. void region::color(int f,int b){B->col.Reg=*this; B->col.fg=f; B->col.bg=b;};
  417. /*----- if region is back to front, reverse its ends */
  418. void region::right_order(){
  419. if(!beg.r) MOAN("BUG: right_order() with zero left argument");
  420. if(!end.r) MOAN("BUG: right_order() with zero right argument");
  421. if(beg<end) return; mark W(beg); beg=end; end=W;};
  422. /*----- also counts (lines on screen) which are new since last time displayed */
  423. int region::ok_to_delete(){reg line*L; reg int n; char s[64];
  424. for(n=0,L=beg.r;L;L=L==end.r?0:L->next) if(L->sl==line_notshown) n++;
  425. if(n) {B->display();
  426.     for(n=0,L=beg.r;L;L=L==end.r?0:L->next) if(L->sl==line_notshown) n++;}
  427. if(!n) return 1; pr(s,"OK to delete or kill %1d lines which are off screen?",n);
  428. return yesno(s);}
  429. /*----- delete text between the marks */
  430. mark region::Delete(int query/*=1*/){reg line *I,*J,*K,*L; reg mark *Z;
  431. if(beg==end) return beg;
  432. if(query) if(!ok_to_delete()) MOAN("user abort"); B->changed=1;
  433. if((I=beg.r)==end.r) {reg int x=end.c-beg.c;
  434.     forallmarks(Z) if(Z->r==I) if(Z->c>beg.c) Z->c=beg.c>?(Z->c-x);
  435.     beg<<=x; *I-=x; return mark(I,beg.c);}
  436. *I=beg.c; J=I->next; K=end.r; L=end/1; *I-*L; --*I;
  437. for(K->next=0;J;J=J->next) {forallmarks(Z) if(Z->r==J) {Z->r=beg.r;Z->c=beg.c;}}
  438. if(dustbin) *K-*dustbin; else K->next=0; dustbin=J; return mark(I,beg.c);}
  439. /*----- kill text between the marks */
  440. mark region::kill(int query/*=1*/){reg line *I,*J,*K,*L; mark *Z;
  441. if(query) if(!ok_to_delete()) MOAN("user abort");
  442. static mark prevkill(0,0); B->changed=1; int C=beg.c;
  443. int z=prevobtype!=ob_kill ? 0 : prevkill==beg ? 1 : prevkill==end ? -1 : 0;
  444. if(!z) {nkill++; nkill&=15; killring[nkill].clear();} Text&W=killring[nkill];
  445. if(beg.r==end.r) {int x; I=beg.r; J=K=new line(I->s+C,x=end.c-C);
  446.     forallmarks(Z) if(Z->r==I) if(Z->c>C) Z->c=C>?(Z->c-x);
  447.     beg<<=x; *I-=x; beg.r->la(1);}
  448. else {I=beg.r; J=beg/0; K=end.r; L=end/1; *I-*L; --*I;}
  449. J->prev=K->next=0; /* seal ends of cut-out bit */
  450. for(L=J;L;L=L->next) forallmarks(Z) if(Z->r==L) *Z=mark(I,C);
  451. if(!z ?1: !W.beg) {W.beg=J; W.end=K;}
  452. else if(z<0) {*K-*W.beg; W.beg=J; *K-W;}
  453. else /* if(z>0) */ {*W.end-*J; W.end=K; J=J->prev; *J-W;}
  454. prevkill=mark(I,C); return prevkill;}
  455. /*----- copy text between marks to Text */
  456. void region::copykill(){line *J,*K; char c=B->changed;
  457. nkill++; nkill&=15; Text&W=killring[nkill]; W.clear(); if(beg==end) return;
  458. if(beg.r==end.r) {K=new line(beg.r->s+beg.c,end.c-beg.c); W=Text(K,K);}
  459. else {J=beg/0; K=end.r; end/1; W=Text(J,K).copy(); -*J; --*K; B->changed=c;}}
  460. /*----- copy region to mark, with fudge to save & restore an affected kill */
  461. region region::copyto(mark&m){if(beg==end) return m-m;
  462. if(m.isin(*this)) MOAN("can't copy text to within itself");
  463. Text *K=&killring[nkill],T=*K; K->beg=K->end=0; nkill--; copykill();
  464. region r=m.yank(killring[nkill],0); killring[nkill]=T; return r;}
  465. /*----- move region to mark */ /* push & pop m & end to be corrected for edit */
  466. region region::moveto(mark&m){if(beg==end) return m-m; B->changed=1;
  467. mark M; if(m==end?1:m==beg) return *this;
  468. if(m.isin(*this))MOAN("can't move text to within itself"); m.push(); end.push();
  469. line *A1=beg.r,*A2=beg/0; end.pop(); line *B1=end.r,*B2=end/1;
  470. *A1-*B2; --*A1; M.pop(); return M.yank(Text(A2,B1),0);}
  471. /*----- insert (copy of) Text at mark */
  472. region mark::yank(const Text&k,int copy/*=1*/){
  473. line *L=r,*M; Text K; mark Snip=*this,Z; K=k;
  474. if(!K.beg) return Snip-Snip; if(copy) K=K.copy(); M=Snip/1; B->changed=1;
  475. *L-*K.beg; *K.end-*M; mark(M,0).push(); --*L; -*M; Z.pop(); return Snip-Z;}
  476. /*-----*/
  477. int mark::isin(reg region &RR){reg line*L,*A=RR.beg.r,*R=r,*Z=RR.end.r;
  478. if(R==A) if(c<RR.beg.c) return 0; if(R==Z) return c<RR.end.c; if(R==A) return 1;
  479. if(A==Z) return 0; for(L=A->next;L!=Z;L=L->next) if(L==R) return 1; return 0;}
  480. /*-----*/
  481. byte to__upper[256],to__lower[256]; char chtype[256];
  482. region Found;
  483. /*-----*/
  484. /* void pq(char*s,int n){int i;
  485. putc('"',debug); for(i=0;i<n;i++) putc(s[i],debug); putc('"',debug);} */
  486. /*-----*/
  487. /* Micro-Emacs has : [ = char class start, - = range in [], ] = end of char
  488. class, ^ = negate char class, * = "closure, does not extend past newline", & =
  489. use matched string in replacement */
  490. /* try * = any chars: limit how many?: magic number = count & count limits? */
  491. /* try a symbol for `skip a word' & similar? */
  492. /*-----*//* returns region -> string found */
  493. int heremagic(mark A,val T){if(!T.magic(0)) if(T.s[0]!=*A) return 0;
  494. int b=0,d,sc; mark M(A),N; char sk,C=*M;
  495. char c,*t=T.s,*te=T.s+(T.n&0x00ffffff); byte D; Found=A-=mark(0,0);
  496. if(!M.r) return 0; mark B[17]; B[0]=A;
  497. if(T.magic(0)) if(*t=='/') if(!M.c) t++; /* leading magic '/', at bol already */
  498. CH: if(Breakin()) MOAN("user abort"); if(t>=te) goto OK;
  499. if(!T.magic(t-T.s)) if(C!=*t++) goto NO; else goto NEXT;
  500. switch(D=*t++) {
  501. default: if(isalpha(D))if(D==to_lower(D)){if(D==to_lower(C))goto NEXT; goto NO;}
  502.     Moan="illegal magic character in search: ' '"; Moan[36]=t[-1]; MOAN(Moan);
  503. case '[': if(b>=16) MOAN("magic [] > 16 deep"); B[++b]=M; goto CH;
  504. case '|': if(b<1) MOAN("magic | outside [ ]");
  505.     for(d=b;t<te;t++) if(T.magic(t-T.s)) if((c=*t)=='[') b++;
  506.   else if(c==']') if(--b<d) {t++; goto CH;} goto OK; /* OK to |, skip to ] */
  507. case ']': if(--b<0) MOAN("unpaired magic ]"); goto CH;
  508. case '{': Found.beg=M; goto CH;
  509. case '}': Found.end=M; goto CH;
  510. case '=': M.c=M.r->n; C=LF; goto CH;
  511. case '#': if(isalnum(C)) goto NO; goto NEXT;
  512. case ',': if(!isalnum(C)) goto NO; goto NEXT;
  513. case '/': if(M.c==M.r->n) goto NEXT; /* falls thru */
  514. case '\\': if(!M.r->prev) if(!M.c) goto CH;
  515.        if(!M.r->next) if(M.c==M.r->n) goto CH; goto NO;
  516. case 9:   sk=9; goto SKIP;
  517. case ' ': sk=' '; goto SKIP;
  518. SKIP: sc=0; SK: if(C==' '?:C==9) {++M; C=*M; sc++; goto SK;}
  519.     if(C==LF) if(sk==9) if(N=M,++M,M.r) {sc++; C=*M; goto SK;} else M=N;
  520.     if(!sc) goto NO; goto CH;
  521. case '.': if(C==LF) goto NO; goto NEXT;}  goto CH;
  522. NEXT: N=M; ++M; if(!M.r) {M=N; goto NO;} C=*M; goto CH;
  523. NO: if(b>0) for(d=b;t<te;t++) if(T.magic(t-T.s)) /* skip to | for alt to try */
  524.        if(*t=='[') b++; /* allow omitted initial [ and final ] */
  525.   else if(*t=='|') {if(b<=d) {M=B[b]; C=*M; t++; goto CH;}}
  526.   else if(*t==']') {if(b) d<?=--b;} return 0;
  527. OK: if(b>0) MOAN("unpaired magic ["); if(!Found.end.r) Found.end=M;
  528. if(Found.end<Found.beg) MOAN("magic {range} back-to-front"); return 1;}
  529. /*-----*//* returns mark -> end of string found */
  530. int here(mark A,val T,int word/*=0*/){
  531. char*s=A.r->s+A.c,*se,*t=T.s,*te=t+T.n;
  532. if(word) if(A.c>=word) if(isalnum(s[-word])) return 0; Found.beg=A;
  533. if(Breakin()) MOAN("user abort"); L: se=(A.r->s+A.r->n)<?(s+(te-t));
  534. if(B->Case) while(s<se) {if(*s++!=*t++) return 0;}
  535. else while(s<se) if(to_upper(*s++)!=to_upper(*t++)) return 0;
  536. if(t>=te) {Found.end=mark(A.r,s-A.r->s); return word?!isalnum(*Found.end):1;}
  537. if(!(A.r=A.r->next)) return -1; if(*t++!=LF) return 0; s=A.r->s; goto L;}
  538. /*----- search forwards for text in region */
  539. int region::huntf(val T,int word/*=0*/) {if(!T.n) return 0;
  540. reg char *s,*se,C; line*A=beg.r,*D; if(!A) return 0; int a=beg.c,v=0,i,n; val U;
  541. line*e=end.r; line*E=e->next; e->next=0; int en=e->n; e->n=end.c; /*pseudo eof*/
  542. jmp_buf*oldbad=bad,failed; bad=&failed; if(setjmp(*bad)) goto F;
  543. if(T.magic()) if(T.magic(0)?0:T.s[0]==LF) {for(;A;A=A->next)
  544.       if(heremagic(mark(A,A->n),T)) {v=1; goto F;}  goto F;}
  545. else {for(;A;A=A->next,a=0) for(n=A->n;a<=n;a++)
  546.       if(heremagic(mark(A,a   ),T)) {v=1; goto F;}  goto F;}
  547. C=B->Case?T.s[0]:to_upper(T.s[0]); U=val(T.s+1,T.n-1); if(word) word=2;
  548. if(C==LF) {for(;D=A->next;A=D) if(i=here(mark(D,0),U),i>0) {
  549.     Found.beg=mark(A,A->n); v=1; goto F;} else if(i<0) goto F;}
  550. else if(B->Case) {for(;A;A=A->next,a=0) for(se=A->s+A->n,s=A->s+a;s<se;s++)
  551.       if(         *s ==      C) if(i=here(mark(A,s-A->s+1),U,word),i>0) {
  552.     Found.beg.c--; v=1; goto F;} else if(i<0) goto F;}
  553. else             {for(;A;A=A->next,a=0) for(se=A->s+A->n,s=A->s+a;s<se;s++)
  554.       if(to_upper(*s)==(byte)C) if(i=here(mark(A,s-A->s+1),U,word),i>0) {
  555.     Found.beg.c--; v=1; goto F;} else if(i<0) goto F;}
  556. F: bad=oldbad; e->next=E; e->n=en; /* remove pseudo eof */ if(Moan) MOAN(Moan);
  557. return v;}
  558. /*----- search backwards for text in region */
  559. int region::huntb(val T,int word/*=0*/) {if(!T.n) return 0;
  560. reg char *s,*sb; line*e=end.r,*D,*A,*b=beg.r?:B->text.next; if(!e) return 0;
  561. int a=end.c,v=0; val U; reg char C; static char X;
  562. line*E=e->next; e->next=0; int en=e->n; e->n=end.c; /* pseudo eof */
  563. line*BB=b->prev; b->prev=0; b->s+=beg.c; b->n-=beg.c; /* make a pseudo bof */
  564. jmp_buf*oldbad=bad,failed; bad=&failed; if(setjmp(*bad)) goto F;
  565. if(T.magic()) if(T.magic(0)?0:T.s[0]==LF) {for(A=e;A;A=A->prev)
  566.       if(heremagic(mark(A,A->n),T)) {v=1; goto F;}  goto F;}
  567. else {for(A=e;A;A=A->prev) for(a=A->n;a>=0;a--)
  568.       if(heremagic(mark(A,a   ),T)) {v=1; goto F;}  goto F;}
  569. C=B->Case?T.s[0]:to_upper(T.s[0]); U=val(T.s+1,T.n-1); if(word) word=2;
  570. if(C==LF) {for(A=e;D=A,A=A->prev;) if(here(mark(D,0),U,word)>0) {
  571.     Found.beg=mark(A,A->n); v=1; goto F;}}
  572. else if(B->Case) {for(A=e;A;A=A->prev) for(s=(sb=A->s?:&X)+A->n-1;s>=sb;s--)
  573.       if(         *s ==      C) if(here(mark(A,s-A->s+1),U,word)>0) {
  574.       Found.beg.c--; v=1; goto F;}}
  575. else             {for(A=e;A;A=A->prev) for(s=(sb=A->s?:&X)+A->n-1;s>=sb;s--)
  576.       if(to_upper(*s)==(byte)C) if(here(mark(A,s-A->s+1),U,word)>0) {
  577.       Found.beg.c--; v=1; goto F;}}
  578. F: bad=oldbad; e->next=E; e->n=en; b->prev=BB; b->s-=beg.c; b->n+=beg.c;
  579. /* remove pseudo bof & eof */ if(Moan) MOAN(Moan); return v;}
  580. /*** treats e.g. 'fi' as word 'i' if mark beg is at the i. Similarly huntf. */
  581. /*-----*/
  582. void line::de_trailing_space(){mark M(this,n),N(this,n);
  583. N.skip_white(1,1); (N-M).Delete(0);}
  584. /*-----*/
  585. void mark::skip_white(int back/*=0*/,int Eol/*=0*/){
  586. if(back) while(!c   ?(Eol?r->prev!=0:0):mark(r,c-1).is_white()) --*this;
  587. else     while(eol()?(Eol?r->next!=0:0):            is_white()) ++*this;}
  588. /*-----*/
  589. void mark::find_white(int back/*=0*/){
  590. if(back) while(!c   ?0:!mark(r,c-1).is_white()) --*this;
  591. else     while(eol()?0:!            is_white()) ++*this;}
  592. /*-----*/
  593. void mark::skip_alnum(int back/*=0*/){
  594. if(back) while(!c   ?0:mark(r,c-1).is_alnum()) --*this;
  595. else     while(eol()?0:            is_alnum()) ++*this;}
  596. /*-----*/
  597. void mark::find_alnum(int back/*=0*/,int Eol/*=0*/){
  598. if(back) while(!c   ?(Eol?r->prev!=0:0):!mark(r,c-1).is_alnum()) --*this;
  599. else     while(eol()?(Eol?r->next!=0:0):!            is_alnum()) ++*this;}
  600. /*-----*/
  601. void line::split_if_long(){int nn; line *L=this,*N; mark M;
  602. B->dotcc=-1;
  603. AGAIN: nn=L->from_tabexp(abs(B->rmargin)-1,B->rmargin<0); if(L->n<=nn) return;
  604. M=mark(L,nn); if(M.is_white()) M.skip_white(0); else M.find_white(1);
  605. if(!M.c) {M.find_white(0); M.skip_white();}
  606. if(M.c<L->n) {N=M/2; L->de_trailing_space(); L=N; goto AGAIN;}}
  607. /*----- turn val into Text */
  608. Text::Text(val&T){line *J,*K,*L; int i=0,j,n=T.n; char *t=T.s;
  609. K=new line(); J=K; AGAIN: for(j=i;i<n;i++) if(t[i]==LF) break;
  610. *K=val(t+j,i-j); if(i>=n) goto OUT; L=new line(); *K-*L; K=L; i++; goto AGAIN;
  611. OUT: beg=J; end=K;}
  612. /*----- replace text between the marks */
  613. region region::repl(Text&k,int copy/*=1*/){B->changed=1;
  614. line *I,*J,*K,*L; mark M,*Z; int x; Text T(k);
  615. if((I=beg.r)==end.r) {L=beg/0; x=end.c-beg.c;
  616.     forallmarks(Z) if(Z->r==L) Z->c=(Z->c-x)>?0; mark(L,0)<<=x; *L-=x;}
  617. else {*I=beg.c; J=I->next; K=end.r; L=end/1; K->next=0;
  618.     for(;J;J=J->next) {forallmarks(Z) if(Z->r==J) *Z=beg;}
  619.     K->del(); dustbin=J;}
  620. if(!T.beg) {*I-*L; --*I; return beg-beg;} if(copy) T=T.copy();
  621. *I-*T.beg; *T.end-*L; mark(L,0).push(); --*I; -*L; M.pop(); return beg-M;}
  622. /*----- replace all occurences of Old by New in region */
  623. void region::replace(val Old,val New,int ask/*=0*/,int word/*=0*/){
  624. int i,j,k; mark Q;
  625. if(!Old.n) MOAN("tried to search-&-replace nil");
  626. static char*notword="object searched for is not a word";
  627. static char*Z="\
  628. space = yes, N = no, . = yes & exit, alt-end = no & exit, end = no & stay here";
  629. if(word>0) {if(Old.magic()) MOAN(notword);
  630.     for(i=0;i<Old.n;i++) if(!isalnum(Old.s[i])) MOAN(notword);}
  631. int c=' '; B->dot.push(); j=k=0; Q=beg;
  632. NO: if(Q==end?1:!(Q-=end).huntf(Old,word)) goto OUT; Q=Found.end; j++;
  633. if(ask) {B->dot=Found.beg; B->dotcc=-1; Found.color(White,Blue);
  634.     B->display(); showmoan();
  635. X:  display(Z,B->lastrow,0,Cyan); Sl[B->lastrow].ok=1;
  636.     switch(c=getkey()) {
  637.     case -end_: Q.pop(); goto EXIT;
  638.     case -alt_end: goto OUT;
  639.     case 'N': case 'n': case -mbutton: case -rbutton: goto NO;
  640.     case ' ': case '.': case -lbutton: goto F;
  641.     default: if(!Breakin()) goto X;}}
  642. F: end.push(); Q=Found.repl(Text(New)).end; k++; end.pop(); if(c!='.') goto NO;
  643. OUT: B->dot.pop(); EXIT: B->dotcc=-1; Sl[B->lastrow].ok=0;
  644. pr(disp,"%1d found, %1d replaced",j,k); Display=disp;}
  645. /*-----*/
  646. val&getkeyseq(val P){int z=1,c=1; keyarray *keytable=&keys;
  647. val *Key; display(P,B->lastrow,0,Blue+8); c=getkey();
  648. ARRAY: if(c<0) {keyseqc[z++]=0; keyseqc[z++]=-c; Key=&(*keytable)[0][-c];}
  649. else {keyseqc[z++]=c; Key=&(*keytable)[c];} keyseqc[0]=z;
  650. if(Key->n!=_keyarray) return *Key;
  651. if(c){pr(CW,"%v %k   (more reqd)",&P,&keyseq); display(CW,B->lastrow,0,Blue+8);}
  652. c=getkey(); if(c>0) c=to_upper(c); keytable=Key->k; goto ARRAY;}
  653. /*----- look up val in key table, if bound to a keysequence */
  654. val&val::keyseq(){int j=1,nn; val*Key; keyarray *kt=&keys; static val X(0,_bad);
  655. if(n==_char) {if(i&~255) Key=&keys[0][i&255]; else Key=&keys[i&255];
  656.     if(Key->n==_keyarray) {X.s="end of keysequence missing"; return X;}
  657.     return *Key;}
  658. if(n!=_keyseq?:(nn=s[0])<2) {X.s="subr arg is not a keysequence"; return X;}
  659. byte C=s[1],D; ARRAY: keyseqc[j++]=C; Key=&(*kt)[C];
  660. if(Key->n!=_keyarray) {if(j!=nn) {X.s="extra keys after keysequence"; return X;}
  661.     keyseqc[0]=j; return *Key;}
  662. if(j>=nn) {X.s="end of keysequence missing"; return X;}
  663. D=C; C=s[j]; if(D) C=to_upper(C); kt=Key->k; goto ARRAY;}
  664. /*----- ask user for string arg if necessary */
  665. val val::getifn(val&W,char*prompt,int type/*=0*/,char*start/*=0*/){
  666. char*w,*zap="magic string not allowed here"; int max; buffer*C; val Z;
  667. if(n?0:!s) {max=strsize;
  668.     if(start) {max=-max; strcpy(W.s,start); W.n=strlen(W.s);}
  669.     getstring(prompt,B->lastrow,W,max,type); n=W.n; s=W.s;}
  670. else if(n<0) if(!magic()) MOAN("subroutine arg is not a string");
  671. if(type!=_magic) if(magic()) MOAN(zap);
  672. switch(type) {default: return val();
  673. case _subr:
  674.     if(!n) {strcpy(s=W.s,(Z=subrmenu(prompt),Z.S->name)); n=W.n=strlen(s);}
  675.     else Z=named(*this);
  676.     if(Z.n!=_Subr) if(Z.n!=_macro) MOAN("not subroutine and not macro");
  677.     return Z;
  678. case _buffer: C=0;
  679.     if(W.n==1) if(W.s[0]=='=') {strcpy(W.s,B->name); W.n=strlen(W.s);}
  680.     w=s; s=W.s;
  681.     if(!n) strcpy(s,(C=buffer_menu(prompt))->name); else fullfilename(s,w);
  682.     W.n=strlen(s); if(attrib(s)==3*256)MOAN("can't find this file's directory");
  683.     if(attrib(s)&16) filefromdirmenu(W,prompt,B->name); n=W.n;
  684.     return C?val(C):val();}}
  685. /*-----*/
  686. struct {int n; char*s;} smh[20]={
  687. {-alt_R,      " copy 1st arg of replace command into 2nd arg"},
  688. {-alt_K,      " insert name of key sequence"},
  689. {-alt_C,      " insert the name of the current buffer"},
  690. {-alt_B,      " insert name of buffer which is bound to a key"},
  691. {-alt_Y,      " insert last kill"},
  692. {-ctrl_tab,   " menu of characters"},
  693. {-del_,       " delete a character (forwards if possible)"},
  694. {-alt_del,    " delete a character backwards"},
  695. {-alt_ret,    " insert a carriage-return"},
  696. {-leftarrow,  " move one position left"},
  697. {-rightarrow, " move one position right"},
  698. {-home,       " move to start of string"},
  699. {-end_,       " move to end of string"},
  700. {-f1,         " switch this char between magic and non-magic"},
  701. {-f2,         " switch previous char between magic and non-magic"},
  702. {-f3,         " start or stop typing magic"},
  703. {-alt_end,    " abort the command that wanted this string arg"},
  704. {-alt_minus,  " help"},
  705. {-pagedown,   " accept string arg as it is"},
  706. {-2,          " exit from this submenu"}}; enum{smhn=20};
  707. /*-----*/
  708. void getstring(char*prompt,int sl,val &T,int max,int type/*=0*/){
  709. #define MoaN(s) ({Moan=s; goto BAD;})
  710. int c=0,i,j,k=-1,l,M,N=T.n&0x00ffffff,n=0,prmag=0,ptl=strlen(prompt); short*sd;
  711. int pp,C,Q,I; val*f,g; char *s,*SC; mousestate ms,MS; ms=Jerry;
  712. extern char*getstringhelp[],*magichelp[]; if(max<0) {max=-max; n=N; k++;}
  713. if(!T.s) T.s=new char[max]; c_short_addr Sc=Sl[sl].sa; uns short S[strsize+1];
  714. for(i=0;i<N;i++) S[i]=T.s[i]&255; pp=(gp_Cols-ptl)/2; Jerry.mc=1;
  715. if(T.magic()) {s=T.s+N+1; for(i=0;i<N;i++) if(bit(s,i)) S[i]|=256;}
  716. GETCHAR: for(i=0;i<ptl;i++) Sc[i]=sch(prompt[i],Magenta);
  717. Jerry.range(4,(N+1)<?2048);
  718. C=n<=2*pp?0:((n-pp/2)/pp)*pp; M=N; X: j=((N-C)<?(gp_Cols-ptl));
  719. for(i=0;i<j;i++) Sc[i+ptl]=sch(S[C+i]&255,((S[C+i]&256)?Orange+8:Cyan));
  720. for(i=0;i<j;i++) if(l=S[C+i]-256,l==' '?:l==255?:!l) Sc[i+ptl]=254|(Orange<<12);
  721. if(C) Sc[ptl]=sch(17,Red); if(N-C>gp_Cols-ptl) Sc[gp_Cols-1]=sch(16,Red);
  722. gp_clear(sl,ptl+N-C);
  723. Y: cursor.r=sl; cursor.c=((n-C+ptl)>?0)<?(gp_Cols-1);
  724. if(k<0) display("(alt-minus for help)",sl,(ScreenCols()-21)>?cursor.c,Green+8);
  725. if(c!=-mousemove) Jerry.move(0,n); gp_cursor(cursor); c=getkey(); if(!++k) N=0;
  726. switch(c){ /* special char */
  727. case -alt_R: if(T.s!=T2w ?: T1t.n<=0 ?: T1t.s!=T1w) break;
  728.     if(max-N<T1t.n) MoaN("text is too long to fit in reply");
  729.     for(i=N-1;i>=n;i--) S[i+T1t.n]=S[i]; for(i=0;i<T1t.n;i++) S[n+i]=T1w[i]&255;
  730.     n+=T1t.n; N+=T1t.n; break;
  731. case -alt_K: f=&getkeyseq("type key sequence whose name you want to insert: ");
  732.     pr(CW,"%k",&keyseq); g=CW; goto Z;
  733. case -alt_C: g=B->name; goto Z;
  734. case -alt_B: f=&getkeyseq("key sequence bound to a buffer:");
  735.     if(f->n!=_buffer) {pr(CW,"%k is not bound to a buffer",&keyseq); MoaN(CW);}
  736.     g=f->b->name; goto Z;
  737. case -alt_Y: g=killring[nkill].asstring(); goto Z; /* insert last kill */
  738. Z: if(max-N<g.n) MoaN("too long to fit in reply");
  739.     for(i=N-1;i>=n;i--) S[i+g.n]=S[i]; for(i=0;i<g.n;i++) S[n+i]=g.s[i]&255;
  740.     n+=g.n; N+=g.n; if(c==-alt_Y) delete g.s; break;
  741. case -del_:    if(N) {for(i=n+1;i<N;i++) S[i-1]=S[i]; --N; n<?=N;} break;
  742. case -alt_del: if(n) {for(i=n--;i<N;i++) S[i-1]=S[i]; --N; n<?=N;} break;
  743. case -leftarrow: if(n>0) n--; break;
  744. case -rightarrow: n++; n<?=N; break;
  745. case -home: n=0; break;
  746. case -end_: if(!k) N=M; n=N; break; /* if end first, keep the old reply */
  747. case -f1: if(n<N) S[n]^=256; break;
  748. case -f2: if(n>0) S[n-1]^=256; break;
  749. case -f3: prmag=256-prmag; break;
  750. case -alt_ret: c=CR; goto C;
  751. case -alt_end: case 0: T.n=0; MoaN("user abort");
  752. case -alt_minus: sd=screendump();
  753.     for(i=0;SC=getstringhelp[i];i++) display(SC,i,0,Cyan);
  754.     display("(type any key to continue)",0,52,Cyan); getkey();
  755.     screenrestore(sd); sd=screendump();
  756.     for(i=0;SC=magichelp[i];i++) display(SC,i,0,Cyan);
  757.     display("(type any key to continue)",0,52,Cyan); getkey();
  758.     screenrestore(sd); break;
  759. case -ctrl_tab: i=B->dotcc; j=B->dot.c; c=charfrommenu();
  760.     B->dotcc=i; B->dot.c=j; /* cancel effect of dotty() */ if(c<0)break; goto C;
  761. case -mousemove: k--; n=Jerry.x; j=C; C=n<=2*pp?0:((n-pp/2)/pp)*pp;
  762.     if(j==C) goto Y; else goto X;
  763. case -lbuttond: case -mbuttond: case -rbuttond: k--; goto X;
  764. case -lbutton: if(!k) if(type==_buffer) {S[0]='.'; S[1]=0; N=1; k=0;} /*run on*/
  765.     else break; /* get a dir menu */
  766. case -pagedown: Sl[sl].ok=0; T.n=N=k?N:M; s=T.s+N+1;
  767.     for(i=0;i<N;i++) T.s[i]=S[i]; for(i=lmagic(N)-1;i>=N;i--) T.s[i]=0; k=0;
  768.     for(i=0;i<N;i++) if(S[i]&256) {s[i>>3]|=(128>>(i&7)); k=1;}
  769.     if(k) T.n=N|0x80000000; Jerry=ms; return; /* exit */
  770. case -f4: case -rbutton: case -mbutton: {short ss[gp_Cols*gp_Rows]; Q=0; k--;
  771.     c_get(ss,screen,gp_Cols*gp_Rows);
  772.     for(i=0;i<smhn;i++) display(smh[i].s,i,31,Cyan);
  773.     MS=Jerry; Jerry.move(0,0); Jerry.range(smhn,4); i=0; scr(0,31)=1+256*White;
  774.     MENU: I=i; scr(i,31)=1+256*White; if(Q!=-mousemove) Jerry.move(i,0);
  775.     switch(Q=getkey()) {
  776.     case -uparrow: i=(i+smhn-1)%smhn; break;
  777.     case -downarrow: i=(i+1)%smhn; break;
  778.     case -mousemove: i=Jerry.y; break;
  779.     case -mbutton: case -rbutton: i=19;
  780.     case CR: case -lbutton: inject(smh[i].n);
  781.         Jerry=MS; for(i=0;i<smhn;i++) display(" ",i+1,47,White);
  782.         c_put(screen,ss,gp_Cols*gp_Rows); goto Y;}
  783.     if(I!=i) scr(I,31)=' '+256*White; goto MENU;}
  784. default: if(c&~255) goto GETCHAR; if(c==CR) c=LF; /* so RET makes end-of-line */
  785. C:  if(N<max) {for(i=N++;i>n;i--) S[i]=S[i-1]; S[n++]=(c&255)+prmag;}}
  786. N>?=n; N<?=max; goto GETCHAR;  BAD: Jerry=ms; MOAN(Moan);}
  787. /*-----*/
  788. char*getstringhelp[]={
  789. "[SPECIAL KEYS USED WHEN INPUTTING STRING ARGUMENTS]",
  790. "printing & control chars & space & newline go into the string argument",
  791. "alt-B          then keysequence bound to buffer, inserts that buffer's name",
  792. "alt-C          inserts the current buffer's name",
  793. "alt-K          then keysequence: inserts that keysequence's name",
  794. "alt-R          in 2nd arg of replace commands, insert 1st arg just entered",
  795. "alt-Y          insert last kill",
  796. "delete         delete this character (at end of string, previous character)",
  797. "alt_delete     delete previous character",
  798. "\033              go left one character",
  799. "\032              go right one character",
  800. "home           go to start of string",
  801. "end            go to end of string. If first keypunch, do not lose old string",
  802. "alt_end        abort the command that asked for this string argument",
  803. "ctrl_tab       menu to input a character",
  804. "F1             change char from normal (cyan) to magic (yellow) or vice-versa",
  805. "F2             ditto but previous char (usually the character just typed)",
  806. "F3             start or stop typing chars as magic",
  807. "F4             get menu of these options",
  808. "pagedown       accept string as it is",
  809. "alt_ret        put a ctrl-M (CR) (carriage-return) into the string as a byte",
  810. "ret or ctrlJ or ctrlM    put a ctrl-J (LF) (end-of-line) into the string",
  811. 0};
  812. /*-----*/
  813. byte esc_alt[26]={alt_A,alt_B,alt_C,alt_D,alt_E,alt_F,alt_G,alt_H,alt_I,
  814.     alt_J,alt_K,alt_L,alt_M,alt_N,alt_O,alt_P,alt_Q,alt_R,alt_S,alt_T,alt_U,
  815.     alt_V,alt_W,alt_X,alt_Y,alt_Z};
  816. /*-----*/
  817. void out_of_store(){Moan="out of store"; longjmp(*bad,1);}
  818. /*----- replace all tabs by spaces in region */
  819. void region::expandtabs(){B->changed=1;
  820. reg int i,j,k,m; int b,e,D,E,n,N; mark *M; line *L; reg char*s,*t,c; int *mc;
  821. for(L=beg.r,b=beg.c;L;L=L->next,b=0) {
  822.     L->la(1); s=L->s; n=L->n; e=L==end.r?end.c:n; m=0;
  823.     for(D=i=0  ;i<b;i++) if(s[i]==9)  D=totab(D);       else D++;
  824.     for(E=D,i=b;i<e;i++) if(s[i]==9) {E=totab(E); m=1;} else E++;
  825.     if(!m) goto NEXT;
  826.     if(!(j=E-D-e+b)) {for(i=b;i<e;i++) if(s[i]==9) s[i]=' '; goto NEXT;}
  827.     t=new char[roundup(N=n+j)]; mc=new int[n+1];
  828.     for(i=0;i<b;i++) t[mc[i]=i]=s[i];
  829.     for(j=D,m=i=b;i<e;i++) {mc[i]=m; if((c=s[i])==9)
  830.     for(k=j,j=totab(j);k<j;k++) t[m++]=' '; else {j++; t[m++]=c;}}
  831.     for(i=e;i<n;i++) t[mc[i]=m++]=s[i]; mc[n]=m;
  832.     forallmarks(M) if(M->r==L) M->c=mc[M->c];
  833.     delete mc; L->s=t; delete s; L->n=N;
  834.     NEXT: if(L==end.r) return;}}
  835. /*----- replace spaces by tabs in region */
  836. void region::maketabs(){B->changed=1;
  837. reg int i,j,k,m; int b,e,D,E,n,N; mark *M; line *L; reg char*s,*t; int *mc;
  838. for(L=beg.r,b=beg.c;L;L=L->next,b=0) {
  839.     L->la(1); s=L->s; n=L->n; e=L==end.r?end.c:n; m=0;
  840.     for(D=i=0  ;i<b;i++) if(s[i]==9) D=totab(D); else D++;
  841.     for(E=D,i=b;i<e;i++) if(s[i]==9) E=totab(E); else E++;
  842.     j=E-D-e+b; t=new char[roundup(N=n+j)]; mc=new int[n+1];
  843.     for(j=D,i=b;i<e;i++) {mc[i]=j; if(s[i]==9) j=totab(j); else j++;}
  844.     for(i=b;i<e;i++) if(s[i]==' ') if(!sptotab(mc[i])) s[i]=9;
  845.     for(i=e-1;i>b;i--) if(s[i]==9 ?1: mc[i]==-2) if(s[i-1]==' ') mc[i-1]=-2;
  846.     for(i=b>?1;i<e;i++) if(s[i]==9) if(!sptotab(mc[i])) if(mc[i-1]!=-2)s[i]=' ';
  847.     for(i=0;i<b;i++) t[mc[i]=i]=s[i]; m=b;
  848.     for(i=b;i<e;i++) {k=mc[i]; mc[i]=m; if(k>=0) t[m++]=s[i];}
  849.     for(i=e;i<n;i++) t[mc[i]=m++]=s[i]; mc[n]=m;
  850.     forallmarks(M) if(M->r==L) M->c=mc[M->c]; delete mc;
  851.     L->s=t; delete s; L->n=m; if(L==end.r) return;}}
  852. /*-----*/
  853. short*screendump(){int i=ScreenRows()*ScreenCols(); short*s=new short[i];
  854.     c_get(s,screen,i); return s;}
  855. void screenrestore(short*s){int i=ScreenRows()*ScreenCols();
  856.     c_put(screen,s,i); delete s;}
  857. /*-----*/
  858.