9fans.net/go@v0.0.7/cmd/acme/xfid.go (about)

     1  // #include <u.h>
     2  // #include <libc.h>
     3  // #include <draw.h>
     4  // #include <thread.h>
     5  // #include <cursor.h>
     6  // #include <mouse.h>
     7  // #include <keyboard.h>
     8  // #include <frame.h>
     9  // #include <fcall.h>
    10  // #include <plumb.h>
    11  // #include <libsec.h>
    12  // #include "dat.h"
    13  // #include "fns.h"
    14  
    15  package main
    16  
    17  import (
    18  	"bytes"
    19  	"fmt"
    20  	"io"
    21  	"strings"
    22  	"unicode/utf8"
    23  
    24  	addrpkg "9fans.net/go/cmd/acme/internal/addr"
    25  	"9fans.net/go/cmd/acme/internal/adraw"
    26  	"9fans.net/go/cmd/acme/internal/alog"
    27  	"9fans.net/go/cmd/acme/internal/bufs"
    28  	"9fans.net/go/cmd/acme/internal/disk"
    29  	editpkg "9fans.net/go/cmd/acme/internal/edit"
    30  	"9fans.net/go/cmd/acme/internal/exec"
    31  	"9fans.net/go/cmd/acme/internal/file"
    32  	"9fans.net/go/cmd/acme/internal/runes"
    33  	"9fans.net/go/cmd/acme/internal/ui"
    34  	"9fans.net/go/cmd/acme/internal/util"
    35  	"9fans.net/go/cmd/acme/internal/wind"
    36  	"9fans.net/go/plan9"
    37  )
    38  
    39  const (
    40  	Ctlsize = 5 * 12
    41  )
    42  
    43  var Edel string = "deleted window"
    44  var Ebadctl string = "ill-formed control message"
    45  var Ebadaddr string = "bad address syntax"
    46  var Eaddr string = "address out of range"
    47  var Einuse string = "already in use"
    48  var Ebadevent string = "bad event syntax"
    49  
    50  // extern var Eperm [unknown]C.char
    51  
    52  func clampaddr(w *wind.Window) {
    53  	if w.Addr.Pos < 0 {
    54  		w.Addr.Pos = 0
    55  	}
    56  	if w.Addr.End < 0 {
    57  		w.Addr.End = 0
    58  	}
    59  	if w.Addr.Pos > w.Body.Len() {
    60  		w.Addr.Pos = w.Body.Len()
    61  	}
    62  	if w.Addr.End > w.Body.Len() {
    63  		w.Addr.End = w.Body.Len()
    64  	}
    65  }
    66  
    67  func xfidctl(x *Xfid) {
    68  	for {
    69  		f := <-x.c
    70  		bigLock()
    71  		f(x)
    72  		adraw.Display.Flush()
    73  		bigUnlock()
    74  		cxfidfree <- x
    75  	}
    76  }
    77  
    78  func xfidflush(x *Xfid) {
    79  	xfidlogflush(x)
    80  
    81  	// search windows for matching tag
    82  	bigUnlock()
    83  	wind.TheRow.Lk.Lock()
    84  	bigLock()
    85  	for j := 0; j < len(wind.TheRow.Col); j++ {
    86  		c := wind.TheRow.Col[j]
    87  		for i := 0; i < len(c.W); i++ {
    88  			w := c.W[i]
    89  			wind.Winlock(w, 'E')
    90  			ch := w.Eventwait
    91  			if ch != nil && w.Eventtag == x.fcall.Oldtag {
    92  				w.Eventwait = nil
    93  				ch <- false // flushed
    94  				wind.Winunlock(w)
    95  				goto out
    96  			}
    97  			wind.Winunlock(w)
    98  		}
    99  	}
   100  out:
   101  	wind.TheRow.Lk.Unlock()
   102  	var fc plan9.Fcall
   103  	respond(x, &fc, "")
   104  }
   105  
   106  func xfidopen(x *Xfid) {
   107  	w := x.f.w
   108  	q := FILE(x.f.qid)
   109  	var fc plan9.Fcall
   110  	if w != nil {
   111  		t := &w.Body
   112  		wind.Winlock(w, 'E')
   113  		switch q {
   114  		case QWaddr:
   115  			tmp30 := nopen[wq{w, q}]
   116  			nopen[wq{w, q}]++
   117  			if tmp30 == 0 {
   118  				w.Addr = runes.Rng(0, 0)
   119  				w.Limit = runes.Rng(-1, -1)
   120  			}
   121  		case QWdata,
   122  			QWxdata:
   123  			nopen[wq{w, q}]++
   124  		case QWevent:
   125  			tmp31 := nopen[wq{w, q}]
   126  			nopen[wq{w, q}]++
   127  			if tmp31 == 0 {
   128  
   129  				w.External = true
   130  				if !w.IsDir && w.Col != nil {
   131  					w.Filemenu = false
   132  					wind.Winsettag(w)
   133  				}
   134  			}
   135  		/*
   136  		 * Use a temporary file.
   137  		 * A pipe would be the obvious, but we can't afford the
   138  		 * broken pipe notification.  Using the code to read QWbody
   139  		 * is n², which should probably also be fixed.  Even then,
   140  		 * though, we'd need to squirrel away the data in case it's
   141  		 * modified during the operation, e.g. by |sort
   142  		 */
   143  		case QWrdsel:
   144  			if w.Rdselfd != nil {
   145  				wind.Winunlock(w)
   146  				respond(x, &fc, Einuse)
   147  				return
   148  			}
   149  			w.Rdselfd = disk.TempFile() // TODO(rsc): who deletes this?
   150  			if w.Rdselfd == nil {       // TODO(rsc): impossible
   151  				wind.Winunlock(w)
   152  				respond(x, &fc, "can't create temp file")
   153  				return
   154  			}
   155  			nopen[wq{w, q}]++
   156  			q0 := t.Q0
   157  			q1 := t.Q1
   158  			r := bufs.AllocRunes()
   159  			s := bufs.AllocRunes()
   160  			for q0 < q1 {
   161  				n := q1 - q0
   162  				if n > bufs.Len/utf8.UTFMax {
   163  					n = bufs.Len / utf8.UTFMax
   164  				}
   165  				t.File.Read(q0, r[:n])
   166  				s := []byte(string(r[:n])) // TODO(rsc)
   167  				if _, err := w.Rdselfd.Write(s); err != nil {
   168  					alog.Printf("can't write temp file for pipe command %v\n", err)
   169  					break
   170  				}
   171  				q0 += n
   172  			}
   173  			bufs.FreeRunes(s)
   174  			bufs.FreeRunes(r)
   175  		case QWwrsel:
   176  			nopen[wq{w, q}]++
   177  			file.Seq++
   178  			t.File.Mark()
   179  			ui.XCut(t, t, nil, false, true, nil)
   180  			w.Wrselrange = runes.Rng(t.Q1, t.Q1)
   181  			w.Nomark = true
   182  		case QWeditout:
   183  			if editpkg.Editing == editpkg.Inactive {
   184  				wind.Winunlock(w)
   185  				respond(x, &fc, Eperm)
   186  				return
   187  			}
   188  			if !w.Editoutlk.TryLock() {
   189  				wind.Winunlock(w)
   190  				respond(x, &fc, Einuse)
   191  				return
   192  			}
   193  			w.Wrselrange = runes.Rng(t.Q1, t.Q1)
   194  		}
   195  		wind.Winunlock(w)
   196  	} else {
   197  		switch q {
   198  		case Qlog:
   199  			xfidlogopen(x)
   200  		case Qeditout:
   201  			if !editpkg.Editoutlk.TryLock() {
   202  				respond(x, &fc, Einuse)
   203  				return
   204  			}
   205  		}
   206  	}
   207  	fc.Qid = x.f.qid
   208  	fc.Iounit = uint32(messagesize - plan9.IOHDRSZ)
   209  	x.f.open = true
   210  	respond(x, &fc, "")
   211  }
   212  
   213  func xfidclose(x *Xfid) {
   214  	w := x.f.w
   215  	x.f.busy = false
   216  	x.f.w = nil
   217  	var fc plan9.Fcall
   218  	if !x.f.open {
   219  		if w != nil {
   220  			wind.Winclose(w)
   221  		}
   222  		respond(x, &fc, "")
   223  		return
   224  	}
   225  
   226  	q := FILE(x.f.qid)
   227  	x.f.open = false
   228  	if w != nil {
   229  		wind.Winlock(w, 'E')
   230  		var t *wind.Text
   231  		switch q {
   232  		case QWctl:
   233  			if w.Ctlfid != ^0 && w.Ctlfid == x.f.fid {
   234  				w.Ctlfid = ^0
   235  				w.Ctllock.Unlock()
   236  			}
   237  		case QWdata,
   238  			QWxdata:
   239  			w.Nomark = false
   240  			fallthrough
   241  		// fall through
   242  		case QWaddr,
   243  			QWevent: // BUG: do we need to shut down Xfid?
   244  			nopen[wq{w, q}]--
   245  			if nopen[wq{w, q}] == 0 {
   246  				if q == QWdata || q == QWxdata {
   247  					w.Nomark = false
   248  				}
   249  				if q == QWevent && !w.IsDir && w.Col != nil {
   250  					w.Filemenu = true
   251  					wind.Winsettag(w)
   252  				}
   253  				if q == QWevent {
   254  
   255  					w.External = false
   256  					w.Dumpstr = ""
   257  					w.Dumpdir = ""
   258  				}
   259  			}
   260  		case QWrdsel:
   261  			w.Rdselfd.Close()
   262  			w.Rdselfd = nil
   263  		case QWwrsel:
   264  			w.Nomark = false
   265  			t = &w.Body
   266  			// before: only did this if !w->noscroll, but that didn't seem right in practice
   267  			wind.Textshow(t, util.Min(w.Wrselrange.Pos, t.Len()), util.Min(w.Wrselrange.End, t.Len()), true)
   268  			wind.Textscrdraw(t)
   269  		case QWeditout:
   270  			w.Editoutlk.Unlock()
   271  		}
   272  		wind.Winunlock(w)
   273  		wind.Winclose(w)
   274  	} else {
   275  		switch q {
   276  		case Qeditout:
   277  			editpkg.Editoutlk.Unlock()
   278  		}
   279  	}
   280  	respond(x, &fc, "")
   281  }
   282  
   283  func xfidread(x *Xfid) {
   284  	q := FILE(x.f.qid)
   285  	w := x.f.w
   286  	var fc plan9.Fcall
   287  	if w == nil {
   288  		fc.Count = 0
   289  		switch q {
   290  		case Qcons,
   291  			Qlabel:
   292  			break
   293  		case Qindex:
   294  			xfidindexread(x)
   295  			return
   296  		case Qlog:
   297  			xfidlogread(x)
   298  			return
   299  		default:
   300  			alog.Printf("unknown qid %d\n", q)
   301  		}
   302  		respond(x, &fc, "")
   303  		return
   304  	}
   305  
   306  	wind.Winlock(w, 'F')
   307  	if w.Col == nil {
   308  		wind.Winunlock(w)
   309  		respond(x, &fc, Edel)
   310  		return
   311  	}
   312  	defer wind.Winunlock(w)
   313  
   314  	off := int64(x.fcall.Offset)
   315  	var buf []byte
   316  	switch q {
   317  	case QWaddr:
   318  		wind.Textcommit(&w.Body, true)
   319  		clampaddr(w)
   320  		buf = []byte(fmt.Sprintf("%11d %11d ", w.Addr.Pos, w.Addr.End))
   321  		goto Readbuf
   322  
   323  	case QWbody:
   324  		xfidutfread(x, &w.Body, w.Body.Len(), QWbody)
   325  
   326  	case QWctl:
   327  		buf = []byte(wind.Winctlprint(w, true))
   328  		goto Readbuf
   329  
   330  	case QWevent:
   331  		xfideventread(x, w)
   332  
   333  	case QWdata:
   334  		// BUG: what should happen if q1 > q0?
   335  		if w.Addr.Pos > w.Body.Len() {
   336  			respond(x, &fc, Eaddr)
   337  			break
   338  		}
   339  		w.Addr.Pos += xfidruneread(x, &w.Body, w.Addr.Pos, w.Body.Len())
   340  		w.Addr.End = w.Addr.Pos
   341  
   342  	case QWxdata:
   343  		// BUG: what should happen if q1 > q0?
   344  		if w.Addr.Pos > w.Body.Len() {
   345  			respond(x, &fc, Eaddr)
   346  			break
   347  		}
   348  		w.Addr.Pos += xfidruneread(x, &w.Body, w.Addr.Pos, w.Addr.End)
   349  
   350  	case QWtag:
   351  		xfidutfread(x, &w.Tag, w.Tag.Len(), QWtag)
   352  
   353  	case QWrdsel:
   354  		w.Rdselfd.Seek(int64(off), 0)
   355  		n := int(x.fcall.Count)
   356  		if x.fcall.Count > bufs.Len {
   357  			n = bufs.Len
   358  		}
   359  		b := make([]byte, bufs.Len) // TODO fbufalloc()
   360  		n, err := w.Rdselfd.Read(b[:n])
   361  		if err != nil && err != io.EOF {
   362  			respond(x, &fc, "I/O error in temp file")
   363  			break
   364  		}
   365  		fc.Count = uint32(n)
   366  		fc.Data = b[:n]
   367  		respond(x, &fc, "")
   368  		// fbuffree(b)
   369  
   370  	default:
   371  		respond(x, &fc, fmt.Sprintf("unknown qid %d in read", q))
   372  	}
   373  	return
   374  
   375  Readbuf:
   376  	if off > int64(len(buf)) {
   377  		off = int64(len(buf))
   378  	}
   379  	fc.Data = buf[off:]
   380  	if int64(len(fc.Data)) > int64(x.fcall.Count) {
   381  		fc.Data = fc.Data[:x.fcall.Count]
   382  	}
   383  	fc.Count = uint32(len(fc.Data))
   384  	respond(x, &fc, "")
   385  }
   386  
   387  func shouldscroll(t *wind.Text, q0 int, qid int) bool {
   388  	if qid == Qcons {
   389  		return true
   390  	}
   391  	return t.Org <= q0 && q0 <= t.Org+t.Fr.NumChars
   392  }
   393  
   394  func fullrunewrite(x *Xfid) []rune {
   395  	q := len(x.f.rpart)
   396  	cnt := len(x.fcall.Data)
   397  	if q > 0 {
   398  		x.fcall.Data = x.fcall.Data[:cnt+q]
   399  		copy(x.fcall.Data[q:], x.fcall.Data)
   400  		copy(x.fcall.Data, x.f.rpart[:q])
   401  		x.f.rpart = x.f.rpart[:0]
   402  	}
   403  	r := make([]rune, cnt)
   404  	nb, nr, _ := runes.Convert(x.fcall.Data, r, false)
   405  	r = r[:nr]
   406  	// approach end of buffer
   407  	for utf8.FullRune(x.fcall.Data[nb:cnt]) {
   408  		ch, w := utf8.DecodeRune(x.fcall.Data[nb:])
   409  		nb += w
   410  		if ch != 0 {
   411  			r = append(r, ch)
   412  		}
   413  	}
   414  	if nb < cnt {
   415  		if cap(x.f.rpart) < utf8.UTFMax {
   416  			x.f.rpart = make([]byte, 0, utf8.UTFMax)
   417  		}
   418  		x.f.rpart = append(x.f.rpart, x.fcall.Data[nb:]...)
   419  	}
   420  	return r
   421  }
   422  
   423  func xfidwrite(x *Xfid) {
   424  	qid := FILE(x.f.qid)
   425  	w := x.f.w
   426  	var fc plan9.Fcall
   427  	if w != nil {
   428  		c := 'F'
   429  		if qid == QWtag || qid == QWbody {
   430  			c = 'E'
   431  		}
   432  		wind.Winlock(w, c)
   433  		if w.Col == nil {
   434  			wind.Winunlock(w)
   435  			respond(x, &fc, Edel)
   436  			return
   437  		}
   438  	}
   439  	switch qid {
   440  	case Qlabel:
   441  		fc.Count = uint32(len(x.fcall.Data))
   442  		respond(x, &fc, "")
   443  
   444  	case QWaddr:
   445  		r := []rune(string(x.fcall.Data))
   446  		t := &w.Body
   447  		wind.Wincommit(w, t)
   448  		eval := true
   449  		var nb int
   450  		a := addrpkg.Eval(false, t, w.Limit, w.Addr, r, 0, len(r), rgetc, &eval, &nb)
   451  		if nb < len(r) {
   452  			respond(x, &fc, Ebadaddr)
   453  			break
   454  		}
   455  		if !eval {
   456  			respond(x, &fc, Eaddr)
   457  			break
   458  		}
   459  		w.Addr = a
   460  		fc.Count = uint32(len(x.fcall.Data))
   461  		respond(x, &fc, "")
   462  
   463  	case Qeditout,
   464  		QWeditout:
   465  		r := fullrunewrite(x)
   466  		var err error
   467  		if w != nil {
   468  			err = editpkg.Edittext(w, w.Wrselrange.End, r)
   469  		} else {
   470  			err = editpkg.Edittext(nil, 0, r)
   471  		}
   472  		if err != nil {
   473  			respond(x, &fc, err.Error())
   474  			break
   475  		}
   476  		fc.Count = uint32(len(x.fcall.Data))
   477  		respond(x, &fc, "")
   478  
   479  	case QWctl:
   480  		xfidctlwrite(x, w)
   481  
   482  	case QWdata:
   483  		a := w.Addr
   484  		t := &w.Body
   485  		wind.Wincommit(w, t)
   486  		if a.Pos > t.Len() || a.End > t.Len() {
   487  			respond(x, &fc, Eaddr)
   488  			break
   489  		}
   490  		r := make([]rune, len(x.fcall.Data))
   491  		_, nr, _ := runes.Convert(x.fcall.Data, r, true)
   492  		r = r[:nr]
   493  		if !w.Nomark {
   494  			file.Seq++
   495  			t.File.Mark()
   496  		}
   497  		q0 := a.Pos
   498  		if a.End > q0 {
   499  			wind.Textdelete(t, q0, a.End, true)
   500  			w.Addr.End = q0
   501  		}
   502  		tq0 := t.Q0
   503  		tq1 := t.Q1
   504  		wind.Textinsert(t, q0, r, true)
   505  		if tq0 >= q0 {
   506  			tq0 += nr
   507  		}
   508  		if tq1 >= q0 {
   509  			tq1 += nr
   510  		}
   511  		wind.Textsetselect(t, tq0, tq1)
   512  		if shouldscroll(t, q0, qid) {
   513  			wind.Textshow(t, q0+nr, q0+nr, false)
   514  		}
   515  		wind.Textscrdraw(t)
   516  		wind.Winsettag(w)
   517  		w.Addr.Pos += nr
   518  		w.Addr.End = w.Addr.Pos
   519  		fc.Count = uint32(len(x.fcall.Data))
   520  		respond(x, &fc, "")
   521  
   522  	case QWevent:
   523  		xfideventwrite(x, w)
   524  
   525  	case Qcons, QWerrors, QWbody, QWwrsel, QWtag:
   526  		var t *wind.Text
   527  		switch qid {
   528  		case Qcons:
   529  			w = errorwin(x.f.mntdir, 'X')
   530  			t = &w.Body
   531  
   532  		case QWerrors:
   533  			w = errorwinforwin(w)
   534  			t = &w.Body
   535  
   536  		case QWbody,
   537  			QWwrsel:
   538  			t = &w.Body
   539  
   540  		case QWtag:
   541  			t = &w.Tag
   542  		}
   543  
   544  		r := fullrunewrite(x)
   545  		if len(r) > 0 {
   546  			wind.Wincommit(w, t)
   547  			var q0 int
   548  			if qid == QWwrsel {
   549  				q0 = w.Wrselrange.End
   550  				if q0 > t.Len() {
   551  					q0 = t.Len()
   552  				}
   553  			} else {
   554  				q0 = t.Len()
   555  			}
   556  			nr := len(r)
   557  			if qid == QWtag {
   558  				wind.Textinsert(t, q0, r, true)
   559  			} else {
   560  				if !w.Nomark {
   561  					file.Seq++
   562  					t.File.Mark()
   563  				}
   564  				q0 = wind.Textbsinsert(t, q0, r, true, &nr)
   565  				wind.Textsetselect(t, t.Q0, t.Q1) // insert could leave it somewhere else
   566  				if qid != QWwrsel && shouldscroll(t, q0, qid) {
   567  					wind.Textshow(t, q0+nr, q0+nr, true)
   568  				}
   569  				wind.Textscrdraw(t)
   570  			}
   571  			wind.Winsettag(w)
   572  			if qid == QWwrsel {
   573  				w.Wrselrange.End += nr
   574  			}
   575  		}
   576  		fc.Count = uint32(len(x.fcall.Data))
   577  		respond(x, &fc, "")
   578  
   579  	default:
   580  		respond(x, &fc, fmt.Sprintf("unknown qid %d in write", qid))
   581  	}
   582  	if w != nil {
   583  		// Note: Cannot defer above - w changes in errorwinforwin call.
   584  		wind.Winunlock(w)
   585  	}
   586  }
   587  
   588  func xfidctlwrite(x *Xfid, w *wind.Window) {
   589  	scrdraw := false
   590  	settag := false
   591  	isfbuf := true
   592  	var r []rune
   593  	if int(x.fcall.Count) < bufs.RuneLen {
   594  		r = bufs.AllocRunes()
   595  	} else {
   596  		isfbuf = false
   597  		r = make([]rune, x.fcall.Count*utf8.UTFMax)
   598  	}
   599  	wind.Textcommit(&w.Tag, true)
   600  	p := string(x.fcall.Data)
   601  	var err string
   602  	for p != "" {
   603  		if strings.HasPrefix(p, "lock") { // make window exclusive use
   604  			w.Ctllock.Lock()
   605  			w.Ctlfid = x.f.fid
   606  			p = p[4:]
   607  		} else if strings.HasPrefix(p, "unlock") { // release exclusive use
   608  			w.Ctlfid = ^0
   609  			w.Ctllock.Unlock()
   610  			p = p[6:]
   611  		} else if strings.HasPrefix(p, "clean") { // mark window 'clean', seq=0
   612  			t := &w.Body
   613  			t.Eq0 = ^0
   614  			t.File.ResetLogs()
   615  			t.File.SetMod(false)
   616  			w.Dirty = false
   617  			settag = true
   618  			p = p[5:]
   619  		} else if strings.HasPrefix(p, "dirty") { // mark window 'dirty'
   620  			t := &w.Body
   621  			// doesn't change sequence number, so "Put" won't appear.  it shouldn't.
   622  			t.File.SetMod(true)
   623  			w.Dirty = true
   624  			settag = true
   625  			p = p[5:]
   626  		} else if strings.HasPrefix(p, "show") { // show dot
   627  			t := &w.Body
   628  			wind.Textshow(t, t.Q0, t.Q1, true)
   629  			p = p[4:]
   630  		} else if strings.HasPrefix(p, "name ") { // set file name
   631  			pp := p[5:]
   632  			p = p[5:]
   633  			i := strings.Index(pp, "\n")
   634  			if i <= 0 {
   635  				err = Ebadctl
   636  				break
   637  			}
   638  			pp = pp[:i]
   639  			p = p[i+1:]
   640  			r := make([]rune, len(pp))
   641  			_, nr, nulls := runes.Convert([]byte(pp), r, true)
   642  			if nulls {
   643  				err = "nulls in file name"
   644  				break
   645  			}
   646  			r = r[:nr]
   647  			for i := 0; i < nr; i++ {
   648  				if r[i] <= ' ' {
   649  					err = "bad character in file name"
   650  					goto out // TODO(rsc): still set name?
   651  				}
   652  			}
   653  		out:
   654  			file.Seq++
   655  			w.Body.File.Mark()
   656  			wind.Winsetname(w, r[:nr])
   657  		} else if strings.HasPrefix(p, "font ") { // execute font command
   658  			pp := p[5:]
   659  			p = p[5:]
   660  			i := strings.Index(pp, "\n")
   661  			if i <= 0 {
   662  				err = Ebadctl
   663  				break
   664  			}
   665  			pp = pp[:i]
   666  			p = p[i+1:]
   667  			r := make([]rune, len(pp))
   668  			_, nr, nulls := runes.Convert([]byte(pp), r, true)
   669  			if nulls {
   670  				err = "nulls in font string"
   671  				break
   672  			}
   673  			r = r[:nr]
   674  			ui.Fontx(&w.Body, nil, nil, false, exec.XXX, r)
   675  		} else if strings.HasPrefix(p, "dump ") { // set dump string
   676  			pp := p[5:]
   677  			p = p[5:]
   678  			i := strings.Index(pp, "\n")
   679  			if i <= 0 {
   680  				err = Ebadctl
   681  				break
   682  			}
   683  			pp = pp[:i]
   684  			p = p[i+1:]
   685  			r := make([]rune, len(pp))
   686  			_, nr, nulls := runes.Convert([]byte(pp), r, true)
   687  			if nulls {
   688  				err = "nulls in dump string"
   689  				break
   690  			}
   691  			r = r[:nr]
   692  			w.Dumpstr = string(r)
   693  		} else if strings.HasPrefix(p, "dumpdir ") { // set dump directory
   694  			pp := p[8:]
   695  			p = p[8:]
   696  			i := strings.Index(pp, "\n")
   697  			if i <= 0 {
   698  				err = Ebadctl
   699  				break
   700  			}
   701  			pp = pp[:i]
   702  			p = p[i+1:]
   703  			r := make([]rune, len(pp))
   704  			_, nr, nulls := runes.Convert([]byte(pp), r, true)
   705  			if nulls {
   706  				err = "nulls in dump string"
   707  				break
   708  			}
   709  			r = r[:nr]
   710  			w.Dumpdir = string(r)
   711  		} else if strings.HasPrefix(p, "delete") { // delete for sure
   712  			ui.ColcloseAndMouse(w.Col, w, true)
   713  			p = p[6:]
   714  		} else if strings.HasPrefix(p, "del") { // delete, but check dirty
   715  			if !wind.Winclean(w, true) {
   716  				err = "file dirty"
   717  				break
   718  			}
   719  			ui.ColcloseAndMouse(w.Col, w, true)
   720  			p = p[3:]
   721  		} else if strings.HasPrefix(p, "get") { // get file
   722  			exec.Get(&w.Body, nil, nil, false, exec.XXX, nil)
   723  			p = p[3:]
   724  		} else if strings.HasPrefix(p, "put") { // put file
   725  			exec.Put(&w.Body, nil, nil, exec.XXX, exec.XXX, nil)
   726  			p = p[3:]
   727  		} else if strings.HasPrefix(p, "dot=addr") { // set dot
   728  			wind.Textcommit(&w.Body, true)
   729  			clampaddr(w)
   730  			w.Body.Q0 = w.Addr.Pos
   731  			w.Body.Q1 = w.Addr.End
   732  			wind.Textsetselect(&w.Body, w.Body.Q0, w.Body.Q1)
   733  			settag = true
   734  			p = p[8:]
   735  		} else if strings.HasPrefix(p, "addr=dot") { // set addr
   736  			w.Addr.Pos = w.Body.Q0
   737  			w.Addr.End = w.Body.Q1
   738  			p = p[8:]
   739  		} else if strings.HasPrefix(p, "limit=addr") { // set limit
   740  			wind.Textcommit(&w.Body, true)
   741  			clampaddr(w)
   742  			w.Limit.Pos = w.Addr.Pos
   743  			w.Limit.End = w.Addr.End
   744  			p = p[10:]
   745  		} else if strings.HasPrefix(p, "nomark") { // turn off automatic marking
   746  			w.Nomark = true
   747  			p = p[6:]
   748  		} else if strings.HasPrefix(p, "mark") { // mark file
   749  			file.Seq++
   750  			w.Body.File.Mark()
   751  			settag = true
   752  			p = p[4:]
   753  		} else if strings.HasPrefix(p, "nomenu") { // turn off automatic menu
   754  			w.Filemenu = false
   755  			settag = true
   756  			p = p[6:]
   757  		} else if strings.HasPrefix(p, "menu") { // enable automatic menu
   758  			w.Filemenu = true
   759  			settag = true
   760  			p = p[4:]
   761  		} else if strings.HasPrefix(p, "cleartag") { // wipe tag right of bar
   762  			wind.Wincleartatg(w)
   763  			settag = true
   764  			p = p[8:]
   765  		} else {
   766  			err = Ebadctl
   767  			break
   768  		}
   769  		for p != "" && p[0] == '\n' {
   770  			p = p[1:]
   771  		}
   772  	}
   773  
   774  	if isfbuf {
   775  		bufs.FreeRunes(r)
   776  	}
   777  	n := len(x.fcall.Data)
   778  	if err != "" {
   779  		n = 0
   780  	}
   781  	var fc plan9.Fcall
   782  	fc.Count = uint32(n)
   783  	respond(x, &fc, err)
   784  	if settag {
   785  		wind.Winsettag(w)
   786  	}
   787  	if scrdraw {
   788  		wind.Textscrdraw(&w.Body)
   789  	}
   790  }
   791  
   792  func xfideventwrite(x *Xfid, w *wind.Window) {
   793  	isfbuf := true
   794  	var r []rune
   795  	if len(x.fcall.Data) < bufs.RuneLen {
   796  		r = bufs.AllocRunes()
   797  	} else {
   798  		isfbuf = false
   799  		r = make([]rune, len(x.fcall.Data)*utf8.UTFMax)
   800  	}
   801  	var err string
   802  	p := x.fcall.Data
   803  	for len(p) > 0 {
   804  		// Parse event.
   805  		w.Owner = rune(p[0])
   806  		p = p[1:]
   807  		if len(p) == 0 {
   808  			goto Rescue
   809  		}
   810  		c := p[0]
   811  		p = p[1:]
   812  		for len(p) > 0 && p[0] == ' ' {
   813  			p = p[1:]
   814  		}
   815  		q0, i := strtoul(p)
   816  		if i == 0 {
   817  			goto Rescue
   818  		}
   819  		p = p[i:]
   820  		for len(p) > 0 && p[0] == ' ' {
   821  			p = p[1:]
   822  		}
   823  		q1, i := strtoul(p)
   824  		if i == 0 {
   825  			goto Rescue
   826  		}
   827  		p = p[i:]
   828  		for len(p) > 0 && p[0] == ' ' {
   829  			p = p[1:]
   830  		}
   831  		if len(p) == 0 || p[0] != '\n' {
   832  			goto Rescue
   833  		}
   834  		p = p[1:]
   835  
   836  		// Apply event.
   837  		var t *wind.Text
   838  		if 'a' <= c && c <= 'z' {
   839  			t = &w.Tag
   840  		} else if 'A' <= c && c <= 'Z' {
   841  			t = &w.Body
   842  		} else {
   843  			goto Rescue
   844  		}
   845  		if q0 > t.Len() || q1 > t.Len() || q0 > q1 {
   846  			goto Rescue
   847  		}
   848  
   849  		bigUnlock()
   850  		wind.TheRow.Lk.Lock() // just like mousethread
   851  		bigLock()
   852  		switch c {
   853  		case 'x',
   854  			'X':
   855  			exec.Execute(t, q0, q1, true, nil)
   856  		case 'l',
   857  			'L':
   858  			ui.Look3(t, q0, q1, true)
   859  		default:
   860  			wind.TheRow.Lk.Unlock()
   861  			goto Rescue
   862  		}
   863  		wind.TheRow.Lk.Unlock()
   864  	}
   865  	goto Out
   866  
   867  Rescue:
   868  	err = Ebadevent
   869  	goto Out
   870  
   871  Out:
   872  	if isfbuf {
   873  		bufs.FreeRunes(r)
   874  	}
   875  	n := len(x.fcall.Data)
   876  	if err != "" {
   877  		n = 0
   878  	}
   879  	var fc plan9.Fcall
   880  	fc.Count = uint32(n)
   881  	respond(x, &fc, err)
   882  }
   883  
   884  func strtoul(p []byte) (value, width int) {
   885  	i := 0
   886  	for i < len(p) && '0' <= p[i] && p[i] <= '9' {
   887  		value = value*10 + int(p[i]) - '0'
   888  		i++
   889  	}
   890  	return value, i
   891  }
   892  
   893  func xfidutfread(x *Xfid, t *wind.Text, q1 int, qid int) {
   894  	w := t.W
   895  	wind.Wincommit(w, t)
   896  	off := int64(x.fcall.Offset)
   897  	r := bufs.AllocRunes()
   898  	b1 := make([]byte, bufs.Len) // fbufalloc()
   899  	n := 0
   900  	var q int
   901  	var boff int64
   902  	if qid == w.Utflastqid && off >= int64(w.Utflastboff) && w.Utflastq <= q1 {
   903  		boff = w.Utflastboff
   904  		q = w.Utflastq
   905  	} else {
   906  		// BUG: stupid code: scan from beginning
   907  		boff = 0
   908  		q = 0
   909  	}
   910  	w.Utflastqid = qid
   911  	for q < q1 && n < int(x.fcall.Count) {
   912  		/*
   913  		 * Updating here avoids partial rune problem: we're always on a
   914  		 * char boundary. The cost is we will usually do one more read
   915  		 * than we really need, but that's better than being n^2.
   916  		 */
   917  		w.Utflastboff = boff
   918  		w.Utflastq = q
   919  		nr := q1 - q
   920  		if nr > bufs.Len/utf8.UTFMax {
   921  			nr = bufs.Len / utf8.UTFMax
   922  		}
   923  		t.File.Read(q, r[:nr])
   924  		b := []byte(string(r[:nr]))
   925  		if boff >= off {
   926  			m := len(b)
   927  			if boff+int64(m) > off+int64(x.fcall.Count) {
   928  				m = int(off + int64(x.fcall.Count) - boff)
   929  			}
   930  			copy(b1[n:], b[:m])
   931  			n += m
   932  		} else if boff+int64(len(b)) > off {
   933  			if n != 0 {
   934  				util.Fatal("bad count in utfrune")
   935  			}
   936  			m := int(int64(len(b)) - (off - boff))
   937  			if m > int(x.fcall.Count) {
   938  				m = int(x.fcall.Count)
   939  			}
   940  			copy(b1[:m], b[off-boff:])
   941  			n += m
   942  		}
   943  		boff += int64(len(b))
   944  		q += nr
   945  	}
   946  	bufs.FreeRunes(r)
   947  	var fc plan9.Fcall
   948  	fc.Count = uint32(n)
   949  	fc.Data = b1[:n]
   950  	respond(x, &fc, "")
   951  	// TODO fbuffree(b1)
   952  }
   953  
   954  func xfidruneread(x *Xfid, t *wind.Text, q0 int, q1 int) int {
   955  	w := t.W
   956  	wind.Wincommit(w, t)
   957  	r := bufs.AllocRunes()
   958  	// b := fbufalloc()
   959  	b1 := make([]byte, bufs.Len) // fbufalloc()
   960  	n := 0
   961  	q := q0
   962  	boff := 0
   963  	for q < q1 && n < int(x.fcall.Count) {
   964  		nr := q1 - q
   965  		if nr > bufs.Len/utf8.UTFMax {
   966  			nr = bufs.Len / utf8.UTFMax
   967  		}
   968  		t.File.Read(q, r[:nr])
   969  		b := []byte(string(r[:nr]))
   970  		nb := len(b)
   971  		m := nb
   972  		if boff+m > int(x.fcall.Count) {
   973  			i := int(x.fcall.Count) - boff
   974  			// copy whole runes only
   975  			m = 0
   976  			nr = 0
   977  			for m < i {
   978  				_, rw := utf8.DecodeRune(b[m:])
   979  				if m+rw > i {
   980  					break
   981  				}
   982  				m += rw
   983  				nr++
   984  			}
   985  			if m == 0 {
   986  				break
   987  			}
   988  		}
   989  		copy(b1[n:], b[:m])
   990  		n += m
   991  		boff += nb
   992  		q += nr
   993  	}
   994  	bufs.FreeRunes(r)
   995  	var fc plan9.Fcall
   996  	fc.Count = uint32(n)
   997  	fc.Data = b1[:n]
   998  	respond(x, &fc, "")
   999  	return q - q0
  1000  }
  1001  
  1002  func xfideventread(x *Xfid, w *wind.Window) {
  1003  	x.flushed = false
  1004  	var fc plan9.Fcall
  1005  	if len(w.Events) == 0 {
  1006  		c := make(chan bool, 1)
  1007  		w.Eventtag = x.fcall.Tag
  1008  		w.Eventwait = c
  1009  		wind.Winunlock(w)
  1010  		bigUnlock()
  1011  		ok := <-w.Eventwait
  1012  		bigLock()
  1013  		wind.Winlock(w, 'F')
  1014  		if !ok {
  1015  			return
  1016  		}
  1017  		if len(w.Events) == 0 {
  1018  			respond(x, &fc, "window shut down")
  1019  			return
  1020  		}
  1021  	}
  1022  
  1023  	n := len(w.Events)
  1024  	if n > int(x.fcall.Count) {
  1025  		n = int(x.fcall.Count)
  1026  	}
  1027  	fc.Count = uint32(n)
  1028  	fc.Data = w.Events[:n]
  1029  	respond(x, &fc, "")
  1030  	m := copy(w.Events[n:], w.Events)
  1031  	w.Events = w.Events[:m]
  1032  }
  1033  
  1034  func xfidindexread(x *Xfid) {
  1035  	wind.TheRow.Lk.Lock()
  1036  	nmax := 0
  1037  	var i int
  1038  	var j int
  1039  	var w *wind.Window
  1040  	var c *wind.Column
  1041  	for j = 0; j < len(wind.TheRow.Col); j++ {
  1042  		c = wind.TheRow.Col[j]
  1043  		for i = 0; i < len(c.W); i++ {
  1044  			w = c.W[i]
  1045  			nmax += Ctlsize + w.Tag.Len()*utf8.UTFMax + 1
  1046  		}
  1047  	}
  1048  	nmax++
  1049  	var buf bytes.Buffer
  1050  	r := bufs.AllocRunes()
  1051  	for j = 0; j < len(wind.TheRow.Col); j++ {
  1052  		c = wind.TheRow.Col[j]
  1053  		for i = 0; i < len(c.W); i++ {
  1054  			w = c.W[i]
  1055  			// only show the currently active window of a set
  1056  			if w.Body.File.Curtext != &w.Body {
  1057  				continue
  1058  			}
  1059  			buf.WriteString(wind.Winctlprint(w, false))
  1060  			m := util.Min(bufs.RuneLen, w.Tag.Len())
  1061  			w.Tag.File.Read(0, r[:m])
  1062  			for i := 0; i < m && r[i] != '\n'; i++ {
  1063  				buf.WriteRune(r[i])
  1064  			}
  1065  			buf.WriteRune('\n')
  1066  		}
  1067  	}
  1068  	bufs.FreeRunes(r)
  1069  	wind.TheRow.Lk.Unlock()
  1070  	off := int(x.fcall.Offset)
  1071  	cnt := int(x.fcall.Count)
  1072  	n := buf.Len()
  1073  	if off > n {
  1074  		off = n
  1075  	}
  1076  	if off+cnt > n {
  1077  		cnt = n - off
  1078  	}
  1079  	var fc plan9.Fcall
  1080  	fc.Count = uint32(cnt)
  1081  	fc.Data = buf.Bytes()[off : off+cnt]
  1082  	respond(x, &fc, "")
  1083  }
  1084  
  1085  type wq struct {
  1086  	w *wind.Window
  1087  	q int
  1088  }
  1089  
  1090  var nopen = make(map[wq]int)