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

     1  package wind
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"strings"
     7  	"sync"
     8  	"unsafe"
     9  
    10  	"9fans.net/go/cmd/acme/internal/adraw"
    11  	"9fans.net/go/cmd/acme/internal/alog"
    12  	"9fans.net/go/cmd/acme/internal/bufs"
    13  	"9fans.net/go/cmd/acme/internal/file"
    14  	"9fans.net/go/cmd/acme/internal/runes"
    15  	"9fans.net/go/cmd/acme/internal/util"
    16  	"9fans.net/go/draw"
    17  	"9fans.net/go/draw/frame"
    18  )
    19  
    20  type Window struct {
    21  	lk          sync.Mutex
    22  	Ref         uint32
    23  	Tag         Text
    24  	Body        Text
    25  	R           draw.Rectangle
    26  	IsDir       bool
    27  	IsScratch   bool
    28  	Filemenu    bool
    29  	Dirty       bool
    30  	Autoindent  bool
    31  	Showdel     bool
    32  	ID          int
    33  	Addr        runes.Range
    34  	Limit       runes.Range
    35  	Nomark      bool
    36  	Wrselrange  runes.Range
    37  	Rdselfd     *os.File
    38  	Col         *Column
    39  	Eventtag    uint16
    40  	Eventwait   chan bool
    41  	Events      []byte
    42  	Owner       rune
    43  	Maxlines    int
    44  	Dlp         []*Dirlist
    45  	Putseq      int
    46  	Incl        [][]rune
    47  	reffont     *adraw.RefFont
    48  	Ctllock     sync.Mutex
    49  	Ctlfid      int
    50  	Dumpstr     string
    51  	Dumpdir     string
    52  	dumpid      int
    53  	Utflastqid  int
    54  	Utflastboff int64
    55  	Utflastq    int
    56  	Tagsafe     bool
    57  	Tagexpand   bool
    58  	Taglines    int
    59  	tagtop      draw.Rectangle
    60  	Editoutlk   util.QLock
    61  	External    bool
    62  }
    63  
    64  // Text.what
    65  
    66  const (
    67  	Columntag = iota
    68  	Rowtag
    69  	Tag
    70  	Body
    71  )
    72  
    73  type Dirlist struct {
    74  	R   []rune
    75  	Wid int
    76  }
    77  
    78  var GlobalIncref int
    79  
    80  // extern var wdir [unknown]C.char /* must use extern because no dimension given */
    81  var GlobalAutoindent bool
    82  
    83  var Activewin *Window
    84  
    85  var winid int
    86  
    87  func Init(w *Window, clone *Window, r draw.Rectangle) {
    88  	w.Tag.W = w
    89  	w.Taglines = 1
    90  	w.Tagexpand = true
    91  	w.Body.W = w
    92  	winid++
    93  	w.ID = winid
    94  	util.Incref(&w.Ref)
    95  	if GlobalIncref != 0 {
    96  		util.Incref(&w.Ref)
    97  	}
    98  	w.Ctlfid = ^0
    99  	w.Utflastqid = -1
   100  	r1 := r
   101  
   102  	w.tagtop = r
   103  	w.tagtop.Max.Y = r.Min.Y + adraw.Font.Height
   104  	r1.Max.Y = r1.Min.Y + w.Taglines*adraw.Font.Height
   105  
   106  	util.Incref(&adraw.RefFont1.Ref)
   107  	f := fileaddtext(nil, &w.Tag)
   108  	textinit(&w.Tag, f, r1, &adraw.RefFont1, adraw.TagCols[:])
   109  	w.Tag.What = Tag
   110  	// tag is a copy of the contents, not a tracked image
   111  	if clone != nil {
   112  		Textdelete(&w.Tag, 0, w.Tag.Len(), true)
   113  		nc := clone.Tag.Len()
   114  		rp := make([]rune, nc)
   115  		clone.Tag.File.Read(0, rp)
   116  		Textinsert(&w.Tag, 0, rp, true)
   117  		w.Tag.File.ResetLogs()
   118  		Textsetselect(&w.Tag, nc, nc)
   119  	}
   120  	r1 = r
   121  	r1.Min.Y += w.Taglines*adraw.Font.Height + 1
   122  	if r1.Max.Y < r1.Min.Y {
   123  		r1.Max.Y = r1.Min.Y
   124  	}
   125  	f = nil
   126  	var rf *adraw.RefFont
   127  	if clone != nil {
   128  		f = clone.Body.File
   129  		w.Body.Org = clone.Body.Org
   130  		w.IsScratch = clone.IsScratch
   131  		rf = adraw.FindFont(false, false, false, clone.Body.Reffont.F.Name)
   132  	} else {
   133  		rf = adraw.FindFont(false, false, false, "")
   134  	}
   135  	f = fileaddtext(f, &w.Body)
   136  	w.Body.What = Body
   137  	textinit(&w.Body, f, r1, rf, adraw.TextCols[:])
   138  	r1.Min.Y -= 1
   139  	r1.Max.Y = r1.Min.Y + 1
   140  	adraw.Display.ScreenImage.Draw(r1, adraw.TagCols[frame.BORD], nil, draw.ZP)
   141  	Textscrdraw(&w.Body)
   142  	w.R = r
   143  	var br draw.Rectangle
   144  	br.Min = w.Tag.ScrollR.Min
   145  	br.Max.X = br.Min.X + adraw.Button.R.Dx()
   146  	br.Max.Y = br.Min.Y + adraw.Button.R.Dy()
   147  	adraw.Display.ScreenImage.Draw(br, adraw.Button, nil, adraw.Button.R.Min)
   148  	w.Filemenu = true
   149  	w.Maxlines = w.Body.Fr.MaxLines
   150  	w.Autoindent = GlobalAutoindent
   151  	if clone != nil {
   152  		w.Dirty = clone.Dirty
   153  		w.Autoindent = clone.Autoindent
   154  		Textsetselect(&w.Body, clone.Body.Q0, clone.Body.Q1)
   155  		Winsettag(w)
   156  	}
   157  }
   158  
   159  /*
   160   * Draw the appropriate button.
   161   */
   162  func windrawbutton(w *Window) {
   163  	b := adraw.Button
   164  	if !w.IsDir && !w.IsScratch && (w.Body.File.Mod() || len(w.Body.Cache) != 0) {
   165  		b = adraw.ModButton
   166  	}
   167  	var br draw.Rectangle
   168  	br.Min = w.Tag.ScrollR.Min
   169  	br.Max.X = br.Min.X + b.R.Dx()
   170  	br.Max.Y = br.Min.Y + b.R.Dy()
   171  	adraw.Display.ScreenImage.Draw(br, b, nil, b.R.Min)
   172  }
   173  
   174  func Delrunepos(w *Window) int {
   175  	_, i := parsetag(w, 0)
   176  	i += 2
   177  	if i >= w.Tag.Len() {
   178  		return -1
   179  	}
   180  	return i
   181  }
   182  
   183  /*
   184   * Compute number of tag lines required
   185   * to display entire tag text.
   186   */
   187  func wintaglines(w *Window, r draw.Rectangle) int {
   188  	if !w.Tagexpand && !w.Showdel {
   189  		return 1
   190  	}
   191  	w.Showdel = false
   192  	w.Tag.Fr.NoRedraw = 1
   193  	Textresize(&w.Tag, r, true)
   194  	w.Tag.Fr.NoRedraw = 0
   195  	w.Tagsafe = false
   196  	var n int
   197  
   198  	if !w.Tagexpand {
   199  		// use just as many lines as needed to show the Del
   200  		n = Delrunepos(w)
   201  		if n < 0 {
   202  			return 1
   203  		}
   204  		p := w.Tag.Fr.PointOf(n).Sub(w.Tag.Fr.R.Min)
   205  		return 1 + p.Y/w.Tag.Fr.Font.Height
   206  	}
   207  
   208  	// can't use more than we have
   209  	if w.Tag.Fr.NumLines >= w.Tag.Fr.MaxLines {
   210  		return w.Tag.Fr.MaxLines
   211  	}
   212  
   213  	// if tag ends with \n, include empty line at end for typing
   214  	n = w.Tag.Fr.NumLines
   215  	if w.Tag.Len() > 0 {
   216  		var rune_ [1]rune
   217  		w.Tag.File.Read(w.Tag.Len()-1, rune_[:])
   218  		if rune_[0] == '\n' {
   219  			n++
   220  		}
   221  	}
   222  	if n == 0 {
   223  		n = 1
   224  	}
   225  	return n
   226  }
   227  
   228  func Winresize(w *Window, r draw.Rectangle, safe, keepextra bool) int {
   229  	// tagtop is first line of tag
   230  	w.tagtop = r
   231  	w.tagtop.Max.Y = r.Min.Y + adraw.Font.Height
   232  
   233  	r1 := r
   234  	r1.Max.Y = util.Min(r.Max.Y, r1.Min.Y+w.Taglines*adraw.Font.Height)
   235  
   236  	// If needed, recompute number of lines in tag.
   237  	if !safe || !w.Tagsafe || !(w.Tag.All == r1) {
   238  		w.Taglines = wintaglines(w, r)
   239  		r1.Max.Y = util.Min(r.Max.Y, r1.Min.Y+w.Taglines*adraw.Font.Height)
   240  	}
   241  
   242  	// If needed, resize & redraw tag.
   243  	y := r1.Max.Y
   244  	if !safe || !w.Tagsafe || !(w.Tag.All == r1) {
   245  		Textresize(&w.Tag, r1, true)
   246  		y = w.Tag.Fr.R.Max.Y
   247  		windrawbutton(w)
   248  		w.Tagsafe = true
   249  	}
   250  
   251  	// If needed, resize & redraw body.
   252  	r1 = r
   253  	r1.Min.Y = y
   254  	if !safe || !(w.Body.All == r1) {
   255  		oy := y
   256  		if y+1+w.Body.Fr.Font.Height <= r.Max.Y { // room for one line
   257  			r1.Min.Y = y
   258  			r1.Max.Y = y + 1
   259  			adraw.Display.ScreenImage.Draw(r1, adraw.TagCols[frame.BORD], nil, draw.ZP)
   260  			y++
   261  			r1.Min.Y = util.Min(y, r.Max.Y)
   262  			r1.Max.Y = r.Max.Y
   263  		} else {
   264  			r1.Min.Y = y
   265  			r1.Max.Y = y
   266  		}
   267  		y = Textresize(&w.Body, r1, keepextra)
   268  		w.R = r
   269  		w.R.Max.Y = y
   270  		Textscrdraw(&w.Body)
   271  		w.Body.All.Min.Y = oy
   272  	}
   273  	w.Maxlines = util.Min(w.Body.Fr.NumLines, util.Max(w.Maxlines, w.Body.Fr.MaxLines))
   274  	return w.R.Max.Y
   275  }
   276  
   277  func Winclean(w *Window, conservative bool) bool {
   278  	if w.IsScratch || w.IsDir { // don't whine if it's a guide file, error window, etc.
   279  		return true
   280  	}
   281  	if !conservative && w.External {
   282  		return true
   283  	}
   284  	if w.Dirty {
   285  		if len(w.Body.File.Name()) != 0 {
   286  			alog.Printf("%s modified\n", string(w.Body.File.Name()))
   287  		} else {
   288  			if w.Body.Len() < 100 { // don't whine if it's too small
   289  				return true
   290  			}
   291  			alog.Printf("unnamed file modified\n")
   292  		}
   293  		w.Dirty = false
   294  		return false
   295  	}
   296  	return true
   297  }
   298  
   299  func Winlock(w *Window, owner rune) {
   300  	f := w.Body.File
   301  	for i := 0; i < len(f.Text); i++ {
   302  		Winlock1(f.Text[i].W, owner)
   303  	}
   304  }
   305  
   306  func Winlock1(w *Window, owner rune) {
   307  	util.Incref(&w.Ref)
   308  	w.lk.Lock()
   309  	w.Owner = owner
   310  }
   311  
   312  func Winunlock(w *Window) {
   313  	/*
   314  	 * subtle: loop runs backwards to avoid tripping over
   315  	 * winclose indirectly editing f->text and freeing f
   316  	 * on the last iteration of the loop.
   317  	 */
   318  	f := w.Body.File
   319  	for i := len(f.Text) - 1; i >= 0; i-- {
   320  		w = f.Text[i].W
   321  		w.Owner = 0
   322  		w.lk.Unlock()
   323  		Winclose(w)
   324  	}
   325  }
   326  
   327  func Windirfree(w *Window) {
   328  	w.Dlp = nil
   329  }
   330  
   331  var OnWinclose func(*Window)
   332  
   333  func Winclose(w *Window) {
   334  	if util.Decref(&w.Ref) == 0 {
   335  		if OnWinclose != nil {
   336  			OnWinclose(w)
   337  		}
   338  		Windirfree(w)
   339  		textclose(&w.Tag)
   340  		textclose(&w.Body)
   341  		if Activewin == w {
   342  			Activewin = nil
   343  		}
   344  	}
   345  }
   346  
   347  func windelete(w *Window) {
   348  	c := w.Eventwait
   349  	if c != nil {
   350  		w.Events = nil
   351  		w.Eventwait = nil
   352  		c <- true // wake him up
   353  	}
   354  }
   355  
   356  func Winundo(w *Window, isundo bool) {
   357  	w.Utflastqid = -1
   358  	body := &w.Body
   359  	body.File.Undo(isundo, &body.Q0, &body.Q1)
   360  	Textshow(body, body.Q0, body.Q1, true)
   361  	f := body.File
   362  	for i := 0; i < len(f.Text); i++ {
   363  		v := f.Text[i].W
   364  		v.Dirty = (f.Seq() != v.Putseq)
   365  		if v != w {
   366  			v.Body.Q0 = v.Body.Fr.P0 + v.Body.Org
   367  			v.Body.Q1 = v.Body.Fr.P1 + v.Body.Org
   368  		}
   369  	}
   370  	Winsettag(w)
   371  }
   372  
   373  func Winsetname(w *Window, name []rune) {
   374  	t := &w.Body
   375  	if runes.Equal(t.File.Name(), name) {
   376  		return
   377  	}
   378  	w.IsScratch = false
   379  	if len(name) >= 6 && runes.Equal([]rune("/guide"), name[len(name)-6:]) {
   380  		w.IsScratch = true
   381  	} else if len(name) >= 7 && runes.Equal([]rune("+Errors"), name[len(name)-7:]) {
   382  		w.IsScratch = true
   383  	}
   384  	t.File.SetName(name)
   385  	for i := 0; i < len(t.File.Text); i++ {
   386  		v := t.File.Text[i].W
   387  		Winsettag(v)
   388  		v.IsScratch = w.IsScratch
   389  	}
   390  }
   391  
   392  func Wincleartatg(w *Window) {
   393  	// w must be committed
   394  	n := w.Tag.Len()
   395  	r, i := parsetag(w, 0)
   396  	for ; i < n; i++ {
   397  		if r[i] == '|' {
   398  			break
   399  		}
   400  	}
   401  	if i == n {
   402  		return
   403  	}
   404  	i++
   405  	Textdelete(&w.Tag, i, n, true)
   406  	w.Tag.File.SetMod(false)
   407  	if w.Tag.Q0 > i {
   408  		w.Tag.Q0 = i
   409  	}
   410  	if w.Tag.Q1 > i {
   411  		w.Tag.Q1 = i
   412  	}
   413  	Textsetselect(&w.Tag, w.Tag.Q0, w.Tag.Q1)
   414  }
   415  
   416  func parsetag(w *Window, extra int) ([]rune, int) {
   417  	r := make([]rune, w.Tag.Len(), w.Tag.Len()+extra+1)
   418  	w.Tag.File.Read(0, r)
   419  
   420  	/*
   421  	 * " |" or "\t|" ends left half of tag
   422  	 * If we find " Del Snarf" in the left half of the tag
   423  	 * (before the pipe), that ends the file name.
   424  	 */
   425  	pipe := runes.Index(r, []rune(" |"))
   426  	p := runes.Index(r, []rune("\t|"))
   427  	if p >= 0 && (pipe < 0 || p < pipe) {
   428  		pipe = p
   429  	}
   430  	p = runes.Index(r, []rune(" Del Snarf"))
   431  	var i int
   432  	if p >= 0 && (pipe < 0 || p < pipe) {
   433  		i = p
   434  	} else {
   435  		for i = 0; i < w.Tag.Len(); i++ {
   436  			if r[i] == ' ' || r[i] == '\t' {
   437  				break
   438  			}
   439  		}
   440  	}
   441  	return r, i
   442  }
   443  
   444  func Winsettag(w *Window) {
   445  	f := w.Body.File
   446  	for i := 0; i < len(f.Text); i++ {
   447  		v := f.Text[i].W
   448  		if v.Col.Safe || v.Body.Fr.MaxLines > 0 {
   449  			winsettag1(v)
   450  		}
   451  	}
   452  }
   453  
   454  func winsettag1(w *Window) {
   455  
   456  	// there are races that get us here with stuff in the tag cache, so we take extra care to sync it
   457  	if len(w.Tag.Cache) != 0 || w.Tag.File.Mod() {
   458  		Wincommit(w, &w.Tag) // check file name; also guarantees we can modify tag contents
   459  	}
   460  	old, ii := parsetag(w, 0)
   461  	if !runes.Equal(old[:ii], w.Body.File.Name()) {
   462  		Textdelete(&w.Tag, 0, ii, true)
   463  		Textinsert(&w.Tag, 0, w.Body.File.Name(), true)
   464  		old = make([]rune, w.Tag.Len())
   465  		w.Tag.File.Read(0, old)
   466  	}
   467  
   468  	// compute the text for the whole tag, replacing current only if it differs
   469  	new_ := make([]rune, 0, len(w.Body.File.Name())+100)
   470  	new_ = append(new_, w.Body.File.Name()...)
   471  	new_ = append(new_, []rune(" Del Snarf")...)
   472  	if w.Filemenu {
   473  		if w.Body.Needundo || w.Body.File.CanUndo() || len(w.Body.Cache) != 0 {
   474  			new_ = append(new_, []rune(" Undo")...)
   475  		}
   476  		if w.Body.File.CanRedo() {
   477  			new_ = append(new_, []rune(" Redo")...)
   478  		}
   479  		dirty := len(w.Body.File.Name()) != 0 && (len(w.Body.Cache) != 0 || w.Body.File.Seq() != w.Putseq)
   480  		if !w.IsDir && dirty {
   481  			new_ = append(new_, []rune(" Put")...)
   482  		}
   483  	}
   484  	if w.IsDir {
   485  		new_ = append(new_, []rune(" Get")...)
   486  	}
   487  	new_ = append(new_, []rune(" |")...)
   488  	r := runes.IndexRune(old, '|')
   489  	var k int
   490  	if r >= 0 {
   491  		k = r + 1
   492  	} else {
   493  		k = len(old)
   494  		if w.Body.File.Seq() == 0 {
   495  			new_ = append(new_, []rune(" Look ")...)
   496  		}
   497  	}
   498  
   499  	// replace tag if the new one is different
   500  	resize := 0
   501  	var n int
   502  	if !runes.Equal(new_, old[:k]) {
   503  		resize = 1
   504  		n = k
   505  		if n > len(new_) {
   506  			n = len(new_)
   507  		}
   508  		var j int
   509  		for j = 0; j < n; j++ {
   510  			if old[j] != new_[j] {
   511  				break
   512  			}
   513  		}
   514  		q0 := w.Tag.Q0
   515  		q1 := w.Tag.Q1
   516  		Textdelete(&w.Tag, j, k, true)
   517  		Textinsert(&w.Tag, j, new_[j:], true)
   518  		// try to preserve user selection
   519  		r = runes.IndexRune(old, '|')
   520  		if r >= 0 {
   521  			bar := r
   522  			if q0 > bar {
   523  				bar = runes.IndexRune(new_, '|') - bar
   524  				w.Tag.Q0 = q0 + bar
   525  				w.Tag.Q1 = q1 + bar
   526  			}
   527  		}
   528  	}
   529  	w.Tag.File.SetMod(false)
   530  	n = w.Tag.Len() + len(w.Tag.Cache)
   531  	if w.Tag.Q0 > n {
   532  		w.Tag.Q0 = n
   533  	}
   534  	if w.Tag.Q1 > n {
   535  		w.Tag.Q1 = n
   536  	}
   537  	Textsetselect(&w.Tag, w.Tag.Q0, w.Tag.Q1)
   538  	windrawbutton(w)
   539  	if resize != 0 {
   540  		w.Tagsafe = false
   541  		Winresize(w, w.R, true, true)
   542  	}
   543  }
   544  
   545  func Wincommit(w *Window, t *Text) {
   546  	Textcommit(t, true)
   547  	f := t.File
   548  	var i int
   549  	if len(f.Text) > 1 {
   550  		for i = 0; i < len(f.Text); i++ {
   551  			Textcommit(f.Text[i], false) // no-op for t
   552  		}
   553  	}
   554  	if t.What == Body {
   555  		return
   556  	}
   557  	r, i := parsetag(w, 0)
   558  	if !runes.Equal(r[:i], w.Body.File.Name()) {
   559  		file.Seq++
   560  		w.Body.File.Mark()
   561  		w.Body.File.SetMod(true)
   562  		w.Dirty = true
   563  		Winsetname(w, r[:i])
   564  		Winsettag(w)
   565  	}
   566  }
   567  
   568  func Winaddincl(w *Window, r []rune) {
   569  	a := string(r)
   570  	info, err := os.Stat(a)
   571  	if err != nil {
   572  		if !strings.HasPrefix(a, "/") {
   573  			rs := Dirname(&w.Body, r)
   574  			r = rs
   575  			a = string(r)
   576  			info, err = os.Stat(a)
   577  		}
   578  		if err != nil {
   579  			alog.Printf("%s: %v", a, err)
   580  			return
   581  		}
   582  	}
   583  	if !info.IsDir() {
   584  		alog.Printf("%s: not a directory\n", a)
   585  		return
   586  	}
   587  	w.Incl = append(w.Incl, nil)
   588  	copy(w.Incl[1:], w.Incl)
   589  	w.Incl[0] = runes.Clone(r)
   590  }
   591  
   592  func Winctlprint(w *Window, fonts bool) string {
   593  	isdir := 0
   594  	if w.IsDir {
   595  		isdir = 1
   596  	}
   597  	dirty := 0
   598  	if w.Dirty {
   599  		dirty = 1
   600  	}
   601  	base := fmt.Sprintf("%11d %11d %11d %11d %11d ", w.ID, w.Tag.Len(), w.Body.Len(), isdir, dirty)
   602  	if fonts {
   603  		base += fmt.Sprintf("%11d %q %11d ", w.Body.Fr.R.Dx(), w.Body.Reffont.F.Name, w.Body.Fr.MaxTab)
   604  	}
   605  	return base
   606  }
   607  
   608  // fbufalloc() guarantees room off end of BUFSIZE
   609  const (
   610  	BUFSIZE   = 8192
   611  	RUNESIZE  = int(unsafe.Sizeof(rune(0)))
   612  	RBUFSIZE  = bufs.Len / runes.RuneSize
   613  	EVENTSIZE = 256
   614  )
   615  
   616  func Winevent(w *Window, format string, args ...interface{}) {
   617  	if !w.External {
   618  		return
   619  	}
   620  	if w.Owner == 0 {
   621  		util.Fatal("no window owner")
   622  	}
   623  	b := fmt.Sprintf(format, args...)
   624  	w.Events = append(w.Events, byte(w.Owner))
   625  	w.Events = append(w.Events, b...)
   626  	c := w.Eventwait
   627  	if c != nil {
   628  		w.Eventwait = nil
   629  		c <- true
   630  	}
   631  }