9fans.net/go@v0.0.5/acme/acme.go (about) 1 // Package acme is a simple interface for interacting with acme windows. 2 // 3 // Many of the functions in this package take a format string and optional 4 // parameters. In the documentation, the notation format, ... denotes the result 5 // of formatting the string and arguments using fmt.Sprintf. 6 package acme // import "9fans.net/go/acme" 7 8 import ( 9 "bufio" 10 "bytes" 11 "errors" 12 "fmt" 13 "io" 14 "io/ioutil" 15 "log" 16 "os" 17 "path" 18 "reflect" 19 "sort" 20 "strconv" 21 "strings" 22 "sync" 23 "time" 24 25 "9fans.net/go/draw" 26 "9fans.net/go/plan9" 27 "9fans.net/go/plan9/client" 28 ) 29 30 // A Win represents a single acme window and its control files. 31 type Win struct { 32 id int 33 ctl *client.Fid 34 tag *client.Fid 35 body *client.Fid 36 addr *client.Fid 37 event *client.Fid 38 data *client.Fid 39 xdata *client.Fid 40 errors *client.Fid 41 ebuf *bufio.Reader 42 c chan *Event 43 next, prev *Win 44 buf []byte 45 e2, e3, e4 Event 46 name string 47 48 errorPrefix string 49 } 50 51 var windowsMu sync.Mutex 52 var windows, last *Win 53 var autoExit bool 54 55 var fsys *client.Fsys 56 var fsysErr error 57 var fsysOnce sync.Once 58 59 // AutoExit sets whether to call os.Exit the next time the last managed acme window is deleted. 60 // If there are no acme windows at the time of the call, the exit does not happen until one 61 // is created and then deleted. 62 func AutoExit(exit bool) { 63 windowsMu.Lock() 64 defer windowsMu.Unlock() 65 autoExit = exit 66 } 67 68 // New creates a new window. 69 func New() (*Win, error) { 70 fsysOnce.Do(mountAcme) 71 if fsysErr != nil { 72 return nil, fsysErr 73 } 74 fid, err := fsys.Open("new/ctl", plan9.ORDWR) 75 if err != nil { 76 return nil, err 77 } 78 buf := make([]byte, 100) 79 n, err := fid.Read(buf) 80 if err != nil { 81 fid.Close() 82 return nil, err 83 } 84 a := strings.Fields(string(buf[0:n])) 85 if len(a) == 0 { 86 fid.Close() 87 return nil, errors.New("short read from acme/new/ctl") 88 } 89 id, err := strconv.Atoi(a[0]) 90 if err != nil { 91 fid.Close() 92 return nil, errors.New("invalid window id in acme/new/ctl: " + a[0]) 93 } 94 return Open(id, fid) 95 } 96 97 type WinInfo struct { 98 ID int 99 // TagLen holds the length of the tag in runes. 100 TagLen int 101 // TagLen holds the length of the body in runes. 102 BodyLen int 103 IsDir bool 104 IsModified bool 105 // Name and Tag are only populated when the 106 // WinInfo has been obtained by calling the Windows 107 // function, as they're not available by reading the ctl file. 108 109 // Name holds the filename of the window. 110 Name string 111 112 // Tag holds the rest of the tag after the filename. 113 Tag string 114 115 // The Size and History fields can only be non-nil 116 // when WinInfo has been obtained by calling 117 // the Win.Info method, because that information 118 // isn't available as part of the index file. 119 Size *WinSizeInfo 120 } 121 122 type WinSizeInfo struct { 123 Width int 124 Font string 125 TabWidth int 126 } 127 128 // A LogReader provides read access to the acme log file. 129 type LogReader struct { 130 f *client.Fid 131 buf [8192]byte 132 } 133 134 func (r *LogReader) Close() error { 135 return r.f.Close() 136 } 137 138 // A LogEvent is a single event in the acme log file. 139 type LogEvent struct { 140 ID int 141 Op string 142 Name string 143 } 144 145 // Read reads an event from the acme log file. 146 func (r *LogReader) Read() (LogEvent, error) { 147 n, err := r.f.Read(r.buf[:]) 148 if err != nil { 149 return LogEvent{}, err 150 } 151 f := strings.SplitN(string(r.buf[:n]), " ", 3) 152 if len(f) != 3 { 153 return LogEvent{}, fmt.Errorf("malformed log event") 154 } 155 id, _ := strconv.Atoi(f[0]) 156 op := f[1] 157 name := f[2] 158 name = strings.TrimSpace(name) 159 return LogEvent{id, op, name}, nil 160 } 161 162 // Log returns a reader reading the acme/log file. 163 func Log() (*LogReader, error) { 164 fsysOnce.Do(mountAcme) 165 if fsysErr != nil { 166 return nil, fsysErr 167 } 168 f, err := fsys.Open("log", plan9.OREAD) 169 if err != nil { 170 return nil, err 171 } 172 return &LogReader{f: f}, nil 173 } 174 175 // Windows returns a list of the existing acme windows. 176 func Windows() ([]WinInfo, error) { 177 fsysOnce.Do(mountAcme) 178 if fsysErr != nil { 179 return nil, fsysErr 180 } 181 index, err := fsys.Open("index", plan9.OREAD) 182 if err != nil { 183 return nil, err 184 } 185 defer index.Close() 186 data, err := ioutil.ReadAll(index) 187 if err != nil { 188 return nil, err 189 } 190 var infos []WinInfo 191 for _, line := range strings.Split(string(data), "\n") { 192 if len(line) == 0 { 193 continue 194 } 195 var info WinInfo 196 tag, err := splitFields(line, 197 &info.ID, 198 &info.TagLen, 199 &info.BodyLen, 200 &info.IsDir, 201 &info.IsModified, 202 ) 203 if err != nil { 204 return nil, fmt.Errorf("invalid index line %q: %v", line, err) 205 } 206 i := strings.Index(tag, " Del Snarf ") 207 if i == -1 { 208 return nil, fmt.Errorf("cannot determine filename in tag %q", tag) 209 } 210 info.Name = tag[:i] 211 info.Tag = tag[i:] 212 infos = append(infos, info) 213 } 214 return infos, nil 215 } 216 217 // Show looks and causes acme to show the window with the given name, 218 // returning that window. 219 // If this process has not created a window with the given name 220 // (or if any such window has since been deleted), 221 // Show returns nil. 222 func Show(name string) *Win { 223 windowsMu.Lock() 224 defer windowsMu.Unlock() 225 226 for w := windows; w != nil; w = w.next { 227 if w.name == name { 228 if err := w.Ctl("show"); err != nil { 229 w.dropLocked() 230 return nil 231 } 232 return w 233 } 234 } 235 return nil 236 } 237 238 // Open connects to the existing window with the given id. 239 // If ctl is non-nil, Open uses it as the window's control file 240 // and takes ownership of it. 241 func Open(id int, ctl *client.Fid) (*Win, error) { 242 fsysOnce.Do(mountAcme) 243 if fsysErr != nil { 244 return nil, fsysErr 245 } 246 if ctl == nil { 247 var err error 248 ctl, err = fsys.Open(fmt.Sprintf("%d/ctl", id), plan9.ORDWR) 249 if err != nil { 250 return nil, err 251 } 252 } 253 254 w := new(Win) 255 w.id = id 256 w.ctl = ctl 257 w.next = nil 258 w.prev = last 259 if last != nil { 260 last.next = w 261 } else { 262 windows = w 263 } 264 last = w 265 return w, nil 266 } 267 268 // Addr writes format, ... to the window's addr file. 269 func (w *Win) Addr(format string, args ...interface{}) error { 270 return w.Fprintf("addr", format, args...) 271 } 272 273 // CloseFiles closes all the open files associated with the window w. 274 // (These file descriptors are cached across calls to Ctl, etc.) 275 func (w *Win) CloseFiles() { 276 w.ctl.Close() 277 w.ctl = nil 278 279 w.body.Close() 280 w.body = nil 281 282 w.addr.Close() 283 w.addr = nil 284 285 w.tag.Close() 286 w.tag = nil 287 288 w.event.Close() 289 w.event = nil 290 w.ebuf = nil 291 292 w.data.Close() 293 w.data = nil 294 295 w.xdata.Close() 296 w.xdata = nil 297 298 w.errors.Close() 299 w.errors = nil 300 } 301 302 // Ctl writes the command format, ... to the window's ctl file. 303 func (w *Win) Ctl(format string, args ...interface{}) error { 304 return w.Fprintf("ctl", format+"\n", args...) 305 } 306 307 // Winctl deletes the window, writing `del' (or, if sure is true, `delete') to the ctl file. 308 func (w *Win) Del(sure bool) error { 309 cmd := "del" 310 if sure { 311 cmd = "delete" 312 } 313 return w.Ctl(cmd) 314 } 315 316 // DeleteAll deletes all windows. 317 func DeleteAll() { 318 for w := windows; w != nil; w = w.next { 319 w.Ctl("delete") 320 } 321 } 322 323 func (w *Win) OpenEvent() error { 324 _, err := w.fid("event") 325 return err 326 } 327 328 func (w *Win) fid(name string) (*client.Fid, error) { 329 var f **client.Fid 330 var mode uint8 = plan9.ORDWR 331 switch name { 332 case "addr": 333 f = &w.addr 334 case "body": 335 f = &w.body 336 case "ctl": 337 f = &w.ctl 338 case "data": 339 f = &w.data 340 case "event": 341 f = &w.event 342 case "tag": 343 f = &w.tag 344 case "xdata": 345 f = &w.xdata 346 case "errors": 347 f = &w.errors 348 mode = plan9.OWRITE 349 default: 350 return nil, errors.New("unknown acme file: " + name) 351 } 352 if *f == nil { 353 var err error 354 *f, err = fsys.Open(fmt.Sprintf("%d/%s", w.id, name), mode) 355 if err != nil { 356 return nil, err 357 } 358 } 359 return *f, nil 360 } 361 362 // ReadAll 363 func (w *Win) ReadAll(file string) ([]byte, error) { 364 f, err := w.fid(file) 365 if err != nil { 366 return nil, err 367 } 368 f.Seek(0, 0) 369 return ioutil.ReadAll(f) 370 } 371 372 func (w *Win) ID() int { 373 return w.id 374 } 375 376 func (w *Win) Name(format string, args ...interface{}) error { 377 name := fmt.Sprintf(format, args...) 378 if err := w.Ctl("name %s", name); err != nil { 379 return err 380 } 381 w.name = name 382 return nil 383 } 384 385 func (w *Win) Fprintf(file, format string, args ...interface{}) error { 386 f, err := w.fid(file) 387 if err != nil { 388 return err 389 } 390 var buf bytes.Buffer 391 fmt.Fprintf(&buf, format, args...) 392 _, err = f.Write(buf.Bytes()) 393 return err 394 } 395 396 func (w *Win) Read(file string, b []byte) (n int, err error) { 397 f, err := w.fid(file) 398 if err != nil { 399 return 0, err 400 } 401 return f.Read(b) 402 } 403 404 func (w *Win) ReadAddr() (q0, q1 int, err error) { 405 f, err := w.fid("addr") 406 if err != nil { 407 return 0, 0, err 408 } 409 buf := make([]byte, 40) 410 n, err := f.ReadAt(buf, 0) 411 if err != nil && err != io.EOF { 412 return 0, 0, err 413 } 414 a := strings.Fields(string(buf[0:n])) 415 if len(a) < 2 { 416 return 0, 0, errors.New("short read from acme addr") 417 } 418 q0, err0 := strconv.Atoi(a[0]) 419 q1, err1 := strconv.Atoi(a[1]) 420 if err0 != nil || err1 != nil { 421 return 0, 0, errors.New("invalid read from acme addr") 422 } 423 return q0, q1, nil 424 } 425 426 func (w *Win) Info() (WinInfo, error) { 427 f, err := w.fid("ctl") 428 if err != nil { 429 return WinInfo{}, err 430 } 431 buf := make([]byte, 8192) 432 n, err := f.ReadAt(buf, 0) 433 if err != nil && err != io.EOF { 434 return WinInfo{}, err 435 } 436 line := string(buf[:n]) 437 info := WinInfo{ 438 Size: new(WinSizeInfo), 439 } 440 if _, err := splitFields(line, 441 &info.ID, 442 &info.TagLen, 443 &info.BodyLen, 444 &info.IsDir, 445 &info.IsModified, 446 &info.Size.Width, 447 &info.Size.Font, 448 &info.Size.TabWidth, 449 ); err != nil { 450 return WinInfo{}, fmt.Errorf("invalid ctl contents %q: %v", line, err) 451 } 452 return info, nil 453 } 454 455 func (w *Win) Seek(file string, offset int64, whence int) (int64, error) { 456 f, err := w.fid(file) 457 if err != nil { 458 return 0, err 459 } 460 return f.Seek(offset, whence) 461 } 462 463 func (w *Win) Write(file string, b []byte) (n int, err error) { 464 f, err := w.fid(file) 465 if err != nil { 466 return 0, err 467 } 468 return f.Write(b) 469 } 470 471 const eventSize = 256 472 473 // An Event represents an event originating in a particular window. 474 // The fields correspond to the fields in acme's event messages. 475 // See http://swtch.com/plan9port/man/man4/acme.html for details. 476 type Event struct { 477 // The two event characters, indicating origin and type of action 478 C1, C2 rune 479 480 // The character addresses of the action. 481 // If the original event had an empty selection (OrigQ0=OrigQ1) 482 // and was accompanied by an expansion (the 2 bit is set in Flag), 483 // then Q0 and Q1 will indicate the expansion rather than the 484 // original event. 485 Q0, Q1 int 486 487 // The Q0 and Q1 of the original event, even if it was expanded. 488 // If there was no expansion, OrigQ0=Q0 and OrigQ1=Q1. 489 OrigQ0, OrigQ1 int 490 491 // The flag bits. 492 Flag int 493 494 // The number of bytes in the optional text. 495 Nb int 496 497 // The number of characters (UTF-8 sequences) in the optional text. 498 Nr int 499 500 // The optional text itself, encoded in UTF-8. 501 Text []byte 502 503 // The chorded argument, if present (the 8 bit is set in the flag). 504 Arg []byte 505 506 // The chorded location, if present (the 8 bit is set in the flag). 507 Loc []byte 508 } 509 510 // ReadEvent reads the next event from the window's event file. 511 func (w *Win) ReadEvent() (e *Event, err error) { 512 defer func() { 513 if v := recover(); v != nil { 514 e = nil 515 err = errors.New("malformed acme event: " + v.(string)) 516 } 517 }() 518 519 if _, err = w.fid("event"); err != nil { 520 return nil, err 521 } 522 523 e = new(Event) 524 w.gete(e) 525 e.OrigQ0 = e.Q0 526 e.OrigQ1 = e.Q1 527 528 // expansion 529 if e.Flag&2 != 0 { 530 e2 := new(Event) 531 w.gete(e2) 532 if e.Q0 == e.Q1 { 533 e2.OrigQ0 = e.Q0 534 e2.OrigQ1 = e.Q1 535 e2.Flag = e.Flag 536 e = e2 537 } 538 } 539 540 // chorded argument 541 if e.Flag&8 != 0 { 542 e3 := new(Event) 543 e4 := new(Event) 544 w.gete(e3) 545 w.gete(e4) 546 e.Arg = e3.Text 547 e.Loc = e4.Text 548 } 549 550 return e, nil 551 } 552 553 func (w *Win) gete(e *Event) { 554 if w.ebuf == nil { 555 w.ebuf = bufio.NewReader(w.event) 556 } 557 e.C1 = w.getec() 558 e.C2 = w.getec() 559 e.Q0 = w.geten() 560 e.Q1 = w.geten() 561 e.Flag = w.geten() 562 e.Nr = w.geten() 563 if e.Nr > eventSize { 564 panic("event string too long") 565 } 566 r := make([]rune, e.Nr) 567 for i := 0; i < e.Nr; i++ { 568 r[i] = w.getec() 569 } 570 e.Text = []byte(string(r)) 571 if w.getec() != '\n' { 572 panic("phase error") 573 } 574 } 575 576 func (w *Win) getec() rune { 577 c, _, err := w.ebuf.ReadRune() 578 if err != nil { 579 panic(err.Error()) 580 } 581 return c 582 } 583 584 func (w *Win) geten() int { 585 var ( 586 c rune 587 n int 588 ) 589 for { 590 c = w.getec() 591 if c < '0' || c > '9' { 592 break 593 } 594 n = n*10 + int(c) - '0' 595 } 596 if c != ' ' { 597 panic("event number syntax") 598 } 599 return n 600 } 601 602 // WriteEvent writes an event back to the window's event file, 603 // indicating to acme that the event should be handled internally. 604 func (w *Win) WriteEvent(e *Event) error { 605 var buf bytes.Buffer 606 fmt.Fprintf(&buf, "%c%c%d %d \n", e.C1, e.C2, e.OrigQ0, e.OrigQ1) 607 _, err := w.Write("event", buf.Bytes()) 608 return err 609 } 610 611 // EventChan returns a channel on which events can be read. 612 // The first call to EventChan allocates a channel and starts a 613 // new goroutine that loops calling ReadEvent and sending 614 // the result into the channel. Subsequent calls return the 615 // same channel. Clients should not call ReadEvent after calling 616 // EventChan. 617 func (w *Win) EventChan() <-chan *Event { 618 if w.c == nil { 619 w.c = make(chan *Event, 0) 620 go w.eventReader() 621 } 622 return w.c 623 } 624 625 func (w *Win) eventReader() { 626 for { 627 e, err := w.ReadEvent() 628 if err != nil { 629 break 630 } 631 w.c <- e 632 } 633 w.c <- new(Event) // make sure event reader is done processing last event; drop might exit 634 w.drop() 635 close(w.c) 636 } 637 638 func (w *Win) drop() { 639 windowsMu.Lock() 640 defer windowsMu.Unlock() 641 w.dropLocked() 642 } 643 644 func (w *Win) dropLocked() { 645 if w.prev == nil && w.next == nil && windows != w { 646 return 647 } 648 if w.prev != nil { 649 w.prev.next = w.next 650 } else { 651 windows = w.next 652 } 653 if w.next != nil { 654 w.next.prev = w.prev 655 } else { 656 last = w.prev 657 } 658 w.prev = nil 659 w.next = nil 660 if autoExit && windows == nil { 661 os.Exit(0) 662 } 663 } 664 665 var fontCache struct { 666 sync.Mutex 667 m map[string]*draw.Font 668 } 669 670 // Font returns the window's current tab width (in zeros) and font. 671 func (w *Win) Font() (tab int, font *draw.Font, err error) { 672 ctl := make([]byte, 1000) 673 w.Seek("ctl", 0, 0) 674 n, err := w.Read("ctl", ctl) 675 if err != nil { 676 return 0, nil, err 677 } 678 f := strings.Fields(string(ctl[:n])) 679 if len(f) < 8 { 680 return 0, nil, fmt.Errorf("malformed ctl file") 681 } 682 tab, _ = strconv.Atoi(f[7]) 683 if tab == 0 { 684 return 0, nil, fmt.Errorf("malformed ctl file") 685 } 686 name := f[6] 687 688 fontCache.Lock() 689 font = fontCache.m[name] 690 fontCache.Unlock() 691 692 if font != nil { 693 return tab, font, nil 694 } 695 696 var disp *draw.Display = nil 697 font, err = disp.OpenFont(name) 698 if err != nil { 699 return tab, nil, err 700 } 701 702 fontCache.Lock() 703 if fontCache.m == nil { 704 fontCache.m = make(map[string]*draw.Font) 705 } 706 if fontCache.m[name] != nil { 707 font = fontCache.m[name] 708 } else { 709 fontCache.m[name] = font 710 } 711 fontCache.Unlock() 712 713 return tab, font, nil 714 } 715 716 // Blink starts the window tag blinking and returns a function that stops it. 717 // When stop returns, the blinking is over and the window state is clean. 718 func (w *Win) Blink() (stop func()) { 719 c := make(chan struct{}) 720 go func() { 721 t := time.NewTicker(1000 * time.Millisecond) 722 defer t.Stop() 723 dirty := false 724 for { 725 select { 726 case <-t.C: 727 dirty = !dirty 728 if dirty { 729 w.Ctl("dirty") 730 } else { 731 w.Ctl("clean") 732 } 733 case <-c: 734 w.Ctl("clean") 735 c <- struct{}{} 736 return 737 } 738 } 739 }() 740 return func() { 741 c <- struct{}{} 742 <-c 743 } 744 } 745 746 // Sort sorts the lines in the current address range 747 // according to the comparison function. 748 func (w *Win) Sort(less func(x, y string) bool) error { 749 q0, q1, err := w.ReadAddr() 750 if err != nil { 751 return err 752 } 753 data, err := w.ReadAll("xdata") 754 if err != nil { 755 return err 756 } 757 suffix := "" 758 lines := strings.Split(string(data), "\n") 759 if lines[len(lines)-1] == "" { 760 suffix = "\n" 761 lines = lines[:len(lines)-1] 762 } 763 sort.SliceStable(lines, func(i, j int) bool { return less(lines[i], lines[j]) }) 764 w.Addr("#%d,#%d", q0, q1) 765 w.Write("data", []byte(strings.Join(lines, "\n")+suffix)) 766 return nil 767 } 768 769 // PrintTabbed prints tab-separated columnated text to body, 770 // replacing single tabs with runs of tabs as needed to align columns. 771 func (w *Win) PrintTabbed(text string) { 772 tab, font, _ := w.Font() 773 774 lines := strings.SplitAfter(text, "\n") 775 var allRows [][]string 776 for _, line := range lines { 777 if line == "" { 778 continue 779 } 780 line = strings.TrimSuffix(line, "\n") 781 allRows = append(allRows, strings.Split(line, "\t")) 782 } 783 784 var buf bytes.Buffer 785 for len(allRows) > 0 { 786 if row := allRows[0]; len(row) <= 1 { 787 if len(row) > 0 { 788 buf.WriteString(row[0]) 789 } 790 buf.WriteString("\n") 791 allRows = allRows[1:] 792 continue 793 } 794 795 i := 0 796 for i < len(allRows) && len(allRows[i]) > 1 { 797 i++ 798 } 799 800 rows := allRows[:i] 801 allRows = allRows[i:] 802 803 var wid []int 804 if font != nil { 805 for _, row := range rows { 806 for len(wid) < len(row) { 807 wid = append(wid, 0) 808 } 809 for i, col := range row { 810 n := font.StringWidth(col) 811 if wid[i] < n { 812 wid[i] = n 813 } 814 } 815 } 816 } 817 818 for _, row := range rows { 819 for i, col := range row { 820 buf.WriteString(col) 821 if i == len(row)-1 { 822 break 823 } 824 if font == nil || tab == 0 { 825 buf.WriteString("\t") 826 continue 827 } 828 pos := font.StringWidth(col) 829 for pos <= wid[i] { 830 buf.WriteString("\t") 831 pos += tab - pos%tab 832 } 833 } 834 buf.WriteString("\n") 835 } 836 } 837 838 w.Write("body", buf.Bytes()) 839 } 840 841 // Clear clears the window body. 842 func (w *Win) Clear() { 843 w.Addr(",") 844 w.Write("data", nil) 845 } 846 847 type EventHandler interface { 848 Execute(cmd string) bool 849 Look(arg string) bool 850 } 851 852 func (w *Win) loadText(e *Event, h EventHandler) { 853 if len(e.Text) == 0 && e.Q0 < e.Q1 { 854 w.Addr("#%d,#%d", e.Q0, e.Q1) 855 data, err := w.ReadAll("xdata") 856 if err != nil { 857 w.Err(err.Error()) 858 } 859 e.Text = data 860 } 861 } 862 863 func (w *Win) EventLoop(h EventHandler) { 864 for e := range w.EventChan() { 865 switch e.C2 { 866 case 'x', 'X': // execute 867 cmd := strings.TrimSpace(string(e.Text)) 868 if !w.execute(h, cmd) { 869 w.WriteEvent(e) 870 } 871 case 'l', 'L': // look 872 // TODO(rsc): Expand selection, especially for URLs. 873 w.loadText(e, h) 874 if !h.Look(string(e.Text)) { 875 w.WriteEvent(e) 876 } 877 } 878 } 879 } 880 881 func (w *Win) execute(h EventHandler, cmd string) bool { 882 verb, arg := cmd, "" 883 if i := strings.IndexAny(verb, " \t"); i >= 0 { 884 verb, arg = verb[:i], strings.TrimSpace(verb[i+1:]) 885 } 886 887 // Look for specific method. 888 m := reflect.ValueOf(h).MethodByName("Exec" + verb) 889 if !m.IsValid() { 890 // Fall back to general Execute. 891 return h.Execute(cmd) 892 } 893 894 // Found method. 895 // Committed to handling the event. 896 // All returns below should be return true. 897 898 // Check method signature. 899 t := m.Type() 900 switch t.NumOut() { 901 default: 902 w.Errf("bad method %s: too many results", cmd) 903 return true 904 case 0: 905 // ok 906 case 1: 907 if t.Out(0) != reflect.TypeOf((*error)(nil)).Elem() { 908 w.Errf("bad method %s: return type %v, not error", cmd, t.Out(0)) 909 return true 910 } 911 } 912 varg := reflect.ValueOf(arg) 913 switch t.NumIn() { 914 default: 915 w.Errf("bad method %s: too many arguments", cmd) 916 return true 917 case 0: 918 if arg != "" { 919 w.Errf("%s takes no arguments", cmd) 920 return true 921 } 922 case 1: 923 if t.In(0) != varg.Type() { 924 w.Errf("bad method %s: argument type %v, not string", cmd, t.In(0)) 925 return true 926 } 927 } 928 929 args := []reflect.Value{} 930 if t.NumIn() > 0 { 931 args = append(args, varg) 932 } 933 out := m.Call(args) 934 var err error 935 if len(out) == 1 { 936 err, _ = out[0].Interface().(error) 937 } 938 if err != nil { 939 w.Errf("%v", err) 940 } 941 942 return true 943 } 944 945 func (w *Win) Selection() string { 946 w.Ctl("addr=dot") 947 data, err := w.ReadAll("xdata") 948 if err != nil { 949 w.Err(err.Error()) 950 } 951 return string(data) 952 } 953 954 func (w *Win) SetErrorPrefix(p string) { 955 w.errorPrefix = p 956 } 957 958 // Err finds or creates a window appropriate for showing errors related to w 959 // and then prints msg to that window. 960 // It adds a final newline to msg if needed. 961 func (w *Win) Err(msg string) { 962 Err(w.errorPrefix, msg) 963 } 964 965 func (w *Win) Errf(format string, args ...interface{}) { 966 w.Err(fmt.Sprintf(format, args...)) 967 } 968 969 // Err finds or creates a window appropriate for showing errors related to a window titled src 970 // and then prints msg to that window. It adds a final newline to msg if needed. 971 func Err(src, msg string) { 972 if !strings.HasSuffix(msg, "\n") { 973 msg = msg + "\n" 974 } 975 prefix, _ := path.Split(src) 976 if prefix == "/" || prefix == "." { 977 prefix = "" 978 } 979 name := prefix + "+Errors" 980 w1 := Show(name) 981 if w1 == nil { 982 var err error 983 w1, err = New() 984 if err != nil { 985 time.Sleep(100 * time.Millisecond) 986 w1, err = New() 987 if err != nil { 988 log.Fatalf("cannot create +Errors window") 989 } 990 } 991 w1.Name("%s", name) 992 } 993 w1.Addr("$") 994 w1.Ctl("dot=addr") 995 w1.Fprintf("body", "%s", msg) 996 w1.Addr(".,") 997 w1.Ctl("dot=addr") 998 w1.Ctl("show") 999 } 1000 1001 // Errf is like Err but accepts a printf-style formatting. 1002 func Errf(src, format string, args ...interface{}) { 1003 Err(src, fmt.Sprintf(format, args...)) 1004 } 1005 1006 // splitFields parses the line into fields. 1007 // Each element of fields must be one of *int, *string or *bool 1008 // which are set to the respective field value. 1009 // Boolean and numeric fields are expected to numbers formatted 1010 // in 11 characters followed by a space. 1011 // String fields are expected to be space terminated. 1012 // 1013 // It returns the rest of line after all the fields have been parsed. 1014 func splitFields(line string, fields ...interface{}) (string, error) { 1015 n := 0 1016 for len(fields) > 0 { 1017 switch f := fields[0].(type) { 1018 case *int, *bool: 1019 if len(line) < 12 { 1020 return "", fmt.Errorf("field %d is too short", n) 1021 } 1022 if line[11] != ' ' { 1023 return "", fmt.Errorf("field %d doesn't terminate in a space", n) 1024 } 1025 fn, err := strconv.Atoi(strings.TrimSpace(line[:11])) 1026 if err != nil { 1027 return "", fmt.Errorf("field %d is invalid: %v", n, err) 1028 } 1029 switch f := f.(type) { 1030 case *int: 1031 *f = fn 1032 case *bool: 1033 if fn != 0 && fn != 1 { 1034 return "", fmt.Errorf("field %d should be either 0 or 1", n) 1035 } 1036 *f = fn != 0 1037 } 1038 line = line[12:] 1039 case *string: 1040 i := strings.IndexByte(line, ' ') 1041 if i == -1 { 1042 return "", fmt.Errorf("no space found at end of string field %d", n) 1043 } 1044 *f = line[:i] 1045 line = line[i+1:] 1046 } 1047 fields = fields[1:] 1048 } 1049 return line, nil 1050 }