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  }