github.com/jmigpin/editor@v1.6.0/core/godebuginstance.go (about) 1 package core 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "io" 8 "log" 9 "path/filepath" 10 "sort" 11 "strings" 12 "sync" 13 "time" 14 15 "github.com/jmigpin/editor/core/godebug" 16 "github.com/jmigpin/editor/core/godebug/debug" 17 "github.com/jmigpin/editor/ui" 18 "github.com/jmigpin/editor/util/ctxutil" 19 "github.com/jmigpin/editor/util/drawutil/drawer4" 20 "github.com/jmigpin/editor/util/parseutil" 21 ) 22 23 // Note: Should have a unique instance because there is no easy solution to debug two (or more) programs that have common files in the same editor 24 25 const updatesPerSecond = 15 26 27 type GoDebugManager struct { 28 ed *Editor 29 inst struct { 30 sync.Mutex 31 inst *GoDebugInstance 32 cancel context.CancelFunc 33 } 34 } 35 36 func NewGoDebugManager(ed *Editor) *GoDebugManager { 37 gdm := &GoDebugManager{ed: ed} 38 return gdm 39 } 40 41 func (gdm *GoDebugManager) Printf(format string, args ...interface{}) { 42 gdm.ed.Messagef("godebug: "+format, args...) 43 } 44 45 func (gdm *GoDebugManager) RunAsync(reqCtx context.Context, erow *ERow, args []string) error { 46 47 gdm.inst.Lock() 48 defer gdm.inst.Unlock() 49 50 // cancel and wait for previous 51 gdm.cancelAndWait() 52 53 // setup instance context // TODO: editor ctx? 54 ctx, cancel := context.WithCancel(context.Background()) 55 56 // call cancel if reqCtx is done (short amount of time, just watches start) 57 clearWatching := ctxutil.WatchDone(cancel, reqCtx) 58 defer clearWatching() 59 60 inst, err := startGoDebugInstance(ctx, gdm.ed, gdm, erow, args) 61 if err != nil { 62 cancel() 63 return err 64 } 65 66 gdm.inst.inst = inst 67 gdm.inst.cancel = cancel 68 return nil 69 } 70 71 func (gdm *GoDebugManager) CancelAndClear() { 72 gdm.inst.Lock() 73 defer gdm.inst.Unlock() 74 gdm.cancelAndWait() // clears 75 } 76 func (gdm *GoDebugManager) cancelAndWait() { 77 if gdm.inst.inst != nil { 78 gdm.inst.cancel() 79 gdm.inst.inst.wait() 80 gdm.inst.inst = nil 81 gdm.inst.cancel = nil 82 } 83 } 84 85 func (gdm *GoDebugManager) SelectAnnotation(rowPos *ui.RowPos, ev *ui.RootSelectAnnotationEvent) { 86 gdm.inst.Lock() 87 defer gdm.inst.Unlock() 88 if gdm.inst.inst != nil { 89 gdm.inst.inst.selectAnnotation(rowPos, ev) 90 } 91 } 92 93 func (gdm *GoDebugManager) SelectERowAnnotation(erow *ERow, ev *ui.TextAreaSelectAnnotationEvent) { 94 gdm.inst.Lock() 95 defer gdm.inst.Unlock() 96 if gdm.inst.inst != nil { 97 gdm.inst.inst.selectERowAnnotation(erow, ev) 98 } 99 } 100 101 func (gdm *GoDebugManager) AnnotationFind(s string) error { 102 gdm.inst.Lock() 103 defer gdm.inst.Unlock() 104 if gdm.inst.inst == nil { 105 return fmt.Errorf("missing godebug instance") 106 } 107 return gdm.inst.inst.annotationFind(s) 108 } 109 110 func (gdm *GoDebugManager) UpdateUIERowInfo(info *ERowInfo) { 111 gdm.inst.Lock() 112 defer gdm.inst.Unlock() 113 if gdm.inst.inst != nil { 114 gdm.inst.inst.updateUIERowInfo(info) 115 } 116 } 117 118 //---------- 119 120 type GoDebugInstance struct { 121 ed *Editor 122 gdm *GoDebugManager 123 di *GDDataIndex 124 erowExecWait sync.WaitGroup 125 } 126 127 func startGoDebugInstance(ctx context.Context, ed *Editor, gdm *GoDebugManager, erow *ERow, args []string) (*GoDebugInstance, error) { 128 gdi := &GoDebugInstance{ed: ed, gdm: gdm} 129 gdi.di = NewGDDataIndex(ed) 130 if err := gdi.start2(ctx, erow, args); err != nil { 131 return nil, err 132 } 133 return gdi, nil 134 } 135 136 func (gdi *GoDebugInstance) wait() { 137 gdi.erowExecWait.Wait() 138 gdi.clearInfosUI() 139 } 140 141 //---------- 142 143 func (gdi *GoDebugInstance) start2(ctx context.Context, erow *ERow, args []string) error { 144 // warn other annotators about starting a godebug session 145 _ = gdi.ed.CanModifyAnnotations(EareqGoDebugStart, erow.Row.TextArea) 146 147 // create new erow if necessary 148 if erow.Info.IsFileButNotDir() { 149 dir := filepath.Dir(erow.Info.Name()) 150 info := erow.Ed.ReadERowInfo(dir) 151 rowPos := erow.Row.PosBelow() 152 erow = NewBasicERow(info, rowPos) 153 } 154 155 if !erow.Info.IsDir() { 156 return fmt.Errorf("can't run on this erow type") 157 } 158 159 gdi.erowExecWait.Add(1) 160 erow.Exec.RunAsync(func(erowCtx context.Context, rw io.ReadWriter) error { 161 defer gdi.erowExecWait.Done() 162 163 // call cancel if ctx is done (allow cancel from godebugmanager) 164 erowCtx2, cancel := context.WithCancel(erowCtx) 165 defer cancel() 166 clearWatching := ctxutil.WatchDone(cancel, ctx) 167 defer clearWatching() 168 169 return gdi.runCmd(erowCtx2, erow, args, rw) 170 }) 171 172 return nil 173 } 174 175 func (gdi *GoDebugInstance) runCmd(ctx context.Context, erow *ERow, args []string, w io.Writer) error { 176 cmd := godebug.NewCmd() 177 178 cmd.Dir = erow.Info.Name() 179 cmd.Stdout = w 180 cmd.Stderr = w 181 182 done, err := cmd.Start(ctx, args[1:]) 183 if err != nil { 184 return err 185 } 186 if done { 187 return nil 188 } 189 190 if err := gdi.di.handleFilesDataMsg(cmd.FilesData()); err != nil { 191 return err 192 } 193 194 gdi.clientMsgsLoop(ctx, w, cmd) // blocking 195 196 return cmd.Wait() 197 } 198 199 //---------- 200 201 func (gdi *GoDebugInstance) selectERowAnnotation(erow *ERow, ev *ui.TextAreaSelectAnnotationEvent) { 202 if gdi.selectERowAnnotation2(erow, ev) { 203 gdi.updateUIShowLine(erow.Row.PosBelow()) 204 } 205 } 206 207 func (gdi *GoDebugInstance) selectERowAnnotation2(erow *ERow, ev *ui.TextAreaSelectAnnotationEvent) bool { 208 switch ev.Type { 209 case ui.TASelAnnTypeCurrent, 210 ui.TASelAnnTypeCurrentPrev, 211 ui.TASelAnnTypeCurrentNext: 212 return gdi.di.annMsgChangeCurrent(erow.Info.Name(), ev.AnnotationIndex, ev.Type) 213 case ui.TASelAnnTypePrint: 214 gdi.printIndex(erow, ev.AnnotationIndex, ev.Offset) 215 return false 216 case ui.TASelAnnTypePrintAllPrevious: 217 gdi.printIndexAllPrevious(erow, ev.AnnotationIndex, ev.Offset) 218 return false 219 default: 220 log.Printf("todo: %#v", ev) 221 } 222 return false 223 } 224 225 //---------- 226 227 func (gdi *GoDebugInstance) annotationFind(s string) error { 228 _, ok := gdi.di.selectedAnnFind(s) 229 if !ok { 230 return fmt.Errorf("string not found in selected annotation: %v", s) 231 } 232 gdi.updateUIShowLine(gdi.ed.GoodRowPos()) 233 return nil 234 } 235 236 //---------- 237 238 func (gdi *GoDebugInstance) selectAnnotation(rowPos *ui.RowPos, ev *ui.RootSelectAnnotationEvent) { 239 if gdi.selectAnnotation2(ev) { 240 gdi.updateUIShowLine(rowPos) 241 } 242 } 243 244 func (gdi *GoDebugInstance) selectAnnotation2(ev *ui.RootSelectAnnotationEvent) bool { 245 switch ev.Type { 246 case ui.RootSelAnnTypeFirst: 247 _ = gdi.di.selectFirst() 248 gdi.openArrivalIndexERow() 249 return true // show always 250 case ui.RootSelAnnTypeLast: 251 _ = gdi.di.selectLast() 252 gdi.openArrivalIndexERow() 253 return true // show always 254 case ui.RootSelAnnTypePrev: 255 _ = gdi.di.selectPrev() 256 gdi.openArrivalIndexERow() 257 return true // show always 258 case ui.RootSelAnnTypeNext: 259 _ = gdi.di.selectNext() 260 gdi.openArrivalIndexERow() 261 return true // show always 262 case ui.RootSelAnnTypeClear: 263 gdi.di.clearMsgs() 264 return true 265 default: 266 log.Printf("todo: %#v", ev) 267 } 268 return false 269 } 270 271 //---------- 272 273 func (gdi *GoDebugInstance) printIndex(erow *ERow, annIndex, offset int) { 274 msg, ok := gdi.di.annMsg(erow.Info.Name(), annIndex) 275 if !ok { 276 return 277 } 278 // build output 279 s := godebug.StringifyItemFull(msg.dbgLineMsg.Item) 280 gdi.gdm.Printf("annotation: #%d\n\t%v\n", msg.arrivalIndex, s) 281 } 282 283 func (gdi *GoDebugInstance) printIndexAllPrevious(erow *ERow, annIndex, offset int) { 284 msgs, ok := gdi.di.annPreviousMsgs(erow.Info.Name(), annIndex) 285 if !ok { 286 return 287 } 288 // build output 289 sb := strings.Builder{} 290 for _, msg := range msgs { 291 s := godebug.StringifyItemFull(msg.dbgLineMsg.Item) 292 sb.WriteString(fmt.Sprintf("\t" + s + "\n")) 293 } 294 gdi.gdm.Printf("annotations (%d entries):\n%v\n", len(msgs), sb.String()) 295 } 296 297 //---------- 298 299 func (gdi *GoDebugInstance) clientMsgsLoop(ctx context.Context, w io.Writer, cmd *godebug.Cmd) { 300 var updatec <-chan time.Time // update channel 301 updateUI := func() { 302 if updatec != nil { 303 updatec = nil 304 gdi.updateUI() 305 } 306 } 307 308 for { 309 select { 310 case <-ctx.Done(): 311 updateUI() // final ui update 312 return 313 case msg, ok := <-cmd.Client.Messages: 314 if !ok { 315 updateUI() // last msg (end of program), final ui update 316 return 317 } 318 if err := gdi.handleMsg(msg, cmd); err != nil { 319 fmt.Fprintf(w, "error: %v\n", err) 320 } 321 if updatec == nil { 322 t := time.NewTimer(time.Second / updatesPerSecond) 323 updatec = t.C 324 } 325 case <-updatec: 326 updateUI() 327 } 328 } 329 } 330 331 //---------- 332 333 func (gdi *GoDebugInstance) handleMsg(msg interface{}, cmd *godebug.Cmd) error { 334 switch t := msg.(type) { 335 case error: 336 return t 337 case *debug.LineMsg: 338 return gdi.di.handleLineMsgs(t) 339 case []*debug.LineMsg: 340 return gdi.di.handleLineMsgs(t...) 341 default: 342 return fmt.Errorf("unexpected msg: %T", msg) 343 } 344 return nil 345 } 346 347 //---------- 348 349 func (gdi *GoDebugInstance) updateUI() { 350 gdi.ed.UI.RunOnUIGoRoutine(func() { 351 gdi.updateUI2() 352 }) 353 } 354 355 func (gdi *GoDebugInstance) updateUIShowLine(rowPos *ui.RowPos) { 356 gdi.ed.UI.RunOnUIGoRoutine(func() { 357 gdi.updateUI2() 358 gdi.showSelectedLine(rowPos) 359 }) 360 } 361 362 func (gdi *GoDebugInstance) updateUIERowInfo(info *ERowInfo) { 363 gdi.ed.UI.RunOnUIGoRoutine(func() { 364 gdi.updateInfoUI(info) 365 }) 366 } 367 368 //---------- 369 370 func (gdi *GoDebugInstance) clearInfosUI() { 371 gdi.ed.UI.RunOnUIGoRoutine(func() { 372 for _, info := range gdi.ed.ERowInfos() { 373 gdi.clearInfoUI(info) 374 } 375 }) 376 } 377 378 func (gdi *GoDebugInstance) clearInfoUI(info *ERowInfo) { 379 info.UpdateAnnotationsRowState(false) 380 info.UpdateAnnotationsEditedRowState(false) 381 gdi.clearAnnotations(info) 382 } 383 384 //---------- 385 386 func (gdi *GoDebugInstance) updateUI2() { 387 for _, info := range gdi.ed.ERowInfos() { 388 gdi.updateInfoUI(info) 389 } 390 } 391 392 func (gdi *GoDebugInstance) updateInfoUI(info *ERowInfo) { 393 // TODO: the info should be get with one locked call to the dataindex 394 395 // Note: the current selected debug line might not have an open erow (ex: when auto increased to match the lastarrivalindex). 396 397 // file belongs to the godebug session 398 findex, ok := gdi.di.FilesIndex(info.Name()) 399 if !ok { 400 info.UpdateAnnotationsRowState(false) 401 info.UpdateAnnotationsEditedRowState(false) 402 gdi.clearAnnotations(info) 403 return 404 } 405 info.UpdateAnnotationsRowState(true) 406 407 // check if content has changed 408 edited := gdi.di.updateFileEdited(info) 409 if edited { 410 info.UpdateAnnotationsEditedRowState(true) 411 gdi.clearAnnotations(info) 412 return 413 } 414 info.UpdateAnnotationsEditedRowState(false) 415 416 selLine, ok := gdi.di.findSelectedAndUpdateAnnEntries(findex) 417 if !ok { 418 selLine = -1 419 } 420 421 // set annotations 422 file := gdi.di.files[findex] // TODO: not locked (file.AnnEntries used) 423 for _, erow := range info.ERows { 424 gdi.setAnnotations(erow, true, selLine, file.annEntries) 425 } 426 } 427 428 func (gdi *GoDebugInstance) clearAnnotations(info *ERowInfo) { 429 for _, erow := range info.ERows { 430 gdi.setAnnotations(erow, false, -1, nil) 431 } 432 } 433 434 func (gdi *GoDebugInstance) setAnnotations(erow *ERow, on bool, selIndex int, entries []*drawer4.Annotation) { 435 gdi.ed.SetAnnotations(EareqGoDebug, erow.Row.TextArea, on, selIndex, entries) 436 } 437 438 //---------- 439 440 func (gdi *GoDebugInstance) showSelectedLine(rowPos *ui.RowPos) { 441 msg, filename, arrivalIndex, edited, ok := gdi.di.selectedMsg() 442 if !ok { 443 return 444 } 445 446 // TODO: don't show if on UI list, show warnings about skipped steps 447 // some rows show because the selected arrival index is just increased 448 // but in the case of searching for the next selected arrival index, if the info row is not opened, it doesn't search inside that file, and so the index stays the same as the last selected index 449 450 // don't show on edited files 451 if edited { 452 gdi.ed.Errorf("selection at edited row: %v: step %v", filename, arrivalIndex) 453 return 454 } 455 456 // file offset 457 dlm := msg.dbgLineMsg 458 fo := &parseutil.FilePos{Filename: filename, Offset: dlm.Offset} 459 460 // show line 461 conf := &OpenFileERowConfig{ 462 FilePos: fo, 463 RowPos: rowPos, 464 FlashVisibleOffsets: true, 465 NewIfNotExistent: true, 466 } 467 OpenFileERow(gdi.ed, conf) 468 } 469 470 //---------- 471 472 func (gdi *GoDebugInstance) openArrivalIndexERow() { 473 _, filename, ok := gdi.di.selectedArrivalIndexFilename() 474 if !ok { 475 return 476 } 477 478 rowPos := gdi.ed.GoodRowPos() 479 conf := &OpenFileERowConfig{ 480 FilePos: &parseutil.FilePos{Filename: filename}, 481 RowPos: rowPos, 482 CancelIfExistent: true, 483 NewIfNotExistent: true, 484 } 485 gdi.ed.UI.RunOnUIGoRoutine(func() { 486 OpenFileERow(gdi.ed, conf) 487 }) 488 } 489 490 //---------- 491 492 // GoDebug data Index 493 type GDDataIndex struct { 494 sync.RWMutex // used internally, not to be locked outside 495 496 ed *Editor 497 filesIndexM map[string]int // [name]fileindex 498 filesEdited map[int]bool // [fileindex] 499 500 afds []*debug.AnnotatorFileData // [fileindex] 501 files []*GDFileMsgs // [fileindex] 502 503 lastArrivalIndex int 504 selected struct { 505 arrivalIndex int 506 fileIndex int 507 lineIndex int 508 lineStepIndex int 509 } 510 } 511 512 func NewGDDataIndex(ed *Editor) *GDDataIndex { 513 di := &GDDataIndex{ed: ed} 514 di.filesIndexM = map[string]int{} 515 di.filesEdited = map[int]bool{} 516 di.clearMsgs() 517 return di 518 } 519 520 func (di *GDDataIndex) FilesIndex(name string) (int, bool) { 521 name = di.FilesIndexKey(name) 522 v, ok := di.filesIndexM[name] 523 return v, ok 524 } 525 func (di *GDDataIndex) FilesIndexKey(name string) string { 526 if di.ed.FsCaseInsensitive { 527 name = strings.ToLower(name) 528 } 529 return name 530 } 531 532 func (di *GDDataIndex) clearMsgs() { 533 di.Lock() 534 defer di.Unlock() 535 for _, f := range di.files { 536 n := len(f.linesMsgs) // keep n 537 u := NewGDFileMsgs(n) 538 *f = *u 539 } 540 di.lastArrivalIndex = -1 541 di.selected.arrivalIndex = di.lastArrivalIndex 542 } 543 544 //---------- 545 546 func (di *GDDataIndex) handleFilesDataMsg(fdm *debug.FilesDataMsg) error { 547 di.Lock() 548 defer di.Unlock() 549 550 di.afds = fdm.Data 551 // index filenames 552 di.filesIndexM = map[string]int{} 553 for _, afd := range di.afds { 554 name := di.FilesIndexKey(afd.Filename) 555 di.filesIndexM[name] = afd.FileIndex 556 } 557 // init index 558 di.files = make([]*GDFileMsgs, len(di.afds)) 559 for _, afd := range di.afds { 560 // check index 561 if afd.FileIndex >= len(di.files) { 562 return fmt.Errorf("bad file index at init: %v len=%v", afd.FileIndex, len(di.files)) 563 } 564 di.files[afd.FileIndex] = NewGDFileMsgs(afd.DebugLen) 565 } 566 return nil 567 } 568 569 func (di *GDDataIndex) handleLineMsgs(msgs ...*debug.LineMsg) error { 570 di.Lock() 571 defer di.Unlock() 572 for _, msg := range msgs { 573 err := di._handleLineMsg(msg) 574 if err != nil { 575 return err 576 } 577 } 578 return nil 579 } 580 581 // Not locked 582 func (di *GDDataIndex) _handleLineMsg(u *debug.LineMsg) error { 583 // check index 584 l1 := len(di.files) 585 if u.FileIndex >= l1 { 586 return fmt.Errorf("bad file index: %v len=%v", u.FileIndex, l1) 587 } 588 // check index 589 l2 := len(di.files[u.FileIndex].linesMsgs) 590 if u.DebugIndex >= l2 { 591 return fmt.Errorf("bad debug index: %v len=%v", u.DebugIndex, l2) 592 } 593 // line msg 594 di.lastArrivalIndex++ // starts/clears to -1, so first n is 0 595 lm := &GDLineMsg{arrivalIndex: di.lastArrivalIndex, dbgLineMsg: u} 596 // index msg 597 w := &di.files[u.FileIndex].linesMsgs[u.DebugIndex].lineMsgs 598 *w = append(*w, lm) 599 // mark file as having new data (performance) 600 //di.files[u.FileIndex].hasNewData = true 601 602 // auto update selected index if at last position 603 if di.selected.arrivalIndex == di.lastArrivalIndex-1 { 604 di.selected.arrivalIndex = di.lastArrivalIndex 605 } 606 607 return nil 608 } 609 610 //---------- 611 612 func (di *GDDataIndex) annMsg(filename string, annIndex int) (*GDLineMsg, bool) { 613 di.RLock() 614 defer di.RUnlock() 615 616 file, line, ok := di._annIndexFileLine(filename, annIndex) 617 if !ok { 618 return nil, false 619 } 620 // current msg index at line 621 k := file.annEntriesLMIndex[annIndex] // same length as lineMsgs 622 if k < 0 || k >= len(line.lineMsgs) { // currently nothing is shown or cleared 623 return nil, false 624 } 625 return line.lineMsgs[k], true 626 } 627 628 func (di *GDDataIndex) annPreviousMsgs(filename string, annIndex int) ([]*GDLineMsg, bool) { 629 di.RLock() 630 defer di.RUnlock() 631 632 file, line, ok := di._annIndexFileLine(filename, annIndex) 633 if !ok { 634 return nil, false 635 } 636 // current msg index at line 637 k := file.annEntriesLMIndex[annIndex] // same length as lineMsgs 638 if k < 0 || k >= len(line.lineMsgs) { // currently nothing is shown or cleared 639 return nil, false 640 } 641 return line.lineMsgs[:k+1], true 642 } 643 644 func (di *GDDataIndex) selectedAnnFind(s string) (*GDLineMsg, bool) { 645 di.RLock() 646 defer di.RUnlock() 647 648 annIndex, filename, ok := di.selectedArrivalIndexFilename() 649 if !ok { 650 return nil, false 651 } 652 653 file, line, ok := di._annIndexFileLine(filename, annIndex) 654 if !ok { 655 return nil, false 656 } 657 658 b := []byte(s) 659 k := file.annEntriesLMIndex[annIndex] // current entry 660 for i := 0; i < len(line.lineMsgs); i++ { 661 h := (k + 1 + i) % len(line.lineMsgs) 662 msg := line.lineMsgs[h] 663 ann := msg.annotation() 664 j := bytes.Index(ann.Bytes, b) 665 if j >= 0 { 666 di.selected.arrivalIndex = msg.arrivalIndex 667 return msg, true 668 } 669 } 670 671 return nil, false 672 } 673 674 func (di *GDDataIndex) annMsgChangeCurrent(filename string, annIndex int, typ ui.TASelAnnType) bool { 675 di.Lock() // writes di.selected 676 defer di.Unlock() 677 678 file, line, ok := di._annIndexFileLine(filename, annIndex) 679 if !ok { 680 return false 681 } 682 // current msg index at line 683 k := file.annEntriesLMIndex[annIndex] // same length as lineMsgs 684 685 // adjust k according to type 686 switch typ { 687 case ui.TASelAnnTypeCurrent: 688 // allow to select first if no line is visible 689 if k < 0 { 690 k = 0 691 } 692 case ui.TASelAnnTypeCurrentPrev: 693 k-- 694 case ui.TASelAnnTypeCurrentNext: 695 k++ 696 default: 697 panic(fmt.Sprintf("unexpected type: %v", typ)) 698 } 699 700 if k < 0 || k >= len(line.lineMsgs) { // currently nothing is shown or cleared 701 return false 702 } 703 di.selected.arrivalIndex = line.lineMsgs[k].arrivalIndex 704 return true 705 } 706 707 // Not locked 708 func (di *GDDataIndex) _annIndexFileLine(filename string, annIndex int) (*GDFileMsgs, *GDLineMsgs, bool) { 709 // file 710 findex, ok := di.FilesIndex(filename) 711 if !ok { 712 return nil, nil, false 713 } 714 file := di.files[findex] 715 // line 716 if annIndex < 0 || annIndex >= len(file.linesMsgs) { 717 return nil, nil, false 718 } 719 return file, &file.linesMsgs[annIndex], true 720 } 721 722 //---------- 723 724 func (di *GDDataIndex) selectFirst() bool { 725 di.Lock() 726 defer di.Unlock() 727 if di.selected.arrivalIndex != 0 && 0 <= di.lastArrivalIndex { // could be -1 728 di.selected.arrivalIndex = 0 729 return true 730 } 731 return false 732 } 733 734 func (di *GDDataIndex) selectLast() bool { 735 di.Lock() 736 defer di.Unlock() 737 if di.selected.arrivalIndex != di.lastArrivalIndex { 738 di.selected.arrivalIndex = di.lastArrivalIndex 739 return true 740 } 741 return false 742 } 743 744 func (di *GDDataIndex) selectPrev() bool { 745 di.Lock() 746 defer di.Unlock() 747 if di.selected.arrivalIndex > 0 { 748 di.selected.arrivalIndex-- 749 return true 750 } 751 return false 752 } 753 754 func (di *GDDataIndex) selectNext() bool { 755 di.Lock() 756 defer di.Unlock() 757 if di.selected.arrivalIndex < di.lastArrivalIndex { 758 di.selected.arrivalIndex++ 759 return true 760 } 761 return false 762 } 763 764 //---------- 765 766 func (di *GDDataIndex) findSelectedAndUpdateAnnEntries(findex int) (int, bool) { 767 di.Lock() 768 defer di.Unlock() 769 file := di.files[findex] 770 selLine, selLineStep, selFound := file._findSelectedAndUpdateAnnEntries(di.selected.arrivalIndex) 771 if selFound { 772 di.selected.fileIndex = findex 773 di.selected.lineIndex = selLine 774 di.selected.lineStepIndex = selLineStep 775 } 776 return selLine, selFound 777 } 778 779 //---------- 780 781 func (di *GDDataIndex) selectedMsg() (*GDLineMsg, string, int, bool, bool) { 782 di.RLock() 783 defer di.RUnlock() 784 785 msg, ok := di._selectedMsg2() 786 if !ok { 787 return nil, "", 0, false, false 788 } 789 790 findex := di.selected.fileIndex 791 filename := di.afds[findex].Filename 792 edited := di.filesEdited[findex] 793 return msg, filename, di.selected.arrivalIndex, edited, true 794 } 795 796 // Not locked. 797 func (di *GDDataIndex) _selectedMsg2() (*GDLineMsg, bool) { 798 // in case of a clear 799 if di.selected.arrivalIndex < 0 { 800 return nil, false 801 } 802 803 findex := di.selected.fileIndex 804 if findex < 0 || findex >= len(di.files) { 805 return nil, false 806 } 807 file := di.files[findex] 808 809 lineIndex := di.selected.lineIndex 810 if lineIndex < 0 || lineIndex >= len(file.linesMsgs) { 811 return nil, false 812 } 813 lm := file.linesMsgs[lineIndex] 814 815 stepIndex := di.selected.lineStepIndex 816 if stepIndex < 0 || stepIndex >= len(lm.lineMsgs) { 817 return nil, false 818 } 819 820 return lm.lineMsgs[stepIndex], true 821 } 822 823 //---------- 824 825 func (di *GDDataIndex) updateFileEdited(info *ERowInfo) bool { 826 di.Lock() 827 defer di.Unlock() 828 findex, ok := di.FilesIndex(info.Name()) 829 if !ok { 830 return false 831 } 832 afd := di.afds[findex] 833 edited := !info.EqualToBytesHash(afd.FileSize, afd.FileHash) 834 di.filesEdited[findex] = edited 835 return edited 836 } 837 838 func (di *GDDataIndex) isFileEdited(filename string) bool { 839 di.RLock() 840 defer di.RUnlock() 841 findex, ok := di.FilesIndex(filename) 842 if !ok { 843 return false 844 } 845 return di.filesEdited[findex] 846 } 847 848 //---------- 849 850 func (di *GDDataIndex) selectedArrivalIndexFilename() (int, string, bool) { 851 return di.arrivalIndexFilename(di.selected.arrivalIndex) 852 } 853 854 func (di *GDDataIndex) arrivalIndexFilename(arrivalIndex int) (int, string, bool) { 855 di.RLock() 856 defer di.RUnlock() 857 for findex, file := range di.files { 858 for j, lm := range file.linesMsgs { 859 _, eqK, _ := lm.findIndex(arrivalIndex) 860 if eqK { 861 return j, di.afds[findex].Filename, true 862 } 863 } 864 } 865 return -1, "", false 866 } 867 868 //---------- 869 870 type GDFileMsgs struct { 871 linesMsgs []GDLineMsgs // [lineIndex] file annotations received 872 873 // current annotation entries to be shown with a file 874 annEntries []*drawer4.Annotation 875 annEntriesLMIndex []int // [lineIndex]stepIndex: line messages index: keep selected step index to know the msg entry when coming from a click on an annotation 876 877 //hasNewData bool // performance 878 } 879 880 func NewGDFileMsgs(n int) *GDFileMsgs { 881 return &GDFileMsgs{ 882 linesMsgs: make([]GDLineMsgs, n), 883 annEntries: make([]*drawer4.Annotation, n), 884 annEntriesLMIndex: make([]int, n), 885 } 886 } 887 888 // Not locked 889 func (file *GDFileMsgs) _findSelectedAndUpdateAnnEntries(arrivalIndex int) (int, int, bool) { 890 found := false 891 selLine := 0 892 selLineStep := 0 893 for line, lm := range file.linesMsgs { 894 k, eqK, foundK := lm.findIndex(arrivalIndex) 895 if foundK { 896 file.annEntries[line] = lm.lineMsgs[k].annotation() 897 file.annEntriesLMIndex[line] = k 898 if eqK { 899 found = true 900 selLine = line 901 selLineStep = k 902 } 903 } else { 904 if len(lm.lineMsgs) > 0 { 905 file.annEntries[line] = lm.lineMsgs[0].emptyAnnotation() 906 } else { 907 file.annEntries[line] = nil // no msgs ever received 908 } 909 file.annEntriesLMIndex[line] = -1 910 } 911 } 912 return selLine, selLineStep, found 913 } 914 915 //---------- 916 917 type GDLineMsgs struct { 918 lineMsgs []*GDLineMsg // [arrivalIndex] line annotations received 919 } 920 921 func (lm *GDLineMsgs) findIndex(arrivalIndex int) (int, bool, bool) { 922 k := sort.Search(len(lm.lineMsgs), func(i int) bool { 923 u := lm.lineMsgs[i].arrivalIndex 924 return u >= arrivalIndex 925 }) 926 foundK := false 927 eqK := false 928 if k < len(lm.lineMsgs) && lm.lineMsgs[k].arrivalIndex == arrivalIndex { 929 eqK = true 930 foundK = true 931 } else { 932 k-- // current k is above arrivalIndex, want previous 933 foundK = k >= 0 934 } 935 return k, eqK, foundK 936 } 937 938 //---------- 939 940 type GDLineMsg struct { 941 arrivalIndex int 942 dbgLineMsg *debug.LineMsg 943 cache struct { 944 item []byte 945 ann *drawer4.Annotation 946 } 947 } 948 949 func (msg *GDLineMsg) ann() *drawer4.Annotation { 950 if msg.cache.ann == nil { 951 msg.cache.ann = &drawer4.Annotation{Offset: msg.dbgLineMsg.Offset} 952 } 953 return msg.cache.ann 954 } 955 956 func (msg *GDLineMsg) annotation() *drawer4.Annotation { 957 ann := msg.ann() 958 if msg.cache.item == nil { 959 s := godebug.StringifyItem(msg.dbgLineMsg.Item) 960 msg.cache.item = []byte(s) 961 } 962 ann.Bytes = msg.cache.item 963 ann.NotesBytes = []byte(fmt.Sprintf("#%d", msg.arrivalIndex)) 964 return ann 965 } 966 967 func (msg *GDLineMsg) emptyAnnotation() *drawer4.Annotation { 968 ann := msg.ann() 969 ann.Bytes = []byte(" ") 970 ann.NotesBytes = nil 971 return ann 972 }