9fans.net/go@v0.0.7/cmd/acme/internal/wind/text.go (about) 1 package wind 2 3 import ( 4 "fmt" 5 "os" 6 7 "9fans.net/go/cmd/acme/internal/adraw" 8 "9fans.net/go/cmd/acme/internal/bufs" 9 "9fans.net/go/cmd/acme/internal/file" 10 "9fans.net/go/cmd/acme/internal/runes" 11 "9fans.net/go/cmd/acme/internal/util" 12 "9fans.net/go/draw" 13 "9fans.net/go/draw/frame" 14 ) 15 16 type Text struct { 17 File *File 18 Fr frame.Frame 19 Reffont *adraw.RefFont 20 Org int 21 Q0 int 22 Q1 int 23 What int 24 Tabstop int 25 W *Window 26 ScrollR draw.Rectangle 27 lastsr draw.Rectangle 28 All draw.Rectangle 29 Row *Row 30 Col *Column 31 IQ1 int 32 Eq0 int 33 Cq0 int 34 Cache []rune 35 Nofill bool 36 Needundo bool 37 } 38 39 func (t *Text) RuneAt(pos int) rune { return textreadc(t, pos) } 40 41 func (t *Text) Len() int { return t.File.Len() } 42 43 type File struct { 44 *file.File 45 Curtext *Text 46 Text []*Text 47 Info os.FileInfo 48 SHA1 [20]byte 49 Unread bool 50 dumpid int 51 } 52 53 func (f *File) SetName(r []rune) { 54 f.File.SetName(r) 55 f.Unread = true 56 } 57 58 type fileView File 59 60 func (f *fileView) Insert(pos int, data []rune) { 61 for _, t := range f.Text { 62 Textinsert(t, pos, data, false) 63 } 64 } 65 66 func (f *fileView) Delete(pos, end int) { 67 for _, t := range f.Text { 68 Textdelete(t, pos, end, false) 69 } 70 } 71 72 var Argtext *Text 73 74 var Typetext *Text // global because Text.close needs to clear it 75 76 var Seltext *Text 77 78 var Mousetext *Text // global because Text.close needs to clear it 79 80 var Barttext *Text // shared between mousetask and keyboardthread 81 82 const ( 83 TABDIR = 3 84 ) // width of tabs in directory windows 85 86 var MaxTab int // size of a tab, in units of the '0' character 87 88 func textinit(t *Text, f *File, r draw.Rectangle, rf *adraw.RefFont, cols []*draw.Image) { 89 t.File = f 90 t.All = r 91 t.ScrollR = r 92 t.ScrollR.Max.X = r.Min.X + adraw.Scrollwid() 93 t.lastsr = draw.ZR 94 r.Min.X += adraw.Scrollwid() + adraw.Scrollgap() 95 t.Eq0 = ^0 96 t.Cache = t.Cache[:0] 97 t.Reffont = rf 98 t.Tabstop = MaxTab 99 copy(t.Fr.Cols[:], cols) 100 textredraw(t, r, rf.F, adraw.Display.ScreenImage, -1) 101 } 102 103 func textclose(t *Text) { 104 t.Fr.Clear(true) 105 filedeltext(t.File, t) 106 t.File = nil 107 adraw.CloseFont(t.Reffont) 108 if Argtext == t { 109 Argtext = nil 110 } 111 if Typetext == t { 112 Typetext = nil 113 } 114 if Seltext == t { 115 Seltext = nil 116 } 117 if Mousetext == t { 118 Mousetext = nil 119 } 120 if Barttext == t { 121 Barttext = nil 122 } 123 } 124 125 func Textreset(t *Text) { 126 t.File.SetSeq(0) 127 t.Eq0 = ^0 128 // do t->delete(0, t->nc, TRUE) without building backup stuff 129 Textsetselect(t, t.Org, t.Org) 130 t.Fr.Delete(0, t.Fr.NumChars) 131 t.Org = 0 132 t.Q0 = 0 133 t.Q1 = 0 134 t.File.ResetLogs() 135 t.File.Truncate() 136 } 137 138 func textreadc(t *Text, q int) rune { 139 var r [1]rune 140 if t.Cq0 <= q && q < t.Cq0+len(t.Cache) { 141 r[0] = t.Cache[q-t.Cq0] 142 } else { 143 t.File.Read(q, r[:]) 144 } 145 return r[0] 146 } 147 148 func textredraw(t *Text, r draw.Rectangle, f *draw.Font, b *draw.Image, odx int) { 149 t.Fr.Init(r, f, b, t.Fr.Cols[:]) 150 rr := t.Fr.R 151 rr.Min.X -= adraw.Scrollwid() + adraw.Scrollgap() // back fill to scroll bar 152 if t.Fr.NoRedraw == 0 { 153 t.Fr.B.Draw(rr, t.Fr.Cols[frame.BACK], nil, draw.ZP) 154 } 155 // use no wider than 3-space tabs in a directory 156 maxt := MaxTab 157 if t.What == Body { 158 if t.W.IsDir { 159 maxt = util.Min(TABDIR, MaxTab) 160 } else { 161 maxt = t.Tabstop 162 } 163 } 164 t.Fr.MaxTab = maxt * f.StringWidth("0") 165 if t.What == Body && t.W.IsDir && odx != t.All.Dx() { 166 if t.Fr.MaxLines > 0 { 167 Textreset(t) 168 Textcolumnate(t, t.W.Dlp) 169 Textshow(t, 0, 0, true) 170 } 171 } else { 172 Textfill(t) 173 Textsetselect(t, t.Q0, t.Q1) 174 } 175 } 176 177 func Textfill(t *Text) { 178 if t.Fr.LastLineFull || t.Nofill { 179 return 180 } 181 if len(t.Cache) > 0 { 182 Typecommit(t) 183 } 184 rp := bufs.AllocRunes() 185 for { 186 n := t.Len() - (t.Org + t.Fr.NumChars) 187 if n == 0 { 188 break 189 } 190 if n > 2000 { // educated guess at reasonable amount 191 n = 2000 192 } 193 t.File.Read(t.Org+t.Fr.NumChars, rp[:n]) 194 /* 195 * it's expensive to frinsert more than we need, so 196 * count newlines. 197 */ 198 nl := t.Fr.MaxLines - t.Fr.NumLines 199 m := 0 200 var i int 201 for i = 0; i < n; { 202 tmp25 := i 203 i++ 204 if rp[tmp25] == '\n' { 205 m++ 206 if m >= nl { 207 break 208 } 209 } 210 } 211 t.Fr.Insert(rp[:i], t.Fr.NumChars) 212 if t.Fr.LastLineFull { 213 break 214 } 215 } 216 bufs.FreeRunes(rp) 217 } 218 219 func Textresize(t *Text, r draw.Rectangle, keepextra bool) int { 220 if r.Dy() <= 0 { 221 r.Max.Y = r.Min.Y 222 } else if !keepextra { 223 r.Max.Y -= r.Dy() % t.Fr.Font.Height 224 } 225 odx := t.All.Dx() 226 t.All = r 227 t.ScrollR = r 228 t.ScrollR.Max.X = r.Min.X + adraw.Scrollwid() 229 t.lastsr = draw.ZR 230 r.Min.X += adraw.Scrollwid() + adraw.Scrollgap() 231 t.Fr.Clear(false) 232 textredraw(t, r, t.Fr.Font, t.Fr.B, odx) 233 if keepextra && t.Fr.R.Max.Y < t.All.Max.Y && t.Fr.NoRedraw == 0 { 234 // draw background in bottom fringe of window 235 r.Min.X -= adraw.Scrollgap() 236 r.Min.Y = t.Fr.R.Max.Y 237 r.Max.Y = t.All.Max.Y 238 adraw.Display.ScreenImage.Draw(r, t.Fr.Cols[frame.BACK], nil, draw.ZP) 239 } 240 return t.All.Max.Y 241 } 242 243 func Textcolumnate(t *Text, dlp []*Dirlist) { 244 if len(t.File.Text) > 1 { 245 return 246 } 247 mint := t.Fr.Font.StringWidth("0") 248 // go for narrower tabs if set more than 3 wide 249 t.Fr.MaxTab = util.Min(MaxTab, TABDIR) * mint 250 maxt := t.Fr.MaxTab 251 colw := 0 252 var i int 253 var w int 254 var dl *Dirlist 255 for i = 0; i < len(dlp); i++ { 256 dl = dlp[i] 257 w = dl.Wid 258 if maxt-w%maxt < mint || w%maxt == 0 { 259 w += mint 260 } 261 if w%maxt != 0 { 262 w += maxt - (w % maxt) 263 } 264 if w > colw { 265 colw = w 266 } 267 } 268 var ncol int 269 if colw == 0 { 270 ncol = 1 271 } else { 272 ncol = util.Max(1, t.Fr.R.Dx()/colw) 273 } 274 nrow := (len(dlp) + ncol - 1) / ncol 275 276 q1 := 0 277 for i = 0; i < nrow; i++ { 278 for j := i; j < len(dlp); j += nrow { 279 dl = dlp[j] 280 t.File.Insert(q1, dl.R) 281 q1 += len(dl.R) 282 if j+nrow >= len(dlp) { 283 break 284 } 285 w = dl.Wid 286 if maxt-w%maxt < mint { 287 t.File.Insert(q1, []rune("\t")) 288 q1++ 289 w += mint 290 } 291 for { 292 t.File.Insert(q1, []rune("\t")) 293 q1++ 294 w += maxt - (w % maxt) 295 if w >= colw { 296 break 297 } 298 } 299 } 300 t.File.Insert(q1, []rune("\n")) 301 q1++ 302 } 303 } 304 305 func Textinsert(t *Text, q0 int, r []rune, tofile bool) { 306 if tofile && len(t.Cache) > 0 { 307 util.Fatal("text.insert") 308 } 309 if len(r) == 0 { 310 return 311 } 312 if tofile { 313 t.File.Insert(q0, r) 314 if t.What == Body { 315 t.W.Dirty = true 316 t.W.Utflastqid = -1 317 } 318 if len(t.File.Text) > 1 { 319 for i := 0; i < len(t.File.Text); i++ { 320 u := t.File.Text[i] 321 if u != t { 322 u.W.Dirty = true // always a body 323 Textinsert(u, q0, r, false) 324 Textsetselect(u, u.Q0, u.Q1) 325 Textscrdraw(u) 326 } 327 } 328 } 329 330 } 331 if q0 < t.IQ1 { 332 t.IQ1 += len(r) 333 } 334 if q0 < t.Q1 { 335 t.Q1 += len(r) 336 } 337 if q0 < t.Q0 { 338 t.Q0 += len(r) 339 } 340 if q0 < t.Org { 341 t.Org += len(r) 342 } else if q0 <= t.Org+t.Fr.NumChars { 343 t.Fr.Insert(r, q0-t.Org) 344 } 345 if t.W != nil { 346 c := 'i' 347 if t.What == Body { 348 c = 'I' 349 } 350 if len(r) <= EVENTSIZE { 351 Winevent(t.W, "%c%d %d 0 %d %s\n", c, q0, q0+len(r), len(r), string(r)) 352 } else { 353 Winevent(t.W, "%c%d %d 0 0 \n", c, q0, q0+len(r)) 354 } 355 } 356 } 357 358 func Textdelete(t *Text, q0 int, q1 int, tofile bool) { 359 if tofile && len(t.Cache) > 0 { 360 util.Fatal("text.delete") 361 } 362 n := q1 - q0 363 if n == 0 { 364 return 365 } 366 if tofile { 367 t.File.Delete(q0, q1) 368 if t.What == Body { 369 t.W.Dirty = true 370 t.W.Utflastqid = -1 371 } 372 if len(t.File.Text) > 1 { 373 for i := 0; i < len(t.File.Text); i++ { 374 u := t.File.Text[i] 375 if u != t { 376 u.W.Dirty = true // always a body 377 Textdelete(u, q0, q1, false) 378 Textsetselect(u, u.Q0, u.Q1) 379 Textscrdraw(u) 380 } 381 } 382 } 383 } 384 if q0 < t.IQ1 { 385 t.IQ1 -= util.Min(n, t.IQ1-q0) 386 } 387 if q0 < t.Q0 { 388 t.Q0 -= util.Min(n, t.Q0-q0) 389 } 390 if q0 < t.Q1 { 391 t.Q1 -= util.Min(n, t.Q1-q0) 392 } 393 if q1 <= t.Org { 394 t.Org -= n 395 } else if q0 < t.Org+t.Fr.NumChars { 396 p1 := q1 - t.Org 397 if p1 > t.Fr.NumChars { 398 p1 = t.Fr.NumChars 399 } 400 var p0 int 401 if q0 < t.Org { 402 t.Org = q0 403 p0 = 0 404 } else { 405 p0 = q0 - t.Org 406 } 407 t.Fr.Delete(p0, p1) 408 Textfill(t) 409 } 410 if t.W != nil { 411 c := 'd' 412 if t.What == Body { 413 c = 'D' 414 } 415 Winevent(t.W, "%c%d %d 0 0 \n", c, q0, q1) 416 } 417 } 418 419 func Textcommit(t *Text, tofile bool) { 420 if len(t.Cache) == 0 { 421 return 422 } 423 if tofile { 424 t.File.Insert(t.Cq0, t.Cache) 425 } 426 if t.What == Body { 427 t.W.Dirty = true 428 t.W.Utflastqid = -1 429 } 430 t.Cache = t.Cache[:0] 431 } 432 433 func Typecommit(t *Text) { 434 if t.W != nil { 435 Wincommit(t.W, t) 436 } else { 437 Textcommit(t, true) 438 } 439 } 440 441 func Textbsinsert(t *Text, q0 int, r []rune, tofile bool, nrp *int) int { 442 if t.What == Tag { // can't happen but safety first: mustn't backspace over file name 443 goto Err 444 } 445 446 for i := 0; i < len(r); i++ { 447 if r[i] == '\b' { 448 initial := 0 449 tp := make([]rune, len(r)) 450 copy(tp, r[:i]) 451 ti := i 452 for ; i < len(r); i++ { 453 tp[ti] = r[i] 454 if tp[ti] == '\b' { 455 if ti == 0 { 456 initial++ 457 } else { 458 ti-- 459 } 460 } else { 461 ti++ 462 } 463 } 464 if initial != 0 { 465 if initial > q0 { 466 initial = q0 467 } 468 q0 -= initial 469 Textdelete(t, q0, q0+initial, tofile) 470 } 471 Textinsert(t, q0, tp[:ti], tofile) 472 *nrp = ti 473 return q0 474 } 475 } 476 477 Err: 478 Textinsert(t, q0, r, tofile) 479 *nrp = len(r) 480 return q0 481 } 482 483 func Textbswidth(t *Text, c rune) int { 484 // there is known to be at least one character to erase 485 if c == 0x08 { // ^H: erase character 486 return 1 487 } 488 q := t.Q0 489 skipping := true 490 for q > 0 { 491 r := t.RuneAt(q - 1) 492 if r == '\n' { // eat at most one more character 493 if q == t.Q0 { // eat the newline 494 q-- 495 } 496 break 497 } 498 if c == 0x17 { 499 eq := runes.IsAlphaNum(r) 500 if eq && skipping { // found one; stop skipping 501 skipping = false 502 } else if !eq && !skipping { 503 break 504 } 505 } 506 q-- 507 } 508 return t.Q0 - q 509 } 510 511 func Textfilewidth(t *Text, q0 int, oneelement bool) int { 512 q := q0 513 for q > 0 { 514 r := t.RuneAt(q - 1) 515 if r <= ' ' { 516 break 517 } 518 if oneelement && r == '/' { 519 break 520 } 521 q-- 522 } 523 return q0 - q 524 } 525 526 func Textshow(t *Text, q0 int, q1 int, doselect bool) { 527 if t.What != Body { 528 if doselect { 529 Textsetselect(t, q0, q1) 530 } 531 return 532 } 533 if t.W != nil && t.Fr.MaxLines == 0 { 534 Colgrow(t.Col, t.W, 1) 535 } 536 if doselect { 537 Textsetselect(t, q0, q1) 538 } 539 qe := t.Org + t.Fr.NumChars 540 tsd := false // do we call textscrdraw? 541 nc := t.Len() + len(t.Cache) 542 if t.Org <= q0 { 543 if nc == 0 || q0 < qe { 544 tsd = true 545 } else if q0 == qe && qe == nc { 546 if t.RuneAt(nc-1) == '\n' { 547 if t.Fr.NumLines < t.Fr.MaxLines { 548 tsd = true 549 } 550 } else { 551 tsd = true 552 } 553 } 554 } 555 if tsd { 556 Textscrdraw(t) 557 } else { 558 var nl int 559 if t.W.External { 560 nl = 3 * t.Fr.MaxLines / 4 561 } else { 562 nl = t.Fr.MaxLines / 4 563 } 564 q := Textbacknl(t, q0, nl) 565 // avoid going backwards if trying to go forwards - long lines! 566 if !(q0 > t.Org) || !(q < t.Org) { 567 Textsetorigin(t, q, true) 568 } 569 for q0 > t.Org+t.Fr.NumChars { 570 Textsetorigin(t, t.Org+1, false) 571 } 572 } 573 } 574 575 func Textbacknl(t *Text, p int, n int) int { 576 // look for start of this line if n==0 577 if n == 0 && p > 0 && t.RuneAt(p-1) != '\n' { 578 n = 1 579 } 580 i := n 581 for { 582 tmp29 := i 583 i-- 584 if !(tmp29 > 0) || !(p > 0) { 585 break 586 } 587 p-- // it's at a newline now; back over it 588 if p == 0 { 589 break 590 } 591 // at 128 chars, call it a line anyway 592 for j := 128; ; p-- { 593 j-- 594 if !(j > 0) || !(p > 0) { 595 break 596 } 597 if t.RuneAt(p-1) == '\n' { 598 break 599 } 600 } 601 } 602 return p 603 } 604 605 func Textsetorigin(t *Text, org int, exact bool) { 606 if org > 0 && !exact && t.RuneAt(org-1) != '\n' { 607 // org is an estimate of the char posn; find a newline 608 // don't try harder than 256 chars 609 for i := 0; i < 256 && org < t.Len(); i++ { 610 if t.RuneAt(org) == '\n' { 611 org++ 612 break 613 } 614 org++ 615 } 616 } 617 a := org - t.Org 618 fixup := 0 619 if a >= 0 && a < t.Fr.NumChars { 620 t.Fr.Delete(0, a) 621 fixup = 1 // frdelete can leave end of last line in wrong selection mode; it doesn't know what follows 622 } else if a < 0 && -a < t.Fr.NumChars { 623 n := t.Org - org 624 r := make([]rune, n) 625 t.File.Read(org, r) 626 t.Fr.Insert(r, 0) 627 } else { 628 t.Fr.Delete(0, t.Fr.NumChars) 629 } 630 t.Org = org 631 Textfill(t) 632 Textscrdraw(t) 633 Textsetselect(t, t.Q0, t.Q1) 634 if fixup != 0 && t.Fr.P1 > t.Fr.P0 { 635 t.Fr.Drawsel(t.Fr.PointOf(t.Fr.P1-1), t.Fr.P1-1, t.Fr.P1, true) 636 } 637 } 638 639 func Region(a int, b int) int { 640 if a < b { 641 return -1 642 } 643 if a == b { 644 return 0 645 } 646 return 1 647 } 648 649 func Selrestore(f *frame.Frame, pt0 draw.Point, p0 int, p1 int) { 650 if p1 <= f.P0 || p0 >= f.P1 { 651 // no overlap 652 f.Drawsel0(pt0, p0, p1, f.Cols[frame.BACK], f.Cols[frame.TEXT]) 653 return 654 } 655 if p0 >= f.P0 && p1 <= f.P1 { 656 // entirely inside 657 f.Drawsel0(pt0, p0, p1, f.Cols[frame.HIGH], f.Cols[frame.HTEXT]) 658 return 659 } 660 661 // they now are known to overlap 662 663 // before selection 664 if p0 < f.P0 { 665 f.Drawsel0(pt0, p0, f.P0, f.Cols[frame.BACK], f.Cols[frame.TEXT]) 666 p0 = f.P0 667 pt0 = f.PointOf(p0) 668 } 669 // after selection 670 if p1 > f.P1 { 671 f.Drawsel0(f.PointOf(f.P1), f.P1, p1, f.Cols[frame.BACK], f.Cols[frame.TEXT]) 672 p1 = f.P1 673 } 674 // inside selection 675 f.Drawsel0(pt0, p0, p1, f.Cols[frame.HIGH], f.Cols[frame.HTEXT]) 676 } 677 678 func Textsetselect(t *Text, q0 int, q1 int) { 679 // t->fr.p0 and t->fr.p1 are always right; t->q0 and t->q1 may be off 680 t.Q0 = q0 681 t.Q1 = q1 682 // compute desired p0,p1 from q0,q1 683 p0 := q0 - t.Org 684 p1 := q1 - t.Org 685 ticked := true 686 if p0 < 0 { 687 ticked = false 688 p0 = 0 689 } 690 if p1 < 0 { 691 p1 = 0 692 } 693 if p0 > t.Fr.NumChars { 694 p0 = t.Fr.NumChars 695 } 696 if p1 > t.Fr.NumChars { 697 ticked = false 698 p1 = t.Fr.NumChars 699 } 700 if p0 == t.Fr.P0 && p1 == t.Fr.P1 { 701 if p0 == p1 && ticked != t.Fr.Ticked { 702 t.Fr.Tick(t.Fr.PointOf(p0), ticked) 703 } 704 return 705 } 706 if p0 > p1 { 707 panic(fmt.Sprintf("acme: textsetselect p0=%d p1=%d q0=%d q1=%d t->org=%d nchars=%d", p0, p1, q0, q1, int(t.Org), int(t.Fr.NumChars))) 708 } 709 // screen disagrees with desired selection 710 if t.Fr.P1 <= p0 || p1 <= t.Fr.P0 || p0 == p1 || t.Fr.P1 == t.Fr.P0 { 711 // no overlap or too easy to bother trying 712 t.Fr.Drawsel(t.Fr.PointOf(t.Fr.P0), t.Fr.P0, t.Fr.P1, false) 713 if p0 != p1 || ticked { 714 t.Fr.Drawsel(t.Fr.PointOf(p0), p0, p1, true) 715 } 716 goto Return 717 } 718 // overlap; avoid unnecessary painting 719 if p0 < t.Fr.P0 { 720 // extend selection backwards 721 t.Fr.Drawsel(t.Fr.PointOf(p0), p0, t.Fr.P0, true) 722 } else if p0 > t.Fr.P0 { 723 // trim first part of selection 724 t.Fr.Drawsel(t.Fr.PointOf(t.Fr.P0), t.Fr.P0, p0, false) 725 } 726 if p1 > t.Fr.P1 { 727 // extend selection forwards 728 t.Fr.Drawsel(t.Fr.PointOf(t.Fr.P1), t.Fr.P1, p1, true) 729 } else if p1 < t.Fr.P1 { 730 // trim last part of selection 731 t.Fr.Drawsel(t.Fr.PointOf(p1), p1, t.Fr.P1, false) 732 } 733 734 Return: 735 t.Fr.P0 = p0 736 t.Fr.P1 = p1 737 } 738 739 var ( 740 left = [][]rune{[]rune("{[(<«"), []rune("\n"), []rune("'\"`")} 741 right = [][]rune{[]rune("}])>»"), []rune("\n"), []rune("'\"`")} 742 ) 743 744 func Textdoubleclick(t *Text, q0 *int, q1 *int) { 745 if textclickhtmlmatch(t, q0, q1) != 0 { 746 return 747 } 748 749 for i := 0; i < len(left); i++ { 750 q := *q0 751 l := left[i] 752 r := right[i] 753 var c rune 754 // try matching character to left, looking right 755 if q == 0 { 756 c = '\n' 757 } else { 758 c = t.RuneAt(q - 1) 759 } 760 pi := runes.IndexRune(l, c) 761 if pi >= 0 { 762 if textclickmatch(t, c, r[pi], 1, &q) { 763 if c != '\n' { 764 q-- 765 } 766 *q1 = q 767 } 768 return 769 } 770 // try matching character to right, looking left 771 if q == t.Len() { 772 c = '\n' 773 } else { 774 c = t.RuneAt(q) 775 } 776 pi = runes.IndexRune(r, c) 777 if pi >= 0 { 778 if textclickmatch(t, c, l[pi], -1, &q) { 779 *q1 = *q0 780 if *q0 < t.Len() && c == '\n' { 781 (*q1)++ 782 } 783 *q0 = q 784 if c != '\n' || q != 0 || t.RuneAt(0) == '\n' { 785 (*q0)++ 786 } 787 } 788 return 789 } 790 } 791 792 // try filling out word to right 793 for *q1 < t.Len() && runes.IsAlphaNum(t.RuneAt(*q1)) { 794 (*q1)++ 795 } 796 // try filling out word to left 797 for *q0 > 0 && runes.IsAlphaNum(t.RuneAt(*q0-1)) { 798 (*q0)-- 799 } 800 } 801 802 func textclickmatch(t *Text, cl rune, cr rune, dir int, q *int) bool { 803 nest := 1 804 for { 805 var c rune 806 if dir > 0 { 807 if *q == t.Len() { 808 break 809 } 810 c = t.RuneAt(*q) 811 (*q)++ 812 } else { 813 if *q == 0 { 814 break 815 } 816 (*q)-- 817 c = t.RuneAt(*q) 818 } 819 if c == cr { 820 nest-- 821 if nest == 0 { 822 return true 823 } 824 } else if c == cl { 825 nest++ 826 } 827 } 828 return cl == '\n' && nest == 1 829 } 830 831 func textclickhtmlmatch(t *Text, q0 *int, q1 *int) int { 832 q := *q0 833 var depth int 834 var n int 835 var nq int 836 // after opening tag? scan forward for closing tag 837 if ishtmlend(t, q, nil) == 1 { 838 depth = 1 839 for q < t.Len() { 840 n = ishtmlstart(t, q, &nq) 841 if n != 0 { 842 depth += n 843 if depth == 0 { 844 *q1 = q 845 return 1 846 } 847 q = nq 848 continue 849 } 850 q++ 851 } 852 } 853 854 // before closing tag? scan backward for opening tag 855 if ishtmlstart(t, q, nil) == -1 { 856 depth = -1 857 for q > 0 { 858 n = ishtmlend(t, q, &nq) 859 if n != 0 { 860 depth += n 861 if depth == 0 { 862 *q0 = q 863 return 1 864 } 865 q = nq 866 continue 867 } 868 q-- 869 } 870 } 871 872 return 0 873 } 874 875 // Is the text starting at location q an html tag? 876 // Return 1 for <a>, -1 for </a>, 0 for no tag or <a />. 877 // Set *q1, if non-nil, to the location after the tag. 878 func ishtmlstart(t *Text, q int, q1 *int) int { 879 if q+2 > t.Len() { 880 return 0 881 } 882 tmp28 := q 883 q++ 884 if t.RuneAt(tmp28) != '<' { 885 return 0 886 } 887 c := t.RuneAt(q) 888 q++ 889 c1 := c 890 c2 := c 891 for c != '>' { 892 if q >= t.Len() { 893 return 0 894 } 895 c2 = c 896 c = t.RuneAt(q) 897 q++ 898 } 899 if q1 != nil { 900 *q1 = q 901 } 902 if c1 == '/' { // closing tag 903 return -1 904 } 905 if c2 == '/' || c2 == '!' { // open + close tag or comment 906 return 0 907 } 908 return 1 909 } 910 911 // Is the text ending at location q an html tag? 912 // Return 1 for <a>, -1 for </a>, 0 for no tag or <a />. 913 // Set *q0, if non-nil, to the start of the tag. 914 func ishtmlend(t *Text, q int, q0 *int) int { 915 if q < 2 { 916 return 0 917 } 918 q-- 919 if t.RuneAt(q) != '>' { 920 return 0 921 } 922 q-- 923 c := t.RuneAt(q) 924 c1 := c 925 c2 := c 926 for c != '<' { 927 if q == 0 { 928 return 0 929 } 930 c1 = c 931 q-- 932 c = t.RuneAt(q) 933 } 934 if q0 != nil { 935 *q0 = q 936 } 937 if c1 == '/' { // closing tag 938 return -1 939 } 940 if c2 == '/' || c2 == '!' { // open + close tag or comment 941 return 0 942 } 943 return 1 944 } 945 946 func fileaddtext(f *File, t *Text) *File { 947 if f == nil { 948 f = &File{File: new(file.File)} 949 f.File.SetView((*fileView)(f)) 950 f.Unread = true 951 } 952 f.Text = append(f.Text, t) 953 f.Curtext = t 954 return f 955 } 956 957 func filedeltext(f *File, t *Text) { 958 var i int 959 for i = 0; i < len(f.Text); i++ { 960 if f.Text[i] == t { 961 goto Found 962 } 963 } 964 util.Fatal("can't find text in filedeltext") 965 966 Found: 967 copy(f.Text[i:], f.Text[i+1:]) 968 f.Text = f.Text[:len(f.Text)-1] 969 if len(f.Text) == 0 { 970 f.Close() 971 return 972 } 973 if f.Curtext == t { 974 f.Curtext = f.Text[0] 975 } 976 } 977 978 func Dirname(t *Text, r []rune) []rune { 979 if t == nil || t.W == nil { 980 goto Rescue 981 } 982 { 983 nt := t.W.Tag.Len() 984 if nt == 0 { 985 goto Rescue 986 } 987 if len(r) >= 1 && r[0] == '/' { 988 goto Rescue 989 } 990 b, i := parsetag(t.W, len(r)) 991 slash := -1 992 for i--; i >= 0; i-- { 993 if b[i] == '/' { 994 slash = i 995 break 996 } 997 } 998 if slash < 0 { 999 goto Rescue 1000 } 1001 b = append(b[:slash+1], r...) 1002 return runes.CleanPath(b) 1003 } 1004 1005 Rescue: 1006 tmp := r 1007 if len(r) >= 1 { 1008 return runes.CleanPath(tmp) 1009 } 1010 return tmp 1011 }