9fans.net/go@v0.0.7/cmd/samterm/main.go (about)

     1  package main
     2  
     3  import (
     4  	"image"
     5  	"os"
     6  	"os/signal"
     7  	"strings"
     8  
     9  	"9fans.net/go/draw"
    10  )
    11  
    12  var (
    13  	cmd         Text
    14  	cursor      *draw.Cursor
    15  	which       *Flayer
    16  	work        *Flayer
    17  	snarflen    int
    18  	typestart   int  = -1
    19  	typeend     int  = -1
    20  	typeesc     int  = -1
    21  	modified    bool /* strange lookahead for menus */
    22  	hostlock    int  = 1
    23  	hasunlocked bool
    24  	maxtab      int = 8
    25  	chord       int
    26  	autoindent  bool
    27  	display     *draw.Display
    28  	screen      *draw.Image
    29  	font        *draw.Font
    30  	textID      int64
    31  	textByID    map[int64]*Text
    32  )
    33  
    34  const chording = false /* code here for reference but it causes deadlocks */
    35  
    36  func main() {
    37  	/*
    38  	 * sam is talking to us on fd 0 and 1.
    39  	 * move these elsewhere so that if we accidentally
    40  	 * use 0 and 1 in other code, nothing bad happens.
    41  	 */
    42  	hostfd[0] = os.Stdin
    43  	hostfd[1] = os.Stdout
    44  	os.Stdin, _ = os.Open(os.DevNull)
    45  	os.Stdout = os.Stderr
    46  
    47  	// ignore interrupt signals
    48  	signal.Notify(make(chan os.Signal), os.Interrupt)
    49  
    50  	if protodebug {
    51  		print("getscreen\n")
    52  	}
    53  	getscreen()
    54  	if protodebug {
    55  		print("iconinit\n")
    56  	}
    57  	iconinit()
    58  	if protodebug {
    59  		print("initio\n")
    60  	}
    61  	initio()
    62  	if protodebug {
    63  		print("scratch\n")
    64  	}
    65  	r := screen.R
    66  	r.Max.Y = r.Min.Y + r.Dy()/5
    67  	if protodebug {
    68  		print("flstart\n")
    69  	}
    70  	flstart(screen.Clipr)
    71  	rinit(&cmd.rasp)
    72  	flnew(&cmd.l[0], gettext, &cmd)
    73  	flinit(&cmd.l[0], r, font, cmdcols[:])
    74  	textID++
    75  	cmd.id = textID
    76  	textByID = make(map[int64]*Text)
    77  	textByID[cmd.id] = &cmd
    78  	cmd.nwin = 1
    79  	which = &cmd.l[0]
    80  	cmd.tag = Untagged
    81  	outTs(Tversion, VERSION)
    82  	startnewfile(Tstartcmdfile, &cmd)
    83  
    84  	got := 0
    85  	if protodebug {
    86  		print("loop\n")
    87  	}
    88  	for ; ; got = waitforio() {
    89  		if hasunlocked && RESIZED() {
    90  			resize()
    91  		}
    92  		if got&(1<<RHost) != 0 {
    93  			rcv()
    94  		}
    95  		if got&(1<<RPlumb) != 0 {
    96  			var i int
    97  			for i = 0; cmd.l[i].textfn == nil; i++ {
    98  			}
    99  			current(&cmd.l[i])
   100  			flsetselect(which, cmd.rasp.nrunes, cmd.rasp.nrunes)
   101  			ktype(which, RPlumb)
   102  		}
   103  		if got&(1<<RKeyboard) != 0 {
   104  			if which != nil {
   105  				ktype(which, RKeyboard)
   106  			} else {
   107  				kbdblock()
   108  			}
   109  		}
   110  		if got&(1<<RMouse) != 0 {
   111  			if hostlock == 2 || !mousep.Point.In(screen.R) {
   112  				mouseunblock()
   113  				continue
   114  			}
   115  			nwhich := flwhich(mousep.Point)
   116  			scr := which != nil && mousep.Point.In(which.scroll)
   117  			if mousep.Buttons != 0 {
   118  				flushtyping(true)
   119  			}
   120  			if chording && chord == 1 && mousep.Buttons == 0 {
   121  				chord = 0
   122  			}
   123  			if chording && chord != 0 {
   124  				chord |= mousep.Buttons
   125  			} else if mousep.Buttons&1 != 0 {
   126  				if nwhich != nil {
   127  					if nwhich != which {
   128  						current(nwhich)
   129  					} else if scr {
   130  						scroll(which, 1)
   131  					} else {
   132  						t := which.text
   133  						if flselect(which) {
   134  							outTsl(Tdclick, t.tag, which.p0)
   135  							t.lock++
   136  						} else if t != &cmd {
   137  							outcmd()
   138  						}
   139  						if mousep.Buttons&1 != 0 {
   140  							chord = mousep.Buttons
   141  						}
   142  					}
   143  				}
   144  			} else if mousep.Buttons&2 != 0 && which != nil {
   145  				if scr {
   146  					scroll(which, 2)
   147  				} else {
   148  					menu2hit()
   149  				}
   150  			} else if mousep.Buttons&4 != 0 {
   151  				if scr {
   152  					scroll(which, 3)
   153  				} else {
   154  					menu3hit()
   155  				}
   156  			}
   157  			mouseunblock()
   158  		}
   159  		if chording && chord != 0 {
   160  			t := which.text
   161  			if t.lock == 0 && hostlock == 0 {
   162  				w := t.find(which)
   163  				if chord&2 != 0 {
   164  					cut(t, w, true, true)
   165  					chord &= ^2
   166  				} else if chord&4 != 0 {
   167  					paste(t, w)
   168  					chord &= ^4
   169  				}
   170  			}
   171  		}
   172  	}
   173  }
   174  
   175  func (t *Text) find(l *Flayer) int {
   176  	w := 0
   177  	for &t.l[w] != l {
   178  		w++
   179  	}
   180  	return w
   181  }
   182  
   183  func resize() {
   184  	flresize(screen.Clipr)
   185  	for _, t := range text {
   186  		if t != nil {
   187  			hcheck(t.tag)
   188  		}
   189  	}
   190  }
   191  
   192  func current(nw *Flayer) {
   193  	if which != nil {
   194  		flborder(which, false)
   195  	}
   196  	if nw != nil {
   197  		flushtyping(true)
   198  		flupfront(nw)
   199  		flborder(nw, true)
   200  		buttons(Up)
   201  		t := nw.text
   202  		t.front = t.find(nw)
   203  		if t != &cmd {
   204  			work = nw
   205  		}
   206  	}
   207  	which = nw
   208  }
   209  
   210  func closeup(l *Flayer) {
   211  	t := l.text
   212  	m := whichmenu(t.tag)
   213  	if m < 0 {
   214  		return
   215  	}
   216  	flclose(l)
   217  	if l == which {
   218  		which = nil
   219  		current(flwhich(image.Pt(0, 0)))
   220  	}
   221  	if l == work {
   222  		work = nil
   223  	}
   224  	t.nwin--
   225  	if t.nwin == 0 {
   226  		rclear(&t.rasp)
   227  		delete(textByID, t.id)
   228  		free(t)
   229  		text[m] = nil
   230  	} else if l == &t.l[t.front] {
   231  		for m = 0; m < NL; m++ { /* find one; any one will do */
   232  			if t.l[m].textfn != nil {
   233  				t.front = m
   234  				return
   235  			}
   236  		}
   237  		panic("close")
   238  	}
   239  }
   240  
   241  func findl(t *Text) *Flayer {
   242  	for i := 0; i < NL; i++ {
   243  		if t.l[i].textfn == nil {
   244  			return &t.l[i]
   245  		}
   246  	}
   247  	return nil
   248  }
   249  
   250  func duplicate(l *Flayer, r image.Rectangle, f *draw.Font, close bool) {
   251  	t := l.text
   252  	nl := findl(t)
   253  	if nl != nil {
   254  		flnew(nl, gettext, t)
   255  		flinit(nl, r, f, l.f.Cols[:])
   256  		nl.origin = l.origin
   257  		rp := l.textfn(l, l.f.NumChars)
   258  		flinsert(nl, rp, l.origin)
   259  		flsetselect(nl, l.p0, l.p1)
   260  		if close {
   261  			flclose(l)
   262  			if l == which {
   263  				which = nil
   264  			}
   265  		} else {
   266  			t.nwin++
   267  		}
   268  		current(nl)
   269  		hcheck(t.tag)
   270  	}
   271  	display.SwitchCursor(cursor)
   272  }
   273  
   274  func buttons(updown int) {
   275  	for (mousep.Buttons&7 != 0) != (updown == Down) {
   276  		getmouse()
   277  	}
   278  }
   279  
   280  func getr(rp *image.Rectangle) bool {
   281  	*rp = draw.SweepRect(3, mousectl)
   282  	if rp.Max.X != 0 && rp.Max.X-rp.Min.X <= 5 && rp.Max.Y-rp.Min.Y <= 5 {
   283  		p := rp.Min
   284  		r := cmd.l[cmd.front].entire
   285  		*rp = screen.R
   286  		if cmd.nwin == 1 {
   287  			if p.Y <= r.Min.Y {
   288  				rp.Max.Y = r.Min.Y
   289  			} else if p.Y >= r.Max.Y {
   290  				rp.Min.Y = r.Max.Y
   291  			}
   292  			if p.X <= r.Min.X {
   293  				rp.Max.X = r.Min.X
   294  			} else if p.X >= r.Max.X {
   295  				rp.Min.X = r.Max.X
   296  			}
   297  		}
   298  	}
   299  	return draw.RectClip(rp, screen.R) && rp.Max.X-rp.Min.X > 100 && rp.Max.Y-rp.Min.Y > 40
   300  }
   301  
   302  func snarf(t *Text, w int) {
   303  	l := &t.l[w]
   304  	if l.p1 > l.p0 {
   305  		snarflen = l.p1 - l.p0
   306  		outTsll(Tsnarf, t.tag, l.p0, l.p1)
   307  	}
   308  }
   309  
   310  func cut(t *Text, w int, save bool, check bool) {
   311  	l := &t.l[w]
   312  	p0 := l.p0
   313  	p1 := l.p1
   314  	if p0 == p1 {
   315  		return
   316  	}
   317  	if p0 < 0 {
   318  		panic("cut")
   319  	}
   320  	if save {
   321  		snarf(t, w)
   322  	}
   323  	outTsll(Tcut, t.tag, p0, p1)
   324  	flsetselect(l, p0, p0)
   325  	t.lock++
   326  	hcut(t.tag, p0, p1-p0)
   327  	if check {
   328  		hcheck(t.tag)
   329  	}
   330  }
   331  
   332  func paste(t *Text, w int) {
   333  	if snarflen != 0 {
   334  		cut(t, w, false, false)
   335  		t.lock++
   336  		outTsl(Tpaste, t.tag, t.l[w].p0)
   337  	}
   338  }
   339  
   340  func scrorigin(l *Flayer, but int, p0 int) {
   341  	t := l.text
   342  	switch but {
   343  	case 1:
   344  		outTsll(Torigin, t.tag, l.origin, p0)
   345  	case 2:
   346  		outTsll(Torigin, t.tag, p0, 1)
   347  	case 3:
   348  		horigin(t.tag, p0)
   349  	}
   350  }
   351  
   352  func alnum(c rune) bool {
   353  	/*
   354  	 * Hard to get absolutely right.  Use what we know about ASCII
   355  	 * and assume anything above the Latin control characters is
   356  	 * potentially an alphanumeric.
   357  	 */
   358  	if c <= ' ' {
   359  		return false
   360  	}
   361  	if 0x7F <= c && c <= 0xA0 {
   362  		return false
   363  	}
   364  	if strings.ContainsRune("!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~", c) {
   365  		return false
   366  	}
   367  	return true
   368  }
   369  
   370  func raspc(r *Rasp, p int) rune {
   371  	return rload(r, p, p+1)[0]
   372  }
   373  
   374  func ctlw(r *Rasp, o int, p int) int {
   375  	p--
   376  	if p < o {
   377  		return o
   378  	}
   379  	if raspc(r, p) == '\n' {
   380  		return p
   381  	}
   382  	for ; p >= o; p-- {
   383  		c := raspc(r, p)
   384  		if alnum(c) {
   385  			break
   386  		}
   387  		if c == '\n' {
   388  			return p + 1
   389  		}
   390  	}
   391  	for ; p > o && alnum(raspc(r, p-1)); p-- {
   392  	}
   393  	if p >= o {
   394  		return p
   395  	}
   396  	return o
   397  }
   398  
   399  func ctlu(r *Rasp, o int, p int) int {
   400  	p--
   401  	if p < o {
   402  		return o
   403  	}
   404  	if raspc(r, p) == '\n' {
   405  		return p
   406  	}
   407  	for ; p-1 >= o && raspc(r, p-1) != '\n'; p-- {
   408  	}
   409  	if p >= o {
   410  		return p
   411  	}
   412  	return o
   413  }
   414  
   415  func center(l *Flayer, a int) bool {
   416  	t := l.text
   417  	if t.lock == 0 && (a < l.origin || l.origin+l.f.NumChars < a) {
   418  		if a > t.rasp.nrunes {
   419  			a = t.rasp.nrunes
   420  		}
   421  		outTsll(Torigin, t.tag, a, 2)
   422  		return true
   423  	}
   424  	return false
   425  }
   426  
   427  func thirds(l *Flayer, a int, n int) bool {
   428  	t := l.text
   429  	if t.lock == 0 && (a < l.origin || l.origin+l.f.NumChars < a) {
   430  		if a > t.rasp.nrunes {
   431  			a = t.rasp.nrunes
   432  		}
   433  		s := l.scroll.Inset(1)
   434  		lines := (n*(s.Max.Y-s.Min.Y)/l.f.Font.Height + 1) / 3
   435  		if lines < 2 {
   436  			lines = 2
   437  		}
   438  		outTsll(Torigin, t.tag, a, lines)
   439  		return true
   440  	}
   441  	return false
   442  }
   443  
   444  func onethird(l *Flayer, a int) bool {
   445  	return thirds(l, a, 1)
   446  }
   447  
   448  func twothirds(l *Flayer, a int) bool {
   449  	return thirds(l, a, 2)
   450  }
   451  
   452  func flushtyping(clearesc bool) {
   453  	if clearesc {
   454  		typeesc = -1
   455  	}
   456  	if typestart == typeend {
   457  		modified = false
   458  		return
   459  	}
   460  	t := which.text
   461  	if t != &cmd {
   462  		modified = true
   463  	}
   464  	rp := rload(&t.rasp, typestart, typeend)
   465  	if t == &cmd && typeend == t.rasp.nrunes && rp[len(rp)-1] == '\n' {
   466  		setlock()
   467  		outcmd()
   468  	}
   469  	outTslS(Ttype, t.tag, typestart, rp)
   470  	typestart = -1
   471  	typeend = -1
   472  }
   473  
   474  const (
   475  	BACKSCROLLKEY = draw.KeyUp
   476  	ENDKEY        = draw.KeyEnd
   477  	ESC           = '\x1B'
   478  	HOMEKEY       = draw.KeyHome
   479  	LEFTARROW     = draw.KeyLeft
   480  	LINEEND       = '\x05'
   481  	LINESTART     = '\x01'
   482  	PAGEDOWN      = draw.KeyPageDown
   483  	PAGEUP        = draw.KeyPageUp
   484  	RIGHTARROW    = draw.KeyRight
   485  	SCROLLKEY     = draw.KeyDown
   486  	CUT           = draw.KeyCmd + 'x'
   487  	COPY          = draw.KeyCmd + 'c'
   488  	PASTE         = draw.KeyCmd + 'v'
   489  )
   490  
   491  func nontypingkey(c rune) bool {
   492  	switch c {
   493  	case BACKSCROLLKEY,
   494  		ENDKEY,
   495  		HOMEKEY,
   496  		LEFTARROW,
   497  		LINEEND,
   498  		LINESTART,
   499  		PAGEDOWN,
   500  		PAGEUP,
   501  		RIGHTARROW,
   502  		SCROLLKEY,
   503  		CUT,
   504  		COPY,
   505  		PASTE:
   506  		return true
   507  	}
   508  	return false
   509  }
   510  
   511  var kinput = make([]rune, 0, 100)
   512  
   513  func ktype(l *Flayer, res Resource) {
   514  	t := l.text
   515  	scrollkey := false
   516  	if res == RKeyboard {
   517  		scrollkey = nontypingkey(qpeekc()) /* ICK */
   518  	}
   519  
   520  	if hostlock != 0 || t.lock != 0 {
   521  		kbdblock()
   522  		return
   523  	}
   524  	a := l.p0
   525  	if a != l.p1 && !scrollkey {
   526  		flushtyping(true)
   527  		cut(t, t.front, true, true)
   528  		return /* it may now be locked */
   529  	}
   530  	backspacing := 0
   531  	kinput = kinput[:0]
   532  	var c rune
   533  	for {
   534  		c = kbdchar()
   535  		if c <= 0 {
   536  			break
   537  		}
   538  		if res == RKeyboard {
   539  			if nontypingkey(c) || c == ESC {
   540  				break
   541  			}
   542  			/* backspace, ctrl-u, ctrl-w, del */
   543  			if c == '\b' || c == 0x15 || c == 0x17 || c == 0x7F {
   544  				backspacing = 1
   545  				break
   546  			}
   547  		}
   548  		kinput = append(kinput, c)
   549  		if autoindent {
   550  			if c == '\n' {
   551  				cursor := ctlu(&t.rasp, 0, a+len(kinput)-1)
   552  				for len(kinput) < cap(kinput) {
   553  					ch := raspc(&t.rasp, cursor)
   554  					cursor++
   555  					if ch == ' ' || ch == '\t' {
   556  						kinput = append(kinput, ch)
   557  					} else {
   558  						break
   559  					}
   560  				}
   561  			}
   562  		}
   563  		if c == '\n' || len(kinput) == cap(kinput) {
   564  			break
   565  		}
   566  	}
   567  	if len(kinput) > 0 {
   568  		if typestart < 0 {
   569  			typestart = a
   570  		}
   571  		if typeesc < 0 {
   572  			typeesc = a
   573  		}
   574  		hgrow(t.tag, a, len(kinput), 0)
   575  		t.lock++ /* pretend we Trequest'ed for hdatarune*/
   576  		hdatarune(t.tag, a, kinput)
   577  		a += len(kinput)
   578  		l.p0 = a
   579  		l.p1 = a
   580  		typeend = a
   581  		if c == '\n' || typeend-typestart > 100 {
   582  			flushtyping(false)
   583  		}
   584  		onethird(l, a)
   585  	}
   586  	if c == SCROLLKEY || c == PAGEDOWN {
   587  		flushtyping(false)
   588  		center(l, l.origin+l.f.NumChars+1)
   589  	} else if c == BACKSCROLLKEY || c == PAGEUP {
   590  		flushtyping(false)
   591  		a0 := l.origin - l.f.NumChars
   592  		if a0 < 0 {
   593  			a0 = 0
   594  		}
   595  		center(l, a0)
   596  	} else if c == RIGHTARROW {
   597  		flushtyping(false)
   598  		a0 := l.p0
   599  		if a0 < t.rasp.nrunes {
   600  			a0++
   601  		}
   602  		flsetselect(l, a0, a0)
   603  		center(l, a0)
   604  	} else if c == LEFTARROW {
   605  		flushtyping(false)
   606  		a0 := l.p0
   607  		if a0 > 0 {
   608  			a0--
   609  		}
   610  		flsetselect(l, a0, a0)
   611  		center(l, a0)
   612  	} else if c == HOMEKEY {
   613  		flushtyping(false)
   614  		center(l, 0)
   615  	} else if c == ENDKEY {
   616  		flushtyping(false)
   617  		center(l, t.rasp.nrunes)
   618  	} else if c == LINESTART || c == LINEEND {
   619  		flushtyping(true)
   620  		if c == LINESTART {
   621  			for a > 0 && raspc(&t.rasp, a-1) != '\n' {
   622  				a--
   623  			}
   624  		} else {
   625  			for a < t.rasp.nrunes && raspc(&t.rasp, a) != '\n' {
   626  				a++
   627  			}
   628  		}
   629  		l.p1 = a
   630  		l.p0 = l.p1
   631  		for i := 0; i < NL; i++ {
   632  			l := &t.l[i]
   633  			if l.textfn != nil {
   634  				flsetselect(l, l.p0, l.p1)
   635  			}
   636  		}
   637  	} else if backspacing != 0 && hostlock == 0 {
   638  		/* backspacing immediately after outcmd(): sorry */
   639  		if l.f.P0 > 0 && a > 0 {
   640  			switch c {
   641  			case '\b',
   642  				0x7F: /* del */
   643  				l.p0 = a - 1
   644  			case 0x15: /* ctrl-u */
   645  				l.p0 = ctlu(&t.rasp, l.origin, a)
   646  			case 0x17: /* ctrl-w */
   647  				l.p0 = ctlw(&t.rasp, l.origin, a)
   648  			}
   649  			l.p1 = a
   650  			if l.p1 != l.p0 {
   651  				/* cut locally if possible */
   652  				if typestart <= l.p0 && l.p1 <= typeend {
   653  					t.lock++ /* to call hcut */
   654  					hcut(t.tag, l.p0, l.p1-l.p0)
   655  					/* hcheck is local because we know rasp is contiguous */
   656  					hcheck(t.tag)
   657  				} else {
   658  					flushtyping(false)
   659  					cut(t, t.front, false, true)
   660  				}
   661  			}
   662  			if typeesc >= l.p0 {
   663  				typeesc = l.p0
   664  			}
   665  			if typestart >= 0 {
   666  				if typestart >= l.p0 {
   667  					typestart = l.p0
   668  				}
   669  				typeend = l.p0
   670  				if typestart == typeend {
   671  					typestart = -1
   672  					typeend = -1
   673  					modified = false
   674  				}
   675  			}
   676  		}
   677  	} else {
   678  		if c == ESC && typeesc >= 0 {
   679  			l.p0 = typeesc
   680  			l.p1 = a
   681  			flushtyping(true)
   682  		}
   683  		for i := 0; i < NL; i++ {
   684  			l := &t.l[i]
   685  			if l.textfn != nil {
   686  				flsetselect(l, l.p0, l.p1)
   687  			}
   688  		}
   689  		switch c {
   690  		case CUT:
   691  			flushtyping(false)
   692  			cut(t, t.front, true, true)
   693  		case COPY:
   694  			flushtyping(false)
   695  			snarf(t, t.front)
   696  		case PASTE:
   697  			flushtyping(false)
   698  			paste(t, t.front)
   699  		}
   700  	}
   701  }
   702  
   703  func outcmd() {
   704  	if work != nil {
   705  		outTsll(Tworkfile, work.text.tag, work.p0, work.p1)
   706  	}
   707  }
   708  
   709  func gettext(l *Flayer, n int) []rune {
   710  	return rload(&l.text.rasp, l.origin, l.origin+n)
   711  }
   712  
   713  func scrtotal(l *Flayer) int {
   714  	return l.text.rasp.nrunes
   715  }