9fans.net/go@v0.0.7/cmd/samterm/main.go (about) 1 package main 2 3 import ( 4 "image" 5 "os" 6 "os/signal" 7 "strings" 8 9 "9fans.net/go/draw" 10 ) 11 12 var ( 13 cmd Text 14 cursor *draw.Cursor 15 which *Flayer 16 work *Flayer 17 snarflen int 18 typestart int = -1 19 typeend int = -1 20 typeesc int = -1 21 modified bool /* strange lookahead for menus */ 22 hostlock int = 1 23 hasunlocked bool 24 maxtab int = 8 25 chord int 26 autoindent bool 27 display *draw.Display 28 screen *draw.Image 29 font *draw.Font 30 textID int64 31 textByID map[int64]*Text 32 ) 33 34 const chording = false /* code here for reference but it causes deadlocks */ 35 36 func main() { 37 /* 38 * sam is talking to us on fd 0 and 1. 39 * move these elsewhere so that if we accidentally 40 * use 0 and 1 in other code, nothing bad happens. 41 */ 42 hostfd[0] = os.Stdin 43 hostfd[1] = os.Stdout 44 os.Stdin, _ = os.Open(os.DevNull) 45 os.Stdout = os.Stderr 46 47 // ignore interrupt signals 48 signal.Notify(make(chan os.Signal), os.Interrupt) 49 50 if protodebug { 51 print("getscreen\n") 52 } 53 getscreen() 54 if protodebug { 55 print("iconinit\n") 56 } 57 iconinit() 58 if protodebug { 59 print("initio\n") 60 } 61 initio() 62 if protodebug { 63 print("scratch\n") 64 } 65 r := screen.R 66 r.Max.Y = r.Min.Y + r.Dy()/5 67 if protodebug { 68 print("flstart\n") 69 } 70 flstart(screen.Clipr) 71 rinit(&cmd.rasp) 72 flnew(&cmd.l[0], gettext, &cmd) 73 flinit(&cmd.l[0], r, font, cmdcols[:]) 74 textID++ 75 cmd.id = textID 76 textByID = make(map[int64]*Text) 77 textByID[cmd.id] = &cmd 78 cmd.nwin = 1 79 which = &cmd.l[0] 80 cmd.tag = Untagged 81 outTs(Tversion, VERSION) 82 startnewfile(Tstartcmdfile, &cmd) 83 84 got := 0 85 if protodebug { 86 print("loop\n") 87 } 88 for ; ; got = waitforio() { 89 if hasunlocked && RESIZED() { 90 resize() 91 } 92 if got&(1<<RHost) != 0 { 93 rcv() 94 } 95 if got&(1<<RPlumb) != 0 { 96 var i int 97 for i = 0; cmd.l[i].textfn == nil; i++ { 98 } 99 current(&cmd.l[i]) 100 flsetselect(which, cmd.rasp.nrunes, cmd.rasp.nrunes) 101 ktype(which, RPlumb) 102 } 103 if got&(1<<RKeyboard) != 0 { 104 if which != nil { 105 ktype(which, RKeyboard) 106 } else { 107 kbdblock() 108 } 109 } 110 if got&(1<<RMouse) != 0 { 111 if hostlock == 2 || !mousep.Point.In(screen.R) { 112 mouseunblock() 113 continue 114 } 115 nwhich := flwhich(mousep.Point) 116 scr := which != nil && mousep.Point.In(which.scroll) 117 if mousep.Buttons != 0 { 118 flushtyping(true) 119 } 120 if chording && chord == 1 && mousep.Buttons == 0 { 121 chord = 0 122 } 123 if chording && chord != 0 { 124 chord |= mousep.Buttons 125 } else if mousep.Buttons&1 != 0 { 126 if nwhich != nil { 127 if nwhich != which { 128 current(nwhich) 129 } else if scr { 130 scroll(which, 1) 131 } else { 132 t := which.text 133 if flselect(which) { 134 outTsl(Tdclick, t.tag, which.p0) 135 t.lock++ 136 } else if t != &cmd { 137 outcmd() 138 } 139 if mousep.Buttons&1 != 0 { 140 chord = mousep.Buttons 141 } 142 } 143 } 144 } else if mousep.Buttons&2 != 0 && which != nil { 145 if scr { 146 scroll(which, 2) 147 } else { 148 menu2hit() 149 } 150 } else if mousep.Buttons&4 != 0 { 151 if scr { 152 scroll(which, 3) 153 } else { 154 menu3hit() 155 } 156 } 157 mouseunblock() 158 } 159 if chording && chord != 0 { 160 t := which.text 161 if t.lock == 0 && hostlock == 0 { 162 w := t.find(which) 163 if chord&2 != 0 { 164 cut(t, w, true, true) 165 chord &= ^2 166 } else if chord&4 != 0 { 167 paste(t, w) 168 chord &= ^4 169 } 170 } 171 } 172 } 173 } 174 175 func (t *Text) find(l *Flayer) int { 176 w := 0 177 for &t.l[w] != l { 178 w++ 179 } 180 return w 181 } 182 183 func resize() { 184 flresize(screen.Clipr) 185 for _, t := range text { 186 if t != nil { 187 hcheck(t.tag) 188 } 189 } 190 } 191 192 func current(nw *Flayer) { 193 if which != nil { 194 flborder(which, false) 195 } 196 if nw != nil { 197 flushtyping(true) 198 flupfront(nw) 199 flborder(nw, true) 200 buttons(Up) 201 t := nw.text 202 t.front = t.find(nw) 203 if t != &cmd { 204 work = nw 205 } 206 } 207 which = nw 208 } 209 210 func closeup(l *Flayer) { 211 t := l.text 212 m := whichmenu(t.tag) 213 if m < 0 { 214 return 215 } 216 flclose(l) 217 if l == which { 218 which = nil 219 current(flwhich(image.Pt(0, 0))) 220 } 221 if l == work { 222 work = nil 223 } 224 t.nwin-- 225 if t.nwin == 0 { 226 rclear(&t.rasp) 227 delete(textByID, t.id) 228 free(t) 229 text[m] = nil 230 } else if l == &t.l[t.front] { 231 for m = 0; m < NL; m++ { /* find one; any one will do */ 232 if t.l[m].textfn != nil { 233 t.front = m 234 return 235 } 236 } 237 panic("close") 238 } 239 } 240 241 func findl(t *Text) *Flayer { 242 for i := 0; i < NL; i++ { 243 if t.l[i].textfn == nil { 244 return &t.l[i] 245 } 246 } 247 return nil 248 } 249 250 func duplicate(l *Flayer, r image.Rectangle, f *draw.Font, close bool) { 251 t := l.text 252 nl := findl(t) 253 if nl != nil { 254 flnew(nl, gettext, t) 255 flinit(nl, r, f, l.f.Cols[:]) 256 nl.origin = l.origin 257 rp := l.textfn(l, l.f.NumChars) 258 flinsert(nl, rp, l.origin) 259 flsetselect(nl, l.p0, l.p1) 260 if close { 261 flclose(l) 262 if l == which { 263 which = nil 264 } 265 } else { 266 t.nwin++ 267 } 268 current(nl) 269 hcheck(t.tag) 270 } 271 display.SwitchCursor(cursor) 272 } 273 274 func buttons(updown int) { 275 for (mousep.Buttons&7 != 0) != (updown == Down) { 276 getmouse() 277 } 278 } 279 280 func getr(rp *image.Rectangle) bool { 281 *rp = draw.SweepRect(3, mousectl) 282 if rp.Max.X != 0 && rp.Max.X-rp.Min.X <= 5 && rp.Max.Y-rp.Min.Y <= 5 { 283 p := rp.Min 284 r := cmd.l[cmd.front].entire 285 *rp = screen.R 286 if cmd.nwin == 1 { 287 if p.Y <= r.Min.Y { 288 rp.Max.Y = r.Min.Y 289 } else if p.Y >= r.Max.Y { 290 rp.Min.Y = r.Max.Y 291 } 292 if p.X <= r.Min.X { 293 rp.Max.X = r.Min.X 294 } else if p.X >= r.Max.X { 295 rp.Min.X = r.Max.X 296 } 297 } 298 } 299 return draw.RectClip(rp, screen.R) && rp.Max.X-rp.Min.X > 100 && rp.Max.Y-rp.Min.Y > 40 300 } 301 302 func snarf(t *Text, w int) { 303 l := &t.l[w] 304 if l.p1 > l.p0 { 305 snarflen = l.p1 - l.p0 306 outTsll(Tsnarf, t.tag, l.p0, l.p1) 307 } 308 } 309 310 func cut(t *Text, w int, save bool, check bool) { 311 l := &t.l[w] 312 p0 := l.p0 313 p1 := l.p1 314 if p0 == p1 { 315 return 316 } 317 if p0 < 0 { 318 panic("cut") 319 } 320 if save { 321 snarf(t, w) 322 } 323 outTsll(Tcut, t.tag, p0, p1) 324 flsetselect(l, p0, p0) 325 t.lock++ 326 hcut(t.tag, p0, p1-p0) 327 if check { 328 hcheck(t.tag) 329 } 330 } 331 332 func paste(t *Text, w int) { 333 if snarflen != 0 { 334 cut(t, w, false, false) 335 t.lock++ 336 outTsl(Tpaste, t.tag, t.l[w].p0) 337 } 338 } 339 340 func scrorigin(l *Flayer, but int, p0 int) { 341 t := l.text 342 switch but { 343 case 1: 344 outTsll(Torigin, t.tag, l.origin, p0) 345 case 2: 346 outTsll(Torigin, t.tag, p0, 1) 347 case 3: 348 horigin(t.tag, p0) 349 } 350 } 351 352 func alnum(c rune) bool { 353 /* 354 * Hard to get absolutely right. Use what we know about ASCII 355 * and assume anything above the Latin control characters is 356 * potentially an alphanumeric. 357 */ 358 if c <= ' ' { 359 return false 360 } 361 if 0x7F <= c && c <= 0xA0 { 362 return false 363 } 364 if strings.ContainsRune("!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~", c) { 365 return false 366 } 367 return true 368 } 369 370 func raspc(r *Rasp, p int) rune { 371 return rload(r, p, p+1)[0] 372 } 373 374 func ctlw(r *Rasp, o int, p int) int { 375 p-- 376 if p < o { 377 return o 378 } 379 if raspc(r, p) == '\n' { 380 return p 381 } 382 for ; p >= o; p-- { 383 c := raspc(r, p) 384 if alnum(c) { 385 break 386 } 387 if c == '\n' { 388 return p + 1 389 } 390 } 391 for ; p > o && alnum(raspc(r, p-1)); p-- { 392 } 393 if p >= o { 394 return p 395 } 396 return o 397 } 398 399 func ctlu(r *Rasp, o int, p int) int { 400 p-- 401 if p < o { 402 return o 403 } 404 if raspc(r, p) == '\n' { 405 return p 406 } 407 for ; p-1 >= o && raspc(r, p-1) != '\n'; p-- { 408 } 409 if p >= o { 410 return p 411 } 412 return o 413 } 414 415 func center(l *Flayer, a int) bool { 416 t := l.text 417 if t.lock == 0 && (a < l.origin || l.origin+l.f.NumChars < a) { 418 if a > t.rasp.nrunes { 419 a = t.rasp.nrunes 420 } 421 outTsll(Torigin, t.tag, a, 2) 422 return true 423 } 424 return false 425 } 426 427 func thirds(l *Flayer, a int, n int) bool { 428 t := l.text 429 if t.lock == 0 && (a < l.origin || l.origin+l.f.NumChars < a) { 430 if a > t.rasp.nrunes { 431 a = t.rasp.nrunes 432 } 433 s := l.scroll.Inset(1) 434 lines := (n*(s.Max.Y-s.Min.Y)/l.f.Font.Height + 1) / 3 435 if lines < 2 { 436 lines = 2 437 } 438 outTsll(Torigin, t.tag, a, lines) 439 return true 440 } 441 return false 442 } 443 444 func onethird(l *Flayer, a int) bool { 445 return thirds(l, a, 1) 446 } 447 448 func twothirds(l *Flayer, a int) bool { 449 return thirds(l, a, 2) 450 } 451 452 func flushtyping(clearesc bool) { 453 if clearesc { 454 typeesc = -1 455 } 456 if typestart == typeend { 457 modified = false 458 return 459 } 460 t := which.text 461 if t != &cmd { 462 modified = true 463 } 464 rp := rload(&t.rasp, typestart, typeend) 465 if t == &cmd && typeend == t.rasp.nrunes && rp[len(rp)-1] == '\n' { 466 setlock() 467 outcmd() 468 } 469 outTslS(Ttype, t.tag, typestart, rp) 470 typestart = -1 471 typeend = -1 472 } 473 474 const ( 475 BACKSCROLLKEY = draw.KeyUp 476 ENDKEY = draw.KeyEnd 477 ESC = '\x1B' 478 HOMEKEY = draw.KeyHome 479 LEFTARROW = draw.KeyLeft 480 LINEEND = '\x05' 481 LINESTART = '\x01' 482 PAGEDOWN = draw.KeyPageDown 483 PAGEUP = draw.KeyPageUp 484 RIGHTARROW = draw.KeyRight 485 SCROLLKEY = draw.KeyDown 486 CUT = draw.KeyCmd + 'x' 487 COPY = draw.KeyCmd + 'c' 488 PASTE = draw.KeyCmd + 'v' 489 ) 490 491 func nontypingkey(c rune) bool { 492 switch c { 493 case BACKSCROLLKEY, 494 ENDKEY, 495 HOMEKEY, 496 LEFTARROW, 497 LINEEND, 498 LINESTART, 499 PAGEDOWN, 500 PAGEUP, 501 RIGHTARROW, 502 SCROLLKEY, 503 CUT, 504 COPY, 505 PASTE: 506 return true 507 } 508 return false 509 } 510 511 var kinput = make([]rune, 0, 100) 512 513 func ktype(l *Flayer, res Resource) { 514 t := l.text 515 scrollkey := false 516 if res == RKeyboard { 517 scrollkey = nontypingkey(qpeekc()) /* ICK */ 518 } 519 520 if hostlock != 0 || t.lock != 0 { 521 kbdblock() 522 return 523 } 524 a := l.p0 525 if a != l.p1 && !scrollkey { 526 flushtyping(true) 527 cut(t, t.front, true, true) 528 return /* it may now be locked */ 529 } 530 backspacing := 0 531 kinput = kinput[:0] 532 var c rune 533 for { 534 c = kbdchar() 535 if c <= 0 { 536 break 537 } 538 if res == RKeyboard { 539 if nontypingkey(c) || c == ESC { 540 break 541 } 542 /* backspace, ctrl-u, ctrl-w, del */ 543 if c == '\b' || c == 0x15 || c == 0x17 || c == 0x7F { 544 backspacing = 1 545 break 546 } 547 } 548 kinput = append(kinput, c) 549 if autoindent { 550 if c == '\n' { 551 cursor := ctlu(&t.rasp, 0, a+len(kinput)-1) 552 for len(kinput) < cap(kinput) { 553 ch := raspc(&t.rasp, cursor) 554 cursor++ 555 if ch == ' ' || ch == '\t' { 556 kinput = append(kinput, ch) 557 } else { 558 break 559 } 560 } 561 } 562 } 563 if c == '\n' || len(kinput) == cap(kinput) { 564 break 565 } 566 } 567 if len(kinput) > 0 { 568 if typestart < 0 { 569 typestart = a 570 } 571 if typeesc < 0 { 572 typeesc = a 573 } 574 hgrow(t.tag, a, len(kinput), 0) 575 t.lock++ /* pretend we Trequest'ed for hdatarune*/ 576 hdatarune(t.tag, a, kinput) 577 a += len(kinput) 578 l.p0 = a 579 l.p1 = a 580 typeend = a 581 if c == '\n' || typeend-typestart > 100 { 582 flushtyping(false) 583 } 584 onethird(l, a) 585 } 586 if c == SCROLLKEY || c == PAGEDOWN { 587 flushtyping(false) 588 center(l, l.origin+l.f.NumChars+1) 589 } else if c == BACKSCROLLKEY || c == PAGEUP { 590 flushtyping(false) 591 a0 := l.origin - l.f.NumChars 592 if a0 < 0 { 593 a0 = 0 594 } 595 center(l, a0) 596 } else if c == RIGHTARROW { 597 flushtyping(false) 598 a0 := l.p0 599 if a0 < t.rasp.nrunes { 600 a0++ 601 } 602 flsetselect(l, a0, a0) 603 center(l, a0) 604 } else if c == LEFTARROW { 605 flushtyping(false) 606 a0 := l.p0 607 if a0 > 0 { 608 a0-- 609 } 610 flsetselect(l, a0, a0) 611 center(l, a0) 612 } else if c == HOMEKEY { 613 flushtyping(false) 614 center(l, 0) 615 } else if c == ENDKEY { 616 flushtyping(false) 617 center(l, t.rasp.nrunes) 618 } else if c == LINESTART || c == LINEEND { 619 flushtyping(true) 620 if c == LINESTART { 621 for a > 0 && raspc(&t.rasp, a-1) != '\n' { 622 a-- 623 } 624 } else { 625 for a < t.rasp.nrunes && raspc(&t.rasp, a) != '\n' { 626 a++ 627 } 628 } 629 l.p1 = a 630 l.p0 = l.p1 631 for i := 0; i < NL; i++ { 632 l := &t.l[i] 633 if l.textfn != nil { 634 flsetselect(l, l.p0, l.p1) 635 } 636 } 637 } else if backspacing != 0 && hostlock == 0 { 638 /* backspacing immediately after outcmd(): sorry */ 639 if l.f.P0 > 0 && a > 0 { 640 switch c { 641 case '\b', 642 0x7F: /* del */ 643 l.p0 = a - 1 644 case 0x15: /* ctrl-u */ 645 l.p0 = ctlu(&t.rasp, l.origin, a) 646 case 0x17: /* ctrl-w */ 647 l.p0 = ctlw(&t.rasp, l.origin, a) 648 } 649 l.p1 = a 650 if l.p1 != l.p0 { 651 /* cut locally if possible */ 652 if typestart <= l.p0 && l.p1 <= typeend { 653 t.lock++ /* to call hcut */ 654 hcut(t.tag, l.p0, l.p1-l.p0) 655 /* hcheck is local because we know rasp is contiguous */ 656 hcheck(t.tag) 657 } else { 658 flushtyping(false) 659 cut(t, t.front, false, true) 660 } 661 } 662 if typeesc >= l.p0 { 663 typeesc = l.p0 664 } 665 if typestart >= 0 { 666 if typestart >= l.p0 { 667 typestart = l.p0 668 } 669 typeend = l.p0 670 if typestart == typeend { 671 typestart = -1 672 typeend = -1 673 modified = false 674 } 675 } 676 } 677 } else { 678 if c == ESC && typeesc >= 0 { 679 l.p0 = typeesc 680 l.p1 = a 681 flushtyping(true) 682 } 683 for i := 0; i < NL; i++ { 684 l := &t.l[i] 685 if l.textfn != nil { 686 flsetselect(l, l.p0, l.p1) 687 } 688 } 689 switch c { 690 case CUT: 691 flushtyping(false) 692 cut(t, t.front, true, true) 693 case COPY: 694 flushtyping(false) 695 snarf(t, t.front) 696 case PASTE: 697 flushtyping(false) 698 paste(t, t.front) 699 } 700 } 701 } 702 703 func outcmd() { 704 if work != nil { 705 outTsll(Tworkfile, work.text.tag, work.p0, work.p1) 706 } 707 } 708 709 func gettext(l *Flayer, n int) []rune { 710 return rload(&l.text.rasp, l.origin, l.origin+n) 711 } 712 713 func scrtotal(l *Flayer) int { 714 return l.text.rasp.nrunes 715 }