363 lines
7.7 KiB
C
363 lines
7.7 KiB
C
|
/*
|
||
|
* This file is part of the UCB release of Plan 9. It is subject to the license
|
||
|
* terms in the LICENSE file found in the top-level directory of this
|
||
|
* distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
|
||
|
* part of the UCB release of Plan 9, including this file, may be copied,
|
||
|
* modified, propagated, or distributed except according to the terms contained
|
||
|
* in the LICENSE file.
|
||
|
*/
|
||
|
|
||
|
#include <u.h>
|
||
|
#include <libc.h>
|
||
|
#include <draw.h>
|
||
|
#include <thread.h>
|
||
|
#include <cursor.h>
|
||
|
#include <mouse.h>
|
||
|
#include <keyboard.h>
|
||
|
#include <frame.h>
|
||
|
#include <fcall.h>
|
||
|
#include <plumb.h>
|
||
|
#include "dat.h"
|
||
|
#include "fns.h"
|
||
|
#include "edit.h"
|
||
|
|
||
|
static char Wsequence[] = "warning: changes out of sequence\n";
|
||
|
static int warned = FALSE;
|
||
|
|
||
|
/*
|
||
|
* Log of changes made by editing commands. Three reasons for this:
|
||
|
* 1) We want addresses in commands to apply to old file, not file-in-change.
|
||
|
* 2) It's difficult to track changes correctly as things move, e.g. ,x m$
|
||
|
* 3) This gives an opportunity to optimize by merging adjacent changes.
|
||
|
* It's a little bit like the Undo/Redo log in Files, but Point 3) argues for a
|
||
|
* separate implementation. To do this well, we use Replace as well as
|
||
|
* Insert and Delete
|
||
|
*/
|
||
|
|
||
|
typedef struct Buflog Buflog;
|
||
|
struct Buflog
|
||
|
{
|
||
|
int16_t type; /* Replace, Filename */
|
||
|
uint32_t q0; /* location of change (unused in f) */
|
||
|
uint32_t nd; /* # runes to delete */
|
||
|
uint32_t nr; /* # runes in string or file name */
|
||
|
};
|
||
|
|
||
|
enum
|
||
|
{
|
||
|
Buflogsize = sizeof(Buflog)/sizeof(Rune),
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
* Minstring shouldn't be very big or we will do lots of I/O for small changes.
|
||
|
* Maxstring is RBUFSIZE so we can fbufalloc() once and not realloc elog.r.
|
||
|
*/
|
||
|
enum
|
||
|
{
|
||
|
Minstring = 16, /* distance beneath which we merge changes */
|
||
|
Maxstring = RBUFSIZE, /* maximum length of change we will merge into one */
|
||
|
};
|
||
|
|
||
|
void
|
||
|
eloginit(File *f)
|
||
|
{
|
||
|
if(f->elog.type != Empty)
|
||
|
return;
|
||
|
f->elog.type = Null;
|
||
|
if(f->elogbuf == nil)
|
||
|
f->elogbuf = emalloc(sizeof(Buffer));
|
||
|
if(f->elog.r == nil)
|
||
|
f->elog.r = fbufalloc();
|
||
|
bufreset(f->elogbuf);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
elogclose(File *f)
|
||
|
{
|
||
|
if(f->elogbuf){
|
||
|
bufclose(f->elogbuf);
|
||
|
free(f->elogbuf);
|
||
|
f->elogbuf = nil;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void
|
||
|
elogreset(File *f)
|
||
|
{
|
||
|
f->elog.type = Null;
|
||
|
f->elog.nd = 0;
|
||
|
f->elog.nr = 0;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
elogterm(File *f)
|
||
|
{
|
||
|
elogreset(f);
|
||
|
if(f->elogbuf)
|
||
|
bufreset(f->elogbuf);
|
||
|
f->elog.type = Empty;
|
||
|
fbuffree(f->elog.r);
|
||
|
f->elog.r = nil;
|
||
|
warned = FALSE;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
elogflush(File *f)
|
||
|
{
|
||
|
Buflog b;
|
||
|
|
||
|
b.type = f->elog.type;
|
||
|
b.q0 = f->elog.q0;
|
||
|
b.nd = f->elog.nd;
|
||
|
b.nr = f->elog.nr;
|
||
|
switch(f->elog.type){
|
||
|
default:
|
||
|
warning(nil, "unknown elog type 0x%ux\n", f->elog.type);
|
||
|
break;
|
||
|
case Null:
|
||
|
break;
|
||
|
case Insert:
|
||
|
case Replace:
|
||
|
if(f->elog.nr > 0)
|
||
|
bufinsert(f->elogbuf, f->elogbuf->nc, f->elog.r, f->elog.nr);
|
||
|
/* fall through */
|
||
|
case Delete:
|
||
|
bufinsert(f->elogbuf, f->elogbuf->nc, (Rune*)&b, Buflogsize);
|
||
|
break;
|
||
|
}
|
||
|
elogreset(f);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
elogreplace(File *f, int q0, int q1, Rune *r, int nr)
|
||
|
{
|
||
|
uint32_t gap;
|
||
|
|
||
|
if(q0==q1 && nr==0)
|
||
|
return;
|
||
|
eloginit(f);
|
||
|
if(f->elog.type!=Null && q0<f->elog.q0){
|
||
|
if(warned++ == 0)
|
||
|
warning(nil, Wsequence);
|
||
|
elogflush(f);
|
||
|
}
|
||
|
/* try to merge with previous */
|
||
|
gap = q0 - (f->elog.q0+f->elog.nd); /* gap between previous and this */
|
||
|
if(f->elog.type==Replace && f->elog.nr+gap+nr<Maxstring){
|
||
|
if(gap < Minstring){
|
||
|
if(gap > 0){
|
||
|
bufread(f, f->elog.q0+f->elog.nd, f->elog.r+f->elog.nr, gap);
|
||
|
f->elog.nr += gap;
|
||
|
}
|
||
|
f->elog.nd += gap + q1-q0;
|
||
|
runemove(f->elog.r+f->elog.nr, r, nr);
|
||
|
f->elog.nr += nr;
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
elogflush(f);
|
||
|
f->elog.type = Replace;
|
||
|
f->elog.q0 = q0;
|
||
|
f->elog.nd = q1-q0;
|
||
|
f->elog.nr = nr;
|
||
|
if(nr > RBUFSIZE)
|
||
|
editerror("internal error: replacement string too large(%d)", nr);
|
||
|
runemove(f->elog.r, r, nr);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
eloginsert(File *f, int q0, Rune *r, int nr)
|
||
|
{
|
||
|
int n;
|
||
|
|
||
|
if(nr == 0)
|
||
|
return;
|
||
|
eloginit(f);
|
||
|
if(f->elog.type!=Null && q0<f->elog.q0){
|
||
|
if(warned++ == 0)
|
||
|
warning(nil, Wsequence);
|
||
|
elogflush(f);
|
||
|
}
|
||
|
/* try to merge with previous */
|
||
|
if(f->elog.type==Insert && q0==f->elog.q0 && f->elog.nr+nr<Maxstring){
|
||
|
runemove(f->elog.r+f->elog.nr, r, nr);
|
||
|
f->elog.nr += nr;
|
||
|
return;
|
||
|
}
|
||
|
while(nr > 0){
|
||
|
elogflush(f);
|
||
|
f->elog.type = Insert;
|
||
|
f->elog.q0 = q0;
|
||
|
n = nr;
|
||
|
if(n > RBUFSIZE)
|
||
|
n = RBUFSIZE;
|
||
|
f->elog.nr = n;
|
||
|
runemove(f->elog.r, r, n);
|
||
|
r += n;
|
||
|
nr -= n;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void
|
||
|
elogdelete(File *f, int q0, int q1)
|
||
|
{
|
||
|
if(q0 == q1)
|
||
|
return;
|
||
|
eloginit(f);
|
||
|
if(f->elog.type!=Null && q0<f->elog.q0+f->elog.nd){
|
||
|
if(warned++ == 0)
|
||
|
warning(nil, Wsequence);
|
||
|
elogflush(f);
|
||
|
}
|
||
|
/* try to merge with previous */
|
||
|
if(f->elog.type==Delete && f->elog.q0+f->elog.nd==q0){
|
||
|
f->elog.nd += q1-q0;
|
||
|
return;
|
||
|
}
|
||
|
elogflush(f);
|
||
|
f->elog.type = Delete;
|
||
|
f->elog.q0 = q0;
|
||
|
f->elog.nd = q1-q0;
|
||
|
}
|
||
|
|
||
|
#define tracelog 0
|
||
|
void
|
||
|
elogapply(File *f)
|
||
|
{
|
||
|
Buflog b;
|
||
|
Rune *buf;
|
||
|
uint32_t i, n, up, mod;
|
||
|
uint32_t tq0, tq1;
|
||
|
Buffer *log;
|
||
|
Text *t;
|
||
|
int owner;
|
||
|
|
||
|
elogflush(f);
|
||
|
log = f->elogbuf;
|
||
|
t = f->curtext;
|
||
|
|
||
|
buf = fbufalloc();
|
||
|
mod = FALSE;
|
||
|
|
||
|
owner = 0;
|
||
|
if(t->w){
|
||
|
owner = t->w->owner;
|
||
|
if(owner == 0)
|
||
|
t->w->owner = 'E';
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* The edit commands have already updated the selection in t->q0, t->q1,
|
||
|
* but using coordinates relative to the unmodified buffer. As we apply the log,
|
||
|
* we have to update the coordinates to be relative to the modified buffer.
|
||
|
* Textinsert and textdelete will do this for us; our only work is to apply the
|
||
|
* convention that an insertion at t->q0==t->q1 is intended to select the
|
||
|
* inserted text.
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
* We constrain the addresses in here (with textconstrain()) because
|
||
|
* overlapping changes will generate bogus addresses. We will warn
|
||
|
* about changes out of sequence but proceed anyway; here we must
|
||
|
* keep things in range.
|
||
|
*/
|
||
|
|
||
|
while(log->nc > 0){
|
||
|
up = log->nc-Buflogsize;
|
||
|
bufread(log, up, (Rune*)&b, Buflogsize);
|
||
|
switch(b.type){
|
||
|
default:
|
||
|
fprint(2, "elogapply: 0x%ux\n", b.type);
|
||
|
abort();
|
||
|
break;
|
||
|
|
||
|
case Replace:
|
||
|
if(tracelog)
|
||
|
warning(nil, "elog replace %d %d (%d %d)\n",
|
||
|
b.q0, b.q0+b.nd, t->q0, t->q1);
|
||
|
if(!mod){
|
||
|
mod = TRUE;
|
||
|
filemark(f);
|
||
|
}
|
||
|
textconstrain(t, b.q0, b.q0+b.nd, &tq0, &tq1);
|
||
|
textdelete(t, tq0, tq1, TRUE);
|
||
|
up -= b.nr;
|
||
|
for(i=0; i<b.nr; i+=n){
|
||
|
n = b.nr - i;
|
||
|
if(n > RBUFSIZE)
|
||
|
n = RBUFSIZE;
|
||
|
bufread(log, up+i, buf, n);
|
||
|
textinsert(t, tq0+i, buf, n, TRUE);
|
||
|
}
|
||
|
if(t->q0 == b.q0 && t->q1 == b.q0)
|
||
|
t->q1 += b.nr;
|
||
|
break;
|
||
|
|
||
|
case Delete:
|
||
|
if(tracelog)
|
||
|
warning(nil, "elog delete %d %d (%d %d)\n",
|
||
|
b.q0, b.q0+b.nd, t->q0, t->q1);
|
||
|
if(!mod){
|
||
|
mod = TRUE;
|
||
|
filemark(f);
|
||
|
}
|
||
|
textconstrain(t, b.q0, b.q0+b.nd, &tq0, &tq1);
|
||
|
textdelete(t, tq0, tq1, TRUE);
|
||
|
break;
|
||
|
|
||
|
case Insert:
|
||
|
if(tracelog)
|
||
|
warning(nil, "elog insert %d %d (%d %d)\n",
|
||
|
b.q0, b.q0+b.nr, t->q0, t->q1);
|
||
|
if(!mod){
|
||
|
mod = TRUE;
|
||
|
filemark(f);
|
||
|
}
|
||
|
textconstrain(t, b.q0, b.q0, &tq0, &tq1);
|
||
|
up -= b.nr;
|
||
|
for(i=0; i<b.nr; i+=n){
|
||
|
n = b.nr - i;
|
||
|
if(n > RBUFSIZE)
|
||
|
n = RBUFSIZE;
|
||
|
bufread(log, up+i, buf, n);
|
||
|
textinsert(t, tq0+i, buf, n, TRUE);
|
||
|
}
|
||
|
if(t->q0 == b.q0 && t->q1 == b.q0)
|
||
|
t->q1 += b.nr;
|
||
|
break;
|
||
|
|
||
|
/* case Filename:
|
||
|
f->seq = u.seq;
|
||
|
fileunsetname(f, epsilon);
|
||
|
f->mod = u.mod;
|
||
|
up -= u.n;
|
||
|
free(f->name);
|
||
|
if(u.n == 0)
|
||
|
f->name = nil;
|
||
|
else
|
||
|
f->name = runemalloc(u.n);
|
||
|
bufread(delta, up, f->name, u.n);
|
||
|
f->nname = u.n;
|
||
|
break;
|
||
|
*/
|
||
|
}
|
||
|
bufdelete(log, up, log->nc);
|
||
|
}
|
||
|
fbuffree(buf);
|
||
|
elogterm(f);
|
||
|
|
||
|
/*
|
||
|
* Bad addresses will cause bufload to crash, so double check.
|
||
|
* If changes were out of order, we expect problems so don't complain further.
|
||
|
*/
|
||
|
if(t->q0 > f->nc || t->q1 > f->nc || t->q0 > t->q1){
|
||
|
if(!warned)
|
||
|
warning(nil, "elogapply: can't happen %d %d %d\n", t->q0, t->q1, f->nc);
|
||
|
t->q1 = min(t->q1, f->nc);
|
||
|
t->q0 = min(t->q0, t->q1);
|
||
|
}
|
||
|
|
||
|
if(t->w)
|
||
|
t->w->owner = owner;
|
||
|
}
|