Blame


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