├── LICENSE ├── README.md ├── analytics.go ├── breakpoints_panel.go ├── console_panel.go ├── debug_screen.go ├── debugger.go ├── disassembly.go ├── fsnotify.go ├── func_panel.go ├── go.mod ├── go.sum ├── main.go ├── memory_panel.go ├── open_files_panel.go ├── packages_panel.go ├── path.go ├── proc.go ├── runtime.go ├── screenshots └── main.png ├── sidebar.go ├── source_panel.go ├── sources_panel.go ├── stack_panel.go ├── structures.go ├── toolbar.go ├── types_panel.go ├── ui ├── atoms.go ├── charts.go ├── clickable.go ├── colors.go ├── components.go ├── conditionals.go ├── directory_browser.go ├── form.go ├── layouts.go ├── lists.go ├── math.go ├── spacers.go ├── tabs.go ├── text.go ├── theme.go ├── toolbar.go ├── tooltip.go └── widgets.go └── utils.go /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 Emad Elsaid 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | DEBUGGER 2 | ======== 3 | 4 | Golang debugger graphical user interface. Built on Delve debugger. It aims to provide a similar user experience to Chrome developer tools. 5 | 6 | ![](screenshots/main.png) 7 | 8 | # Features 9 | 10 | - Source code view 11 | - Breakpoints (Add, remove, activate, deactivate) 12 | - Disassmbly panel 13 | - Watches 14 | - List of functions 15 | - List of types 16 | - List of Open files by the process 17 | - Show Process ID 18 | - Shows Process current working directory 19 | - Compiles go module executable or tests 20 | - Recompile and restart the process when changes are detected 21 | - Stack trace panel, listing all go routines 22 | - List of packages, and links to package documentation 23 | - List of source files 24 | - Memory statistics 25 | 26 | # Supported OSs 27 | 28 | - Linux: tested on Archlinux machine with go-1.19/amd64 29 | 30 | # Installation 31 | 32 | ## Prerequisites 33 | 34 | - Project depends on Gio package. make sure you install it's dependencies https://gioui.org/doc/install 35 | - Install `debugger` latest version using `go install` 36 | ``` 37 | go install github.com/emad-elsaid/debugger@latest 38 | ``` 39 | 40 | # Getting Started 41 | 42 | ## Debug binary 43 | 44 | ```shell 45 | debugger run -- 46 | ``` 47 | 48 | * arguments before `--` is passed to `go build` command. 49 | * arguments after `--` is passed to your program. 50 | 51 | Examples: 52 | 53 | Compile and debug the package in current directory 54 | ```shell 55 | debugger run . 56 | ``` 57 | Compile and debug package in `cmd/cli/` directory 58 | ```shell 59 | debugger run ./cmd/cli/ 60 | ``` 61 | Compile and debug current package and pass `--secure=false` to the program 62 | ```shell 63 | debugger run . -- --secure=false 64 | ``` 65 | 66 | ## Test binary 67 | 68 | ```shell 69 | debugger test -- 70 | ``` 71 | 72 | # Dependencies 73 | 74 | - fsnotify 75 | - Delve 76 | - Gioui 77 | 78 | # Contributing 79 | 80 | ## Code contributions 81 | 82 | - Fork 83 | - Branch 84 | - Add proof of concept for your feature to get the conversation started 85 | - Open Pull request with your changes 86 | - Discuss the idea and the POC 87 | - Continue until the PR is in a good shape for merging 88 | 89 | ## Feature requests 90 | 91 | - Open an issue with the idea state the following: 92 | - Problem statement 93 | - List of solutions 94 | - Preferred solution 95 | - Why this solution was choosen? 96 | 97 | # License 98 | 99 | This project is published under the MIT license 100 | -------------------------------------------------------------------------------- /analytics.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | ) 7 | 8 | const ANALYTICS_LIMIT = 1000 9 | 10 | type Analytics struct { 11 | OpenFiles []int 12 | Threads []uint 13 | Vsize []uint64 14 | ResidentMem []uint64 15 | SharedMem []uint64 16 | TextMem []uint64 17 | DataMem []uint64 18 | PID int 19 | } 20 | 21 | func NewAnalytics() Analytics { 22 | return Analytics{ 23 | OpenFiles: make([]int, 0, ANALYTICS_LIMIT), 24 | Threads: make([]uint, 0, ANALYTICS_LIMIT), 25 | Vsize: make([]uint64, 0, ANALYTICS_LIMIT), 26 | ResidentMem: make([]uint64, 0, ANALYTICS_LIMIT), 27 | SharedMem: make([]uint64, 0, ANALYTICS_LIMIT), 28 | TextMem: make([]uint64, 0, ANALYTICS_LIMIT), 29 | DataMem: make([]uint64, 0, ANALYTICS_LIMIT), 30 | } 31 | } 32 | 33 | func (a *Analytics) Collect(pid int) { 34 | if pid != a.PID { 35 | a.PID = pid 36 | a.Clear() 37 | } 38 | a.openFiles(pid) 39 | a.stat(pid) 40 | a.statm(pid) 41 | } 42 | 43 | func (a *Analytics) Clear() { 44 | a.OpenFiles = make([]int, 0, ANALYTICS_LIMIT) 45 | a.Threads = make([]uint, 0, ANALYTICS_LIMIT) 46 | a.Vsize = make([]uint64, 0, ANALYTICS_LIMIT) 47 | a.ResidentMem = make([]uint64, 0, ANALYTICS_LIMIT) 48 | a.SharedMem = make([]uint64, 0, ANALYTICS_LIMIT) 49 | a.TextMem = make([]uint64, 0, ANALYTICS_LIMIT) 50 | a.DataMem = make([]uint64, 0, ANALYTICS_LIMIT) 51 | } 52 | 53 | func (a *Analytics) openFiles(pid int) { 54 | f, err := os.Open(fmt.Sprintf("/proc/%d/fd", pid)) 55 | if err != nil { 56 | return 57 | } 58 | defer f.Close() 59 | 60 | dirs, _ := f.ReadDir(-1) 61 | limitedAppend(&a.OpenFiles, len(dirs)) 62 | } 63 | 64 | // https://man.archlinux.org/man/proc.5.en 65 | func (a *Analytics) stat(pid int) { 66 | s, err := os.ReadFile(fmt.Sprintf("/proc/%d/stat", pid)) 67 | if err != nil { 68 | return 69 | } 70 | 71 | var ( 72 | comm string 73 | state rune 74 | ppid, pgrp, session, tty_nr, tpgid int 75 | flags uint 76 | minflt, cminflt, majflt, cmajflt, utime, stime uint64 77 | cutime, cstime, priority, nice, num_threads, itrealvalue uint 78 | starttime, vsize, rss uint64 79 | rsslim, startcode, endcode, startstack, kstkesp uint64 80 | kstkeip, signal, blocked, sigignore, sigcatch uint64 81 | wchan, nswap, cnswap uint64 82 | exit_signal, processor int 83 | rt_priority, policy uint 84 | delayacct_blkio_ticks, guest_time, cguest_time uint64 85 | start_data, end_data, start_brk, arg_start uint64 86 | arg_end, env_start, env_end uint64 87 | exit_code int 88 | ) 89 | 90 | fmt.Sscanf( 91 | string(s), 92 | ""+ 93 | "%d %s %c %d %d "+ 94 | "%d %d %d %d %d "+ 95 | "%d %d %d %d %d "+ 96 | "%d %d %d %d %d "+ 97 | "%d %d %d %d %d "+ 98 | "%d %d %d %d %d "+ 99 | "%d %d %d %d %d "+ 100 | "%d %d %d %d %d "+ 101 | "%d %d %d %d %d "+ 102 | "%d %d %d %d %d "+ 103 | "%d %d", 104 | &pid, &comm, &state, &ppid, &pgrp, 105 | &session, &tty_nr, &tpgid, &flags, &minflt, 106 | &cminflt, &majflt, &cmajflt, &utime, &stime, 107 | &cutime, &cstime, &priority, &nice, &num_threads, 108 | &itrealvalue, &starttime, &vsize, &rss, &rsslim, 109 | &startcode, &endcode, &startstack, &kstkesp, &kstkeip, 110 | &signal, &blocked, &sigignore, &sigcatch, &wchan, 111 | &nswap, &cnswap, &exit_signal, &processor, &rt_priority, 112 | &policy, &delayacct_blkio_ticks, &guest_time, &cguest_time, &start_data, 113 | &end_data, &start_brk, &arg_start, &arg_end, &env_start, 114 | &env_end, &exit_code, 115 | ) 116 | 117 | limitedAppend(&a.Threads, num_threads) 118 | limitedAppend(&a.Vsize, vsize) 119 | } 120 | 121 | func (a *Analytics) statm(pid int) { 122 | s, err := os.ReadFile(fmt.Sprintf("/proc/%d/statm", pid)) 123 | if err != nil { 124 | return 125 | } 126 | 127 | var size, resident, shared, text, lib, data, dt uint64 128 | fmt.Sscanf( 129 | string(s), 130 | "%d %d %d %d %d %d %d", 131 | &size, &resident, &shared, &text, &lib, &data, &dt, 132 | ) 133 | 134 | limitedAppend(&a.ResidentMem, resident) 135 | limitedAppend(&a.SharedMem, shared) 136 | limitedAppend(&a.TextMem, text) 137 | limitedAppend(&a.DataMem, data) 138 | } 139 | -------------------------------------------------------------------------------- /breakpoints_panel.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | 7 | . "github.com/emad-elsaid/debugger/ui" 8 | "golang.org/x/exp/shiny/materialdesign/icons" 9 | ) 10 | 11 | var ( 12 | BreakPointColor = DangerColor 13 | BreakPointDisabledColor = MixColor(BreakPointColor, WHITE, 50) 14 | 15 | IconBreakPoint = Icon(icons.AVFiberManualRecord, BreakPointColor) 16 | IconBreakPointDisabled = Icon(icons.AVFiberManualRecord, BreakPointDisabledColor) 17 | IconDelete = Icon(icons.ContentClear, DangerColor) 18 | ) 19 | 20 | type BreakpointsPanel struct { 21 | BreakpointsList ClickableList 22 | EnableAll Clickable 23 | DisableAll Clickable 24 | ClearAll Clickable 25 | Clickables Clickables 26 | } 27 | 28 | func NewBreakpointsPanel() BreakpointsPanel { 29 | return BreakpointsPanel{ 30 | BreakpointsList: NewClickableList(), 31 | Clickables: NewClickables(), 32 | } 33 | } 34 | 35 | func (b *BreakpointsPanel) Layout(d *Debugger) W { 36 | return Rows( 37 | Rigid(b.Toolbar(d)), 38 | Flexed(1, b.BreakpointsPanel(d)), 39 | ) 40 | } 41 | 42 | func (b *BreakpointsPanel) BreakpointsPanel(d *Debugger) W { 43 | breakpoints := d.Breakpoints() 44 | 45 | sort.Slice(breakpoints, func(i, j int) bool { 46 | return breakpoints[i].ID < breakpoints[j].ID 47 | }) 48 | 49 | IconFont := FontEnlarge(2) 50 | 51 | elem := func(c C, i int) D { 52 | r := breakpoints[i] 53 | id := fmt.Sprintf("breakpoint-%d", r.ID) 54 | delId := fmt.Sprintf("breakpoint-del-%d", r.ID) 55 | 56 | click := func() { d.ToggleBreakpoint(r) } 57 | delClick := func() { d.ClearBreakpoint(r) } 58 | 59 | return Columns( 60 | Rigid(IconFont(Checkbox(b.Clickables.Get(id), !r.Disabled, click))), 61 | Flexed(1, 62 | Rows( 63 | Rigid(Label(r.Name)), 64 | Rigid(Wrap(Text(fmt.Sprintf("%s:%d", r.File, r.Line)), TextColor(SecondaryTextColor), MaxLines(3))), 65 | ), 66 | ), 67 | Rigid(OnClick(b.Clickables.Get(delId), IconFont(IconDelete), delClick)), 68 | )(c) 69 | } 70 | 71 | click := func(i int) { 72 | bp := breakpoints[i] 73 | d.SetFileLine(bp.File, bp.Line) 74 | } 75 | 76 | return b.BreakpointsList.Layout(len(breakpoints), elem, click) 77 | } 78 | 79 | func (b *BreakpointsPanel) Toolbar(d *Debugger) W { 80 | if b.EnableAll.Clicked() { 81 | d.EnableAllBreakpoints() 82 | } 83 | 84 | if b.DisableAll.Clicked() { 85 | d.DisableAllBreakpoints() 86 | } 87 | 88 | if b.ClearAll.Clicked() { 89 | d.ClearAllBreakpoints() 90 | } 91 | 92 | IconFont := FontEnlarge(2) 93 | 94 | return Background(ToolbarBgColor, 95 | Columns( 96 | Rigid(ToolbarButton(&b.EnableAll, IconFont(IconBreakPoint), "Enable All")), 97 | Rigid(ToolbarButton(&b.DisableAll, IconFont(IconBreakPointDisabled), "Disable All")), 98 | Rigid(ToolbarButton(&b.ClearAll, IconFont(IconDelete), "Clear All")), 99 | ), 100 | ) 101 | } 102 | -------------------------------------------------------------------------------- /console_panel.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "gioui.org/widget" 8 | . "github.com/emad-elsaid/debugger/ui" 9 | ) 10 | 11 | type LogKind int 12 | 13 | const ( 14 | LogInfo LogKind = iota 15 | LogError 16 | LogCmd 17 | ) 18 | 19 | func (k LogKind) String() string { 20 | switch k { 21 | case LogInfo: 22 | return "INFO" 23 | case LogError: 24 | return "ERR" 25 | case LogCmd: 26 | return "CMD" 27 | default: 28 | return "UNK" 29 | } 30 | } 31 | 32 | type Log struct { 33 | Kind LogKind 34 | Log string 35 | } 36 | 37 | func (log *Log) Layout(c C) D { 38 | fg := Theme.TextColor 39 | bg := BackgroundColor 40 | 41 | switch log.Kind { 42 | case LogInfo: 43 | bg = BLUEBERRY_100 44 | fg = BLUEBERRY_700 45 | case LogError: 46 | bg = STRAWBERRY_100 47 | fg = STRAWBERRY_700 48 | } 49 | 50 | return Columns( 51 | Rigid( 52 | Background(bg, 53 | Inset05( 54 | AlignMiddle( 55 | TextColor(fg)( 56 | Label(log.Kind.String()), 57 | ), 58 | ), 59 | ), 60 | ), 61 | ), 62 | ColSpacer1, 63 | Flexed(1, Label(log.Log)), 64 | )(c) 65 | } 66 | 67 | type ConsolePanel struct { 68 | Editor widget.Editor 69 | List List 70 | Logs []W 71 | } 72 | 73 | func NewConsolePanel() ConsolePanel { 74 | return ConsolePanel{ 75 | Editor: LineEditor(), 76 | List: NewVerticalList(), 77 | Logs: []W{}, 78 | } 79 | } 80 | 81 | func (cp *ConsolePanel) Layout(d *Debugger) W { 82 | cp.List.ScrollToEnd = true 83 | cp.HandleEvent(d) 84 | 85 | logsPanel := ZebraList(&cp.List, len(cp.Logs), func(c C, i int) D { 86 | return Inset05(cp.Logs[i])(c) 87 | }) 88 | 89 | return Rows( 90 | Flexed(1, logsPanel), 91 | Rigid(TextInput(&cp.Editor, "Write commands here")), 92 | ) 93 | } 94 | 95 | func (cp *ConsolePanel) HandleEvent(d *Debugger) { 96 | evts := cp.Editor.Events() 97 | for _, evt := range evts { 98 | switch v := evt.(type) { 99 | case widget.SubmitEvent: 100 | text := v.Text 101 | if len(text) == 0 { 102 | continue 103 | } 104 | cp.Run(d, text) 105 | cp.Editor.SetText("") 106 | } 107 | } 108 | } 109 | 110 | func (cp *ConsolePanel) Run(d *Debugger, expr string) { 111 | cp.Log(LogCmd, expr) 112 | 113 | v, err := d.ExecuteExpr(expr) 114 | if err != nil { 115 | log.Printf("Executing command: %s resulted in error\n%s", expr, err.Error()) 116 | return 117 | } 118 | 119 | widget := NewVarWidget(v) 120 | cp.Logs = append(cp.Logs, widget.Layout) 121 | } 122 | 123 | func (cp *ConsolePanel) Log(kind LogKind, log string, params ...interface{}) { 124 | l := Log{kind, fmt.Sprintf(log, params...)} 125 | cp.Logs = append(cp.Logs, l.Layout) 126 | } 127 | -------------------------------------------------------------------------------- /debug_screen.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "time" 6 | 7 | . "github.com/emad-elsaid/debugger/ui" 8 | "github.com/fsnotify/fsnotify" 9 | ) 10 | 11 | const STACK_LIMIT = 100000 12 | 13 | var ( 14 | ErrCantInspectTargetProcess = errors.New("Can't inspect target process") 15 | ErrProcessExited = errors.New("Process exited") 16 | ErrNoSelectedGoRoutine = errors.New("Can't find any go routines") 17 | ErrCantGetScope = errors.New("Can't get current scope") 18 | ) 19 | 20 | type DebugScreen struct { 21 | Watch *fsnotify.Watcher 22 | Analytics Analytics 23 | Debugger *Debugger 24 | BottomTabs Tabs 25 | Toolbar Toolbar 26 | SideBar SideBar 27 | FunctionsPanel FunctionsPanel 28 | PackagesPanel PackagesPanel 29 | SourcesPanel SourcesPanel 30 | StackPanel StackPanel 31 | TypesPanel TypesPanel 32 | ConsolePanel ConsolePanel 33 | BreakpointsPanel BreakpointsPanel 34 | DisassemblyPanel DisassemblyPanel 35 | SourcePanel SourcePanel 36 | OpenFilesPanel OpenFilesPanel 37 | MemoryPanel MemoryPanel 38 | } 39 | 40 | func (d *DebugScreen) Layout(c C) D { 41 | debugger := d.Debugger 42 | 43 | if debugger.File == "" { 44 | debugger.JumpToFunction("main.main") 45 | } 46 | 47 | return Rows( 48 | Rigid(d.Toolbar.Layout(debugger)), 49 | Rigid(HR(1)), 50 | Flexed(2, 51 | Columns( 52 | Flexed(4, d.SourcePanel.Layout(debugger)), 53 | Flexed(1, d.SideBar.Layout(debugger)), 54 | ), 55 | ), 56 | Flexed(1, 57 | d.BottomTabs.Layout( 58 | &TabChild{Name: "Console", Panel: func(c C) D { return d.ConsolePanel.Layout(debugger)(c) }}, 59 | &TabChild{Name: "Stack Trace", Panel: func(c C) D { return d.StackPanel.Layout(debugger)(c) }}, 60 | &TabChild{Name: "Types", Panel: func(c C) D { return d.TypesPanel.Layout(debugger)(c) }}, 61 | &TabChild{Name: "Functions", Panel: func(c C) D { return d.FunctionsPanel.Layout(debugger)(c) }}, 62 | &TabChild{Name: "Packages", Panel: func(c C) D { return d.PackagesPanel.Layout(debugger)(c) }}, 63 | &TabChild{Name: "Sources", Panel: func(c C) D { return d.SourcesPanel.Layout(debugger)(c) }}, 64 | &TabChild{Name: "Disassembly", Panel: func(c C) D { return d.DisassemblyPanel.Layout(debugger)(c) }}, 65 | &TabChild{Name: "Breakpoints", Panel: func(c C) D { return d.BreakpointsPanel.Layout(debugger)(c) }}, 66 | &TabChild{Name: "Open Files", Panel: func(c C) D { return d.OpenFilesPanel.Layout(debugger, &d.Analytics)(c) }}, 67 | &TabChild{Name: "Memory", Panel: func(c C) D { return d.MemoryPanel.Layout(&d.Analytics)(c) }}, 68 | ), 69 | ), 70 | )(c) 71 | } 72 | 73 | func NewDebugScreen(debugger *Debugger) (*DebugScreen, error) { 74 | w := &DebugScreen{ 75 | Debugger: debugger, 76 | Analytics: NewAnalytics(), 77 | BottomTabs: Tabs{}, 78 | Toolbar: Toolbar{}, 79 | SideBar: NewSideBar(), 80 | FunctionsPanel: NewFunctionsPanel(), 81 | PackagesPanel: NewPackagesPanel(), 82 | SourcesPanel: NewSourcesPanel(), 83 | StackPanel: NewStackPanel(), 84 | TypesPanel: NewTypesPanel(), 85 | ConsolePanel: NewConsolePanel(), 86 | BreakpointsPanel: NewBreakpointsPanel(), 87 | DisassemblyPanel: NewDisassemblyPanel(), 88 | SourcePanel: NewSourcePanel(), 89 | OpenFilesPanel: NewOpenFilesPanel(), 90 | MemoryPanel: NewMemoryPanel(), 91 | } 92 | 93 | go w.Clock() 94 | go w.StartWatch() 95 | 96 | return w, nil 97 | } 98 | 99 | func (d *DebugScreen) Log(kind LogKind, log string, params ...interface{}) { 100 | d.ConsolePanel.Log(kind, log, params...) 101 | } 102 | 103 | func (d *DebugScreen) StopWatch() { 104 | if d.Watch != nil { 105 | d.Watch.Close() 106 | d.Watch = nil 107 | } 108 | } 109 | 110 | func (d *DebugScreen) StartWatch() { 111 | debugger := d.Debugger 112 | 113 | watcher, err := fsnotify.NewWatcher() 114 | if err != nil { 115 | d.Log(LogError, "Can't create watch. Error: %s", err) 116 | return 117 | } 118 | defer watcher.Close() 119 | 120 | d.Watch = watcher 121 | 122 | done := make(chan bool) 123 | go func() { 124 | for { 125 | select { 126 | case event, ok := <-watcher.Events: 127 | if !ok { 128 | return 129 | } 130 | 131 | isWrite := event.Op == fsnotify.Write 132 | isRename := event.Op == fsnotify.Rename 133 | isRemove := event.Op == fsnotify.Remove 134 | 135 | supportedOp := isWrite || isRename || isRemove 136 | 137 | if supportedOp && event.Name != d.Debugger.BinName { 138 | d.Log(LogInfo, "%s file: %s", event.Op, event.Name) 139 | debugger.Restart() 140 | } 141 | case err, ok := <-watcher.Errors: 142 | if !ok { 143 | return 144 | } 145 | d.Log(LogError, "Watch error: %s", err) 146 | } 147 | } 148 | }() 149 | 150 | if err := WatchAddRecursive(watcher, debugger.Path); err != nil { 151 | d.Log(LogError, "Can't watch: %s Error: %s", debugger.Path, err) 152 | } 153 | 154 | <-done 155 | } 156 | 157 | func (d *DebugScreen) Clock() { 158 | for { 159 | if d.Debugger.IsRunning() { 160 | d.Analytics.Collect(d.Debugger.Target().Pid()) 161 | } 162 | time.Sleep(time.Millisecond * 10) 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /debugger.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "reflect" 7 | "strings" 8 | "sync" 9 | "unsafe" 10 | 11 | "gioui.org/io/system" 12 | . "github.com/emad-elsaid/types" 13 | "github.com/go-delve/delve/pkg/gobuild" 14 | "github.com/go-delve/delve/pkg/proc" 15 | "github.com/go-delve/delve/service/api" 16 | "github.com/go-delve/delve/service/debugger" 17 | ) 18 | 19 | type DebuggerState uint64 20 | 21 | type Debugger struct { 22 | *debugger.Debugger 23 | State DebuggerState 24 | LastState *api.DebuggerState 25 | 26 | // Project props 27 | Path string 28 | BinName string 29 | Args []string 30 | Test bool 31 | 32 | // Run state 33 | StackFrame int 34 | File string 35 | Line int 36 | 37 | // Cached items 38 | breakpoints []*api.Breakpoint 39 | 40 | // Watches 41 | WatchesExpr []string 42 | } 43 | 44 | func NewDebugger(bin string, args []string, runImmediately bool, test bool) (*Debugger, error) { 45 | wd, _ := os.Getwd() 46 | 47 | d := Debugger{ 48 | Path: wd, 49 | BinName: bin, 50 | Args: args, 51 | breakpoints: []*api.Breakpoint{}, 52 | Test: test, 53 | } 54 | 55 | if err := d.InitDebugger(); err != nil { 56 | return nil, err 57 | } 58 | 59 | d.Continue() 60 | 61 | return &d, nil 62 | } 63 | 64 | func (d *Debugger) TryLockTarget() bool { 65 | field := reflect.ValueOf(d.Debugger).Elem().FieldByName("targetMutex") 66 | mtx := reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Interface() 67 | return mtx.(*sync.Mutex).TryLock() 68 | } 69 | 70 | func (d *Debugger) compileArgs() []string { 71 | var a Slice[string] = d.Args 72 | sep := a.Index("--") 73 | 74 | if sep == -1 { 75 | return d.Args 76 | } 77 | 78 | if sep == 0 { 79 | return []string{} 80 | } 81 | 82 | return d.Args[:sep] 83 | } 84 | 85 | func (d *Debugger) runArgs() []string { 86 | var a Slice[string] = d.Args 87 | sep := a.Index("--") 88 | 89 | if sep == -1 { 90 | return []string{} 91 | } 92 | 93 | return d.Args[sep+1:] 94 | } 95 | 96 | func (d *Debugger) InitDebugger() error { 97 | config := d.DebugConfig() 98 | 99 | if err := d.Compile(); err != nil { 100 | return err 101 | } 102 | 103 | deb, err := debugger.New(config, append([]string{d.BinName}, d.runArgs()...)) 104 | if err != nil { 105 | return err 106 | } 107 | 108 | d.Debugger = deb 109 | 110 | return nil 111 | } 112 | 113 | func (d *Debugger) DebugConfig() *debugger.Config { 114 | executeKind := debugger.ExecutingGeneratedFile 115 | 116 | if d.Test { 117 | executeKind = debugger.ExecutingGeneratedTest 118 | } 119 | 120 | return &debugger.Config{ 121 | WorkingDir: d.Path, 122 | Backend: "default", 123 | Foreground: true, 124 | ExecuteKind: executeKind, 125 | Packages: []string{}, 126 | BuildFlags: strings.Join(d.compileArgs(), " "), 127 | } 128 | } 129 | 130 | func (d *Debugger) Stacktrace() ([]proc.Stackframe, error) { 131 | if d.LastState != nil && d.LastState.Exited { 132 | return nil, ErrProcessExited 133 | } 134 | 135 | g, err := d.SelectedGoRoutine() 136 | if err != nil { 137 | return nil, err 138 | } 139 | 140 | if !d.TryLockTarget() { 141 | return nil, ErrCantInspectTargetProcess 142 | } 143 | defer d.UnlockTarget() 144 | 145 | return g.Stacktrace(STACK_LIMIT, proc.StacktraceSimple) 146 | } 147 | 148 | func (d *Debugger) CreateBreakpoint(bp *api.Breakpoint) (*api.Breakpoint, error) { 149 | if d.IsRunning() { 150 | d.Stop() 151 | defer d.Continue() 152 | } 153 | 154 | b, err := d.Debugger.CreateBreakpoint(bp, "", nil, false) 155 | d.cacheBreakpoints() 156 | return b, err 157 | } 158 | 159 | func (d *Debugger) AmendBreakpoint(bp *api.Breakpoint) error { 160 | if d.IsRunning() { 161 | d.Stop() 162 | defer d.Continue() 163 | } 164 | 165 | err := d.Debugger.AmendBreakpoint(bp) 166 | d.cacheBreakpoints() 167 | return err 168 | } 169 | 170 | func (d *Debugger) ClearBreakpoint(bp *api.Breakpoint) (*api.Breakpoint, error) { 171 | if d.IsRunning() { 172 | d.Stop() 173 | defer d.Continue() 174 | } 175 | 176 | b, err := d.Debugger.ClearBreakpoint(bp) 177 | d.cacheBreakpoints() 178 | return b, err 179 | } 180 | 181 | func (d *Debugger) ClearAllBreakpoints() error { 182 | if d.IsRunning() { 183 | d.Stop() 184 | defer d.Continue() 185 | } 186 | 187 | for _, bp := range d.Breakpoints() { 188 | if _, err := d.Debugger.ClearBreakpoint(bp); err != nil { 189 | return err 190 | } 191 | } 192 | 193 | d.cacheBreakpoints() 194 | 195 | return nil 196 | } 197 | 198 | func (d *Debugger) ToggleBreakpoint(bp *api.Breakpoint) error { 199 | bp.Disabled = !bp.Disabled 200 | return d.AmendBreakpoint(bp) 201 | } 202 | 203 | func (d *Debugger) EnableAllBreakpoints() error { 204 | if d.IsRunning() { 205 | d.Stop() 206 | defer d.Continue() 207 | } 208 | 209 | for _, bp := range d.Breakpoints() { 210 | if !bp.Disabled { 211 | continue 212 | } 213 | bp.Disabled = false 214 | if err := d.Debugger.AmendBreakpoint(bp); err != nil { 215 | return err 216 | } 217 | } 218 | 219 | return nil 220 | } 221 | 222 | func (d *Debugger) DisableAllBreakpoints() error { 223 | if d.IsRunning() { 224 | d.Stop() 225 | defer d.Continue() 226 | } 227 | 228 | for _, bp := range d.Breakpoints() { 229 | if bp.Disabled { 230 | continue 231 | } 232 | bp.Disabled = true 233 | if err := d.Debugger.AmendBreakpoint(bp); err != nil { 234 | return err 235 | } 236 | } 237 | 238 | return nil 239 | } 240 | 241 | func (d *Debugger) ExecuteExpr(expr string) (*proc.Variable, error) { 242 | if !d.TryLockTarget() { 243 | return nil, ErrCantInspectTargetProcess 244 | } 245 | defer d.UnlockTarget() 246 | 247 | scope, err := proc.ConvertEvalScope(d.Target(), -1, d.StackFrame, 0) 248 | if err != nil { 249 | return nil, ErrCantGetScope 250 | } 251 | 252 | v, err := scope.EvalExpression(expr, ProcLoadConfig) 253 | if err != nil { 254 | return nil, err 255 | } 256 | 257 | return v, nil 258 | } 259 | 260 | func (d *Debugger) SetFileLine(file string, line int) { 261 | d.File = file 262 | d.Line = line 263 | d.InvalidateState() 264 | } 265 | 266 | func (d *Debugger) InvalidateState() { 267 | d.State++ 268 | } 269 | 270 | func (d *Debugger) Command(cmd *api.DebuggerCommand) { 271 | state, _ := d.Debugger.Command(cmd, nil) 272 | if state == nil { 273 | return 274 | } 275 | 276 | d.LastState = state 277 | d.InvalidateState() 278 | 279 | if state.NextInProgress { 280 | if err := d.CancelNext(); err != nil { 281 | log.Printf("Error cancelling next: %s", err) 282 | } 283 | } 284 | 285 | for _, thread := range state.Threads { 286 | if thread.Breakpoint != nil { 287 | file := thread.Breakpoint.File 288 | line := thread.Breakpoint.Line 289 | d.SetFileLine(file, line) 290 | win.Perform(system.ActionRaise) 291 | break 292 | } 293 | } 294 | 295 | if cmd.Name == api.Step || 296 | cmd.Name == api.StepOut || 297 | cmd.Name == api.Next { 298 | g, err := d.SelectedGoRoutine() 299 | if err != nil { 300 | return 301 | } 302 | 303 | file := g.CurrentLoc.File 304 | line := g.CurrentLoc.Line 305 | d.SetFileLine(file, line) 306 | win.Perform(system.ActionRaise) 307 | } 308 | 309 | // If the process exited load it again into the debugger. 310 | // this allow the user to manipulate the breakpoints after exiting the program 311 | if state.Exited { 312 | d.Debugger.Restart(false, "", false, []string{}, [3]string{}, false) 313 | } 314 | } 315 | 316 | func (d *Debugger) SelectedGoRoutine() (*proc.G, error) { 317 | if !d.TryLockTarget() { 318 | return nil, ErrCantInspectTargetProcess 319 | } 320 | defer d.UnlockTarget() 321 | 322 | t := d.Target() 323 | g := t.SelectedGoroutine() 324 | if g == nil { 325 | routines, _, _ := proc.GoroutinesInfo(t, 0, 0) 326 | for _, g = range routines { 327 | break 328 | } 329 | } 330 | 331 | if g == nil { 332 | return nil, ErrNoSelectedGoRoutine 333 | } 334 | 335 | return g, nil 336 | } 337 | 338 | func (d *Debugger) GoRoutines() ([]*proc.G, error) { 339 | if !d.TryLockTarget() { 340 | return nil, ErrCantInspectTargetProcess 341 | } 342 | defer d.UnlockTarget() 343 | 344 | t := d.Target() 345 | var routines Slice[*proc.G] 346 | routines, _, err := proc.GoroutinesInfo(t, 0, 0) 347 | if err != nil { 348 | return nil, err 349 | } 350 | 351 | routines = routines.Select(func(g *proc.G) bool { return !g.System(t) }) 352 | 353 | return routines, nil 354 | } 355 | 356 | func (d *Debugger) Continue() { 357 | if d.IsRunning() { 358 | return 359 | } 360 | 361 | go d.Command(&api.DebuggerCommand{Name: api.Continue}) 362 | } 363 | 364 | func (d *Debugger) Stop() { 365 | d.Command(&api.DebuggerCommand{Name: api.Halt}) 366 | } 367 | 368 | func (d *Debugger) Restart() { 369 | d.Stop() 370 | 371 | _, err := d.Debugger.Restart(false, "", false, []string{}, [3]string{}, true) 372 | if err != nil { 373 | log.Printf("Error restarting: %s", err) 374 | return 375 | } 376 | 377 | go d.Continue() 378 | } 379 | 380 | func (d *Debugger) StepOut() { 381 | if d.IsRunning() { 382 | return 383 | } 384 | 385 | go d.Command(&api.DebuggerCommand{Name: api.StepOut}) 386 | } 387 | 388 | func (d *Debugger) Next() { 389 | if d.IsRunning() { 390 | return 391 | } 392 | go d.Command(&api.DebuggerCommand{Name: api.Next}) 393 | } 394 | 395 | func (d *Debugger) Step() { 396 | if d.IsRunning() { 397 | return 398 | } 399 | 400 | go d.Command(&api.DebuggerCommand{Name: api.Step}) 401 | } 402 | 403 | func (d *Debugger) JumpToFunction(name string) { 404 | bi := d.Target().BinInfo() 405 | pc, err := proc.FindFunctionLocation(d.Target().Process, name, 0) 406 | if err != nil { 407 | return 408 | } 409 | 410 | for _, v := range pc { 411 | file, line, _ := bi.PCToLine(v) 412 | if file != "" { 413 | d.SetFileLine(file, line) 414 | return 415 | } 416 | } 417 | } 418 | 419 | func (d *Debugger) Compile() error { 420 | config := d.DebugConfig() 421 | 422 | if _, err := os.Stat(d.BinName); err == nil { 423 | gobuild.Remove(d.BinName) 424 | } 425 | 426 | var err error 427 | if d.Test { 428 | err = gobuild.GoTestBuild(d.BinName, config.Packages, config.BuildFlags) 429 | } else { 430 | err = gobuild.GoBuild(d.BinName, config.Packages, config.BuildFlags) 431 | } 432 | 433 | if err != nil { 434 | return err 435 | } 436 | 437 | return nil 438 | } 439 | 440 | func (d *Debugger) Breakpoints() []*api.Breakpoint { 441 | if !d.TryLockTarget() { 442 | return d.breakpoints 443 | } 444 | defer d.UnlockTarget() 445 | 446 | d.cacheBreakpoints() 447 | return d.breakpoints 448 | } 449 | 450 | func (d *Debugger) cacheBreakpoints() { 451 | abps := []*api.Breakpoint{} 452 | for _, lbp := range d.Target().Breakpoints().Logical { 453 | abp := api.ConvertLogicalBreakpoint(lbp) 454 | pids, bp := d.findBreakpoint(lbp.LogicalID) 455 | api.ConvertPhysicalBreakpoints(abp, pids, bp) 456 | abps = append(abps, abp) 457 | } 458 | 459 | d.breakpoints = abps 460 | 461 | } 462 | 463 | func (d *Debugger) findBreakpoint(id int) ([]int, []*proc.Breakpoint) { 464 | var bps []*proc.Breakpoint 465 | var pids []int 466 | for _, bp := range d.Target().Breakpoints().M { 467 | if bp.LogicalID() == id { 468 | pids = append(pids, bp.LogicalID()) 469 | bps = append(bps, bp) 470 | } 471 | } 472 | return pids, bps 473 | } 474 | 475 | func (d *Debugger) FunctionArguments() ([]*proc.Variable, error) { 476 | s, err := proc.ConvertEvalScope(d.Target(), -1, d.StackFrame, 0) 477 | if err != nil { 478 | return nil, err 479 | } 480 | 481 | return s.FunctionArguments(ProcLoadConfig) 482 | } 483 | 484 | func (d *Debugger) LocalVariables() ([]*proc.Variable, error) { 485 | s, err := proc.ConvertEvalScope(d.Target(), -1, d.StackFrame, 0) 486 | if err != nil { 487 | return nil, err 488 | } 489 | 490 | return s.LocalVariables(ProcLoadConfig) 491 | } 492 | 493 | func (d *Debugger) PackageVariables() ([]*proc.Variable, error) { 494 | s, err := proc.ConvertEvalScope(d.Target(), -1, d.StackFrame, 0) 495 | if err != nil { 496 | return nil, err 497 | } 498 | 499 | return s.PackageVariables(ProcLoadConfig) 500 | } 501 | 502 | func (d *Debugger) RunningLines() []int { 503 | lines := []int{} 504 | 505 | if !d.TryLockTarget() { 506 | return lines 507 | } 508 | defer d.UnlockTarget() 509 | 510 | if ok, _ := d.Target().Valid(); ok { 511 | threads := d.Target().ThreadList() 512 | for _, thread := range threads { 513 | loc, err := thread.Location() 514 | if err == nil && loc.File == d.File { 515 | lines = append(lines, loc.Line) 516 | } 517 | } 518 | } 519 | 520 | return lines 521 | } 522 | 523 | func (d *Debugger) CreateWatch(expr string) { 524 | d.WatchesExpr = append(d.WatchesExpr, expr) 525 | } 526 | 527 | func (d *Debugger) DeleteWatch(expr string) { 528 | nWatches := []string{} 529 | for _, v := range d.WatchesExpr { 530 | if v != expr { 531 | nWatches = append(nWatches, v) 532 | } 533 | } 534 | d.WatchesExpr = nWatches 535 | } 536 | -------------------------------------------------------------------------------- /disassembly.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | . "github.com/emad-elsaid/debugger/ui" 7 | "github.com/go-delve/delve/pkg/proc" 8 | ) 9 | 10 | type DisassemblyPanel struct { 11 | AssemblyList ClickableList 12 | } 13 | 14 | func NewDisassemblyPanel() DisassemblyPanel { 15 | return DisassemblyPanel{ 16 | AssemblyList: NewClickableList(), 17 | } 18 | } 19 | 20 | func (s *DisassemblyPanel) Layout(d *Debugger) W { 21 | g, err := d.SelectedGoRoutine() 22 | if err != nil { 23 | return Centered(Label(err.Error())) 24 | } 25 | 26 | fn := g.CurrentLoc.Fn 27 | asm, err := d.Disassemble(g.ID, fn.Entry, fn.End) 28 | if err != nil { 29 | return Centered(Label(err.Error())) 30 | } 31 | 32 | ele := func(c C, i int) D { 33 | l := asm[i] 34 | w := Inset1(Label(l.Text(proc.GoFlavour, d.Target().BinInfo()))) 35 | if i == 0 || l.Loc.Line != asm[i-1].Loc.Line { 36 | w = Rows( 37 | Rigid( 38 | Inset1( 39 | Bold( 40 | Label(fmt.Sprintf("%s:%d", l.Loc.File, l.Loc.Line)), 41 | ), 42 | ), 43 | ), 44 | Rigid(w), 45 | ) 46 | } 47 | return w(c) 48 | } 49 | 50 | click := func(i int) { 51 | l := asm[i] 52 | loc := l.Loc 53 | d.SetFileLine(loc.File, loc.Line) 54 | } 55 | 56 | return Rows( 57 | Rigid( 58 | Bold( 59 | Label(fmt.Sprintf("Function: %s PC:0x%x:0x%x", fn.Name, fn.Entry, fn.End)), 60 | ), 61 | ), 62 | RowSpacer1, 63 | Flexed(1, s.AssemblyList.Layout(len(asm), ele, click)), 64 | ) 65 | } 66 | -------------------------------------------------------------------------------- /fsnotify.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io/fs" 5 | "path/filepath" 6 | "strings" 7 | 8 | "github.com/fsnotify/fsnotify" 9 | ) 10 | 11 | func WatchAddRecursive(watcher *fsnotify.Watcher, p string) error { 12 | return filepath.WalkDir(p, func(path string, info fs.DirEntry, err error) error { 13 | if err != nil { 14 | return err 15 | } 16 | 17 | if strings.HasPrefix(info.Name(), ".") { 18 | return fs.SkipDir 19 | } 20 | 21 | if info.IsDir() { 22 | return watcher.Add(path) 23 | } 24 | 25 | return err 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /func_panel.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | 7 | "gioui.org/widget" 8 | . "github.com/emad-elsaid/debugger/ui" 9 | ) 10 | 11 | type FunctionsPanel struct { 12 | List ClickableList 13 | Filter widget.Editor 14 | } 15 | 16 | func NewFunctionsPanel() FunctionsPanel { 17 | return FunctionsPanel{ 18 | List: NewClickableList(), 19 | Filter: LineEditor(), 20 | } 21 | } 22 | 23 | func (f *FunctionsPanel) Layout(d *Debugger) W { 24 | filter := f.Filter.Text() 25 | regex, err := regexp.Compile(filter) 26 | if err != nil { 27 | regex, _ = regexp.Compile(regexp.QuoteMeta(filter)) 28 | } 29 | 30 | fs := []string{} 31 | for _, f := range d.Target().BinInfo().Functions { 32 | if regex.MatchString(f.Name) { 33 | fs = append(fs, f.Name) 34 | } 35 | } 36 | 37 | ele := func(c C, i int) D { 38 | return Inset1(Label(fs[i]))(c) 39 | } 40 | 41 | click := func(i int) { 42 | d.JumpToFunction(fs[i]) 43 | } 44 | 45 | return Rows( 46 | RowSpacer1, 47 | Rigid( 48 | FormRow( 49 | Rigid(Label(fmt.Sprintf(" %d", len(fs)))), 50 | ColSpacer1, 51 | Flexed(1, TextInput(&f.Filter, "Search Functions...")), 52 | ), 53 | ), 54 | RowSpacer1, 55 | Flexed(1, f.List.Layout(len(fs), ele, click)), 56 | ) 57 | } 58 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/emad-elsaid/debugger 2 | 3 | go 1.19 4 | 5 | require ( 6 | gioui.org v0.1.0 7 | github.com/emad-elsaid/types v0.0.2 8 | github.com/fsnotify/fsnotify v1.6.0 9 | github.com/go-delve/delve v1.21.0 10 | github.com/google/uuid v1.3.0 11 | golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 12 | golang.org/x/exp/shiny v0.0.0-20230713183714-613f0c0eb8a1 13 | golang.org/x/text v0.11.0 14 | ) 15 | 16 | require ( 17 | gioui.org/cpu v0.0.0-20220412190645-f1e9e8c3b1f7 // indirect 18 | gioui.org/shader v1.0.6 // indirect 19 | github.com/benoitkugler/textlayout v0.3.0 // indirect 20 | github.com/cilium/ebpf v0.11.0 // indirect 21 | github.com/gioui/uax v0.2.1-0.20220325163150-e3d987515a12 // indirect 22 | github.com/go-text/typesetting v0.0.0-20230714130734-4b4e92d4c7ae // indirect 23 | github.com/hashicorp/golang-lru v0.5.4 // indirect 24 | github.com/kr/pretty v0.3.1 // indirect 25 | github.com/mattn/go-isatty v0.0.19 // indirect 26 | github.com/sirupsen/logrus v1.9.3 // indirect 27 | golang.org/x/arch v0.4.0 // indirect 28 | golang.org/x/image v0.9.0 // indirect 29 | golang.org/x/sys v0.10.0 // indirect 30 | gopkg.in/yaml.v2 v2.4.0 // indirect 31 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect 32 | ) 33 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 8 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 9 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 10 | cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= 11 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 12 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 13 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 14 | eliasnaur.com/font v0.0.0-20220124212145-832bb8fc08c3 h1:djFprmHZgrSepsHAIRMp5UJn3PzsoTg9drI+BDmif5Q= 15 | eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d h1:ARo7NCVvN2NdhLlJE9xAbKweuI9L6UgfTbYb0YwPacY= 16 | gioui.org v0.0.0-20220710125950-53da73de35db h1:XEeVXLo6/rsn0FTpH2Iu3y/G0YvfDuN2nd6bQDhXe7M= 17 | gioui.org v0.0.0-20220710125950-53da73de35db/go.mod h1:WHoHbUjH91BJS2xkfps2AhKxji+9o3xwfsphGsCBfnM= 18 | gioui.org v0.0.0-20230425023356-bba91263b077 h1:BfZ3LodNX41vEUJI2dWHkFsZe+NVEHSTv8TceMyVe0M= 19 | gioui.org v0.0.0-20230425023356-bba91263b077/go.mod h1:8CFQM/4LurRd9G3NUYdacFb9j2pK0LrAyVO2mAZo4mw= 20 | gioui.org v0.1.0 h1:fEDY5A4+epOdzjCBYSUC4BzvjWqsjfqf5D6mskbthOs= 21 | gioui.org v0.1.0/go.mod h1:a3hz8FyrPMkt899D9YrxMGtyRzpPrJpz1Lzbssn81vI= 22 | gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ= 23 | gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2 h1:AGDDxsJE1RpcXTAxPG2B4jrwVUJGFDjINIPi1jtO6pc= 24 | gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ= 25 | gioui.org/cpu v0.0.0-20220412190645-f1e9e8c3b1f7 h1:tNJdnP5CgM39PRc+KWmBRRYX/zJ+rd5XaYxY5d5veqA= 26 | gioui.org/cpu v0.0.0-20220412190645-f1e9e8c3b1f7/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ= 27 | gioui.org/shader v1.0.6 h1:cvZmU+eODFR2545X+/8XucgZdTtEjR3QWW6W65b0q5Y= 28 | gioui.org/shader v1.0.6/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM= 29 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 30 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 31 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 32 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 33 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 34 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= 35 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= 36 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 37 | github.com/benoitkugler/pstokenizer v1.0.0/go.mod h1:l1G2Voirz0q/jj0TQfabNxVsa8HZXh/VMxFSRALWTiE= 38 | github.com/benoitkugler/textlayout v0.0.5/go.mod h1:puH4v13Uz7uIhIH0XMk5jgc8U3MXcn5r3VlV9K8n0D8= 39 | github.com/benoitkugler/textlayout v0.1.1 h1:hizE/085xAeY8q7gwV00uHR2Q27KYB2g1HW+UacXl68= 40 | github.com/benoitkugler/textlayout v0.1.1/go.mod h1:o+1hFV+JSHBC9qNLIuwVoLedERU7sBPgEFcuSgfvi/w= 41 | github.com/benoitkugler/textlayout v0.3.0 h1:2ehWXEkgb6RUokTjXh1LzdGwG4dRP6X3dqhYYDYhUVk= 42 | github.com/benoitkugler/textlayout v0.3.0/go.mod h1:o+1hFV+JSHBC9qNLIuwVoLedERU7sBPgEFcuSgfvi/w= 43 | github.com/benoitkugler/textlayout-testdata v0.1.1 h1:AvFxBxpfrQd8v55qH59mZOJOQjtD6K2SFe9/HvnIbJk= 44 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 45 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 46 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 47 | github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= 48 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 49 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 50 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 51 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 52 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 53 | github.com/cilium/ebpf v0.7.0 h1:1k/q3ATgxSXRdrmPfH8d7YK0GfqVsEKZAX9dQZvs56k= 54 | github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= 55 | github.com/cilium/ebpf v0.10.0 h1:nk5HPMeoBXtOzbkZBWym+ZWq1GIiHUsBFXxwewXAHLQ= 56 | github.com/cilium/ebpf v0.10.0/go.mod h1:DPiVdY/kT534dgc9ERmvP8mWA+9gvwgKfRvk4nNWnoE= 57 | github.com/cilium/ebpf v0.11.0 h1:V8gS/bTCCjX9uUnkUFUpPsksM8n1lXBAvHcpiFk1X2Y= 58 | github.com/cilium/ebpf v0.11.0/go.mod h1:WE7CZAnqOL2RouJ4f1uyNhqr2P4CCvXFIqdRDUgWsVs= 59 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 60 | github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21/go.mod h1:po7NpZ/QiTKzBKyrsEAxwnTamCoh8uDk/egRpQ7siIc= 61 | github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= 62 | github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 63 | github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 64 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 65 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 66 | github.com/cosiner/argv v0.1.0/go.mod h1:EusR6TucWKX+zFgtdUsKT2Cvg45K5rtpCcWz4hK06d8= 67 | github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 68 | github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w= 69 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 70 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 71 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 72 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 73 | github.com/derekparker/trie v0.0.0-20221213183930-4c74548207f4/go.mod h1:C7Es+DLenIpPc9J6IYw4jrK0h7S9bKj4DNl8+KxGEXU= 74 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 75 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= 76 | github.com/emad-elsaid/types v0.0.1 h1:7/IUZOQVNcVFkcXCrkCx5hcP2fJ/2PCHYjsRwwfWLBU= 77 | github.com/emad-elsaid/types v0.0.1/go.mod h1:V+Kq+VclMzClQ2y2k+I74gkBl1aFgLRNyyRYID/GVKw= 78 | github.com/emad-elsaid/types v0.0.2 h1:DjUeEzdcBZ75wGjlrI6Lz832Frg9+i4484Wk8UGe5GA= 79 | github.com/emad-elsaid/types v0.0.2/go.mod h1:V+Kq+VclMzClQ2y2k+I74gkBl1aFgLRNyyRYID/GVKw= 80 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 81 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 82 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 83 | github.com/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebPhedY= 84 | github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= 85 | github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= 86 | github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA= 87 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 88 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 89 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 90 | github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= 91 | github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= 92 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 93 | github.com/gioui/uax v0.2.1-0.20220325163150-e3d987515a12 h1:1bjaB/5IIicfKpP4k0s30T2WEw//Kh00zULa8DQ0cxA= 94 | github.com/gioui/uax v0.2.1-0.20220325163150-e3d987515a12/go.mod h1:kDhBRTA/i3H46PVdhqcw26TdGSIj42TOKNWKY+Kipnw= 95 | github.com/go-delve/delve v1.20.2 h1:rgPK7Iqb1oQk+i2Ilg0fpH6p5LqyixYiAt4N3Lhx4/Y= 96 | github.com/go-delve/delve v1.20.2/go.mod h1:KQtnLRy2M+cNHCRnDzURxljVNbRTdvVDD5Mb10KGP18= 97 | github.com/go-delve/delve v1.21.0 h1:npcc8TZhdVxaMSJon+zqcE3bXM/ck8SSOOWw/id13jI= 98 | github.com/go-delve/delve v1.21.0/go.mod h1:U+OAdfhewudkHsVs/AwhfpSBu7t/NgIXH3+my4T5q78= 99 | github.com/go-delve/liner v1.2.3-0.20220127212407-d32d89dd2a5d/go.mod h1:biJCRbqp51wS+I92HMqn5H8/A0PAhxn2vyOT+JqhiGI= 100 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 101 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 102 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 103 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 104 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 105 | github.com/go-text/typesetting v0.0.0-20220411150340-35994bc27a7b h1:WINlj3ANt+CVrO2B4NGDHRlPvEWZPxjhb7z+JKypwXI= 106 | github.com/go-text/typesetting v0.0.0-20220411150340-35994bc27a7b/go.mod h1:ZNYu5saGoMOqtkVH5T8onTwhzenDUVszI+5WFHJRaxQ= 107 | github.com/go-text/typesetting v0.0.0-20230413204129-b4f0492bf7ae h1:LCcaQgYrnS+sx9Tc3oGUvbRBRt+5oFnKWakaxeAvNVI= 108 | github.com/go-text/typesetting v0.0.0-20230413204129-b4f0492bf7ae/go.mod h1:KmrpWuSMFcO2yjmyhGpnBGQHSKAoEgMTSSzvLDzCuEA= 109 | github.com/go-text/typesetting v0.0.0-20230714130734-4b4e92d4c7ae h1:hIq73tWAj5c56QIoEdappaccyyV2+pbxQHmA0ziKxi4= 110 | github.com/go-text/typesetting v0.0.0-20230714130734-4b4e92d4c7ae/go.mod h1:evDBbvNR/KaVFZ2ZlDSOWWXIUKq0wCOEtzLxRM8SG3k= 111 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 112 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 113 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 114 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 115 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 116 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 117 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 118 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 119 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 120 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 121 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 122 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 123 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 124 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 125 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 126 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 127 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 128 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 129 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 130 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 131 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 132 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 133 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 134 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 135 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 136 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 137 | github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= 138 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 139 | github.com/google/go-dap v0.7.0/go.mod h1:5q8aYQFnHOAZEMP+6vmq25HKYAEwE+LF5yh7JKrrhSQ= 140 | github.com/google/go-dap v0.9.1/go.mod h1:HAeyoSd2WIfTfg+0GRXcFrb+RnojAtGNh+k+XTIxJDE= 141 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 142 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 143 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 144 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 145 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 146 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 147 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 148 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 149 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 150 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 151 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 152 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 153 | github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 154 | github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= 155 | github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= 156 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 157 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 158 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 159 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= 160 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 161 | github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= 162 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= 163 | github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= 164 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 165 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 166 | github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= 167 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 168 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 169 | github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= 170 | github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 171 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 172 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= 173 | github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= 174 | github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= 175 | github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= 176 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 177 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 178 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 179 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 180 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 181 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 182 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 183 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 184 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 185 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 186 | github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 187 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 188 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 189 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 190 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 191 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 192 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 193 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 194 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 195 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 196 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 197 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 198 | github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 199 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 200 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 201 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= 202 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 203 | github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= 204 | github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 205 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= 206 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 207 | github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 208 | github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 209 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 210 | github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= 211 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= 212 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 213 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 214 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 215 | github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= 216 | github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= 217 | github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 218 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 219 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 220 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 221 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 222 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 223 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 224 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 225 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 226 | github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg= 227 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 228 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 229 | github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48= 230 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 231 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 232 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 233 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 234 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 235 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 236 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 237 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 238 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 239 | github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= 240 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 241 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 242 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 243 | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 244 | github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 245 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 246 | github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 247 | github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= 248 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 249 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 250 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 251 | github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= 252 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 253 | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= 254 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 255 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 256 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= 257 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= 258 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 259 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 260 | github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= 261 | github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= 262 | github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 263 | github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= 264 | github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 265 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 266 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 267 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 268 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 269 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 270 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 271 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 272 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 273 | github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= 274 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 275 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 276 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 277 | github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= 278 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 279 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 280 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 281 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 282 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 283 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 284 | github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= 285 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 286 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 287 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 288 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 289 | go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 290 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 291 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 292 | go.starlark.net v0.0.0-20220816155156-cfacd8902214/go.mod h1:VZcBMdr3cT3PnBoWunTabuSEXwVAH+ZJ5zxfs3AdASk= 293 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 294 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 295 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 296 | golang.org/x/arch v0.0.0-20190927153633-4e8777c89be4 h1:QlVATYS7JBoZMVaf+cNjb90WD/beKVHnIxFKT4QaHVI= 297 | golang.org/x/arch v0.0.0-20190927153633-4e8777c89be4/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4= 298 | golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= 299 | golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 300 | golang.org/x/arch v0.4.0 h1:A8WCeEWhLwPBKNbFi5Wv5UTCBx5zzubnXDlMOFAzFMc= 301 | golang.org/x/arch v0.4.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 302 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 303 | golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 304 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 305 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 306 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 307 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 308 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 309 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 310 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 311 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 312 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 313 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 314 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 315 | golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA= 316 | golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= 317 | golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 h1:5llv2sWeaMSnA3w2kS57ouQQ4pudlXrR0dCgw51QK9o= 318 | golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= 319 | golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw= 320 | golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= 321 | golang.org/x/exp/shiny v0.0.0-20220722155223-a9213eeb770e h1:pkl1Ko5DrhA4ezwKwdnmO7H1sKmMy9qLuYKRjS7SlmE= 322 | golang.org/x/exp/shiny v0.0.0-20220722155223-a9213eeb770e/go.mod h1:VjAR7z0ngyATZTELrBSkxOOHhhlnVUxDye4mcjx5h/8= 323 | golang.org/x/exp/shiny v0.0.0-20230425010034-47ecfdc1ba53 h1:sF0Ip2V6Qkmm6XD01YbQQxqTj3FRjStwRmm8XQq8Hek= 324 | golang.org/x/exp/shiny v0.0.0-20230425010034-47ecfdc1ba53/go.mod h1:UH99kUObWAZkDnWqppdQe5ZhPYESUw8I0zVV1uWBR+0= 325 | golang.org/x/exp/shiny v0.0.0-20230713183714-613f0c0eb8a1 h1:W1Zj+1CgHyyNhDTG2eZ71X5hb6XWRr/oAsplhk22IB8= 326 | golang.org/x/exp/shiny v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:UH99kUObWAZkDnWqppdQe5ZhPYESUw8I0zVV1uWBR+0= 327 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 328 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 329 | golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 330 | golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= 331 | golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d h1:RNPAfi2nHY7C2srAV8A49jpsYr0ADedCk1wq6fTMTvs= 332 | golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= 333 | golang.org/x/image v0.7.0 h1:gzS29xtG1J5ybQlv0PuyfE3nmc6R4qB73m6LUUmvFuw= 334 | golang.org/x/image v0.7.0/go.mod h1:nd/q4ef1AKKYl/4kft7g+6UyGbdiqWqTP1ZAbRoV7Rg= 335 | golang.org/x/image v0.9.0 h1:QrzfX26snvCM20hIhBwuHI/ThTg18b/+kcKdXHvnR+g= 336 | golang.org/x/image v0.9.0/go.mod h1:jtrku+n79PfroUbvDdeUWMAI+heR786BofxrbiSF+J0= 337 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 338 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 339 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 340 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 341 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 342 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 343 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 344 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 345 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 346 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 347 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 348 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 349 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 350 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 351 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 352 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 353 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 354 | golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 355 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 356 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 357 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 358 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 359 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 360 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 361 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 362 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 363 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 364 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 365 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 366 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 367 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 368 | golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 369 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 370 | golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 371 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 372 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 373 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 374 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 375 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 376 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 377 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 378 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 379 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 380 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 381 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 382 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 383 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 384 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 385 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 386 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 387 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 388 | golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 389 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 390 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 391 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 392 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 393 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 394 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 395 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 396 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 397 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 398 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 399 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 400 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 401 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 402 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 403 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 404 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 405 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 406 | golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 407 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 408 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 409 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 410 | golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 411 | golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 412 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 413 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 414 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 415 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 416 | golang.org/x/sys v0.0.0-20220908164124-27713097b956 h1:XeJjHH1KiLpKGb6lvMiksZ9l0fVUh+AmGcm0nOMEBOY= 417 | golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 418 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 419 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 420 | golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= 421 | golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 422 | golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= 423 | golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 424 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 425 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 426 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 427 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 428 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 429 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 430 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 431 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 432 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 433 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 434 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 435 | golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= 436 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 437 | golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= 438 | golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 439 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 440 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 441 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 442 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 443 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 444 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 445 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 446 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 447 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 448 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 449 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 450 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 451 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 452 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 453 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 454 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 455 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 456 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 457 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 458 | golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 459 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 460 | golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 461 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 462 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 463 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 464 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 465 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 466 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 467 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 468 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 469 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 470 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 471 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 472 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 473 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 474 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 475 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 476 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 477 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 478 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 479 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 480 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 481 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 482 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 483 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 484 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 485 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 486 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 487 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 488 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 489 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 490 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 491 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 492 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 493 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 494 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 495 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 496 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 497 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 498 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 499 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 500 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 501 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 502 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 503 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 504 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 505 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 506 | gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 507 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 508 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 509 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 510 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 511 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 512 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 513 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 514 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 515 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 516 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= 517 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 518 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 519 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 520 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 521 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 522 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 523 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 524 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 525 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "time" 7 | 8 | "gioui.org/app" 9 | "gioui.org/io/system" 10 | "gioui.org/layout" 11 | "gioui.org/op" 12 | . "github.com/emad-elsaid/debugger/ui" 13 | ) 14 | 15 | var ( 16 | win = app.NewWindow() 17 | ops op.Ops 18 | tree W 19 | ) 20 | 21 | func main() { 22 | win.Option(app.Title("Go Debugger")) 23 | 24 | cmdAndArgs := os.Args[1:] 25 | if len(cmdAndArgs) == 0 { 26 | log.Fatalln("debugger needs a command: `run` or `test`") 27 | } 28 | 29 | cmd := cmdAndArgs[0] 30 | 31 | args := []string{"."} 32 | if len(cmd) > 1 { 33 | args = cmdAndArgs[1:] 34 | } 35 | 36 | debugger, err := NewDebugger("debug", args, true, cmd == "test") 37 | if err != nil { 38 | log.Fatalln(err.Error()) 39 | } 40 | 41 | screen, err := NewDebugScreen(debugger) 42 | if err != nil { 43 | log.Fatalln(err.Error()) 44 | } 45 | 46 | tree = screen.Layout 47 | 48 | go RunWindowAndExit(debugger) 49 | go Refresher() 50 | app.Main() 51 | } 52 | 53 | func RunWindowAndExit(debugger *Debugger) { 54 | if err := EventLoop(debugger); err != nil { 55 | log.Fatal(err) 56 | } 57 | os.Exit(0) 58 | } 59 | 60 | func EventLoop(debugger *Debugger) error { 61 | for e := range win.Events() { 62 | switch e := e.(type) { 63 | case system.DestroyEvent: 64 | debugger.Detach(true) 65 | return e.Err 66 | case system.FrameEvent: 67 | c := layout.NewContext(&ops, e) 68 | tree(c) 69 | e.Frame(c.Ops) 70 | } 71 | } 72 | 73 | return nil 74 | } 75 | 76 | func Refresher() { 77 | for { 78 | win.Invalidate() 79 | time.Sleep(time.Millisecond * 100) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /memory_panel.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import . "github.com/emad-elsaid/debugger/ui" 4 | 5 | type MemoryPanel struct { 6 | list List 7 | } 8 | 9 | func NewMemoryPanel() MemoryPanel { 10 | return MemoryPanel{ 11 | list: NewVerticalList(), 12 | } 13 | } 14 | 15 | func (m *MemoryPanel) Layout(a *Analytics) W { 16 | charts := []struct { 17 | title string 18 | analytics *[]uint64 19 | }{ 20 | {"Virtual Mem", &a.Vsize}, 21 | {"Resident Mem", &a.ResidentMem}, 22 | {"Shared Mem", &a.SharedMem}, 23 | {"Text Mem", &a.TextMem}, 24 | {"Data Mem", &a.DataMem}, 25 | } 26 | 27 | ele := func(c C, i int) D { 28 | a := *charts[i].analytics 29 | var last uint64 30 | if len(a) > 0 { 31 | last = a[len(a)-1] 32 | } 33 | 34 | return Inset1( 35 | Rows( 36 | Rigid( 37 | Columns( 38 | Flexed(1, Label(*&charts[i].title)), 39 | Rigid(Label(ByteCountToDecimal(last))), 40 | ), 41 | ), 42 | Rigid(Chart(a, 100)), 43 | ), 44 | )(c) 45 | } 46 | 47 | return Grid(&m.list, len(charts), 300, ele) 48 | } 49 | -------------------------------------------------------------------------------- /open_files_panel.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path" 7 | "strings" 8 | 9 | "gioui.org/widget" 10 | . "github.com/emad-elsaid/debugger/ui" 11 | . "github.com/emad-elsaid/types" 12 | ) 13 | 14 | type OpenFilesPanel struct { 15 | List List 16 | Filter widget.Editor 17 | } 18 | 19 | func NewOpenFilesPanel() OpenFilesPanel { 20 | return OpenFilesPanel{ 21 | List: NewVerticalList(), 22 | Filter: LineEditor(), 23 | } 24 | } 25 | 26 | func (o *OpenFilesPanel) Layout(d *Debugger, a *Analytics) W { 27 | filter := o.Filter.Text() 28 | files := o.Files(d.Target().Pid()) 29 | filtered := Slice[string](files).Select(func(s string) bool { return strings.Contains(s, filter) }) 30 | ele := func(c C, i int) D { return Inset1(Label(filtered[i]))(c) } 31 | openFiles := Slice[int](a.OpenFiles).Fetch(len(a.OpenFiles)-1, 0) 32 | 33 | return Columns( 34 | Flexed(1, 35 | Rows( 36 | RowSpacer1, 37 | Rigid( 38 | FormRow( 39 | Rigid(Label(fmt.Sprintf(" %d", len(filtered)))), 40 | ColSpacer1, 41 | Flexed(1, TextInput(&o.Filter, "Search open files...")), 42 | ), 43 | ), 44 | Rigid(HSpacer1), 45 | Flexed(1, ZebraList(&o.List, len(filtered), ele)), 46 | ), 47 | ), 48 | Rigid( 49 | Inset1( 50 | Panel( 51 | fmt.Sprintf("Open files (%d)", openFiles), 52 | Constraint(300, 100, Chart(a.OpenFiles, 100)), 53 | ), 54 | ), 55 | ), 56 | ) 57 | } 58 | 59 | func (o *OpenFilesPanel) Files(pid int) []string { 60 | fd := fmt.Sprintf("/proc/%d/fd", pid) 61 | fs, err := os.ReadDir(fd) 62 | if err != nil { 63 | return []string{} 64 | } 65 | 66 | paths := make([]string, 0, len(fs)) 67 | for _, v := range fs { 68 | dest, _ := os.Readlink(path.Join(fd, v.Name())) 69 | paths = append(paths, dest) 70 | } 71 | 72 | return paths 73 | } 74 | -------------------------------------------------------------------------------- /packages_panel.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | "strings" 7 | 8 | "gioui.org/widget" 9 | . "github.com/emad-elsaid/debugger/ui" 10 | ) 11 | 12 | type PackagesPanel struct { 13 | Filter widget.Editor 14 | List ClickableList 15 | } 16 | 17 | func NewPackagesPanel() PackagesPanel { 18 | return PackagesPanel{ 19 | Filter: LineEditor(), 20 | List: NewClickableList(), 21 | } 22 | } 23 | 24 | func (p *PackagesPanel) Layout(d *Debugger) W { 25 | filter := p.Filter.Text() 26 | 27 | pkgsMap := map[string]bool{} 28 | fs := d.Target().BinInfo().Functions 29 | for _, i := range fs { 30 | pkgsMap[i.PackageName()] = true 31 | } 32 | 33 | pkgs := make([]string, 0, len(pkgsMap)) 34 | for i := range pkgsMap { 35 | if strings.Contains(i, filter) { 36 | pkgs = append(pkgs, i) 37 | } 38 | } 39 | 40 | sort.Strings(pkgs) 41 | 42 | ele := func(c C, i int) D { 43 | return Inset1(Label(pkgs[i]))(c) 44 | } 45 | 46 | click := func(i int) { 47 | OpenBrowser(fmt.Sprintf("https://pkg.go.dev/%s", pkgs[i])) 48 | } 49 | 50 | return Rows( 51 | RowSpacer1, 52 | Rigid( 53 | FormRow( 54 | Rigid(Label(fmt.Sprintf(" %d", len(pkgs)))), 55 | ColSpacer1, 56 | Flexed(1, TextInput(&p.Filter, "Search Packages...")), 57 | ), 58 | ), 59 | RowSpacer1, 60 | Flexed(1, 61 | p.List.Layout(len(pkgs), ele, click), 62 | ), 63 | ) 64 | } 65 | -------------------------------------------------------------------------------- /path.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "path" 6 | ) 7 | 8 | func ProjectDir(p string) string { 9 | files := []string{ 10 | "go.mod", 11 | ".git", 12 | } 13 | 14 | for cp := p; cp != path.Dir(cp); cp = path.Dir(cp) { 15 | for _, f := range files { 16 | indicator := path.Join(cp, f) 17 | if _, err := os.Stat(indicator); err == nil { 18 | return cp 19 | } 20 | } 21 | } 22 | 23 | return p 24 | } 25 | -------------------------------------------------------------------------------- /proc.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | 7 | . "github.com/emad-elsaid/debugger/ui" 8 | "github.com/go-delve/delve/pkg/proc" 9 | ) 10 | 11 | const MaxVarUINest = 4 12 | 13 | var ProcLoadConfig = proc.LoadConfig{ 14 | FollowPointers: true, 15 | MaxStringLen: 10000, 16 | MaxArrayValues: 100, 17 | MaxStructFields: 100, 18 | MaxMapBuckets: 100, 19 | MaxVariableRecurse: 2, 20 | } 21 | 22 | type VarWidget interface { 23 | Layout(c C) D 24 | } 25 | 26 | func NewVarWidget(v *proc.Variable) VarWidget { 27 | if v == nil { 28 | return &UnknownVarWidget{Variable: v} 29 | } 30 | 31 | switch v.Kind { 32 | case reflect.String: 33 | return &StringVarWidget{Variable: v} 34 | case reflect.Struct: 35 | return &StructVarWidget{ 36 | Variable: v, 37 | open: false, 38 | clickable: new(Clickable), 39 | } 40 | case reflect.Float32, reflect.Float64, 41 | reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, 42 | reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, 43 | reflect.Uintptr: 44 | return &NumberVarWidget{Variable: v} 45 | case reflect.Slice, reflect.Array: 46 | return &SliceVarWidget{ 47 | Variable: v, 48 | open: false, 49 | clickable: new(Clickable), 50 | } 51 | case reflect.Bool: 52 | return &BoolVarWidget{Variable: v} 53 | case reflect.Func: 54 | return &FuncVarWidget{Variable: v} 55 | case reflect.Ptr, reflect.UnsafePointer: 56 | return &PtrVarWidget{Variable: v} 57 | case reflect.Map: 58 | return &MapVarWidget{ 59 | Variable: v, 60 | open: false, 61 | clickable: new(Clickable), 62 | } 63 | case reflect.Complex64, reflect.Complex128: 64 | return &ComplexVarWidget{Variable: v} 65 | case reflect.Chan: 66 | return &ChanVarWidget{Variable: v} 67 | case reflect.Interface: 68 | return &InterfaceVarWidget{Variable: v} 69 | default: 70 | return &UnknownVarWidget{Variable: v} 71 | } 72 | } 73 | 74 | // String widget 75 | type StringVarWidget struct { 76 | *proc.Variable 77 | } 78 | 79 | func (w *StringVarWidget) Layout(c C) D { 80 | var val string = "nil" 81 | 82 | if w.Value != nil { 83 | val = w.Value.ExactString() 84 | } 85 | return Label(fmt.Sprintf("%s %s = %s", w.Name, w.TypeString(), val))(c) 86 | } 87 | 88 | // Struct widget 89 | type StructVarWidget struct { 90 | *proc.Variable 91 | open bool 92 | clickable *Clickable 93 | children []VarWidget 94 | } 95 | 96 | func (w *StructVarWidget) Layout(c C) D { 97 | if w.clickable.Clicked() { 98 | w.open = !w.open 99 | } 100 | 101 | if w.open { 102 | if w.children == nil { 103 | w.children = make([]VarWidget, 0, len(w.Children)) 104 | 105 | for _, f := range w.Children { 106 | w.children = append(w.children, NewVarWidget(&f)) 107 | } 108 | } 109 | 110 | fields := []FlexChild{ 111 | Rigid( 112 | LayoutToWidget( 113 | w.clickable.Layout, 114 | Label(fmt.Sprintf("%s %s = {", w.Name, w.TypeString())), 115 | ), 116 | ), 117 | } 118 | 119 | padding := Margin(0, 0, 0, SpaceUnit*3) 120 | for _, c := range w.children { 121 | fields = append(fields, Rigid(padding(c.Layout))) 122 | } 123 | 124 | fields = append(fields, Rigid(Label("}"))) 125 | 126 | return Rows(fields...)(c) 127 | } else { 128 | return LayoutToWidget( 129 | w.clickable.Layout, 130 | Label(fmt.Sprintf("%s %s = {...}", w.Name, w.TypeString())), 131 | )(c) 132 | } 133 | } 134 | 135 | // Map widget 136 | type MapVarWidget struct { 137 | *proc.Variable 138 | open bool 139 | clickable *Clickable 140 | keys []VarWidget 141 | values []VarWidget 142 | } 143 | 144 | func (w *MapVarWidget) Layout(c C) D { 145 | if w.clickable.Clicked() { 146 | w.open = !w.open 147 | } 148 | 149 | if w.open { 150 | if w.keys == nil || w.values == nil { 151 | w.keys = make([]VarWidget, 0, len(w.Children)/2) 152 | w.values = make([]VarWidget, 0, len(w.Children)/2) 153 | 154 | for i := range w.Children { 155 | if i%2 == 0 { 156 | w.keys = append(w.keys, NewVarWidget(&w.Children[i])) 157 | } else { 158 | w.values = append(w.values, NewVarWidget(&w.Children[i])) 159 | } 160 | } 161 | } 162 | 163 | fields := []FlexChild{ 164 | Rigid( 165 | LayoutToWidget( 166 | w.clickable.Layout, Label(fmt.Sprintf("%s %s = {", w.Name, w.TypeString())), 167 | ), 168 | ), 169 | } 170 | 171 | for k := range w.keys { 172 | fields = append(fields, 173 | Rigid( 174 | Columns( 175 | ColSpacer3, 176 | Rigid(w.keys[k].Layout), 177 | Rigid(Label(" : ")), 178 | Rigid(w.values[k].Layout), 179 | ), 180 | ), 181 | ) 182 | } 183 | 184 | fields = append(fields, Rigid(Label("}"))) 185 | 186 | return Rows(fields...)(c) 187 | } else { 188 | return LayoutToWidget( 189 | w.clickable.Layout, 190 | Label(fmt.Sprintf("%s %s = {...(%d)...}", w.Name, w.TypeString(), w.Len/2)), 191 | )(c) 192 | } 193 | } 194 | 195 | // Numbers widget like int, float, uint... 196 | type NumberVarWidget struct { 197 | *proc.Variable 198 | } 199 | 200 | func (v *NumberVarWidget) Layout(c C) D { 201 | var val string 202 | if v.Value == nil { 203 | val = "nil" 204 | } else { 205 | val = v.Value.ExactString() 206 | } 207 | 208 | return Label(fmt.Sprintf("%s %s = %s", v.Name, v.TypeString(), val))(c) 209 | } 210 | 211 | // Slice of values 212 | type SliceVarWidget struct { 213 | *proc.Variable 214 | open bool 215 | clickable *Clickable 216 | children []VarWidget 217 | } 218 | 219 | func (w *SliceVarWidget) Layout(c C) D { 220 | if w.clickable.Clicked() { 221 | w.open = !w.open 222 | } 223 | 224 | if w.open { 225 | if w.children == nil { 226 | w.children = make([]VarWidget, 0, len(w.Children)) 227 | 228 | for _, f := range w.Children { 229 | w.children = append(w.children, NewVarWidget(&f)) 230 | } 231 | } 232 | 233 | fields := []FlexChild{ 234 | Rigid(LayoutToWidget(w.clickable.Layout, Label(fmt.Sprintf("%s %s = {", w.Name, w.TypeString())))), 235 | } 236 | 237 | padding := Margin(0, 0, 0, SpaceUnit*3) 238 | for _, c := range w.children { 239 | fields = append(fields, Rigid(padding(c.Layout))) 240 | } 241 | 242 | fields = append(fields, Rigid(Label("}"))) 243 | 244 | return Rows(fields...)(c) 245 | } else { 246 | return LayoutToWidget(w.clickable.Layout, Label(fmt.Sprintf("%s %s = {...(%d)...}", w.Name, w.TypeString(), w.Len)))(c) 247 | } 248 | } 249 | 250 | // Boolean widget 251 | type BoolVarWidget struct { 252 | *proc.Variable 253 | } 254 | 255 | func (v *BoolVarWidget) Layout(c C) D { 256 | var val string 257 | if v.Value != nil { 258 | val = v.Value.ExactString() 259 | } 260 | return Label(fmt.Sprintf("%s %s = %s", v.Name, v.TypeString(), val))(c) 261 | } 262 | 263 | // Func widget 264 | type FuncVarWidget struct { 265 | *proc.Variable 266 | } 267 | 268 | func (v *FuncVarWidget) Layout(c C) D { 269 | var val string 270 | if v.Value != nil { 271 | val = v.Value.ExactString() 272 | } 273 | return Label(fmt.Sprintf("%s %s = %s", v.Name, v.TypeString(), val))(c) 274 | } 275 | 276 | // Pointer widget 277 | type PtrVarWidget struct { 278 | *proc.Variable 279 | child VarWidget 280 | } 281 | 282 | func (v *PtrVarWidget) Layout(c C) D { 283 | 284 | var val W = EmptyWidget 285 | 286 | if len(v.Children) > 0 { 287 | if v.child == nil { 288 | v.child = NewVarWidget(&v.Children[0]) 289 | } 290 | val = v.child.Layout 291 | } 292 | 293 | return Columns( 294 | Rigid(Label(fmt.Sprintf("%s %s ->", v.Name, v.TypeString()))), 295 | Flexed(1, val), 296 | )(c) 297 | } 298 | 299 | // Complex numbers widget 300 | type ComplexVarWidget struct { 301 | *proc.Variable 302 | } 303 | 304 | func (v *ComplexVarWidget) Layout(c C) D { 305 | var val string 306 | if v.Value == nil { 307 | val = "nil" 308 | } else { 309 | val = v.Value.ExactString() 310 | } 311 | 312 | return Label(fmt.Sprintf("%s %s = %s", v.Name, v.TypeString(), val))(c) 313 | } 314 | 315 | // Chan widget 316 | type ChanVarWidget struct { 317 | *proc.Variable 318 | } 319 | 320 | func (v *ChanVarWidget) Layout(c C) D { 321 | return Label(fmt.Sprintf("%s %s", v.Name, v.TypeString()))(c) 322 | } 323 | 324 | // Interface widget 325 | type InterfaceVarWidget struct { 326 | *proc.Variable 327 | child VarWidget 328 | } 329 | 330 | func (v *InterfaceVarWidget) Layout(c C) D { 331 | var val W = EmptyWidget 332 | 333 | if len(v.Children) > 0 { 334 | if v.child == nil { 335 | v.child = NewVarWidget(&v.Children[0]) 336 | } 337 | val = v.child.Layout 338 | } 339 | 340 | return Columns( 341 | Rigid(Label(fmt.Sprintf("%s %s ->", v.Name, v.TypeString()))), 342 | Flexed(1, val), 343 | )(c) 344 | } 345 | 346 | // Rest of variables widgets 347 | type UnknownVarWidget struct { 348 | *proc.Variable 349 | } 350 | 351 | func (v *UnknownVarWidget) Layout(c C) D { 352 | if v.Variable == nil { 353 | return Label("nil")(c) 354 | } 355 | 356 | return Label(fmt.Sprintf("Can't render '%s' type: %s", v.Name, v.Kind))(c) 357 | } 358 | -------------------------------------------------------------------------------- /runtime.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/go-delve/delve/pkg/proc" 4 | 5 | // copied from: https://go.dev/src/runtime/runtime2.go 6 | 7 | // A waitReason explains why a goroutine has been stopped. 8 | // See gopark. Do not re-use waitReasons, add new ones. 9 | type waitReason uint8 10 | 11 | const ( 12 | waitReasonZero waitReason = iota // "" 13 | waitReasonGCAssistMarking // "GC assist marking" 14 | waitReasonIOWait // "IO wait" 15 | waitReasonChanReceiveNilChan // "chan receive (nil chan)" 16 | waitReasonChanSendNilChan // "chan send (nil chan)" 17 | waitReasonDumpingHeap // "dumping heap" 18 | waitReasonGarbageCollection // "garbage collection" 19 | waitReasonGarbageCollectionScan // "garbage collection scan" 20 | waitReasonPanicWait // "panicwait" 21 | waitReasonSelect // "select" 22 | waitReasonSelectNoCases // "select (no cases)" 23 | waitReasonGCAssistWait // "GC assist wait" 24 | waitReasonGCSweepWait // "GC sweep wait" 25 | waitReasonGCScavengeWait // "GC scavenge wait" 26 | waitReasonChanReceive // "chan receive" 27 | waitReasonChanSend // "chan send" 28 | waitReasonFinalizerWait // "finalizer wait" 29 | waitReasonForceGCIdle // "force gc (idle)" 30 | waitReasonSemacquire // "semacquire" 31 | waitReasonSleep // "sleep" 32 | waitReasonSyncCondWait // "sync.Cond.Wait" 33 | waitReasonTimerGoroutineIdle // "timer goroutine (idle)" 34 | waitReasonTraceReaderBlocked // "trace reader (blocked)" 35 | waitReasonWaitForGCCycle // "wait for GC cycle" 36 | waitReasonGCWorkerIdle // "GC worker (idle)" 37 | waitReasonPreempted // "preempted" 38 | waitReasonDebugCall // "debug call" 39 | ) 40 | 41 | var waitReasonStrings = [...]string{ 42 | waitReasonZero: "", 43 | waitReasonGCAssistMarking: "GC assist marking", 44 | waitReasonIOWait: "IO wait", 45 | waitReasonChanReceiveNilChan: "chan receive (nil chan)", 46 | waitReasonChanSendNilChan: "chan send (nil chan)", 47 | waitReasonDumpingHeap: "dumping heap", 48 | waitReasonGarbageCollection: "garbage collection", 49 | waitReasonGarbageCollectionScan: "garbage collection scan", 50 | waitReasonPanicWait: "panicwait", 51 | waitReasonSelect: "select", 52 | waitReasonSelectNoCases: "select (no cases)", 53 | waitReasonGCAssistWait: "GC assist wait", 54 | waitReasonGCSweepWait: "GC sweep wait", 55 | waitReasonGCScavengeWait: "GC scavenge wait", 56 | waitReasonChanReceive: "chan receive", 57 | waitReasonChanSend: "chan send", 58 | waitReasonFinalizerWait: "finalizer wait", 59 | waitReasonForceGCIdle: "force gc (idle)", 60 | waitReasonSemacquire: "semacquire", 61 | waitReasonSleep: "sleep", 62 | waitReasonSyncCondWait: "sync.Cond.Wait", 63 | waitReasonTimerGoroutineIdle: "timer goroutine (idle)", 64 | waitReasonTraceReaderBlocked: "trace reader (blocked)", 65 | waitReasonWaitForGCCycle: "wait for GC cycle", 66 | waitReasonGCWorkerIdle: "GC worker (idle)", 67 | waitReasonPreempted: "preempted", 68 | waitReasonDebugCall: "debug call", 69 | } 70 | 71 | func (w waitReason) String() string { 72 | if w < 0 || w >= waitReason(len(waitReasonStrings)) { 73 | return "unknown wait reason" 74 | } 75 | return waitReasonStrings[w] 76 | } 77 | 78 | func GoRoutineStatus(s uint64) string { 79 | switch s { 80 | case proc.Gidle: 81 | return "Idle" 82 | case proc.Grunnable: 83 | return "Runnable" 84 | case proc.Grunning: 85 | return "Running" 86 | case proc.Gsyscall: 87 | return "Syscall" 88 | case proc.Gwaiting: 89 | return "Waiting" 90 | case proc.GmoribundUnused: 91 | return "Unused" 92 | case proc.Gdead: 93 | return "Dead" 94 | case proc.Genqueue: 95 | return "Enqueue" 96 | case proc.Gcopystack: 97 | return "CopyStack" 98 | } 99 | 100 | return "" 101 | } 102 | -------------------------------------------------------------------------------- /screenshots/main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emad-elsaid/debugger/ae768a3b5dc2c75d8cc9d06beafc067f4146af3e/screenshots/main.png -------------------------------------------------------------------------------- /sidebar.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "gioui.org/widget" 5 | . "github.com/emad-elsaid/debugger/ui" 6 | "golang.org/x/text/language" 7 | "golang.org/x/text/message" 8 | ) 9 | 10 | var lfmt = message.NewPrinter(language.English) 11 | 12 | type SideBar struct { 13 | watches []VarWidget 14 | watchesList List 15 | watchEditor widget.Editor 16 | clickables Clickables 17 | debuggerLastState DebuggerState 18 | } 19 | 20 | func NewSideBar() SideBar { 21 | return SideBar{ 22 | watches: []VarWidget{}, 23 | watchesList: NewVerticalList(), 24 | watchEditor: LineEditor(), 25 | clickables: NewClickables(), 26 | } 27 | } 28 | 29 | func (s *SideBar) Layout(d *Debugger) W { 30 | return Background(SidebarBgColor, Rows( 31 | Rigid(s.Watches(d)), 32 | )) 33 | } 34 | 35 | func (s *SideBar) Watches(d *Debugger) W { 36 | evts := s.watchEditor.Events() 37 | for _, evt := range evts { 38 | switch v := evt.(type) { 39 | case widget.SubmitEvent: 40 | text := v.Text 41 | if len(text) == 0 { 42 | continue 43 | } 44 | 45 | d.CreateWatch(text) 46 | s.watchEditor.SetText("") 47 | } 48 | } 49 | 50 | s.ExecuteWatches(d) 51 | 52 | ele := func(c C, i int) D { 53 | if i >= len(s.watches) { 54 | return D{} 55 | } 56 | expr := d.WatchesExpr[i] 57 | varW := s.watches[i] 58 | var w W 59 | if varW == nil { 60 | w = Label(expr) 61 | } else { 62 | w = varW.Layout 63 | } 64 | 65 | del := func() { d.DeleteWatch(expr) } 66 | 67 | return Inset05( 68 | Columns( 69 | Flexed(1, w), 70 | Rigid(OnClick(s.clickables.Get(expr), IconDelete, del)), 71 | ), 72 | )(c) 73 | } 74 | 75 | return Panel("Watches", 76 | Rows( 77 | Rigid(ZebraList(&s.watchesList, len(d.WatchesExpr), ele)), 78 | Rigid(TextInput(&s.watchEditor, "New watch expression...")), 79 | ), 80 | ) 81 | } 82 | 83 | func (s *SideBar) ExecuteWatches(d *Debugger) { 84 | if d.State == s.debuggerLastState && len(s.watches) == len(d.WatchesExpr) { 85 | return 86 | } 87 | 88 | s.debuggerLastState = d.State 89 | 90 | results := []VarWidget{} 91 | for _, expr := range d.WatchesExpr { 92 | val, err := d.ExecuteExpr(expr) 93 | if err != nil { 94 | results = append(results, nil) 95 | } else { 96 | results = append(results, NewVarWidget(val)) 97 | } 98 | } 99 | 100 | s.watches = results 101 | } 102 | -------------------------------------------------------------------------------- /source_panel.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "strings" 8 | "time" 9 | 10 | "gioui.org/layout" 11 | . "github.com/emad-elsaid/debugger/ui" 12 | . "github.com/emad-elsaid/types" 13 | "github.com/go-delve/delve/service/api" 14 | ) 15 | 16 | var ( 17 | HighlightColor = BANANA_100 18 | RunningColor = LIME_100 19 | ) 20 | 21 | type SourcePanel struct { 22 | layout.List 23 | content []string 24 | File string 25 | Line int 26 | LastUpdate time.Time 27 | LinesBtns map[int]*Clickable 28 | BpBtns map[int]*Clickable 29 | DebuggerLastState DebuggerState 30 | Vars map[int64][]VarWidget 31 | } 32 | 33 | func NewSourcePanel() SourcePanel { 34 | return SourcePanel{ 35 | List: NewVerticalList(), 36 | content: []string{}, 37 | LinesBtns: map[int]*Clickable{}, 38 | BpBtns: map[int]*Clickable{}, 39 | Vars: map[int64][]VarWidget{}, 40 | } 41 | } 42 | 43 | func (s *SourcePanel) Layout(d *Debugger) W { 44 | s.LoadFile(d.File) 45 | s.ScrollTo(d.File, d.Line) 46 | s.LoadVarsWidgets(d) 47 | 48 | bps := map[int]bool{} 49 | breakpoints := d.Breakpoints() 50 | for _, v := range breakpoints { 51 | if v.File == d.File { 52 | bps[v.Line] = !v.Disabled 53 | } 54 | } 55 | 56 | running := d.RunningLines() 57 | 58 | click := func(i int) { 59 | var err error 60 | name := fmt.Sprintf("%s:%d", d.File, i+1) 61 | 62 | if d.IsRunning() { 63 | d.Stop() 64 | defer d.Continue() 65 | } 66 | 67 | bp := d.FindBreakpointByName(name) 68 | 69 | if bp == nil { 70 | bp = &api.Breakpoint{ 71 | Name: name, 72 | File: d.File, 73 | Line: i + 1, 74 | } 75 | 76 | _, err = d.CreateBreakpoint(bp) 77 | } else if bp.Disabled { 78 | _, err = d.ClearBreakpoint(bp) 79 | } else { 80 | bp.Disabled = true 81 | err = d.AmendBreakpoint(bp) 82 | } 83 | 84 | if err != nil { 85 | log.Println(LogError, err.Error()) 86 | } 87 | } 88 | 89 | return s.LayoutLines(d, running, bps, click) 90 | } 91 | 92 | func (s *SourcePanel) LoadFile(file string) { 93 | stat, err := os.Stat(file) 94 | if err != nil { 95 | s.SetText("") 96 | return 97 | } 98 | 99 | if file == s.File && stat.ModTime() == s.LastUpdate { 100 | return 101 | } 102 | 103 | s.File = file 104 | s.LastUpdate = stat.ModTime() 105 | 106 | c, err := os.ReadFile(file) 107 | if err != nil { 108 | s.SetText("") 109 | return 110 | } 111 | 112 | s.SetText(string(c)) 113 | } 114 | 115 | func (s *SourcePanel) ScrollTo(file string, line int) { 116 | if file == s.File && line == s.Line { 117 | return 118 | } 119 | 120 | s.File = file 121 | s.Line = line 122 | 123 | topLines := s.Position.Count / 2 124 | if topLines <= 0 { 125 | topLines = 5 126 | } 127 | 128 | scrollTo := line - topLines 129 | if scrollTo < 0 { 130 | scrollTo = 0 131 | } 132 | 133 | s.Position.First = scrollTo 134 | } 135 | 136 | func (s *SourcePanel) LineBtn(i int) *Clickable { 137 | if btn, ok := s.LinesBtns[i]; ok { 138 | return btn 139 | } 140 | 141 | btn := Clickable{} 142 | s.LinesBtns[i] = &btn 143 | return &btn 144 | } 145 | 146 | func (s *SourcePanel) SetText(t string) { 147 | s.content = strings.Split(t, "\n") 148 | } 149 | 150 | func (src *SourcePanel) LayoutLines(d *Debugger, running []int, bps map[int]bool, BpClick func(int)) W { 151 | content := src.content 152 | l := len(src.content) 153 | 154 | elem := func(c C, i int) D { 155 | lineNo := i + 1 156 | 157 | if _, ok := src.BpBtns[i]; !ok { 158 | src.BpBtns[i] = new(Clickable) 159 | } 160 | btn := src.BpBtns[i] 161 | if btn.Clicked() { 162 | BpClick(i) 163 | } 164 | 165 | line := strings.ReplaceAll(content[i], "\t", " ") 166 | 167 | bgCol := BackgroundColor 168 | fgCol := SecondaryTextColor 169 | if v, ok := bps[lineNo]; ok { 170 | fgCol = BackgroundColor 171 | if v { 172 | bgCol = BreakPointColor 173 | } else { 174 | bgCol = BreakPointDisabledColor 175 | } 176 | } 177 | 178 | bp := Wrap(Label(fmt.Sprintf("%03d", lineNo)), Inset05, AlignEnd, TextColor(fgCol)) 179 | if bgCol != BackgroundColor { 180 | bp = Background(bgCol, bp) 181 | } 182 | bp = RoundedCorners(bp) 183 | 184 | bpWithButton := LayoutToWidget(btn.Layout, bp) 185 | 186 | lineBtn := src.LineBtn(i) 187 | if lineBtn.Clicked() { 188 | d.Line = i + 1 189 | src.Line = i + 1 190 | } 191 | 192 | varRows := []FlexChild{} 193 | if vs, ok := src.Vars[int64(lineNo)]; ok { 194 | for _, lineVar := range vs { 195 | varRows = append(varRows, Rigid(lineVar.Layout)) 196 | } 197 | } 198 | 199 | w := Columns( 200 | ColSpacer1, 201 | Rigid(bpWithButton), 202 | ColSpacer1, 203 | Rigid(Inset05(Label(line))), 204 | ColSpacer1, 205 | Rigid( 206 | Wrap(Rows(varRows...), Inset05, TextColor(SecondaryTextColor)), 207 | ), 208 | ) 209 | 210 | if Slice[int](running).Include(lineNo) { 211 | w = Background(RunningColor, w) 212 | } else if d.Line == lineNo { 213 | w = Background(HighlightColor, w) 214 | } 215 | 216 | return lineBtn.Layout(c, w) 217 | } 218 | 219 | return func(c C) D { 220 | return src.List.Layout(c, l, elem) 221 | } 222 | } 223 | 224 | func (s *SourcePanel) LoadVarsWidgets(d *Debugger) { 225 | if d.LastState == nil || d.State == s.DebuggerLastState { 226 | return 227 | } 228 | 229 | s.DebuggerLastState = d.State 230 | s.Vars = map[int64][]VarWidget{} 231 | 232 | locals, _ := d.LocalVariables() 233 | for _, v := range locals { 234 | w := NewVarWidget(v) 235 | if _, ok := s.Vars[v.DeclLine]; !ok { 236 | s.Vars[v.DeclLine] = []VarWidget{w} 237 | } else { 238 | s.Vars[v.DeclLine] = append(s.Vars[v.DeclLine], w) 239 | } 240 | } 241 | 242 | args, _ := d.FunctionArguments() 243 | for _, v := range args { 244 | w := NewVarWidget(v) 245 | if _, ok := s.Vars[v.DeclLine]; !ok { 246 | s.Vars[v.DeclLine] = []VarWidget{w} 247 | } else { 248 | s.Vars[v.DeclLine] = append(s.Vars[v.DeclLine], w) 249 | } 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /sources_panel.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "strings" 7 | 8 | "gioui.org/widget" 9 | . "github.com/emad-elsaid/debugger/ui" 10 | ) 11 | 12 | type SourcesPanel struct { 13 | List ClickableList 14 | Filter widget.Editor 15 | } 16 | 17 | func NewSourcesPanel() SourcesPanel { 18 | return SourcesPanel{ 19 | List: NewClickableList(), 20 | Filter: LineEditor(), 21 | } 22 | } 23 | 24 | func (s *SourcesPanel) Layout(d *Debugger) W { 25 | f := s.Filter.Text() 26 | ss := d.Target().BinInfo().Sources 27 | filtered := []string{} 28 | for i := range ss { 29 | if strings.Contains(ss[i], f) { 30 | filtered = append(filtered, ss[i]) 31 | } 32 | } 33 | 34 | ele := func(c C, i int) D { 35 | w := Inset1(Label(filtered[i])) 36 | 37 | if filtered[i] == d.File { 38 | w = Background(HighlightColor, w) 39 | } 40 | 41 | return w(c) 42 | } 43 | 44 | click := func(i int) { 45 | f := filtered[i] 46 | log.Printf("Switching file: %s", f) 47 | d.SetFileLine(f, 0) 48 | } 49 | 50 | return Rows( 51 | RowSpacer1, 52 | Rigid( 53 | FormRow( 54 | Rigid(Label(fmt.Sprintf(" %d", len(filtered)))), 55 | ColSpacer1, 56 | Flexed(1, TextInput(&s.Filter, "Search Sources...")), 57 | ), 58 | ), 59 | Rigid(HSpacer1), 60 | Flexed(1, s.List.Layout(len(filtered), ele, click)), 61 | ) 62 | } 63 | -------------------------------------------------------------------------------- /stack_panel.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "strings" 7 | "time" 8 | 9 | "gioui.org/widget" 10 | . "github.com/emad-elsaid/debugger/ui" 11 | "github.com/go-delve/delve/pkg/proc" 12 | ) 13 | 14 | type StackPanel struct { 15 | Filter widget.Editor 16 | List ClickableList 17 | RoutineList ClickableList 18 | StackVarsList List 19 | Vars []W 20 | 21 | // Cache 22 | GoRoutines []*proc.G 23 | DebuggerState DebuggerState 24 | } 25 | 26 | func NewStackPanel() StackPanel { 27 | return StackPanel{ 28 | Filter: LineEditor(), 29 | List: NewClickableList(), 30 | RoutineList: NewClickableList(), 31 | StackVarsList: NewVerticalList(), 32 | Vars: []W{}, 33 | GoRoutines: []*proc.G{}, 34 | } 35 | } 36 | 37 | func (sp *StackPanel) Layout(d *Debugger) W { 38 | if d.State != sp.DebuggerState { 39 | sp.DebuggerState = d.State 40 | sp.SetVariables(d) 41 | sp.SetGoRoutines(d) 42 | } 43 | 44 | return Columns( 45 | Flexed(1, sp.GoroutinesPanel(d)), 46 | ColSpacer2, 47 | Flexed(4, sp.StackPanel(d)), 48 | ColSpacer2, 49 | Flexed(2, sp.VariablesPanel), 50 | ) 51 | } 52 | 53 | func (sp *StackPanel) GoroutinesPanel(d *Debugger) W { 54 | count := len(sp.GoRoutines) 55 | 56 | elem := func(c C, i int) D { 57 | if i >= count { 58 | return D{} 59 | } 60 | 61 | r := sp.GoRoutines[i] 62 | g, _ := d.SelectedGoRoutine() 63 | label := "" 64 | if r.CurrentLoc.Fn != nil { 65 | label = r.CurrentLoc.Fn.Name 66 | } else { 67 | label = fmt.Sprintf("%s:%d", r.CurrentLoc.File, r.CurrentLoc.Line) 68 | } 69 | 70 | w := Inset1(Label(fmt.Sprintf("%d: %s", r.ID, label))) 71 | 72 | if g != nil && g.ID == r.ID { 73 | w = Background(HighlightColor, w) 74 | } 75 | 76 | return w(c) 77 | } 78 | 79 | onClick := func(i int) { 80 | if i >= count { 81 | return 82 | } 83 | 84 | r := sp.GoRoutines[i] 85 | log.Printf("Switching Go routine to: %d: %s", r.ID, r.Go().File) 86 | d.Target().SwitchGoroutine(r) 87 | } 88 | 89 | return Panel( 90 | fmt.Sprintf("Go Routines: (%d)", count), 91 | sp.RoutineList.Layout(count, elem, onClick), 92 | ) 93 | } 94 | 95 | func (sp *StackPanel) StackPanel(d *Debugger) W { 96 | stack, err := d.Stacktrace() 97 | if err != nil { 98 | return Centered(Label(err.Error())) 99 | } 100 | 101 | filter := sp.Filter.Text() 102 | stk := make([]proc.Stackframe, 0, len(stack)) 103 | for _, i := range stack { 104 | l := "" 105 | if i.Current.Fn != nil { 106 | l = i.Current.Fn.Name 107 | } else { 108 | l = fmt.Sprintf("%s:%d", i.Current.File, i.Current.Line) 109 | } 110 | 111 | if strings.Contains(l, filter) { 112 | stk = append(stk, i) 113 | } 114 | } 115 | 116 | ele := func(c C, i int) D { 117 | s := stk[i] 118 | l := "" 119 | if s.Current.Fn != nil { 120 | l = s.Current.Fn.Name 121 | } else { 122 | l = fmt.Sprintf("%s:%d", s.Current.File, s.Current.Line) 123 | } 124 | 125 | w := Inset1(Label(l)) 126 | 127 | if d.File == s.Current.File && d.Line == s.Current.Line { 128 | w = Background(HighlightColor, w) 129 | } 130 | 131 | return w(c) 132 | } 133 | 134 | click := func(i int) { 135 | d.StackFrame = i 136 | s := stk[i] 137 | d.SetFileLine(s.Current.File, s.Current.Line) 138 | } 139 | 140 | return Rows( 141 | RowSpacer1, 142 | Rigid( 143 | FormRow( 144 | Rigid(Label(fmt.Sprintf("%d", len(stk)))), 145 | ColSpacer1, 146 | Flexed(1, TextInput(&sp.Filter, "Search Stack...")), 147 | ), 148 | ), 149 | RowSpacer1, 150 | Flexed(1, sp.List.Layout(len(stk), ele, click)), 151 | ) 152 | } 153 | 154 | func (sp *StackPanel) SetVariables(d *Debugger) { 155 | sp.Vars = []W{} 156 | 157 | args, err := d.FunctionArguments() 158 | if err != nil { 159 | return 160 | } 161 | sp.varsToVarWidget("Arguments", args) 162 | 163 | vars, err := d.LocalVariables() 164 | if err != nil { 165 | return 166 | } 167 | sp.varsToVarWidget("Local Variables", vars) 168 | 169 | g, err := d.SelectedGoRoutine() 170 | if err != nil { 171 | return 172 | } 173 | sp.GoRoutineToWidgets(g) 174 | 175 | } 176 | 177 | func (sp *StackPanel) SetGoRoutines(d *Debugger) { 178 | routines, err := d.GoRoutines() 179 | if err == nil { 180 | sp.GoRoutines = routines 181 | } 182 | } 183 | 184 | func (sp *StackPanel) varsToVarWidget(title string, args []*proc.Variable) { 185 | sp.Vars = append(sp.Vars, Bold(Text(title))) 186 | for _, v := range args { 187 | w := NewVarWidget(v) 188 | sp.Vars = append(sp.Vars, w.Layout) 189 | } 190 | } 191 | 192 | func (sp *StackPanel) GoRoutineToWidgets(g *proc.G) { 193 | sp.Vars = append(sp.Vars, 194 | Bold(Text("Go Routine properties")), 195 | Text(fmt.Sprintf("Status: %s", GoRoutineStatus(g.Status))), 196 | Text(fmt.Sprintf("SystemStack: %v", g.SystemStack)), 197 | Text(fmt.Sprintf("Wait Since: %s", time.Duration(g.WaitSince))), 198 | Text(fmt.Sprintf("Wait Reason: %s", waitReason(g.WaitReason))), 199 | Text(fmt.Sprintf("Go statement: %s:%d", g.Go().File, g.Go().Line)), 200 | ) 201 | } 202 | 203 | func (sp *StackPanel) VariablesPanel(c C) D { 204 | ele := func(c C, i int) D { 205 | item := sp.Vars[i] 206 | return item(c) 207 | } 208 | return sp.StackVarsList.Layout(c, len(sp.Vars), ele) 209 | } 210 | -------------------------------------------------------------------------------- /structures.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Appends to the slice. if len reached cap removed the first item 4 | // This allow a slice to be limited by its capacity 5 | func limitedAppend[T any](c *[]T, i T) { 6 | if len(*c) >= cap(*c) { 7 | copy((*c)[:len(*c)-1],(*c)[1:]) 8 | *c = (*c)[:len(*c)-1] 9 | } 10 | 11 | *c = append(*c, i) 12 | } 13 | -------------------------------------------------------------------------------- /toolbar.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | . "github.com/emad-elsaid/debugger/ui" 8 | "github.com/go-delve/delve/pkg/proc" 9 | "golang.org/x/exp/shiny/materialdesign/icons" 10 | ) 11 | 12 | var ( 13 | IconStop = Icon(icons.AVStop, Theme.TextColor) 14 | IconRestart = Icon(icons.NavigationRefresh, Theme.TextColor) 15 | IconContinue = Icon(icons.AVPlayArrow, SuccessColor) 16 | IconStepOut = Icon(icons.NavigationArrowBack, Theme.TextColor) 17 | IconNext = Icon(icons.NavigationArrowDownward, Theme.TextColor) 18 | IconStep = Icon(icons.NavigationArrowForward, Theme.TextColor) 19 | IconProcess = Icon(icons.AVPlayCircleFilled, SecondaryTextColor) 20 | ) 21 | 22 | type Toolbar struct { 23 | RestartBtn Clickable 24 | StopBtn Clickable 25 | ContinueBtn Clickable 26 | NextBtn Clickable 27 | StepBtn Clickable 28 | StepOutBtn Clickable 29 | } 30 | 31 | func (t *Toolbar) Layout(d *Debugger) W { 32 | target := d.Target() 33 | pid := target.Pid() 34 | 35 | valid := d.LastState == nil || !d.LastState.Exited 36 | showStop := d.IsRunning() 37 | showCont := !d.IsRunning() && valid 38 | showRestart := showStop || showCont || !valid 39 | 40 | showStep := !d.IsRunning() && valid && d.StopReason() != proc.StopLaunched 41 | showNext := !d.IsRunning() && valid && d.StopReason() != proc.StopLaunched 42 | showStepOut := !d.IsRunning() && valid && d.StopReason() != proc.StopLaunched 43 | 44 | IconSize := FontEnlarge(2) 45 | 46 | btns := []FlexChild{ 47 | Flexed(1, 48 | Inset1( 49 | Columns( 50 | Rigid(IconSize(IconFolder)), 51 | ColSpacer1, 52 | Rigid( 53 | Rows( 54 | Rigid(Bold(Label(d.Path))), 55 | Rigid( 56 | WidgetIf( 57 | d.File != "", 58 | Wrap( 59 | Text(fmt.Sprintf("%s:%d", d.File, d.Line)), 60 | AlignStart, 61 | TextColor(SecondaryTextColor), 62 | MaxLines(3), 63 | ), 64 | ), 65 | ), 66 | ), 67 | ), 68 | ColSpacer3, 69 | Rigid(IconSize(IconProcess)), 70 | ColSpacer1, 71 | Rigid( 72 | Rows( 73 | Rigid(Label(fmt.Sprintf("pid: %d", pid))), 74 | Rigid(Label(fmt.Sprintf("cwd: %s", t.CWD(pid)))), 75 | ), 76 | ), 77 | ), 78 | ), 79 | ), 80 | } 81 | 82 | if t.StopBtn.Clicked() { 83 | d.Stop() 84 | } 85 | if showStop { 86 | btns = append(btns, 87 | Rigid(ToolbarButton(&t.StopBtn, IconSize(IconStop), "Stop")), 88 | ) 89 | } 90 | 91 | if t.RestartBtn.Clicked() { 92 | d.Restart() 93 | } 94 | if showRestart { 95 | btns = append(btns, 96 | Rigid(ToolbarButton(&t.RestartBtn, IconSize(IconRestart), "Restart")), 97 | ) 98 | } 99 | 100 | if t.ContinueBtn.Clicked() { 101 | d.Continue() 102 | } 103 | if showCont { 104 | btns = append(btns, 105 | Rigid(ToolbarButton(&t.ContinueBtn, IconSize(IconContinue), "Continue")), 106 | ) 107 | } 108 | 109 | if t.StepOutBtn.Clicked() { 110 | d.StepOut() 111 | } 112 | if showStepOut { 113 | btns = append(btns, 114 | Rigid(ToolbarButton(&t.StepOutBtn, IconSize(IconStepOut), "Step out")), 115 | ) 116 | } 117 | 118 | if t.NextBtn.Clicked() { 119 | d.Next() 120 | } 121 | if showNext { 122 | btns = append(btns, 123 | Rigid(ToolbarButton(&t.NextBtn, IconSize(IconNext), "Next")), 124 | ) 125 | } 126 | 127 | if t.StepBtn.Clicked() { 128 | d.Step() 129 | } 130 | if showStep { 131 | btns = append(btns, 132 | Rigid(ToolbarButton(&t.StepBtn, IconSize(IconStep), "Step")), 133 | ) 134 | } 135 | 136 | return Background(ToolbarBgColor, 137 | ColumnsVCentered(btns...), 138 | ) 139 | } 140 | 141 | func (t *Toolbar) CWD(pid int) string { 142 | cwd, _ := os.Readlink(fmt.Sprintf("/proc/%d/cwd", pid)) 143 | return string(cwd) 144 | } 145 | -------------------------------------------------------------------------------- /types_panel.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | "sort" 7 | 8 | "gioui.org/widget" 9 | . "github.com/emad-elsaid/debugger/ui" 10 | ) 11 | 12 | type TypesPanel struct { 13 | List List 14 | Filter widget.Editor 15 | } 16 | 17 | func NewTypesPanel() TypesPanel { 18 | return TypesPanel{ 19 | List: NewVerticalList(), 20 | Filter: LineEditor(), 21 | } 22 | } 23 | 24 | func (t *TypesPanel) Layout(d *Debugger) W { 25 | filter := t.Filter.Text() 26 | regex, err := regexp.Compile(filter) 27 | if err != nil { 28 | regex, _ = regexp.Compile(regexp.QuoteMeta(filter)) 29 | } 30 | 31 | allTypes, err := d.Target().BinInfo().Types() 32 | if err != nil { 33 | allTypes = []string{} 34 | } 35 | 36 | types := make([]string, 0, len(allTypes)) 37 | for _, typ := range allTypes { 38 | if regex.Match([]byte(typ)) { 39 | types = append(types, typ) 40 | } 41 | } 42 | 43 | sort.Strings(types) 44 | 45 | ele := func(c C, i int) D { 46 | return Inset1(Label(types[i]))(c) 47 | } 48 | 49 | return Rows( 50 | RowSpacer1, 51 | Rigid( 52 | FormRow( 53 | Rigid(Label(fmt.Sprintf(" %d", len(types)))), 54 | ColSpacer1, 55 | Flexed(1, TextInput(&t.Filter, "Search types...")), 56 | ), 57 | ), 58 | RowSpacer1, 59 | Flexed(1, ZebraList(&t.List, len(types), ele)), 60 | ) 61 | } 62 | -------------------------------------------------------------------------------- /ui/atoms.go: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import ( 4 | "image" 5 | "image/color" 6 | 7 | "gioui.org/op" 8 | "gioui.org/op/clip" 9 | "gioui.org/op/paint" 10 | "gioui.org/widget" 11 | ) 12 | 13 | func RoundedCorners(w W) W { 14 | return func(c C) D { 15 | macro := op.Record(c.Ops) 16 | d := w(c) 17 | macroOp := macro.Stop() 18 | 19 | defer clip.UniformRRect(image.Rect(0, 0, d.Size.X, d.Size.Y), 5).Push(c.Ops).Pop() 20 | macroOp.Add(c.Ops) 21 | return d 22 | } 23 | } 24 | 25 | func Border(w W) W { 26 | return func(c C) D { 27 | return widget.Border{Color: BorderColor, Width: BorderSize, CornerRadius: 0}.Layout(c, w) 28 | } 29 | } 30 | 31 | func BorderActive(w W) W { 32 | return func(c C) D { 33 | return widget.Border{Color: ActiveBorderColor, Width: BorderSize, CornerRadius: 0}.Layout(c, w) 34 | } 35 | } 36 | 37 | func Background(background color.NRGBA, w W) W { 38 | return func(c C) D { 39 | macro := op.Record(c.Ops) 40 | d := w(c) 41 | path := macro.Stop() 42 | 43 | cl := clip.Rect{Max: d.Size}.Push(c.Ops) 44 | paint.Fill(c.Ops, background) 45 | cl.Pop() 46 | 47 | path.Add(c.Ops) 48 | return d 49 | } 50 | } 51 | 52 | func HR(sz int) W { 53 | return func(c C) D { 54 | cl := clip.Path{} 55 | cl.Begin(c.Ops) 56 | cl.MoveTo(Pt(0, 0)) 57 | cl.Line(Pt(float32(c.Constraints.Max.X), 0)) 58 | 59 | defer clip.Stroke{ 60 | Path: cl.End(), 61 | Width: float32(sz), 62 | }.Op().Push(c.Ops).Pop() 63 | 64 | paint.Fill(c.Ops, BorderColor) 65 | 66 | return D{Size: P{c.Constraints.Min.X, sz}} 67 | } 68 | } 69 | 70 | func VR(sz int) W { 71 | return func(c C) D { 72 | cl := clip.Path{} 73 | cl.Begin(c.Ops) 74 | cl.MoveTo(Pt(0, 0)) 75 | cl.Line(Pt(0, float32(c.Constraints.Max.Y))) 76 | 77 | defer clip.Stroke{ 78 | Path: cl.End(), 79 | Width: float32(sz), 80 | }.Op().Push(c.Ops).Pop() 81 | 82 | paint.Fill(c.Ops, BorderColor) 83 | 84 | return D{Size: P{sz, c.Constraints.Min.Y}} 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /ui/charts.go: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import ( 4 | "math" 5 | 6 | "gioui.org/f32" 7 | "gioui.org/op" 8 | "gioui.org/op/clip" 9 | "gioui.org/op/paint" 10 | ) 11 | 12 | func Chart[T Numeric](ds []T, height float32) W { 13 | return func(c C) D { 14 | 15 | max := float32(Max(ds)) 16 | if max == 0 { 17 | return EmptyWidget(c) 18 | } 19 | 20 | capds := float32(cap(ds)) 21 | lends := float32(len(ds)) 22 | width := float32(c.Constraints.Max.X) 23 | center := Pt(width/2, height/2) 24 | xunit := width / capds 25 | yunit := height / max 26 | 27 | matOp := op.Affine( 28 | f32.Affine2D{}. 29 | Rotate(center, math.Pi). 30 | Scale(center, Pt(-1, 1)). 31 | Offset(Pt((capds-lends)*xunit, 0)), 32 | ).Push(c.Ops) 33 | 34 | p := new(clip.Path) 35 | p.Begin(c.Ops) 36 | p.MoveTo(Pt(0, 0)) 37 | 38 | for i, v := range ds { 39 | x := xunit * float32(i) 40 | y := yunit * float32(v) 41 | p.LineTo(Pt(x, y)) 42 | } 43 | 44 | p.LineTo( 45 | Pt(xunit*lends, 0), 46 | ) 47 | p.Close() 48 | 49 | defer clip.Outline{Path: p.End()}.Op().Push(c.Ops).Pop() 50 | paint.Fill(c.Ops, BLUEBERRY_100) 51 | matOp.Pop() 52 | 53 | return D{Size: P{X: int(width), Y: int(height)}} 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /ui/clickable.go: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import "gioui.org/widget" 4 | 5 | type ( 6 | Clickable = widget.Clickable 7 | ) 8 | 9 | type Clickables map[string]*Clickable 10 | 11 | func NewClickables() Clickables { 12 | return map[string]*Clickable{} 13 | } 14 | 15 | func (c Clickables) Get(id string) *Clickable { 16 | if btn, ok := c[id]; ok { 17 | return btn 18 | } 19 | 20 | btn := new(Clickable) 21 | c[id] = btn 22 | 23 | return btn 24 | } 25 | -------------------------------------------------------------------------------- /ui/colors.go: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import "image/color" 4 | 5 | func Color(hex int) color.NRGBA { 6 | return color.NRGBA{ 7 | R: uint8(hex >> 16), 8 | G: uint8(hex >> 8), 9 | B: uint8(hex), 10 | A: 255, 11 | } 12 | } 13 | 14 | func Alpha(c color.NRGBA, alpha uint8) color.NRGBA { 15 | c.A = alpha 16 | return c 17 | } 18 | 19 | func MixColor(c1, c2 color.NRGBA, percent int) color.NRGBA { 20 | p1 := float32(percent) / float32(100.0) 21 | p2 := 1 - p1 22 | return color.NRGBA{ 23 | R: uint8(float32(c1.R)*p1 + float32(c2.R)*p2), 24 | G: uint8(float32(c1.G)*p1 + float32(c2.G)*p2), 25 | B: uint8(float32(c1.B)*p1 + float32(c2.B)*p2), 26 | A: uint8(float32(c1.A)*p1 + float32(c2.A)*p2), 27 | } 28 | } 29 | 30 | // ElementaryOS palette https://github.com/elementary/stylesheet/blob/master/src/gtk-4.0/_palette.scss 31 | var ( 32 | STRAWBERRY_100 = Color(0xff8c82) 33 | STRAWBERRY_300 = Color(0xed5353) 34 | STRAWBERRY_500 = Color(0xc6262e) 35 | STRAWBERRY_700 = Color(0xa10705) 36 | STRAWBERRY_900 = Color(0x7a0000) 37 | 38 | ORANGE_100 = Color(0xffc27d) 39 | ORANGE_300 = Color(0xffa154) 40 | ORANGE_500 = Color(0xf37329) 41 | ORANGE_700 = Color(0xcc3b02) 42 | ORANGE_900 = Color(0xa62100) 43 | 44 | BANANA_100 = Color(0xfff394) 45 | BANANA_300 = Color(0xffe16b) 46 | BANANA_500 = Color(0xf9c440) 47 | BANANA_700 = Color(0xd48e15) 48 | BANANA_900 = Color(0xad5f00) 49 | 50 | LIME_100 = Color(0xd1ff82) 51 | LIME_300 = Color(0x9bdb4d) 52 | LIME_500 = Color(0x68b723) 53 | LIME_700 = Color(0x3a9104) 54 | LIME_900 = Color(0x206b00) 55 | 56 | MINT_100 = Color(0x89ffdd) 57 | MINT_300 = Color(0x43d6b5) 58 | MINT_500 = Color(0x28bca3) 59 | MINT_700 = Color(0x0e9a83) 60 | MINT_900 = Color(0x007367) 61 | 62 | BLUEBERRY_100 = Color(0x8cd5ff) 63 | BLUEBERRY_300 = Color(0x64baff) 64 | BLUEBERRY_500 = Color(0x3689e6) 65 | BLUEBERRY_700 = Color(0x0d52bf) 66 | BLUEBERRY_900 = Color(0x002e99) 67 | 68 | BUBBLEGUM_100 = Color(0xfe9ab8) 69 | BUBBLEGUM_300 = Color(0xf4679d) 70 | BUBBLEGUM_500 = Color(0xde3e80) 71 | BUBBLEGUM_700 = Color(0xbc245d) 72 | BUBBLEGUM_900 = Color(0x910e38) 73 | 74 | GRAPE_100 = Color(0xe4c6fa) 75 | GRAPE_300 = Color(0xcd9ef7) 76 | GRAPE_500 = Color(0xa56de2) 77 | GRAPE_700 = Color(0x7239b3) 78 | GRAPE_900 = Color(0x452981) 79 | 80 | COCOA_100 = Color(0xa3907c) 81 | COCOA_300 = Color(0x8a715e) 82 | COCOA_500 = Color(0x715344) 83 | COCOA_700 = Color(0x57392d) 84 | COCOA_900 = Color(0x3d211b) 85 | 86 | SILVER_100 = Color(0xfafafa) 87 | SILVER_300 = Color(0xd4d4d4) 88 | SILVER_500 = Color(0xabacae) 89 | SILVER_700 = Color(0x7e8087) 90 | SILVER_900 = Color(0x555761) 91 | 92 | SLATE_100 = Color(0x95a3ab) 93 | SLATE_300 = Color(0x667885) 94 | SLATE_500 = Color(0x485a6c) 95 | SLATE_700 = Color(0x273445) 96 | SLATE_900 = Color(0x0e141f) 97 | 98 | BLACK_100 = Color(0x666666) 99 | BLACK_300 = Color(0x4d4d4d) 100 | BLACK_500 = Color(0x333333) 101 | BLACK_700 = Color(0x1a1a1a) 102 | BLACK_900 = Color(0x000000) 103 | 104 | WHITE = Color(0xffffff) 105 | ) 106 | 107 | var ( 108 | ACCENT_COLOR_100 = BLUEBERRY_100 109 | ACCENT_COLOR_300 = BLUEBERRY_300 110 | ACCENT_COLOR_500 = BLUEBERRY_500 111 | ACCENT_COLOR_700 = BLUEBERRY_700 112 | ACCENT_COLOR_900 = BLUEBERRY_900 113 | ACCENT_COLOR = MixColor(BLUEBERRY_300, BLUEBERRY_500, 25) 114 | ) 115 | 116 | var ( 117 | BackgroundColor = WHITE 118 | SecondaryTextColor = SILVER_500 119 | 120 | DangerColor = STRAWBERRY_500 121 | SuccessColor = LIME_700 122 | WarningColor = BANANA_900 123 | 124 | InputBgColor = SILVER_100 125 | ViewsBgColor = WHITE 126 | SidebarBgColor = MixColor(SILVER_100, SILVER_300, 75) 127 | 128 | BorderColor = MixColor(SILVER_300, WHITE, 70) 129 | ActiveBorderColor = SILVER_500 130 | 131 | CardColor = SidebarBgColor 132 | CheckboxColor = ACCENT_COLOR_500 133 | ) 134 | -------------------------------------------------------------------------------- /ui/components.go: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import ( 4 | "image/color" 5 | 6 | "gioui.org/widget" 7 | ) 8 | 9 | func Icon(b []byte, color color.NRGBA) W { 10 | i, _ := widget.NewIcon(b) 11 | 12 | return func(c C) D { 13 | c.Constraints.Min.X = int(Theme.FontSize) 14 | return i.Layout(c, color) 15 | } 16 | } 17 | 18 | func Panel(title string, w W) W { 19 | return Rows( 20 | Rigid( 21 | Inset1( 22 | Bold( 23 | Label(title), 24 | ), 25 | ), 26 | ), 27 | Rigid(w), 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /ui/conditionals.go: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | func WidgetIf(cond bool, w W) W { 4 | if cond { 5 | return w 6 | } else { 7 | return EmptyWidget 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /ui/directory_browser.go: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import ( 4 | "errors" 5 | "io/fs" 6 | "os" 7 | "path" 8 | "path/filepath" 9 | 10 | "golang.org/x/exp/shiny/materialdesign/icons" 11 | ) 12 | 13 | var ( 14 | IconFolder = Icon(icons.FileFolderOpen, SecondaryTextColor) 15 | IconFile = Icon(icons.ActionDescription, SecondaryTextColor) 16 | IconUp = Icon(icons.NavigationArrowUpward, Theme.TextColor) 17 | ) 18 | 19 | type DirectoryBrowser struct { 20 | List List 21 | Btns map[string]*Clickable 22 | Up Clickable 23 | MinItemSize int 24 | } 25 | 26 | func NewDirectoryBrowser() DirectoryBrowser { 27 | return DirectoryBrowser{ 28 | List: NewVerticalList(), 29 | Btns: map[string]*Clickable{}, 30 | Up: Clickable{}, 31 | MinItemSize: int(Theme.FontSize * 20), 32 | } 33 | } 34 | 35 | func (d *DirectoryBrowser) Layout(dist *string) W { 36 | stat, err := os.Stat(*dist) 37 | if errors.Is(err, os.ErrNotExist) { 38 | *dist, _ = os.UserHomeDir() 39 | } else if stat != nil && !stat.IsDir() { 40 | *dist = path.Dir(*dist) 41 | } 42 | 43 | files := []fs.DirEntry{} 44 | filepath.WalkDir(*dist, func(path string, d fs.DirEntry, err error) error { 45 | if err != nil { 46 | return err 47 | } 48 | 49 | if path == *dist { 50 | return nil 51 | } 52 | files = append(files, d) 53 | if d.IsDir() { 54 | return fs.SkipDir 55 | } 56 | 57 | return nil 58 | }) 59 | 60 | ele := func(c C, i int) D { 61 | f := files[i] 62 | p := path.Join(*dist, f.Name()) 63 | 64 | ico := IconFile 65 | if f.IsDir() { 66 | ico = IconFolder 67 | } 68 | 69 | doubleFont := FontSize(Theme.FontSize * 2) 70 | 71 | card := Border( 72 | Inset1( 73 | Columns( 74 | Rigid(doubleFont(ico)), 75 | ColSpacer1, 76 | Flexed(1, Label(f.Name())), 77 | ), 78 | ), 79 | ) 80 | 81 | if !f.IsDir() { 82 | return Inset05(card)(c) 83 | } 84 | 85 | var btn *Clickable 86 | var ok bool 87 | if btn, ok = d.Btns[p]; !ok { 88 | btn = new(Clickable) 89 | d.Btns[p] = btn 90 | } 91 | 92 | if btn.Clicked() { 93 | *dist = p 94 | } 95 | 96 | if btn.Hovered() { 97 | card = Background(BorderColor, card) 98 | } 99 | 100 | return Inset05(func(c C) D { return btn.Layout(c, card) })(c) 101 | } 102 | 103 | return func(c C) D { 104 | if d.Up.Clicked() { 105 | *dist = path.Dir(*dist) 106 | } 107 | 108 | return Rows( 109 | Rigid( 110 | Background(ToolbarBgColor, 111 | Columns( 112 | Rigid(ToolbarButton(&d.Up, IconUp, "Up")), 113 | Flexed(1, Inset1(Bold(Label(*dist)))), 114 | ), 115 | ), 116 | ), 117 | Flexed(1, Grid(&d.List, len(files), d.MinItemSize, ele)), 118 | )(c) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /ui/form.go: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import ( 4 | "gioui.org/widget" 5 | "gioui.org/widget/material" 6 | "golang.org/x/exp/shiny/materialdesign/icons" 7 | ) 8 | 9 | func LineEditor() widget.Editor { 10 | return widget.Editor{ 11 | SingleLine: true, 12 | Submit: true, 13 | } 14 | } 15 | 16 | func TextInput(editor *widget.Editor, hint string) W { 17 | border := Border 18 | 19 | if editor.Focused() { 20 | border = BorderActive 21 | } 22 | 23 | return Background(BackgroundColor, 24 | Wrap( 25 | material.Editor(th, editor, hint).Layout, 26 | border, Inset1, 27 | ), 28 | ) 29 | } 30 | 31 | func Button(clickable *Clickable, label string) W { 32 | col := ACCENT_COLOR 33 | if clickable.Hovered() { 34 | col = ACCENT_COLOR_500 35 | } 36 | 37 | return LayoutToWidget( 38 | clickable.Layout, 39 | RoundedCorners( 40 | Background(col, Wrap(Label(label), Inset1, AlignMiddle, TextColor(WHITE))), 41 | ), 42 | ) 43 | } 44 | 45 | var ( 46 | IconCheckbox = Inset05(Icon(icons.ToggleCheckBoxOutlineBlank, CheckboxColor)) 47 | IconCheckboxActive = Inset05(Icon(icons.ToggleCheckBox, CheckboxColor)) 48 | ) 49 | 50 | func CheckboxBtn(value bool, btn *Clickable) W { 51 | var icon W 52 | if value { 53 | icon = IconCheckboxActive 54 | } else { 55 | icon = IconCheckbox 56 | } 57 | 58 | return LayoutToWidget(btn.Layout, icon) 59 | } 60 | 61 | func Checkbox(btn *Clickable, value bool, onclick func()) W { 62 | if btn.Clicked() { 63 | onclick() 64 | } 65 | 66 | var icon W 67 | if value { 68 | icon = IconCheckboxActive 69 | } else { 70 | icon = IconCheckbox 71 | } 72 | 73 | return LayoutToWidget(btn.Layout, icon) 74 | } 75 | 76 | func CheckboxBool(btn *Clickable, value *bool) W { 77 | return Checkbox(btn, *value, func() { *value = !*value }) 78 | } 79 | 80 | func OnClick(btn *Clickable, w W, onclick func()) W { 81 | if btn.Clicked() { 82 | onclick() 83 | } 84 | 85 | return func(c C) D { 86 | return btn.Layout(c, w) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /ui/layouts.go: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import ( 4 | "math" 5 | 6 | "gioui.org/layout" 7 | ) 8 | 9 | type ( 10 | FlexChild = layout.FlexChild 11 | ) 12 | 13 | var ( 14 | Flexed = layout.Flexed 15 | Rigid = layout.Rigid 16 | ) 17 | 18 | func LayoutToWidget(r func(C, W) D, w W) W { 19 | return func(c C) D { 20 | return r(c, w) 21 | } 22 | } 23 | 24 | func LayoutToWrapper(r func(C, W) D) func(w W) W { 25 | return func(w W) W { 26 | return func(c C) D { 27 | return r(c, w) 28 | } 29 | } 30 | } 31 | 32 | func Rows(children ...layout.FlexChild) W { 33 | return func(c C) D { 34 | return layout.Flex{Axis: layout.Vertical}.Layout(c, children...) 35 | } 36 | } 37 | 38 | var ( 39 | RowSpacer1 = Rigid(HSpacer1) 40 | RowSpacer2 = Rigid(HSpacer2) 41 | RowSpacer3 = Rigid(HSpacer3) 42 | RowSpacer4 = Rigid(HSpacer4) 43 | RowSpacer5 = Rigid(HSpacer5) 44 | RowSpacer6 = Rigid(HSpacer6) 45 | ) 46 | 47 | func Columns(children ...layout.FlexChild) W { 48 | return func(c C) D { 49 | return layout.Flex{Axis: layout.Horizontal}.Layout(c, children...) 50 | } 51 | } 52 | 53 | func ColumnsVCentered(children ...layout.FlexChild) W { 54 | return func(c C) D { 55 | return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(c, children...) 56 | } 57 | } 58 | 59 | var ( 60 | ColSpacer1 = Rigid(WSpacer1) 61 | ColSpacer2 = Rigid(WSpacer2) 62 | ColSpacer3 = Rigid(WSpacer3) 63 | ColSpacer4 = Rigid(WSpacer4) 64 | ColSpacer5 = Rigid(WSpacer5) 65 | ColSpacer6 = Rigid(WSpacer6) 66 | ) 67 | 68 | func FormRow(children ...layout.FlexChild) W { 69 | return func(c C) D { 70 | return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(c, children...) 71 | } 72 | } 73 | 74 | func Grid(l *List, count int, minItemSize int, ele layout.ListElement) W { 75 | return func(c C) D { 76 | perRow := c.Constraints.Max.X / minItemSize 77 | 78 | row := func(c C, i int) D { 79 | children := []FlexChild{} 80 | start := i * int(perRow) 81 | end := (i + 1) * int(perRow) 82 | for f := start; f < end; f++ { 83 | if f < count { 84 | children = append(children, Flexed(1, func(f int) W { 85 | return func(c C) D { 86 | return ele(c, f) 87 | } 88 | }(f))) 89 | } else { 90 | children = append(children, Flexed(1, EmptyWidget)) 91 | } 92 | } 93 | 94 | return Columns(children...)(c) 95 | } 96 | 97 | return l.Layout(c, int(math.Ceil(float64(count)/float64(perRow))), row) 98 | } 99 | } 100 | 101 | func Centered(w W) W { 102 | return func(c C) D { 103 | v := layout.Flex{Axis: layout.Vertical, Spacing: layout.SpaceAround} 104 | h := layout.Flex{Axis: layout.Horizontal, Spacing: layout.SpaceAround} 105 | return h.Layout(c, Rigid(func(c C) D { 106 | return v.Layout(c, Rigid(w)) 107 | })) 108 | } 109 | } 110 | 111 | func Constraint(width, height int, w W) W { 112 | return func(c C) D { 113 | c.Constraints.Max = P{width, height} 114 | return w(c) 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /ui/lists.go: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import "gioui.org/layout" 4 | 5 | func NewVerticalList() List { 6 | return List{ 7 | Axis: layout.Vertical, 8 | } 9 | } 10 | 11 | func ZebraList(l *List, len int, ele layout.ListElement) W { 12 | return func(c C) D { 13 | return l.Layout(c, len, func(c C, i int) D { 14 | w := func(c C) D { 15 | return ele(c, i) 16 | } 17 | 18 | if i%2 == 0 { 19 | return Background(SILVER_100, w)(c) 20 | } 21 | 22 | return w(c) 23 | }) 24 | } 25 | } 26 | 27 | type ClickableList struct { 28 | List 29 | Btns map[int]*Clickable 30 | } 31 | 32 | func NewClickableList() ClickableList { 33 | return ClickableList{ 34 | List: NewVerticalList(), 35 | Btns: map[int]*Clickable{}, 36 | } 37 | } 38 | 39 | func (l *ClickableList) Layout(len int, ele layout.ListElement, onClick func(i int)) W { 40 | for i, cl := range l.Btns { 41 | if cl.Clicked() { 42 | onClick(i) 43 | } 44 | } 45 | 46 | return ZebraList(&l.List, len, func(c C, i int) D { 47 | if _, ok := l.Btns[i]; !ok { 48 | l.Btns[i] = &Clickable{} 49 | } 50 | 51 | w := func(c C) D { return ele(c, i) } 52 | return l.Btns[i].Layout(c, w) 53 | }) 54 | } 55 | -------------------------------------------------------------------------------- /ui/math.go: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import "golang.org/x/exp/constraints" 4 | 5 | type Numeric interface { 6 | constraints.Float | constraints.Integer 7 | } 8 | 9 | func Max[T Numeric](s []T) (m T) { 10 | for _, v := range s { 11 | if v > m { 12 | m = v 13 | } 14 | } 15 | return 16 | } 17 | -------------------------------------------------------------------------------- /ui/spacers.go: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import ( 4 | "gioui.org/layout" 5 | "gioui.org/unit" 6 | ) 7 | 8 | // Insets 9 | var ( 10 | Inset05 = LayoutToWrapper(layout.UniformInset(SpaceUnit * 0.5).Layout) 11 | Inset1 = LayoutToWrapper(layout.UniformInset(SpaceUnit).Layout) 12 | Inset2 = LayoutToWrapper(layout.UniformInset(SpaceUnit * 2).Layout) 13 | Inset3 = LayoutToWrapper(layout.UniformInset(SpaceUnit * 3).Layout) 14 | Inset4 = LayoutToWrapper(layout.UniformInset(SpaceUnit * 4).Layout) 15 | Inset5 = LayoutToWrapper(layout.UniformInset(SpaceUnit * 5).Layout) 16 | Inset6 = LayoutToWrapper(layout.UniformInset(SpaceUnit * 6).Layout) 17 | ) 18 | 19 | // Spaces 20 | var ( 21 | WSpacer1 = layout.Spacer{Width: SpaceUnit}.Layout 22 | WSpacer2 = layout.Spacer{Width: SpaceUnit * 2}.Layout 23 | WSpacer3 = layout.Spacer{Width: SpaceUnit * 3}.Layout 24 | WSpacer4 = layout.Spacer{Width: SpaceUnit * 4}.Layout 25 | WSpacer5 = layout.Spacer{Width: SpaceUnit * 5}.Layout 26 | WSpacer6 = layout.Spacer{Width: SpaceUnit * 6}.Layout 27 | ) 28 | 29 | var ( 30 | HSpacer1 = layout.Spacer{Height: SpaceUnit}.Layout 31 | HSpacer2 = layout.Spacer{Height: SpaceUnit * 2}.Layout 32 | HSpacer3 = layout.Spacer{Height: SpaceUnit * 3}.Layout 33 | HSpacer4 = layout.Spacer{Height: SpaceUnit * 4}.Layout 34 | HSpacer5 = layout.Spacer{Height: SpaceUnit * 5}.Layout 35 | HSpacer6 = layout.Spacer{Height: SpaceUnit * 6}.Layout 36 | ) 37 | 38 | func Margin(t, r, b, l unit.Dp) Wrapper { 39 | return LayoutToWrapper(layout.Inset{Top: t, Right: r, Bottom: b, Left: l}.Layout) 40 | } 41 | -------------------------------------------------------------------------------- /ui/tabs.go: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | var ( 4 | SelectedTabBgColor = MixColor(ACCENT_COLOR_100, WHITE, 20) 5 | SelectedTabFgColor = ACCENT_COLOR_500 6 | ) 7 | 8 | type TabChild struct { 9 | Name string 10 | Panel W 11 | } 12 | 13 | type Tabs struct { 14 | List List 15 | Active string 16 | Clickable map[string]*Clickable 17 | } 18 | 19 | func (l *Tabs) Layout(tabs ...*TabChild) W { 20 | if l.Clickable == nil { 21 | l.Clickable = map[string]*Clickable{} 22 | } 23 | 24 | var panel = EmptyWidget 25 | for i, t := range tabs { 26 | active := l.Active == t.Name || l.Active == "" && i == 0 27 | if active { 28 | panel = t.Panel 29 | } 30 | } 31 | 32 | ele := func(c C, i int) D { 33 | t := tabs[i] 34 | var b *Clickable 35 | var ok bool 36 | if b, ok = l.Clickable[t.Name]; !ok { 37 | b = &Clickable{} 38 | l.Clickable[t.Name] = b 39 | } else if b.Clicked() { 40 | l.Active = t.Name 41 | } 42 | 43 | active := l.Active == t.Name || l.Active == "" && i == 0 44 | return Columns( 45 | Rigid(TabButton(b, active, t.Name)), 46 | Rigid(VR(1)), 47 | )(c) 48 | } 49 | 50 | return Rows( 51 | Rigid(HR(1)), 52 | Rigid(func(c C) D { return l.List.Layout(c, len(tabs), ele) }), 53 | Rigid(HR(1)), 54 | Flexed(1, panel), 55 | ) 56 | } 57 | 58 | func TabButton(cl *Clickable, active bool, l string) W { 59 | var b W 60 | if cl.Hovered() && !active { 61 | b = Background(CardColor, Inset1(Label(l))) 62 | } else if active { 63 | b = Background(SelectedTabBgColor, Wrap(Label(l), Inset1, TextColor(SelectedTabFgColor))) 64 | } else { 65 | b = Inset1(Label(l)) 66 | } 67 | 68 | return func(c C) D { 69 | return cl.Layout(c, b) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /ui/text.go: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import ( 4 | "gioui.org/font" 5 | "gioui.org/op" 6 | "gioui.org/op/paint" 7 | "gioui.org/widget" 8 | ) 9 | 10 | func Text(s string) W { 11 | return func(c C) D { 12 | tl := widget.Label{Alignment: Theme.TextAlignment, MaxLines: Theme.MaxLines} 13 | paint.ColorOp{Color: Theme.TextColor}.Add(c.Ops) 14 | return tl.Layout(c, Theme.FontFamily, font.Font{Weight: Theme.FontWeight}, Theme.FontSize, s, op.CallOp{}) 15 | } 16 | } 17 | 18 | var OneLine = MaxLines(1) 19 | 20 | func Label(s string) W { return OneLine(Text(s)) } 21 | 22 | var Bold = FontWeight(font.Bold) 23 | -------------------------------------------------------------------------------- /ui/theme.go: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import ( 4 | "image/color" 5 | 6 | "gioui.org/font" 7 | "gioui.org/text" 8 | ) 9 | 10 | type ThemeStyle struct { 11 | FontSize SP 12 | FontFamily *text.Shaper 13 | FontWeight font.Weight 14 | TextAlignment text.Alignment 15 | TextColor color.NRGBA 16 | MaxLines int 17 | } 18 | 19 | var Theme = ThemeStyle{ 20 | FontSize: 13, 21 | FontFamily: fontShaper, 22 | FontWeight: font.Normal, 23 | TextAlignment: text.Start, 24 | TextColor: BLACK_500, 25 | MaxLines: 0, 26 | } 27 | 28 | func FontSize(s SP) Wrapper { 29 | return func(w W) W { 30 | return func(c C) D { 31 | old := Theme.FontSize 32 | Theme.FontSize = s 33 | d := w(c) 34 | Theme.FontSize = old 35 | return d 36 | } 37 | } 38 | } 39 | 40 | func FontEnlarge(s float32) Wrapper { 41 | return FontSize(SP(s) * Theme.FontSize) 42 | } 43 | 44 | func Font(f *text.Shaper) Wrapper { 45 | return func(w W) W { 46 | return func(c C) D { 47 | old := Theme.FontFamily 48 | Theme.FontFamily = f 49 | d := w(c) 50 | Theme.FontFamily = old 51 | return d 52 | } 53 | } 54 | } 55 | 56 | func FontWeight(f font.Weight) Wrapper { 57 | return func(w W) W { 58 | return func(c C) D { 59 | old := Theme.FontWeight 60 | Theme.FontWeight = f 61 | d := w(c) 62 | Theme.FontWeight = old 63 | return d 64 | } 65 | } 66 | } 67 | 68 | func TextAlignment(a text.Alignment) Wrapper { 69 | return func(w W) W { 70 | return func(c C) D { 71 | old := Theme.TextAlignment 72 | Theme.TextAlignment = a 73 | d := w(c) 74 | Theme.TextAlignment = old 75 | return d 76 | } 77 | } 78 | } 79 | 80 | var AlignStart = TextAlignment(text.Start) 81 | var AlignMiddle = TextAlignment(text.Middle) 82 | var AlignEnd = TextAlignment(text.End) 83 | 84 | func TextColor(col color.NRGBA) Wrapper { 85 | return func(w W) W { 86 | return func(c C) D { 87 | old := Theme.TextColor 88 | Theme.TextColor = col 89 | d := w(c) 90 | Theme.TextColor = old 91 | return d 92 | } 93 | } 94 | } 95 | 96 | func MaxLines(i int) Wrapper { 97 | return func(w W) W { 98 | return func(c C) D { 99 | old := Theme.MaxLines 100 | Theme.MaxLines = i 101 | d := w(c) 102 | Theme.MaxLines = old 103 | return d 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /ui/toolbar.go: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | var ( 4 | ToolbarBgColor = SILVER_100 5 | ) 6 | 7 | func ToolbarButton(clickable *Clickable, icon W, desc string) W { 8 | bg := ToolbarBgColor 9 | hovered := clickable.Hovered() 10 | if hovered { 11 | bg = MixColor(ToolbarBgColor, BLACK_900, 80) 12 | } 13 | 14 | btn := func(c C) D { 15 | return clickable.Layout(c, 16 | Background(bg, 17 | Inset1(icon), 18 | ), 19 | ) 20 | } 21 | 22 | if hovered { 23 | btn = Tooltip(btn, desc) 24 | } 25 | 26 | return btn 27 | } 28 | -------------------------------------------------------------------------------- /ui/tooltip.go: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import ( 4 | "image" 5 | 6 | "gioui.org/op" 7 | ) 8 | 9 | var ( 10 | TooltipFgColor = WHITE 11 | TooltipBgColor = Alpha(BLACK_500, 242) 12 | ) 13 | 14 | func Tooltip(attachTo W, s string) W { 15 | return func(c C) D { 16 | attachToD := attachTo(c) 17 | tooltipMacro := op.Record(c.Ops) 18 | d := RoundedCorners( 19 | Background(TooltipBgColor, 20 | Inset1( 21 | TextColor(TooltipFgColor)(Label(s)), 22 | ), 23 | ), 24 | )(c) 25 | tooltipOp := tooltipMacro.Stop() 26 | 27 | macro := op.Record(c.Ops) 28 | trans := op.Offset(image.Pt(attachToD.Size.X/2-d.Size.X/2, attachToD.Size.Y+int(SpaceUnit/2))).Push(c.Ops) 29 | tooltipOp.Add(c.Ops) 30 | trans.Pop() 31 | op.Defer(c.Ops, macro.Stop()) 32 | 33 | return attachToD 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /ui/widgets.go: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import ( 4 | "image" 5 | 6 | "gioui.org/f32" 7 | "gioui.org/font/gofont" 8 | "gioui.org/layout" 9 | "gioui.org/text" 10 | "gioui.org/unit" 11 | "gioui.org/widget/material" 12 | ) 13 | 14 | type ( 15 | W = layout.Widget 16 | C = layout.Context 17 | D = layout.Dimensions 18 | P = image.Point 19 | DP = unit.Dp 20 | SP = unit.Sp 21 | Wrapper = func(W) W 22 | List = layout.List 23 | ) 24 | 25 | var ( 26 | Pt = f32.Pt 27 | SpaceUnit DP = 8 28 | BorderSize DP = 1 29 | 30 | fonts = gofont.Collection() 31 | fontShaper = text.NewShaper(fonts) 32 | th = material.NewTheme(fonts) 33 | ) 34 | 35 | func init() { 36 | th.TextSize = Theme.FontSize 37 | th.Fg = Theme.TextColor 38 | } 39 | 40 | func EmptyWidget(c C) D { return D{} } 41 | 42 | func Wrap(w W, wrappers ...Wrapper) W { 43 | for i := len(wrappers) - 1; i >= 0; i-- { 44 | w = wrappers[i](w) 45 | } 46 | 47 | return w 48 | } 49 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os/exec" 6 | "runtime" 7 | ) 8 | 9 | func OpenBrowser(url string) (err error) { 10 | switch runtime.GOOS { 11 | case "linux": 12 | return exec.Command("xdg-open", url).Start() 13 | case "windows": 14 | return exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start() 15 | case "darwin": 16 | return exec.Command("open", url).Start() 17 | default: 18 | return fmt.Errorf("unsupported platform") 19 | } 20 | } 21 | 22 | func ByteCountToDecimal(b uint64) string { 23 | const unit = 1000 24 | if b < unit { 25 | return fmt.Sprintf("%d B", b) 26 | } 27 | div, exp := int64(unit), 0 28 | for n := b / unit; n >= unit; n /= unit { 29 | div *= unit 30 | exp++ 31 | } 32 | return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "kMGTPE"[exp]) 33 | } 34 | --------------------------------------------------------------------------------