1implement Textm; 2 3include "common.m"; 4include "keyboard.m"; 5 6sys : Sys; 7utils : Utils; 8framem : Framem; 9drawm : Draw; 10acme : Acme; 11graph : Graph; 12gui : Gui; 13dat : Dat; 14scrl : Scroll; 15bufferm : Bufferm; 16filem : Filem; 17columnm : Columnm; 18windowm : Windowm; 19exec : Exec; 20 21Dir, sprint : import sys; 22frgetmouse : import acme; 23min, warning, error, stralloc, strfree, isalnum : import utils; 24Frame, frinsert, frdelete, frptofchar, frcharofpt, frselect, frdrawsel, frdrawsel0, frtick : import framem; 25BUFSIZE, Astring, SZINT, TRUE, FALSE, XXX, Reffont, Dirlist,Scrollwid, Scrollgap, seq, mouse : import dat; 26EM_NORMAL, EM_RAW, EM_MASK : import dat; 27ALPHA_LATIN, ALPHA_GREEK, ALPHA_CYRILLIC: import Dat; 28BACK, TEXT, HIGH, HTEXT : import Framem; 29Flushon, Flushoff : import Draw; 30Point, Display, Rect, Image : import drawm; 31charwidth, bflush, draw : import graph; 32black, white, mainwin, display : import gui; 33Buffer : import bufferm; 34File : import filem; 35Column : import columnm; 36Window : import windowm; 37scrdraw : import scrl; 38 39cvlist: adt { 40 ld: int; 41 nm: string; 42 si: string; 43 so: string; 44}; 45 46# "@@", "'EKSTYZekstyz ", "ьЕКСТЫЗекстызъЁё", 47 48latintab := array[] of { 49 cvlist( 50 ALPHA_LATIN, 51 "latin", 52 nil, 53 nil 54 ), 55 cvlist( 56 ALPHA_GREEK, 57 "greek", 58 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 59 "ΑΒΞΔΕΦΓΘΙΪΚΛΜΝΟΠΨΡΣΤΥΫΩΧΗΖαβξδεφγθιϊκλμνοπψρστυϋωχηζ" 60 ), 61 cvlist( 62 ALPHA_CYRILLIC, 63 "cyrillic", 64 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 65 "АБЧДЭФГШИЙХЛМНОПЕРЩЦУВЮХЯЖабчдэфгшийхлмноперщцувюхяж" 66 ), 67 cvlist(-1, nil, nil, nil) 68}; 69 70alphabet := ALPHA_LATIN; # per window perhaps 71 72setalphabet(s: string) 73{ 74 for(a := 0; latintab[a].ld != -1; a++){ 75 k := latintab[a].ld; 76 for(i := 0; latintab[i].ld != -1; i++){ 77 if(s == transs(latintab[i].nm, k)){ 78 alphabet = latintab[i].ld; 79 return; 80 } 81 } 82 } 83} 84 85transc(c: int, k: int): int 86{ 87 for(i := 0; latintab[i].ld != -1; i++){ 88 if(k == latintab[i].ld){ 89 si := latintab[i].si; 90 so := latintab[i].so; 91 ln := len si; 92 for(j := 0; j < ln; j++) 93 if(c == si[j]) 94 return so[j]; 95 } 96 } 97 return c; 98} 99 100transs(s: string, k: int): string 101{ 102 ln := len s; 103 for(i := 0; i < ln; i++) 104 s[i] = transc(s[i], k); 105 return s; 106} 107 108init(mods : ref Dat->Mods) 109{ 110 sys = mods.sys; 111 framem = mods.framem; 112 dat = mods.dat; 113 utils = mods.utils; 114 drawm = mods.draw; 115 acme = mods.acme; 116 graph = mods.graph; 117 gui = mods.gui; 118 scrl = mods.scroll; 119 bufferm = mods.bufferm; 120 filem = mods.filem; 121 columnm = mods.columnm; 122 windowm = mods.windowm; 123 exec = mods.exec; 124} 125 126TABDIR : con 3; # width of tabs in directory windows 127 128# remove eventually 129KF : con 16rF000; 130Kup : con KF | 16r0E; 131Kleft : con KF | 16r11; 132Kright : con KF | 16r12; 133Kend : con KF | 16r18; 134Kdown : con 16r80; 135 136 137nulltext : Text; 138 139newtext() : ref Text 140{ 141 t := ref nulltext; 142 t.frame = framem->newframe(); 143 return t; 144} 145 146Text.init(t : self ref Text, f : ref File, r : Rect, rf : ref Dat->Reffont, cols : array of ref Image) 147{ 148 t.file = f; 149 t.all = r; 150 t.scrollr = r; 151 t.scrollr.max.x = r.min.x+Scrollwid; 152 t.lastsr = dat->nullrect; 153 r.min.x += Scrollwid+Scrollgap; 154 t.eq0 = ~0; 155 t.ncache = 0; 156 t.reffont = rf; 157 t.tabstop = dat->maxtab; 158 for(i:=0; i<Framem->NCOL; i++) 159 t.frame.cols[i] = cols[i]; 160 t.redraw(r, rf.f, mainwin, -1); 161} 162 163Text.redraw(t : self ref Text, r : Rect, f : ref Draw->Font, b : ref Image, odx : int) 164{ 165 framem->frinit(t.frame, r, f, b, t.frame.cols); 166 rr := t.frame.r; 167 rr.min.x -= Scrollwid; # back fill to scroll bar 168 draw(t.frame.b, rr, t.frame.cols[Framem->BACK], nil, (0, 0)); 169 # use no wider than 3-space tabs in a directory 170 maxt := dat->maxtab; 171 if(t.what == Body){ 172 if(t.w != nil && t.w.isdir) 173 maxt = min(TABDIR, dat->maxtab); 174 else 175 maxt = t.tabstop; 176 } 177 t.frame.maxtab = maxt*charwidth(f, '0'); 178 # c = '0'; 179 # if(t.what==Body && t.w!=nil && t.w.isdir) 180 # c = ' '; 181 # t.frame.maxtab = Dat->Maxtab*charwidth(f, c); 182 if(t.what==Body && t.w.isdir && odx!=t.all.dx()){ 183 if(t.frame.maxlines > 0){ 184 t.reset(); 185 t.columnate(t.w.dlp, t.w.ndl); 186 t.show(0, 0, TRUE); 187 } 188 }else{ 189 t.fill(); 190 t.setselect(t.q0, t.q1); 191 } 192} 193 194Text.reshape(t : self ref Text, r : Rect) : int 195{ 196 odx : int; 197 198 if(r.dy() > 0) 199 r.max.y -= r.dy()%t.frame.font.height; 200 else 201 r.max.y = r.min.y; 202 odx = t.all.dx(); 203 t.all = r; 204 t.scrollr = r; 205 t.scrollr.max.x = r.min.x+Scrollwid; 206 t.lastsr = dat->nullrect; 207 r.min.x += Scrollwid+Scrollgap; 208 framem->frclear(t.frame, 0); 209 # t.redraw(r, t.frame.font, t.frame.b, odx); 210 t.redraw(r, t.frame.font, mainwin, odx); 211 return r.max.y; 212} 213 214Text.close(t : self ref Text) 215{ 216 t.cache = nil; 217 framem->frclear(t.frame, 1); 218 t.file.deltext(t); 219 t.file = nil; 220 t.reffont.close(); 221 if(dat->argtext == t) 222 dat->argtext = nil; 223 if(dat->typetext == t) 224 dat->typetext = nil; 225 if(dat->seltext == t) 226 dat->seltext = nil; 227 if(dat->mousetext == t) 228 dat->mousetext = nil; 229 if(dat->barttext == t) 230 dat->barttext = nil; 231} 232 233dircmp(da : ref Dirlist, db : ref Dirlist) : int 234{ 235 if (da.r < db.r) 236 return -1; 237 if (da.r > db.r) 238 return 1; 239 return 0; 240} 241 242qsort(a : array of ref Dirlist, n : int) 243{ 244 i, j : int; 245 t : ref Dirlist; 246 247 while(n > 1) { 248 i = n>>1; 249 t = a[0]; a[0] = a[i]; a[i] = t; 250 i = 0; 251 j = n; 252 for(;;) { 253 do 254 i++; 255 while(i < n && dircmp(a[i], a[0]) < 0); 256 do 257 j--; 258 while(j > 0 && dircmp(a[j], a[0]) > 0); 259 if(j < i) 260 break; 261 t = a[i]; a[i] = a[j]; a[j] = t; 262 } 263 t = a[0]; a[0] = a[j]; a[j] = t; 264 n = n-j-1; 265 if(j >= n) { 266 qsort(a, j); 267 a = a[j+1:]; 268 } else { 269 qsort(a[j+1:], n); 270 n = j; 271 } 272 } 273} 274 275Text.columnate(t : self ref Text, dlp : array of ref Dirlist, ndl : int) 276{ 277 i, j, w, colw, mint, maxt, ncol, nrow : int; 278 dl : ref Dirlist; 279 q1 : int; 280 281 if(t.file.ntext > 1) 282 return; 283 mint = charwidth(t.frame.font, '0'); 284 # go for narrower tabs if set more than 3 wide 285 t.frame.maxtab = min(dat->maxtab, TABDIR)*mint; 286 maxt = t.frame.maxtab; 287 colw = 0; 288 for(i=0; i<ndl; i++){ 289 dl = dlp[i]; 290 w = dl.wid; 291 if(maxt-w%maxt < mint) 292 w += mint; 293 if(w % maxt) 294 w += maxt-(w%maxt); 295 if(w > colw) 296 colw = w; 297 } 298 if(colw == 0) 299 ncol = 1; 300 else 301 ncol = utils->max(1, t.frame.r.dx()/colw); 302 nrow = (ndl+ncol-1)/ncol; 303 304 q1 = 0; 305 for(i=0; i<nrow; i++){ 306 for(j=i; j<ndl; j+=nrow){ 307 dl = dlp[j]; 308 t.file.insert(q1, dl.r, len dl.r); 309 q1 += len dl.r; 310 if(j+nrow >= ndl) 311 break; 312 w = dl.wid; 313 if(maxt-w%maxt < mint){ 314 t.file.insert(q1, "\t", 1); 315 q1++; 316 w += mint; 317 } 318 do{ 319 t.file.insert(q1, "\t", 1); 320 q1++; 321 w += maxt-(w%maxt); 322 }while(w < colw); 323 } 324 t.file.insert(q1, "\n", 1); 325 q1++; 326 } 327} 328 329Text.loadx(t : self ref Text, q0 : int, file : string, setqid : int) : int 330{ 331 rp : ref Astring; 332 dl : ref Dirlist; 333 dlp : array of ref Dirlist; 334 i, n, ndl : int; 335 fd : ref Sys->FD; 336 q, q1 : int; 337 d : Dir; 338 u : ref Text; 339 ok : int; 340 341 if(t.ncache!=0 || t.file.buf.nc || t.w==nil || t!=t.w.body || (t.w.isdir && t.file.name==nil)) 342 error("text.load"); 343 344 { 345 fd = sys->open(file, Sys->OREAD); 346 if(fd == nil){ 347 warning(nil, sprint("can't open %s: %r\n", file)); 348 raise "e"; 349 } 350 (ok, d) = sys->fstat(fd); 351 if(ok){ 352 warning(nil, sprint("can't fstat %s: %r\n", file)); 353 raise "e"; 354 } 355 if(d.qid.qtype & Sys->QTDIR){ 356 # this is checked in get() but it's possible the file changed underfoot 357 if(t.file.ntext > 1){ 358 warning(nil, sprint("%s is a directory; can't read with multiple windows on it\n", file)); 359 raise "e"; 360 } 361 t.w.isdir = TRUE; 362 t.w.filemenu = FALSE; 363 if(t.file.name[len t.file.name-1] != '/') 364 t.w.setname(t.file.name + "/", len t.file.name+1); 365 dlp = nil; 366 ndl = 0; 367 for(;;){ 368 (nd, dbuf) := sys->dirread(fd); 369 if(nd <= 0) 370 break; 371 for(i=0; i<nd; i++){ 372 dl = ref Dirlist; 373 dl.r = dbuf[i].name; 374 if(dbuf[i].mode & Sys->DMDIR) 375 dl.r = dl.r + "/"; 376 dl.wid = graph->strwidth(t.frame.font, dl.r); 377 ndl++; 378 odlp := dlp; 379 dlp = array[ndl] of ref Dirlist; 380 dlp[0:] = odlp[0:ndl-1]; 381 odlp = nil; 382 dlp[ndl-1] = dl; 383 } 384 } 385 qsort(dlp, ndl); 386 t.w.dlp = dlp; 387 t.w.ndl = ndl; 388 t.columnate(dlp, ndl); 389 q1 = t.file.buf.nc; 390 }else{ 391 tmp : int; 392 393 t.w.isdir = FALSE; 394 t.w.filemenu = TRUE; 395 tmp = t.file.loadx(q0, fd); 396 q1 = q0 + tmp; 397 } 398 fd = nil; 399 if(setqid){ 400 t.file.dev = d.dev; 401 t.file.mtime = d.mtime; 402 t.file.qidpath = d.qid.path; 403 } 404 rp = stralloc(BUFSIZE); 405 for(q=q0; q<q1; q+=n){ 406 n = q1-q; 407 if(n > Dat->BUFSIZE) 408 n = Dat->BUFSIZE; 409 t.file.buf.read(q, rp, 0, n); 410 if(q < t.org) 411 t.org += n; 412 else if(q <= t.org+t.frame.nchars) 413 frinsert(t.frame, rp.s, n, q-t.org); 414 if(t.frame.lastlinefull) 415 break; 416 } 417 strfree(rp); 418 rp = nil; 419 for(i=0; i<t.file.ntext; i++){ 420 u = t.file.text[i]; 421 if(u != t){ 422 if(u.org > u.file.buf.nc) # will be 0 because of reset(), but safety first 423 u.org = 0; 424 u.reshape(u.all); 425 u.backnl(u.org, 0); # go to beginning of line 426 } 427 u.setselect(q0, q0); 428 } 429 return q1-q0; 430 } 431 exception{ 432 * => 433 fd = nil; 434 return 0; 435 } 436 return 0; 437} 438 439Text.bsinsert(t : self ref Text, q0 : int, r : string, n : int, tofile : int) : (int, int) 440{ 441 tp : ref Astring; 442 bp, up : int; 443 i, initial : int; 444 445 { 446 if(t.what == Tag) # can't happen but safety first: mustn't backspace over file name 447 raise "e"; 448 bp = 0; 449 for(i=0; i<n; i++) 450 if(r[bp++] == '\b'){ 451 --bp; 452 initial = 0; 453 tp = utils->stralloc(n); 454 for (k := 0; k < i; k++) 455 tp.s[k] = r[k]; 456 up = i; 457 for(; i<n; i++){ 458 tp.s[up] = r[bp++]; 459 if(tp.s[up] == '\b') 460 if(up == 0) 461 initial++; 462 else 463 --up; 464 else 465 up++; 466 } 467 if(initial){ 468 if(initial > q0) 469 initial = q0; 470 q0 -= initial; 471 t.delete(q0, q0+initial, tofile); 472 } 473 n = up; 474 t.insert(q0, tp.s, n, tofile, 0); 475 strfree(tp); 476 tp = nil; 477 return (q0, n); 478 } 479 raise "e"; 480 return(0, 0); 481 } 482 exception{ 483 * => 484 t.insert(q0, r, n, tofile, 0); 485 return (q0, n); 486 } 487 return (0, 0); 488} 489 490Text.insert(t : self ref Text, q0 : int, r : string, n : int, tofile : int, echomode : int) 491{ 492 c, i : int; 493 u : ref Text; 494 495 if(tofile && t.ncache != 0) 496 error("text.insert"); 497 if(n == 0) 498 return; 499 if(tofile){ 500 t.file.insert(q0, r, n); 501 if(t.what == Body){ 502 t.w.dirty = TRUE; 503 t.w.utflastqid = -1; 504 } 505 if(t.file.ntext > 1) 506 for(i=0; i<t.file.ntext; i++){ 507 u = t.file.text[i]; 508 if(u != t){ 509 u.w.dirty = TRUE; # always a body 510 u.insert(q0, r, n, FALSE, echomode); 511 u.setselect(u.q0, u.q1); 512 scrdraw(u); 513 } 514 } 515 } 516 if(q0 < t.q1) 517 t.q1 += n; 518 if(q0 < t.q0) 519 t.q0 += n; 520 if(q0 < t.org) 521 t.org += n; 522 else if(q0 <= t.org+t.frame.nchars) { 523 if (echomode == EM_MASK && len r == 1 && r[0] != '\n') 524 frinsert(t.frame, "*", n, q0-t.org); 525 else 526 frinsert(t.frame, r, n, q0-t.org); 527 } 528 if(t.w != nil){ 529 c = 'i'; 530 if(t.what == Body) 531 c = 'I'; 532 if(n <= Dat->EVENTSIZE) 533 t.w.event(sprint("%c%d %d 0 %d %s\n", c, q0, q0+n, n, r[0:n])); 534 else 535 t.w.event(sprint("%c%d %d 0 0 \n", c, q0, q0+n)); 536 } 537} 538 539Text.fill(t : self ref Text) 540{ 541 rp : ref Astring; 542 i, n, m, nl : int; 543 544 if(t.frame.lastlinefull || t.nofill) 545 return; 546 if(t.ncache > 0){ 547 if(t.w != nil) 548 t.w.commit(t); 549 else 550 t.commit(TRUE); 551 } 552 rp = stralloc(BUFSIZE); 553 do{ 554 n = t.file.buf.nc-(t.org+t.frame.nchars); 555 if(n == 0) 556 break; 557 if(n > 2000) # educated guess at reasonable amount 558 n = 2000; 559 t.file.buf.read(t.org+t.frame.nchars, rp, 0, n); 560 # 561 # it's expensive to frinsert more than we need, so 562 # count newlines. 563 # 564 565 nl = t.frame.maxlines-t.frame.nlines; 566 m = 0; 567 for(i=0; i<n; ){ 568 if(rp.s[i++] == '\n'){ 569 m++; 570 if(m >= nl) 571 break; 572 } 573 } 574 frinsert(t.frame, rp.s, i, t.frame.nchars); 575 }while(t.frame.lastlinefull == FALSE); 576 strfree(rp); 577 rp = nil; 578} 579 580Text.delete(t : self ref Text, q0 : int, q1 : int, tofile : int) 581{ 582 n, p0, p1 : int; 583 i, c : int; 584 u : ref Text; 585 586 if(tofile && t.ncache != 0) 587 error("text.delete"); 588 n = q1-q0; 589 if(n == 0) 590 return; 591 if(tofile){ 592 t.file.delete(q0, q1); 593 if(t.what == Body){ 594 t.w.dirty = TRUE; 595 t.w.utflastqid = -1; 596 } 597 if(t.file.ntext > 1) 598 for(i=0; i<t.file.ntext; i++){ 599 u = t.file.text[i]; 600 if(u != t){ 601 u.w.dirty = TRUE; # always a body 602 u.delete(q0, q1, FALSE); 603 u.setselect(u.q0, u.q1); 604 scrdraw(u); 605 } 606 } 607 } 608 if(q0 < t.q0) 609 t.q0 -= min(n, t.q0-q0); 610 if(q0 < t.q1) 611 t.q1 -= min(n, t.q1-q0); 612 if(q1 <= t.org) 613 t.org -= n; 614 else if(q0 < t.org+t.frame.nchars){ 615 p1 = q1 - t.org; 616 if(p1 > t.frame.nchars) 617 p1 = t.frame.nchars; 618 if(q0 < t.org){ 619 t.org = q0; 620 p0 = 0; 621 }else 622 p0 = q0 - t.org; 623 frdelete(t.frame, p0, p1); 624 t.fill(); 625 } 626 if(t.w != nil){ 627 c = 'd'; 628 if(t.what == Body) 629 c = 'D'; 630 t.w.event(sprint("%c%d %d 0 0 \n", c, q0, q1)); 631 } 632} 633 634onechar : ref Astring; 635 636Text.readc(t : self ref Text, q : int) : int 637{ 638 if(t.cq0<=q && q<t.cq0+t.ncache) 639 return t.cache[q-t.cq0]; 640 if (onechar == nil) 641 onechar = stralloc(1); 642 t.file.buf.read(q, onechar, 0, 1); 643 return onechar.s[0]; 644} 645 646Text.bswidth(t : self ref Text, c : int) : int 647{ 648 q, eq : int; 649 r : int; 650 skipping : int; 651 652 # there is known to be at least one character to erase 653 if(c == 16r08) # ^H: erase character 654 return 1; 655 q = t.q0; 656 skipping = TRUE; 657 while(q > 0){ 658 r = t.readc(q-1); 659 if(r == '\n'){ # eat at most one more character 660 if(q == t.q0) # eat the newline 661 --q; 662 break; 663 } 664 if(c == 16r17){ 665 eq = isalnum(r); 666 if(eq && skipping) # found one; stop skipping 667 skipping = FALSE; 668 else if(!eq && !skipping) 669 break; 670 } 671 --q; 672 } 673 return t.q0-q; 674} 675 676Text.typex(t : self ref Text, r : int, echomode : int) 677{ 678 q0, q1 : int; 679 nnb, nb, n, i : int; 680 u : ref Text; 681 682 if(alphabet != ALPHA_LATIN) 683 r = transc(r, alphabet); 684 if (echomode == EM_RAW && t.what == Body) { 685 if (t.w != nil) { 686 s := "a"; 687 s[0] = r; 688 t.w.event(sprint("R0 0 0 1 %s\n", s)); 689 } 690 return; 691 } 692 if(t.what!=Body && r=='\n') 693 return; 694 case r { 695 Dat->Kscrolldown=> 696 if(t.what == Body){ 697 q0 = t.org+frcharofpt(t.frame, (t.frame.r.min.x, t.frame.r.min.y+2*t.frame.font.height)); 698 t.setorigin(q0, FALSE); 699 } 700 return; 701 Dat->Kscrollup=> 702 if(t.what == Body){ 703 q0 = t.backnl(t.org, 4); 704 t.setorigin(q0, FALSE); 705 } 706 return; 707 Kdown or Keyboard->Down => 708 n = t.frame.maxlines/3; 709 q0 = t.org+frcharofpt(t.frame, (t.frame.r.min.x, t.frame.r.min.y+n*t.frame.font.height)); 710 t.setorigin(q0, FALSE); 711 return; 712 Keyboard->Pgdown => 713 n = 2*t.frame.maxlines/3; 714 q0 = t.org+frcharofpt(t.frame, (t.frame.r.min.x, t.frame.r.min.y+n*t.frame.font.height)); 715 t.setorigin(q0, FALSE); 716 return; 717 Kup or Keyboard->Up => 718 n = t.frame.maxlines/3; 719 q0 = t.backnl(t.org, n); 720 t.setorigin(q0, FALSE); 721 return; 722 Keyboard->Pgup => 723 n = 2*t.frame.maxlines/3; 724 q0 = t.backnl(t.org, n); 725 t.setorigin(q0, FALSE); 726 return; 727 Keyboard->Home => 728 t.commit(TRUE); 729 t.show(0, 0, FALSE); 730 return; 731 Kend or Keyboard->End => 732 t.commit(TRUE); 733 t.show(t.file.buf.nc, t.file.buf.nc, FALSE); 734 return; 735 Kleft or Keyboard->Left => 736 t.commit(TRUE); 737 if(t.q0 != t.q1) 738 t.show(t.q0, t.q0, TRUE); 739 else if(t.q0 != 0) 740 t.show(t.q0-1, t.q0-1, TRUE); 741 return; 742 Kright or Keyboard->Right => 743 t.commit(TRUE); 744 if(t.q0 != t.q1) 745 t.show(t.q1, t.q1, TRUE); 746 else if(t.q1 != t.file.buf.nc) 747 t.show(t.q1+1, t.q1+1, TRUE); 748 return; 749 1 => # ^A: beginning of line 750 t.commit(TRUE); 751 # go to where ^U would erase, if not already at BOL 752 nnb = 0; 753 if(t.q0>0 && t.readc(t.q0-1)!='\n') 754 nnb = t.bswidth(16r15); 755 t.show(t.q0-nnb, t.q0-nnb, TRUE); 756 return; 757 5 => # ^E: end of line 758 t.commit(TRUE); 759 q0 = t.q0; 760 while(q0<t.file.buf.nc && t.readc(q0)!='\n') 761 q0++; 762 t.show(q0, q0, TRUE); 763 return; 764 } 765 if(t.what == Body){ 766 seq++; 767 t.file.mark(); 768 } 769 if(t.q1 > t.q0){ 770 if(t.ncache != 0) 771 error("text.type"); 772 exec->cut(t, t, TRUE, TRUE); 773 t.eq0 = ~0; 774 if (r == 16r08 || r == 16r7f){ # erase character : odd if a char then erased 775 t.show(t.q0, t.q0,TRUE); 776 return; 777 } 778 } 779 t.show(t.q0, t.q0, TRUE); 780 case(r){ 781 16r1B => 782 if(t.eq0 != ~0) 783 t.setselect(t.eq0, t.q0); 784 if(t.ncache > 0){ 785 if(t.w != nil) 786 t.w.commit(t); 787 else 788 t.commit(TRUE); 789 } 790 return; 791 16r08 or 16r15 or 16r17 => 792 # ^H: erase character or ^U: erase line or ^W: erase word 793 if(t.q0 == 0) 794 return; 795if(0) # DEBUGGING 796 for(i=0; i<t.file.ntext; i++){ 797 u = t.file.text[i]; 798 if(u.cq0!=t.cq0 && (u.ncache!=t.ncache || t.ncache!=0)) 799 error("text.type inconsistent caches"); 800 } 801 nnb = t.bswidth(r); 802 q1 = t.q0; 803 q0 = q1-nnb; 804 for(i=0; i<t.file.ntext; i++){ 805 u = t.file.text[i]; 806 u.nofill = TRUE; 807 nb = nnb; 808 n = u.ncache; 809 if(n > 0){ 810 if(q1 != u.cq0+n) 811 error("text.type backspace"); 812 if(n > nb) 813 n = nb; 814 u.ncache -= n; 815 u.delete(q1-n, q1, FALSE); 816 nb -= n; 817 } 818 if(u.eq0==q1 || u.eq0==~0) 819 u.eq0 = q0; 820 if(nb && u==t) 821 u.delete(q0, q0+nb, TRUE); 822 if(u != t) 823 u.setselect(u.q0, u.q1); 824 else 825 t.setselect(q0, q0); 826 u.nofill = FALSE; 827 } 828 for(i=0; i<t.file.ntext; i++) 829 t.file.text[i].fill(); 830 return; 831 16r7f or Keyboard->Del => 832 # Delete character - forward delete 833 t.commit(TRUE); 834 if(t.q0 >= t.file.buf.nc) 835 return; 836 nnb = 1; 837 q0 = t.q0; 838 q1 = q0+nnb; 839 for(i=0; i<t.file.ntext; i++){ 840 u = t.file.text[i]; 841 if (u!=t) 842 u.commit(FALSE); 843 u.nofill = TRUE; 844 if(u.eq0==q1 || u.eq0==~0) 845 u.eq0 = q0; 846 if(u==t) 847 u.delete(q0, q1, TRUE); 848 if(u != t) 849 u.setselect(u.q0, u.q1); 850 else 851 t.setselect(q0, q0); 852 u.nofill = FALSE; 853 } 854 for(i=0; i<t.file.ntext; i++) 855 t.file.text[i].fill(); 856 return; 857 } 858 # otherwise ordinary character; just insert, typically in caches of all texts 859if(0) # DEBUGGING 860 for(i=0; i<t.file.ntext; i++){ 861 u = t.file.text[i]; 862 if(u.cq0!=t.cq0 && (u.ncache!=t.ncache || t.ncache!=0)) 863 error("text.type inconsistent caches"); 864 } 865 for(i=0; i<t.file.ntext; i++){ 866 u = t.file.text[i]; 867 if(u.eq0 == ~0) 868 u.eq0 = t.q0; 869 if(u.ncache == 0) 870 u.cq0 = t.q0; 871 else if(t.q0 != u.cq0+u.ncache) 872 error("text.type cq1"); 873 str := "Z"; 874 str[0] = r; 875 u.insert(t.q0, str, 1, FALSE, echomode); 876 str = nil; 877 if(u != t) 878 u.setselect(u.q0, u.q1); 879 if(u.ncache == u.ncachealloc){ 880 u.ncachealloc += 10; 881 u.cache += "1234567890"; 882 } 883 u.cache[u.ncache++] = r; 884 } 885 t.setselect(t.q0+1, t.q0+1); 886 if(r=='\n' && t.w!=nil) 887 t.w.commit(t); 888} 889 890Text.commit(t : self ref Text, tofile : int) 891{ 892 if(t.ncache == 0) 893 return; 894 if(tofile) 895 t.file.insert(t.cq0, t.cache, t.ncache); 896 if(t.what == Body){ 897 t.w.dirty = TRUE; 898 t.w.utflastqid = -1; 899 } 900 t.ncache = 0; 901} 902 903clicktext : ref Text; 904clickmsec : int = 0; 905selecttext : ref Text; 906selectq : int = 0; 907 908# 909# called from frame library 910# 911 912framescroll(f : ref Frame, dl : int) 913{ 914 if(f != selecttext.frame) 915 error("frameselect not right frame"); 916 selecttext.framescroll(dl); 917} 918 919Text.framescroll(t : self ref Text, dl : int) 920{ 921 q0 : int; 922 923 if(dl == 0){ 924 scrl->scrsleep(100); 925 return; 926 } 927 if(dl < 0){ 928 q0 = t.backnl(t.org, -dl); 929 if(selectq > t.org+t.frame.p0) 930 t.setselect0(t.org+t.frame.p0, selectq); 931 else 932 t.setselect0(selectq, t.org+t.frame.p0); 933 }else{ 934 if(t.org+t.frame.nchars == t.file.buf.nc) 935 return; 936 q0 = t.org+frcharofpt(t.frame, (t.frame.r.min.x, t.frame.r.min.y+dl*t.frame.font.height)); 937 if(selectq > t.org+t.frame.p1) 938 t.setselect0(t.org+t.frame.p1, selectq); 939 else 940 t.setselect0(selectq, t.org+t.frame.p1); 941 } 942 t.setorigin(q0, TRUE); 943} 944 945 946Text.select(t : self ref Text, double : int) 947{ 948 q0, q1 : int; 949 b, x, y : int; 950 state : int; 951 952 selecttext = t; 953 # 954 # To have double-clicking and chording, we double-click 955 # immediately if it might make sense. 956 # 957 958 b = mouse.buttons; 959 q0 = t.q0; 960 q1 = t.q1; 961 selectq = t.org+frcharofpt(t.frame, mouse.xy); 962 if(double || (clicktext==t && mouse.msec-clickmsec<500)) 963 if(q0==q1 && selectq==q0){ 964 (q0, q1) = t.doubleclick(q0, q1); 965 t.setselect(q0, q1); 966 bflush(); 967 x = mouse.xy.x; 968 y = mouse.xy.y; 969 # stay here until something interesting happens 970 do 971 frgetmouse(); 972 while(mouse.buttons==b && utils->abs(mouse.xy.x-x)<3 && utils->abs(mouse.xy.y-y)<3); 973 mouse.xy.x = x; # in case we're calling frselect 974 mouse.xy.y = y; 975 q0 = t.q0; # may have changed 976 q1 = t.q1; 977 selectq = q0; 978 } 979 if(mouse.buttons == b){ 980 t.frame.scroll = 1; 981 frselect(t.frame, mouse); 982 # horrible botch: while asleep, may have lost selection altogether 983 if(selectq > t.file.buf.nc) 984 selectq = t.org + t.frame.p0; 985 t.frame.scroll = 0; 986 if(selectq < t.org) 987 q0 = selectq; 988 else 989 q0 = t.org + t.frame.p0; 990 if(selectq > t.org+t.frame.nchars) 991 q1 = selectq; 992 else 993 q1 = t.org+t.frame.p1; 994 } 995 if(q0 == q1){ 996 if(q0==t.q0 && (double || clicktext==t && mouse.msec-clickmsec<500)){ 997 (q0, q1) = t.doubleclick(q0, q1); 998 clicktext = nil; 999 }else{ 1000 clicktext = t; 1001 clickmsec = mouse.msec; 1002 } 1003 }else 1004 clicktext = nil; 1005 t.setselect(q0, q1); 1006 bflush(); 1007 state = 0; # undo when possible; +1 for cut, -1 for paste 1008 while(mouse.buttons){ 1009 mouse.msec = 0; 1010 b = mouse.buttons; 1011 if(b & 6){ 1012 if(state==0 && t.what==Body){ 1013 seq++; 1014 t.w.body.file.mark(); 1015 } 1016 if(b & 2){ 1017 if(state==-1 && t.what==Body){ 1018 t.w.undo(TRUE); 1019 t.setselect(q0, t.q0); 1020 state = 0; 1021 }else if(state != 1){ 1022 exec->cut(t, t, TRUE, TRUE); 1023 state = 1; 1024 } 1025 }else{ 1026 if(state==1 && t.what==Body){ 1027 t.w.undo(TRUE); 1028 t.setselect(q0, t.q1); 1029 state = 0; 1030 }else if(state != -1){ 1031 exec->paste(t, t, TRUE, FALSE); 1032 state = -1; 1033 } 1034 } 1035 scrdraw(t); 1036 utils->clearmouse(); 1037 } 1038 bflush(); 1039 while(mouse.buttons == b) 1040 frgetmouse(); 1041 clicktext = nil; 1042 } 1043} 1044 1045Text.show(t : self ref Text, q0 : int, q1 : int, doselect : int) 1046{ 1047 qe : int; 1048 nl : int; 1049 q : int; 1050 1051 if(t.what != Body){ 1052 if(doselect) 1053 t.setselect(q0, q1); 1054 return; 1055 } 1056 if(t.w!=nil && t.frame.maxlines==0) 1057 t.col.grow(t.w, 1, 0); 1058 if(doselect) 1059 t.setselect(q0, q1); 1060 qe = t.org+t.frame.nchars; 1061 if(t.org<=q0 && (q0<qe || (q0==qe && qe==t.file.buf.nc+t.ncache))) 1062 scrdraw(t); 1063 else{ 1064 if(t.w.nopen[Dat->QWevent]>byte 0) 1065 nl = 3*t.frame.maxlines/4; 1066 else 1067 nl = t.frame.maxlines/4; 1068 q = t.backnl(q0, nl); 1069 # avoid going backwards if trying to go forwards - long lines! 1070 if(!(q0>t.org && q<t.org)) 1071 t.setorigin(q, TRUE); 1072 while(q0 > t.org+t.frame.nchars) 1073 t.setorigin(t.org+1, FALSE); 1074 } 1075} 1076 1077region(a, b : int) : int 1078{ 1079 if(a < b) 1080 return -1; 1081 if(a == b) 1082 return 0; 1083 return 1; 1084} 1085 1086selrestore(f : ref Frame, pt0 : Point, p0 : int, p1 : int) 1087{ 1088 if(p1<=f.p0 || p0>=f.p1){ 1089 # no overlap 1090 frdrawsel0(f, pt0, p0, p1, f.cols[BACK], f.cols[TEXT]); 1091 return; 1092 } 1093 if(p0>=f.p0 && p1<=f.p1){ 1094 # entirely inside 1095 frdrawsel0(f, pt0, p0, p1, f.cols[HIGH], f.cols[HTEXT]); 1096 return; 1097 } 1098 # they now are known to overlap 1099 # before selection 1100 if(p0 < f.p0){ 1101 frdrawsel0(f, pt0, p0, f.p0, f.cols[BACK], f.cols[TEXT]); 1102 p0 = f.p0; 1103 pt0 = frptofchar(f, p0); 1104 } 1105 # after selection 1106 if(p1 > f.p1){ 1107 frdrawsel0(f, frptofchar(f, f.p1), f.p1, p1, f.cols[BACK], f.cols[TEXT]); 1108 p1 = f.p1; 1109 } 1110 # inside selection 1111 frdrawsel0(f, pt0, p0, p1, f.cols[HIGH], f.cols[HTEXT]); 1112} 1113 1114Text.setselect(t : self ref Text, q0 : int, q1 : int) 1115{ 1116 p0, p1 : int; 1117 1118 # t.p0 and t.p1 are always right; t.q0 and t.q1 may be off 1119 t.q0 = q0; 1120 t.q1 = q1; 1121 # compute desired p0,p1 from q0,q1 1122 p0 = q0-t.org; 1123 p1 = q1-t.org; 1124 if(p0 < 0) 1125 p0 = 0; 1126 if(p1 < 0) 1127 p1 = 0; 1128 if(p0 > t.frame.nchars) 1129 p0 = t.frame.nchars; 1130 if(p1 > t.frame.nchars) 1131 p1 = t.frame.nchars; 1132 if(p0==t.frame.p0 && p1==t.frame.p1) 1133 return; 1134 # screen disagrees with desired selection 1135 if(t.frame.p1<=p0 || p1<=t.frame.p0 || p0==p1 || t.frame.p1==t.frame.p0){ 1136 # no overlap or too easy to bother trying 1137 frdrawsel(t.frame, frptofchar(t.frame, t.frame.p0), t.frame.p0, t.frame.p1, 0); 1138 frdrawsel(t.frame, frptofchar(t.frame, p0), p0, p1, 1); 1139 t.frame.p0 = p0; 1140 t.frame.p1 = p1; 1141 return; 1142 } 1143 # overlap; avoid unnecessary painting 1144 if(p0 < t.frame.p0){ 1145 # extend selection backwards 1146 frdrawsel(t.frame, frptofchar(t.frame, p0), p0, t.frame.p0, 1); 1147 }else if(p0 > t.frame.p0){ 1148 # trim first part of selection 1149 frdrawsel(t.frame, frptofchar(t.frame, t.frame.p0), t.frame.p0, p0, 0); 1150 } 1151 if(p1 > t.frame.p1){ 1152 # extend selection forwards 1153 frdrawsel(t.frame, frptofchar(t.frame, t.frame.p1), t.frame.p1, p1, 1); 1154 }else if(p1 < t.frame.p1){ 1155 # trim last part of selection 1156 frdrawsel(t.frame, frptofchar(t.frame, p1), p1, t.frame.p1, 0); 1157 } 1158 t.frame.p0 = p0; 1159 t.frame.p1 = p1; 1160} 1161 1162Text.setselect0(t : self ref Text, q0 : int, q1 : int) 1163{ 1164 t.q0 = q0; 1165 t.q1 = q1; 1166} 1167 1168xselect(f : ref Frame, mc : ref Draw->Pointer, col, colt : ref Image) : (int, int) 1169{ 1170 p0, p1, q, tmp : int; 1171 mp, pt0, pt1, qt : Point; 1172 reg, b : int; 1173 1174 # when called button 1 is down 1175 mp = mc.xy; 1176 b = mc.buttons; 1177 1178 # remove tick 1179 if(f.p0 == f.p1) 1180 frtick(f, frptofchar(f, f.p0), 0); 1181 p0 = p1 = frcharofpt(f, mp); 1182 pt0 = frptofchar(f, p0); 1183 pt1 = frptofchar(f, p1); 1184 reg = 0; 1185 frtick(f, pt0, 1); 1186 do{ 1187 q = frcharofpt(f, mc.xy); 1188 if(p1 != q){ 1189 if(p0 == p1) 1190 frtick(f, pt0, 0); 1191 if(reg != region(q, p0)){ # crossed starting point; reset 1192 if(reg > 0) 1193 selrestore(f, pt0, p0, p1); 1194 else if(reg < 0) 1195 selrestore(f, pt1, p1, p0); 1196 p1 = p0; 1197 pt1 = pt0; 1198 reg = region(q, p0); 1199 if(reg == 0) 1200 frdrawsel0(f, pt0, p0, p1, col, colt); 1201 } 1202 qt = frptofchar(f, q); 1203 if(reg > 0){ 1204 if(q > p1) 1205 frdrawsel0(f, pt1, p1, q, col, colt); 1206 else if(q < p1) 1207 selrestore(f, qt, q, p1); 1208 }else if(reg < 0){ 1209 if(q > p1) 1210 selrestore(f, pt1, p1, q); 1211 else 1212 frdrawsel0(f, qt, q, p1, col, colt); 1213 } 1214 p1 = q; 1215 pt1 = qt; 1216 } 1217 if(p0 == p1) 1218 frtick(f, pt0, 1); 1219 bflush(); 1220 frgetmouse(); 1221 }while(mc.buttons == b); 1222 if(p1 < p0){ 1223 tmp = p0; 1224 p0 = p1; 1225 p1 = tmp; 1226 } 1227 pt0 = frptofchar(f, p0); 1228 if(p0 == p1) 1229 frtick(f, pt0, 0); 1230 selrestore(f, pt0, p0, p1); 1231 # restore tick 1232 if(f.p0 == f.p1) 1233 frtick(f, frptofchar(f, f.p0), 1); 1234 bflush(); 1235 return (p0, p1); 1236} 1237 1238Text.select23(t : self ref Text, q0 : int, q1 : int, high, low : ref Image, mask : int) : (int, int, int) 1239{ 1240 p0, p1 : int; 1241 buts : int; 1242 1243 (p0, p1) = xselect(t.frame, mouse, high, low); 1244 buts = mouse.buttons; 1245 if((buts & mask) == 0){ 1246 q0 = p0+t.org; 1247 q1 = p1+t.org; 1248 } 1249 while(mouse.buttons) 1250 frgetmouse(); 1251 return (buts, q0, q1); 1252} 1253 1254Text.select2(t : self ref Text, q0 : int, q1 : int) : (int, ref Text, int, int) 1255{ 1256 buts : int; 1257 1258 (buts, q0, q1) = t.select23(q0, q1, acme->but2col, acme->but2colt, 4); 1259 if(buts & 4) 1260 return (0, nil, q0, q1); 1261 if(buts & 1) # pick up argument 1262 return (1, dat->argtext, q0, q1); 1263 return (1, nil, q0, q1); 1264} 1265 1266Text.select3(t : self ref Text, q0 : int, q1 : int) : (int, int, int) 1267{ 1268 buts : int; 1269 1270 (buts, q0, q1) = t.select23(q0, q1, acme->but3col, acme->but3colt, 1|2); 1271 return (buts == 0, q0, q1); 1272} 1273 1274left := array[4] of { 1275 "{[(<«", 1276 "\n", 1277 "'\"`", 1278 nil 1279}; 1280right := array[4] of { 1281 "}])>»", 1282 "\n", 1283 "'\"`", 1284 nil 1285}; 1286 1287Text.doubleclick(t : self ref Text, q0 : int, q1 : int) : (int, int) 1288{ 1289 c, i : int; 1290 r, l : string; 1291 p : int; 1292 q : int; 1293 res : int; 1294 1295 for(i=0; left[i]!=nil; i++){ 1296 q = q0; 1297 l = left[i]; 1298 r = right[i]; 1299 # try matching character to left, looking right 1300 if(q == 0) 1301 c = '\n'; 1302 else 1303 c = t.readc(q-1); 1304 p = utils->strchr(l, c); 1305 if(p >= 0){ 1306 (res, q) = t.clickmatch(c, r[p], 1, q); 1307 if (res) 1308 q1 = q-(c!='\n'); 1309 return (q0, q1); 1310 } 1311 # try matching character to right, looking left 1312 if(q == t.file.buf.nc) 1313 c = '\n'; 1314 else 1315 c = t.readc(q); 1316 p = utils->strchr(r, c); 1317 if(p >= 0){ 1318 (res, q) = t.clickmatch(c, l[p], -1, q); 1319 if (res){ 1320 q1 = q0+(q0<t.file.buf.nc && c=='\n'); 1321 q0 = q; 1322 if(c!='\n' || q!=0 || t.readc(0)=='\n') 1323 q0++; 1324 } 1325 return (q0, q1); 1326 } 1327 } 1328 # try filling out word to right 1329 while(q1<t.file.buf.nc && isalnum(t.readc(q1))) 1330 q1++; 1331 # try filling out word to left 1332 while(q0>0 && isalnum(t.readc(q0-1))) 1333 q0--; 1334 return (q0, q1); 1335} 1336 1337Text.clickmatch(t : self ref Text, cl : int, cr : int, dir : int, q : int) : (int, int) 1338{ 1339 c : int; 1340 nest : int; 1341 1342 nest = 1; 1343 for(;;){ 1344 if(dir > 0){ 1345 if(q == t.file.buf.nc) 1346 break; 1347 c = t.readc(q); 1348 q++; 1349 }else{ 1350 if(q == 0) 1351 break; 1352 q--; 1353 c = t.readc(q); 1354 } 1355 if(c == cr){ 1356 if(--nest==0) 1357 return (1, q); 1358 }else if(c == cl) 1359 nest++; 1360 } 1361 return (cl=='\n' && nest==1, q); 1362} 1363 1364Text.forwnl(t : self ref Text, p : int, n : int) : int 1365{ 1366 i, j : int; 1367 1368 e := t.file.buf.nc-1; 1369 i = n; 1370 while(i-- > 0 && p<e){ 1371 ++p; 1372 if(p == e) 1373 break; 1374 for(j=128; --j>0 && p<e; p++) 1375 if(t.readc(p)=='\n') 1376 break; 1377 } 1378 return p; 1379} 1380 1381Text.backnl(t : self ref Text, p : int, n : int) : int 1382{ 1383 i, j : int; 1384 1385 # look for start of this line if n==0 1386 if(n==0 && p>0 && t.readc(p-1)!='\n') 1387 n = 1; 1388 i = n; 1389 while(i-- > 0 && p>0){ 1390 --p; # it's at a newline now; back over it 1391 if(p == 0) 1392 break; 1393 # at 128 chars, call it a line anyway 1394 for(j=128; --j>0 && p>0; p--) 1395 if(t.readc(p-1)=='\n') 1396 break; 1397 } 1398 return p; 1399} 1400 1401Text.setorigin(t : self ref Text, org : int, exact : int) 1402{ 1403 i, a : int; 1404 r : ref Astring; 1405 n : int; 1406 1407 t.frame.b.flush(Flushoff); 1408 if(org>0 && !exact){ 1409 # org is an estimate of the char posn; find a newline 1410 # don't try harder than 256 chars 1411 for(i=0; i<256 && org<t.file.buf.nc; i++){ 1412 if(t.readc(org) == '\n'){ 1413 org++; 1414 break; 1415 } 1416 org++; 1417 } 1418 } 1419 a = org-t.org; 1420 fixup := 0; 1421 if(a>=0 && a<t.frame.nchars){ 1422 frdelete(t.frame, 0, a); 1423 fixup = 1; # frdelete can leave end of last line in wrong selection mode; it doesn't know what follows 1424 } 1425 else if(a<0 && -a<t.frame.nchars){ 1426 n = t.org - org; 1427 r = utils->stralloc(n); 1428 t.file.buf.read(org, r, 0, n); 1429 frinsert(t.frame, r.s, n, 0); 1430 utils->strfree(r); 1431 r = nil; 1432 }else 1433 frdelete(t.frame, 0, t.frame.nchars); 1434 t.org = org; 1435 t.fill(); 1436 scrdraw(t); 1437 t.setselect(t.q0, t.q1); 1438 if(fixup && t.frame.p1 > t.frame.p0) 1439 frdrawsel(t.frame, frptofchar(t.frame, t.frame.p1-1), t.frame.p1-1, t.frame.p1, 1); 1440 t.frame.b.flush(Flushon); 1441} 1442 1443Text.reset(t : self ref Text) 1444{ 1445 t.file.seq = 0; 1446 t.eq0 = ~0; 1447 # do t.delete(0, t.nc, TRUE) without building backup stuff 1448 t.setselect(t.org, t.org); 1449 frdelete(t.frame, 0, t.frame.nchars); 1450 t.org = 0; 1451 t.q0 = 0; 1452 t.q1 = 0; 1453 t.file.reset(); 1454 t.file.buf.reset(); 1455} 1456