package debugger
import (
"context"
"fmt"
"log"
"regexp"
"slices"
"strings"
"time"
"code.rocketnine.space/tslocum/cbind"
"github.com/gdamore/tcell/v2"
"github.com/pancsta/cview"
amhelp "github.com/pancsta/asyncmachine-go/pkg/helpers"
am "github.com/pancsta/asyncmachine-go/pkg/machine"
ss "github.com/pancsta/asyncmachine-go/tools/debugger/states"
)
var re = regexp .MustCompile (`\[(.*?)\]` )
func normalizeText(text string ) string {
return strings .ToLower (re .ReplaceAllString (text , "" ))
}
func (d *Debugger ) hBindKeyboard () {
inputHandler := d .hInitFocusManager ()
for key , fn := range d .getKeystrokes () {
err := inputHandler .Set (key , fn )
if err != nil {
log .Printf ("Error: binding keys %s" , err )
}
}
d .hSearchSchemaClients (inputHandler )
d .App .SetInputCapture (inputHandler .Capture )
}
func (d *Debugger ) hInitFocusManager () *cbind .Configuration {
d .focusManager = cview .NewFocusManager (d .App .SetFocus )
d .focusManager .SetWrapAround (true )
inputHandler := cbind .NewConfiguration ()
d .App .SetAfterFocusFunc (d .newAfterFocusFn ())
focusChange := func (f func ()) func (ev *tcell .EventKey ) *tcell .EventKey {
return func (ev *tcell .EventKey ) *tcell .EventKey {
defer d .Mach .PanicToErr (nil )
if d .Mach .Any1 (ss .GroupDialog ...) {
return ev
}
f ()
return nil
}
}
for _ , key := range cview .Keys .MovePreviousField {
err := inputHandler .Set (key , focusChange (d .focusManager .FocusPrevious ))
if err != nil {
log .Printf ("Error: binding keys %s" , err )
}
}
for _ , key := range cview .Keys .MoveNextField {
err := inputHandler .Set (key , focusChange (d .focusManager .FocusNext ))
if err != nil {
log .Printf ("Error: binding keys %s" , err )
}
}
return inputHandler
}
func (d *Debugger ) newAfterFocusFn () func (p cview .Primitive ) {
return func (p cview .Primitive ) {
if !d .Mach .WillBe1 (ss .AfterFocus , am .PositionLast ) {
d .Mach .Add1 (ss .AfterFocus , am .A {"cview.Primitive" : p })
}
}
}
func (d *Debugger ) hSearchSchemaClients (inputHandler *cbind .Configuration ) {
var (
bufferStart time .Time
buffer string
keys = []string {"-" , "_" }
)
for i := 0 ; i < 26 ; i ++ {
keys = append (keys ,
fmt .Sprintf ("%c" , 'a' +i ))
}
for _ , key := range keys {
key := key
err := inputHandler .Set (key , func (ev *tcell .EventKey ) *tcell .EventKey {
if d .Mach .Not (am .S {ss .ClientListFocused , ss .TreeFocused }) {
return ev
}
if bufferStart .Add (searchAsTypeWindow ).After (time .Now ()) {
buffer += key
} else {
bufferStart = time .Now ()
buffer = key
}
if d .Mach .Is1 (ss .ClientListFocused ) {
currIdx := d .clientList .GetCurrentItemIndex ()
for i , item := range d .clientList .GetItems () {
if i +1 <= currIdx {
continue
}
text := normalizeText (item .GetMainText ())
if strings .HasPrefix (text , buffer ) {
d .clientList .SetCurrentItem (i )
d .hUpdateClientList ()
d .draw (d .clientList )
break
}
}
} else if d .Mach .Is1 (ss .TreeFocused ) {
currNodePassed := false
currNode := d .tree .GetCurrentNode ()
var nodeMatch *cview .TreeNode
d .treeRoot .Walk (
func (node , parent *cview .TreeNode , depth int ) bool {
if nodeMatch != nil {
return false
}
if !currNodePassed && node != currNode {
return true
} else if !currNodePassed {
currNodePassed = true
return true
}
text := normalizeText (node .GetText ())
p := parent
for p != nil {
if !p .IsExpanded () {
return true
}
p = p .GetParent ()
}
if strings .HasPrefix (text , buffer ) {
nodeMatch = node
return false
}
return true
})
if nodeMatch != nil {
ref , ok := nodeMatch .GetReference ().(*nodeRef )
if ok && ref != nil && ref .stateName != "" {
d .Mach .Add1 (ss .StateNameSelected , am .A {
"state" : ref .stateName ,
})
} else {
d .Mach .Remove1 (ss .StateNameSelected , nil )
}
d .hUpdateSchemaTree ()
d .hUpdateLogReader (nil )
d .draw ()
d .tree .SetCurrentNode (nodeMatch )
}
}
return nil
})
if err != nil {
log .Printf ("Error: binding keys %s" , err )
}
}
}
type tcellKeyFn = func (ev *tcell .EventKey ) *tcell .EventKey
func (d *Debugger ) getKeystrokes () map [string ]tcellKeyFn {
return map [string ]func (ev *tcell .EventKey ) *tcell .EventKey {
"space" : func (ev *tcell .EventKey ) *tcell .EventKey {
if d .Mach .Is1 (ss .Paused ) {
d .Mach .Add1 (ss .Playing , nil )
} else {
d .Mach .Add1 (ss .Paused , nil )
}
return nil
},
"left" : d .hPrevTxKey (),
"right" : d .hNextTxKey (),
"alt+h" : d .hJumpBackKey ,
"alt+l" : d .hJumpFwdKey ,
"alt+Left" : d .hJumpBackKey ,
"alt+Right" : d .hJumpFwdKey ,
"alt+j" : func (ev *tcell .EventKey ) *tcell .EventKey {
d .Mach .Add1 (ss .UserBackStep , nil )
return nil
},
"alt+k" : func (ev *tcell .EventKey ) *tcell .EventKey {
d .Mach .Add1 (ss .UserFwdStep , nil )
return nil
},
"alt+n" : func (ev *tcell .EventKey ) *tcell .EventKey {
return tcell .NewEventKey (tcell .KeyPgDn , ' ' , tcell .ModNone )
},
"alt+p" : func (ev *tcell .EventKey ) *tcell .EventKey {
return tcell .NewEventKey (tcell .KeyPgUp , ' ' , tcell .ModNone )
},
"alt+e" : func (ev *tcell .EventKey ) *tcell .EventKey {
d .hToolExpand ()
return nil
},
"alt+o" : func (ev *tcell .EventKey ) *tcell .EventKey {
d .Mach .Toggle1 (ss .LogReaderEnabled , nil )
return nil
},
"alt+v" : func (ev *tcell .EventKey ) *tcell .EventKey {
d .Mach .Toggle1 (ss .TailMode , nil )
return nil
},
"alt+m" : func (ev *tcell .EventKey ) *tcell .EventKey {
d .toolMatrix ()
return nil
},
"alt+r" : func (ev *tcell .EventKey ) *tcell .EventKey {
d .Mach .Add1 (ss .ToolRain , nil )
return nil
},
"home" : func (ev *tcell .EventKey ) *tcell .EventKey {
d .hToolFirstTx (nil )
return nil
},
"end" : func (ev *tcell .EventKey ) *tcell .EventKey {
d .hToolLastTx (nil )
return nil
},
"ctrl+q" : func (ev *tcell .EventKey ) *tcell .EventKey {
d .Mach .Remove1 (ss .Start , nil )
return nil
},
"?" : func (ev *tcell .EventKey ) *tcell .EventKey {
if d .Mach .Not1 (ss .HelpDialog ) {
d .Mach .Add1 (ss .HelpDialog , nil )
} else {
d .Mach .Remove (ss .GroupDialog , nil )
}
return ev
},
"alt+f" : func (ev *tcell .EventKey ) *tcell .EventKey {
if d .Mach .Not1 (ss .Toolbar1Focused ) {
d .focusManager .Focus (d .toolbars [0 ])
} else {
d .focusManager .Focus (d .clientList )
}
d .draw ()
return ev
},
"alt+s" : func (ev *tcell .EventKey ) *tcell .EventKey {
if d .Mach .Not1 (ss .ExportDialog ) {
d .Mach .Add1 (ss .ExportDialog , nil )
} else {
d .Mach .Remove (ss .GroupDialog , nil )
}
return ev
},
"esc" : func (ev *tcell .EventKey ) *tcell .EventKey {
if d .Mach .Any1 (ss .GroupDialog ...) {
d .Mach .Remove (ss .GroupDialog , nil )
return nil
}
d .focusDefault ()
return ev
},
"backspace" : func (ev *tcell .EventKey ) *tcell .EventKey {
if d .Mach .Not1 (ss .ClientListFocused ) {
return ev
}
sel := d .clientList .GetCurrentItem ()
if sel == nil || d .Mach .Not1 (ss .ClientListFocused ) {
return nil
}
ref := sel .GetReference ().(*sidebarRef )
d .Mach .Add1 (ss .RemoveClient , am .A {"Client.id" : ref .name })
return nil
},
"down" : func (ev *tcell .EventKey ) *tcell .EventKey {
if d .Mach .Is (S {ss .ClientListFocused , ss .ClientListVisible }) {
d .updateClientList ()
} else if d .Mach .Is1 (ss .LogFocused ) {
d .Mach .Add1 (ss .LogUserScrolled , nil )
}
return ev
},
"up" : func (ev *tcell .EventKey ) *tcell .EventKey {
if d .Mach .Is (S {ss .ClientListFocused , ss .ClientListVisible }) {
d .updateClientList ()
} else if d .Mach .Is1 (ss .LogFocused ) {
d .Mach .Add1 (ss .LogUserScrolled , nil )
}
return ev
},
}
}
func (d *Debugger ) focusDefault () {
if d .Mach .Is1 (ss .ClientListVisible ) {
d .Mach .Add1 (ss .ClientListFocused , nil )
} else if d .Mach .Not1 (ss .TimelineTxHidden ) {
d .Mach .Add1 (ss .TimelineTxsFocused , nil )
} else {
d .Mach .Add1 (ss .AddressFocused , nil )
}
d .Mach .Add1 (ss .UpdateFocus , nil )
}
var keyNavigable = am .S {
ss .AddressFocused , ss .Toolbar1Focused ,
ss .Toolbar2Focused , ss .Toolbar3Focused ,
}
func (d *Debugger ) hNextTxKey () tcellKeyFn {
return func (ev *tcell .EventKey ) *tcell .EventKey {
if d .Mach .Is1 (ss .LogFocused ) {
d .Mach .Add1 (ss .LogUserScrolled , nil )
return ev
} else if d .Mach .Any1 (keyNavigable ...) {
return ev
}
if d .Mach .Not1 (ss .ClientSelected ) {
return nil
}
if d .hThrottleKey (ev , arrowThrottleMs ) {
return nil
}
if d .shouldScrollCurrView () {
return ev
}
state := ss .UserFwd
if d .Mach .Is1 (ss .TimelineStepsFocused ) {
state = ss .UserFwdStep
}
if !d .Mach .IsQueuedAbove (scrollTxThrottle , am .MutationAdd , S {state }, false ,
false , 0 ) {
d .Mach .Add1 (state , nil )
}
return nil
}
}
func (d *Debugger ) hPrevTxKey () tcellKeyFn {
return func (ev *tcell .EventKey ) *tcell .EventKey {
if d .Mach .Is1 (ss .LogFocused ) {
d .Mach .Add1 (ss .LogUserScrolled , nil )
return ev
} else if d .Mach .Any1 (keyNavigable ...) {
return ev
}
if d .Mach .Not1 (ss .ClientSelected ) {
return nil
}
if d .hThrottleKey (ev , arrowThrottleMs ) {
return nil
}
if d .shouldScrollCurrView () {
return ev
}
state := ss .UserBack
if d .Mach .Is1 (ss .TimelineStepsFocused ) {
state = ss .UserBackStep
}
if !d .Mach .IsQueuedAbove (scrollTxThrottle , am .MutationAdd , S {state }, false ,
false , 0 ) {
d .Mach .Add1 (state , nil )
}
return nil
}
}
func (d *Debugger ) hJumpBackKey (ev *tcell .EventKey ) *tcell .EventKey {
if d .Mach .Not1 (ss .ClientSelected ) {
return nil
}
if ev != nil && d .hThrottleKey (ev , arrowThrottleMs ) {
return nil
}
ctx := context .TODO ()
d .Mach .Remove (am .S {ss .Playing , ss .TailMode }, nil )
if d .Mach .Is1 (ss .StateNameSelected ) {
state := ss .ScrollToMutTx
amhelp .Add1Block (ctx , d .Mach , state , am .A {
"state" : d .C .SelectedState ,
"fwd" : false ,
})
d .hUpdateClientList ()
} else {
amhelp .Add1Block (ctx , d .Mach , ss .Back , am .A {
"amount" : min (fastJumpAmount , d .C .CursorTx1 ),
})
}
return nil
}
func (d *Debugger ) hJumpFwdKey (ev *tcell .EventKey ) *tcell .EventKey {
if d .Mach .Not1 (ss .ClientSelected ) {
return nil
}
if ev != nil && d .hThrottleKey (ev , arrowThrottleMs ) {
return nil
}
ctx := context .TODO ()
d .Mach .Remove (am .S {ss .Playing , ss .TailMode }, nil )
if d .Mach .Is1 (ss .StateNameSelected ) {
state := ss .ScrollToMutTx
amhelp .Add1Block (ctx , d .Mach , state , am .A {
"state" : d .C .SelectedState ,
"fwd" : true ,
})
d .hUpdateClientList ()
} else {
amhelp .Add1Block (ctx , d .Mach , ss .Fwd , am .A {
"amount" : min (fastJumpAmount , len (d .C .MsgTxs )-d .C .CursorTx1 ),
})
}
return nil
}
func (d *Debugger ) toolMatrix () {
is1 := d .Mach .Is1
not1 := d .Mach .Not1
switch {
case is1 (ss .TreeLogView ):
d .Mach .Add1 (ss .TreeMatrixView , nil )
case is1 (ss .TreeMatrixView ):
if not1 (ss .MatrixRain ) {
d .Mach .Add1 (ss .MatrixRain , nil )
} else {
d .Mach .Remove1 (ss .MatrixRain , nil )
d .Mach .Add1 (ss .MatrixView , nil )
}
case is1 (ss .MatrixView ):
if not1 (ss .MatrixRain ) {
d .Mach .Add1 (ss .MatrixRain , nil )
} else {
d .Mach .Add1 (ss .TreeLogView , nil )
}
}
}
func (d *Debugger ) hToolLastTx (e *am .Event ) {
if d .Mach .Not1 (ss .ClientSelected ) {
return
}
d .hSetCursor1 (e , am .A {
"cursor1" : len (d .C .MsgTxs ),
"filterBack" : true ,
})
d .Mach .Remove (am .S {ss .TailMode , ss .Playing }, nil )
d .hUpdateClientList ()
d .hRedrawFull (true )
}
func (d *Debugger ) hToolFirstTx (e *am .Event ) {
if d .Mach .Not1 (ss .ClientSelected ) {
return
}
d .hSetCursor1 (e , am .A {
"cursor1" : 0 ,
})
d .Mach .Remove (am .S {ss .TailMode , ss .Playing }, nil )
d .hUpdateClientList ()
d .hRedrawFull (true )
}
func (d *Debugger ) hToolExpand () {
if d .Mach .Is1 (ss .LogReaderFocused ) {
root := d .logReader .GetRoot ()
children := root .GetChildren ()
expanded := false
for _ , child := range children {
if child .IsExpanded () {
expanded = true
break
}
child .Collapse ()
}
d .C .ReaderCollapsed = expanded
return
}
expanded := false
children := d .tree .GetRoot ().GetChildren ()
for _ , child := range children {
if child .IsExpanded () {
expanded = true
break
}
child .Collapse ()
}
for _ , child := range children {
if expanded {
child .Collapse ()
child .GetReference ().(*nodeRef ).expanded = false
} else {
child .Expand ()
child .GetReference ().(*nodeRef ).expanded = true
}
}
}
func (d *Debugger ) shouldScrollCurrView () bool {
return d .Mach .Any1 (ss .MatrixFocused , ss .LogFocused )
}
func (d *Debugger ) hThrottleKey (ev *tcell .EventKey , ms int ) bool {
sameKey := d .lastKeystroke == ev .Key ()
elapsed := time .Since (d .lastKeystrokeTime )
if sameKey && elapsed < time .Duration (ms )*time .Millisecond {
return true
}
d .lastKeystroke = ev .Key ()
d .lastKeystrokeTime = time .Now ()
return false
}
func (d *Debugger ) hUpdateFocusable () {
var prims []cview .Primitive
if d .Mach .Is1 (ss .ExportDialog ) {
d .focusable = []*cview .Box {d .exportDialog .Box }
prims = []cview .Primitive {d .exportDialog }
d .focusManager .Reset ()
d .focusManager .Add (prims ...)
return
}
d .focusable = []*cview .Box {
d .addressBar .Box , d .clientList .Box , d .treeGroups .Box , d .tree .Box ,
}
prims = []cview .Primitive {
d .addressBar , d .clientList , d .treeGroups , d .tree ,
}
if d .Mach .Not1 (ss .ClientListVisible ) {
d .focusable = slices .Delete (d .focusable , 1 , 2 )
prims = slices .Delete (prims , 1 , 2 )
}
if d .Mach .Any1 (ss .MatrixView , ss .TreeMatrixView ) {
d .focusable = append (d .focusable , d .matrix .Box )
prims = append (prims , d .matrix )
} else if d .Opts .Filters .LogLevel != am .LogNothing {
d .focusable = append (d .focusable , d .log .Box )
prims = append (prims , d .log )
}
if d .Mach .Is1 (ss .LogReaderVisible ) {
d .focusable = append (d .focusable , d .logReader .Box )
prims = append (prims , d .logReader )
}
switch d .Opts .Timelines {
case 2 :
d .focusable = append (d .focusable , d .timelineTxs .Box , d .timelineSteps .Box )
prims = append (prims , d .timelineTxs , d .timelineSteps )
case 1 :
d .focusable = append (d .focusable , d .timelineTxs .Box )
prims = append (prims , d .timelineTxs )
}
d .focusable = append (d .focusable , d .toolbars [0 ].Box , d .toolbars [1 ].Box ,
d .toolbars [2 ].Box )
prims = append (prims , d .toolbars [0 ], d .toolbars [1 ], d .toolbars [2 ])
d .focusManager .Reset ()
d .focusManager .Add (prims ...)
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .