9fans.net/go@v0.0.5/cmd/acme/internal/edit/edit.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 "runtime" 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/util" 26 "9fans.net/go/cmd/acme/internal/wind" 27 ) 28 29 var linex = "\n" 30 var wordx = " \t\n" 31 var cmdtab []Cmdtab 32 33 type Cmdtab struct { 34 cmdc rune 35 text bool 36 regexp bool 37 addr bool 38 defcmd rune 39 defaddr Defaddr 40 count uint8 41 token string 42 fn func(*wind.Text, *Cmd) bool 43 } 44 45 func init() { cmdtab = cmdtab1 } // break init cycle 46 var cmdtab1 = []Cmdtab{ 47 // cmdc text regexp addr defcmd defaddr count token fn 48 Cmdtab{'\n', false, false, false, 0, aDot, 0, "", nl_cmd}, 49 Cmdtab{'a', true, false, false, 0, aDot, 0, "", a_cmd}, 50 Cmdtab{'b', false, false, false, 0, aNo, 0, linex, b_cmd}, 51 Cmdtab{'c', true, false, false, 0, aDot, 0, "", c_cmd}, 52 Cmdtab{'d', false, false, false, 0, aDot, 0, "", d_cmd}, 53 Cmdtab{'e', false, false, false, 0, aNo, 0, wordx, e_cmd}, 54 Cmdtab{'f', false, false, false, 0, aNo, 0, wordx, f_cmd}, 55 Cmdtab{'g', false, true, false, 'p', aDot, 0, "", g_cmd}, 56 Cmdtab{'i', true, false, false, 0, aDot, 0, "", i_cmd}, 57 Cmdtab{'m', false, false, true, 0, aDot, 0, "", m_cmd}, 58 Cmdtab{'p', false, false, false, 0, aDot, 0, "", p_cmd}, 59 Cmdtab{'r', false, false, false, 0, aDot, 0, wordx, e_cmd}, 60 Cmdtab{'s', false, true, false, 0, aDot, 1, "", s_cmd}, 61 Cmdtab{'t', false, false, true, 0, aDot, 0, "", m_cmd}, 62 Cmdtab{'u', false, false, false, 0, aNo, 2, "", u_cmd}, 63 Cmdtab{'v', false, true, false, 'p', aDot, 0, "", g_cmd}, 64 Cmdtab{'w', false, false, false, 0, aAll, 0, wordx, w_cmd}, 65 Cmdtab{'x', false, true, false, 'p', aDot, 0, "", x_cmd}, 66 Cmdtab{'y', false, true, false, 'p', aDot, 0, "", x_cmd}, 67 Cmdtab{'=', false, false, false, 0, aDot, 0, linex, eq_cmd}, 68 Cmdtab{'B', false, false, false, 0, aNo, 0, linex, B_cmd}, 69 Cmdtab{'D', false, false, false, 0, aNo, 0, linex, D_cmd}, 70 Cmdtab{'X', false, true, false, 'f', aNo, 0, "", X_cmd}, 71 Cmdtab{'Y', false, true, false, 'f', aNo, 0, "", X_cmd}, 72 Cmdtab{'<', false, false, false, 0, aDot, 0, linex, pipe_cmd}, 73 Cmdtab{'|', false, false, false, 0, aDot, 0, linex, pipe_cmd}, 74 Cmdtab{'>', false, false, false, 0, aDot, 0, linex, pipe_cmd}, 75 /* deliberately unimplemented: 76 Cmdtab{'k', 0, 0, 0, 0, aDot, 0, "", k_cmd}, 77 Cmdtab{'n', 0, 0, 0, 0, aNo, 0, "", n_cmd}, 78 Cmdtab{'q', 0, 0, 0, 0, aNo, 0, "", q_cmd}, 79 Cmdtab{'!', 0, 0, 0, 0, aNo, 0, linex, plan9_cmd}, 80 */ 81 } 82 83 var cmdstartp []rune 84 var cmdp int // index into cmdstartp 85 var editerrc chan string 86 87 var lastpat *String 88 var patset bool 89 90 var cmdlist []*Cmd 91 var addrlist []*Addr 92 var stringlist []*String 93 var curtext *wind.Text 94 var Editing int = Inactive 95 96 func editthread() { 97 for { 98 cmdp := parsecmd(0) 99 if cmdp == nil { 100 break 101 } 102 if !cmdexec(curtext, cmdp) { 103 break 104 } 105 freecmd() 106 } 107 editerrc <- "" 108 } 109 110 func allelogterm(w *wind.Window, x interface{}) { 111 if ef := elogfind(w.Body.File); ef != nil { 112 elogterm(ef) 113 } 114 } 115 116 func alleditinit(w *wind.Window, x interface{}) { 117 wind.Textcommit(&w.Tag, true) 118 wind.Textcommit(&w.Body, true) 119 } 120 121 func allupdate(w *wind.Window, x interface{}) { 122 t := &w.Body 123 if t.File.Curtext != t { // do curtext only 124 return 125 } 126 f := elogfind(t.File) 127 if f == nil { 128 return 129 } 130 if f.elog.typ == elogNull { 131 elogterm(f) 132 } else if f.elog.typ != elogEmpty { 133 elogapply(f) 134 if f.editclean { 135 f.SetMod(false) 136 for i := 0; i < len(f.Text); i++ { 137 f.Text[i].W.Dirty = false 138 } 139 } 140 } 141 wind.Textsetselect(t, t.Q0, t.Q1) 142 wind.Textscrdraw(t) 143 wind.Winsettag(w) 144 } 145 146 func editerror(format string, args ...interface{}) { 147 s := fmt.Sprintf(format, args...) 148 freecmd() 149 wind.All(allelogterm, nil) // truncate the edit logs 150 editerrc <- s 151 runtime.Goexit() // TODO(rsc) 152 } 153 154 func Editcmd(ct *wind.Text, r []rune) { 155 if len(r) == 0 { 156 return 157 } 158 if 2*len(r) > bufs.RuneLen { // TODO(rsc): why 2*len? 159 alog.Printf("string too long\n") 160 return 161 } 162 163 wind.All(alleditinit, nil) 164 cmdstartp = make([]rune, len(r), len(r)+1) 165 copy(cmdstartp, r) 166 if r[len(r)-1] != '\n' { 167 cmdstartp = append(cmdstartp, '\n') 168 } 169 cmdp = 0 170 if ct.W == nil { 171 curtext = nil 172 } else { 173 curtext = &ct.W.Body 174 } 175 resetxec() 176 if editerrc == nil { 177 editerrc = make(chan string) 178 lastpat = allocstring(0) 179 } 180 go editthread() 181 err := <-editerrc 182 Editing = Inactive 183 if err != "" { 184 alog.Printf("Edit: %s\n", err) 185 } 186 187 // update everyone whose edit log has data 188 wind.All(allupdate, nil) 189 } 190 191 func getch() rune { 192 if cmdp >= len(cmdstartp) { 193 return -1 194 } 195 r := cmdstartp[cmdp] 196 cmdp++ 197 return r 198 } 199 200 func nextc() rune { 201 if cmdp >= len(cmdstartp) { 202 return -1 203 } 204 return cmdstartp[cmdp] 205 } 206 207 func ungetch() { 208 cmdp-- 209 if cmdp < 0 { 210 util.Fatal("ungetch") 211 } 212 } 213 214 func getnum(signok int) int { 215 n := 0 216 sign := 1 217 if signok > 1 && nextc() == '-' { 218 sign = -1 219 getch() 220 } 221 c := nextc() 222 if c < '0' || '9' < c { // no number defaults to 1 223 return sign 224 } 225 for { 226 c = getch() 227 if !('0' <= c) || !(c <= '9') { 228 break 229 } 230 n = n*10 + int(c-'0') 231 } 232 ungetch() 233 return sign * n 234 } 235 236 func cmdskipbl() rune { 237 var c rune 238 for { 239 c = getch() 240 if !(c == ' ') && !(c == '\t') { 241 break 242 } 243 } 244 if c >= 0 { 245 ungetch() 246 } 247 return c 248 } 249 250 func allocstring(n int) *String { 251 s := new(String) 252 s.r = make([]rune, n, n+10) 253 return s 254 } 255 256 func freestring(s *String) { 257 s.r = nil 258 } 259 260 func newcmd() *Cmd { 261 p := new(Cmd) 262 cmdlist = append(cmdlist, p) 263 return p 264 } 265 266 func newstring(n int) *String { 267 p := allocstring(n) 268 stringlist = append(stringlist, p) 269 return p 270 } 271 272 func newaddr() *Addr { 273 p := new(Addr) 274 addrlist = append(addrlist, p) 275 return p 276 } 277 278 func freecmd() { 279 // free cmdlist[i] 280 // free addrlist[i] 281 // freestring stringlist[i] 282 } 283 284 func okdelim(c rune) { 285 if c == '\\' || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') { 286 editerror("bad delimiter %c\n", c) 287 } 288 } 289 290 func atnl() { 291 cmdskipbl() 292 c := getch() 293 if c != '\n' { 294 editerror("newline expected (saw %c)", c) 295 } 296 } 297 298 func Straddc(s *String, c rune) { 299 s.r = append(s.r, c) 300 } 301 302 func getrhs(s *String, delim, cmd rune) { 303 for { 304 c := getch() 305 if !(c > 0 && c != delim) || !(c != '\n') { 306 break 307 } 308 if c == '\\' { 309 c = getch() 310 if c <= 0 { 311 util.Fatal("bad right hand side") 312 } 313 if c == '\n' { 314 ungetch() 315 c = '\\' 316 } else if c == 'n' { 317 c = '\n' 318 } else if c != delim && (cmd == 's' || c != '\\') { // s does its own 319 Straddc(s, '\\') 320 } 321 } 322 Straddc(s, c) 323 } 324 ungetch() // let client read whether delimiter, '\n' or whatever 325 } 326 327 func collecttoken(end string) *String { 328 s := newstring(0) 329 var c rune 330 for { 331 c = nextc() 332 if !(c == ' ') && !(c == '\t') { 333 break 334 } 335 Straddc(s, getch()) // blanks significant for getname() 336 } 337 for { 338 c = getch() 339 if c <= 0 || strings.ContainsRune(end, c) { 340 break 341 } 342 Straddc(s, c) 343 } 344 if c != '\n' { 345 atnl() 346 } 347 return s 348 } 349 350 func collecttext() *String { 351 s := newstring(0) 352 if cmdskipbl() == '\n' { 353 getch() 354 i := 0 355 for { 356 begline := i 357 var c rune 358 for { 359 c = getch() 360 if !(c > 0) || !(c != '\n') { 361 break 362 } 363 i++ 364 (func() { Straddc(s, c) }()) 365 } 366 i++ 367 (func() { Straddc(s, '\n') }()) 368 if c < 0 { 369 goto Return 370 } 371 if !(s.r[begline] != '.') && !(s.r[begline+1] != '\n') { 372 break 373 } 374 } 375 s.r = s.r[:len(s.r)-2] 376 } else { 377 delim := getch() 378 okdelim(delim) 379 getrhs(s, delim, 'a') 380 if nextc() == delim { 381 getch() 382 } 383 atnl() 384 } 385 Return: 386 return s 387 } 388 389 func cmdlookup(c rune) int { 390 for i := 0; i < len(cmdtab); i++ { 391 if cmdtab[i].cmdc == c { 392 return i 393 } 394 } 395 return -1 396 } 397 398 func parsecmd(nest int) *Cmd { 399 var cmd Cmd 400 cmd.u.cmd = nil 401 cmd.next = cmd.u.cmd 402 cmd.re = nil 403 cmd.num = 0 404 cmd.flag = false 405 cmd.addr = compoundaddr() 406 if cmdskipbl() == -1 { 407 return nil 408 } 409 c := getch() 410 if c == -1 { 411 return nil 412 } 413 cmd.cmdc = c 414 if cmd.cmdc == 'c' && nextc() == 'd' { // sleazy two-character case 415 getch() // the 'd' 416 cmd.cmdc = 'c' | 0x100 417 } 418 i := cmdlookup(cmd.cmdc) 419 var cp *Cmd 420 if i >= 0 { 421 if cmd.cmdc == '\n' { 422 goto Return // let nl_cmd work it all out 423 } 424 ct := &cmdtab[i] 425 if ct.defaddr == aNo && cmd.addr != nil { 426 editerror("command takes no address") 427 } 428 if ct.count != 0 { 429 cmd.num = getnum(int(ct.count)) 430 } 431 if ct.regexp { 432 // x without pattern -> .*\n, indicated by cmd.re==0 433 // X without pattern is all files 434 if (ct.cmdc != 'x' && ct.cmdc != 'X') || func() bool { c = nextc(); return c != ' ' && c != '\t' && c != '\n' }() { 435 cmdskipbl() 436 c = getch() 437 if c == '\n' || c < 0 { 438 editerror("no address") 439 } 440 okdelim(c) 441 cmd.re = getregexp(c) 442 if ct.cmdc == 's' { 443 cmd.u.text = newstring(0) 444 getrhs(cmd.u.text, c, 's') 445 if nextc() == c { 446 getch() 447 if nextc() == 'g' { 448 getch() 449 cmd.flag = true 450 } 451 } 452 453 } 454 } 455 } 456 if ct.addr { 457 cmd.u.mtaddr = simpleaddr() 458 if cmd.u.mtaddr == nil { 459 editerror("bad address") 460 } 461 } 462 if ct.defcmd != 0 { 463 if cmdskipbl() == '\n' { 464 getch() 465 cmd.u.cmd = newcmd() 466 cmd.u.cmd.cmdc = ct.defcmd 467 } else { 468 cmd.u.cmd = parsecmd(nest) 469 if cmd.u.cmd == nil { 470 util.Fatal("defcmd") 471 } 472 } 473 } else if ct.text { 474 cmd.u.text = collecttext() 475 } else if ct.token != "" { 476 cmd.u.text = collecttoken(ct.token) 477 } else { 478 atnl() 479 } 480 } else { 481 var ncp *Cmd 482 switch cmd.cmdc { 483 case '{': 484 cp = nil 485 for { 486 if cmdskipbl() == '\n' { 487 getch() 488 } 489 ncp = parsecmd(nest + 1) 490 if cp != nil { 491 cp.next = ncp 492 } else { 493 cmd.u.cmd = ncp 494 } 495 cp = ncp 496 if cp == nil { 497 break 498 } 499 } 500 case '}': 501 atnl() 502 if nest == 0 { 503 editerror("right brace with no left brace") 504 } 505 return nil 506 default: 507 editerror("unknown command %c", cmd.cmdc) 508 } 509 } 510 Return: 511 cp = newcmd() 512 *cp = cmd 513 return cp 514 } 515 516 func getregexp(delim rune) *String { 517 buf := allocstring(0) 518 var c rune 519 for i := 0; ; i++ { 520 c = getch() 521 if c == '\\' { 522 if nextc() == delim { 523 c = getch() 524 } else if nextc() == '\\' { 525 Straddc(buf, c) 526 c = getch() 527 } 528 } else if c == delim || c == '\n' { 529 break 530 } 531 if i >= bufs.RuneLen { 532 editerror("regular expression too long") 533 } 534 Straddc(buf, c) 535 } 536 if c != delim && c != 0 { 537 ungetch() 538 } 539 if len(buf.r) > 0 { 540 patset = true 541 freestring(lastpat) 542 lastpat = buf 543 } else { 544 freestring(buf) 545 } 546 if len(lastpat.r) == 0 { 547 editerror("no regular expression defined") 548 } 549 r := newstring(len(lastpat.r)) 550 copy(r.r[:len(lastpat.r)], lastpat.r) 551 return r 552 } 553 554 func simpleaddr() *Addr { 555 var addr Addr 556 addr.num = 0 557 addr.next = nil 558 addr.u.left = nil 559 switch cmdskipbl() { 560 case '#': 561 addr.typ = getch() 562 addr.num = getnum(1) 563 case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': 564 addr.num = getnum(1) 565 addr.typ = 'l' 566 case '/', 567 '?', 568 '"': 569 addr.typ = getch() 570 addr.u.re = getregexp(addr.typ) 571 case '.', 572 '$', 573 '+', 574 '-', 575 '\'': 576 addr.typ = getch() 577 default: 578 return nil 579 } 580 addr.next = simpleaddr() 581 if addr.next != nil { 582 var nap *Addr 583 switch addr.next.typ { 584 case '.', 585 '$', 586 '\'': 587 if addr.typ == '"' { 588 break 589 } 590 fallthrough 591 // fall through 592 case '"': 593 editerror("bad address syntax") 594 case 'l', 595 '#': 596 if addr.typ == '"' { 597 break 598 } 599 fallthrough 600 // fall through 601 case '/', 602 '?': 603 if addr.typ != '+' && addr.typ != '-' { 604 // insert the missing '+' 605 nap = newaddr() 606 nap.typ = '+' 607 nap.next = addr.next 608 addr.next = nap 609 } 610 case '+', 611 '-': 612 break 613 default: 614 util.Fatal("simpleaddr") 615 } 616 } 617 ap := newaddr() 618 *ap = addr 619 return ap 620 } 621 622 func compoundaddr() *Addr { 623 var addr Addr 624 addr.u.left = simpleaddr() 625 addr.typ = cmdskipbl() 626 if addr.typ != ',' && addr.typ != ';' { 627 return addr.u.left 628 } 629 getch() 630 addr.next = compoundaddr() 631 next := addr.next 632 if next != nil && (next.typ == ',' || next.typ == ';') && next.u.left == nil { 633 editerror("bad address syntax") 634 } 635 ap := newaddr() 636 *ap = addr 637 return ap 638 }