9fans.net/go@v0.0.7/cmd/acme/internal/ui/text.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 <complete.h> 13 // #include "dat.h" 14 // #include "fns.h" 15 16 package ui 17 18 import ( 19 "time" 20 21 "9fans.net/go/cmd/acme/internal/adraw" 22 "9fans.net/go/cmd/acme/internal/file" 23 "9fans.net/go/cmd/acme/internal/util" 24 "9fans.net/go/cmd/acme/internal/wind" 25 "9fans.net/go/draw" 26 "9fans.net/go/draw/frame" 27 ) 28 29 var Textcomplete func(*wind.Text) []rune 30 31 func Textconstrain(t *wind.Text, q0 int, q1 int, p0 *int, p1 *int) { 32 *p0 = util.Min(q0, t.Len()) 33 *p1 = util.Min(q1, t.Len()) 34 } 35 36 func Texttype(t *wind.Text, r rune) { 37 if t.What != wind.Body && t.What != wind.Tag && r == '\n' { 38 return 39 } 40 if t.What == wind.Tag { 41 t.W.Tagsafe = false 42 } 43 44 var q0 int 45 var nnb int 46 var n int 47 switch r { 48 case draw.KeyLeft: 49 wind.Typecommit(t) 50 if t.Q0 > 0 { 51 wind.Textshow(t, t.Q0-1, t.Q0-1, true) 52 } 53 return 54 case draw.KeyRight: 55 wind.Typecommit(t) 56 if t.Q1 < t.Len() { 57 wind.Textshow(t, t.Q1+1, t.Q1+1, true) 58 } 59 return 60 case draw.KeyDown, draw.KeyPageDown, Kscrollonedown: 61 if t.What == wind.Tag { 62 // expand tag to show all text 63 if !t.W.Tagexpand { 64 t.W.Tagexpand = true 65 WinresizeAndMouse(t.W, t.W.R, false, true) 66 } 67 return 68 } 69 switch r { 70 case draw.KeyDown: 71 n = t.Fr.MaxLines / 3 72 case draw.KeyPageDown: 73 n = 2 * t.Fr.MaxLines / 3 74 case Kscrollonedown: 75 n = draw.MouseScrollSize(t.Fr.MaxLines) 76 if n <= 0 { 77 n = 1 78 } 79 } 80 q0 = t.Org + t.Fr.CharOf(draw.Pt(t.Fr.R.Min.X, t.Fr.R.Min.Y+n*t.Fr.Font.Height)) 81 wind.Textsetorigin(t, q0, true) 82 return 83 case draw.KeyUp, draw.KeyPageUp, Kscrolloneup: 84 if t.What == wind.Tag { 85 // shrink tag to single line 86 if t.W.Tagexpand { 87 t.W.Tagexpand = false 88 t.W.Taglines = 1 89 WinresizeAndMouse(t.W, t.W.R, false, true) 90 } 91 return 92 } 93 switch r { 94 case draw.KeyUp: 95 n = t.Fr.MaxLines / 3 96 case draw.KeyPageUp: 97 n = 2 * t.Fr.MaxLines / 3 98 case Kscrolloneup: 99 n = draw.MouseScrollSize(t.Fr.MaxLines) 100 } 101 q0 = wind.Textbacknl(t, t.Org, n) 102 wind.Textsetorigin(t, q0, true) 103 return 104 case draw.KeyHome: 105 wind.Typecommit(t) 106 if t.Org > t.IQ1 { 107 q0 = wind.Textbacknl(t, t.IQ1, 1) 108 wind.Textsetorigin(t, q0, true) 109 } else { 110 wind.Textshow(t, 0, 0, false) 111 } 112 return 113 case draw.KeyEnd: 114 wind.Typecommit(t) 115 if t.IQ1 > t.Org+t.Fr.NumChars { 116 if t.IQ1 > t.Len() { 117 // should not happen, but does. and it will crash textbacknl. 118 t.IQ1 = t.Len() 119 } 120 q0 = wind.Textbacknl(t, t.IQ1, 1) 121 wind.Textsetorigin(t, q0, true) 122 } else { 123 wind.Textshow(t, t.Len(), t.Len(), false) 124 } 125 return 126 case 0x01: // ^A: beginning of line 127 wind.Typecommit(t) 128 // go to where ^U would erase, if not already at BOL 129 nnb = 0 130 if t.Q0 > 0 && t.RuneAt(t.Q0-1) != '\n' { 131 nnb = wind.Textbswidth(t, 0x15) 132 } 133 wind.Textshow(t, t.Q0-nnb, t.Q0-nnb, true) 134 return 135 case 0x05: // ^E: end of line 136 wind.Typecommit(t) 137 q0 = t.Q0 138 for q0 < t.Len() && t.RuneAt(q0) != '\n' { 139 q0++ 140 } 141 wind.Textshow(t, q0, q0, true) 142 return 143 case draw.KeyCmd + 'c': // %C: copy 144 wind.Typecommit(t) 145 XCut(t, t, nil, true, false, nil) 146 return 147 case draw.KeyCmd + 'z': // %Z: undo 148 wind.Typecommit(t) 149 XUndo(t, nil, nil, true, false, nil) 150 return 151 case draw.KeyCmd + 'Z': // %-shift-Z: redo 152 wind.Typecommit(t) 153 XUndo(t, nil, nil, false, false, nil) 154 return 155 } 156 if t.What == wind.Body { 157 file.Seq++ 158 t.File.Mark() 159 } 160 // cut/paste must be done after the seq++/filemark 161 switch r { 162 case draw.KeyCmd + 'x': // %X: cut 163 wind.Typecommit(t) 164 if t.What == wind.Body { 165 file.Seq++ 166 t.File.Mark() 167 } 168 XCut(t, t, nil, true, true, nil) 169 wind.Textshow(t, t.Q0, t.Q0, true) 170 t.IQ1 = t.Q0 171 return 172 case draw.KeyCmd + 'v': // %V: paste 173 wind.Typecommit(t) 174 if t.What == wind.Body { 175 file.Seq++ 176 t.File.Mark() 177 } 178 XPaste(t, t, nil, true, false, nil) 179 wind.Textshow(t, t.Q0, t.Q1, true) 180 t.IQ1 = t.Q1 181 return 182 } 183 if t.Q1 > t.Q0 { 184 if len(t.Cache) != 0 { 185 util.Fatal("text.type") 186 } 187 XCut(t, t, nil, true, true, nil) 188 t.Eq0 = ^0 189 } 190 wind.Textshow(t, t.Q0, t.Q0, true) 191 var q1 int 192 var nb int 193 var i int 194 var u *wind.Text 195 rp := []rune{r} 196 switch r { 197 case 0x06, // ^F: complete 198 draw.KeyInsert: 199 wind.Typecommit(t) 200 if Textcomplete == nil { 201 rp = nil 202 } else { 203 rp = Textcomplete(t) 204 } 205 if rp == nil { 206 return 207 } 208 209 // break to normal insertion case 210 case 0x1B: 211 if t.Eq0 != ^0 { 212 if t.Eq0 <= t.Q0 { 213 wind.Textsetselect(t, t.Eq0, t.Q0) 214 } else { 215 wind.Textsetselect(t, t.Q0, t.Eq0) 216 } 217 } 218 if len(t.Cache) > 0 { 219 wind.Typecommit(t) 220 } 221 t.IQ1 = t.Q0 222 return 223 case 0x08, // ^H: erase character 224 0x15, // ^U: erase line 225 0x17: // ^W: erase word 226 if t.Q0 == 0 { // nothing to erase 227 return 228 } 229 nnb = wind.Textbswidth(t, r) 230 q1 = t.Q0 231 q0 = q1 - nnb 232 // if selection is at beginning of window, avoid deleting invisible text 233 if q0 < t.Org { 234 q0 = t.Org 235 nnb = q1 - q0 236 } 237 if nnb <= 0 { 238 return 239 } 240 for i = 0; i < len(t.File.Text); i++ { 241 u = t.File.Text[i] 242 u.Nofill = true 243 nb = nnb 244 n = len(u.Cache) 245 if n > 0 { 246 if q1 != u.Cq0+n { 247 util.Fatal("text.type backspace") 248 } 249 if n > nb { 250 n = nb 251 } 252 u.Cache = u.Cache[:len(u.Cache)-n] 253 wind.Textdelete(u, q1-n, q1, false) 254 nb -= n 255 } 256 if u.Eq0 == q1 || u.Eq0 == ^0 { 257 u.Eq0 = q0 258 } 259 if nb != 0 && u == t { 260 wind.Textdelete(u, q0, q0+nb, true) 261 } 262 if u != t { 263 wind.Textsetselect(u, u.Q0, u.Q1) 264 } else { 265 wind.Textsetselect(t, q0, q0) 266 } 267 u.Nofill = false 268 } 269 for i = 0; i < len(t.File.Text); i++ { 270 wind.Textfill(t.File.Text[i]) 271 } 272 t.IQ1 = t.Q0 273 return 274 case '\n': 275 if t.W.Autoindent { 276 // find beginning of previous line using backspace code 277 nnb = wind.Textbswidth(t, 0x15) // ^U case 278 rp = make([]rune, 1, nnb+1) 279 rp[0] = '\n' 280 for i = 0; i < nnb; i++ { 281 r = t.RuneAt(t.Q0 - nnb + i) 282 if r != ' ' && r != '\t' { 283 break 284 } 285 rp = append(rp, r) 286 } 287 } 288 // break to normal code 289 } 290 // otherwise ordinary character; just insert, typically in caches of all texts 291 for i = 0; i < len(t.File.Text); i++ { 292 u = t.File.Text[i] 293 if u.Eq0 == ^0 { 294 u.Eq0 = t.Q0 295 } 296 if len(u.Cache) == 0 { 297 u.Cq0 = t.Q0 298 } else if t.Q0 != u.Cq0+len(u.Cache) { 299 util.Fatal("text.type cq1") 300 } 301 /* 302 * Change the tag before we add to ncache, 303 * so that if the window body is resized the 304 * commit will not find anything in ncache. 305 */ 306 if u.What == wind.Body && len(u.Cache) == 0 { 307 u.Needundo = true 308 wind.Winsettag(t.W) 309 u.Needundo = false 310 } 311 wind.Textinsert(u, t.Q0, rp, false) 312 if u != t { 313 wind.Textsetselect(u, u.Q0, u.Q1) 314 } 315 u.Cache = append(u.Cache, rp...) 316 } 317 wind.Textsetselect(t, t.Q0+len(rp), t.Q0+len(rp)) 318 if r == '\n' && t.W != nil { 319 wind.Wincommit(t.W, t) 320 } 321 t.IQ1 = t.Q0 322 } 323 324 var clicktext *wind.Text 325 326 var clickmsec uint32 327 328 var selecttext *wind.Text 329 330 var selectq int 331 332 /* 333 * called from frame library 334 */ 335 func framescroll(f *frame.Frame, dl int) { 336 if f != &selecttext.Fr { 337 util.Fatal("frameselect not right frame") 338 } 339 textframescroll(selecttext, dl) 340 } 341 342 func textframescroll(t *wind.Text, dl int) { 343 if dl == 0 { 344 scrsleep(100 * time.Millisecond) 345 return 346 } 347 var q0 int 348 if dl < 0 { 349 q0 = wind.Textbacknl(t, t.Org, -dl) 350 if selectq > t.Org+t.Fr.P0 { 351 wind.Textsetselect(t, t.Org+t.Fr.P0, selectq) 352 } else { 353 wind.Textsetselect(t, selectq, t.Org+t.Fr.P0) 354 } 355 } else { 356 if t.Org+t.Fr.NumChars == t.Len() { 357 return 358 } 359 q0 = t.Org + t.Fr.CharOf(draw.Pt(t.Fr.R.Min.X, t.Fr.R.Min.Y+dl*t.Fr.Font.Height)) 360 if selectq > t.Org+t.Fr.P1 { 361 wind.Textsetselect(t, t.Org+t.Fr.P1, selectq) 362 } else { 363 wind.Textsetselect(t, selectq, t.Org+t.Fr.P1) 364 } 365 } 366 wind.Textsetorigin(t, q0, true) 367 } 368 369 func Textselect(t *wind.Text) { 370 const ( 371 None = iota 372 Cut 373 Paste 374 ) 375 376 selecttext = t 377 /* 378 * To have double-clicking and chording, we double-click 379 * immediately if it might make sense. 380 */ 381 b := Mouse.Buttons 382 q0 := t.Q0 383 q1 := t.Q1 384 selectq = t.Org + t.Fr.CharOf(Mouse.Point) 385 if clicktext == t && Mouse.Msec-clickmsec < 500 { 386 if q0 == q1 && selectq == q0 { 387 wind.Textdoubleclick(t, &q0, &q1) 388 wind.Textsetselect(t, q0, q1) 389 adraw.Display.Flush() 390 x := Mouse.Point.X 391 y := Mouse.Point.Y 392 // stay here until something interesting happens 393 for { 394 Mousectl.Read() 395 if !(Mouse.Buttons == b && util.Abs(Mouse.Point.X-x) < 3) || !(util.Abs(Mouse.Point.Y-y) < 3) { 396 break 397 } 398 } 399 Mouse.Point.X = x // in case we're calling frselect 400 Mouse.Point.Y = y 401 q0 = t.Q0 // may have changed 402 q1 = t.Q1 403 selectq = q0 404 } 405 } 406 if Mouse.Buttons == b { 407 t.Fr.Scroll = framescroll 408 t.Fr.Select(Mousectl) 409 // horrible botch: while asleep, may have lost selection altogether 410 if selectq > t.Len() { 411 selectq = t.Org + t.Fr.P0 412 } 413 t.Fr.Scroll = nil 414 if selectq < t.Org { 415 q0 = selectq 416 } else { 417 q0 = t.Org + t.Fr.P0 418 } 419 if selectq > t.Org+t.Fr.NumChars { 420 q1 = selectq 421 } else { 422 q1 = t.Org + t.Fr.P1 423 } 424 } 425 if q0 == q1 { 426 if q0 == t.Q0 && clicktext == t && Mouse.Msec-clickmsec < 500 { 427 wind.Textdoubleclick(t, &q0, &q1) 428 clicktext = nil 429 } else { 430 clicktext = t 431 clickmsec = Mouse.Msec 432 } 433 } else { 434 clicktext = nil 435 } 436 wind.Textsetselect(t, q0, q1) 437 adraw.Display.Flush() 438 state := None // what we've done; undo when possible 439 for Mouse.Buttons != 0 { 440 Mouse.Msec = 0 441 b = Mouse.Buttons 442 if b&1 != 0 && b&6 != 0 { 443 if state == None && t.What == wind.Body { 444 file.Seq++ 445 t.W.Body.File.Mark() 446 } 447 if b&2 != 0 { 448 if state == Paste && t.What == wind.Body { 449 wind.Winundo(t.W, true) 450 wind.Textsetselect(t, q0, t.Q1) 451 state = None 452 } else if state != Cut { 453 XCut(t, t, nil, true, true, nil) 454 state = Cut 455 } 456 } else { 457 if state == Cut && t.What == wind.Body { 458 wind.Winundo(t.W, true) 459 wind.Textsetselect(t, q0, t.Q1) 460 state = None 461 } else if state != Paste { 462 XPaste(t, t, nil, true, false, nil) 463 state = Paste 464 } 465 } 466 wind.Textscrdraw(t) 467 Clearmouse() 468 } 469 adraw.Display.Flush() 470 471 // Mousectl.Read does both the Flush 472 // and the receive. We did the flush. 473 // Do just the receive, dropping biglock 474 // to let other goroutines proceed. 475 // Note that *Mouse is Mousectl.Mouse. 476 BigUnlock() 477 for Mouse.Buttons == b { 478 *Mouse = <-Mousectl.C 479 } 480 BigLock() 481 clicktext = nil 482 } 483 } 484 485 var BigLock = func(){} 486 var BigUnlock = func(){} 487 488 /* 489 * Release the button in less than DELAY ms and it's considered a null selection 490 * if the mouse hardly moved, regardless of whether it crossed a char boundary. 491 */ 492 493 const ( 494 DELAY = 2 495 MINMOVE = 4 496 ) 497 498 func xselect(f *frame.Frame, mc *draw.Mousectl, col *draw.Image, p1p *int) int { 499 mp := mc.Point 500 b := mc.Buttons 501 msec := mc.Msec 502 503 // remove tick 504 if f.P0 == f.P1 { 505 f.Tick(f.PointOf(f.P0), false) 506 } 507 p1 := f.CharOf(mp) 508 p0 := p1 509 pt0 := f.PointOf(p0) 510 pt1 := f.PointOf(p1) 511 reg := 0 512 f.Tick(pt0, true) 513 for { 514 q := f.CharOf(mc.Point) 515 if p1 != q { 516 if p0 == p1 { 517 f.Tick(pt0, false) 518 } 519 if reg != wind.Region(q, p0) { // crossed starting point; reset 520 if reg > 0 { 521 wind.Selrestore(f, pt0, p0, p1) 522 } else if reg < 0 { 523 wind.Selrestore(f, pt1, p1, p0) 524 } 525 p1 = p0 526 pt1 = pt0 527 reg = wind.Region(q, p0) 528 if reg == 0 { 529 f.Drawsel0(pt0, p0, p1, col, adraw.Display.White) 530 } 531 } 532 qt := f.PointOf(q) 533 if reg > 0 { 534 if q > p1 { 535 f.Drawsel0(pt1, p1, q, col, adraw.Display.White) 536 } else if q < p1 { 537 wind.Selrestore(f, qt, q, p1) 538 } 539 } else if reg < 0 { 540 if q > p1 { 541 wind.Selrestore(f, pt1, p1, q) 542 } else { 543 f.Drawsel0(qt, q, p1, col, adraw.Display.White) 544 } 545 } 546 p1 = q 547 pt1 = qt 548 } 549 if p0 == p1 { 550 f.Tick(pt0, true) 551 } 552 f.Display.Flush() 553 mc.Read() 554 if mc.Buttons != b { 555 break 556 } 557 } 558 if mc.Msec-msec < DELAY && p0 != p1 && util.Abs(mp.X-mc.X) < MINMOVE && util.Abs(mp.Y-mc.Y) < MINMOVE { 559 if reg > 0 { 560 wind.Selrestore(f, pt0, p0, p1) 561 } else if reg < 0 { 562 wind.Selrestore(f, pt1, p1, p0) 563 } 564 p1 = p0 565 } 566 if p1 < p0 { 567 tmp := p0 568 p0 = p1 569 p1 = tmp 570 } 571 pt0 = f.PointOf(p0) 572 if p0 == p1 { 573 f.Tick(pt0, false) 574 } 575 wind.Selrestore(f, pt0, p0, p1) 576 // restore tick 577 if f.P0 == f.P1 { 578 f.Tick(f.PointOf(f.P0), true) 579 } 580 f.Display.Flush() 581 *p1p = p1 582 return p0 583 } 584 585 func textselect23(t *wind.Text, q0 *int, q1 *int, high *draw.Image, mask int) int { 586 var p1 int 587 p0 := xselect(&t.Fr, Mousectl, high, &p1) 588 buts := Mousectl.Buttons 589 if buts&mask == 0 { 590 *q0 = p0 + t.Org 591 *q1 = p1 + t.Org 592 } 593 594 for Mousectl.Buttons != 0 { 595 Mousectl.Read() 596 } 597 return buts 598 } 599 600 func Textselect2(t *wind.Text, q0 *int, q1 *int, tp **wind.Text) int { 601 *tp = nil 602 buts := textselect23(t, q0, q1, adraw.Button2Color, 4) 603 if buts&4 != 0 { 604 return 0 605 } 606 if buts&1 != 0 { // pick up argument 607 *tp = wind.Argtext 608 return 1 609 } 610 return 1 611 } 612 613 func Textselect3(t *wind.Text, q0 *int, q1 *int) bool { 614 return textselect23(t, q0, q1, adraw.Button3Color, 1|2) == 0 615 }