9fans.net/go@v0.0.5/cmd/acme/internal/edit/elog.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  // #include "edit.h"
    15  
    16  package edit
    17  
    18  import (
    19  	"fmt"
    20  	"os"
    21  	"reflect"
    22  	"unsafe"
    23  
    24  	"9fans.net/go/cmd/acme/internal/alog"
    25  	"9fans.net/go/cmd/acme/internal/bufs"
    26  	"9fans.net/go/cmd/acme/internal/disk"
    27  	"9fans.net/go/cmd/acme/internal/runes"
    28  	"9fans.net/go/cmd/acme/internal/ui"
    29  	"9fans.net/go/cmd/acme/internal/util"
    30  	"9fans.net/go/cmd/acme/internal/wind"
    31  )
    32  
    33  var Wsequence = "warning: changes out of sequence\n"
    34  var warned = false
    35  
    36  /*
    37   * Log of changes made by editing commands.  Three reasons for this:
    38   * 1) We want addresses in commands to apply to old file, not file-in-change.
    39   * 2) It's difficult to track changes correctly as things move, e.g. ,x m$
    40   * 3) This gives an opportunity to optimize by merging adjacent changes.
    41   * It's a little bit like the Undo/Redo log in Files, but Point 3) argues for a
    42   * separate implementation.  To do this well, we use Replace as well as
    43   * Insert and Delete
    44   */
    45  
    46  type Buflog struct {
    47  	typ int
    48  	q0  int
    49  	nd  int
    50  	nr  int
    51  }
    52  
    53  const (
    54  	Buflogsize = int(unsafe.Sizeof(Buflog{})) / runes.RuneSize
    55  )
    56  
    57  /*
    58   * Minstring shouldn't be very big or we will do lots of I/O for small changes.
    59   * Maxstring is RBUFSIZE so we can fbufalloc() once and not realloc elog.r.
    60   */
    61  
    62  const (
    63  	Minstring = 16
    64  	Maxstring = bufs.RuneLen
    65  )
    66  
    67  type elogFile struct {
    68  	*wind.File
    69  	elogbuf   *disk.Buffer
    70  	elog      Elog
    71  	editclean bool
    72  }
    73  
    74  var elogs = make(map[*wind.File]*elogFile)
    75  
    76  func eloginit(f *wind.File) *elogFile {
    77  	if ef := elogs[f]; ef != nil {
    78  		return ef
    79  	}
    80  	ef := &elogFile{File: f}
    81  	ef.elog.typ = elogNull
    82  	ef.elogbuf = new(disk.Buffer)
    83  	ef.elog.r = bufs.AllocRunes()
    84  	elogs[f] = ef
    85  	return ef
    86  }
    87  
    88  func elogreset(f *elogFile) {
    89  	f.elog.typ = elogNull
    90  	f.elog.nd = 0
    91  	f.elog.r = f.elog.r[:0]
    92  }
    93  
    94  func elogfind(f *wind.File) *elogFile {
    95  	return elogs[f]
    96  }
    97  
    98  func elogterm(f *elogFile) {
    99  	elogreset(f)
   100  	f.elogbuf.Reset()
   101  	f.elog.typ = elogEmpty
   102  	bufs.FreeRunes(f.elog.r)
   103  	f.elog.r = nil
   104  	warned = false
   105  	delete(elogs, f.File)
   106  }
   107  
   108  func elogflush(f *elogFile) {
   109  	var b Buflog
   110  	b.typ = f.elog.typ
   111  	b.q0 = f.elog.q0
   112  	b.nd = f.elog.nd
   113  	b.nr = len(f.elog.r)
   114  	switch f.elog.typ {
   115  	default:
   116  		alog.Printf("unknown elog type %#x\n", f.elog.typ)
   117  	case elogNull:
   118  		break
   119  	case elogInsert,
   120  		elogReplace:
   121  		if len(f.elog.r) > 0 {
   122  			f.elogbuf.Insert(f.elogbuf.Len(), f.elog.r)
   123  		}
   124  		fallthrough
   125  	// fall through
   126  	case elogDelete:
   127  		f.elogbuf.Insert(f.elogbuf.Len(), buflogrunes(&b))
   128  	}
   129  	elogreset(f)
   130  }
   131  
   132  func buflogrunes(b *Buflog) []rune {
   133  	var r []rune
   134  	h := (*reflect.SliceHeader)(unsafe.Pointer(&r))
   135  	h.Data = uintptr(unsafe.Pointer(b))
   136  	h.Len = Buflogsize
   137  	h.Cap = Buflogsize
   138  	return r
   139  }
   140  
   141  func elogreplace(ff *wind.File, q0 int, q1 int, r []rune) {
   142  	if q0 == q1 && len(r) == 0 {
   143  		return
   144  	}
   145  	f := eloginit(ff)
   146  	if f.elog.typ != elogNull && q0 < f.elog.q0 {
   147  		if !warned {
   148  			warned = true
   149  			alog.Printf(Wsequence)
   150  		}
   151  		elogflush(f)
   152  	}
   153  	// try to merge with previous
   154  	gap := q0 - (f.elog.q0 + f.elog.nd) // gap between previous and this
   155  	if f.elog.typ == elogReplace && len(f.elog.r)+gap+len(r) < Maxstring {
   156  		if gap < Minstring {
   157  			if gap > 0 {
   158  				n := len(f.elog.r)
   159  				f.Read(f.elog.q0+f.elog.nd, f.elog.r[n:n+gap])
   160  				f.elog.r = f.elog.r[:n+gap]
   161  			}
   162  			f.elog.nd += gap + q1 - q0
   163  			f.elog.r = append(f.elog.r, r...)
   164  			return
   165  		}
   166  	}
   167  	elogflush(f)
   168  	f.elog.typ = elogReplace
   169  	f.elog.q0 = q0
   170  	f.elog.nd = q1 - q0
   171  	if len(r) > bufs.RuneLen {
   172  		editerror("internal error: replacement string too large(%d)", len(r))
   173  	}
   174  	f.elog.r = f.elog.r[:len(r)]
   175  	copy(f.elog.r, r)
   176  }
   177  
   178  func eloginsert(ff *wind.File, q0 int, r []rune) {
   179  	if len(r) == 0 {
   180  		return
   181  	}
   182  	f := eloginit(ff)
   183  	if f.elog.typ != elogNull && q0 < f.elog.q0 {
   184  		if !warned {
   185  			warned = true
   186  			alog.Printf(Wsequence)
   187  		}
   188  		elogflush(f)
   189  	}
   190  	// try to merge with previous
   191  	if f.elog.typ == elogInsert && q0 == f.elog.q0 && len(f.elog.r)+len(r) < Maxstring {
   192  		f.elog.r = append(f.elog.r, r...)
   193  		return
   194  	}
   195  	for len(r) > 0 {
   196  		elogflush(f)
   197  		f.elog.typ = elogInsert
   198  		f.elog.q0 = q0
   199  		n := len(r)
   200  		if n > bufs.RuneLen {
   201  			n = bufs.RuneLen
   202  		}
   203  		f.elog.r = append(f.elog.r, r[:n]...)
   204  		r = r[n:]
   205  	}
   206  }
   207  
   208  func elogdelete(ff *wind.File, q0 int, q1 int) {
   209  	if q0 == q1 {
   210  		return
   211  	}
   212  	f := eloginit(ff)
   213  	if f.elog.typ != elogNull && q0 < f.elog.q0+f.elog.nd {
   214  		if !warned {
   215  			warned = true
   216  			alog.Printf(Wsequence)
   217  		}
   218  		elogflush(f)
   219  	}
   220  	// try to merge with previous
   221  	if f.elog.typ == elogDelete && f.elog.q0+f.elog.nd == q0 {
   222  		f.elog.nd += q1 - q0
   223  		return
   224  	}
   225  	elogflush(f)
   226  	f.elog.typ = elogDelete
   227  	f.elog.q0 = q0
   228  	f.elog.nd = q1 - q0
   229  }
   230  
   231  func elogapply(f *elogFile) {
   232  	const tracelog = false
   233  
   234  	elogflush(f)
   235  	log := f.elogbuf
   236  	t := f.Curtext
   237  
   238  	buf := bufs.AllocRunes()
   239  	mod := false
   240  
   241  	owner := rune(0)
   242  	if t.W != nil {
   243  		owner = t.W.Owner
   244  		if owner == 0 {
   245  			t.W.Owner = 'E'
   246  		}
   247  	}
   248  
   249  	/*
   250  	 * The edit commands have already updated the selection in t->q0, t->q1,
   251  	 * but using coordinates relative to the unmodified buffer.  As we apply the log,
   252  	 * we have to update the coordinates to be relative to the modified buffer.
   253  	 * Textinsert and textdelete will do this for us; our only work is to apply the
   254  	 * convention that an insertion at t->q0==t->q1 is intended to select the
   255  	 * inserted text.
   256  	 */
   257  
   258  	/*
   259  	 * We constrain the addresses in here (with textconstrain()) because
   260  	 * overlapping changes will generate bogus addresses.   We will warn
   261  	 * about changes out of sequence but proceed anyway; here we must
   262  	 * keep things in range.
   263  	 */
   264  
   265  	for log.Len() > 0 {
   266  		up := log.Len() - Buflogsize
   267  		var b Buflog
   268  		log.Read(up, buflogrunes(&b))
   269  		var tq1 int
   270  		var tq0 int
   271  		var n int
   272  		var i int
   273  		switch b.typ {
   274  		default:
   275  			fmt.Fprintf(os.Stderr, "elogapply: %#x\n", b.typ)
   276  			panic("elogapply")
   277  
   278  		case elogReplace:
   279  			if tracelog {
   280  				alog.Printf("elog replace %d %d (%d %d)\n", b.q0, b.q0+b.nd, t.Q0, t.Q1)
   281  			}
   282  			if !mod {
   283  				mod = true
   284  				f.Mark()
   285  			}
   286  			ui.Textconstrain(t, b.q0, b.q0+b.nd, &tq0, &tq1)
   287  			wind.Textdelete(t, tq0, tq1, true)
   288  			up -= b.nr
   289  			for i = 0; i < b.nr; i += n {
   290  				n = b.nr - i
   291  				if n > bufs.RuneLen {
   292  					n = bufs.RuneLen
   293  				}
   294  				log.Read(up+i, buf[:n])
   295  				wind.Textinsert(t, tq0+i, buf[:n], true)
   296  			}
   297  			if t.Q0 == b.q0 && t.Q1 == b.q0 {
   298  				t.Q1 += b.nr
   299  			}
   300  
   301  		case elogDelete:
   302  			if tracelog {
   303  				alog.Printf("elog delete %d %d (%d %d)\n", b.q0, b.q0+b.nd, t.Q0, t.Q1)
   304  			}
   305  			if !mod {
   306  				mod = true
   307  				f.Mark()
   308  			}
   309  			ui.Textconstrain(t, b.q0, b.q0+b.nd, &tq0, &tq1)
   310  			wind.Textdelete(t, tq0, tq1, true)
   311  
   312  		case elogInsert:
   313  			if tracelog {
   314  				alog.Printf("elog insert %d %d (%d %d)\n", b.q0, b.q0+b.nr, t.Q0, t.Q1)
   315  			}
   316  			if !mod {
   317  				mod = true
   318  				f.Mark()
   319  			}
   320  			ui.Textconstrain(t, b.q0, b.q0, &tq0, &tq1)
   321  			up -= b.nr
   322  			for i = 0; i < b.nr; i += n {
   323  				n = b.nr - i
   324  				if n > bufs.RuneLen {
   325  					n = bufs.RuneLen
   326  				}
   327  				log.Read(up+i, buf[:n])
   328  				wind.Textinsert(t, tq0+i, buf[:n], true)
   329  			}
   330  			if t.Q0 == b.q0 && t.Q1 == b.q0 {
   331  				t.Q1 += b.nr
   332  			}
   333  
   334  			/*		case Filename:
   335  					f->seq = u.seq;
   336  					fileunsetname(f, epsilon);
   337  					f->mod = u.mod;
   338  					up -= u.n;
   339  					free(f->name);
   340  					if(u.n == 0)
   341  						f->name = nil;
   342  					else
   343  						f->name = runemalloc(u.n);
   344  					bufread(delta, up, f->name, u.n);
   345  					f->nname = u.n;
   346  					break;
   347  			*/
   348  		}
   349  		log.Delete(up, log.Len())
   350  	}
   351  	bufs.FreeRunes(buf)
   352  	elogterm(f)
   353  
   354  	/*
   355  	 * Bad addresses will cause bufload to crash, so double check.
   356  	 * If changes were out of order, we expect problems so don't complain further.
   357  	 */
   358  	if t.Q0 > f.Len() || t.Q1 > f.Len() || t.Q0 > t.Q1 {
   359  		if !warned {
   360  			alog.Printf("elogapply: can't happen %d %d %d\n", t.Q0, t.Q1, f.Len())
   361  		}
   362  		t.Q1 = util.Min(t.Q1, f.Len())
   363  		t.Q0 = util.Min(t.Q0, t.Q1)
   364  	}
   365  
   366  	if t.W != nil {
   367  		t.W.Owner = owner
   368  	}
   369  }
   370  
   371  const (
   372  	elogEmpty    = 0
   373  	elogNull     = '-'
   374  	elogDelete   = 'd'
   375  	elogInsert   = 'i'
   376  	elogReplace  = 'r'
   377  	elogFilename = 'f'
   378  )
   379  
   380  type Elog struct {
   381  	typ int
   382  	q0  int
   383  	nd  int
   384  	r   []rune
   385  }