URI:
       telog.c - plan9port - [fork] Plan 9 from user space
  HTML git clone git://src.adamsgaard.dk/plan9port
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
       telog.c (7458B)
       ---
            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 static char Wsequence[] = "warning: changes out of sequence\n";
           17 static int        warned = FALSE;
           18 
           19 /*
           20  * Log of changes made by editing commands.  Three reasons for this:
           21  * 1) We want addresses in commands to apply to old file, not file-in-change.
           22  * 2) It's difficult to track changes correctly as things move, e.g. ,x m$
           23  * 3) This gives an opportunity to optimize by merging adjacent changes.
           24  * It's a little bit like the Undo/Redo log in Files, but Point 3) argues for a
           25  * separate implementation.  To do this well, we use Replace as well as
           26  * Insert and Delete
           27  */
           28 
           29 typedef struct Buflog Buflog;
           30 struct Buflog
           31 {
           32         short        type;                /* Replace, Filename */
           33         uint                q0;                /* location of change (unused in f) */
           34         uint                nd;                /* # runes to delete */
           35         uint                nr;                /* # runes in string or file name */
           36 };
           37 
           38 enum
           39 {
           40         Buflogsize = sizeof(Buflog)/sizeof(Rune)
           41 };
           42 
           43 /*
           44  * Minstring shouldn't be very big or we will do lots of I/O for small changes.
           45  * Maxstring is RBUFSIZE so we can fbufalloc() once and not realloc elog.r.
           46  */
           47 enum
           48 {
           49         Minstring = 16,                /* distance beneath which we merge changes */
           50         Maxstring = RBUFSIZE        /* maximum length of change we will merge into one */
           51 };
           52 
           53 void
           54 eloginit(File *f)
           55 {
           56         if(f->elog.type != Empty)
           57                 return;
           58         f->elog.type = Null;
           59         if(f->elogbuf == nil)
           60                 f->elogbuf = emalloc(sizeof(Buffer));
           61         if(f->elog.r == nil)
           62                 f->elog.r = fbufalloc();
           63         bufreset(f->elogbuf);
           64 }
           65 
           66 void
           67 elogclose(File *f)
           68 {
           69         if(f->elogbuf){
           70                 bufclose(f->elogbuf);
           71                 free(f->elogbuf);
           72                 f->elogbuf = nil;
           73         }
           74 }
           75 
           76 void
           77 elogreset(File *f)
           78 {
           79         f->elog.type = Null;
           80         f->elog.nd = 0;
           81         f->elog.nr = 0;
           82 }
           83 
           84 void
           85 elogterm(File *f)
           86 {
           87         elogreset(f);
           88         if(f->elogbuf)
           89                 bufreset(f->elogbuf);
           90         f->elog.type = Empty;
           91         fbuffree(f->elog.r);
           92         f->elog.r = nil;
           93         warned = FALSE;
           94 }
           95 
           96 void
           97 elogflush(File *f)
           98 {
           99         Buflog b;
          100 
          101         b.type = f->elog.type;
          102         b.q0 = f->elog.q0;
          103         b.nd = f->elog.nd;
          104         b.nr = f->elog.nr;
          105         switch(f->elog.type){
          106         default:
          107                 warning(nil, "unknown elog type 0x%ux\n", f->elog.type);
          108                 break;
          109         case Null:
          110                 break;
          111         case Insert:
          112         case Replace:
          113                 if(f->elog.nr > 0)
          114                         bufinsert(f->elogbuf, f->elogbuf->nc, f->elog.r, f->elog.nr);
          115                 /* fall through */
          116         case Delete:
          117                 bufinsert(f->elogbuf, f->elogbuf->nc, (Rune*)&b, Buflogsize);
          118                 break;
          119         }
          120         elogreset(f);
          121 }
          122 
          123 void
          124 elogreplace(File *f, int q0, int q1, Rune *r, int nr)
          125 {
          126         uint gap;
          127 
          128         if(q0==q1 && nr==0)
          129                 return;
          130         eloginit(f);
          131         if(f->elog.type!=Null && q0<f->elog.q0){
          132                 if(warned++ == 0)
          133                         warning(nil, Wsequence);
          134                 elogflush(f);
          135         }
          136         /* try to merge with previous */
          137         gap = q0 - (f->elog.q0+f->elog.nd);        /* gap between previous and this */
          138         if(f->elog.type==Replace && f->elog.nr+gap+nr<Maxstring){
          139                 if(gap < Minstring){
          140                         if(gap > 0){
          141                                 bufread(&f->b, f->elog.q0+f->elog.nd, f->elog.r+f->elog.nr, gap);
          142                                 f->elog.nr += gap;
          143                         }
          144                         f->elog.nd += gap + q1-q0;
          145                         runemove(f->elog.r+f->elog.nr, r, nr);
          146                         f->elog.nr += nr;
          147                         return;
          148                 }
          149         }
          150         elogflush(f);
          151         f->elog.type = Replace;
          152         f->elog.q0 = q0;
          153         f->elog.nd = q1-q0;
          154         f->elog.nr = nr;
          155         if(nr > RBUFSIZE)
          156                 editerror("internal error: replacement string too large(%d)", nr);
          157         runemove(f->elog.r, r, nr);
          158 }
          159 
          160 void
          161 eloginsert(File *f, int q0, Rune *r, int nr)
          162 {
          163         int n;
          164 
          165         if(nr == 0)
          166                 return;
          167         eloginit(f);
          168         if(f->elog.type!=Null && q0<f->elog.q0){
          169                 if(warned++ == 0)
          170                         warning(nil, Wsequence);
          171                 elogflush(f);
          172         }
          173         /* try to merge with previous */
          174         if(f->elog.type==Insert && q0==f->elog.q0 && f->elog.nr+nr<Maxstring){
          175                 runemove(f->elog.r+f->elog.nr, r, nr);
          176                 f->elog.nr += nr;
          177                 return;
          178         }
          179         while(nr > 0){
          180                 elogflush(f);
          181                 f->elog.type = Insert;
          182                 f->elog.q0 = q0;
          183                 n = nr;
          184                 if(n > RBUFSIZE)
          185                         n = RBUFSIZE;
          186                 f->elog.nr = n;
          187                 runemove(f->elog.r, r, n);
          188                 r += n;
          189                 nr -= n;
          190         }
          191 }
          192 
          193 void
          194 elogdelete(File *f, int q0, int q1)
          195 {
          196         if(q0 == q1)
          197                 return;
          198         eloginit(f);
          199         if(f->elog.type!=Null && q0<f->elog.q0+f->elog.nd){
          200                 if(warned++ == 0)
          201                         warning(nil, Wsequence);
          202                 elogflush(f);
          203         }
          204         /* try to merge with previous */
          205         if(f->elog.type==Delete && f->elog.q0+f->elog.nd==q0){
          206                 f->elog.nd += q1-q0;
          207                 return;
          208         }
          209         elogflush(f);
          210         f->elog.type = Delete;
          211         f->elog.q0 = q0;
          212         f->elog.nd = q1-q0;
          213 }
          214 
          215 #define tracelog 0
          216 void
          217 elogapply(File *f)
          218 {
          219         Buflog b;
          220         Rune *buf;
          221         uint i, n, up, mod;
          222         uint tq0, tq1;
          223         Buffer *log;
          224         Text *t;
          225         int owner;
          226 
          227         elogflush(f);
          228         log = f->elogbuf;
          229         t = f->curtext;
          230 
          231         buf = fbufalloc();
          232         mod = FALSE;
          233 
          234         owner = 0;
          235         if(t->w){
          236                 owner = t->w->owner;
          237                 if(owner == 0)
          238                         t->w->owner = 'E';
          239         }
          240 
          241         /*
          242          * The edit commands have already updated the selection in t->q0, t->q1,
          243          * but using coordinates relative to the unmodified buffer.  As we apply the log,
          244          * we have to update the coordinates to be relative to the modified buffer.
          245          * Textinsert and textdelete will do this for us; our only work is to apply the
          246          * convention that an insertion at t->q0==t->q1 is intended to select the
          247          * inserted text.
          248          */
          249 
          250         /*
          251          * We constrain the addresses in here (with textconstrain()) because
          252          * overlapping changes will generate bogus addresses.   We will warn
          253          * about changes out of sequence but proceed anyway; here we must
          254          * keep things in range.
          255          */
          256 
          257         while(log->nc > 0){
          258                 up = log->nc-Buflogsize;
          259                 bufread(log, up, (Rune*)&b, Buflogsize);
          260                 switch(b.type){
          261                 default:
          262                         fprint(2, "elogapply: 0x%ux\n", b.type);
          263                         abort();
          264                         break;
          265 
          266                 case Replace:
          267                         if(tracelog)
          268                                 warning(nil, "elog replace %d %d (%d %d)\n",
          269                                         b.q0, b.q0+b.nd, t->q0, t->q1);
          270                         if(!mod){
          271                                 mod = TRUE;
          272                                 filemark(f);
          273                         }
          274                         textconstrain(t, b.q0, b.q0+b.nd, &tq0, &tq1);
          275                         textdelete(t, tq0, tq1, TRUE);
          276                         up -= b.nr;
          277                         for(i=0; i<b.nr; i+=n){
          278                                 n = b.nr - i;
          279                                 if(n > RBUFSIZE)
          280                                         n = RBUFSIZE;
          281                                 bufread(log, up+i, buf, n);
          282                                 textinsert(t, tq0+i, buf, n, TRUE);
          283                         }
          284                         if(t->q0 == b.q0 && t->q1 == b.q0)
          285                                 t->q1 += b.nr;
          286                         break;
          287 
          288                 case Delete:
          289                         if(tracelog)
          290                                 warning(nil, "elog delete %d %d (%d %d)\n",
          291                                         b.q0, b.q0+b.nd, t->q0, t->q1);
          292                         if(!mod){
          293                                 mod = TRUE;
          294                                 filemark(f);
          295                         }
          296                         textconstrain(t, b.q0, b.q0+b.nd, &tq0, &tq1);
          297                         textdelete(t, tq0, tq1, TRUE);
          298                         break;
          299 
          300                 case Insert:
          301                         if(tracelog)
          302                                 warning(nil, "elog insert %d %d (%d %d)\n",
          303                                         b.q0, b.q0+b.nr, t->q0, t->q1);
          304                         if(!mod){
          305                                 mod = TRUE;
          306                                 filemark(f);
          307                         }
          308                         textconstrain(t, b.q0, b.q0, &tq0, &tq1);
          309                         up -= b.nr;
          310                         for(i=0; i<b.nr; i+=n){
          311                                 n = b.nr - i;
          312                                 if(n > RBUFSIZE)
          313                                         n = RBUFSIZE;
          314                                 bufread(log, up+i, buf, n);
          315                                 textinsert(t, tq0+i, buf, n, TRUE);
          316                         }
          317                         if(t->q0 == b.q0 && t->q1 == b.q0)
          318                                 t->q1 += b.nr;
          319                         break;
          320 
          321 /*                case Filename:
          322                         f->seq = u.seq;
          323                         fileunsetname(f, epsilon);
          324                         f->mod = u.mod;
          325                         up -= u.n;
          326                         free(f->name);
          327                         if(u.n == 0)
          328                                 f->name = nil;
          329                         else
          330                                 f->name = runemalloc(u.n);
          331                         bufread(delta, up, f->name, u.n);
          332                         f->nname = u.n;
          333                         break;
          334 */
          335                 }
          336                 bufdelete(log, up, log->nc);
          337         }
          338         fbuffree(buf);
          339         elogterm(f);
          340 
          341         /*
          342          * Bad addresses will cause bufload to crash, so double check.
          343          * If changes were out of order, we expect problems so don't complain further.
          344          */
          345         if(t->q0 > f->b.nc || t->q1 > f->b.nc || t->q0 > t->q1){
          346                 if(!warned)
          347                         warning(nil, "elogapply: can't happen %d %d %d\n", t->q0, t->q1, f->b.nc);
          348                 t->q1 = min(t->q1, f->b.nc);
          349                 t->q0 = min(t->q0, t->q1);
          350         }
          351 
          352         if(t->w)
          353                 t->w->owner = owner;
          354 }