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 }