9fans.net/go@v0.0.7/cmd/acme/internal/ui/text.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 <complete.h>
    13  // #include "dat.h"
    14  // #include "fns.h"
    15  
    16  package ui
    17  
    18  import (
    19  	"time"
    20  
    21  	"9fans.net/go/cmd/acme/internal/adraw"
    22  	"9fans.net/go/cmd/acme/internal/file"
    23  	"9fans.net/go/cmd/acme/internal/util"
    24  	"9fans.net/go/cmd/acme/internal/wind"
    25  	"9fans.net/go/draw"
    26  	"9fans.net/go/draw/frame"
    27  )
    28  
    29  var Textcomplete func(*wind.Text) []rune
    30  
    31  func Textconstrain(t *wind.Text, q0 int, q1 int, p0 *int, p1 *int) {
    32  	*p0 = util.Min(q0, t.Len())
    33  	*p1 = util.Min(q1, t.Len())
    34  }
    35  
    36  func Texttype(t *wind.Text, r rune) {
    37  	if t.What != wind.Body && t.What != wind.Tag && r == '\n' {
    38  		return
    39  	}
    40  	if t.What == wind.Tag {
    41  		t.W.Tagsafe = false
    42  	}
    43  
    44  	var q0 int
    45  	var nnb int
    46  	var n int
    47  	switch r {
    48  	case draw.KeyLeft:
    49  		wind.Typecommit(t)
    50  		if t.Q0 > 0 {
    51  			wind.Textshow(t, t.Q0-1, t.Q0-1, true)
    52  		}
    53  		return
    54  	case draw.KeyRight:
    55  		wind.Typecommit(t)
    56  		if t.Q1 < t.Len() {
    57  			wind.Textshow(t, t.Q1+1, t.Q1+1, true)
    58  		}
    59  		return
    60  	case draw.KeyDown, draw.KeyPageDown, Kscrollonedown:
    61  		if t.What == wind.Tag {
    62  			// expand tag to show all text
    63  			if !t.W.Tagexpand {
    64  				t.W.Tagexpand = true
    65  				WinresizeAndMouse(t.W, t.W.R, false, true)
    66  			}
    67  			return
    68  		}
    69  		switch r {
    70  		case draw.KeyDown:
    71  			n = t.Fr.MaxLines / 3
    72  		case draw.KeyPageDown:
    73  			n = 2 * t.Fr.MaxLines / 3
    74  		case Kscrollonedown:
    75  			n = draw.MouseScrollSize(t.Fr.MaxLines)
    76  			if n <= 0 {
    77  				n = 1
    78  			}
    79  		}
    80  		q0 = t.Org + t.Fr.CharOf(draw.Pt(t.Fr.R.Min.X, t.Fr.R.Min.Y+n*t.Fr.Font.Height))
    81  		wind.Textsetorigin(t, q0, true)
    82  		return
    83  	case draw.KeyUp, draw.KeyPageUp, Kscrolloneup:
    84  		if t.What == wind.Tag {
    85  			// shrink tag to single line
    86  			if t.W.Tagexpand {
    87  				t.W.Tagexpand = false
    88  				t.W.Taglines = 1
    89  				WinresizeAndMouse(t.W, t.W.R, false, true)
    90  			}
    91  			return
    92  		}
    93  		switch r {
    94  		case draw.KeyUp:
    95  			n = t.Fr.MaxLines / 3
    96  		case draw.KeyPageUp:
    97  			n = 2 * t.Fr.MaxLines / 3
    98  		case Kscrolloneup:
    99  			n = draw.MouseScrollSize(t.Fr.MaxLines)
   100  		}
   101  		q0 = wind.Textbacknl(t, t.Org, n)
   102  		wind.Textsetorigin(t, q0, true)
   103  		return
   104  	case draw.KeyHome:
   105  		wind.Typecommit(t)
   106  		if t.Org > t.IQ1 {
   107  			q0 = wind.Textbacknl(t, t.IQ1, 1)
   108  			wind.Textsetorigin(t, q0, true)
   109  		} else {
   110  			wind.Textshow(t, 0, 0, false)
   111  		}
   112  		return
   113  	case draw.KeyEnd:
   114  		wind.Typecommit(t)
   115  		if t.IQ1 > t.Org+t.Fr.NumChars {
   116  			if t.IQ1 > t.Len() {
   117  				// should not happen, but does. and it will crash textbacknl.
   118  				t.IQ1 = t.Len()
   119  			}
   120  			q0 = wind.Textbacknl(t, t.IQ1, 1)
   121  			wind.Textsetorigin(t, q0, true)
   122  		} else {
   123  			wind.Textshow(t, t.Len(), t.Len(), false)
   124  		}
   125  		return
   126  	case 0x01: // ^A: beginning of line
   127  		wind.Typecommit(t)
   128  		// go to where ^U would erase, if not already at BOL
   129  		nnb = 0
   130  		if t.Q0 > 0 && t.RuneAt(t.Q0-1) != '\n' {
   131  			nnb = wind.Textbswidth(t, 0x15)
   132  		}
   133  		wind.Textshow(t, t.Q0-nnb, t.Q0-nnb, true)
   134  		return
   135  	case 0x05: // ^E: end of line
   136  		wind.Typecommit(t)
   137  		q0 = t.Q0
   138  		for q0 < t.Len() && t.RuneAt(q0) != '\n' {
   139  			q0++
   140  		}
   141  		wind.Textshow(t, q0, q0, true)
   142  		return
   143  	case draw.KeyCmd + 'c': // %C: copy
   144  		wind.Typecommit(t)
   145  		XCut(t, t, nil, true, false, nil)
   146  		return
   147  	case draw.KeyCmd + 'z': // %Z: undo
   148  		wind.Typecommit(t)
   149  		XUndo(t, nil, nil, true, false, nil)
   150  		return
   151  	case draw.KeyCmd + 'Z': // %-shift-Z: redo
   152  		wind.Typecommit(t)
   153  		XUndo(t, nil, nil, false, false, nil)
   154  		return
   155  	}
   156  	if t.What == wind.Body {
   157  		file.Seq++
   158  		t.File.Mark()
   159  	}
   160  	// cut/paste must be done after the seq++/filemark
   161  	switch r {
   162  	case draw.KeyCmd + 'x': // %X: cut
   163  		wind.Typecommit(t)
   164  		if t.What == wind.Body {
   165  			file.Seq++
   166  			t.File.Mark()
   167  		}
   168  		XCut(t, t, nil, true, true, nil)
   169  		wind.Textshow(t, t.Q0, t.Q0, true)
   170  		t.IQ1 = t.Q0
   171  		return
   172  	case draw.KeyCmd + 'v': // %V: paste
   173  		wind.Typecommit(t)
   174  		if t.What == wind.Body {
   175  			file.Seq++
   176  			t.File.Mark()
   177  		}
   178  		XPaste(t, t, nil, true, false, nil)
   179  		wind.Textshow(t, t.Q0, t.Q1, true)
   180  		t.IQ1 = t.Q1
   181  		return
   182  	}
   183  	if t.Q1 > t.Q0 {
   184  		if len(t.Cache) != 0 {
   185  			util.Fatal("text.type")
   186  		}
   187  		XCut(t, t, nil, true, true, nil)
   188  		t.Eq0 = ^0
   189  	}
   190  	wind.Textshow(t, t.Q0, t.Q0, true)
   191  	var q1 int
   192  	var nb int
   193  	var i int
   194  	var u *wind.Text
   195  	rp := []rune{r}
   196  	switch r {
   197  	case 0x06, // ^F: complete
   198  		draw.KeyInsert:
   199  		wind.Typecommit(t)
   200  		if Textcomplete == nil {
   201  			rp = nil
   202  		} else {
   203  			rp = Textcomplete(t)
   204  		}
   205  		if rp == nil {
   206  			return
   207  		}
   208  
   209  		// break to normal insertion case
   210  	case 0x1B:
   211  		if t.Eq0 != ^0 {
   212  			if t.Eq0 <= t.Q0 {
   213  				wind.Textsetselect(t, t.Eq0, t.Q0)
   214  			} else {
   215  				wind.Textsetselect(t, t.Q0, t.Eq0)
   216  			}
   217  		}
   218  		if len(t.Cache) > 0 {
   219  			wind.Typecommit(t)
   220  		}
   221  		t.IQ1 = t.Q0
   222  		return
   223  	case 0x08, // ^H: erase character
   224  		0x15, // ^U: erase line
   225  		0x17: // ^W: erase word
   226  		if t.Q0 == 0 { // nothing to erase
   227  			return
   228  		}
   229  		nnb = wind.Textbswidth(t, r)
   230  		q1 = t.Q0
   231  		q0 = q1 - nnb
   232  		// if selection is at beginning of window, avoid deleting invisible text
   233  		if q0 < t.Org {
   234  			q0 = t.Org
   235  			nnb = q1 - q0
   236  		}
   237  		if nnb <= 0 {
   238  			return
   239  		}
   240  		for i = 0; i < len(t.File.Text); i++ {
   241  			u = t.File.Text[i]
   242  			u.Nofill = true
   243  			nb = nnb
   244  			n = len(u.Cache)
   245  			if n > 0 {
   246  				if q1 != u.Cq0+n {
   247  					util.Fatal("text.type backspace")
   248  				}
   249  				if n > nb {
   250  					n = nb
   251  				}
   252  				u.Cache = u.Cache[:len(u.Cache)-n]
   253  				wind.Textdelete(u, q1-n, q1, false)
   254  				nb -= n
   255  			}
   256  			if u.Eq0 == q1 || u.Eq0 == ^0 {
   257  				u.Eq0 = q0
   258  			}
   259  			if nb != 0 && u == t {
   260  				wind.Textdelete(u, q0, q0+nb, true)
   261  			}
   262  			if u != t {
   263  				wind.Textsetselect(u, u.Q0, u.Q1)
   264  			} else {
   265  				wind.Textsetselect(t, q0, q0)
   266  			}
   267  			u.Nofill = false
   268  		}
   269  		for i = 0; i < len(t.File.Text); i++ {
   270  			wind.Textfill(t.File.Text[i])
   271  		}
   272  		t.IQ1 = t.Q0
   273  		return
   274  	case '\n':
   275  		if t.W.Autoindent {
   276  			// find beginning of previous line using backspace code
   277  			nnb = wind.Textbswidth(t, 0x15) // ^U case
   278  			rp = make([]rune, 1, nnb+1)
   279  			rp[0] = '\n'
   280  			for i = 0; i < nnb; i++ {
   281  				r = t.RuneAt(t.Q0 - nnb + i)
   282  				if r != ' ' && r != '\t' {
   283  					break
   284  				}
   285  				rp = append(rp, r)
   286  			}
   287  		}
   288  		// break to normal code
   289  	}
   290  	// otherwise ordinary character; just insert, typically in caches of all texts
   291  	for i = 0; i < len(t.File.Text); i++ {
   292  		u = t.File.Text[i]
   293  		if u.Eq0 == ^0 {
   294  			u.Eq0 = t.Q0
   295  		}
   296  		if len(u.Cache) == 0 {
   297  			u.Cq0 = t.Q0
   298  		} else if t.Q0 != u.Cq0+len(u.Cache) {
   299  			util.Fatal("text.type cq1")
   300  		}
   301  		/*
   302  		 * Change the tag before we add to ncache,
   303  		 * so that if the window body is resized the
   304  		 * commit will not find anything in ncache.
   305  		 */
   306  		if u.What == wind.Body && len(u.Cache) == 0 {
   307  			u.Needundo = true
   308  			wind.Winsettag(t.W)
   309  			u.Needundo = false
   310  		}
   311  		wind.Textinsert(u, t.Q0, rp, false)
   312  		if u != t {
   313  			wind.Textsetselect(u, u.Q0, u.Q1)
   314  		}
   315  		u.Cache = append(u.Cache, rp...)
   316  	}
   317  	wind.Textsetselect(t, t.Q0+len(rp), t.Q0+len(rp))
   318  	if r == '\n' && t.W != nil {
   319  		wind.Wincommit(t.W, t)
   320  	}
   321  	t.IQ1 = t.Q0
   322  }
   323  
   324  var clicktext *wind.Text
   325  
   326  var clickmsec uint32
   327  
   328  var selecttext *wind.Text
   329  
   330  var selectq int
   331  
   332  /*
   333   * called from frame library
   334   */
   335  func framescroll(f *frame.Frame, dl int) {
   336  	if f != &selecttext.Fr {
   337  		util.Fatal("frameselect not right frame")
   338  	}
   339  	textframescroll(selecttext, dl)
   340  }
   341  
   342  func textframescroll(t *wind.Text, dl int) {
   343  	if dl == 0 {
   344  		scrsleep(100 * time.Millisecond)
   345  		return
   346  	}
   347  	var q0 int
   348  	if dl < 0 {
   349  		q0 = wind.Textbacknl(t, t.Org, -dl)
   350  		if selectq > t.Org+t.Fr.P0 {
   351  			wind.Textsetselect(t, t.Org+t.Fr.P0, selectq)
   352  		} else {
   353  			wind.Textsetselect(t, selectq, t.Org+t.Fr.P0)
   354  		}
   355  	} else {
   356  		if t.Org+t.Fr.NumChars == t.Len() {
   357  			return
   358  		}
   359  		q0 = t.Org + t.Fr.CharOf(draw.Pt(t.Fr.R.Min.X, t.Fr.R.Min.Y+dl*t.Fr.Font.Height))
   360  		if selectq > t.Org+t.Fr.P1 {
   361  			wind.Textsetselect(t, t.Org+t.Fr.P1, selectq)
   362  		} else {
   363  			wind.Textsetselect(t, selectq, t.Org+t.Fr.P1)
   364  		}
   365  	}
   366  	wind.Textsetorigin(t, q0, true)
   367  }
   368  
   369  func Textselect(t *wind.Text) {
   370  	const (
   371  		None = iota
   372  		Cut
   373  		Paste
   374  	)
   375  
   376  	selecttext = t
   377  	/*
   378  	 * To have double-clicking and chording, we double-click
   379  	 * immediately if it might make sense.
   380  	 */
   381  	b := Mouse.Buttons
   382  	q0 := t.Q0
   383  	q1 := t.Q1
   384  	selectq = t.Org + t.Fr.CharOf(Mouse.Point)
   385  	if clicktext == t && Mouse.Msec-clickmsec < 500 {
   386  		if q0 == q1 && selectq == q0 {
   387  			wind.Textdoubleclick(t, &q0, &q1)
   388  			wind.Textsetselect(t, q0, q1)
   389  			adraw.Display.Flush()
   390  			x := Mouse.Point.X
   391  			y := Mouse.Point.Y
   392  			// stay here until something interesting happens
   393  			for {
   394  				Mousectl.Read()
   395  				if !(Mouse.Buttons == b && util.Abs(Mouse.Point.X-x) < 3) || !(util.Abs(Mouse.Point.Y-y) < 3) {
   396  					break
   397  				}
   398  			}
   399  			Mouse.Point.X = x // in case we're calling frselect
   400  			Mouse.Point.Y = y
   401  			q0 = t.Q0 // may have changed
   402  			q1 = t.Q1
   403  			selectq = q0
   404  		}
   405  	}
   406  	if Mouse.Buttons == b {
   407  		t.Fr.Scroll = framescroll
   408  		t.Fr.Select(Mousectl)
   409  		// horrible botch: while asleep, may have lost selection altogether
   410  		if selectq > t.Len() {
   411  			selectq = t.Org + t.Fr.P0
   412  		}
   413  		t.Fr.Scroll = nil
   414  		if selectq < t.Org {
   415  			q0 = selectq
   416  		} else {
   417  			q0 = t.Org + t.Fr.P0
   418  		}
   419  		if selectq > t.Org+t.Fr.NumChars {
   420  			q1 = selectq
   421  		} else {
   422  			q1 = t.Org + t.Fr.P1
   423  		}
   424  	}
   425  	if q0 == q1 {
   426  		if q0 == t.Q0 && clicktext == t && Mouse.Msec-clickmsec < 500 {
   427  			wind.Textdoubleclick(t, &q0, &q1)
   428  			clicktext = nil
   429  		} else {
   430  			clicktext = t
   431  			clickmsec = Mouse.Msec
   432  		}
   433  	} else {
   434  		clicktext = nil
   435  	}
   436  	wind.Textsetselect(t, q0, q1)
   437  	adraw.Display.Flush()
   438  	state := None // what we've done; undo when possible
   439  	for Mouse.Buttons != 0 {
   440  		Mouse.Msec = 0
   441  		b = Mouse.Buttons
   442  		if b&1 != 0 && b&6 != 0 {
   443  			if state == None && t.What == wind.Body {
   444  				file.Seq++
   445  				t.W.Body.File.Mark()
   446  			}
   447  			if b&2 != 0 {
   448  				if state == Paste && t.What == wind.Body {
   449  					wind.Winundo(t.W, true)
   450  					wind.Textsetselect(t, q0, t.Q1)
   451  					state = None
   452  				} else if state != Cut {
   453  					XCut(t, t, nil, true, true, nil)
   454  					state = Cut
   455  				}
   456  			} else {
   457  				if state == Cut && t.What == wind.Body {
   458  					wind.Winundo(t.W, true)
   459  					wind.Textsetselect(t, q0, t.Q1)
   460  					state = None
   461  				} else if state != Paste {
   462  					XPaste(t, t, nil, true, false, nil)
   463  					state = Paste
   464  				}
   465  			}
   466  			wind.Textscrdraw(t)
   467  			Clearmouse()
   468  		}
   469  		adraw.Display.Flush()
   470  
   471  		// Mousectl.Read does both the Flush
   472  		// and the receive. We did the flush.
   473  		// Do just the receive, dropping biglock
   474  		// to let other goroutines proceed.
   475  		// Note that *Mouse is Mousectl.Mouse.
   476  		BigUnlock()
   477  		for Mouse.Buttons == b {
   478  			*Mouse = <-Mousectl.C
   479  		}
   480  		BigLock()
   481  		clicktext = nil
   482  	}
   483  }
   484  
   485  var BigLock = func(){}
   486  var BigUnlock = func(){}
   487  
   488  /*
   489   * Release the button in less than DELAY ms and it's considered a null selection
   490   * if the mouse hardly moved, regardless of whether it crossed a char boundary.
   491   */
   492  
   493  const (
   494  	DELAY   = 2
   495  	MINMOVE = 4
   496  )
   497  
   498  func xselect(f *frame.Frame, mc *draw.Mousectl, col *draw.Image, p1p *int) int {
   499  	mp := mc.Point
   500  	b := mc.Buttons
   501  	msec := mc.Msec
   502  
   503  	// remove tick
   504  	if f.P0 == f.P1 {
   505  		f.Tick(f.PointOf(f.P0), false)
   506  	}
   507  	p1 := f.CharOf(mp)
   508  	p0 := p1
   509  	pt0 := f.PointOf(p0)
   510  	pt1 := f.PointOf(p1)
   511  	reg := 0
   512  	f.Tick(pt0, true)
   513  	for {
   514  		q := f.CharOf(mc.Point)
   515  		if p1 != q {
   516  			if p0 == p1 {
   517  				f.Tick(pt0, false)
   518  			}
   519  			if reg != wind.Region(q, p0) { // crossed starting point; reset
   520  				if reg > 0 {
   521  					wind.Selrestore(f, pt0, p0, p1)
   522  				} else if reg < 0 {
   523  					wind.Selrestore(f, pt1, p1, p0)
   524  				}
   525  				p1 = p0
   526  				pt1 = pt0
   527  				reg = wind.Region(q, p0)
   528  				if reg == 0 {
   529  					f.Drawsel0(pt0, p0, p1, col, adraw.Display.White)
   530  				}
   531  			}
   532  			qt := f.PointOf(q)
   533  			if reg > 0 {
   534  				if q > p1 {
   535  					f.Drawsel0(pt1, p1, q, col, adraw.Display.White)
   536  				} else if q < p1 {
   537  					wind.Selrestore(f, qt, q, p1)
   538  				}
   539  			} else if reg < 0 {
   540  				if q > p1 {
   541  					wind.Selrestore(f, pt1, p1, q)
   542  				} else {
   543  					f.Drawsel0(qt, q, p1, col, adraw.Display.White)
   544  				}
   545  			}
   546  			p1 = q
   547  			pt1 = qt
   548  		}
   549  		if p0 == p1 {
   550  			f.Tick(pt0, true)
   551  		}
   552  		f.Display.Flush()
   553  		mc.Read()
   554  		if mc.Buttons != b {
   555  			break
   556  		}
   557  	}
   558  	if mc.Msec-msec < DELAY && p0 != p1 && util.Abs(mp.X-mc.X) < MINMOVE && util.Abs(mp.Y-mc.Y) < MINMOVE {
   559  		if reg > 0 {
   560  			wind.Selrestore(f, pt0, p0, p1)
   561  		} else if reg < 0 {
   562  			wind.Selrestore(f, pt1, p1, p0)
   563  		}
   564  		p1 = p0
   565  	}
   566  	if p1 < p0 {
   567  		tmp := p0
   568  		p0 = p1
   569  		p1 = tmp
   570  	}
   571  	pt0 = f.PointOf(p0)
   572  	if p0 == p1 {
   573  		f.Tick(pt0, false)
   574  	}
   575  	wind.Selrestore(f, pt0, p0, p1)
   576  	// restore tick
   577  	if f.P0 == f.P1 {
   578  		f.Tick(f.PointOf(f.P0), true)
   579  	}
   580  	f.Display.Flush()
   581  	*p1p = p1
   582  	return p0
   583  }
   584  
   585  func textselect23(t *wind.Text, q0 *int, q1 *int, high *draw.Image, mask int) int {
   586  	var p1 int
   587  	p0 := xselect(&t.Fr, Mousectl, high, &p1)
   588  	buts := Mousectl.Buttons
   589  	if buts&mask == 0 {
   590  		*q0 = p0 + t.Org
   591  		*q1 = p1 + t.Org
   592  	}
   593  
   594  	for Mousectl.Buttons != 0 {
   595  		Mousectl.Read()
   596  	}
   597  	return buts
   598  }
   599  
   600  func Textselect2(t *wind.Text, q0 *int, q1 *int, tp **wind.Text) int {
   601  	*tp = nil
   602  	buts := textselect23(t, q0, q1, adraw.Button2Color, 4)
   603  	if buts&4 != 0 {
   604  		return 0
   605  	}
   606  	if buts&1 != 0 { // pick up argument
   607  		*tp = wind.Argtext
   608  		return 1
   609  	}
   610  	return 1
   611  }
   612  
   613  func Textselect3(t *wind.Text, q0 *int, q1 *int) bool {
   614  	return textselect23(t, q0, q1, adraw.Button3Color, 1|2) == 0
   615  }