9fans.net/go@v0.0.7/cmd/acme/internal/wind/text.go (about)

     1  package wind
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  
     7  	"9fans.net/go/cmd/acme/internal/adraw"
     8  	"9fans.net/go/cmd/acme/internal/bufs"
     9  	"9fans.net/go/cmd/acme/internal/file"
    10  	"9fans.net/go/cmd/acme/internal/runes"
    11  	"9fans.net/go/cmd/acme/internal/util"
    12  	"9fans.net/go/draw"
    13  	"9fans.net/go/draw/frame"
    14  )
    15  
    16  type Text struct {
    17  	File     *File
    18  	Fr       frame.Frame
    19  	Reffont  *adraw.RefFont
    20  	Org      int
    21  	Q0       int
    22  	Q1       int
    23  	What     int
    24  	Tabstop  int
    25  	W        *Window
    26  	ScrollR  draw.Rectangle
    27  	lastsr   draw.Rectangle
    28  	All      draw.Rectangle
    29  	Row      *Row
    30  	Col      *Column
    31  	IQ1      int
    32  	Eq0      int
    33  	Cq0      int
    34  	Cache    []rune
    35  	Nofill   bool
    36  	Needundo bool
    37  }
    38  
    39  func (t *Text) RuneAt(pos int) rune { return textreadc(t, pos) }
    40  
    41  func (t *Text) Len() int { return t.File.Len() }
    42  
    43  type File struct {
    44  	*file.File
    45  	Curtext *Text
    46  	Text    []*Text
    47  	Info    os.FileInfo
    48  	SHA1    [20]byte
    49  	Unread  bool
    50  	dumpid  int
    51  }
    52  
    53  func (f *File) SetName(r []rune) {
    54  	f.File.SetName(r)
    55  	f.Unread = true
    56  }
    57  
    58  type fileView File
    59  
    60  func (f *fileView) Insert(pos int, data []rune) {
    61  	for _, t := range f.Text {
    62  		Textinsert(t, pos, data, false)
    63  	}
    64  }
    65  
    66  func (f *fileView) Delete(pos, end int) {
    67  	for _, t := range f.Text {
    68  		Textdelete(t, pos, end, false)
    69  	}
    70  }
    71  
    72  var Argtext *Text
    73  
    74  var Typetext *Text // global because Text.close needs to clear it
    75  
    76  var Seltext *Text
    77  
    78  var Mousetext *Text // global because Text.close needs to clear it
    79  
    80  var Barttext *Text // shared between mousetask and keyboardthread
    81  
    82  const (
    83  	TABDIR = 3
    84  ) // width of tabs in directory windows
    85  
    86  var MaxTab int // size of a tab, in units of the '0' character
    87  
    88  func textinit(t *Text, f *File, r draw.Rectangle, rf *adraw.RefFont, cols []*draw.Image) {
    89  	t.File = f
    90  	t.All = r
    91  	t.ScrollR = r
    92  	t.ScrollR.Max.X = r.Min.X + adraw.Scrollwid()
    93  	t.lastsr = draw.ZR
    94  	r.Min.X += adraw.Scrollwid() + adraw.Scrollgap()
    95  	t.Eq0 = ^0
    96  	t.Cache = t.Cache[:0]
    97  	t.Reffont = rf
    98  	t.Tabstop = MaxTab
    99  	copy(t.Fr.Cols[:], cols)
   100  	textredraw(t, r, rf.F, adraw.Display.ScreenImage, -1)
   101  }
   102  
   103  func textclose(t *Text) {
   104  	t.Fr.Clear(true)
   105  	filedeltext(t.File, t)
   106  	t.File = nil
   107  	adraw.CloseFont(t.Reffont)
   108  	if Argtext == t {
   109  		Argtext = nil
   110  	}
   111  	if Typetext == t {
   112  		Typetext = nil
   113  	}
   114  	if Seltext == t {
   115  		Seltext = nil
   116  	}
   117  	if Mousetext == t {
   118  		Mousetext = nil
   119  	}
   120  	if Barttext == t {
   121  		Barttext = nil
   122  	}
   123  }
   124  
   125  func Textreset(t *Text) {
   126  	t.File.SetSeq(0)
   127  	t.Eq0 = ^0
   128  	// do t->delete(0, t->nc, TRUE) without building backup stuff
   129  	Textsetselect(t, t.Org, t.Org)
   130  	t.Fr.Delete(0, t.Fr.NumChars)
   131  	t.Org = 0
   132  	t.Q0 = 0
   133  	t.Q1 = 0
   134  	t.File.ResetLogs()
   135  	t.File.Truncate()
   136  }
   137  
   138  func textreadc(t *Text, q int) rune {
   139  	var r [1]rune
   140  	if t.Cq0 <= q && q < t.Cq0+len(t.Cache) {
   141  		r[0] = t.Cache[q-t.Cq0]
   142  	} else {
   143  		t.File.Read(q, r[:])
   144  	}
   145  	return r[0]
   146  }
   147  
   148  func textredraw(t *Text, r draw.Rectangle, f *draw.Font, b *draw.Image, odx int) {
   149  	t.Fr.Init(r, f, b, t.Fr.Cols[:])
   150  	rr := t.Fr.R
   151  	rr.Min.X -= adraw.Scrollwid() + adraw.Scrollgap() // back fill to scroll bar
   152  	if t.Fr.NoRedraw == 0 {
   153  		t.Fr.B.Draw(rr, t.Fr.Cols[frame.BACK], nil, draw.ZP)
   154  	}
   155  	// use no wider than 3-space tabs in a directory
   156  	maxt := MaxTab
   157  	if t.What == Body {
   158  		if t.W.IsDir {
   159  			maxt = util.Min(TABDIR, MaxTab)
   160  		} else {
   161  			maxt = t.Tabstop
   162  		}
   163  	}
   164  	t.Fr.MaxTab = maxt * f.StringWidth("0")
   165  	if t.What == Body && t.W.IsDir && odx != t.All.Dx() {
   166  		if t.Fr.MaxLines > 0 {
   167  			Textreset(t)
   168  			Textcolumnate(t, t.W.Dlp)
   169  			Textshow(t, 0, 0, true)
   170  		}
   171  	} else {
   172  		Textfill(t)
   173  		Textsetselect(t, t.Q0, t.Q1)
   174  	}
   175  }
   176  
   177  func Textfill(t *Text) {
   178  	if t.Fr.LastLineFull || t.Nofill {
   179  		return
   180  	}
   181  	if len(t.Cache) > 0 {
   182  		Typecommit(t)
   183  	}
   184  	rp := bufs.AllocRunes()
   185  	for {
   186  		n := t.Len() - (t.Org + t.Fr.NumChars)
   187  		if n == 0 {
   188  			break
   189  		}
   190  		if n > 2000 { // educated guess at reasonable amount
   191  			n = 2000
   192  		}
   193  		t.File.Read(t.Org+t.Fr.NumChars, rp[:n])
   194  		/*
   195  		 * it's expensive to frinsert more than we need, so
   196  		 * count newlines.
   197  		 */
   198  		nl := t.Fr.MaxLines - t.Fr.NumLines
   199  		m := 0
   200  		var i int
   201  		for i = 0; i < n; {
   202  			tmp25 := i
   203  			i++
   204  			if rp[tmp25] == '\n' {
   205  				m++
   206  				if m >= nl {
   207  					break
   208  				}
   209  			}
   210  		}
   211  		t.Fr.Insert(rp[:i], t.Fr.NumChars)
   212  		if t.Fr.LastLineFull {
   213  			break
   214  		}
   215  	}
   216  	bufs.FreeRunes(rp)
   217  }
   218  
   219  func Textresize(t *Text, r draw.Rectangle, keepextra bool) int {
   220  	if r.Dy() <= 0 {
   221  		r.Max.Y = r.Min.Y
   222  	} else if !keepextra {
   223  		r.Max.Y -= r.Dy() % t.Fr.Font.Height
   224  	}
   225  	odx := t.All.Dx()
   226  	t.All = r
   227  	t.ScrollR = r
   228  	t.ScrollR.Max.X = r.Min.X + adraw.Scrollwid()
   229  	t.lastsr = draw.ZR
   230  	r.Min.X += adraw.Scrollwid() + adraw.Scrollgap()
   231  	t.Fr.Clear(false)
   232  	textredraw(t, r, t.Fr.Font, t.Fr.B, odx)
   233  	if keepextra && t.Fr.R.Max.Y < t.All.Max.Y && t.Fr.NoRedraw == 0 {
   234  		// draw background in bottom fringe of window
   235  		r.Min.X -= adraw.Scrollgap()
   236  		r.Min.Y = t.Fr.R.Max.Y
   237  		r.Max.Y = t.All.Max.Y
   238  		adraw.Display.ScreenImage.Draw(r, t.Fr.Cols[frame.BACK], nil, draw.ZP)
   239  	}
   240  	return t.All.Max.Y
   241  }
   242  
   243  func Textcolumnate(t *Text, dlp []*Dirlist) {
   244  	if len(t.File.Text) > 1 {
   245  		return
   246  	}
   247  	mint := t.Fr.Font.StringWidth("0")
   248  	// go for narrower tabs if set more than 3 wide
   249  	t.Fr.MaxTab = util.Min(MaxTab, TABDIR) * mint
   250  	maxt := t.Fr.MaxTab
   251  	colw := 0
   252  	var i int
   253  	var w int
   254  	var dl *Dirlist
   255  	for i = 0; i < len(dlp); i++ {
   256  		dl = dlp[i]
   257  		w = dl.Wid
   258  		if maxt-w%maxt < mint || w%maxt == 0 {
   259  			w += mint
   260  		}
   261  		if w%maxt != 0 {
   262  			w += maxt - (w % maxt)
   263  		}
   264  		if w > colw {
   265  			colw = w
   266  		}
   267  	}
   268  	var ncol int
   269  	if colw == 0 {
   270  		ncol = 1
   271  	} else {
   272  		ncol = util.Max(1, t.Fr.R.Dx()/colw)
   273  	}
   274  	nrow := (len(dlp) + ncol - 1) / ncol
   275  
   276  	q1 := 0
   277  	for i = 0; i < nrow; i++ {
   278  		for j := i; j < len(dlp); j += nrow {
   279  			dl = dlp[j]
   280  			t.File.Insert(q1, dl.R)
   281  			q1 += len(dl.R)
   282  			if j+nrow >= len(dlp) {
   283  				break
   284  			}
   285  			w = dl.Wid
   286  			if maxt-w%maxt < mint {
   287  				t.File.Insert(q1, []rune("\t"))
   288  				q1++
   289  				w += mint
   290  			}
   291  			for {
   292  				t.File.Insert(q1, []rune("\t"))
   293  				q1++
   294  				w += maxt - (w % maxt)
   295  				if w >= colw {
   296  					break
   297  				}
   298  			}
   299  		}
   300  		t.File.Insert(q1, []rune("\n"))
   301  		q1++
   302  	}
   303  }
   304  
   305  func Textinsert(t *Text, q0 int, r []rune, tofile bool) {
   306  	if tofile && len(t.Cache) > 0 {
   307  		util.Fatal("text.insert")
   308  	}
   309  	if len(r) == 0 {
   310  		return
   311  	}
   312  	if tofile {
   313  		t.File.Insert(q0, r)
   314  		if t.What == Body {
   315  			t.W.Dirty = true
   316  			t.W.Utflastqid = -1
   317  		}
   318  		if len(t.File.Text) > 1 {
   319  			for i := 0; i < len(t.File.Text); i++ {
   320  				u := t.File.Text[i]
   321  				if u != t {
   322  					u.W.Dirty = true // always a body
   323  					Textinsert(u, q0, r, false)
   324  					Textsetselect(u, u.Q0, u.Q1)
   325  					Textscrdraw(u)
   326  				}
   327  			}
   328  		}
   329  
   330  	}
   331  	if q0 < t.IQ1 {
   332  		t.IQ1 += len(r)
   333  	}
   334  	if q0 < t.Q1 {
   335  		t.Q1 += len(r)
   336  	}
   337  	if q0 < t.Q0 {
   338  		t.Q0 += len(r)
   339  	}
   340  	if q0 < t.Org {
   341  		t.Org += len(r)
   342  	} else if q0 <= t.Org+t.Fr.NumChars {
   343  		t.Fr.Insert(r, q0-t.Org)
   344  	}
   345  	if t.W != nil {
   346  		c := 'i'
   347  		if t.What == Body {
   348  			c = 'I'
   349  		}
   350  		if len(r) <= EVENTSIZE {
   351  			Winevent(t.W, "%c%d %d 0 %d %s\n", c, q0, q0+len(r), len(r), string(r))
   352  		} else {
   353  			Winevent(t.W, "%c%d %d 0 0 \n", c, q0, q0+len(r))
   354  		}
   355  	}
   356  }
   357  
   358  func Textdelete(t *Text, q0 int, q1 int, tofile bool) {
   359  	if tofile && len(t.Cache) > 0 {
   360  		util.Fatal("text.delete")
   361  	}
   362  	n := q1 - q0
   363  	if n == 0 {
   364  		return
   365  	}
   366  	if tofile {
   367  		t.File.Delete(q0, q1)
   368  		if t.What == Body {
   369  			t.W.Dirty = true
   370  			t.W.Utflastqid = -1
   371  		}
   372  		if len(t.File.Text) > 1 {
   373  			for i := 0; i < len(t.File.Text); i++ {
   374  				u := t.File.Text[i]
   375  				if u != t {
   376  					u.W.Dirty = true // always a body
   377  					Textdelete(u, q0, q1, false)
   378  					Textsetselect(u, u.Q0, u.Q1)
   379  					Textscrdraw(u)
   380  				}
   381  			}
   382  		}
   383  	}
   384  	if q0 < t.IQ1 {
   385  		t.IQ1 -= util.Min(n, t.IQ1-q0)
   386  	}
   387  	if q0 < t.Q0 {
   388  		t.Q0 -= util.Min(n, t.Q0-q0)
   389  	}
   390  	if q0 < t.Q1 {
   391  		t.Q1 -= util.Min(n, t.Q1-q0)
   392  	}
   393  	if q1 <= t.Org {
   394  		t.Org -= n
   395  	} else if q0 < t.Org+t.Fr.NumChars {
   396  		p1 := q1 - t.Org
   397  		if p1 > t.Fr.NumChars {
   398  			p1 = t.Fr.NumChars
   399  		}
   400  		var p0 int
   401  		if q0 < t.Org {
   402  			t.Org = q0
   403  			p0 = 0
   404  		} else {
   405  			p0 = q0 - t.Org
   406  		}
   407  		t.Fr.Delete(p0, p1)
   408  		Textfill(t)
   409  	}
   410  	if t.W != nil {
   411  		c := 'd'
   412  		if t.What == Body {
   413  			c = 'D'
   414  		}
   415  		Winevent(t.W, "%c%d %d 0 0 \n", c, q0, q1)
   416  	}
   417  }
   418  
   419  func Textcommit(t *Text, tofile bool) {
   420  	if len(t.Cache) == 0 {
   421  		return
   422  	}
   423  	if tofile {
   424  		t.File.Insert(t.Cq0, t.Cache)
   425  	}
   426  	if t.What == Body {
   427  		t.W.Dirty = true
   428  		t.W.Utflastqid = -1
   429  	}
   430  	t.Cache = t.Cache[:0]
   431  }
   432  
   433  func Typecommit(t *Text) {
   434  	if t.W != nil {
   435  		Wincommit(t.W, t)
   436  	} else {
   437  		Textcommit(t, true)
   438  	}
   439  }
   440  
   441  func Textbsinsert(t *Text, q0 int, r []rune, tofile bool, nrp *int) int {
   442  	if t.What == Tag { // can't happen but safety first: mustn't backspace over file name
   443  		goto Err
   444  	}
   445  
   446  	for i := 0; i < len(r); i++ {
   447  		if r[i] == '\b' {
   448  			initial := 0
   449  			tp := make([]rune, len(r))
   450  			copy(tp, r[:i])
   451  			ti := i
   452  			for ; i < len(r); i++ {
   453  				tp[ti] = r[i]
   454  				if tp[ti] == '\b' {
   455  					if ti == 0 {
   456  						initial++
   457  					} else {
   458  						ti--
   459  					}
   460  				} else {
   461  					ti++
   462  				}
   463  			}
   464  			if initial != 0 {
   465  				if initial > q0 {
   466  					initial = q0
   467  				}
   468  				q0 -= initial
   469  				Textdelete(t, q0, q0+initial, tofile)
   470  			}
   471  			Textinsert(t, q0, tp[:ti], tofile)
   472  			*nrp = ti
   473  			return q0
   474  		}
   475  	}
   476  
   477  Err:
   478  	Textinsert(t, q0, r, tofile)
   479  	*nrp = len(r)
   480  	return q0
   481  }
   482  
   483  func Textbswidth(t *Text, c rune) int {
   484  	// there is known to be at least one character to erase
   485  	if c == 0x08 { // ^H: erase character
   486  		return 1
   487  	}
   488  	q := t.Q0
   489  	skipping := true
   490  	for q > 0 {
   491  		r := t.RuneAt(q - 1)
   492  		if r == '\n' { // eat at most one more character
   493  			if q == t.Q0 { // eat the newline
   494  				q--
   495  			}
   496  			break
   497  		}
   498  		if c == 0x17 {
   499  			eq := runes.IsAlphaNum(r)
   500  			if eq && skipping { // found one; stop skipping
   501  				skipping = false
   502  			} else if !eq && !skipping {
   503  				break
   504  			}
   505  		}
   506  		q--
   507  	}
   508  	return t.Q0 - q
   509  }
   510  
   511  func Textfilewidth(t *Text, q0 int, oneelement bool) int {
   512  	q := q0
   513  	for q > 0 {
   514  		r := t.RuneAt(q - 1)
   515  		if r <= ' ' {
   516  			break
   517  		}
   518  		if oneelement && r == '/' {
   519  			break
   520  		}
   521  		q--
   522  	}
   523  	return q0 - q
   524  }
   525  
   526  func Textshow(t *Text, q0 int, q1 int, doselect bool) {
   527  	if t.What != Body {
   528  		if doselect {
   529  			Textsetselect(t, q0, q1)
   530  		}
   531  		return
   532  	}
   533  	if t.W != nil && t.Fr.MaxLines == 0 {
   534  		Colgrow(t.Col, t.W, 1)
   535  	}
   536  	if doselect {
   537  		Textsetselect(t, q0, q1)
   538  	}
   539  	qe := t.Org + t.Fr.NumChars
   540  	tsd := false // do we call textscrdraw?
   541  	nc := t.Len() + len(t.Cache)
   542  	if t.Org <= q0 {
   543  		if nc == 0 || q0 < qe {
   544  			tsd = true
   545  		} else if q0 == qe && qe == nc {
   546  			if t.RuneAt(nc-1) == '\n' {
   547  				if t.Fr.NumLines < t.Fr.MaxLines {
   548  					tsd = true
   549  				}
   550  			} else {
   551  				tsd = true
   552  			}
   553  		}
   554  	}
   555  	if tsd {
   556  		Textscrdraw(t)
   557  	} else {
   558  		var nl int
   559  		if t.W.External {
   560  			nl = 3 * t.Fr.MaxLines / 4
   561  		} else {
   562  			nl = t.Fr.MaxLines / 4
   563  		}
   564  		q := Textbacknl(t, q0, nl)
   565  		// avoid going backwards if trying to go forwards - long lines!
   566  		if !(q0 > t.Org) || !(q < t.Org) {
   567  			Textsetorigin(t, q, true)
   568  		}
   569  		for q0 > t.Org+t.Fr.NumChars {
   570  			Textsetorigin(t, t.Org+1, false)
   571  		}
   572  	}
   573  }
   574  
   575  func Textbacknl(t *Text, p int, n int) int {
   576  	// look for start of this line if n==0
   577  	if n == 0 && p > 0 && t.RuneAt(p-1) != '\n' {
   578  		n = 1
   579  	}
   580  	i := n
   581  	for {
   582  		tmp29 := i
   583  		i--
   584  		if !(tmp29 > 0) || !(p > 0) {
   585  			break
   586  		}
   587  		p-- // it's at a newline now; back over it
   588  		if p == 0 {
   589  			break
   590  		}
   591  		// at 128 chars, call it a line anyway
   592  		for j := 128; ; p-- {
   593  			j--
   594  			if !(j > 0) || !(p > 0) {
   595  				break
   596  			}
   597  			if t.RuneAt(p-1) == '\n' {
   598  				break
   599  			}
   600  		}
   601  	}
   602  	return p
   603  }
   604  
   605  func Textsetorigin(t *Text, org int, exact bool) {
   606  	if org > 0 && !exact && t.RuneAt(org-1) != '\n' {
   607  		// org is an estimate of the char posn; find a newline
   608  		// don't try harder than 256 chars
   609  		for i := 0; i < 256 && org < t.Len(); i++ {
   610  			if t.RuneAt(org) == '\n' {
   611  				org++
   612  				break
   613  			}
   614  			org++
   615  		}
   616  	}
   617  	a := org - t.Org
   618  	fixup := 0
   619  	if a >= 0 && a < t.Fr.NumChars {
   620  		t.Fr.Delete(0, a)
   621  		fixup = 1 // frdelete can leave end of last line in wrong selection mode; it doesn't know what follows
   622  	} else if a < 0 && -a < t.Fr.NumChars {
   623  		n := t.Org - org
   624  		r := make([]rune, n)
   625  		t.File.Read(org, r)
   626  		t.Fr.Insert(r, 0)
   627  	} else {
   628  		t.Fr.Delete(0, t.Fr.NumChars)
   629  	}
   630  	t.Org = org
   631  	Textfill(t)
   632  	Textscrdraw(t)
   633  	Textsetselect(t, t.Q0, t.Q1)
   634  	if fixup != 0 && t.Fr.P1 > t.Fr.P0 {
   635  		t.Fr.Drawsel(t.Fr.PointOf(t.Fr.P1-1), t.Fr.P1-1, t.Fr.P1, true)
   636  	}
   637  }
   638  
   639  func Region(a int, b int) int {
   640  	if a < b {
   641  		return -1
   642  	}
   643  	if a == b {
   644  		return 0
   645  	}
   646  	return 1
   647  }
   648  
   649  func Selrestore(f *frame.Frame, pt0 draw.Point, p0 int, p1 int) {
   650  	if p1 <= f.P0 || p0 >= f.P1 {
   651  		// no overlap
   652  		f.Drawsel0(pt0, p0, p1, f.Cols[frame.BACK], f.Cols[frame.TEXT])
   653  		return
   654  	}
   655  	if p0 >= f.P0 && p1 <= f.P1 {
   656  		// entirely inside
   657  		f.Drawsel0(pt0, p0, p1, f.Cols[frame.HIGH], f.Cols[frame.HTEXT])
   658  		return
   659  	}
   660  
   661  	// they now are known to overlap
   662  
   663  	// before selection
   664  	if p0 < f.P0 {
   665  		f.Drawsel0(pt0, p0, f.P0, f.Cols[frame.BACK], f.Cols[frame.TEXT])
   666  		p0 = f.P0
   667  		pt0 = f.PointOf(p0)
   668  	}
   669  	// after selection
   670  	if p1 > f.P1 {
   671  		f.Drawsel0(f.PointOf(f.P1), f.P1, p1, f.Cols[frame.BACK], f.Cols[frame.TEXT])
   672  		p1 = f.P1
   673  	}
   674  	// inside selection
   675  	f.Drawsel0(pt0, p0, p1, f.Cols[frame.HIGH], f.Cols[frame.HTEXT])
   676  }
   677  
   678  func Textsetselect(t *Text, q0 int, q1 int) {
   679  	// t->fr.p0 and t->fr.p1 are always right; t->q0 and t->q1 may be off
   680  	t.Q0 = q0
   681  	t.Q1 = q1
   682  	// compute desired p0,p1 from q0,q1
   683  	p0 := q0 - t.Org
   684  	p1 := q1 - t.Org
   685  	ticked := true
   686  	if p0 < 0 {
   687  		ticked = false
   688  		p0 = 0
   689  	}
   690  	if p1 < 0 {
   691  		p1 = 0
   692  	}
   693  	if p0 > t.Fr.NumChars {
   694  		p0 = t.Fr.NumChars
   695  	}
   696  	if p1 > t.Fr.NumChars {
   697  		ticked = false
   698  		p1 = t.Fr.NumChars
   699  	}
   700  	if p0 == t.Fr.P0 && p1 == t.Fr.P1 {
   701  		if p0 == p1 && ticked != t.Fr.Ticked {
   702  			t.Fr.Tick(t.Fr.PointOf(p0), ticked)
   703  		}
   704  		return
   705  	}
   706  	if p0 > p1 {
   707  		panic(fmt.Sprintf("acme: textsetselect p0=%d p1=%d q0=%d q1=%d t->org=%d nchars=%d", p0, p1, q0, q1, int(t.Org), int(t.Fr.NumChars)))
   708  	}
   709  	// screen disagrees with desired selection
   710  	if t.Fr.P1 <= p0 || p1 <= t.Fr.P0 || p0 == p1 || t.Fr.P1 == t.Fr.P0 {
   711  		// no overlap or too easy to bother trying
   712  		t.Fr.Drawsel(t.Fr.PointOf(t.Fr.P0), t.Fr.P0, t.Fr.P1, false)
   713  		if p0 != p1 || ticked {
   714  			t.Fr.Drawsel(t.Fr.PointOf(p0), p0, p1, true)
   715  		}
   716  		goto Return
   717  	}
   718  	// overlap; avoid unnecessary painting
   719  	if p0 < t.Fr.P0 {
   720  		// extend selection backwards
   721  		t.Fr.Drawsel(t.Fr.PointOf(p0), p0, t.Fr.P0, true)
   722  	} else if p0 > t.Fr.P0 {
   723  		// trim first part of selection
   724  		t.Fr.Drawsel(t.Fr.PointOf(t.Fr.P0), t.Fr.P0, p0, false)
   725  	}
   726  	if p1 > t.Fr.P1 {
   727  		// extend selection forwards
   728  		t.Fr.Drawsel(t.Fr.PointOf(t.Fr.P1), t.Fr.P1, p1, true)
   729  	} else if p1 < t.Fr.P1 {
   730  		// trim last part of selection
   731  		t.Fr.Drawsel(t.Fr.PointOf(p1), p1, t.Fr.P1, false)
   732  	}
   733  
   734  Return:
   735  	t.Fr.P0 = p0
   736  	t.Fr.P1 = p1
   737  }
   738  
   739  var (
   740  	left  = [][]rune{[]rune("{[(<«"), []rune("\n"), []rune("'\"`")}
   741  	right = [][]rune{[]rune("}])>»"), []rune("\n"), []rune("'\"`")}
   742  )
   743  
   744  func Textdoubleclick(t *Text, q0 *int, q1 *int) {
   745  	if textclickhtmlmatch(t, q0, q1) != 0 {
   746  		return
   747  	}
   748  
   749  	for i := 0; i < len(left); i++ {
   750  		q := *q0
   751  		l := left[i]
   752  		r := right[i]
   753  		var c rune
   754  		// try matching character to left, looking right
   755  		if q == 0 {
   756  			c = '\n'
   757  		} else {
   758  			c = t.RuneAt(q - 1)
   759  		}
   760  		pi := runes.IndexRune(l, c)
   761  		if pi >= 0 {
   762  			if textclickmatch(t, c, r[pi], 1, &q) {
   763  				if c != '\n' {
   764  					q--
   765  				}
   766  				*q1 = q
   767  			}
   768  			return
   769  		}
   770  		// try matching character to right, looking left
   771  		if q == t.Len() {
   772  			c = '\n'
   773  		} else {
   774  			c = t.RuneAt(q)
   775  		}
   776  		pi = runes.IndexRune(r, c)
   777  		if pi >= 0 {
   778  			if textclickmatch(t, c, l[pi], -1, &q) {
   779  				*q1 = *q0
   780  				if *q0 < t.Len() && c == '\n' {
   781  					(*q1)++
   782  				}
   783  				*q0 = q
   784  				if c != '\n' || q != 0 || t.RuneAt(0) == '\n' {
   785  					(*q0)++
   786  				}
   787  			}
   788  			return
   789  		}
   790  	}
   791  
   792  	// try filling out word to right
   793  	for *q1 < t.Len() && runes.IsAlphaNum(t.RuneAt(*q1)) {
   794  		(*q1)++
   795  	}
   796  	// try filling out word to left
   797  	for *q0 > 0 && runes.IsAlphaNum(t.RuneAt(*q0-1)) {
   798  		(*q0)--
   799  	}
   800  }
   801  
   802  func textclickmatch(t *Text, cl rune, cr rune, dir int, q *int) bool {
   803  	nest := 1
   804  	for {
   805  		var c rune
   806  		if dir > 0 {
   807  			if *q == t.Len() {
   808  				break
   809  			}
   810  			c = t.RuneAt(*q)
   811  			(*q)++
   812  		} else {
   813  			if *q == 0 {
   814  				break
   815  			}
   816  			(*q)--
   817  			c = t.RuneAt(*q)
   818  		}
   819  		if c == cr {
   820  			nest--
   821  			if nest == 0 {
   822  				return true
   823  			}
   824  		} else if c == cl {
   825  			nest++
   826  		}
   827  	}
   828  	return cl == '\n' && nest == 1
   829  }
   830  
   831  func textclickhtmlmatch(t *Text, q0 *int, q1 *int) int {
   832  	q := *q0
   833  	var depth int
   834  	var n int
   835  	var nq int
   836  	// after opening tag?  scan forward for closing tag
   837  	if ishtmlend(t, q, nil) == 1 {
   838  		depth = 1
   839  		for q < t.Len() {
   840  			n = ishtmlstart(t, q, &nq)
   841  			if n != 0 {
   842  				depth += n
   843  				if depth == 0 {
   844  					*q1 = q
   845  					return 1
   846  				}
   847  				q = nq
   848  				continue
   849  			}
   850  			q++
   851  		}
   852  	}
   853  
   854  	// before closing tag?  scan backward for opening tag
   855  	if ishtmlstart(t, q, nil) == -1 {
   856  		depth = -1
   857  		for q > 0 {
   858  			n = ishtmlend(t, q, &nq)
   859  			if n != 0 {
   860  				depth += n
   861  				if depth == 0 {
   862  					*q0 = q
   863  					return 1
   864  				}
   865  				q = nq
   866  				continue
   867  			}
   868  			q--
   869  		}
   870  	}
   871  
   872  	return 0
   873  }
   874  
   875  // Is the text starting at location q an html tag?
   876  // Return 1 for <a>, -1 for </a>, 0 for no tag or <a />.
   877  // Set *q1, if non-nil, to the location after the tag.
   878  func ishtmlstart(t *Text, q int, q1 *int) int {
   879  	if q+2 > t.Len() {
   880  		return 0
   881  	}
   882  	tmp28 := q
   883  	q++
   884  	if t.RuneAt(tmp28) != '<' {
   885  		return 0
   886  	}
   887  	c := t.RuneAt(q)
   888  	q++
   889  	c1 := c
   890  	c2 := c
   891  	for c != '>' {
   892  		if q >= t.Len() {
   893  			return 0
   894  		}
   895  		c2 = c
   896  		c = t.RuneAt(q)
   897  		q++
   898  	}
   899  	if q1 != nil {
   900  		*q1 = q
   901  	}
   902  	if c1 == '/' { // closing tag
   903  		return -1
   904  	}
   905  	if c2 == '/' || c2 == '!' { // open + close tag or comment
   906  		return 0
   907  	}
   908  	return 1
   909  }
   910  
   911  // Is the text ending at location q an html tag?
   912  // Return 1 for <a>, -1 for </a>, 0 for no tag or <a />.
   913  // Set *q0, if non-nil, to the start of the tag.
   914  func ishtmlend(t *Text, q int, q0 *int) int {
   915  	if q < 2 {
   916  		return 0
   917  	}
   918  	q--
   919  	if t.RuneAt(q) != '>' {
   920  		return 0
   921  	}
   922  	q--
   923  	c := t.RuneAt(q)
   924  	c1 := c
   925  	c2 := c
   926  	for c != '<' {
   927  		if q == 0 {
   928  			return 0
   929  		}
   930  		c1 = c
   931  		q--
   932  		c = t.RuneAt(q)
   933  	}
   934  	if q0 != nil {
   935  		*q0 = q
   936  	}
   937  	if c1 == '/' { // closing tag
   938  		return -1
   939  	}
   940  	if c2 == '/' || c2 == '!' { // open + close tag or comment
   941  		return 0
   942  	}
   943  	return 1
   944  }
   945  
   946  func fileaddtext(f *File, t *Text) *File {
   947  	if f == nil {
   948  		f = &File{File: new(file.File)}
   949  		f.File.SetView((*fileView)(f))
   950  		f.Unread = true
   951  	}
   952  	f.Text = append(f.Text, t)
   953  	f.Curtext = t
   954  	return f
   955  }
   956  
   957  func filedeltext(f *File, t *Text) {
   958  	var i int
   959  	for i = 0; i < len(f.Text); i++ {
   960  		if f.Text[i] == t {
   961  			goto Found
   962  		}
   963  	}
   964  	util.Fatal("can't find text in filedeltext")
   965  
   966  Found:
   967  	copy(f.Text[i:], f.Text[i+1:])
   968  	f.Text = f.Text[:len(f.Text)-1]
   969  	if len(f.Text) == 0 {
   970  		f.Close()
   971  		return
   972  	}
   973  	if f.Curtext == t {
   974  		f.Curtext = f.Text[0]
   975  	}
   976  }
   977  
   978  func Dirname(t *Text, r []rune) []rune {
   979  	if t == nil || t.W == nil {
   980  		goto Rescue
   981  	}
   982  	{
   983  		nt := t.W.Tag.Len()
   984  		if nt == 0 {
   985  			goto Rescue
   986  		}
   987  		if len(r) >= 1 && r[0] == '/' {
   988  			goto Rescue
   989  		}
   990  		b, i := parsetag(t.W, len(r))
   991  		slash := -1
   992  		for i--; i >= 0; i-- {
   993  			if b[i] == '/' {
   994  				slash = i
   995  				break
   996  			}
   997  		}
   998  		if slash < 0 {
   999  			goto Rescue
  1000  		}
  1001  		b = append(b[:slash+1], r...)
  1002  		return runes.CleanPath(b)
  1003  	}
  1004  
  1005  Rescue:
  1006  	tmp := r
  1007  	if len(r) >= 1 {
  1008  		return runes.CleanPath(tmp)
  1009  	}
  1010  	return tmp
  1011  }