9fans.net/go@v0.0.7/cmd/acme/acme.go (about) 1 package main 2 3 import ( 4 "flag" 5 "fmt" 6 "log" 7 "os" 8 "strconv" 9 "strings" 10 "sync" 11 "time" 12 "unicode/utf8" 13 14 "9fans.net/go/cmd/acme/internal/adraw" 15 "9fans.net/go/cmd/acme/internal/alog" 16 "9fans.net/go/cmd/acme/internal/disk" 17 dumppkg "9fans.net/go/cmd/acme/internal/dump" 18 editpkg "9fans.net/go/cmd/acme/internal/edit" 19 "9fans.net/go/cmd/acme/internal/exec" 20 fileloadpkg "9fans.net/go/cmd/acme/internal/fileload" 21 "9fans.net/go/cmd/acme/internal/regx" 22 "9fans.net/go/cmd/acme/internal/runes" 23 "9fans.net/go/cmd/acme/internal/ui" 24 "9fans.net/go/cmd/acme/internal/util" 25 "9fans.net/go/cmd/acme/internal/wind" 26 "9fans.net/go/draw" 27 ) 28 29 var snarffd = -1 30 var mainpid int 31 var swapscrollbuttons bool = false 32 var mtpt string 33 34 var mainthread sync.Mutex 35 36 var command *exec.Command 37 38 func derror(d *draw.Display, errorstr string) { 39 util.Fatal(errorstr) 40 } 41 42 func main() { 43 bigLock() 44 log.SetFlags(0) 45 log.SetPrefix("acme: ") 46 47 ncol := -1 48 loadfile := "" 49 winsize := "" 50 51 flag.Bool("D", false, "") // ignored 52 flag.BoolVar(&wind.GlobalAutoindent, "a", wind.GlobalAutoindent, "autoindent") 53 flag.BoolVar(&ui.Bartflag, "b", ui.Bartflag, "bartflag") 54 flag.IntVar(&ncol, "c", ncol, "set number of `columns`") 55 flag.StringVar(&adraw.FontNames[0], "f", adraw.FontNames[0], "font") 56 flag.StringVar(&adraw.FontNames[1], "F", adraw.FontNames[1], "font") 57 flag.StringVar(&loadfile, "l", loadfile, "loadfile") 58 flag.StringVar(&mtpt, "m", mtpt, "mtpt") 59 flag.BoolVar(&swapscrollbuttons, "r", swapscrollbuttons, "swapscrollbuttons") 60 flag.StringVar(&winsize, "W", winsize, "set window `size`") 61 flag.Usage = func() { 62 fmt.Fprintf(os.Stderr, "usage: acme [options] [files...]\n") 63 os.Exit(2) 64 } 65 flag.Parse() 66 67 alog.Init(func(msg string) { warning(nil, "%s", msg) }) 68 ui.Ismtpt = ismtpt 69 fileloadpkg.Ismtpt = ismtpt 70 ui.Textload = fileloadpkg.Textload 71 dumppkg.Get = func(t *wind.Text) { 72 exec.Get(t, nil, nil, false, exec.XXX, nil) 73 } 74 dumppkg.Run = func(s string, rdir []rune) { 75 exec.Run(nil, s, rdir, true, nil, nil, false) 76 } 77 78 cputype = os.Getenv("cputype") 79 ui.Objtype = os.Getenv("objtype") 80 home = os.Getenv("HOME") 81 dumppkg.Home = home 82 exec.Acmeshell = os.Getenv("acmeshell") 83 p := os.Getenv("tabstop") 84 if p != "" { 85 wind.MaxTab, _ = strconv.Atoi(p) 86 } 87 if wind.MaxTab == 0 { 88 wind.MaxTab = 4 89 } 90 if loadfile != "" { 91 dumppkg.LoadFonts(loadfile) 92 } 93 os.Setenv("font", adraw.FontNames[0]) 94 /* 95 snarffd = syscall.Open("/dev/snarf", syscall.O_RDONLY|OCEXEC, 0) 96 if(cputype){ 97 sprint(buf, "/acme/bin/%s", cputype); 98 bind(buf, "/bin", MBEFORE); 99 } 100 bind("/acme/bin", "/bin", MBEFORE); 101 */ 102 ui.Wdir, _ = os.Getwd() 103 104 /* 105 if(geninitdraw(nil, derror, fontnames[0], "acme", nil, Refnone) < 0){ 106 fprint(2, "acme: can't open display: %r\n"); 107 threadexitsall("geninitdraw"); 108 } 109 */ 110 ch := make(chan error) 111 d, err := draw.Init(ch, adraw.FontNames[0], "acme", winsize) 112 if err != nil { 113 log.Fatal(err) 114 } 115 go func() { 116 for err := range ch { 117 bigLock() 118 derror(d, err.Error()) 119 bigUnlock() 120 } 121 }() 122 123 adraw.Display = d 124 adraw.Font = d.Font 125 //assert(font); 126 127 adraw.RefFont1.F = adraw.Font 128 adraw.RefFonts[0] = &adraw.RefFont1 129 util.Incref(&adraw.RefFont1.Ref) // one to hold up 'font' variable 130 util.Incref(&adraw.RefFont1.Ref) // one to hold up reffonts[0] 131 adraw.FontCache = make([]*adraw.RefFont, 1) 132 adraw.FontCache[0] = &adraw.RefFont1 133 134 adraw.Init() 135 // TODO timerinit() 136 regx.Init() 137 138 wind.OnWinclose = func(w *wind.Window) { 139 xfidlog(w, "del") 140 } 141 ui.OnNewWindow = func(w *wind.Window) { 142 xfidlog(w, "new") 143 } 144 dumppkg.OnNewWindow = ui.OnNewWindow 145 146 ui.Textcomplete = fileloadpkg.Textcomplete 147 editpkg.Putfile = exec.Putfile 148 editpkg.BigLock = bigLock 149 editpkg.BigUnlock = bigUnlock 150 editpkg.Run = func(w *wind.Window, s string, rdir []rune) { 151 exec.Run(w, s, rdir, true, nil, nil, true) 152 } 153 ui.BigLock = bigLock 154 ui.BigUnlock = bigUnlock 155 exec.Fsysmount = fsysmount 156 exec.Fsysdelid = fsysdelid 157 exec.Xfidlog = xfidlog 158 159 ui.Mousectl = adraw.Display.InitMouse() 160 if ui.Mousectl == nil { 161 log.Fatal("can't initialize mouse") 162 } 163 ui.Mouse = &ui.Mousectl.Mouse 164 keyboardctl = adraw.Display.InitKeyboard() 165 if keyboardctl == nil { 166 log.Fatal("can't initialize keyboard") 167 } 168 mainpid = os.Getpid() 169 startplumbing() 170 171 fsysinit() 172 173 const WPERCOL = 8 174 disk.Init() 175 if loadfile == "" || !dumppkg.Load(&wind.TheRow, &loadfile, true) { 176 wind.RowInit(&wind.TheRow, adraw.Display.ScreenImage.Clipr) 177 argc := flag.NArg() 178 argv := flag.Args() 179 if ncol < 0 { 180 if argc == 0 { 181 ncol = 2 182 } else { 183 ncol = (argc + (WPERCOL - 1)) / WPERCOL 184 if ncol < 2 { 185 ncol = 2 186 } 187 } 188 } 189 if ncol == 0 { 190 ncol = 2 191 } 192 var c *wind.Column 193 var i int 194 for i = 0; i < ncol; i++ { 195 c = wind.RowAdd(&wind.TheRow, nil, -1) 196 if c == nil && i == 0 { 197 util.Fatal("initializing columns") 198 } 199 } 200 c = wind.TheRow.Col[len(wind.TheRow.Col)-1] 201 if argc == 0 { 202 readfile(c, ui.Wdir) 203 } else { 204 for i = 0; i < argc; i++ { 205 j := strings.LastIndex(argv[i], "/") 206 if j >= 0 && argv[i][j:] == "/guide" || i/WPERCOL >= len(wind.TheRow.Col) { 207 readfile(c, argv[i]) 208 } else { 209 readfile(wind.TheRow.Col[i/WPERCOL], argv[i]) 210 } 211 } 212 } 213 } 214 adraw.Display.Flush() 215 216 acmeerrorinit() 217 go keyboardthread() 218 go mousethread() 219 go waitthread() 220 go xfidallocthread() 221 go newwindowthread() 222 // threadnotify(shutdown, 1) 223 bigUnlock() 224 <-exec.Cexit 225 bigLock() 226 killprocs() 227 os.Exit(0) 228 } 229 230 func readfile(c *wind.Column, s string) { 231 w := ui.ColaddAndMouse(c, nil, nil, -1) 232 var rb []rune 233 if !strings.HasPrefix(s, "/") { 234 rb = []rune(ui.Wdir + "/" + s) 235 } else { 236 rb = []rune(s) 237 } 238 rs := runes.CleanPath(rb) 239 wind.Winsetname(w, rs) 240 fileloadpkg.Textload(&w.Body, 0, s, true) 241 w.Body.File.SetMod(false) 242 w.Dirty = false 243 wind.Winsettag(w) 244 ui.WinresizeAndMouse(w, w.R, false, true) 245 wind.Textscrdraw(&w.Body) 246 wind.Textsetselect(&w.Tag, w.Tag.Len(), w.Tag.Len()) 247 xfidlog(w, "new") 248 } 249 250 var ignotes = []string{ 251 "sys: write on closed pipe", 252 "sys: ttin", 253 "sys: ttou", 254 "sys: tstp", 255 } 256 257 var oknotes = []string{ 258 "delete", 259 "hangup", 260 "kill", 261 "exit", 262 } 263 264 var dumping bool 265 266 func shutdown(v *[0]byte, msg string) bool { 267 for _, ig := range ignotes { 268 if strings.HasPrefix(msg, ig) { 269 return true 270 } 271 } 272 273 killprocs() 274 if !dumping && msg != "kill" && msg != "exit" { 275 dumping = true 276 dumppkg.Dump(&wind.TheRow, nil) 277 } 278 for _, ok := range oknotes { 279 if strings.HasPrefix(msg, ok) { 280 os.Exit(0) 281 } 282 } 283 print("acme: %s\n", msg) 284 return false 285 } 286 287 /* 288 void 289 shutdownthread(void *v) 290 { 291 char *msg; 292 Channel *c; 293 294 USED(v); 295 296 threadsetname("shutdown"); 297 c = threadnotechan(); 298 while((msg = recvp(c)) != nil) 299 shutdown(nil, msg); 300 } 301 */ 302 303 func killprocs() { 304 fsysclose() 305 // if(display) 306 // flushimage(display, 1); 307 308 for c := command; c != nil; c = c.Next { 309 // TODO postnote(PNGROUP, c.pid, "hangup") 310 _ = c 311 } 312 } 313 314 var errorfd *os.File 315 var erroutfd *os.File 316 317 func acmeerrorproc() { 318 buf := make([]byte, 8192) 319 for { 320 n, err := errorfd.Read(buf) 321 if err != nil { 322 break 323 } 324 s := make([]byte, n) 325 copy(s, buf) 326 cerr <- s 327 } 328 } 329 330 func acmeerrorinit() { 331 r, w, err := os.Pipe() 332 if err != nil { 333 log.Fatal(err) 334 } 335 errorfd = r 336 erroutfd = w 337 go acmeerrorproc() 338 } 339 340 /* 341 void 342 plumbproc(void *v) 343 { 344 Plumbmsg *m; 345 346 USED(v); 347 threadsetname("plumbproc"); 348 for(;;){ 349 m = threadplumbrecv(plumbeditfd); 350 if(m == nil) 351 threadexits(nil); 352 sendp(cplumb, m); 353 } 354 } 355 */ 356 357 func keyboardthread() { 358 bigLock() 359 defer bigUnlock() 360 361 var timerc <-chan time.Time 362 var r rune 363 var timer *time.Timer 364 wind.Typetext = nil 365 for { 366 var t *wind.Text 367 bigUnlock() 368 select { 369 case <-timerc: 370 bigLock() 371 timer = nil 372 timerc = nil 373 t = wind.Typetext 374 if t != nil && t.What == wind.Tag { 375 wind.Winlock(t.W, 'K') 376 wind.Wincommit(t.W, t) 377 wind.Winunlock(t.W) 378 adraw.Display.Flush() 379 } 380 381 case r = <-keyboardctl.C: 382 bigLock() 383 Loop: 384 wind.Typetext = ui.Rowtype(&wind.TheRow, r, ui.Mouse.Point) 385 t = wind.Typetext 386 if t != nil && t.Col != nil && (!(r == draw.KeyDown || r == draw.KeyLeft) && !(r == draw.KeyRight)) { // scrolling doesn't change activecol 387 wind.Activecol = t.Col 388 } 389 if t != nil && t.W != nil { 390 t.W.Body.File.Curtext = &t.W.Body 391 } 392 if timer != nil { 393 timer.Stop() 394 timer = nil 395 } 396 if t != nil && t.What == wind.Tag { 397 timer = time.NewTimer(500 * time.Millisecond) 398 timerc = timer.C 399 } else { 400 timer = nil 401 timerc = nil 402 } 403 select { 404 default: 405 // non-blocking 406 case r = <-keyboardctl.C: 407 goto Loop 408 } 409 adraw.Display.Flush() 410 } 411 } 412 } 413 414 func mousethread() { 415 bigLock() 416 defer bigUnlock() 417 418 for { 419 bigUnlock() 420 wind.TheRow.Lk.Lock() 421 bigLock() 422 flushwarnings() 423 wind.TheRow.Lk.Unlock() 424 425 adraw.Display.Flush() 426 427 bigUnlock() 428 select { 429 case <-ui.Mousectl.Resize: 430 bigLock() 431 if err := adraw.Display.Attach(draw.RefNone); err != nil { 432 util.Fatal("attach to window: " + err.Error()) 433 } 434 adraw.Display.ScreenImage.Draw(adraw.Display.ScreenImage.R, adraw.Display.White, nil, draw.ZP) 435 adraw.Init() 436 wind.Scrlresize() 437 wind.Rowresize(&wind.TheRow, adraw.Display.ScreenImage.Clipr) 438 ui.Clearmouse() 439 440 case pm := <-cplumb: 441 bigLock() 442 if pm.Type == "text" { 443 act := pm.LookupAttr("action") 444 if act == "" || act == "showfile" { 445 plumblook(pm) 446 } else if act == "showdata" { 447 plumbshow(pm) 448 } 449 } 450 451 case <-cwarn: 452 bigLock() 453 // ok 454 455 /* 456 * Make a copy so decisions are consistent; mousectl changes 457 * underfoot. Can't just receive into m because this introduces 458 * another race; see /sys/src/libdraw/mouse.c. 459 */ 460 case m := <-ui.Mousectl.C: 461 wind.TheRow.Lk.Lock() 462 bigLock() 463 ui.Mousectl.Mouse = m 464 t := wind.Rowwhich(&wind.TheRow, m.Point) 465 466 if (t != wind.Mousetext && t != nil && t.W != nil) && (wind.Mousetext == nil || wind.Mousetext.W == nil || t.W.ID != wind.Mousetext.W.ID) { 467 xfidlog(t.W, "focus") 468 } 469 470 if t != wind.Mousetext && wind.Mousetext != nil && wind.Mousetext.W != nil { 471 wind.Winlock(wind.Mousetext.W, 'M') 472 wind.Mousetext.Eq0 = ^0 473 wind.Wincommit(wind.Mousetext.W, wind.Mousetext) 474 wind.Winunlock(wind.Mousetext.W) 475 } 476 wind.Mousetext = t 477 var but int 478 var w *wind.Window 479 if t == nil { 480 goto Continue 481 } 482 w = t.W 483 if t == nil || m.Buttons == 0 { // TODO(rsc): just checked t above 484 goto Continue 485 } 486 but = 0 487 if m.Buttons == 1 { 488 but = 1 489 } else if m.Buttons == 2 { 490 but = 2 491 } else if m.Buttons == 4 { 492 but = 3 493 } 494 wind.Barttext = t 495 if t.What == wind.Body && m.Point.In(t.ScrollR) { 496 if but != 0 { 497 if swapscrollbuttons { 498 if but == 1 { 499 but = 3 500 } else if but == 3 { 501 but = 1 502 } 503 } 504 wind.Winlock(w, 'M') 505 t.Eq0 = ^0 506 ui.Textscroll(t, but) 507 wind.Winunlock(w) 508 } 509 goto Continue 510 } 511 // scroll buttons, wheels, etc. 512 if w != nil && m.Buttons&(8|16) != 0 { 513 var ch rune 514 if m.Buttons&8 != 0 { 515 ch = ui.Kscrolloneup 516 } else { 517 ch = ui.Kscrollonedown 518 } 519 wind.Winlock(w, 'M') 520 t.Eq0 = ^0 521 ui.Texttype(t, ch) 522 wind.Winunlock(w) 523 goto Continue 524 } 525 if m.Point.In(t.ScrollR) { 526 if but != 0 { 527 if t.What == wind.Columntag { 528 ui.Rowdragcol(&wind.TheRow, t.Col, but) 529 } else if t.What == wind.Tag { 530 ui.Coldragwin(t.Col, t.W, but) 531 if t.W != nil { 532 wind.Barttext = &t.W.Body 533 } 534 } 535 if t.Col != nil { 536 wind.Activecol = t.Col 537 } 538 } 539 goto Continue 540 } 541 if m.Buttons != 0 { 542 if w != nil { 543 wind.Winlock(w, 'M') 544 } 545 t.Eq0 = ^0 546 if w != nil { 547 wind.Wincommit(w, t) 548 } else { 549 wind.Textcommit(t, true) 550 } 551 if m.Buttons&1 != 0 { 552 ui.Textselect(t) 553 if w != nil { 554 wind.Winsettag(w) 555 } 556 wind.Argtext = t 557 wind.Seltext = t 558 if t.Col != nil { 559 wind.Activecol = t.Col // button 1 only 560 } 561 if t.W != nil && t == &t.W.Body { 562 wind.Activewin = t.W 563 } 564 } else if m.Buttons&2 != 0 { 565 var argt *wind.Text 566 var q0, q1 int 567 if ui.Textselect2(t, &q0, &q1, &argt) != 0 { 568 exec.Execute(t, q0, q1, false, argt) 569 } 570 } else if m.Buttons&4 != 0 { 571 var q0, q1 int 572 if ui.Textselect3(t, &q0, &q1) { 573 ui.Look3(t, q0, q1, false) 574 } 575 } 576 if w != nil { 577 wind.Winunlock(w) 578 } 579 goto Continue 580 } 581 Continue: 582 wind.TheRow.Lk.Unlock() 583 } 584 } 585 } 586 587 /* 588 * There is a race between process exiting and our finding out it was ever created. 589 * This structure keeps a list of processes that have exited we haven't heard of. 590 */ 591 592 type Proc struct { 593 proc *os.Process 594 err error 595 next *Proc 596 } 597 598 func waitthread() { 599 var pids *Proc 600 601 bigLock() 602 defer bigUnlock() 603 604 for { 605 var c *exec.Command 606 bigUnlock() 607 select { 608 case errb := <-cerr: 609 wind.TheRow.Lk.Lock() 610 bigLock() 611 alog.Printf("%s", errb) 612 adraw.Display.Flush() 613 wind.TheRow.Lk.Unlock() 614 615 case cmd := <-exec.Ckill: 616 bigLock() 617 found := false 618 for c = command; c != nil; c = c.Next { 619 // -1 for blank 620 if runes.Equal(c.Name[:len(c.Name)-1], cmd) { 621 /* TODO postnote 622 if postnote(PNGROUP, c.pid, "kill") < 0 { 623 Printf("kill %S: %r\n", cmd) 624 } 625 */ 626 found = true 627 } 628 } 629 if !found { 630 alog.Printf("Kill: no process %s\n", string(cmd)) 631 } 632 633 case w := <-exec.Cwait: 634 wind.TheRow.Lk.Lock() 635 bigLock() 636 proc := w.Proc 637 var lc *exec.Command 638 for c = command; c != nil; c = c.Next { 639 if c.Proc == proc { 640 if lc != nil { 641 lc.Next = c.Next 642 } else { 643 command = c.Next 644 } 645 break 646 } 647 lc = c 648 } 649 t := &wind.TheRow.Tag 650 wind.Textcommit(t, true) 651 if c == nil { 652 p := new(Proc) 653 p.proc = proc 654 p.err = w.Err 655 p.next = pids 656 pids = p 657 } else { 658 if ui.Search(t, c.Name) { 659 wind.Textdelete(t, t.Q0, t.Q1, true) 660 wind.Textsetselect(t, 0, 0) 661 } 662 if w.Err != nil { 663 warning(c.Mntdir, "%s: exit %s\n", string(c.Name[:len(c.Name)-1]), w.Err) 664 } 665 adraw.Display.Flush() 666 } 667 wind.TheRow.Lk.Unlock() 668 goto Freecmd 669 670 case c = <-exec.Ccommand: 671 bigLock() 672 // has this command already exited? 673 var lastp *Proc 674 for p := pids; p != nil; p = p.next { 675 if p.proc == c.Proc { 676 if p.err != nil { 677 warning(c.Mntdir, "%s\n", p.err) 678 } 679 if lastp == nil { 680 pids = p.next 681 } else { 682 lastp.next = p.next 683 } 684 goto Freecmd 685 } 686 lastp = p 687 } 688 c.Next = command 689 command = c 690 bigUnlock() 691 wind.TheRow.Lk.Lock() 692 bigLock() 693 t := &wind.TheRow.Tag 694 wind.Textcommit(t, true) 695 wind.Textinsert(t, 0, c.Name, true) 696 wind.Textsetselect(t, 0, 0) 697 adraw.Display.Flush() 698 wind.TheRow.Lk.Unlock() 699 } 700 continue 701 702 Freecmd: 703 if c != nil { 704 if c.IsEditCmd { 705 editpkg.Cedit <- 0 706 } 707 fsysdelid(c.Mntdir) 708 } 709 } 710 } 711 712 func xfidallocthread() { 713 var xfree *Xfid 714 for { 715 // TODO(rsc): split cxfidalloc into two channels 716 select { 717 case <-cxfidalloc: 718 x := xfree 719 if x != nil { 720 xfree = x.next 721 } else { 722 x = new(Xfid) 723 x.c = make(chan func(*Xfid)) 724 x.arg = x 725 go xfidctl(x) 726 } 727 cxfidalloc <- x 728 729 case x := <-cxfidfree: 730 x.next = xfree 731 xfree = x 732 } 733 } 734 } 735 736 // this thread, in the main proc, allows fsysproc to get a window made without doing graphics 737 func newwindowthread() { 738 for { 739 // only fsysproc is talking to us, so synchronization is trivial 740 // TODO(rsc): split cnewwindow into two channels 741 <-cnewwindow 742 bigLock() 743 w := ui.Makenewwindow(nil) 744 wind.Winsettag(w) 745 ui.Winmousebut(w) 746 xfidlog(w, "new") 747 bigUnlock() 748 cnewwindow <- w 749 } 750 } 751 752 func appendRune(buf []byte, r rune) []byte { 753 n := len(buf) 754 for cap(buf)-n < utf8.UTFMax { 755 buf = append(buf[:cap(buf)], 0)[:n] 756 } 757 w := utf8.EncodeRune(buf[n:n+utf8.UTFMax], r) 758 return buf[:n+w] 759 } 760 761 func ismtpt(file string) bool { 762 if mtpt == "" { 763 return false 764 } 765 766 // This is not foolproof, but it will stop a lot of them. 767 return strings.HasPrefix(file, mtpt) && (len(file) == len(mtpt) || file[len(mtpt)] == '/') 768 } 769 770 // big is big lock, meant to model the cooperative scheduling in Alef. 771 // No blocking operations should happen while holding the big lock: 772 // no channel operations and no other mutex locking. 773 // If channels must be used or other mutexes must be acquired, 774 // code must drop the big lock, do the blocking thing, and reacquire the big lock. 775 // The big lock is always acquired last compared to any other mutexes. 776 // Eventually the goal is to avoid needing it at all, but that will take some time. 777 var big sync.Mutex 778 var stk = make([]byte, 1<<20) 779 780 func bigLock() { 781 big.Lock() 782 //n := runtime.Stack(stk, true) 783 //print("\n\nbig.Lock:\n", string(stk[:n])) 784 } 785 786 func bigUnlock() { 787 //n := runtime.Stack(stk, true) 788 //print("\n\nbig.Unlock:\n", string(stk[:n])) 789 big.Unlock() 790 }