9fans.net/go@v0.0.5/cmd/acme/internal/edit/edit.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 "edit.h"
    14  // #include "fns.h"
    15  
    16  package edit
    17  
    18  import (
    19  	"fmt"
    20  	"runtime"
    21  	"strings"
    22  
    23  	"9fans.net/go/cmd/acme/internal/alog"
    24  	"9fans.net/go/cmd/acme/internal/bufs"
    25  	"9fans.net/go/cmd/acme/internal/util"
    26  	"9fans.net/go/cmd/acme/internal/wind"
    27  )
    28  
    29  var linex = "\n"
    30  var wordx = " \t\n"
    31  var cmdtab []Cmdtab
    32  
    33  type Cmdtab struct {
    34  	cmdc    rune
    35  	text    bool
    36  	regexp  bool
    37  	addr    bool
    38  	defcmd  rune
    39  	defaddr Defaddr
    40  	count   uint8
    41  	token   string
    42  	fn      func(*wind.Text, *Cmd) bool
    43  }
    44  
    45  func init() { cmdtab = cmdtab1 } // break init cycle
    46  var cmdtab1 = []Cmdtab{
    47  	//	cmdc	text	regexp	addr	defcmd	defaddr	count	token	 fn
    48  	Cmdtab{'\n', false, false, false, 0, aDot, 0, "", nl_cmd},
    49  	Cmdtab{'a', true, false, false, 0, aDot, 0, "", a_cmd},
    50  	Cmdtab{'b', false, false, false, 0, aNo, 0, linex, b_cmd},
    51  	Cmdtab{'c', true, false, false, 0, aDot, 0, "", c_cmd},
    52  	Cmdtab{'d', false, false, false, 0, aDot, 0, "", d_cmd},
    53  	Cmdtab{'e', false, false, false, 0, aNo, 0, wordx, e_cmd},
    54  	Cmdtab{'f', false, false, false, 0, aNo, 0, wordx, f_cmd},
    55  	Cmdtab{'g', false, true, false, 'p', aDot, 0, "", g_cmd},
    56  	Cmdtab{'i', true, false, false, 0, aDot, 0, "", i_cmd},
    57  	Cmdtab{'m', false, false, true, 0, aDot, 0, "", m_cmd},
    58  	Cmdtab{'p', false, false, false, 0, aDot, 0, "", p_cmd},
    59  	Cmdtab{'r', false, false, false, 0, aDot, 0, wordx, e_cmd},
    60  	Cmdtab{'s', false, true, false, 0, aDot, 1, "", s_cmd},
    61  	Cmdtab{'t', false, false, true, 0, aDot, 0, "", m_cmd},
    62  	Cmdtab{'u', false, false, false, 0, aNo, 2, "", u_cmd},
    63  	Cmdtab{'v', false, true, false, 'p', aDot, 0, "", g_cmd},
    64  	Cmdtab{'w', false, false, false, 0, aAll, 0, wordx, w_cmd},
    65  	Cmdtab{'x', false, true, false, 'p', aDot, 0, "", x_cmd},
    66  	Cmdtab{'y', false, true, false, 'p', aDot, 0, "", x_cmd},
    67  	Cmdtab{'=', false, false, false, 0, aDot, 0, linex, eq_cmd},
    68  	Cmdtab{'B', false, false, false, 0, aNo, 0, linex, B_cmd},
    69  	Cmdtab{'D', false, false, false, 0, aNo, 0, linex, D_cmd},
    70  	Cmdtab{'X', false, true, false, 'f', aNo, 0, "", X_cmd},
    71  	Cmdtab{'Y', false, true, false, 'f', aNo, 0, "", X_cmd},
    72  	Cmdtab{'<', false, false, false, 0, aDot, 0, linex, pipe_cmd},
    73  	Cmdtab{'|', false, false, false, 0, aDot, 0, linex, pipe_cmd},
    74  	Cmdtab{'>', false, false, false, 0, aDot, 0, linex, pipe_cmd},
    75  	/* deliberately unimplemented:
    76  	Cmdtab{'k', 0, 0, 0, 0, aDot, 0, "", k_cmd},
    77  	Cmdtab{'n', 0, 0, 0, 0, aNo, 0, "", n_cmd},
    78  	Cmdtab{'q', 0, 0, 0, 0, aNo, 0, "", q_cmd},
    79  		Cmdtab{'!',	0,	0,	0,	0,	aNo,	0,	linex,	plan9_cmd},
    80  	*/
    81  }
    82  
    83  var cmdstartp []rune
    84  var cmdp int // index into cmdstartp
    85  var editerrc chan string
    86  
    87  var lastpat *String
    88  var patset bool
    89  
    90  var cmdlist []*Cmd
    91  var addrlist []*Addr
    92  var stringlist []*String
    93  var curtext *wind.Text
    94  var Editing int = Inactive
    95  
    96  func editthread() {
    97  	for {
    98  		cmdp := parsecmd(0)
    99  		if cmdp == nil {
   100  			break
   101  		}
   102  		if !cmdexec(curtext, cmdp) {
   103  			break
   104  		}
   105  		freecmd()
   106  	}
   107  	editerrc <- ""
   108  }
   109  
   110  func allelogterm(w *wind.Window, x interface{}) {
   111  	if ef := elogfind(w.Body.File); ef != nil {
   112  		elogterm(ef)
   113  	}
   114  }
   115  
   116  func alleditinit(w *wind.Window, x interface{}) {
   117  	wind.Textcommit(&w.Tag, true)
   118  	wind.Textcommit(&w.Body, true)
   119  }
   120  
   121  func allupdate(w *wind.Window, x interface{}) {
   122  	t := &w.Body
   123  	if t.File.Curtext != t { // do curtext only
   124  		return
   125  	}
   126  	f := elogfind(t.File)
   127  	if f == nil {
   128  		return
   129  	}
   130  	if f.elog.typ == elogNull {
   131  		elogterm(f)
   132  	} else if f.elog.typ != elogEmpty {
   133  		elogapply(f)
   134  		if f.editclean {
   135  			f.SetMod(false)
   136  			for i := 0; i < len(f.Text); i++ {
   137  				f.Text[i].W.Dirty = false
   138  			}
   139  		}
   140  	}
   141  	wind.Textsetselect(t, t.Q0, t.Q1)
   142  	wind.Textscrdraw(t)
   143  	wind.Winsettag(w)
   144  }
   145  
   146  func editerror(format string, args ...interface{}) {
   147  	s := fmt.Sprintf(format, args...)
   148  	freecmd()
   149  	wind.All(allelogterm, nil) // truncate the edit logs
   150  	editerrc <- s
   151  	runtime.Goexit() // TODO(rsc)
   152  }
   153  
   154  func Editcmd(ct *wind.Text, r []rune) {
   155  	if len(r) == 0 {
   156  		return
   157  	}
   158  	if 2*len(r) > bufs.RuneLen { // TODO(rsc): why 2*len?
   159  		alog.Printf("string too long\n")
   160  		return
   161  	}
   162  
   163  	wind.All(alleditinit, nil)
   164  	cmdstartp = make([]rune, len(r), len(r)+1)
   165  	copy(cmdstartp, r)
   166  	if r[len(r)-1] != '\n' {
   167  		cmdstartp = append(cmdstartp, '\n')
   168  	}
   169  	cmdp = 0
   170  	if ct.W == nil {
   171  		curtext = nil
   172  	} else {
   173  		curtext = &ct.W.Body
   174  	}
   175  	resetxec()
   176  	if editerrc == nil {
   177  		editerrc = make(chan string)
   178  		lastpat = allocstring(0)
   179  	}
   180  	go editthread()
   181  	err := <-editerrc
   182  	Editing = Inactive
   183  	if err != "" {
   184  		alog.Printf("Edit: %s\n", err)
   185  	}
   186  
   187  	// update everyone whose edit log has data
   188  	wind.All(allupdate, nil)
   189  }
   190  
   191  func getch() rune {
   192  	if cmdp >= len(cmdstartp) {
   193  		return -1
   194  	}
   195  	r := cmdstartp[cmdp]
   196  	cmdp++
   197  	return r
   198  }
   199  
   200  func nextc() rune {
   201  	if cmdp >= len(cmdstartp) {
   202  		return -1
   203  	}
   204  	return cmdstartp[cmdp]
   205  }
   206  
   207  func ungetch() {
   208  	cmdp--
   209  	if cmdp < 0 {
   210  		util.Fatal("ungetch")
   211  	}
   212  }
   213  
   214  func getnum(signok int) int {
   215  	n := 0
   216  	sign := 1
   217  	if signok > 1 && nextc() == '-' {
   218  		sign = -1
   219  		getch()
   220  	}
   221  	c := nextc()
   222  	if c < '0' || '9' < c { // no number defaults to 1
   223  		return sign
   224  	}
   225  	for {
   226  		c = getch()
   227  		if !('0' <= c) || !(c <= '9') {
   228  			break
   229  		}
   230  		n = n*10 + int(c-'0')
   231  	}
   232  	ungetch()
   233  	return sign * n
   234  }
   235  
   236  func cmdskipbl() rune {
   237  	var c rune
   238  	for {
   239  		c = getch()
   240  		if !(c == ' ') && !(c == '\t') {
   241  			break
   242  		}
   243  	}
   244  	if c >= 0 {
   245  		ungetch()
   246  	}
   247  	return c
   248  }
   249  
   250  func allocstring(n int) *String {
   251  	s := new(String)
   252  	s.r = make([]rune, n, n+10)
   253  	return s
   254  }
   255  
   256  func freestring(s *String) {
   257  	s.r = nil
   258  }
   259  
   260  func newcmd() *Cmd {
   261  	p := new(Cmd)
   262  	cmdlist = append(cmdlist, p)
   263  	return p
   264  }
   265  
   266  func newstring(n int) *String {
   267  	p := allocstring(n)
   268  	stringlist = append(stringlist, p)
   269  	return p
   270  }
   271  
   272  func newaddr() *Addr {
   273  	p := new(Addr)
   274  	addrlist = append(addrlist, p)
   275  	return p
   276  }
   277  
   278  func freecmd() {
   279  	// free cmdlist[i]
   280  	// free addrlist[i]
   281  	// freestring stringlist[i]
   282  }
   283  
   284  func okdelim(c rune) {
   285  	if c == '\\' || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') {
   286  		editerror("bad delimiter %c\n", c)
   287  	}
   288  }
   289  
   290  func atnl() {
   291  	cmdskipbl()
   292  	c := getch()
   293  	if c != '\n' {
   294  		editerror("newline expected (saw %c)", c)
   295  	}
   296  }
   297  
   298  func Straddc(s *String, c rune) {
   299  	s.r = append(s.r, c)
   300  }
   301  
   302  func getrhs(s *String, delim, cmd rune) {
   303  	for {
   304  		c := getch()
   305  		if !(c > 0 && c != delim) || !(c != '\n') {
   306  			break
   307  		}
   308  		if c == '\\' {
   309  			c = getch()
   310  			if c <= 0 {
   311  				util.Fatal("bad right hand side")
   312  			}
   313  			if c == '\n' {
   314  				ungetch()
   315  				c = '\\'
   316  			} else if c == 'n' {
   317  				c = '\n'
   318  			} else if c != delim && (cmd == 's' || c != '\\') { // s does its own
   319  				Straddc(s, '\\')
   320  			}
   321  		}
   322  		Straddc(s, c)
   323  	}
   324  	ungetch() // let client read whether delimiter, '\n' or whatever
   325  }
   326  
   327  func collecttoken(end string) *String {
   328  	s := newstring(0)
   329  	var c rune
   330  	for {
   331  		c = nextc()
   332  		if !(c == ' ') && !(c == '\t') {
   333  			break
   334  		}
   335  		Straddc(s, getch()) // blanks significant for getname()
   336  	}
   337  	for {
   338  		c = getch()
   339  		if c <= 0 || strings.ContainsRune(end, c) {
   340  			break
   341  		}
   342  		Straddc(s, c)
   343  	}
   344  	if c != '\n' {
   345  		atnl()
   346  	}
   347  	return s
   348  }
   349  
   350  func collecttext() *String {
   351  	s := newstring(0)
   352  	if cmdskipbl() == '\n' {
   353  		getch()
   354  		i := 0
   355  		for {
   356  			begline := i
   357  			var c rune
   358  			for {
   359  				c = getch()
   360  				if !(c > 0) || !(c != '\n') {
   361  					break
   362  				}
   363  				i++
   364  				(func() { Straddc(s, c) }())
   365  			}
   366  			i++
   367  			(func() { Straddc(s, '\n') }())
   368  			if c < 0 {
   369  				goto Return
   370  			}
   371  			if !(s.r[begline] != '.') && !(s.r[begline+1] != '\n') {
   372  				break
   373  			}
   374  		}
   375  		s.r = s.r[:len(s.r)-2]
   376  	} else {
   377  		delim := getch()
   378  		okdelim(delim)
   379  		getrhs(s, delim, 'a')
   380  		if nextc() == delim {
   381  			getch()
   382  		}
   383  		atnl()
   384  	}
   385  Return:
   386  	return s
   387  }
   388  
   389  func cmdlookup(c rune) int {
   390  	for i := 0; i < len(cmdtab); i++ {
   391  		if cmdtab[i].cmdc == c {
   392  			return i
   393  		}
   394  	}
   395  	return -1
   396  }
   397  
   398  func parsecmd(nest int) *Cmd {
   399  	var cmd Cmd
   400  	cmd.u.cmd = nil
   401  	cmd.next = cmd.u.cmd
   402  	cmd.re = nil
   403  	cmd.num = 0
   404  	cmd.flag = false
   405  	cmd.addr = compoundaddr()
   406  	if cmdskipbl() == -1 {
   407  		return nil
   408  	}
   409  	c := getch()
   410  	if c == -1 {
   411  		return nil
   412  	}
   413  	cmd.cmdc = c
   414  	if cmd.cmdc == 'c' && nextc() == 'd' { // sleazy two-character case
   415  		getch() // the 'd'
   416  		cmd.cmdc = 'c' | 0x100
   417  	}
   418  	i := cmdlookup(cmd.cmdc)
   419  	var cp *Cmd
   420  	if i >= 0 {
   421  		if cmd.cmdc == '\n' {
   422  			goto Return // let nl_cmd work it all out
   423  		}
   424  		ct := &cmdtab[i]
   425  		if ct.defaddr == aNo && cmd.addr != nil {
   426  			editerror("command takes no address")
   427  		}
   428  		if ct.count != 0 {
   429  			cmd.num = getnum(int(ct.count))
   430  		}
   431  		if ct.regexp {
   432  			// x without pattern -> .*\n, indicated by cmd.re==0
   433  			// X without pattern is all files
   434  			if (ct.cmdc != 'x' && ct.cmdc != 'X') || func() bool { c = nextc(); return c != ' ' && c != '\t' && c != '\n' }() {
   435  				cmdskipbl()
   436  				c = getch()
   437  				if c == '\n' || c < 0 {
   438  					editerror("no address")
   439  				}
   440  				okdelim(c)
   441  				cmd.re = getregexp(c)
   442  				if ct.cmdc == 's' {
   443  					cmd.u.text = newstring(0)
   444  					getrhs(cmd.u.text, c, 's')
   445  					if nextc() == c {
   446  						getch()
   447  						if nextc() == 'g' {
   448  							getch()
   449  							cmd.flag = true
   450  						}
   451  					}
   452  
   453  				}
   454  			}
   455  		}
   456  		if ct.addr {
   457  			cmd.u.mtaddr = simpleaddr()
   458  			if cmd.u.mtaddr == nil {
   459  				editerror("bad address")
   460  			}
   461  		}
   462  		if ct.defcmd != 0 {
   463  			if cmdskipbl() == '\n' {
   464  				getch()
   465  				cmd.u.cmd = newcmd()
   466  				cmd.u.cmd.cmdc = ct.defcmd
   467  			} else {
   468  				cmd.u.cmd = parsecmd(nest)
   469  				if cmd.u.cmd == nil {
   470  					util.Fatal("defcmd")
   471  				}
   472  			}
   473  		} else if ct.text {
   474  			cmd.u.text = collecttext()
   475  		} else if ct.token != "" {
   476  			cmd.u.text = collecttoken(ct.token)
   477  		} else {
   478  			atnl()
   479  		}
   480  	} else {
   481  		var ncp *Cmd
   482  		switch cmd.cmdc {
   483  		case '{':
   484  			cp = nil
   485  			for {
   486  				if cmdskipbl() == '\n' {
   487  					getch()
   488  				}
   489  				ncp = parsecmd(nest + 1)
   490  				if cp != nil {
   491  					cp.next = ncp
   492  				} else {
   493  					cmd.u.cmd = ncp
   494  				}
   495  				cp = ncp
   496  				if cp == nil {
   497  					break
   498  				}
   499  			}
   500  		case '}':
   501  			atnl()
   502  			if nest == 0 {
   503  				editerror("right brace with no left brace")
   504  			}
   505  			return nil
   506  		default:
   507  			editerror("unknown command %c", cmd.cmdc)
   508  		}
   509  	}
   510  Return:
   511  	cp = newcmd()
   512  	*cp = cmd
   513  	return cp
   514  }
   515  
   516  func getregexp(delim rune) *String {
   517  	buf := allocstring(0)
   518  	var c rune
   519  	for i := 0; ; i++ {
   520  		c = getch()
   521  		if c == '\\' {
   522  			if nextc() == delim {
   523  				c = getch()
   524  			} else if nextc() == '\\' {
   525  				Straddc(buf, c)
   526  				c = getch()
   527  			}
   528  		} else if c == delim || c == '\n' {
   529  			break
   530  		}
   531  		if i >= bufs.RuneLen {
   532  			editerror("regular expression too long")
   533  		}
   534  		Straddc(buf, c)
   535  	}
   536  	if c != delim && c != 0 {
   537  		ungetch()
   538  	}
   539  	if len(buf.r) > 0 {
   540  		patset = true
   541  		freestring(lastpat)
   542  		lastpat = buf
   543  	} else {
   544  		freestring(buf)
   545  	}
   546  	if len(lastpat.r) == 0 {
   547  		editerror("no regular expression defined")
   548  	}
   549  	r := newstring(len(lastpat.r))
   550  	copy(r.r[:len(lastpat.r)], lastpat.r)
   551  	return r
   552  }
   553  
   554  func simpleaddr() *Addr {
   555  	var addr Addr
   556  	addr.num = 0
   557  	addr.next = nil
   558  	addr.u.left = nil
   559  	switch cmdskipbl() {
   560  	case '#':
   561  		addr.typ = getch()
   562  		addr.num = getnum(1)
   563  	case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
   564  		addr.num = getnum(1)
   565  		addr.typ = 'l'
   566  	case '/',
   567  		'?',
   568  		'"':
   569  		addr.typ = getch()
   570  		addr.u.re = getregexp(addr.typ)
   571  	case '.',
   572  		'$',
   573  		'+',
   574  		'-',
   575  		'\'':
   576  		addr.typ = getch()
   577  	default:
   578  		return nil
   579  	}
   580  	addr.next = simpleaddr()
   581  	if addr.next != nil {
   582  		var nap *Addr
   583  		switch addr.next.typ {
   584  		case '.',
   585  			'$',
   586  			'\'':
   587  			if addr.typ == '"' {
   588  				break
   589  			}
   590  			fallthrough
   591  		// fall through
   592  		case '"':
   593  			editerror("bad address syntax")
   594  		case 'l',
   595  			'#':
   596  			if addr.typ == '"' {
   597  				break
   598  			}
   599  			fallthrough
   600  		// fall through
   601  		case '/',
   602  			'?':
   603  			if addr.typ != '+' && addr.typ != '-' {
   604  				// insert the missing '+'
   605  				nap = newaddr()
   606  				nap.typ = '+'
   607  				nap.next = addr.next
   608  				addr.next = nap
   609  			}
   610  		case '+',
   611  			'-':
   612  			break
   613  		default:
   614  			util.Fatal("simpleaddr")
   615  		}
   616  	}
   617  	ap := newaddr()
   618  	*ap = addr
   619  	return ap
   620  }
   621  
   622  func compoundaddr() *Addr {
   623  	var addr Addr
   624  	addr.u.left = simpleaddr()
   625  	addr.typ = cmdskipbl()
   626  	if addr.typ != ',' && addr.typ != ';' {
   627  		return addr.u.left
   628  	}
   629  	getch()
   630  	addr.next = compoundaddr()
   631  	next := addr.next
   632  	if next != nil && (next.typ == ',' || next.typ == ';') && next.u.left == nil {
   633  		editerror("bad address syntax")
   634  	}
   635  	ap := newaddr()
   636  	*ap = addr
   637  	return ap
   638  }