9fans.net/go@v0.0.7/cmd/acme/internal/ui/ui.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 "fns.h"
    14  
    15  package ui
    16  
    17  import (
    18  	"unicode/utf8"
    19  
    20  	"9fans.net/go/cmd/acme/internal/adraw"
    21  	"9fans.net/go/cmd/acme/internal/bufs"
    22  	"9fans.net/go/cmd/acme/internal/disk"
    23  	"9fans.net/go/cmd/acme/internal/runes"
    24  	"9fans.net/go/cmd/acme/internal/util"
    25  	"9fans.net/go/cmd/acme/internal/wind"
    26  	"9fans.net/go/draw"
    27  )
    28  
    29  func savemouse(w *wind.Window) {
    30  	prevmouse = Mouse.Point
    31  	mousew = w
    32  }
    33  
    34  func restoremouse(w *wind.Window) int {
    35  	did := 0
    36  	if mousew != nil && mousew == w {
    37  		adraw.Display.MoveCursor(prevmouse)
    38  		did = 1
    39  	}
    40  	mousew = nil
    41  	return did
    42  }
    43  
    44  func movetodel(w *wind.Window) {
    45  	n := wind.Delrunepos(w)
    46  	if n < 0 {
    47  		return
    48  	}
    49  	adraw.Display.MoveCursor(w.Tag.Fr.PointOf(n).Add(draw.Pt(4, w.Tag.Fr.Font.Height-4)))
    50  }
    51  
    52  func Clearmouse() {
    53  	mousew = nil
    54  }
    55  
    56  var Mouse *draw.Mouse
    57  
    58  var Mousectl *draw.Mousectl
    59  
    60  func Winmousebut(w *wind.Window) {
    61  	adraw.Display.MoveCursor(w.Tag.ScrollR.Min.Add(draw.Pt(w.Tag.ScrollR.Dx(), adraw.Font.Height).Div(2)))
    62  }
    63  
    64  var mousew *wind.Window
    65  
    66  var prevmouse draw.Point
    67  
    68  func XCut(et, t, _ *wind.Text, dosnarf, docut bool, _ []rune) {
    69  
    70  	/*
    71  	 * if not executing a mouse chord (et != t) and snarfing (dosnarf)
    72  	 * and executed Cut or Snarf in window tag (et->w != nil),
    73  	 * then use the window body selection or the tag selection
    74  	 * or do nothing at all.
    75  	 */
    76  	if et != t && dosnarf && et.W != nil {
    77  		if et.W.Body.Q1 > et.W.Body.Q0 {
    78  			t = &et.W.Body
    79  			if docut {
    80  				t.File.Mark() // seq has been incremented by execute
    81  			}
    82  		} else if et.W.Tag.Q1 > et.W.Tag.Q0 {
    83  			t = &et.W.Tag
    84  		} else {
    85  			t = nil
    86  		}
    87  	}
    88  	if t == nil { // no selection
    89  		return
    90  	}
    91  
    92  	locked := false
    93  	if t.W != nil && et.W != t.W {
    94  		locked = true
    95  		c := 'M'
    96  		if et.W != nil {
    97  			c = et.W.Owner
    98  		}
    99  		wind.Winlock(t.W, c)
   100  	}
   101  	if t.Q0 == t.Q1 {
   102  		if locked {
   103  			wind.Winunlock(t.W)
   104  		}
   105  		return
   106  	}
   107  	if dosnarf {
   108  		q0 := t.Q0
   109  		q1 := t.Q1
   110  		snarfbuf.Delete(0, snarfbuf.Len())
   111  		r := bufs.AllocRunes()
   112  		for q0 < q1 {
   113  			n := q1 - q0
   114  			if n > bufs.RuneLen {
   115  				n = bufs.RuneLen
   116  			}
   117  			t.File.Read(q0, r[:n])
   118  			snarfbuf.Insert(snarfbuf.Len(), r[:n])
   119  			q0 += n
   120  		}
   121  		bufs.FreeRunes(r)
   122  		acmeputsnarf()
   123  	}
   124  	if docut {
   125  		wind.Textdelete(t, t.Q0, t.Q1, true)
   126  		wind.Textsetselect(t, t.Q0, t.Q0)
   127  		if t.W != nil {
   128  			wind.Textscrdraw(t)
   129  			wind.Winsettag(t.W)
   130  		}
   131  	} else if dosnarf { // Snarf command
   132  		wind.Argtext = t
   133  	}
   134  	if locked {
   135  		wind.Winunlock(t.W)
   136  	}
   137  }
   138  
   139  func XPaste(et, t, _ *wind.Text, selectall, tobody bool, _ []rune) {
   140  
   141  	// if(tobody), use body of executing window  (Paste or Send command)
   142  	if tobody && et != nil && et.W != nil {
   143  		t = &et.W.Body
   144  		t.File.Mark() // seq has been incremented by execute
   145  	}
   146  	if t == nil {
   147  		return
   148  	}
   149  
   150  	acmegetsnarf()
   151  	if t == nil || snarfbuf.Len() == 0 {
   152  		return
   153  	}
   154  	if t.W != nil && et.W != t.W {
   155  		c := 'M'
   156  		if et.W != nil {
   157  			c = et.W.Owner
   158  		}
   159  		wind.Winlock(t.W, c)
   160  	}
   161  	XCut(t, t, nil, false, true, nil)
   162  	q := 0
   163  	q0 := t.Q0
   164  	q1 := t.Q0 + snarfbuf.Len()
   165  	r := bufs.AllocRunes()
   166  	for q0 < q1 {
   167  		n := q1 - q0
   168  		if n > bufs.RuneLen {
   169  			n = bufs.RuneLen
   170  		}
   171  		snarfbuf.Read(q, r[:n])
   172  		wind.Textinsert(t, q0, r[:n], true)
   173  		q += n
   174  		q0 += n
   175  	}
   176  	bufs.FreeRunes(r)
   177  	if selectall {
   178  		wind.Textsetselect(t, t.Q0, q1)
   179  	} else {
   180  		wind.Textsetselect(t, q1, q1)
   181  	}
   182  	if t.W != nil {
   183  		wind.Textscrdraw(t)
   184  		wind.Winsettag(t.W)
   185  	}
   186  	if t.W != nil && et.W != t.W {
   187  		wind.Winunlock(t.W)
   188  	}
   189  }
   190  
   191  func XUndo(et, _, _ *wind.Text, isundo, _ bool, _ []rune) {
   192  	if et == nil || et.W == nil {
   193  		return
   194  	}
   195  	seq := seqof(et.W, isundo)
   196  	if seq == 0 {
   197  		// nothing to undo
   198  		return
   199  	}
   200  	/*
   201  	 * Undo the executing window first. Its display will update. other windows
   202  	 * in the same file will not call show() and jump to a different location in the file.
   203  	 * Simultaneous changes to other files will be chaotic, however.
   204  	 */
   205  	wind.Winundo(et.W, isundo)
   206  	for i := 0; i < len(wind.TheRow.Col); i++ {
   207  		c := wind.TheRow.Col[i]
   208  		for j := 0; j < len(c.W); j++ {
   209  			w := c.W[j]
   210  			if w == et.W {
   211  				continue
   212  			}
   213  			if seqof(w, isundo) == seq {
   214  				wind.Winundo(w, isundo)
   215  			}
   216  		}
   217  	}
   218  }
   219  
   220  const (
   221  	Kscrolloneup   = draw.KeyFn | 0x20
   222  	Kscrollonedown = draw.KeyFn | 0x21
   223  )
   224  
   225  /*
   226   * /dev/snarf updates when the file is closed, so we must open our own
   227   * fd here rather than use snarffd
   228   */
   229  
   230  /* rio truncates larges snarf buffers, so this avoids using the
   231   * service if the string is huge */
   232  
   233  const MAXSNARF = 100 * 1024
   234  
   235  const (
   236  	NSnarf = 1000
   237  )
   238  
   239  var snarfrune [NSnarf + 1]rune
   240  
   241  var snarfbuf disk.Buffer
   242  
   243  func acmeputsnarf() {
   244  	if snarfbuf.Len() == 0 {
   245  		return
   246  	}
   247  	if snarfbuf.Len() > MAXSNARF {
   248  		return
   249  	}
   250  
   251  	var buf []byte
   252  	var n int
   253  	for i := 0; i < snarfbuf.Len(); i += n {
   254  		n = snarfbuf.Len() - i
   255  		if n >= NSnarf {
   256  			n = NSnarf
   257  		}
   258  		snarfbuf.Read(i, snarfrune[:n])
   259  		var rbuf [utf8.UTFMax]byte
   260  		for _, r := range snarfrune[:n] {
   261  			w := utf8.EncodeRune(rbuf[:], r)
   262  			buf = append(buf, rbuf[:w]...)
   263  		}
   264  	}
   265  	if len(buf) > 0 {
   266  		adraw.Display.WriteSnarf(buf)
   267  	}
   268  }
   269  
   270  func acmegetsnarf() {
   271  	_, m, err := adraw.Display.ReadSnarf(nil)
   272  	if err != nil {
   273  		return
   274  	}
   275  	buf := make([]byte, m+100)
   276  	n, _, err := adraw.Display.ReadSnarf(buf)
   277  	if n == 0 || err != nil {
   278  		return
   279  	}
   280  	buf = buf[:n]
   281  
   282  	r := make([]rune, utf8.RuneCount(buf))
   283  	_, nr, _ := runes.Convert(buf, r, true)
   284  	snarfbuf.Reset()
   285  	snarfbuf.Insert(0, r[:nr])
   286  }
   287  
   288  func seqof(w *wind.Window, isundo bool) int {
   289  	// if it's undo, see who changed with us
   290  	if isundo {
   291  		return w.Body.File.Seq()
   292  	}
   293  	// if it's redo, see who we'll be sync'ed up with
   294  	return w.Body.File.RedoSeq()
   295  }
   296  
   297  /*
   298   * Heuristic city.
   299   */
   300  func Makenewwindow(t *wind.Text) *wind.Window {
   301  	var c *wind.Column
   302  	if wind.Activecol != nil {
   303  		c = wind.Activecol
   304  	} else if wind.Seltext != nil && wind.Seltext.Col != nil {
   305  		c = wind.Seltext.Col
   306  	} else if t != nil && t.Col != nil {
   307  		c = t.Col
   308  	} else {
   309  		if len(wind.TheRow.Col) == 0 && wind.RowAdd(&wind.TheRow, nil, -1) == nil {
   310  			util.Fatal("can't make column")
   311  		}
   312  		c = wind.TheRow.Col[len(wind.TheRow.Col)-1]
   313  	}
   314  	wind.Activecol = c
   315  	if t == nil || t.W == nil || len(c.W) == 0 {
   316  		return ColaddAndMouse(c, nil, nil, -1)
   317  	}
   318  
   319  	// find biggest window and biggest blank spot
   320  	emptyw := c.W[0]
   321  	bigw := emptyw
   322  	var w *wind.Window
   323  	for i := 1; i < len(c.W); i++ {
   324  		w = c.W[i]
   325  		// use >= to choose one near bottom of screen
   326  		if w.Body.Fr.MaxLines >= bigw.Body.Fr.MaxLines {
   327  			bigw = w
   328  		}
   329  		if w.Body.Fr.MaxLines-w.Body.Fr.NumLines >= emptyw.Body.Fr.MaxLines-emptyw.Body.Fr.NumLines {
   330  			emptyw = w
   331  		}
   332  	}
   333  	emptyb := &emptyw.Body
   334  	el := emptyb.Fr.MaxLines - emptyb.Fr.NumLines
   335  	var y int
   336  	// if empty space is big, use it
   337  	if el > 15 || (el > 3 && el > (bigw.Body.Fr.MaxLines-1)/2) {
   338  		y = emptyb.Fr.R.Min.Y + emptyb.Fr.NumLines*adraw.Font.Height
   339  	} else {
   340  		// if this window is in column and isn't much smaller, split it
   341  		if t.Col == c && t.W.R.Dy() > 2*bigw.R.Dy()/3 {
   342  			bigw = t.W
   343  		}
   344  		y = (bigw.R.Min.Y + bigw.R.Max.Y) / 2
   345  	}
   346  	w = ColaddAndMouse(c, nil, nil, y)
   347  	if w.Body.Fr.MaxLines < 2 {
   348  		wind.Colgrow(w.Col, w, 1)
   349  	}
   350  	return w
   351  }