9fans.net/go@v0.0.5/cmd/acme/internal/edit/ecmd.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 "edit.h" 14 // #include "fns.h" 15 16 package edit 17 18 import ( 19 "fmt" 20 "os" 21 "strings" 22 23 "9fans.net/go/cmd/acme/internal/alog" 24 "9fans.net/go/cmd/acme/internal/bufs" 25 "9fans.net/go/cmd/acme/internal/file" 26 "9fans.net/go/cmd/acme/internal/fileload" 27 "9fans.net/go/cmd/acme/internal/regx" 28 "9fans.net/go/cmd/acme/internal/runes" 29 "9fans.net/go/cmd/acme/internal/ui" 30 "9fans.net/go/cmd/acme/internal/util" 31 "9fans.net/go/cmd/acme/internal/wind" 32 ) 33 34 var BigLock = func() {} 35 var BigUnlock = func() {} 36 37 var Glooping int 38 var nest int 39 var Enoname = "no file name given" 40 41 var TheAddr Address 42 var menu *wind.File 43 44 // extern var curtext *Text 45 var collection []rune 46 47 func clearcollection() { 48 collection = nil 49 } 50 51 func resetxec() { 52 nest = 0 53 Glooping = nest 54 clearcollection() 55 } 56 57 func mkaddr(a *Address, f *wind.File) { 58 a.r.Pos = f.Curtext.Q0 59 a.r.End = f.Curtext.Q1 60 a.f = f 61 } 62 63 func cmdexec(t *wind.Text, cp *Cmd) bool { 64 var w *wind.Window 65 if t == nil { 66 w = nil 67 } else { 68 w = t.W 69 } 70 if w == nil && (cp.addr == nil || cp.addr.typ != '"') && !strings.ContainsRune("bBnqUXY!", cp.cmdc) && (!(cp.cmdc == 'D') || cp.u.text == nil) { 71 editerror("no current window") 72 } 73 i := cmdlookup(cp.cmdc) // will be -1 for '{' 74 var f *wind.File 75 if t != nil && t.W != nil { 76 t = &t.W.Body 77 f = t.File 78 f.Curtext = t 79 } 80 var dot Address 81 if i >= 0 && cmdtab[i].defaddr != aNo { 82 ap := cp.addr 83 if ap == nil && cp.cmdc != '\n' { 84 ap = newaddr() 85 cp.addr = ap 86 ap.typ = '.' 87 if cmdtab[i].defaddr == aAll { 88 ap.typ = '*' 89 } 90 } else if ap != nil && ap.typ == '"' && ap.next == nil && cp.cmdc != '\n' { 91 ap.next = newaddr() 92 ap.next.typ = '.' 93 if cmdtab[i].defaddr == aAll { 94 ap.next.typ = '*' 95 } 96 } 97 if cp.addr != nil { // may be false for '\n' (only) 98 none := Address{} 99 if f != nil { 100 mkaddr(&dot, f) 101 TheAddr = cmdaddress(ap, dot, 0) 102 } else { // a " 103 TheAddr = cmdaddress(ap, none, 0) 104 } 105 f = TheAddr.f 106 t = f.Curtext 107 } 108 } 109 switch cp.cmdc { 110 case '{': 111 mkaddr(&dot, f) 112 if cp.addr != nil { 113 dot = cmdaddress(cp.addr, dot, 0) 114 } 115 for cp = cp.u.cmd; cp != nil; cp = cp.next { 116 if dot.r.End > t.Len() { 117 editerror("dot extends past end of buffer during { command") 118 } 119 t.Q0 = dot.r.Pos 120 t.Q1 = dot.r.End 121 cmdexec(t, cp) 122 } 123 default: 124 if i < 0 { 125 editerror("unknown command %c in cmdexec", cp.cmdc) 126 } 127 return cmdtab[i].fn(t, cp) 128 } 129 return true 130 } 131 132 func Edittext(w *wind.Window, q int, r []rune) error { 133 f := w.Body.File 134 switch Editing { 135 case Inactive: 136 return fmt.Errorf("permission denied") 137 case Inserting: 138 eloginsert(f, q, r) 139 return nil 140 case Collecting: 141 collection = append(collection, r...) 142 return nil 143 default: 144 return fmt.Errorf("unknown state in edittext") 145 } 146 } 147 148 // string is known to be NUL-terminated 149 func filelist(t *wind.Text, r []rune) []rune { 150 if len(r) == 0 { 151 return nil 152 } 153 r = runes.SkipBlank(r) 154 if len(r) == 0 || r[0] != '<' { 155 return runes.Clone(r) 156 } 157 // use < command to collect text 158 clearcollection() 159 runpipe(t, '<', r[1:], Collecting) 160 return collection 161 } 162 163 func a_cmd(t *wind.Text, cp *Cmd) bool { 164 return fappend(t.File, cp, TheAddr.r.End) 165 } 166 167 func b_cmd(t *wind.Text, cp *Cmd) bool { 168 f := tofile(cp.u.text) 169 if nest == 0 { 170 pfilename(f) 171 } 172 curtext = f.Curtext 173 return true 174 } 175 176 func B_cmd(t *wind.Text, cp *Cmd) bool { 177 list := filelist(t, cp.u.text.r) 178 if list == nil { 179 editerror(Enoname) 180 } 181 r := runes.SkipBlank(list) 182 if len(r) == 0 { 183 ui.New(t, t, nil, false, false, r) 184 } else { 185 for len(r) > 0 { 186 s := runes.SkipNonBlank(r) 187 ui.New(t, t, nil, false, false, r[:len(r)-len(s)]) 188 r = runes.SkipBlank(s) 189 } 190 } 191 clearcollection() 192 return true 193 } 194 195 func c_cmd(t *wind.Text, cp *Cmd) bool { 196 elogreplace(t.File, TheAddr.r.Pos, TheAddr.r.End, cp.u.text.r) 197 t.Q0 = TheAddr.r.Pos 198 t.Q1 = TheAddr.r.End 199 return true 200 } 201 202 func d_cmd(t *wind.Text, cp *Cmd) bool { 203 if TheAddr.r.End > TheAddr.r.Pos { 204 elogdelete(t.File, TheAddr.r.Pos, TheAddr.r.End) 205 } 206 t.Q0 = TheAddr.r.Pos 207 t.Q1 = TheAddr.r.Pos 208 return true 209 } 210 211 func D1(t *wind.Text) { 212 if len(t.W.Body.File.Text) > 1 || wind.Winclean(t.W, false) { 213 ui.ColcloseAndMouse(t.Col, t.W, true) 214 } 215 } 216 217 func D_cmd(t *wind.Text, cp *Cmd) bool { 218 list := filelist(t, cp.u.text.r) 219 if list == nil { 220 D1(t) 221 return true 222 } 223 dir := wind.Dirname(t, nil) 224 r := runes.SkipBlank(list) 225 for { 226 s := runes.SkipNonBlank(r) 227 r = r[:len(r)-len(s)] 228 var rs []rune 229 // first time through, could be empty string, meaning delete file empty name 230 if len(r) == 0 || r[0] == '/' || len(dir) == 0 { 231 rs = runes.Clone(r) 232 } else { 233 n := make([]rune, len(dir)+1+len(r)) 234 copy(n, dir) 235 n[len(dir)] = '/' 236 copy(n[len(dir)+1:], r) 237 rs = runes.CleanPath(n) 238 } 239 w := ui.LookFile(rs) 240 if w == nil { 241 editerror(fmt.Sprintf("no such file %s", string(rs))) 242 } 243 D1(&w.Body) 244 r = runes.SkipBlank(s) 245 if len(r) == 0 { 246 break 247 } 248 } 249 clearcollection() 250 return true 251 } 252 253 func readloader(f *wind.File) func(pos int, data []rune) int { 254 return func(pos int, data []rune) int { 255 if len(data) > 0 { 256 eloginsert(f, pos, data) 257 } 258 return 0 259 } 260 } 261 262 func e_cmd(t *wind.Text, cp *Cmd) bool { 263 f := t.File 264 q0 := TheAddr.r.Pos 265 q1 := TheAddr.r.End 266 if cp.cmdc == 'e' { 267 if !wind.Winclean(t.W, true) { 268 editerror("") // winclean generated message already 269 } 270 q0 = 0 271 q1 = f.Len() 272 } 273 allreplaced := (q0 == 0 && q1 == f.Len()) 274 name := cmdname(f, cp.u.text, cp.cmdc == 'e') 275 if name == nil { 276 editerror(Enoname) 277 } 278 samename := runes.Equal(name, t.File.Name()) 279 s := string(name) 280 fd, err := os.Open(s) 281 if err != nil { 282 editerror(fmt.Sprintf("can't open %s: %v", s, err)) 283 } 284 defer fd.Close() 285 if info, err := fd.Stat(); err == nil && info.IsDir() { 286 editerror(fmt.Sprintf("%s is a directory", s)) 287 } 288 elogdelete(f, q0, q1) 289 nulls := false 290 fileload.Loadfile(fd, q1, &nulls, readloader(f), nil) 291 if nulls { 292 alog.Printf("%s: NUL bytes elided\n", s) 293 } else if allreplaced && samename { 294 elogfind(f).editclean = true 295 } 296 return true 297 } 298 299 func f_cmd(t *wind.Text, cp *Cmd) bool { 300 var str *String 301 if cp.u.text == nil { 302 str = new(String) // empty 303 } else { 304 str = cp.u.text 305 } 306 cmdname(t.File, str, true) 307 pfilename(t.File) 308 return true 309 } 310 311 func g_cmd(t *wind.Text, cp *Cmd) bool { 312 if t.File != TheAddr.f { 313 alog.Printf("internal error: g_cmd f!=addr.f\n") 314 return false 315 } 316 if !regx.Compile(cp.re.r) { 317 editerror("bad regexp in g command") 318 } 319 if regx.Match(t, nil, TheAddr.r.Pos, TheAddr.r.End, ®x.Sel) != (cp.cmdc == 'v') { 320 t.Q0 = TheAddr.r.Pos 321 t.Q1 = TheAddr.r.End 322 return cmdexec(t, cp.u.cmd) 323 } 324 return true 325 } 326 327 func i_cmd(t *wind.Text, cp *Cmd) bool { 328 return fappend(t.File, cp, TheAddr.r.Pos) 329 } 330 331 func fbufalloc() []rune { 332 return make([]rune, bufs.Len/runes.RuneSize) 333 } 334 335 func fbuffree(b []rune) {} 336 337 func fcopy(f *wind.File, addr2 Address) { 338 buf := bufs.AllocRunes() 339 var ni int 340 for p := TheAddr.r.Pos; p < TheAddr.r.End; p += ni { 341 ni = TheAddr.r.End - p 342 if ni > bufs.RuneLen { 343 ni = bufs.RuneLen 344 } 345 f.Read(p, buf[:ni]) 346 eloginsert(addr2.f, addr2.r.End, buf[:ni]) 347 } 348 bufs.FreeRunes(buf) 349 } 350 351 func move(f *wind.File, addr2 Address) { 352 if TheAddr.f != addr2.f || TheAddr.r.End <= addr2.r.Pos { 353 elogdelete(f, TheAddr.r.Pos, TheAddr.r.End) 354 fcopy(f, addr2) 355 } else if TheAddr.r.Pos >= addr2.r.End { 356 fcopy(f, addr2) 357 elogdelete(f, TheAddr.r.Pos, TheAddr.r.End) 358 } else if TheAddr.r.Pos == addr2.r.Pos && TheAddr.r.End == addr2.r.End { // move to self; no-op 359 } else { 360 editerror("move overlaps itself") 361 } 362 } 363 364 func m_cmd(t *wind.Text, cp *Cmd) bool { 365 var dot Address 366 mkaddr(&dot, t.File) 367 addr2 := cmdaddress(cp.u.mtaddr, dot, 0) 368 if cp.cmdc == 'm' { 369 move(t.File, addr2) 370 } else { 371 fcopy(t.File, addr2) 372 } 373 return true 374 } 375 376 func p_cmd(t *wind.Text, cp *Cmd) bool { 377 return pdisplay(t.File) 378 } 379 380 func s_cmd(t *wind.Text, cp *Cmd) bool { 381 n := cp.num 382 op := -1 383 if !regx.Compile(cp.re.r) { 384 editerror("bad regexp in s command") 385 } 386 var rp []regx.Ranges 387 delta := 0 388 didsub := false 389 for p1 := TheAddr.r.Pos; p1 <= TheAddr.r.End && regx.Match(t, nil, p1, TheAddr.r.End, ®x.Sel); { 390 if regx.Sel.R[0].Pos == regx.Sel.R[0].End { // empty match? 391 if regx.Sel.R[0].Pos == op { 392 p1++ 393 continue 394 } 395 p1 = regx.Sel.R[0].End + 1 396 } else { 397 p1 = regx.Sel.R[0].End 398 } 399 op = regx.Sel.R[0].End 400 n-- 401 if n > 0 { 402 continue 403 } 404 rp = append(rp, regx.Sel) 405 } 406 rbuf := bufs.AllocRunes() 407 buf := allocstring(0) 408 var err string 409 for m := 0; m < len(rp); m++ { 410 buf.r = buf.r[:0] 411 regx.Sel = rp[m] 412 for i := 0; i < len(cp.u.text.r); i++ { 413 c := cp.u.text.r[i] 414 if c == '\\' && i < len(cp.u.text.r)-1 { 415 i++ 416 c = cp.u.text.r[i] 417 if '1' <= c && c <= '9' { 418 j := c - '0' 419 if regx.Sel.R[j].End-regx.Sel.R[j].Pos > bufs.RuneLen { 420 err = "replacement string too long" 421 goto Err 422 } 423 t.File.Read(regx.Sel.R[j].Pos, rbuf[:regx.Sel.R[j].End-regx.Sel.R[j].Pos]) 424 for k := 0; k < regx.Sel.R[j].End-regx.Sel.R[j].Pos; k++ { 425 Straddc(buf, rbuf[k]) 426 } 427 } else { 428 Straddc(buf, c) 429 } 430 } else if c != '&' { 431 Straddc(buf, c) 432 } else { 433 if regx.Sel.R[0].End-regx.Sel.R[0].Pos > bufs.RuneLen { 434 err = "right hand side too long in substitution" 435 goto Err 436 } 437 t.File.Read(regx.Sel.R[0].Pos, rbuf[:regx.Sel.R[0].End-regx.Sel.R[0].Pos]) 438 for k := 0; k < regx.Sel.R[0].End-regx.Sel.R[0].Pos; k++ { 439 Straddc(buf, rbuf[k]) 440 } 441 } 442 } 443 elogreplace(t.File, regx.Sel.R[0].Pos, regx.Sel.R[0].End, buf.r) 444 delta -= regx.Sel.R[0].End - regx.Sel.R[0].Pos 445 delta += len(buf.r) 446 didsub = true 447 if !cp.flag { 448 break 449 } 450 } 451 freestring(buf) 452 bufs.FreeRunes(rbuf) 453 if !didsub && nest == 0 { 454 editerror("no substitution") 455 } 456 t.Q0 = TheAddr.r.Pos 457 t.Q1 = TheAddr.r.End 458 return true 459 460 Err: 461 freestring(buf) 462 bufs.FreeRunes(rbuf) 463 editerror(err) 464 return false 465 } 466 467 func u_cmd(t *wind.Text, cp *Cmd) bool { 468 n := cp.num 469 flag := true 470 if n < 0 { 471 n = -n 472 flag = false 473 } 474 oseq := -1 475 for { 476 tmp3 := n 477 n-- 478 if !(tmp3 > 0) || !(t.File.Seq() != oseq) { 479 break 480 } 481 oseq = t.File.Seq() 482 const XXX = false 483 ui.XUndo(t, nil, nil, flag, XXX, nil) 484 } 485 return true 486 } 487 488 func w_cmd(t *wind.Text, cp *Cmd) bool { 489 f := t.File 490 if f.Seq() == file.Seq { 491 editerror("can't write file with pending modifications") 492 } 493 r := cmdname(f, cp.u.text, false) 494 if r == nil { 495 editerror("no name specified for 'w' command") 496 } 497 Putfile(f, TheAddr.r.Pos, TheAddr.r.End, r) 498 // r is freed by putfile 499 return true 500 } 501 502 var Putfile = func(*wind.File, int, int, []rune) {} 503 504 func x_cmd(t *wind.Text, cp *Cmd) bool { 505 if cp.re != nil { 506 looper(t.File, cp, cp.cmdc == 'x') 507 } else { 508 linelooper(t.File, cp) 509 } 510 return true 511 } 512 513 func X_cmd(t *wind.Text, cp *Cmd) bool { 514 515 filelooper(t, cp, cp.cmdc == 'X') 516 return true 517 } 518 519 var Run = func(w *wind.Window, s string, rdir []rune) {} 520 521 func runpipe(t *wind.Text, cmd rune, cr []rune, state int) { 522 r := runes.SkipBlank(cr) 523 if len(r) == 0 { 524 editerror("no command specified for %c", cmd) 525 } 526 var w *wind.Window 527 if state == Inserting { 528 w = t.W 529 t.Q0 = TheAddr.r.Pos 530 t.Q1 = TheAddr.r.End 531 if cmd == '<' || cmd == '|' { 532 elogdelete(t.File, t.Q0, t.Q1) 533 } 534 } 535 s := make([]rune, len(r)+1) 536 s[0] = cmd 537 copy(s[1:], r) 538 var dir []rune 539 dir = nil 540 if t != nil { 541 dir = wind.Dirname(t, nil) 542 } 543 if len(dir) == 1 && dir[0] == '.' { // sigh 544 dir = nil 545 } 546 Editing = state 547 if t != nil && t.W != nil { 548 util.Incref(&t.W.Ref) // run will decref 549 } 550 Run(w, string(s), dir) 551 if t != nil && t.W != nil { 552 wind.Winunlock(t.W) 553 } 554 wind.TheRow.Lk.Unlock() 555 BigUnlock() 556 <-Cedit 557 558 /* 559 * The editoutlk exists only so that we can tell when 560 * the editout file has been closed. It can get closed *after* 561 * the process exits because, since the process cannot be 562 * connected directly to editout (no 9P kernel support), 563 * the process is actually connected to a pipe to another 564 * process (arranged via 9pserve) that reads from the pipe 565 * and then writes the data in the pipe to editout using 566 * 9P transactions. This process might still have a couple 567 * writes left to copy after the original process has exited. 568 */ 569 var q *util.QLock 570 if w != nil { 571 q = &w.Editoutlk 572 } else { 573 q = &Editoutlk 574 } 575 q.Lock() // wait for file to close 576 q.Unlock() 577 wind.TheRow.Lk.Lock() 578 BigLock() 579 Editing = Inactive 580 if t != nil && t.W != nil { 581 wind.Winlock(t.W, 'M') 582 } 583 } 584 585 func pipe_cmd(t *wind.Text, cp *Cmd) bool { 586 runpipe(t, cp.cmdc, cp.u.text.r, Inserting) 587 return true 588 } 589 590 func Nlcount(t *wind.Text, q0 int, q1 int, pnr *int) int { 591 buf := bufs.AllocRunes() 592 nbuf := 0 593 nl := 0 594 i := nl 595 start := q0 596 for q0 < q1 { 597 if i == nbuf { 598 nbuf = q1 - q0 599 if nbuf > bufs.RuneLen { 600 nbuf = bufs.RuneLen 601 } 602 t.File.Read(q0, buf[:nbuf]) 603 i = 0 604 } 605 if buf[i] == '\n' { 606 start = q0 + 1 607 nl++ 608 } 609 i++ 610 q0++ 611 } 612 bufs.FreeRunes(buf) 613 if pnr != nil { 614 *pnr = q0 - start 615 } 616 return nl 617 } 618 619 const ( 620 PosnLine = 0 621 PosnChars = 1 622 PosnLineChars = 2 623 ) 624 625 func printposn(t *wind.Text, mode int) { 626 if t != nil && t.File != nil && t.File.Name() != nil { 627 alog.Printf("%s:", string(t.File.Name())) 628 } 629 var l1 int 630 var l2 int 631 var r1 int 632 var r2 int 633 634 switch mode { 635 case PosnChars: 636 alog.Printf("#%d", TheAddr.r.Pos) 637 if TheAddr.r.End != TheAddr.r.Pos { 638 alog.Printf(",#%d", TheAddr.r.End) 639 } 640 alog.Printf("\n") 641 return 642 643 default: 644 case PosnLine: 645 l1 = 1 + Nlcount(t, 0, TheAddr.r.Pos, nil) 646 l2 = l1 + Nlcount(t, TheAddr.r.Pos, TheAddr.r.End, nil) 647 // check if addr ends with '\n' 648 if TheAddr.r.End > 0 && TheAddr.r.End > TheAddr.r.Pos && t.RuneAt(TheAddr.r.End-1) == '\n' { 649 l2-- 650 } 651 alog.Printf("%d", l1) 652 if l2 != l1 { 653 alog.Printf(",%d", l2) 654 } 655 alog.Printf("\n") 656 return 657 658 case PosnLineChars: 659 l1 = 1 + Nlcount(t, 0, TheAddr.r.Pos, &r1) 660 l2 = l1 + Nlcount(t, TheAddr.r.Pos, TheAddr.r.End, &r2) 661 if l2 == l1 { 662 r2 += r1 663 } 664 alog.Printf("%d+#%d", l1, r1) 665 if l2 != l1 { 666 alog.Printf(",%d+#%d", l2, r2) 667 } 668 alog.Printf("\n") 669 return 670 } 671 } 672 673 func eq_cmd(t *wind.Text, cp *Cmd) bool { 674 var mode int 675 switch len(cp.u.text.r) { 676 case 0: 677 mode = PosnLine 678 case 1: 679 if cp.u.text.r[0] == '#' { 680 mode = PosnChars 681 break 682 } 683 if cp.u.text.r[0] == '+' { 684 mode = PosnLineChars 685 break 686 } 687 fallthrough 688 default: 689 editerror("newline expected") 690 } 691 printposn(t, mode) 692 return true 693 } 694 695 func nl_cmd(t *wind.Text, cp *Cmd) bool { 696 f := t.File 697 if cp.addr == nil { 698 var a Address 699 // First put it on newline boundaries 700 mkaddr(&a, f) 701 TheAddr = lineaddr(0, a, -1) 702 a = lineaddr(0, a, 1) 703 TheAddr.r.End = a.r.End 704 if TheAddr.r.Pos == t.Q0 && TheAddr.r.End == t.Q1 { 705 mkaddr(&a, f) 706 TheAddr = lineaddr(1, a, 1) 707 } 708 } 709 wind.Textshow(t, TheAddr.r.Pos, TheAddr.r.End, true) 710 return true 711 } 712 713 func fappend(f *wind.File, cp *Cmd, p int) bool { 714 if len(cp.u.text.r) > 0 { 715 eloginsert(f, p, cp.u.text.r) 716 } 717 f.Curtext.Q0 = p 718 f.Curtext.Q1 = p 719 return true 720 } 721 722 func pdisplay(f *wind.File) bool { 723 p1 := TheAddr.r.Pos 724 p2 := TheAddr.r.End 725 if p2 > f.Len() { 726 p2 = f.Len() 727 } 728 buf := bufs.AllocRunes() 729 for p1 < p2 { 730 np := p2 - p1 731 if np > bufs.RuneLen-1 { 732 np = bufs.RuneLen - 1 733 } 734 f.Read(p1, buf[:np]) 735 alog.Printf("%s", string(buf[:np])) 736 p1 += np 737 } 738 bufs.FreeRunes(buf) 739 f.Curtext.Q0 = TheAddr.r.Pos 740 f.Curtext.Q1 = TheAddr.r.End 741 return true 742 } 743 744 func pfilename(f *wind.File) { 745 w := f.Curtext.W 746 // same check for dirty as in settag, but we know ncache==0 747 dirty := !w.IsDir && !w.IsScratch && f.Mod() 748 ch := func(s string, b bool) byte { 749 if b { 750 return s[1] 751 } 752 return s[0] 753 } 754 alog.Printf("%c%c%c %s\n", ch(" '", dirty), '+', ch(" .", curtext != nil && curtext.File == f), string(f.Name())) 755 } 756 757 func loopcmd(f *wind.File, cp *Cmd, rp []runes.Range) { 758 for i := 0; i < len(rp); i++ { 759 f.Curtext.Q0 = rp[i].Pos 760 f.Curtext.Q1 = rp[i].End 761 cmdexec(f.Curtext, cp) 762 } 763 } 764 765 func looper(f *wind.File, cp *Cmd, xy bool) { 766 r := TheAddr.r 767 op := r.Pos 768 if xy { 769 op = -1 770 } 771 nest++ 772 if !regx.Compile(cp.re.r) { 773 editerror("bad regexp in %c command", cp.cmdc) 774 } 775 var rp []runes.Range 776 for p := r.Pos; p <= r.End; { 777 var tr runes.Range 778 if !regx.Match(f.Curtext, nil, p, r.End, ®x.Sel) { // no match, but y should still run 779 if xy || op > r.End { 780 break 781 } 782 tr.Pos = op 783 tr.End = r.End 784 p = r.End + 1 // exit next loop 785 } else { 786 if regx.Sel.R[0].Pos == regx.Sel.R[0].End { // empty match? 787 if regx.Sel.R[0].Pos == op { 788 p++ 789 continue 790 } 791 p = regx.Sel.R[0].End + 1 792 } else { 793 p = regx.Sel.R[0].End 794 } 795 if xy { 796 tr = regx.Sel.R[0] 797 } else { 798 tr.Pos = op 799 tr.End = regx.Sel.R[0].Pos 800 } 801 } 802 op = regx.Sel.R[0].End 803 rp = append(rp, tr) 804 } 805 loopcmd(f, cp.u.cmd, rp) 806 nest-- 807 } 808 809 func linelooper(f *wind.File, cp *Cmd) { 810 nest++ 811 var rp []runes.Range 812 r := TheAddr.r 813 var a3 Address 814 a3.f = f 815 a3.r.End = r.Pos 816 a3.r.Pos = a3.r.End 817 a := lineaddr(0, a3, 1) 818 linesel := a.r 819 for p := r.Pos; p < r.End; p = a3.r.End { 820 a3.r.Pos = a3.r.End 821 if p != r.Pos || linesel.End == p { 822 a = lineaddr(1, a3, 1) 823 linesel = a.r 824 } 825 if linesel.Pos >= r.End { 826 break 827 } 828 if linesel.End >= r.End { 829 linesel.End = r.End 830 } 831 if linesel.End > linesel.Pos { 832 if linesel.Pos >= a3.r.End && linesel.End > a3.r.End { 833 a3.r = linesel 834 rp = append(rp, linesel) 835 continue 836 } 837 } 838 break 839 } 840 loopcmd(f, cp.u.cmd, rp) 841 nest-- 842 } 843 844 type Looper struct { 845 cp *Cmd 846 XY bool 847 w []*wind.Window 848 } 849 850 var loopstruct Looper // only one; X and Y can't nest 851 852 func alllooper(w *wind.Window, v interface{}) { 853 lp := v.(*Looper) 854 cp := lp.cp 855 // if(w->isscratch || w->isdir) 856 // return; 857 t := &w.Body 858 // only use this window if it's the current window for the file 859 if t.File.Curtext != t { 860 return 861 } 862 // if(w->nopen[QWevent] > 0) 863 // return; 864 // no auto-execute on files without names 865 if cp.re == nil && len(t.File.Name()) == 0 { 866 return 867 } 868 if cp.re == nil || filematch(t.File, cp.re) == lp.XY { 869 lp.w = append(lp.w, w) 870 } 871 } 872 873 func alllocker(w *wind.Window, v interface{}) { 874 if v.(bool) { 875 util.Incref(&w.Ref) 876 } else { 877 wind.Winclose(w) 878 } 879 } 880 881 func filelooper(t *wind.Text, cp *Cmd, XY bool) { 882 tmp6 := Glooping 883 Glooping++ 884 if tmp6 != 0 { 885 cmd := 'Y' 886 if XY { 887 cmd = 'X' 888 } 889 editerror("can't nest %c command", cmd) 890 } 891 nest++ 892 893 loopstruct.cp = cp 894 loopstruct.XY = XY 895 loopstruct.w = nil 896 wind.All(alllooper, &loopstruct) 897 /* 898 * add a ref to all windows to keep safe windows accessed by X 899 * that would not otherwise have a ref to hold them up during 900 * the shenanigans. note this with globalincref so that any 901 * newly created windows start with an extra reference. 902 */ 903 wind.All(alllocker, true) 904 wind.GlobalIncref = 1 905 906 /* 907 * Unlock the window running the X command. 908 * We'll need to lock and unlock each target window in turn. 909 */ 910 if t != nil && t.W != nil { 911 wind.Winunlock(t.W) 912 } 913 914 for i := 0; i < len(loopstruct.w); i++ { 915 targ := &loopstruct.w[i].Body 916 if targ != nil && targ.W != nil { 917 wind.Winlock(targ.W, cp.cmdc) 918 } 919 cmdexec(targ, cp.u.cmd) 920 if targ != nil && targ.W != nil { 921 wind.Winunlock(targ.W) 922 } 923 } 924 925 if t != nil && t.W != nil { 926 wind.Winlock(t.W, cp.cmdc) 927 } 928 929 wind.All(alllocker, false) 930 wind.GlobalIncref = 0 931 loopstruct.w = nil 932 933 Glooping-- 934 nest-- 935 } 936 937 func nextmatch(f *wind.File, r *String, p int, sign int) { 938 if !regx.Compile(r.r) { 939 editerror("bad regexp in command address") 940 } 941 if sign >= 0 { 942 if !regx.Match(f.Curtext, nil, p, 0x7FFFFFFF, ®x.Sel) { 943 editerror("no match for regexp") 944 } 945 if regx.Sel.R[0].Pos == regx.Sel.R[0].End && regx.Sel.R[0].Pos == p { 946 p++ 947 if p > f.Len() { 948 p = 0 949 } 950 if !regx.Match(f.Curtext, nil, p, 0x7FFFFFFF, ®x.Sel) { 951 editerror("address") 952 } 953 } 954 } else { 955 if !regx.MatchBackward(f.Curtext, p, ®x.Sel) { 956 editerror("no match for regexp") 957 } 958 if regx.Sel.R[0].Pos == regx.Sel.R[0].End && regx.Sel.R[0].End == p { 959 p-- 960 if p < 0 { 961 p = f.Len() 962 } 963 if !regx.MatchBackward(f.Curtext, p, ®x.Sel) { 964 editerror("address") 965 } 966 } 967 } 968 } 969 970 func cmdaddress(ap *Addr, a Address, sign int) Address { 971 f := a.f 972 for { 973 var a2 Address 974 var a1 Address 975 switch ap.typ { 976 case 'l': 977 a = lineaddr(ap.num, a, sign) 978 979 case '#': 980 a = charaddr(ap.num, a, sign) 981 982 case '.': 983 mkaddr(&a, f) 984 985 case '$': 986 a.r.End = f.Len() 987 a.r.Pos = a.r.End 988 989 case '\'': 990 editerror("can't handle '") 991 // a.r = f->mark; 992 993 case '?': 994 sign = -sign 995 if sign == 0 { 996 sign = -1 997 } 998 fallthrough 999 // fall through 1000 case '/': 1001 start := a.r.End 1002 if sign < 0 { 1003 start = a.r.Pos 1004 } 1005 nextmatch(f, ap.u.re, start, sign) 1006 a.r = regx.Sel.R[0] 1007 1008 case '"': 1009 f = matchfile(ap.u.re) 1010 mkaddr(&a, f) 1011 1012 case '*': 1013 a.r.Pos = 0 1014 a.r.End = f.Len() 1015 return a 1016 1017 case ',', 1018 ';': 1019 if ap.u.left != nil { 1020 a1 = cmdaddress(ap.u.left, a, 0) 1021 } else { 1022 a1.f = a.f 1023 a1.r.End = 0 1024 a1.r.Pos = a1.r.End 1025 } 1026 if ap.typ == ';' { 1027 f = a1.f 1028 a = a1 1029 f.Curtext.Q0 = a1.r.Pos 1030 f.Curtext.Q1 = a1.r.End 1031 } 1032 if ap.next != nil { 1033 a2 = cmdaddress(ap.next, a, 0) 1034 } else { 1035 a2.f = a.f 1036 a2.r.End = f.Len() 1037 a2.r.Pos = a2.r.End 1038 } 1039 if a1.f != a2.f { 1040 editerror("addresses in different files") 1041 } 1042 a.f = a1.f 1043 a.r.Pos = a1.r.Pos 1044 a.r.End = a2.r.End 1045 if a.r.End < a.r.Pos { 1046 editerror("addresses out of order") 1047 } 1048 return a 1049 1050 case '+', 1051 '-': 1052 sign = 1 1053 if ap.typ == '-' { 1054 sign = -1 1055 } 1056 if ap.next == nil || ap.next.typ == '+' || ap.next.typ == '-' { 1057 a = lineaddr(1, a, sign) 1058 } 1059 default: 1060 util.Fatal("cmdaddress") 1061 return a 1062 } 1063 ap = ap.next 1064 if ap == nil { // assign = 1065 break 1066 } 1067 } 1068 return a 1069 } 1070 1071 type Tofile struct { 1072 f *wind.File 1073 r *String 1074 } 1075 1076 func alltofile(w *wind.Window, v interface{}) { 1077 tp := v.(*Tofile) 1078 if tp.f != nil { 1079 return 1080 } 1081 if w.IsScratch || w.IsDir { 1082 return 1083 } 1084 t := &w.Body 1085 // only use this window if it's the current window for the file 1086 if t.File.Curtext != t { 1087 return 1088 } 1089 // if(w->nopen[QWevent] > 0) 1090 // return; 1091 if runes.Equal(tp.r.r, t.File.Name()) { 1092 tp.f = t.File 1093 } 1094 } 1095 1096 func tofile(r *String) *wind.File { 1097 var rr String 1098 rr.r = runes.SkipBlank(r.r) 1099 var t Tofile 1100 t.f = nil 1101 t.r = &rr 1102 wind.All(alltofile, &t) 1103 if t.f == nil { 1104 editerror("no such file\"%s\"", string(rr.r)) 1105 } 1106 return t.f 1107 } 1108 1109 func allmatchfile(w *wind.Window, v interface{}) { 1110 tp := v.(*Tofile) 1111 if w.IsScratch || w.IsDir { 1112 return 1113 } 1114 t := &w.Body 1115 // only use this window if it's the current window for the file 1116 if t.File.Curtext != t { 1117 return 1118 } 1119 // if(w->nopen[QWevent] > 0) 1120 // return; 1121 if filematch(w.Body.File, tp.r) { 1122 if tp.f != nil { 1123 editerror("too many files match \"%s\"", string(tp.r.r)) 1124 } 1125 tp.f = w.Body.File 1126 } 1127 } 1128 1129 func matchfile(r *String) *wind.File { 1130 var tf Tofile 1131 tf.f = nil 1132 tf.r = r 1133 wind.All(allmatchfile, &tf) 1134 1135 if tf.f == nil { 1136 editerror("no file matches \"%s\"", string(r.r)) 1137 } 1138 return tf.f 1139 } 1140 1141 func filematch(f *wind.File, r *String) bool { 1142 // compile expr first so if we get an error, we haven't allocated anything 1143 if !regx.Compile(r.r) { 1144 editerror("bad regexp in file match") 1145 } 1146 w := f.Curtext.W 1147 // same check for dirty as in settag, but we know ncache==0 1148 dirty := !w.IsDir && !w.IsScratch && f.Mod() 1149 ch := func(s string, b bool) byte { 1150 if b { 1151 return s[1] 1152 } 1153 return s[0] 1154 } 1155 rbuf := []rune(fmt.Sprintf("%c%c%c %s\n", ch(" '", dirty), '+', ch(" .", curtext != nil && curtext.File == f), string(f.Name()))) 1156 var s regx.Ranges 1157 return regx.Match(nil, rbuf, 0, len(rbuf), &s) 1158 } 1159 1160 func charaddr(l int, addr Address, sign int) Address { 1161 if sign == 0 { 1162 addr.r.End = l 1163 addr.r.Pos = addr.r.End 1164 } else if sign < 0 { 1165 addr.r.Pos -= l 1166 addr.r.End = addr.r.Pos 1167 } else if sign > 0 { 1168 addr.r.End += l 1169 addr.r.Pos = addr.r.End 1170 } 1171 if addr.r.Pos < 0 || addr.r.End > addr.f.Len() { 1172 editerror("address out of range") 1173 } 1174 return addr 1175 } 1176 1177 func lineaddr(l int, addr Address, sign int) Address { 1178 f := addr.f 1179 var a Address 1180 a.f = f 1181 if sign >= 0 { 1182 var p int 1183 if l == 0 { 1184 if sign == 0 || addr.r.End == 0 { 1185 a.r.End = 0 1186 a.r.Pos = a.r.End 1187 return a 1188 } 1189 a.r.Pos = addr.r.End 1190 p = addr.r.End - 1 1191 } else { 1192 var n int 1193 if sign == 0 || addr.r.End == 0 { 1194 p = 0 1195 n = 1 1196 } else { 1197 p = addr.r.End - 1 1198 if f.Curtext.RuneAt(p) == '\n' { 1199 n = 1 1200 } 1201 p++ 1202 } 1203 for n < l { 1204 if p >= f.Len() { 1205 editerror("address out of range") 1206 } 1207 tmp9 := p 1208 p++ 1209 if f.Curtext.RuneAt(tmp9) == '\n' { 1210 n++ 1211 } 1212 } 1213 a.r.Pos = p 1214 } 1215 for p < f.Len() { 1216 c := f.Curtext.RuneAt(p) 1217 p++ 1218 if c == '\n' { 1219 break 1220 } 1221 } 1222 a.r.End = p 1223 } else { 1224 p := addr.r.Pos 1225 if l == 0 { 1226 a.r.End = addr.r.Pos 1227 } else { 1228 for n := 0; n < l; { // always runs once 1229 if p == 0 { 1230 n++ 1231 if n != l { 1232 editerror("address out of range") 1233 } 1234 } else { 1235 c := f.Curtext.RuneAt(p - 1) 1236 if c != '\n' || func() bool { n++; return n != l }() { 1237 p-- 1238 } 1239 } 1240 } 1241 a.r.End = p 1242 if p > 0 { 1243 p-- 1244 } 1245 } 1246 for p > 0 && f.Curtext.RuneAt(p-1) != '\n' { // lines start after a newline 1247 p-- 1248 } 1249 a.r.Pos = p 1250 } 1251 return a 1252 } 1253 1254 type Filecheck struct { 1255 f *wind.File 1256 r []rune 1257 } 1258 1259 func allfilecheck(w *wind.Window, v interface{}) { 1260 fp := v.(*Filecheck) 1261 f := w.Body.File 1262 if w.Body.File == fp.f { 1263 return 1264 } 1265 if runes.Equal(fp.r, f.Name()) { 1266 alog.Printf("warning: duplicate file name \"%s\"\n", string(fp.r)) 1267 } 1268 } 1269 1270 func cmdname(f *wind.File, str *String, set bool) []rune { 1271 s := str.r 1272 if len(s) == 0 { 1273 // no name; use existing 1274 if len(f.Name()) == 0 { 1275 return nil 1276 } 1277 return runes.Clone(f.Name()) 1278 } 1279 s = runes.SkipBlank(s) 1280 var r []rune 1281 if len(s) > 0 { 1282 if s[0] == '/' { 1283 r = runes.Clone(s) 1284 } else { 1285 newname := wind.Dirname(f.Curtext, runes.Clone(s)) 1286 r = newname 1287 } 1288 var fc Filecheck 1289 fc.f = f 1290 fc.r = r 1291 wind.All(allfilecheck, &fc) 1292 if len(f.Name()) == 0 { 1293 set = true 1294 } 1295 } 1296 1297 if set && !runes.Equal(r, f.Name()) { 1298 f.Mark() 1299 f.SetMod(true) 1300 f.Curtext.W.Dirty = true 1301 wind.Winsetname(f.Curtext.W, r) 1302 } 1303 return r 1304 } 1305 1306 // editing 1307 1308 const ( 1309 Inactive = 0 + iota 1310 Inserting 1311 Collecting 1312 ) 1313 1314 var Cedit = make(chan int) 1315 1316 var Editoutlk util.QLock // atomic flag