elog.c 7.3 KB

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