9fans.net/go@v0.0.7/cmd/acme/internal/wind/wind.go (about) 1 package wind 2 3 import ( 4 "fmt" 5 "os" 6 "strings" 7 "sync" 8 "unsafe" 9 10 "9fans.net/go/cmd/acme/internal/adraw" 11 "9fans.net/go/cmd/acme/internal/alog" 12 "9fans.net/go/cmd/acme/internal/bufs" 13 "9fans.net/go/cmd/acme/internal/file" 14 "9fans.net/go/cmd/acme/internal/runes" 15 "9fans.net/go/cmd/acme/internal/util" 16 "9fans.net/go/draw" 17 "9fans.net/go/draw/frame" 18 ) 19 20 type Window struct { 21 lk sync.Mutex 22 Ref uint32 23 Tag Text 24 Body Text 25 R draw.Rectangle 26 IsDir bool 27 IsScratch bool 28 Filemenu bool 29 Dirty bool 30 Autoindent bool 31 Showdel bool 32 ID int 33 Addr runes.Range 34 Limit runes.Range 35 Nomark bool 36 Wrselrange runes.Range 37 Rdselfd *os.File 38 Col *Column 39 Eventtag uint16 40 Eventwait chan bool 41 Events []byte 42 Owner rune 43 Maxlines int 44 Dlp []*Dirlist 45 Putseq int 46 Incl [][]rune 47 reffont *adraw.RefFont 48 Ctllock sync.Mutex 49 Ctlfid int 50 Dumpstr string 51 Dumpdir string 52 dumpid int 53 Utflastqid int 54 Utflastboff int64 55 Utflastq int 56 Tagsafe bool 57 Tagexpand bool 58 Taglines int 59 tagtop draw.Rectangle 60 Editoutlk util.QLock 61 External bool 62 } 63 64 // Text.what 65 66 const ( 67 Columntag = iota 68 Rowtag 69 Tag 70 Body 71 ) 72 73 type Dirlist struct { 74 R []rune 75 Wid int 76 } 77 78 var GlobalIncref int 79 80 // extern var wdir [unknown]C.char /* must use extern because no dimension given */ 81 var GlobalAutoindent bool 82 83 var Activewin *Window 84 85 var winid int 86 87 func Init(w *Window, clone *Window, r draw.Rectangle) { 88 w.Tag.W = w 89 w.Taglines = 1 90 w.Tagexpand = true 91 w.Body.W = w 92 winid++ 93 w.ID = winid 94 util.Incref(&w.Ref) 95 if GlobalIncref != 0 { 96 util.Incref(&w.Ref) 97 } 98 w.Ctlfid = ^0 99 w.Utflastqid = -1 100 r1 := r 101 102 w.tagtop = r 103 w.tagtop.Max.Y = r.Min.Y + adraw.Font.Height 104 r1.Max.Y = r1.Min.Y + w.Taglines*adraw.Font.Height 105 106 util.Incref(&adraw.RefFont1.Ref) 107 f := fileaddtext(nil, &w.Tag) 108 textinit(&w.Tag, f, r1, &adraw.RefFont1, adraw.TagCols[:]) 109 w.Tag.What = Tag 110 // tag is a copy of the contents, not a tracked image 111 if clone != nil { 112 Textdelete(&w.Tag, 0, w.Tag.Len(), true) 113 nc := clone.Tag.Len() 114 rp := make([]rune, nc) 115 clone.Tag.File.Read(0, rp) 116 Textinsert(&w.Tag, 0, rp, true) 117 w.Tag.File.ResetLogs() 118 Textsetselect(&w.Tag, nc, nc) 119 } 120 r1 = r 121 r1.Min.Y += w.Taglines*adraw.Font.Height + 1 122 if r1.Max.Y < r1.Min.Y { 123 r1.Max.Y = r1.Min.Y 124 } 125 f = nil 126 var rf *adraw.RefFont 127 if clone != nil { 128 f = clone.Body.File 129 w.Body.Org = clone.Body.Org 130 w.IsScratch = clone.IsScratch 131 rf = adraw.FindFont(false, false, false, clone.Body.Reffont.F.Name) 132 } else { 133 rf = adraw.FindFont(false, false, false, "") 134 } 135 f = fileaddtext(f, &w.Body) 136 w.Body.What = Body 137 textinit(&w.Body, f, r1, rf, adraw.TextCols[:]) 138 r1.Min.Y -= 1 139 r1.Max.Y = r1.Min.Y + 1 140 adraw.Display.ScreenImage.Draw(r1, adraw.TagCols[frame.BORD], nil, draw.ZP) 141 Textscrdraw(&w.Body) 142 w.R = r 143 var br draw.Rectangle 144 br.Min = w.Tag.ScrollR.Min 145 br.Max.X = br.Min.X + adraw.Button.R.Dx() 146 br.Max.Y = br.Min.Y + adraw.Button.R.Dy() 147 adraw.Display.ScreenImage.Draw(br, adraw.Button, nil, adraw.Button.R.Min) 148 w.Filemenu = true 149 w.Maxlines = w.Body.Fr.MaxLines 150 w.Autoindent = GlobalAutoindent 151 if clone != nil { 152 w.Dirty = clone.Dirty 153 w.Autoindent = clone.Autoindent 154 Textsetselect(&w.Body, clone.Body.Q0, clone.Body.Q1) 155 Winsettag(w) 156 } 157 } 158 159 /* 160 * Draw the appropriate button. 161 */ 162 func windrawbutton(w *Window) { 163 b := adraw.Button 164 if !w.IsDir && !w.IsScratch && (w.Body.File.Mod() || len(w.Body.Cache) != 0) { 165 b = adraw.ModButton 166 } 167 var br draw.Rectangle 168 br.Min = w.Tag.ScrollR.Min 169 br.Max.X = br.Min.X + b.R.Dx() 170 br.Max.Y = br.Min.Y + b.R.Dy() 171 adraw.Display.ScreenImage.Draw(br, b, nil, b.R.Min) 172 } 173 174 func Delrunepos(w *Window) int { 175 _, i := parsetag(w, 0) 176 i += 2 177 if i >= w.Tag.Len() { 178 return -1 179 } 180 return i 181 } 182 183 /* 184 * Compute number of tag lines required 185 * to display entire tag text. 186 */ 187 func wintaglines(w *Window, r draw.Rectangle) int { 188 if !w.Tagexpand && !w.Showdel { 189 return 1 190 } 191 w.Showdel = false 192 w.Tag.Fr.NoRedraw = 1 193 Textresize(&w.Tag, r, true) 194 w.Tag.Fr.NoRedraw = 0 195 w.Tagsafe = false 196 var n int 197 198 if !w.Tagexpand { 199 // use just as many lines as needed to show the Del 200 n = Delrunepos(w) 201 if n < 0 { 202 return 1 203 } 204 p := w.Tag.Fr.PointOf(n).Sub(w.Tag.Fr.R.Min) 205 return 1 + p.Y/w.Tag.Fr.Font.Height 206 } 207 208 // can't use more than we have 209 if w.Tag.Fr.NumLines >= w.Tag.Fr.MaxLines { 210 return w.Tag.Fr.MaxLines 211 } 212 213 // if tag ends with \n, include empty line at end for typing 214 n = w.Tag.Fr.NumLines 215 if w.Tag.Len() > 0 { 216 var rune_ [1]rune 217 w.Tag.File.Read(w.Tag.Len()-1, rune_[:]) 218 if rune_[0] == '\n' { 219 n++ 220 } 221 } 222 if n == 0 { 223 n = 1 224 } 225 return n 226 } 227 228 func Winresize(w *Window, r draw.Rectangle, safe, keepextra bool) int { 229 // tagtop is first line of tag 230 w.tagtop = r 231 w.tagtop.Max.Y = r.Min.Y + adraw.Font.Height 232 233 r1 := r 234 r1.Max.Y = util.Min(r.Max.Y, r1.Min.Y+w.Taglines*adraw.Font.Height) 235 236 // If needed, recompute number of lines in tag. 237 if !safe || !w.Tagsafe || !(w.Tag.All == r1) { 238 w.Taglines = wintaglines(w, r) 239 r1.Max.Y = util.Min(r.Max.Y, r1.Min.Y+w.Taglines*adraw.Font.Height) 240 } 241 242 // If needed, resize & redraw tag. 243 y := r1.Max.Y 244 if !safe || !w.Tagsafe || !(w.Tag.All == r1) { 245 Textresize(&w.Tag, r1, true) 246 y = w.Tag.Fr.R.Max.Y 247 windrawbutton(w) 248 w.Tagsafe = true 249 } 250 251 // If needed, resize & redraw body. 252 r1 = r 253 r1.Min.Y = y 254 if !safe || !(w.Body.All == r1) { 255 oy := y 256 if y+1+w.Body.Fr.Font.Height <= r.Max.Y { // room for one line 257 r1.Min.Y = y 258 r1.Max.Y = y + 1 259 adraw.Display.ScreenImage.Draw(r1, adraw.TagCols[frame.BORD], nil, draw.ZP) 260 y++ 261 r1.Min.Y = util.Min(y, r.Max.Y) 262 r1.Max.Y = r.Max.Y 263 } else { 264 r1.Min.Y = y 265 r1.Max.Y = y 266 } 267 y = Textresize(&w.Body, r1, keepextra) 268 w.R = r 269 w.R.Max.Y = y 270 Textscrdraw(&w.Body) 271 w.Body.All.Min.Y = oy 272 } 273 w.Maxlines = util.Min(w.Body.Fr.NumLines, util.Max(w.Maxlines, w.Body.Fr.MaxLines)) 274 return w.R.Max.Y 275 } 276 277 func Winclean(w *Window, conservative bool) bool { 278 if w.IsScratch || w.IsDir { // don't whine if it's a guide file, error window, etc. 279 return true 280 } 281 if !conservative && w.External { 282 return true 283 } 284 if w.Dirty { 285 if len(w.Body.File.Name()) != 0 { 286 alog.Printf("%s modified\n", string(w.Body.File.Name())) 287 } else { 288 if w.Body.Len() < 100 { // don't whine if it's too small 289 return true 290 } 291 alog.Printf("unnamed file modified\n") 292 } 293 w.Dirty = false 294 return false 295 } 296 return true 297 } 298 299 func Winlock(w *Window, owner rune) { 300 f := w.Body.File 301 for i := 0; i < len(f.Text); i++ { 302 Winlock1(f.Text[i].W, owner) 303 } 304 } 305 306 func Winlock1(w *Window, owner rune) { 307 util.Incref(&w.Ref) 308 w.lk.Lock() 309 w.Owner = owner 310 } 311 312 func Winunlock(w *Window) { 313 /* 314 * subtle: loop runs backwards to avoid tripping over 315 * winclose indirectly editing f->text and freeing f 316 * on the last iteration of the loop. 317 */ 318 f := w.Body.File 319 for i := len(f.Text) - 1; i >= 0; i-- { 320 w = f.Text[i].W 321 w.Owner = 0 322 w.lk.Unlock() 323 Winclose(w) 324 } 325 } 326 327 func Windirfree(w *Window) { 328 w.Dlp = nil 329 } 330 331 var OnWinclose func(*Window) 332 333 func Winclose(w *Window) { 334 if util.Decref(&w.Ref) == 0 { 335 if OnWinclose != nil { 336 OnWinclose(w) 337 } 338 Windirfree(w) 339 textclose(&w.Tag) 340 textclose(&w.Body) 341 if Activewin == w { 342 Activewin = nil 343 } 344 } 345 } 346 347 func windelete(w *Window) { 348 c := w.Eventwait 349 if c != nil { 350 w.Events = nil 351 w.Eventwait = nil 352 c <- true // wake him up 353 } 354 } 355 356 func Winundo(w *Window, isundo bool) { 357 w.Utflastqid = -1 358 body := &w.Body 359 body.File.Undo(isundo, &body.Q0, &body.Q1) 360 Textshow(body, body.Q0, body.Q1, true) 361 f := body.File 362 for i := 0; i < len(f.Text); i++ { 363 v := f.Text[i].W 364 v.Dirty = (f.Seq() != v.Putseq) 365 if v != w { 366 v.Body.Q0 = v.Body.Fr.P0 + v.Body.Org 367 v.Body.Q1 = v.Body.Fr.P1 + v.Body.Org 368 } 369 } 370 Winsettag(w) 371 } 372 373 func Winsetname(w *Window, name []rune) { 374 t := &w.Body 375 if runes.Equal(t.File.Name(), name) { 376 return 377 } 378 w.IsScratch = false 379 if len(name) >= 6 && runes.Equal([]rune("/guide"), name[len(name)-6:]) { 380 w.IsScratch = true 381 } else if len(name) >= 7 && runes.Equal([]rune("+Errors"), name[len(name)-7:]) { 382 w.IsScratch = true 383 } 384 t.File.SetName(name) 385 for i := 0; i < len(t.File.Text); i++ { 386 v := t.File.Text[i].W 387 Winsettag(v) 388 v.IsScratch = w.IsScratch 389 } 390 } 391 392 func Wincleartatg(w *Window) { 393 // w must be committed 394 n := w.Tag.Len() 395 r, i := parsetag(w, 0) 396 for ; i < n; i++ { 397 if r[i] == '|' { 398 break 399 } 400 } 401 if i == n { 402 return 403 } 404 i++ 405 Textdelete(&w.Tag, i, n, true) 406 w.Tag.File.SetMod(false) 407 if w.Tag.Q0 > i { 408 w.Tag.Q0 = i 409 } 410 if w.Tag.Q1 > i { 411 w.Tag.Q1 = i 412 } 413 Textsetselect(&w.Tag, w.Tag.Q0, w.Tag.Q1) 414 } 415 416 func parsetag(w *Window, extra int) ([]rune, int) { 417 r := make([]rune, w.Tag.Len(), w.Tag.Len()+extra+1) 418 w.Tag.File.Read(0, r) 419 420 /* 421 * " |" or "\t|" ends left half of tag 422 * If we find " Del Snarf" in the left half of the tag 423 * (before the pipe), that ends the file name. 424 */ 425 pipe := runes.Index(r, []rune(" |")) 426 p := runes.Index(r, []rune("\t|")) 427 if p >= 0 && (pipe < 0 || p < pipe) { 428 pipe = p 429 } 430 p = runes.Index(r, []rune(" Del Snarf")) 431 var i int 432 if p >= 0 && (pipe < 0 || p < pipe) { 433 i = p 434 } else { 435 for i = 0; i < w.Tag.Len(); i++ { 436 if r[i] == ' ' || r[i] == '\t' { 437 break 438 } 439 } 440 } 441 return r, i 442 } 443 444 func Winsettag(w *Window) { 445 f := w.Body.File 446 for i := 0; i < len(f.Text); i++ { 447 v := f.Text[i].W 448 if v.Col.Safe || v.Body.Fr.MaxLines > 0 { 449 winsettag1(v) 450 } 451 } 452 } 453 454 func winsettag1(w *Window) { 455 456 // there are races that get us here with stuff in the tag cache, so we take extra care to sync it 457 if len(w.Tag.Cache) != 0 || w.Tag.File.Mod() { 458 Wincommit(w, &w.Tag) // check file name; also guarantees we can modify tag contents 459 } 460 old, ii := parsetag(w, 0) 461 if !runes.Equal(old[:ii], w.Body.File.Name()) { 462 Textdelete(&w.Tag, 0, ii, true) 463 Textinsert(&w.Tag, 0, w.Body.File.Name(), true) 464 old = make([]rune, w.Tag.Len()) 465 w.Tag.File.Read(0, old) 466 } 467 468 // compute the text for the whole tag, replacing current only if it differs 469 new_ := make([]rune, 0, len(w.Body.File.Name())+100) 470 new_ = append(new_, w.Body.File.Name()...) 471 new_ = append(new_, []rune(" Del Snarf")...) 472 if w.Filemenu { 473 if w.Body.Needundo || w.Body.File.CanUndo() || len(w.Body.Cache) != 0 { 474 new_ = append(new_, []rune(" Undo")...) 475 } 476 if w.Body.File.CanRedo() { 477 new_ = append(new_, []rune(" Redo")...) 478 } 479 dirty := len(w.Body.File.Name()) != 0 && (len(w.Body.Cache) != 0 || w.Body.File.Seq() != w.Putseq) 480 if !w.IsDir && dirty { 481 new_ = append(new_, []rune(" Put")...) 482 } 483 } 484 if w.IsDir { 485 new_ = append(new_, []rune(" Get")...) 486 } 487 new_ = append(new_, []rune(" |")...) 488 r := runes.IndexRune(old, '|') 489 var k int 490 if r >= 0 { 491 k = r + 1 492 } else { 493 k = len(old) 494 if w.Body.File.Seq() == 0 { 495 new_ = append(new_, []rune(" Look ")...) 496 } 497 } 498 499 // replace tag if the new one is different 500 resize := 0 501 var n int 502 if !runes.Equal(new_, old[:k]) { 503 resize = 1 504 n = k 505 if n > len(new_) { 506 n = len(new_) 507 } 508 var j int 509 for j = 0; j < n; j++ { 510 if old[j] != new_[j] { 511 break 512 } 513 } 514 q0 := w.Tag.Q0 515 q1 := w.Tag.Q1 516 Textdelete(&w.Tag, j, k, true) 517 Textinsert(&w.Tag, j, new_[j:], true) 518 // try to preserve user selection 519 r = runes.IndexRune(old, '|') 520 if r >= 0 { 521 bar := r 522 if q0 > bar { 523 bar = runes.IndexRune(new_, '|') - bar 524 w.Tag.Q0 = q0 + bar 525 w.Tag.Q1 = q1 + bar 526 } 527 } 528 } 529 w.Tag.File.SetMod(false) 530 n = w.Tag.Len() + len(w.Tag.Cache) 531 if w.Tag.Q0 > n { 532 w.Tag.Q0 = n 533 } 534 if w.Tag.Q1 > n { 535 w.Tag.Q1 = n 536 } 537 Textsetselect(&w.Tag, w.Tag.Q0, w.Tag.Q1) 538 windrawbutton(w) 539 if resize != 0 { 540 w.Tagsafe = false 541 Winresize(w, w.R, true, true) 542 } 543 } 544 545 func Wincommit(w *Window, t *Text) { 546 Textcommit(t, true) 547 f := t.File 548 var i int 549 if len(f.Text) > 1 { 550 for i = 0; i < len(f.Text); i++ { 551 Textcommit(f.Text[i], false) // no-op for t 552 } 553 } 554 if t.What == Body { 555 return 556 } 557 r, i := parsetag(w, 0) 558 if !runes.Equal(r[:i], w.Body.File.Name()) { 559 file.Seq++ 560 w.Body.File.Mark() 561 w.Body.File.SetMod(true) 562 w.Dirty = true 563 Winsetname(w, r[:i]) 564 Winsettag(w) 565 } 566 } 567 568 func Winaddincl(w *Window, r []rune) { 569 a := string(r) 570 info, err := os.Stat(a) 571 if err != nil { 572 if !strings.HasPrefix(a, "/") { 573 rs := Dirname(&w.Body, r) 574 r = rs 575 a = string(r) 576 info, err = os.Stat(a) 577 } 578 if err != nil { 579 alog.Printf("%s: %v", a, err) 580 return 581 } 582 } 583 if !info.IsDir() { 584 alog.Printf("%s: not a directory\n", a) 585 return 586 } 587 w.Incl = append(w.Incl, nil) 588 copy(w.Incl[1:], w.Incl) 589 w.Incl[0] = runes.Clone(r) 590 } 591 592 func Winctlprint(w *Window, fonts bool) string { 593 isdir := 0 594 if w.IsDir { 595 isdir = 1 596 } 597 dirty := 0 598 if w.Dirty { 599 dirty = 1 600 } 601 base := fmt.Sprintf("%11d %11d %11d %11d %11d ", w.ID, w.Tag.Len(), w.Body.Len(), isdir, dirty) 602 if fonts { 603 base += fmt.Sprintf("%11d %q %11d ", w.Body.Fr.R.Dx(), w.Body.Reffont.F.Name, w.Body.Fr.MaxTab) 604 } 605 return base 606 } 607 608 // fbufalloc() guarantees room off end of BUFSIZE 609 const ( 610 BUFSIZE = 8192 611 RUNESIZE = int(unsafe.Sizeof(rune(0))) 612 RBUFSIZE = bufs.Len / runes.RuneSize 613 EVENTSIZE = 256 614 ) 615 616 func Winevent(w *Window, format string, args ...interface{}) { 617 if !w.External { 618 return 619 } 620 if w.Owner == 0 { 621 util.Fatal("no window owner") 622 } 623 b := fmt.Sprintf(format, args...) 624 w.Events = append(w.Events, byte(w.Owner)) 625 w.Events = append(w.Events, b...) 626 c := w.Eventwait 627 if c != nil { 628 w.Eventwait = nil 629 c <- true 630 } 631 }