├── .gitignore ├── LICENSE ├── NOTICE ├── README.md ├── breakpointspage.go ├── client.go ├── cmd.go ├── codepage.go ├── config.go ├── config.yaml ├── go.mod ├── go.sum ├── goroutinepage.go ├── linecolumn.go ├── main.go ├── nav └── nav.go ├── pageview.go ├── perftextview.go ├── preview.gif ├── stackpage.go ├── tui.go └── varspage.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything 2 | * 3 | 4 | # But not these files... 5 | !/.gitignore 6 | 7 | !*.go 8 | !go.sum 9 | !go.mod 10 | 11 | !README.md 12 | !LICENSE 13 | !NOTICE 14 | 15 | !preview.gif 16 | 17 | !config.yaml 18 | 19 | # !Makefile 20 | 21 | # ...even if they are in subdirectories 22 | !*/ 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2022 Salle Helevä 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dlv-tui 2 |

3 | 4 |

5 | 6 | ***dlv-tui*** is a terminal user interface for the [delve debugger](https://github.com/go-delve/delve). Made for Go developers who prefer using terminal-only tools in their workflow. The goal is to provide all functionality of the delve cli-debugger, wrapped in a TUI. 7 | 8 | ## Usage 9 | 10 | The client supports debugging by running an excecutable or by attaching to an existing process. 11 | The debug target is the first argument, after which the following options can be provided: 12 | 13 | - `-attach` - If enabled, attach debugger to process. Interpret first argument as PID. 14 | - `-port` - The port dlv rpc server will listen to. (default "8181") 15 | - `-logfile` - Path to the log file. (default "$XDG_DATA_HOME/dlvtui.log") 16 | 17 | ## Configuration 18 | 19 | Keybindings, colors and behavior of the client are customizable via a yaml configuration file located at `$XDG_CONFIG_HOME/dlvtui/config.yaml`. 20 | 21 | Refer to `config.yaml` for an example configuration. 22 | 23 | To enable syntax highlighting, set the option `syntaxhighlighter` to a command that outputs to stdout. 24 | For example `bat -p -f --paging=never` 25 | -------------------------------------------------------------------------------- /breakpointspage.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/ilmari-h/dlvtui/nav" 5 | "fmt" 6 | "sort" 7 | 8 | "github.com/gdamore/tcell/v2" 9 | "github.com/rivo/tview" 10 | ) 11 | 12 | type BreakpointsPage struct { 13 | preRenderSelection *nav.UiBreakpoint // Id of selected breakpoint before rerender. 14 | commandHandler *CommandHandler 15 | treeView *tview.TreeView 16 | widget *tview.Frame 17 | fileList map[string]*tview.TreeNode 18 | } 19 | 20 | func NewBreakpointsPage() *BreakpointsPage { 21 | root := tview.NewTreeNode("."). 22 | SetColor(tcell.ColorDefault) 23 | 24 | treeView := tview.NewTreeView(). 25 | SetRoot(root) 26 | 27 | treeView.SetBackgroundColor(tcell.ColorDefault) 28 | treeView.SetInputCapture(listInputCaptureC) 29 | 30 | pageFrame := tview.NewFrame(treeView). 31 | SetBorders(0, 0, 0, 0, 0, 0). 32 | AddText(fmt.Sprintf("[%s::b]Breakpoints:", iToColorS(gConfig.Colors.HeaderFg)), 33 | true, 34 | tview.AlignLeft, 35 | tcell.ColorWhite, 36 | ) 37 | pageFrame.SetBackgroundColor(tcell.ColorDefault) 38 | treeView.SetCurrentNode(root) 39 | bp := BreakpointsPage{ 40 | treeView: treeView, 41 | widget: pageFrame, 42 | } 43 | return &bp 44 | } 45 | 46 | func (page *BreakpointsPage) SetCommandHandler(ch *CommandHandler) { 47 | page.commandHandler = ch 48 | } 49 | 50 | func (page *BreakpointsPage) RenderBreakpoints(bps []*nav.UiBreakpoint) { 51 | sort.SliceStable(bps, func(i, j int) bool { 52 | return bps[i].Line < bps[j].Line || bps[i].File < bps[j].File 53 | }) 54 | page.fileList = make(map[string]*tview.TreeNode) 55 | rootNode := page.treeView.GetRoot() 56 | rootNode.ClearChildren() 57 | 58 | var lastSelectedNode *tview.TreeNode = nil 59 | 60 | for _, bp := range bps { 61 | if bp.ID < 0 { 62 | continue 63 | } 64 | fileNode, ok := page.fileList[bp.File] 65 | if !ok { 66 | fileNode = tview.NewTreeNode(fmt.Sprintf("[%s::b]%s", 67 | iToColorS(gConfig.Colors.ListHeaderFg), 68 | bp.File, 69 | )). 70 | SetSelectable(true) 71 | rootNode.AddChild(fileNode) 72 | page.fileList[bp.File] = fileNode 73 | fileNode.SetSelectedFunc(func() { 74 | fileNode.SetExpanded(!fileNode.IsExpanded()) 75 | }) 76 | fileNode.SetColor(tcell.ColorBlack) 77 | } 78 | 79 | bpNode := tview.NewTreeNode(fmt.Sprintf("[%s]%s [%s]%s[%s]:%d", 80 | iToColorS(gConfig.Colors.BpFg), 81 | gConfig.Icons.Bp, 82 | iToColorS(gConfig.Colors.VarNameFg), 83 | bp.FunctionName, 84 | iToColorS(gConfig.Colors.LineFg), 85 | bp.Line, 86 | )). 87 | SetSelectable(true) 88 | 89 | if page.preRenderSelection != nil && bp.Line == page.preRenderSelection.Line && bp.File == page.preRenderSelection.File { 90 | lastSelectedNode = bpNode 91 | } 92 | 93 | bpNode.SetColor(tcell.ColorBlack) 94 | 95 | current := bp.Line == page.commandHandler.view.navState.CurrentDebuggerPos.Line && 96 | bp.File == page.commandHandler.view.navState.CurrentDebuggerPos.File 97 | if current { 98 | bpNode.SetText(fmt.Sprintf("[%s]%s [%s::b]%s[%s]:%d", 99 | iToColorS(gConfig.Colors.BpActiveFg), 100 | gConfig.Icons.BpActive, 101 | iToColorS(gConfig.Colors.VarTypeFg), 102 | bp.FunctionName, 103 | iToColorS(gConfig.Colors.LineFg), 104 | bp.Line, 105 | )) 106 | } else if bp.Disabled { 107 | bpNode.SetText(fmt.Sprintf("[%s]%s [%s]%s[%s]:%d", 108 | iToColorS(gConfig.Colors.BpFg), 109 | gConfig.Icons.BpDisabled, 110 | iToColorS(gConfig.Colors.VarNameFg), 111 | bp.FunctionName, 112 | iToColorS(gConfig.Colors.LineFg), 113 | bp.Line, 114 | )) 115 | } 116 | 117 | bpNode.SetReference(bp) 118 | bpNode.SetSelectable(true) 119 | bpNode.SetSelectedFunc(func() { 120 | ref := bpNode.GetReference().(*nav.UiBreakpoint) 121 | page.commandHandler.RunCommand(&OpenFile{ 122 | File: ref.File, 123 | AtLine: ref.Line - 1, 124 | }) 125 | }) 126 | fileNode.AddChild(bpNode) 127 | } 128 | if lastSelectedNode != nil { 129 | page.treeView.SetCurrentNode(lastSelectedNode) 130 | } 131 | } 132 | 133 | func (page *BreakpointsPage) GetWidget() tview.Primitive { 134 | return page.widget 135 | } 136 | 137 | func (page *BreakpointsPage) GetName() string { 138 | return "breakpoints" 139 | } 140 | 141 | func (page *BreakpointsPage) HandleKeyEvent(event *tcell.EventKey) *tcell.EventKey { 142 | if keyPressed(event, gConfig.Keys.ClearBreakpoint) { 143 | selectedNode := page.treeView.GetCurrentNode() 144 | if selectedNode.GetReference() == nil { 145 | return nil 146 | } 147 | selectedBp := selectedNode.GetReference().(*nav.UiBreakpoint) 148 | page.fileList[selectedBp.File].RemoveChild(selectedNode) 149 | 150 | if selectedBp.Disabled { 151 | page.commandHandler.RunCommand(&ClearBreakpoint{selectedBp, false, selectedBp}) 152 | } else { 153 | page.commandHandler.RunCommand(&ClearBreakpoint{selectedBp, false, nil}) 154 | } 155 | page.treeView.SetCurrentNode(page.fileList[selectedBp.File]) 156 | return nil 157 | } else if keyPressed(event, gConfig.Keys.ToggleBreakpoint) { 158 | selectedNode := page.treeView.GetCurrentNode() 159 | if selectedNode.GetReference() == nil { 160 | return nil 161 | } 162 | selectedBp := selectedNode.GetReference().(*nav.UiBreakpoint) 163 | page.preRenderSelection = selectedBp 164 | if !selectedBp.Disabled { 165 | page.commandHandler.RunCommand(&ClearBreakpoint{selectedBp, true, nil}) 166 | } else { 167 | page.commandHandler.RunCommand(&CreateBreakpoint{selectedBp.Line, selectedBp.File}) 168 | } 169 | } 170 | page.treeView.InputHandler()(event, func(p tview.Primitive) {}) 171 | return nil 172 | } 173 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net" 5 | "time" 6 | 7 | "github.com/go-delve/delve/service/rpc2" 8 | log "github.com/sirupsen/logrus" 9 | ) 10 | 11 | func NewClient(addr string, clientChan chan *rpc2.RPCClient) { 12 | 13 | attempts := 0 14 | for { 15 | conn, _ := net.Dial("tcp", addr) 16 | time.Sleep(time.Second / 10) 17 | if conn != nil { 18 | log.Print("Client connection established.") 19 | clientChan <- rpc2.NewClientFromConn(conn) 20 | break 21 | } 22 | attempts++ 23 | if attempts >= 50 { 24 | log.Fatalf("Failed to connect client to dlv backend after %d attempts!", attempts) 25 | } 26 | log.Printf("Client connection to %s refused, retry number %d.", addr, attempts) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /cmd.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "github.com/ilmari-h/dlvtui/nav" 6 | "fmt" 7 | "os" 8 | "os/exec" 9 | "path/filepath" 10 | "strings" 11 | 12 | "github.com/go-delve/delve/service/api" 13 | "github.com/go-delve/delve/service/rpc2" 14 | "github.com/rivo/tview" 15 | log "github.com/sirupsen/logrus" 16 | ) 17 | 18 | // TODO: make configurable 19 | var defaultConfig = api.LoadConfig{ 20 | FollowPointers: true, 21 | MaxVariableRecurse: 5, 22 | MaxStringLen: 999, 23 | MaxArrayValues: 999, 24 | MaxStructFields: -1, 25 | } 26 | 27 | // Read file from disk. 28 | func loadFile(path string, fileChan chan *nav.File) { 29 | 30 | var scanner *bufio.Scanner = nil 31 | if gConfig.SyntaxHighlighter != "" { 32 | commandArr := strings.Fields(gConfig.SyntaxHighlighter) 33 | commandArr = append(commandArr, path) 34 | cmd := exec.Command(commandArr[0], commandArr[1:]...) 35 | pipe, _ := cmd.StdoutPipe() 36 | cmd.Stderr = cmd.Stdout 37 | scanner = bufio.NewScanner(pipe) 38 | err := cmd.Start() 39 | if err != nil { 40 | log.Fatal(err.Error()) 41 | } 42 | } else { 43 | 44 | f, err := os.Open(path) 45 | defer f.Close() 46 | if err != nil { 47 | log.Printf("Error loading file %s: %s", path, err) 48 | return 49 | } 50 | scanner = bufio.NewScanner(f) 51 | } 52 | 53 | buf := "" 54 | lineIndex := 0 55 | lineIndices := []int{0} 56 | for scanner.Scan() { 57 | line := tview.TranslateANSI(scanner.Text()) 58 | buf += line + "\n" 59 | lineIndex += len(line) + 1 60 | lineIndices = append(lineIndices, lineIndex) 61 | } 62 | absPath, _ := filepath.Abs(path) 63 | file := nav.File{ 64 | Name: path, 65 | Path: absPath, 66 | Content: buf, 67 | LineCount: len(lineIndices), 68 | LineIndices: lineIndices, 69 | } 70 | log.Printf("Loaded file: %s", absPath) 71 | fileChan <- &file 72 | } 73 | 74 | var AvailableCommands = []string{ 75 | "open", 76 | "bs", "breakpoints", 77 | "stack", 78 | "goroutines", 79 | "locals", 80 | "code", 81 | "restart", 82 | "c", "continue", 83 | "n", "next", 84 | "s", "step", 85 | "so", "stepout", 86 | "q", "quit", 87 | } 88 | 89 | func StringToLineCommand(s string, args []string) LineCommand { 90 | log.Printf("Parsed command '%s %v'", s, args) 91 | switch s { 92 | case "open": 93 | return &OpenFile{ 94 | File: args[0], 95 | } 96 | case "bs", "breakpoints": 97 | return &OpenPage{PageIndex: IBreakPointsPage} 98 | case "stack": 99 | return &OpenPage{PageIndex: IStackPage} 100 | case "goroutines": 101 | return &OpenPage{PageIndex: IGoroutinePage} 102 | case "locals": 103 | return &OpenPage{PageIndex: IVarsPage} 104 | case "code": 105 | return &OpenPage{PageIndex: ICodePage} 106 | case "restart": 107 | return &Restart{} 108 | case "c", "continue": 109 | return &Continue{} 110 | case "n", "next": 111 | return &Next{} 112 | case "s", "step": 113 | return &Step{} 114 | case "so", "stepout": 115 | return &StepOut{} 116 | case "q", "quit": 117 | return &Quit{} 118 | } 119 | return nil 120 | } 121 | 122 | type CommandHandler struct { 123 | view *View 124 | app *tview.Application 125 | rpcClient *rpc2.RPCClient 126 | } 127 | 128 | type LineCommand interface { 129 | run(*View, *tview.Application, *rpc2.RPCClient) 130 | } 131 | 132 | func NewCommandHandler(view *View, app *tview.Application, client *rpc2.RPCClient) *CommandHandler { 133 | return &CommandHandler{ 134 | view: view, 135 | app: app, 136 | rpcClient: client, 137 | } 138 | } 139 | 140 | func (commandHandler *CommandHandler) RunCommand(cmd LineCommand) { 141 | go cmd.run(commandHandler.view, commandHandler.app, commandHandler.rpcClient) 142 | } 143 | 144 | func applyPrefix(pfx string, arr []string) []string { 145 | res := []string{} 146 | for _, v := range arr { 147 | res = append(res, pfx+v) 148 | } 149 | return res 150 | } 151 | 152 | func substractPrefix(pfx string, arr []string) []string { 153 | res := []string{} 154 | for _, v := range arr { 155 | if strings.HasPrefix(v, pfx) { 156 | res = append(res, v[len(pfx)+1:]) 157 | } 158 | } 159 | return res 160 | } 161 | 162 | func filter(f string, arr []string) []string { 163 | res := []string{} 164 | for _, v := range arr { 165 | if strings.HasPrefix(v, f) { 166 | res = append(res, v) 167 | } 168 | } 169 | return res 170 | } 171 | 172 | func (commandHandler *CommandHandler) GetSuggestions(input string) []string { 173 | allArgs := strings.Fields(input) 174 | s := "" 175 | if len(allArgs) > 0 { 176 | s = allArgs[0] 177 | } 178 | switch s { 179 | case "open": 180 | opts := applyPrefix(s+" ", 181 | substractPrefix( 182 | commandHandler.view.navState.ProjectPath, 183 | commandHandler.view.navState.SourceFiles, 184 | ), 185 | ) 186 | return filter(input, opts) 187 | case "c", "continue": 188 | break 189 | } 190 | return filter(s, AvailableCommands) 191 | } 192 | 193 | type CreateBreakpoint struct { 194 | Line int 195 | File string 196 | } 197 | 198 | func (cmd *CreateBreakpoint) run(view *View, app *tview.Application, client *rpc2.RPCClient) { 199 | 200 | log.Printf("Creating bp in %s at line %d", cmd.File, cmd.Line) 201 | 202 | res, err := client.CreateBreakpoint(&api.Breakpoint{ 203 | File: cmd.File, 204 | Line: cmd.Line, 205 | Goroutine: true, 206 | LoadLocals: &defaultConfig, 207 | LoadArgs: &defaultConfig, 208 | }) 209 | 210 | if err != nil { 211 | log.Printf("rpc error: %s", err.Error()) 212 | view.showNotification(err.Error(), true) 213 | return 214 | } 215 | view.breakpointChan <- &nav.UiBreakpoint{false, res} 216 | } 217 | 218 | type OpenPage struct { 219 | PageIndex PageIndex 220 | } 221 | 222 | func (cmd *OpenPage) run(view *View, app *tview.Application, client *rpc2.RPCClient) { 223 | view.pageView.SwitchToPage(cmd.PageIndex) 224 | } 225 | 226 | type OpenFile struct { 227 | File string 228 | AtLine int 229 | } 230 | 231 | func (cmd *OpenFile) run(view *View, app *tview.Application, client *rpc2.RPCClient) { 232 | 233 | // Check cache or open new file. 234 | absPath := cmd.File 235 | if !filepath.IsAbs(cmd.File) { 236 | absPath = filepath.Join(view.navState.ProjectPath, cmd.File) 237 | } 238 | view.navState.CurrentLines[absPath] = cmd.AtLine 239 | 240 | // If there's a stack frame for current file at current line, select it. 241 | for _, sf := range view.navState.CurrentStack { 242 | if sf.File == absPath && sf.Line == cmd.AtLine+1 { 243 | view.navState.CurrentStackFrame = &sf 244 | break 245 | } else { 246 | view.navState.CurrentStackFrame = nil 247 | } 248 | } 249 | 250 | if val, ok := view.navState.FileCache[absPath]; ok { 251 | view.fileChan <- val 252 | return 253 | } 254 | go loadFile(absPath, view.fileChan) 255 | } 256 | 257 | type ClearBreakpoint struct { 258 | Breakpoint *nav.UiBreakpoint 259 | Disable bool 260 | OfflineBp *nav.UiBreakpoint 261 | } 262 | 263 | func (cmd *ClearBreakpoint) run(view *View, app *tview.Application, client *rpc2.RPCClient) { 264 | 265 | // If removing breakpoint that doesn't exist in the backend, don't do an rpc call. 266 | if cmd.OfflineBp != nil { 267 | cmd.OfflineBp.ID = -1 // Mark as deleted 268 | view.breakpointChan <- cmd.OfflineBp 269 | return 270 | } 271 | 272 | res, err := client.ClearBreakpoint(cmd.Breakpoint.ID) 273 | if err != nil { 274 | log.Printf("rpc error: %s", err.Error()) 275 | view.showNotification(err.Error(), true) 276 | return 277 | } 278 | if !cmd.Disable { 279 | res.ID = -1 // Mark as deleted 280 | } 281 | view.breakpointChan <- &nav.UiBreakpoint{cmd.Disable, res} 282 | } 283 | 284 | type Quit struct { 285 | } 286 | 287 | func (cmd *Quit) run(view *View, app *tview.Application, client *rpc2.RPCClient) { 288 | app.Stop() 289 | } 290 | 291 | type Continue struct { 292 | } 293 | 294 | func (cmd *Continue) run(view *View, app *tview.Application, client *rpc2.RPCClient) { 295 | 296 | view.renderPendingContinue() 297 | view.SetBlocking(true) 298 | 299 | res := <-client.Continue() 300 | view.SetBlocking(false) 301 | 302 | debuggerMoveCommand(view, app, client, res) 303 | } 304 | 305 | type GetBreakpoints struct { 306 | } 307 | 308 | func (cmd *GetBreakpoints) run(view *View, app *tview.Application, client *rpc2.RPCClient) { 309 | bps, err := client.ListBreakpoints(true) 310 | if err != nil { 311 | log.Printf("rpc error: %s", err.Error()) 312 | view.showNotification(err.Error(), true) 313 | return 314 | } 315 | for i := range bps { 316 | view.breakpointChan <- &nav.UiBreakpoint{false, bps[i]} 317 | } 318 | } 319 | 320 | type Next struct { 321 | } 322 | 323 | func (cmd *Next) run(view *View, app *tview.Application, client *rpc2.RPCClient) { 324 | 325 | nres, nerr := client.Next() 326 | 327 | if nerr != nil { 328 | log.Printf("rpc error: %s", nerr.Error()) 329 | return 330 | } 331 | 332 | if nres.Exited { 333 | msg := fmt.Sprintf("Program has finished with exit status %d.", nres.ExitStatus) 334 | log.Print(msg) 335 | view.showNotification(msg, false) 336 | return 337 | } 338 | 339 | sres, serr := client.Stacktrace(nres.CurrentThread.GoroutineID, 5, api.StacktraceSimple, &defaultConfig) 340 | 341 | if serr != nil { 342 | log.Printf("rpc error: %s", serr.Error()) 343 | return 344 | } 345 | 346 | // Run ListGoroutines-command when ever new Goroutines may have been started. 347 | lg := ListGoroutines{} 348 | go lg.run(view, app, client) 349 | 350 | view.dbgMoveChan <- &DebuggerMove{nres, sres} 351 | } 352 | 353 | func debuggerMoveCommand(view *View, app *tview.Application, client *rpc2.RPCClient, cmdRes *api.DebuggerState) { 354 | 355 | if cmdRes.Exited { 356 | view.notifyProgramEnded(cmdRes.ExitStatus) 357 | return 358 | } 359 | 360 | sres, serr := client.Stacktrace(cmdRes.CurrentThread.GoroutineID, 5, api.StacktraceSimple, &defaultConfig) 361 | 362 | if serr != nil { 363 | log.Printf("rpc error: %s", serr.Error()) 364 | return 365 | } 366 | 367 | // If file about to move has not been loaded, load it now. 368 | if view.navState.FileCache[cmdRes.CurrentThread.File] == nil { 369 | ch := make(chan *nav.File) 370 | go loadFile(cmdRes.CurrentThread.File, ch) 371 | 372 | // Block until file loaded so it can be opened. 373 | file := <-ch 374 | view.OpenFile(file, cmdRes.CurrentThread.Line-1) 375 | } 376 | view.dbgMoveChan <- &DebuggerMove{cmdRes, sres} 377 | 378 | } 379 | 380 | type Step struct { 381 | } 382 | 383 | func (cmd *Step) run(view *View, app *tview.Application, client *rpc2.RPCClient) { 384 | nres, nerr := client.Step() 385 | 386 | if nerr != nil { 387 | log.Printf("rpc error: %s", nerr.Error()) 388 | return 389 | } 390 | 391 | debuggerMoveCommand(view, app, client, nres) 392 | } 393 | 394 | type StepOut struct { 395 | } 396 | 397 | func (cmd *StepOut) run(view *View, app *tview.Application, client *rpc2.RPCClient) { 398 | nres, nerr := client.StepOut() 399 | 400 | if nerr != nil { 401 | log.Printf("rpc error: %s", nerr.Error()) 402 | return 403 | } 404 | 405 | debuggerMoveCommand(view, app, client, nres) 406 | } 407 | 408 | type ListGoroutines struct { 409 | } 410 | 411 | func (cmd *ListGoroutines) run(view *View, app *tview.Application, client *rpc2.RPCClient) { 412 | lres, _, lerr := client.ListGoroutines(0, 99) 413 | if lerr != nil { 414 | log.Printf("rpc error: %s", lerr.Error()) 415 | return 416 | } 417 | log.Printf("Fetched active goroutines: %v", lres) 418 | view.goroutineChan <- lres 419 | } 420 | 421 | type SwitchGoroutines struct { 422 | Id int 423 | } 424 | 425 | func (cmd *SwitchGoroutines) run(view *View, app *tview.Application, client *rpc2.RPCClient) { 426 | log.Printf("Switching to goroutine %d.", cmd.Id) 427 | res, err := client.SwitchGoroutine(cmd.Id) 428 | if err != nil { 429 | log.Printf("rpc error: %s", err.Error()) 430 | view.showNotification(err.Error(), true) 431 | return 432 | } 433 | sres, serr := client.Stacktrace(res.CurrentThread.GoroutineID, 5, api.StacktraceSimple, &defaultConfig) 434 | 435 | if serr != nil { 436 | log.Printf("rpc error: %s", serr.Error()) 437 | return 438 | } 439 | 440 | log.Printf("Switched to goroutine %d.", res.Pid) 441 | 442 | view.dbgMoveChan <- &DebuggerMove{res, sres} 443 | } 444 | 445 | type Restart struct { 446 | } 447 | 448 | func (cmd *Restart) run(view *View, app *tview.Application, client *rpc2.RPCClient) { 449 | _, err := client.Restart(false) 450 | if err != nil { 451 | log.Printf("rpc error while restarting program: %s", err.Error()) 452 | return 453 | } 454 | view.SetBlocking(false) 455 | } 456 | -------------------------------------------------------------------------------- /codepage.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/ilmari-h/dlvtui/nav" 5 | "fmt" 6 | 7 | "github.com/gdamore/tcell/v2" 8 | "github.com/rivo/tview" 9 | ) 10 | 11 | type CodePage struct { 12 | pageFrame *tview.Frame 13 | flex *tview.Flex 14 | commandHandler *CommandHandler 15 | navState *nav.Nav 16 | perfTextView *PerfTextView 17 | lineColumn *LineColumn 18 | } 19 | 20 | func NewCodePage(app *tview.Application, navState *nav.Nav) *CodePage { 21 | 22 | textView := NewPerfTextView() 23 | 24 | lineColumn := NewLineColumn(navState) 25 | lineColumn.textView. 26 | SetRegions(true). 27 | SetDynamicColors(true). 28 | SetChangedFunc(func() { 29 | app.Draw() 30 | }). 31 | SetBackgroundColor(tcell.ColorDefault) 32 | 33 | textView.SetLineColumn(lineColumn) 34 | textView.SetDynamicColors(true) 35 | textView.SetBackgroundColor(tcell.ColorDefault) 36 | textView.SetWrap(false) 37 | 38 | flex := tview.NewFlex().SetDirection(tview.FlexColumn). 39 | AddItem(lineColumn.textView, 1, 1, false). 40 | AddItem(textView, 0, 1, false) 41 | 42 | pageFrame := tview.NewFrame(flex). 43 | SetBorders(0, 0, 0, 0, 0, 0). 44 | AddText("[::b]No file loaded.", true, tview.AlignLeft, iToColorTcell(gConfig.Colors.HeaderFg)) 45 | pageFrame.SetBackgroundColor(tcell.ColorDefault) 46 | 47 | return &CodePage{ 48 | pageFrame: pageFrame, 49 | navState: navState, 50 | flex: flex, 51 | perfTextView: textView, 52 | lineColumn: lineColumn, 53 | } 54 | } 55 | 56 | func (page *CodePage) OpenFile(file *nav.File, atLine int) { 57 | 58 | // Reset header. 59 | page.pageFrame.Clear() 60 | page.pageFrame.AddText(fmt.Sprintf("[::b]%s", file.Path), true, tview.AlignLeft, iToColorTcell(gConfig.Colors.CodeHeaderFg)) 61 | 62 | // Redraw flex view with new column width. 63 | mv := getMaxLineColWidth(file.LineCount) 64 | page.lineColumn.SetWidth(mv) 65 | page.flex.ResizeItem(page.lineColumn.GetTextView(), mv, 1) 66 | 67 | page.perfTextView.SetTextP(file.Content, file.LineIndices) 68 | page.perfTextView.JumpTo(atLine) 69 | } 70 | 71 | func (page *CodePage) GetName() string { 72 | return "code" 73 | } 74 | 75 | func (page *CodePage) SetCommandHandler(cmdHdlr *CommandHandler) { 76 | page.commandHandler = cmdHdlr 77 | } 78 | 79 | func (page *CodePage) GetWidget() tview.Primitive { 80 | return page.pageFrame 81 | } 82 | 83 | func (page *CodePage) HandleKeyEvent(event *tcell.EventKey) *tcell.EventKey { 84 | if keyPressed(event, gConfig.Keys.LineDown) { 85 | if page.navState.CurrentFile != nil { 86 | line := page.navState.SetLine(page.navState.CurrentLine() + 1) 87 | page.perfTextView.scrollTo(line, false) 88 | } 89 | return nil 90 | } 91 | if keyPressed(event, gConfig.Keys.LineUp) { 92 | if page.navState.CurrentFile != nil { 93 | line := page.navState.SetLine(page.navState.CurrentLine() - 1) 94 | page.perfTextView.scrollTo(line, false) 95 | } 96 | return nil 97 | } 98 | if keyPressed(event, gConfig.Keys.PageTop) { 99 | line := page.navState.SetLine(0) 100 | page.perfTextView.scrollTo(line, true) 101 | return nil 102 | } 103 | if keyPressed(event, gConfig.Keys.PageEnd) { 104 | line := page.navState.SetLine(page.navState.CurrentFile.LineCount - 2) 105 | page.perfTextView.scrollTo(line, true) 106 | return nil 107 | } 108 | if keyPressed(event, gConfig.Keys.Breakpoint) { 109 | bps := page.navState.Breakpoints 110 | if _, ok := bps[page.navState.CurrentFile.Path][page.navState.CurrentLine()+1]; !ok { 111 | page.commandHandler.RunCommand(&CreateBreakpoint{ 112 | Line: page.navState.CurrentLine() + 1, // Using 1 based indices on the backend. 113 | File: page.navState.CurrentFile.Path, 114 | }) 115 | } 116 | return nil 117 | } 118 | if keyPressed(event, gConfig.Keys.ToggleBreakpoint) { 119 | bps := page.navState.Breakpoints 120 | // If breakpoint on this line, remove it. 121 | if len(bps[page.navState.CurrentFile.Path]) != 0 { // Using 1 based indices on the backend. 122 | if bp, ok := bps[page.navState.CurrentFile.Path][page.navState.CurrentLine()+1]; ok { 123 | if bp.Disabled { 124 | page.commandHandler.RunCommand(&CreateBreakpoint{ 125 | Line: page.navState.CurrentLine() + 1, // Using 1 based indices on the backend. 126 | File: page.navState.CurrentFile.Path, 127 | }) 128 | } else { 129 | page.commandHandler.RunCommand(&ClearBreakpoint{bp, true, nil}) 130 | } 131 | } 132 | } 133 | return nil 134 | } 135 | if keyPressed(event, gConfig.Keys.ClearBreakpoint) { 136 | bps := page.navState.Breakpoints 137 | // If breakpoint on this line, remove it. 138 | if len(bps[page.navState.CurrentFile.Path]) != 0 { // Using 1 based indices on the backend. 139 | if bp, ok := bps[page.navState.CurrentFile.Path][page.navState.CurrentLine()+1]; ok { 140 | if bp.Disabled { 141 | page.commandHandler.RunCommand(&ClearBreakpoint{bp, false, bp}) 142 | } else { 143 | page.commandHandler.RunCommand(&ClearBreakpoint{bp, false, nil}) 144 | } 145 | return nil 146 | } 147 | } 148 | } 149 | return event // Propagate. 150 | } 151 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | 8 | "github.com/gdamore/tcell/v2" 9 | log "github.com/sirupsen/logrus" 10 | "github.com/spf13/viper" 11 | ) 12 | 13 | var strColors = []string{ 14 | "black", // 0 15 | "maroon", // 1 16 | "green", // 2 17 | "olive", // 3 18 | "navy", // 4 19 | "purple", // 5 20 | "teal", // 6 21 | "silver", // 7 22 | "gray", // 8 23 | "red", // 9 24 | "lime", // 10 25 | "yellow", // 11 26 | "blue", // 12 27 | "fuchsia", // 13 28 | "aqua", // 14 29 | "white", // 15 30 | "aliceblue", 31 | "antiquewhite", 32 | "aquamarine", 33 | "azure", 34 | "beige", 35 | "bisque", 36 | "blanchedalmond", 37 | "blueviolet", 38 | "brown", 39 | "burlywood", 40 | "cadetblue", 41 | "chartreuse", 42 | "chocolate", 43 | "coral", 44 | "cornflowerblue", 45 | "cornsilk", 46 | "crimson", 47 | "darkblue", 48 | "darkcyan", 49 | "darkgoldenrod", 50 | "darkgray", 51 | "darkgreen", 52 | "darkkhaki", 53 | "darkmagenta", 54 | "darkolivegreen", 55 | "darkorange", 56 | "darkorchid", 57 | "darkred", 58 | "darksalmon", 59 | "darkseagreen", 60 | "darkslateblue", 61 | "darkslategray", 62 | "darkturquoise", 63 | "darkviolet", 64 | "deeppink", 65 | "deepskyblue", 66 | "dimgray", 67 | "dodgerblue", 68 | "firebrick", 69 | "floralwhite", 70 | "forestgreen", 71 | "gainsboro", 72 | "ghostwhite", 73 | "gold", 74 | "goldenrod", 75 | "greenyellow", 76 | "honeydew", 77 | "hotpink", 78 | "indianred", 79 | "indigo", 80 | "ivory", 81 | "khaki", 82 | "lavender", 83 | "lavenderblush", 84 | "lawngreen", 85 | "lemonchiffon", 86 | "lightblue", 87 | "lightcoral", 88 | "lightcyan", 89 | "lightgoldenrodyellow", 90 | "lightgray", 91 | "lightgreen", 92 | "lightpink", 93 | "lightsalmon", 94 | "lightseagreen", 95 | "lightskyblue", 96 | "lightslategray", 97 | "lightsteelblue", 98 | "lightyellow", 99 | "limegreen", 100 | "linen", 101 | "mediumaquamarine", 102 | "mediumblue", 103 | "mediumorchid", 104 | "mediumpurple", 105 | "mediumseagreen", 106 | "mediumslateblue", 107 | "mediumspringgreen", 108 | "mediumturquoise", 109 | "mediumvioletred", 110 | "midnightblue", 111 | "mintcream", 112 | "mistyrose", 113 | "moccasin", 114 | "navajowhite", 115 | "oldlace", 116 | "olivedrab", 117 | "orange", 118 | "orangered", 119 | "orchid", 120 | "palegoldenrod", 121 | "palegreen", 122 | "paleturquoise", 123 | "palevioletred", 124 | "papayawhip", 125 | "peachpuff", 126 | "peru", 127 | "pink", 128 | "plum", 129 | "powderblue", 130 | "rebeccapurple", 131 | "rosybrown", 132 | "royalblue", 133 | "saddlebrown", 134 | "salmon", 135 | "sandybrown", 136 | "seagreen", 137 | "seashell", 138 | "sienna", 139 | "skyblue", 140 | "slateblue", 141 | "slategray", 142 | "snow", 143 | "springgreen", 144 | "steelblue", 145 | "tan", 146 | "thistle", 147 | "tomato", 148 | "turquoise", 149 | "violet", 150 | "wheat", 151 | "whitesmoke", 152 | "yellowgreen", 153 | "grey", 154 | "dimgrey", 155 | "darkgrey", 156 | "darkslategrey", 157 | "lightgrey", 158 | "lightslategrey", 159 | "slategrey", 160 | } 161 | 162 | type Keys struct { 163 | Breakpoint string 164 | PageTop string 165 | PageEnd string 166 | LineUp string 167 | LineDown string 168 | PrevTab string 169 | NextTab string 170 | 171 | PrevSection string 172 | NextSection string 173 | 174 | SelectItem string 175 | 176 | ToggleBreakpoint string 177 | ClearBreakpoint string 178 | } 179 | 180 | type Colors struct { 181 | BpFg int 182 | BpActiveFg int 183 | LineFg int 184 | LineSelectedFg int 185 | LineSelectedBg int 186 | LineActiveFg int 187 | LineActiveBg int 188 | LineColumnBg int 189 | 190 | VarTypeFg int 191 | VarNameFg int 192 | VarValueFg int 193 | VarAddrFg int 194 | 195 | ListHeaderFg int 196 | ListExpand int 197 | ListSelectedBg int 198 | HeaderFg int 199 | CodeHeaderFg int 200 | 201 | NotifErrorFg int 202 | NotifPromptFg int 203 | NotifMsgFg int 204 | 205 | MenuBg int 206 | MenuFg int 207 | MenuSelectedBg int 208 | MenuSelectedFg int 209 | } 210 | 211 | type Icons struct { 212 | Bp string 213 | BpDisabled string 214 | BpActive string 215 | 216 | IndRunning string 217 | IndStopped string 218 | IndExitSuccess string 219 | IndExitError string 220 | } 221 | 222 | type Config struct { 223 | SyntaxHighlighter string 224 | Keys Keys 225 | Colors Colors 226 | Icons Icons 227 | } 228 | 229 | var gConfig Config 230 | 231 | func NewConfig() Config { 232 | keyconf := Keys{ 233 | Breakpoint: "b", 234 | PageTop: "g", 235 | PageEnd: "G", 236 | LineUp: "k", 237 | LineDown: "j", 238 | PrevTab: "h", 239 | NextTab: "l", 240 | PrevSection: "Backtab", 241 | NextSection: "Tab", 242 | SelectItem: "Enter", 243 | ToggleBreakpoint: "d", 244 | ClearBreakpoint: "D", 245 | } 246 | colorconf := Colors{ 247 | BpFg: 9, 248 | BpActiveFg: 1, 249 | LineFg: 15, 250 | LineSelectedFg: 0, 251 | LineSelectedBg: 15, 252 | LineActiveFg: 0, 253 | LineActiveBg: 6, 254 | LineColumnBg: 0, 255 | 256 | VarTypeFg: 6, 257 | VarNameFg: 2, 258 | VarValueFg: 15, 259 | VarAddrFg: 8, 260 | 261 | ListHeaderFg: 5, 262 | ListExpand: 12, 263 | ListSelectedBg: 0, 264 | HeaderFg: 15, 265 | CodeHeaderFg: 12, 266 | 267 | NotifErrorFg: 1, 268 | NotifPromptFg: 2, 269 | NotifMsgFg: 15, 270 | 271 | MenuBg: 7, 272 | MenuFg: 0, 273 | MenuSelectedBg: 15, 274 | MenuSelectedFg: 0, 275 | } 276 | iconconf := Icons{ 277 | Bp: "●", 278 | BpDisabled: "○", 279 | BpActive: "◎", 280 | 281 | IndRunning: "▶", 282 | IndStopped: "◼", 283 | IndExitSuccess: "⚑", 284 | IndExitError: "⚐", 285 | } 286 | return Config{ 287 | SyntaxHighlighter: "", 288 | Keys: keyconf, 289 | Colors: colorconf, 290 | Icons: iconconf, 291 | } 292 | } 293 | 294 | func iToColorS(c int) string { 295 | return strColors[c] 296 | } 297 | 298 | func iToColorTcell(c int) tcell.Color { 299 | return tcell.ColorNames[strColors[c]] 300 | } 301 | 302 | // Override tree view input using custom keybindings. 303 | func listInputCaptureC(event *tcell.EventKey) *tcell.EventKey { 304 | if keyPressed(event, gConfig.Keys.LineDown) { 305 | return tcell.NewEventKey(256, 'j', tcell.ModNone) 306 | } 307 | if keyPressed(event, gConfig.Keys.LineUp) { 308 | return tcell.NewEventKey(256, 'k', tcell.ModNone) 309 | } 310 | if keyPressed(event, gConfig.Keys.SelectItem) { 311 | return tcell.NewEventKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone) 312 | } 313 | return nil 314 | } 315 | 316 | func keyPressed(event *tcell.EventKey, binding string) bool { 317 | if tBindingName, ok := tcell.KeyNames[event.Key()]; ok { 318 | return strings.ToLower(tBindingName) == strings.ToLower(binding) 319 | } 320 | return binding == string(event.Rune()) 321 | } 322 | 323 | func getConfig() { 324 | gConfig = NewConfig() // Initialize with default values. 325 | 326 | viper.SetConfigName("config") 327 | viper.SetConfigType("yaml") 328 | viper.AddConfigPath("$XDG_CONFIG_HOME/dlvtui") 329 | viper.AddConfigPath(".") // Optionally use config in working directory. 330 | 331 | // Ignore error wher config file was not found, print error and exit on any other errors. 332 | if err := viper.ReadInConfig(); err != nil { 333 | if _, ok := err.(viper.ConfigFileNotFoundError); !ok { 334 | // Config file was found but another error was produced 335 | fmt.Println("Error parsing config file: %w", err) 336 | os.Exit(1) 337 | } 338 | log.Println("No config file found, using defaults.") 339 | } 340 | 341 | conf_err := viper.Unmarshal(&gConfig) 342 | if conf_err != nil { 343 | log.Fatalf("Error reading configuration: %v", conf_err) 344 | } 345 | conf_keys_err := viper.UnmarshalKey("keys", &gConfig.Keys) 346 | if conf_keys_err != nil { 347 | log.Fatalf("Error reading key configuration: %v", conf_keys_err) 348 | } 349 | conf_icons_err := viper.UnmarshalKey("icons", &gConfig.Keys) 350 | if conf_keys_err != nil { 351 | log.Fatalf("Error reading icon configuration: %v", conf_icons_err) 352 | } 353 | } 354 | -------------------------------------------------------------------------------- /config.yaml: -------------------------------------------------------------------------------- 1 | # This is an example configuration with default values. 2 | # You should place your configuration in $XDG_CONFIG_HOME/dlvtui/config.yaml 3 | 4 | syntaxhighlighter: "" 5 | keys: 6 | breakpoint: "b" 7 | pagetop: "g" 8 | pageend: "G" 9 | lineup: "k" 10 | linedown: "j" 11 | prevtab: "h" 12 | nexttab: "l" 13 | prevsection: "Backtab" 14 | nextsection: "Tab" 15 | selectitem: "Enter" 16 | togglebreakpoint: "d" 17 | clearbreakpoint: "D" 18 | 19 | colors: 20 | bpfg: 9 21 | bpactivefg: 1 22 | linefg: 15 23 | lineselectedfg: 0 24 | lineselectedbg: 15 25 | lineactivefg: 0 26 | lineactivebg: 6 27 | linecolumnbg: 0 28 | 29 | vartypefg: 6 30 | varnamefg: 2 31 | varvaluefg: 15 32 | varaddrfg: 8 33 | 34 | listheaderfg: 5 35 | listexpand: 12 36 | listselectedbg: 0 37 | headerfg: 15 38 | codeheaderfg: 12 39 | 40 | notiferrorfg: 1 41 | notifpromptfg: 2 42 | notifmsgfg: 15 43 | 44 | menubg: 7 45 | menufg: 0 46 | menuselectedbg: 15 47 | menuselectedfg: 0 48 | 49 | icons: 50 | bp: "●" 51 | bpdisabled: "○" 52 | bpactive: "◎" 53 | 54 | indrunning: "▶" 55 | indstopped: "◼" 56 | indexitsuccess: "⚑" 57 | indexiterror: "⚐" 58 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ilmari-h/dlvtui 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/gdamore/tcell/v2 v2.4.1-0.20210905002822-f057f0a857a1 7 | github.com/go-delve/delve v1.8.3 8 | github.com/rivo/tview v0.0.0-20220307222120-9994674d60a8 9 | github.com/spf13/viper v1.12.0 10 | ) 11 | 12 | require ( 13 | github.com/cilium/ebpf v0.7.0 // indirect 14 | github.com/fsnotify/fsnotify v1.5.4 // indirect 15 | github.com/gdamore/encoding v1.0.0 // indirect 16 | github.com/hashicorp/golang-lru v0.5.4 // indirect 17 | github.com/hashicorp/hcl v1.0.0 // indirect 18 | github.com/konsorten/go-windows-terminal-sequences v1.0.3 // indirect 19 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect 20 | github.com/magiconair/properties v1.8.6 // indirect 21 | github.com/mattn/go-isatty v0.0.14 // indirect 22 | github.com/mattn/go-runewidth v0.0.13 // indirect 23 | github.com/mitchellh/mapstructure v1.5.0 // indirect 24 | github.com/pelletier/go-toml v1.9.5 // indirect 25 | github.com/pelletier/go-toml/v2 v2.0.1 // indirect 26 | github.com/rivo/uniseg v0.2.0 // indirect 27 | github.com/sirupsen/logrus v1.6.0 // indirect 28 | github.com/spf13/afero v1.8.2 // indirect 29 | github.com/spf13/cast v1.5.0 // indirect 30 | github.com/spf13/jwalterweatherman v1.1.0 // indirect 31 | github.com/spf13/pflag v1.0.5 // indirect 32 | github.com/subosito/gotenv v1.3.0 // indirect 33 | golang.org/x/arch v0.0.0-20190927153633-4e8777c89be4 // indirect 34 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect 35 | golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d // indirect 36 | golang.org/x/text v0.3.7 // indirect 37 | gopkg.in/ini.v1 v1.66.4 // indirect 38 | gopkg.in/yaml.v2 v2.4.0 // indirect 39 | gopkg.in/yaml.v3 v3.0.0 // indirect 40 | ) 41 | -------------------------------------------------------------------------------- /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.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 7 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 8 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 9 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= 10 | cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= 11 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= 12 | cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= 13 | cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= 14 | cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= 15 | cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= 16 | cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= 17 | cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= 18 | cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= 19 | cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= 20 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 21 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= 22 | cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= 23 | cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= 24 | cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= 25 | cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= 26 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 27 | cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= 28 | cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= 29 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 30 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= 31 | cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= 32 | cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= 33 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 34 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= 35 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= 36 | cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= 37 | cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= 38 | cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= 39 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 40 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 41 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 42 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 43 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 44 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 45 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= 46 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= 47 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 48 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 49 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 50 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 51 | github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= 52 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 53 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 54 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 55 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 56 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 57 | github.com/cilium/ebpf v0.7.0 h1:1k/q3ATgxSXRdrmPfH8d7YK0GfqVsEKZAX9dQZvs56k= 58 | github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= 59 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 60 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 61 | github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 62 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 63 | github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= 64 | github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 65 | github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 66 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 67 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 68 | github.com/cosiner/argv v0.1.0/go.mod h1:EusR6TucWKX+zFgtdUsKT2Cvg45K5rtpCcWz4hK06d8= 69 | github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 70 | github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w= 71 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 72 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 73 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 74 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 75 | github.com/derekparker/trie v0.0.0-20200317170641-1fdf38b7b0e9/go.mod h1:D6ICZm05D9VN1n/8iOtBxLpXtoGp6HDFUJ1RNVieOSE= 76 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 77 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= 78 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 79 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 80 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 81 | github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= 82 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 83 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 84 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 85 | github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= 86 | github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= 87 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 88 | github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= 89 | github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= 90 | github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= 91 | github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= 92 | github.com/gdamore/tcell/v2 v2.4.1-0.20210905002822-f057f0a857a1 h1:QqwPZCwh/k1uYqq6uXSb9TRDhTkfQbO80v8zhnIe5zM= 93 | github.com/gdamore/tcell/v2 v2.4.1-0.20210905002822-f057f0a857a1/go.mod h1:Az6Jt+M5idSED2YPGtwnfJV0kXohgdCBPmHGSYc1r04= 94 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 95 | github.com/go-delve/delve v1.8.3 h1:D0jTF4DHZQNBPMQwAPe0WtZV4I4gEeWOsSt4VmhGys8= 96 | github.com/go-delve/delve v1.8.3/go.mod h1:XB6XKpI5DqMCNai0MkNPVbrd3OtBovJ/vfcVofkWy/k= 97 | github.com/go-delve/liner v1.2.2-1/go.mod h1:biJCRbqp51wS+I92HMqn5H8/A0PAhxn2vyOT+JqhiGI= 98 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 99 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 100 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 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/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 106 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 107 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 108 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 109 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 110 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 111 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 112 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 113 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 114 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 115 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 116 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 117 | github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 118 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= 119 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 120 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 121 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 122 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 123 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 124 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 125 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 126 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 127 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 128 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 129 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 130 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 131 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 132 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 133 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 134 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 135 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 136 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 137 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 138 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 139 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 140 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 141 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 142 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 143 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 144 | github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= 145 | github.com/google/go-dap v0.6.0/go.mod h1:5q8aYQFnHOAZEMP+6vmq25HKYAEwE+LF5yh7JKrrhSQ= 146 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 147 | github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 148 | github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 149 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 150 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 151 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 152 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 153 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 154 | github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 155 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 156 | github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 157 | github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 158 | github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 159 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 160 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 161 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 162 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 163 | github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= 164 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 165 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 166 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 167 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 168 | github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 169 | github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= 170 | github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= 171 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 172 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 173 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 174 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= 175 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 176 | github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= 177 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= 178 | github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= 179 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 180 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 181 | github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= 182 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 183 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 184 | github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= 185 | github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 186 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 187 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 188 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= 189 | github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= 190 | github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= 191 | github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= 192 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 193 | github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 194 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 195 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 196 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 197 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 198 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 199 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 200 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 201 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 202 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 203 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 204 | github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= 205 | github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 206 | github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= 207 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 208 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 209 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 210 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 211 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 212 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 213 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 214 | github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= 215 | github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 216 | github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 217 | github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= 218 | github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= 219 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 220 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 221 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= 222 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 223 | github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 224 | github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= 225 | github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 226 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 227 | github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= 228 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= 229 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 230 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 231 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 232 | github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= 233 | github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= 234 | github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 235 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 236 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= 237 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 238 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 239 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 240 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 241 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 242 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 243 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 244 | github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= 245 | github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= 246 | github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU= 247 | github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= 248 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 249 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 250 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 251 | github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= 252 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 253 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 254 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 255 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 256 | github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= 257 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 258 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 259 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 260 | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 261 | github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 262 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 263 | github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 264 | github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= 265 | github.com/rivo/tview v0.0.0-20220307222120-9994674d60a8 h1:xe+mmCnDN82KhC010l3NfYlA8ZbOuzbXAzSYBa6wbMc= 266 | github.com/rivo/tview v0.0.0-20220307222120-9994674d60a8/go.mod h1:WIfMkQNY+oq/mWwtsjOYHIZBuwthioY2srOmljJkTnk= 267 | github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= 268 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 269 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 270 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 271 | github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= 272 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 273 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= 274 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= 275 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 276 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 277 | github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= 278 | github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= 279 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 280 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 281 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 282 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 283 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 284 | github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= 285 | github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= 286 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 287 | github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= 288 | github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= 289 | github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= 290 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 291 | github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= 292 | github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= 293 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 294 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 295 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 296 | github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= 297 | github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= 298 | github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= 299 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 300 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 301 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 302 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 303 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 304 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 305 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 306 | github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= 307 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 308 | github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= 309 | github.com/subosito/gotenv v1.3.0 h1:mjC+YW8QpAdXibNi+vNWgzmgBH4+5l5dCXv8cNysBLI= 310 | github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs= 311 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 312 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 313 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 314 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 315 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 316 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 317 | github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 318 | go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 319 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 320 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 321 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 322 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 323 | go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 324 | go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= 325 | go.starlark.net v0.0.0-20200821142938-949cc6f4b097/go.mod h1:f0znQkUKRrkk36XxWbGjMqQM8wGv/xHBVE2qc3B5oFU= 326 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 327 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 328 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 329 | golang.org/x/arch v0.0.0-20190927153633-4e8777c89be4 h1:QlVATYS7JBoZMVaf+cNjb90WD/beKVHnIxFKT4QaHVI= 330 | golang.org/x/arch v0.0.0-20190927153633-4e8777c89be4/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4= 331 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 332 | golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 333 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 334 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 335 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 336 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 337 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 338 | golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= 339 | golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 340 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 341 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 342 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 343 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 344 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 345 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 346 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 347 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 348 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= 349 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= 350 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 351 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 352 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 353 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 354 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 355 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 356 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 357 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 358 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 359 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= 360 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 361 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 362 | golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 363 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 364 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 365 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 366 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 367 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 368 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 369 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 370 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 371 | golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 372 | golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 373 | golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= 374 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 375 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 376 | golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 377 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 378 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 379 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 380 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 381 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 382 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 383 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 384 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 385 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 386 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 387 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 388 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 389 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 390 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 391 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 392 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 393 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 394 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 395 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 396 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 397 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 398 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 399 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 400 | golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 401 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 402 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 403 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 404 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 405 | golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 406 | golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 407 | golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 408 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 409 | golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 410 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 411 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 412 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 413 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 414 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 415 | golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 416 | golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 417 | golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 418 | golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 419 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 420 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 421 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 422 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 423 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 424 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 425 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 426 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 427 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 428 | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 429 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 430 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 431 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 432 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 433 | golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 434 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 435 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 436 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 437 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 438 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 439 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 440 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 441 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 442 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 443 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 444 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 445 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 446 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 447 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 448 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 449 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 450 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 451 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 452 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 453 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 454 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 455 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 456 | golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 457 | golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 458 | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 459 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 460 | golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 461 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 462 | golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 463 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 464 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 465 | golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 466 | golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 467 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 468 | golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 469 | golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 470 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 471 | golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 472 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 473 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 474 | golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 475 | golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 476 | golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 477 | golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 478 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= 479 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 480 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 481 | golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 482 | golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE= 483 | golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 484 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 485 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 486 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 487 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 488 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 489 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 490 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 491 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 492 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 493 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 494 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 495 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 496 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 497 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 498 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 499 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 500 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 501 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 502 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 503 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 504 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 505 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 506 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 507 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 508 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 509 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 510 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 511 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 512 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 513 | golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 514 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 515 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 516 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 517 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 518 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 519 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 520 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 521 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 522 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 523 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 524 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 525 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 526 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 527 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 528 | golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 529 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 530 | golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 531 | golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= 532 | golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 533 | golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 534 | golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 535 | golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 536 | golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 537 | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 538 | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 539 | golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= 540 | golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 541 | golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 542 | golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 543 | golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 544 | golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 545 | golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= 546 | golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= 547 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 548 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 549 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 550 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 551 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 552 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 553 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 554 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 555 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 556 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 557 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 558 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 559 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 560 | google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 561 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 562 | google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 563 | google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 564 | google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 565 | google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= 566 | google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= 567 | google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= 568 | google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= 569 | google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= 570 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 571 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 572 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 573 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 574 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 575 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 576 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 577 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 578 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 579 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 580 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 581 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 582 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 583 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 584 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 585 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 586 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 587 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 588 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 589 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 590 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 591 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= 592 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 593 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 594 | google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 595 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 596 | google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 597 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 598 | google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 599 | google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 600 | google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= 601 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 602 | google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= 603 | google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 604 | google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 605 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 606 | google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 607 | google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 608 | google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 609 | google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 610 | google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 611 | google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 612 | google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 613 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 614 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 615 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 616 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 617 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 618 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 619 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 620 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 621 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= 622 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= 623 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 624 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 625 | google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 626 | google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= 627 | google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= 628 | google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 629 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 630 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 631 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 632 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 633 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 634 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 635 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 636 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 637 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 638 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 639 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 640 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 641 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 642 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 643 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 644 | gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 645 | gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4= 646 | gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 647 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 648 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 649 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 650 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 651 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 652 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 653 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 654 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 655 | gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= 656 | gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 657 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 658 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 659 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 660 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 661 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 662 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 663 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 664 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 665 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 666 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= 667 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= 668 | -------------------------------------------------------------------------------- /goroutinepage.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/gdamore/tcell/v2" 8 | "github.com/go-delve/delve/service/api" 9 | "github.com/rivo/tview" 10 | ) 11 | 12 | type GoroutinePage struct { 13 | renderedGoroutines []*api.Goroutine 14 | commandHandler *CommandHandler 15 | listView *tview.List 16 | widget *tview.Frame 17 | } 18 | 19 | func NewGoroutinePage() *GoroutinePage { 20 | listView := tview.NewList() 21 | listView.SetBackgroundColor(tcell.ColorDefault) 22 | listView.ShowSecondaryText(false) 23 | 24 | selectedStyle := tcell.StyleDefault. 25 | Foreground(iToColorTcell(gConfig.Colors.LineFg)). 26 | Background(iToColorTcell(gConfig.Colors.ListSelectedBg)). 27 | Attributes(tcell.AttrBold) 28 | 29 | listView.SetSelectedStyle(selectedStyle) 30 | 31 | listView.SetInputCapture(listInputCaptureC) 32 | 33 | pageFrame := tview.NewFrame(listView). 34 | SetBorders(0, 0, 0, 0, 0, 0). 35 | AddText("[::b]Goroutines:", true, tview.AlignLeft, iToColorTcell(gConfig.Colors.HeaderFg)) 36 | pageFrame.SetBackgroundColor(tcell.ColorDefault) 37 | gp := GoroutinePage{ 38 | listView: listView, 39 | widget: pageFrame, 40 | } 41 | return &gp 42 | } 43 | 44 | func (page *GoroutinePage) SetCommandHandler(ch *CommandHandler) { 45 | page.commandHandler = ch 46 | } 47 | 48 | func (page *GoroutinePage) RenderGoroutines(grs []*api.Goroutine, currId int) { 49 | 50 | // Filter out external goroutines. 51 | projectGrs := []*api.Goroutine{} 52 | for _, gor := range grs { 53 | if strings.HasPrefix(gor.CurrentLoc.File, page.commandHandler.view.navState.ProjectPath) || 54 | strings.HasPrefix(gor.GoStatementLoc.File, page.commandHandler.view.navState.ProjectPath) || 55 | strings.HasPrefix(gor.StartLoc.File, page.commandHandler.view.navState.ProjectPath) { 56 | projectGrs = append(projectGrs, gor) 57 | } 58 | } 59 | 60 | page.listView.Clear() 61 | selectedI := 0 62 | page.renderedGoroutines = grs 63 | for i, gor := range projectGrs { 64 | label := fmt.Sprintf(" [%s]%d.[%s] %s[%s]:%d", 65 | iToColorS(gConfig.Colors.VarTypeFg), 66 | gor.ID, 67 | iToColorS(gConfig.Colors.VarNameFg), 68 | gor.CurrentLoc.File, 69 | iToColorS(gConfig.Colors.VarValueFg), 70 | gor.CurrentLoc.Line, 71 | ) 72 | if gor.ID == currId { 73 | selectedI = i 74 | label = fmt.Sprintf("> [%s::b]%d. [%s]%s[%s]:%d", 75 | iToColorS(gConfig.Colors.VarTypeFg), 76 | gor.ID, 77 | iToColorS(gConfig.Colors.VarNameFg), 78 | gor.CurrentLoc.File, 79 | iToColorS(gConfig.Colors.VarValueFg), 80 | gor.CurrentLoc.Line, 81 | ) 82 | } 83 | page.listView.AddItem( 84 | label, 85 | "", 86 | rune(48+i), 87 | nil). 88 | SetSelectedFunc(func(i int, s1, s2 string, r rune) { 89 | page.commandHandler.RunCommand(&SwitchGoroutines{ 90 | Id: page.renderedGoroutines[i].ID, 91 | }) 92 | }) 93 | } 94 | page.listView.SetCurrentItem(selectedI) 95 | } 96 | 97 | func (sp *GoroutinePage) GetWidget() tview.Primitive { 98 | return sp.widget 99 | } 100 | 101 | func (sp *GoroutinePage) GetName() string { 102 | return "goroutines" 103 | } 104 | 105 | func (sp *GoroutinePage) HandleKeyEvent(event *tcell.EventKey) *tcell.EventKey { 106 | if keyPressed(event, gConfig.Keys.LineDown) { 107 | sp.listView.SetCurrentItem(sp.listView.GetCurrentItem() + 1) 108 | return nil 109 | } 110 | if keyPressed(event, gConfig.Keys.LineUp) { 111 | if sp.listView.GetCurrentItem() > 0 { 112 | sp.listView.SetCurrentItem(sp.listView.GetCurrentItem() - 1) 113 | } 114 | return nil 115 | } 116 | sp.listView.InputHandler()(event, func(p tview.Primitive) {}) 117 | return nil 118 | } 119 | -------------------------------------------------------------------------------- /linecolumn.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/ilmari-h/dlvtui/nav" 5 | "fmt" 6 | "strconv" 7 | 8 | "github.com/rivo/tview" 9 | ) 10 | 11 | type LineColumn struct { 12 | width int 13 | navState *nav.Nav 14 | textView *tview.TextView 15 | } 16 | 17 | func getMaxLineColWidth(maxLine int) int { 18 | return len(strconv.Itoa(maxLine)) + 3 19 | } 20 | 21 | func NewLineColumn(navState *nav.Nav) *LineColumn { 22 | return &LineColumn{ 23 | navState: navState, 24 | textView: tview.NewTextView(), 25 | } 26 | } 27 | 28 | func (lc *LineColumn) SetWidth(nw int) { 29 | lc.width = nw 30 | } 31 | 32 | func (lc *LineColumn) Render(lineStart, lineEnd, current int) { 33 | lc.textView.SetBackgroundColor(iToColorTcell(gConfig.Colors.LineColumnBg)) 34 | 35 | if lc.navState == nil || lc.navState.CurrentFile == nil { 36 | return 37 | } 38 | 39 | // Set line numbers in gutter. 40 | lineNumbers := "" 41 | breakpoints := lc.navState.Breakpoints[lc.navState.CurrentFile.Path] 42 | for i := lineStart; i <= lineEnd; i++ { 43 | bp := " " 44 | if fbp, ok := breakpoints[i]; ok && fbp.ID >= 0 { 45 | if breakpoints[i].Disabled { 46 | bp = fmt.Sprintf("[%s]%s[-::-]", 47 | iToColorS(gConfig.Colors.BpFg), 48 | gConfig.Icons.BpDisabled, 49 | ) 50 | } else if i == lc.navState.CurrentDebuggerPos.Line && 51 | lc.navState.CurrentFile.Path == lc.navState.CurrentDebuggerPos.File { 52 | bp = fmt.Sprintf("[%s:%s]%s[-::-]", 53 | iToColorS(gConfig.Colors.BpActiveFg), 54 | iToColorS(gConfig.Colors.LineActiveBg), 55 | gConfig.Icons.BpActive, 56 | ) 57 | } else { 58 | bp = fmt.Sprintf("[%s]%s[-::-]", 59 | iToColorS(gConfig.Colors.BpFg), 60 | gConfig.Icons.Bp, 61 | ) 62 | } 63 | } 64 | 65 | // TODO: Also: if some stack frame has this line 66 | if i == lc.navState.CurrentDebuggerPos.Line && 67 | lc.navState.CurrentFile.Path == lc.navState.CurrentDebuggerPos.File { 68 | 69 | lineNumbers += fmt.Sprintf(`[%s:%s]%s[%s]%*d [-:-:-]`, 70 | iToColorS(gConfig.Colors.LineSelectedFg), 71 | iToColorS(gConfig.Colors.LineActiveBg), 72 | bp, 73 | iToColorS(gConfig.Colors.LineSelectedFg), 74 | lc.width-2, 75 | i, 76 | ) 77 | 78 | } else if i == current { 79 | lineNumbers += fmt.Sprintf(`[%s:%s]%s[%s]%*d [-:-:-]`, 80 | iToColorS(gConfig.Colors.LineSelectedFg), 81 | iToColorS(gConfig.Colors.LineSelectedBg), 82 | bp, 83 | iToColorS(gConfig.Colors.LineSelectedFg), 84 | lc.width-2, 85 | i, 86 | ) 87 | } else { 88 | lineNumbers += fmt.Sprintf(`[%s]%s%*d `, 89 | iToColorS(gConfig.Colors.LineFg), 90 | bp, 91 | lc.width-2, 92 | i, 93 | ) 94 | } 95 | } 96 | lc.textView.SetText(lineNumbers) 97 | } 98 | 99 | func (lc *LineColumn) GetTextView() *tview.TextView { 100 | return lc.textView 101 | } 102 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "flag" 6 | "fmt" 7 | "os" 8 | "os/exec" 9 | "path/filepath" 10 | "strings" 11 | "syscall" 12 | 13 | "github.com/ilmari-h/dlvtui/nav" 14 | 15 | "github.com/go-delve/delve/service/rpc2" 16 | "github.com/rivo/tview" 17 | log "github.com/sirupsen/logrus" 18 | ) 19 | 20 | func execDebuggerCmd(executable string, exArgs []string, port string) []string { 21 | log.Printf("Debugging executable at path: %s", executable) 22 | allArgs := []string{ 23 | "exec", 24 | "--headless", 25 | "--accept-multiclient", 26 | "--api-version=2", 27 | "--listen=127.0.0.1:" + port, 28 | executable, 29 | } 30 | if exArgs != nil && len(exArgs) > 0 { 31 | allArgs = append(allArgs, "--") 32 | allArgs = append(allArgs, exArgs...) 33 | } 34 | return allArgs 35 | } 36 | 37 | func attachDebuggerCmd(pid string, exArgs []string, port string) []string { 38 | log.Printf("Debugging process with PID: %s", pid) 39 | allArgs := []string{ 40 | "attach", 41 | "--headless", 42 | "--accept-multiclient", 43 | "--api-version=2", 44 | "--listen=127.0.0.1:" + port, 45 | pid, 46 | } 47 | if exArgs != nil && len(exArgs) > 0 { 48 | allArgs = append(allArgs, "--") 49 | allArgs = append(allArgs, exArgs...) 50 | } 51 | return allArgs 52 | } 53 | 54 | func startDebugger(commandArgs []string) int { 55 | log.Printf("Starting dlv-backend: dlv %s", strings.Join(commandArgs, " ")) 56 | cmd := exec.Command( 57 | "dlv", 58 | commandArgs..., 59 | ) 60 | cmd.SysProcAttr = &syscall.SysProcAttr{ 61 | Pdeathsig: syscall.SIGKILL, 62 | } 63 | stdout, _ := cmd.StdoutPipe() 64 | if err := cmd.Start(); err != nil { 65 | log.Printf("Error starting dlv-backend: %s", string(err.Error())) 66 | panic(err) 67 | } 68 | 69 | log.Printf("dlv-backend running with pid %d", cmd.Process.Pid) 70 | 71 | go func() { 72 | in := bufio.NewScanner(stdout) 73 | for in.Scan() { 74 | log.Printf("dlv-backend:%s", in.Text()) 75 | } 76 | if err := in.Err(); err != nil { 77 | log.Printf("Error: %s", err) 78 | } 79 | }() 80 | 81 | return cmd.Process.Pid 82 | } 83 | 84 | // Used for autosuggestions for now, a browser window in the future. 85 | func getFileList(client *rpc2.RPCClient) chan []string { 86 | filesListC := make(chan []string) 87 | go func() { 88 | files, err := client.ListSources("") 89 | if err != nil { 90 | log.Fatalf("Error tracing directory: %s", err) 91 | } 92 | filesListC <- files 93 | }() 94 | return filesListC 95 | } 96 | 97 | var ( 98 | port string 99 | logfile string 100 | attachMode bool 101 | ) 102 | 103 | func init() { 104 | 105 | // Parse flags after first argument. 106 | exFlags := flag.NewFlagSet("", flag.ExitOnError) 107 | exFlags.StringVar(&port, "port", "8181", "The port dlv rpc server will listen to.") 108 | exFlags.BoolVar(&attachMode, "attach", false, "If enabled, attach debugger to process. Interpret first argument as PID.") 109 | exFlags.StringVar(&logfile, "logfile", "$XDG_DATA_HOME/dlvtui.log", "Path to the log file.") 110 | 111 | if len(os.Args) < 2 { 112 | fmt.Println("No debug target provided.\n" + 113 | "The first argument should be an executable or a PID if the flag `attach` is set.") 114 | exFlags.Usage() 115 | os.Exit(1) 116 | return 117 | } 118 | exFlags.Parse(os.Args[2:]) 119 | 120 | log.SetLevel(log.InfoLevel) 121 | file, err := os.OpenFile(os.ExpandEnv(logfile), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) 122 | file.Truncate(0) 123 | if err == nil { 124 | log.SetOutput(file) 125 | } else { 126 | log.Fatal("Failed to log to file: " + err.Error()) 127 | } 128 | } 129 | 130 | func main() { 131 | 132 | getConfig() 133 | 134 | target := os.Args[1] 135 | 136 | clientC := make(chan *rpc2.RPCClient) 137 | 138 | if attachMode { 139 | startDebugger(attachDebuggerCmd(target, []string{}, port)) 140 | } else { 141 | targetFile, _ := filepath.Abs(target) 142 | startDebugger(execDebuggerCmd(targetFile, []string{}, port)) 143 | } 144 | 145 | go NewClient("127.0.0.1:"+port, clientC) 146 | rpcClient := <-clientC 147 | fileList := <-getFileList(rpcClient) 148 | 149 | if fileList == nil || len(fileList) == 0 { 150 | log.Fatalf("Error: empty source list.") 151 | } 152 | 153 | // Resolve dir. For now just find by assuming it's the one prefixed by /home. 154 | var dir string 155 | for _, f := range fileList { 156 | if strings.HasPrefix(f, "/home/") && !strings.Contains(f, "/go/pkg") { 157 | dir = filepath.Dir(f) 158 | break 159 | } 160 | } 161 | log.Printf("Using dir: %s", dir) 162 | 163 | app := tview.NewApplication() 164 | nav := nav.NewNav(dir) 165 | 166 | nav.SourceFiles = fileList 167 | 168 | CreateTui(app, &nav, rpcClient) 169 | 170 | if err := app.Run(); err != nil { 171 | panic(err) 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /nav/nav.go: -------------------------------------------------------------------------------- 1 | package nav 2 | 3 | import ( 4 | "github.com/go-delve/delve/service/api" 5 | ) 6 | 7 | type File struct { 8 | Name string 9 | Path string 10 | Content string 11 | LineCount int 12 | LineIndices []int 13 | PackageName string // TODO 14 | } 15 | 16 | type DebuggerPos struct { 17 | File string 18 | Line int 19 | 20 | } 21 | 22 | type UiBreakpoint struct { 23 | Disabled bool 24 | *api.Breakpoint 25 | } 26 | 27 | func (nav *Nav) CurrentLine() int { 28 | return nav.CurrentLines[nav.CurrentFile.Path] 29 | } 30 | 31 | func (nav *Nav) SetLine(line int) int { 32 | if line >= 0 && line < nav.CurrentFile.LineCount - 1 { 33 | nav.CurrentLines[nav.CurrentFile.Path] = line 34 | return line 35 | } 36 | return nav.CurrentLines[nav.CurrentFile.Path] 37 | } 38 | 39 | func (nav *Nav) LineInFile(filePath string) int{ 40 | if _, ok := nav.CurrentLines[filePath]; !ok { 41 | return 0 42 | } 43 | return nav.CurrentLines[filePath] 44 | } 45 | 46 | func (nav *Nav) ChangeCurrentFile(file *File){ 47 | if _, ok := nav.CurrentLines[file.Path]; !ok { 48 | nav.CurrentLines[file.Path] = 0 49 | } 50 | if _, ok := nav.FileCache[file.Path]; !ok { 51 | nav.FileCache[file.Path] = file 52 | } 53 | nav.CurrentFile = file 54 | } 55 | 56 | func (nav *Nav) GetAllBreakpoints() []*UiBreakpoint { 57 | bps := []*UiBreakpoint{} 58 | if nav.Breakpoints == nil { 59 | return bps 60 | } 61 | for _, fileMap := range nav.Breakpoints { 62 | for _, bp := range fileMap { 63 | bps = append(bps, bp) 64 | } 65 | } 66 | return bps 67 | } 68 | 69 | // Represents state of navigation within the project directory and the debugger. 70 | type Nav struct { 71 | 72 | // Project level 73 | SourceFiles []string 74 | ProjectPath string 75 | FileCache map[string]*File 76 | Goroutines []*api.Goroutine 77 | 78 | Breakpoints map[string] map[int]*UiBreakpoint 79 | 80 | CurrentFile *File 81 | CurrentLines map[string]int 82 | CurrentDebuggerPos DebuggerPos 83 | 84 | DbgState *api.DebuggerState 85 | CurrentStack []api.Stackframe 86 | CurrentStackFrame *api.Stackframe 87 | } 88 | 89 | // Load saved session 90 | func loadNav(projectRootPath string) Nav { 91 | return Nav{} 92 | } 93 | 94 | func NewNav(projectPath string) Nav { 95 | 96 | return Nav{ 97 | SourceFiles: []string{}, 98 | ProjectPath: projectPath, 99 | FileCache: make(map[string]*File), 100 | CurrentLines: make(map[string]int), 101 | Breakpoints: make(map[string] map[int]*UiBreakpoint), 102 | Goroutines: []*api.Goroutine{}, 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /pageview.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/ilmari-h/dlvtui/nav" 5 | 6 | "github.com/gdamore/tcell/v2" 7 | "github.com/go-delve/delve/service/api" 8 | "github.com/rivo/tview" 9 | ) 10 | 11 | type Page interface { 12 | GetName() string 13 | GetWidget() tview.Primitive 14 | HandleKeyEvent(event *tcell.EventKey) *tcell.EventKey 15 | SetCommandHandler(cmdHdlr *CommandHandler) // Each page will be given a reference to CommandHandler 16 | } 17 | 18 | type PageIndex int8 19 | 20 | const ( 21 | ICodePage PageIndex = 0 22 | IBreakPointsPage = 1 23 | IVarsPage = 2 24 | IStackPage = 3 25 | IGoroutinePage = 4 26 | ) 27 | 28 | type PageView struct { 29 | commandHandler *CommandHandler 30 | index int 31 | pages []Page 32 | pagesView *tview.Pages 33 | 34 | codePage *CodePage 35 | breakpointsPage *BreakpointsPage 36 | varsPage *VarsPage 37 | stackPage *StackPage 38 | goroutinePage *GoroutinePage 39 | } 40 | 41 | func NewPageView(cmdHdlr *CommandHandler, nav *nav.Nav, app *tview.Application) *PageView { 42 | pv := PageView{ 43 | commandHandler: cmdHdlr, 44 | index: 0, 45 | pages: []Page{}, 46 | pagesView: tview.NewPages(), 47 | codePage: NewCodePage(app, nav), 48 | breakpointsPage: NewBreakpointsPage(), 49 | varsPage: NewVarPage(), 50 | stackPage: NewStackPage(), 51 | goroutinePage: NewGoroutinePage(), 52 | } 53 | pv.pages = []Page{pv.codePage, pv.breakpointsPage, pv.varsPage, pv.stackPage, pv.goroutinePage} 54 | 55 | for _, p := range pv.pages { 56 | pv.pagesView.AddPage(p.GetName(), p.GetWidget(), true, true) 57 | p.SetCommandHandler(cmdHdlr) 58 | } 59 | pv.pagesView.SwitchToPage(pv.pages[0].GetName()) 60 | 61 | return &pv 62 | } 63 | 64 | func (pv *PageView) SwitchToPage(pi PageIndex) { 65 | pv.index = int(pi) 66 | page := pv.pages[pi] 67 | pv.pagesView.SwitchToPage(page.GetName()) 68 | } 69 | 70 | func (pv *PageView) CurrentPage() Page { 71 | return pv.pages[pv.index] 72 | } 73 | 74 | func (pv *PageView) RefreshCodePage() { 75 | pv.codePage.perfTextView.ReRender() 76 | } 77 | 78 | func (pv *PageView) ResizeCodePage(height int) { 79 | pv.codePage.perfTextView.ReRenderWithHeight(height) 80 | } 81 | 82 | func (pv *PageView) RenderBreakpoints(bps []*nav.UiBreakpoint) { 83 | if bps == nil { 84 | return 85 | } 86 | pv.breakpointsPage.RenderBreakpoints(bps) 87 | } 88 | 89 | func (pv *PageView) RenderBreakpointHit(bp *api.BreakpointInfo) { 90 | if bp == nil { 91 | return 92 | } 93 | pv.varsPage.RenderVariables(bp.Arguments, bp.Locals, []api.Variable{}) 94 | } 95 | 96 | func (pv *PageView) RenderStack(sf []api.Stackframe, csf *api.Stackframe, returns []api.Variable) { 97 | if sf == nil || len(sf) == 0 || csf == nil { 98 | return 99 | } 100 | pv.varsPage.RenderVariables(csf.Arguments, csf.Locals, returns) 101 | pv.stackPage.RenderStack(sf, csf) 102 | } 103 | 104 | func (pv *PageView) RenderJumpToLine(toLine int) { 105 | pv.codePage.perfTextView.scrollTo(toLine, true) 106 | } 107 | 108 | func (pv *PageView) GetWidget() tview.Primitive { 109 | return pv.pagesView 110 | } 111 | 112 | func (pv *PageView) LoadFile(file *nav.File, atLine int) { 113 | pv.index = 0 114 | pv.pagesView.SwitchToPage(pv.codePage.GetName()) 115 | pv.codePage.OpenFile(file, atLine) 116 | } 117 | 118 | // Consumes event if changing page. Otherwise delegates to active page. 119 | func (pv *PageView) HandleKeyEvent(event *tcell.EventKey) *tcell.EventKey { 120 | if keyPressed(event, gConfig.Keys.PrevTab) { 121 | if pv.index > 0 { 122 | pv.index-- 123 | pv.pagesView.SwitchToPage(pv.CurrentPage().GetName()) 124 | } 125 | return nil // Consume event 126 | } else if keyPressed(event, gConfig.Keys.NextTab) { 127 | if pv.index < len(pv.pages)-1 { 128 | pv.index++ 129 | pv.pagesView.SwitchToPage(pv.CurrentPage().GetName()) 130 | } 131 | return nil // Consume event 132 | } 133 | // Delegate 134 | return pv.CurrentPage().HandleKeyEvent(event) 135 | } 136 | -------------------------------------------------------------------------------- /perftextview.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "math" 5 | 6 | "github.com/rivo/tview" 7 | log "github.com/sirupsen/logrus" 8 | ) 9 | 10 | type PerfTextView struct { 11 | lineIndices []int 12 | currentViewHeight int 13 | virtualLine int 14 | scroll int 15 | fullText string 16 | lineColumn *LineColumn 17 | tview.TextView 18 | } 19 | 20 | func NewPerfTextView() *PerfTextView { 21 | pv := &PerfTextView{[]int{}, 1, 1, 1, "", nil, *tview.NewTextView()} 22 | pv.SetScrollable(false) 23 | return pv 24 | } 25 | 26 | var offset = 5 27 | 28 | func (perfTextView *PerfTextView) ReRender() { 29 | 30 | _, _, _, h := perfTextView.GetInnerRect() 31 | perfTextView.render(perfTextView.scroll, perfTextView.virtualLine, h) 32 | } 33 | 34 | func (perfTextView *PerfTextView) ReRenderWithHeight(height int) { 35 | perfTextView.render(perfTextView.scroll, perfTextView.virtualLine, height) 36 | } 37 | 38 | func (perfTextView *PerfTextView) render(scroll int, line int, maxHeight int) { 39 | 40 | // No text loaded, don't render. 41 | if len(perfTextView.lineIndices) == 0 { 42 | return 43 | } 44 | 45 | firstLine := perfTextView.lineIndices[scroll] 46 | var endIdx = perfTextView.lineIndices[len(perfTextView.lineIndices)-1] 47 | if scroll+maxHeight < len(perfTextView.lineIndices) { 48 | endIdx = perfTextView.lineIndices[scroll+maxHeight] - 1 49 | } 50 | 51 | perfTextView.lineColumn.Render(scroll+1, scroll+maxHeight, perfTextView.virtualLine) 52 | perfTextView.SetText(perfTextView.fullText[firstLine:endIdx]) 53 | perfTextView.lineColumn.textView.ScrollToBeginning() 54 | perfTextView.ScrollToBeginning() 55 | 56 | } 57 | 58 | func (perfTextView *PerfTextView) scrollTo(line int, center bool) { 59 | perfTextView.virtualLine = line + 1 // File line numbers start at 1. 60 | 61 | if center { // Scroll selected line at the center of the view. 62 | perfTextView.scroll = int(math.Max(0, 63 | float64(line)-math.Max(1, float64(perfTextView.currentViewHeight/2)), 64 | )) 65 | log.Debugf("Jumping to line %d", perfTextView.scroll) 66 | } else { 67 | // Scroll down 68 | if perfTextView.virtualLine > offset && 69 | perfTextView.scroll+perfTextView.currentViewHeight-perfTextView.virtualLine < offset { 70 | 71 | perfTextView.scroll++ 72 | } 73 | // Scroll up 74 | if perfTextView.scroll != 0 && perfTextView.virtualLine-perfTextView.scroll < offset { 75 | perfTextView.scroll-- 76 | } 77 | } 78 | scroll := perfTextView.scroll 79 | 80 | _, _, _, h := perfTextView.GetInnerRect() 81 | perfTextView.currentViewHeight = h 82 | perfTextView.SetMaxLines(h) 83 | 84 | perfTextView.render(scroll, perfTextView.virtualLine, perfTextView.currentViewHeight) 85 | } 86 | 87 | func (perfTextView *PerfTextView) ScrollTo(line int) { 88 | perfTextView.scrollTo(line, false) 89 | } 90 | 91 | func (perfTextView *PerfTextView) JumpTo(line int) { 92 | perfTextView.scrollTo(line, true) 93 | } 94 | 95 | func (perfTextView *PerfTextView) SetTextP(text string, lineIndices []int) { 96 | 97 | perfTextView.fullText = text 98 | perfTextView.lineIndices = lineIndices 99 | _, _, _, h := perfTextView.GetInnerRect() 100 | perfTextView.currentViewHeight = h - 1 101 | perfTextView.SetMaxLines(h) 102 | 103 | var strIndx = 0 104 | if len(lineIndices) > perfTextView.currentViewHeight { 105 | strIndx = lineIndices[perfTextView.currentViewHeight] 106 | } else { 107 | strIndx = len(lineIndices) - 1 108 | } 109 | perfTextView.SetText(perfTextView.fullText[:strIndx]) 110 | } 111 | 112 | func (perfTextView *PerfTextView) SetLineColumn(column *LineColumn) { 113 | perfTextView.lineColumn = column 114 | } 115 | -------------------------------------------------------------------------------- /preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilmari-h/dlvtui/aad6f81595d5b16deb5d9ec25e45d2b3f5c2141a/preview.gif -------------------------------------------------------------------------------- /stackpage.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/gdamore/tcell/v2" 8 | "github.com/go-delve/delve/service/api" 9 | "github.com/rivo/tview" 10 | ) 11 | 12 | type StackPage struct { 13 | commandHandler *CommandHandler 14 | listView *tview.List 15 | widget *tview.Frame 16 | } 17 | 18 | func NewStackPage() *StackPage { 19 | listView := tview.NewList() 20 | listView.SetBackgroundColor(tcell.ColorDefault) 21 | 22 | selectedStyle := tcell.StyleDefault. 23 | Foreground(iToColorTcell(gConfig.Colors.LineFg)). 24 | Background(iToColorTcell(gConfig.Colors.ListSelectedBg)). 25 | Attributes(tcell.AttrBold) 26 | 27 | listView.SetSelectedStyle(selectedStyle) 28 | listView.SetInputCapture(listInputCaptureC) 29 | 30 | pageFrame := tview.NewFrame(listView). 31 | SetBorders(0, 0, 0, 0, 0, 0). 32 | AddText("[::b]Call stack:", true, tview.AlignLeft, iToColorTcell(gConfig.Colors.HeaderFg)) 33 | pageFrame.SetBackgroundColor(tcell.ColorDefault) 34 | 35 | sp := StackPage{ 36 | listView: listView, 37 | widget: pageFrame, 38 | } 39 | return &sp 40 | } 41 | 42 | func (sp *StackPage) RenderStack(stack []api.Stackframe, curr *api.Stackframe) { 43 | sp.listView.Clear() 44 | selectedI := 0 45 | for i, frame := range stack { 46 | if curr.Line == frame.Line && curr.File == frame.File { 47 | selectedI = i 48 | } 49 | 50 | // Format header 51 | fullName := frame.Function.Name() 52 | dotIdx := strings.Index(fullName, ".") 53 | pkgName := fullName[:dotIdx] 54 | functionName := fullName[dotIdx:] 55 | header := fmt.Sprintf("[%s::-]%s[%s::-]%s", 56 | iToColorS(gConfig.Colors.VarValueFg), 57 | pkgName, 58 | iToColorS(gConfig.Colors.VarTypeFg), 59 | functionName, 60 | ) 61 | 62 | sp.listView.AddItem( 63 | header, 64 | fmt.Sprintf("[%s]%s[white]:%d", 65 | iToColorS(gConfig.Colors.VarNameFg), 66 | frame.File, 67 | frame.Line, 68 | ), 69 | rune(48+i), 70 | nil). 71 | SetSelectedFunc(func(i int, s1, s2 string, r rune) { 72 | sp.commandHandler.RunCommand(&OpenFile{ 73 | File: stack[i].File, 74 | AtLine: stack[i].Line - 1, 75 | }) 76 | }) 77 | } 78 | sp.listView.SetCurrentItem(selectedI) 79 | } 80 | 81 | func (sp *StackPage) GetWidget() tview.Primitive { 82 | return sp.widget 83 | } 84 | 85 | func (sp *StackPage) GetName() string { 86 | return "stack" 87 | } 88 | 89 | func (page *StackPage) SetCommandHandler(ch *CommandHandler) { 90 | page.commandHandler = ch 91 | } 92 | 93 | func (sp *StackPage) HandleKeyEvent(event *tcell.EventKey) *tcell.EventKey { 94 | if keyPressed(event, gConfig.Keys.LineDown) { 95 | sp.listView.SetCurrentItem(sp.listView.GetCurrentItem() + 1) 96 | return nil 97 | } 98 | if keyPressed(event, gConfig.Keys.LineUp) { 99 | if sp.listView.GetCurrentItem() > 0 { 100 | sp.listView.SetCurrentItem(sp.listView.GetCurrentItem() - 1) 101 | } 102 | return nil 103 | } 104 | sp.listView.InputHandler()(event, func(p tview.Primitive) {}) 105 | return nil 106 | } 107 | -------------------------------------------------------------------------------- /tui.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "strings" 7 | "time" 8 | 9 | "github.com/ilmari-h/dlvtui/nav" 10 | 11 | "github.com/gdamore/tcell/v2" 12 | "github.com/go-delve/delve/service/api" 13 | "github.com/go-delve/delve/service/rpc2" 14 | "github.com/rivo/tview" 15 | log "github.com/sirupsen/logrus" 16 | ) 17 | 18 | type Mode int 19 | 20 | const ( 21 | Normal Mode = iota 22 | Cmd 23 | ) 24 | 25 | type KeyPress struct { 26 | mode Mode 27 | event *tcell.EventKey 28 | } 29 | 30 | type DebuggerStep struct { 31 | locals []api.Variable 32 | args []api.Variable 33 | globals []api.Variable 34 | } 35 | 36 | type DebuggerMove struct { 37 | DbgState *api.DebuggerState 38 | Stack []api.Stackframe 39 | } 40 | 41 | type View struct { 42 | nwBlocking bool 43 | 44 | commandChan chan string 45 | keyHandler KeyHandler 46 | currentMode Mode 47 | fileChan chan *nav.File 48 | 49 | pageView *PageView 50 | masterView *tview.Flex 51 | currentPage int 52 | 53 | cmdLine *tview.InputField 54 | indicatorText *tview.TextView 55 | cmdHandler *CommandHandler 56 | 57 | notificationLine *tview.TextView 58 | 59 | dbgMoveChan chan *DebuggerMove 60 | breakpointChan chan *nav.UiBreakpoint 61 | navState *nav.Nav 62 | 63 | goroutineChan chan []*api.Goroutine 64 | } 65 | 66 | func parseCommand(input string) LineCommand { 67 | if len(input) == 0 { 68 | return nil 69 | } 70 | args := strings.Fields(input) 71 | command := args[0] 72 | cargs := args[1:] 73 | return StringToLineCommand(command, cargs) 74 | } 75 | 76 | type KeyHandler struct { 77 | app *tview.Application 78 | view *View 79 | prevKey *tcell.EventKey 80 | } 81 | 82 | func (keyHandler *KeyHandler) handleKeyEvent(kp KeyPress) *tcell.EventKey { 83 | view := keyHandler.view 84 | rune := kp.event.Rune() 85 | key := kp.event.Key() 86 | keyHandler.prevKey = kp.event 87 | 88 | // Block events if there's a notification prompt. 89 | if view.notificationLine.GetText(true) != "" { 90 | if key == tcell.KeyEnter { 91 | view.clearNotification() 92 | } 93 | return nil 94 | } 95 | 96 | if rune == ':' { 97 | view.toCmdMode() 98 | return nil 99 | } 100 | if key == tcell.KeyEscape { // This is only relevant when typing commands 101 | view.toNormalMode() 102 | return nil 103 | } 104 | 105 | // Parse and run command from line input 106 | if key == tcell.KeyEnter && view.cmdLine.HasFocus() { 107 | linetext := view.cmdLine.GetText() 108 | view.toNormalMode() 109 | command := parseCommand(linetext) 110 | if command != nil { 111 | view.cmdHandler.RunCommand(command) 112 | } 113 | return nil 114 | } 115 | 116 | if view.currentMode != Cmd { 117 | // Delegate to page view, which either changes page or delegates to current page. 118 | return view.pageView.HandleKeyEvent(kp.event) 119 | } 120 | return kp.event 121 | } 122 | 123 | func (view *View) SetBlocking(blocking bool) { 124 | view.nwBlocking = blocking 125 | if blocking { 126 | view.indicatorText.SetText(fmt.Sprintf("%s ", gConfig.Icons.IndRunning)) 127 | } else { 128 | view.indicatorText.SetText(fmt.Sprintf("%s ", gConfig.Icons.IndStopped)) 129 | } 130 | } 131 | 132 | /** 133 | * Open a file. 134 | * If file has been opened previously, resume on that line. Otherwise open at 135 | * the first line. 136 | */ 137 | func (view *View) OpenFile(file *nav.File, atLine int) { 138 | view.navState.ChangeCurrentFile(file) 139 | view.navState.SetLine(atLine) 140 | view.pageView.LoadFile(file, atLine) 141 | 142 | // Render current stack frame if one is selected. 143 | if view.navState.CurrentStackFrame != nil { 144 | view.pageView.RenderStack( 145 | view.navState.CurrentStack, 146 | view.navState.CurrentStackFrame, 147 | view.navState.DbgState.CurrentThread.ReturnValues) 148 | } 149 | } 150 | 151 | /** 152 | * Listen to debugger state related messages from channels. 153 | * These usually result in re-rendering of some ui elements. 154 | */ 155 | func (view *View) uiEventLoop() { 156 | for { 157 | select { 158 | case dbgMove := <-view.dbgMoveChan: 159 | view.onDebuggerMove(dbgMove) 160 | case newFile := <-view.fileChan: 161 | view.onNewFile(newFile) 162 | case activeGoroutines := <-view.goroutineChan: 163 | view.onNewGoroutines(activeGoroutines) 164 | case newBp := <-view.breakpointChan: 165 | view.onNewBreakpoint(newBp) 166 | } 167 | } 168 | } 169 | 170 | // Render that's done before a continue command, essentially just refresh current pages. 171 | func (view *View) renderPendingContinue() { 172 | 173 | view.navState.CurrentDebuggerPos = nav.DebuggerPos{File: "", Line: -1} 174 | view.pageView.RenderBreakpoints(view.navState.GetAllBreakpoints()) 175 | view.pageView.RefreshCodePage() 176 | } 177 | 178 | /** 179 | * Render a debugger move. 180 | * A debugger move is any operation where the current line or file changes, and 181 | * the current stack frame may also have new variables or function calls. 182 | */ 183 | func (view *View) onDebuggerMove(dbgMove *DebuggerMove) { 184 | newState := dbgMove.DbgState 185 | line := newState.CurrentThread.Line 186 | file := newState.CurrentThread.File 187 | view.navState.DbgState = newState 188 | view.navState.CurrentDebuggerPos = nav.DebuggerPos{File: file, Line: line} 189 | 190 | // Navigate to file and update call stack. 191 | log.Printf("Debugger move inside file %s on line %d.", file, line-1) 192 | view.OpenFile(view.navState.FileCache[file], line-1) 193 | 194 | if len(dbgMove.Stack) > 0 { 195 | view.navState.CurrentStack = dbgMove.Stack 196 | view.navState.CurrentStackFrame = &dbgMove.Stack[0] 197 | } 198 | 199 | // If hit breakpoint. 200 | if newState.CurrentThread.BreakpointInfo != nil { 201 | 202 | log.Printf("Hit breakpoint in %s on line %d.", file, line) 203 | 204 | // Update breakpoint that was hit 205 | view.navState.Breakpoints[file][line] = &nav.UiBreakpoint{false, newState.CurrentThread.Breakpoint} 206 | view.pageView.RenderBreakpointHit(dbgMove.DbgState.CurrentThread.BreakpointInfo) 207 | } 208 | 209 | // Update pages. 210 | view.pageView.RenderBreakpoints(view.navState.GetAllBreakpoints()) 211 | view.pageView.RenderStack( 212 | view.navState.CurrentStack, 213 | view.navState.CurrentStackFrame, 214 | view.navState.DbgState.CurrentThread.ReturnValues) 215 | view.pageView.RenderJumpToLine(line - 1) 216 | } 217 | 218 | func (view *View) onNewFile(newFile *nav.File) { 219 | view.OpenFile( 220 | newFile, 221 | view.navState.LineInFile(newFile.Path), 222 | ) 223 | } 224 | 225 | func (view *View) onNewBreakpoint(newBp *nav.UiBreakpoint) { 226 | 227 | log.Printf("Got breakpoint in %s on line %d!", newBp.File, newBp.Line) 228 | 229 | // ID -1 signifies deleted breakpoint. 230 | if newBp.ID == -1 { 231 | delete(view.navState.Breakpoints[newBp.File], newBp.Line) 232 | view.pageView.RefreshCodePage() 233 | return 234 | } 235 | 236 | if len(view.navState.Breakpoints[newBp.File]) == 0 { 237 | view.navState.Breakpoints[newBp.File] = make(map[int]*nav.UiBreakpoint) 238 | } 239 | 240 | view.navState.Breakpoints[newBp.File][newBp.Line] = newBp 241 | view.pageView.RenderBreakpoints(view.navState.GetAllBreakpoints()) 242 | view.pageView.RefreshCodePage() 243 | } 244 | 245 | func (view *View) onNewGoroutines(activeGoroutines []*api.Goroutine) { 246 | view.navState.Goroutines = activeGoroutines 247 | view.pageView.goroutinePage.RenderGoroutines( 248 | activeGoroutines, 249 | view.navState.DbgState.CurrentThread.GoroutineID, 250 | ) 251 | } 252 | 253 | func (view *View) toNormalMode() { 254 | view.cmdLine.SetAutocompleteFunc(func(currentText string) (entries []string) { 255 | return []string{} 256 | }) 257 | view.cmdLine.SetLabel("") 258 | view.cmdLine.SetText("") 259 | view.keyHandler.app.SetFocus(view.masterView) 260 | view.currentMode = Normal 261 | } 262 | 263 | func (view *View) toCmdMode() { 264 | view.cmdLine.SetAutocompleteFunc(view.cmdHandler.GetSuggestions) 265 | view.cmdLine.SetLabel(":") 266 | view.keyHandler.app.SetFocus(view.cmdLine) 267 | view.currentMode = Cmd 268 | } 269 | 270 | func (view *View) clearNotification() { 271 | view.notificationLine.SetText("") 272 | view.masterView.ResizeItem(view.notificationLine, 0, 0) 273 | view.masterView.ResizeItem(view.pageView.GetWidget(), 0, 1) 274 | go func() { 275 | time.Sleep(time.Second / 10) 276 | view.pageView.RefreshCodePage() 277 | }() 278 | } 279 | 280 | func (view *View) notifyProgramEnded(exitCode int) { 281 | msg := fmt.Sprintf("Program has finished with exit status %d.", exitCode) 282 | log.Print(msg) 283 | view.showNotification(msg, false) 284 | if exitCode == 0 { 285 | view.indicatorText.SetText(fmt.Sprintf("%s ", gConfig.Icons.IndExitSuccess)) 286 | } else { 287 | view.indicatorText.SetText(fmt.Sprintf("%s %d ", gConfig.Icons.IndExitError, exitCode)) 288 | } 289 | } 290 | 291 | func (view *View) showNotification(msg string, error bool) { 292 | msgLen := len(msg) 293 | msg += fmt.Sprintf("\n[%s::b]Press Enter to continue", iToColorS(gConfig.Colors.NotifPromptFg)) 294 | if error { 295 | msgLen += 7 296 | view.notificationLine.SetText(fmt.Sprintf("[%s::b]Error[%s:-:-]: %s", 297 | iToColorS(gConfig.Colors.NotifErrorFg), 298 | iToColorS(gConfig.Colors.NotifMsgFg), 299 | msg, 300 | )) 301 | } else { 302 | view.notificationLine.SetText(fmt.Sprintf("[%s]%s", iToColorS(gConfig.Colors.NotifMsgFg), msg)) 303 | } 304 | _, _, boxWidth, _ := view.notificationLine.GetRect() 305 | lines := int(math.Ceil(float64(msgLen) / float64(boxWidth))) 306 | _, _, _, linesText := view.pageView.pagesView.GetInnerRect() 307 | view.masterView.ResizeItem(view.notificationLine, lines+1, 1) 308 | view.masterView.ResizeItem(view.pageView.GetWidget(), linesText-lines, 1) 309 | view.pageView.ResizeCodePage(linesText - lines - 1) 310 | } 311 | 312 | func CreateTui(app *tview.Application, navState *nav.Nav, rpcClient *rpc2.RPCClient) View { 313 | 314 | var view = View{ 315 | nwBlocking: false, 316 | commandChan: make(chan string, 1024), 317 | fileChan: make(chan *nav.File, 1024), 318 | dbgMoveChan: make(chan *DebuggerMove, 1024), 319 | goroutineChan: make(chan []*api.Goroutine, 1024), 320 | breakpointChan: make(chan *nav.UiBreakpoint, 1024), 321 | navState: navState, 322 | currentMode: Normal, 323 | pageView: nil, 324 | } 325 | 326 | view.cmdHandler = NewCommandHandler(&view, app, rpcClient) 327 | view.pageView = NewPageView(view.cmdHandler, navState, app) 328 | view.keyHandler = KeyHandler{app: app, view: &view} 329 | 330 | flex := tview.NewFlex().SetDirection(tview.FlexRow) 331 | flex.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { 332 | return view.keyHandler.handleKeyEvent(KeyPress{event: event, mode: view.currentMode}) 333 | }) 334 | 335 | commandLine := tview.NewInputField(). 336 | SetLabel(""). 337 | SetFieldWidth(0). 338 | SetDoneFunc(func(key tcell.Key) { 339 | event := tcell.NewEventKey(key, 0, tcell.ModNone) 340 | view.keyHandler.handleKeyEvent(KeyPress{event: event, mode: Cmd}) 341 | }) 342 | notificationLine := tview.NewTextView(). 343 | SetDynamicColors(true). 344 | SetText("") 345 | notificationLine.SetBackgroundColor(tcell.ColorDefault) 346 | notificationLine.SetWordWrap(true) 347 | 348 | commandLine.SetBackgroundColor(tcell.ColorDefault) 349 | commandLine.SetFieldBackgroundColor(tcell.ColorDefault) 350 | 351 | suggestionStyle := tcell.StyleDefault. 352 | Foreground(iToColorTcell(gConfig.Colors.MenuFg)). 353 | Background(iToColorTcell(gConfig.Colors.MenuBg)) 354 | 355 | suggestionStyleSelected := tcell.StyleDefault. 356 | Foreground(iToColorTcell(gConfig.Colors.MenuSelectedFg)). 357 | Background(iToColorTcell(gConfig.Colors.MenuSelectedBg)). 358 | Attributes(tcell.AttrBold) 359 | 360 | commandLine.SetAutocompleteStyles(iToColorTcell(gConfig.Colors.MenuBg), suggestionStyle, suggestionStyleSelected) 361 | 362 | indicatorText := tview.NewTextView() 363 | indicatorText.SetBackgroundColor(tcell.ColorDefault) 364 | indicatorText.SetTextAlign(tview.AlignRight) 365 | indicatorText.SetText(fmt.Sprintf("%s ", gConfig.Icons.IndStopped)) 366 | 367 | bottomRow := tview.NewFlex(). 368 | AddItem(commandLine, 0, 1, false). 369 | AddItem(indicatorText, 5, 1, false) 370 | 371 | flex.AddItem(view.pageView.GetWidget(), 0, 1, false) 372 | flex.AddItem(notificationLine, 0, 0, false) 373 | flex.AddItem(bottomRow, 1, 1, false) 374 | 375 | app.SetRoot(flex, true).SetFocus(flex) 376 | 377 | view.masterView = flex 378 | view.cmdLine = commandLine 379 | view.indicatorText = indicatorText 380 | view.notificationLine = notificationLine 381 | 382 | go view.uiEventLoop() 383 | go func() { 384 | rpcClient.SetReturnValuesLoadConfig(&defaultConfig) 385 | }() 386 | go view.cmdHandler.RunCommand(&GetBreakpoints{}) 387 | 388 | return view 389 | } 390 | -------------------------------------------------------------------------------- /varspage.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/gdamore/tcell/v2" 7 | "github.com/go-delve/delve/service/api" 8 | "github.com/rivo/tview" 9 | ) 10 | 11 | type VarsPage struct { 12 | widget tview.Primitive 13 | commandHandler *CommandHandler 14 | 15 | treeView *tview.TreeView 16 | locals *tview.TreeNode 17 | 18 | args *tview.TreeNode 19 | 20 | returns *tview.TreeNode 21 | 22 | varHeaders []*tview.TreeNode 23 | varHeaderIdx int 24 | 25 | expandedCache map[uint64]bool 26 | 27 | lastSelected struct { 28 | exists bool 29 | val api.Variable 30 | } 31 | } 32 | 33 | func NewVarPage() *VarsPage { 34 | 35 | root := tview.NewTreeNode("."). 36 | SetColor(tcell.ColorDefault). 37 | SetSelectable(false) 38 | treeView := tview.NewTreeView(). 39 | SetRoot(root) 40 | treeView.SetBackgroundColor(tcell.ColorDefault) 41 | treeView.SetInputCapture(listInputCaptureC) 42 | 43 | localsHeader := tview.NewTreeNode(fmt.Sprintf("[%s::b]locals", 44 | iToColorS(gConfig.Colors.ListHeaderFg), 45 | )). 46 | SetColor(iToColorTcell(gConfig.Colors.ListHeaderFg)). 47 | SetSelectable(false) 48 | 49 | argsHeader := tview.NewTreeNode(fmt.Sprintf("[%s::b]arguments", 50 | iToColorS(gConfig.Colors.ListHeaderFg), 51 | )). 52 | SetColor(tcell.ColorGreen). 53 | SetSelectable(false) 54 | 55 | returnsHeader := tview.NewTreeNode(fmt.Sprintf("[%s::b]return values", 56 | iToColorS(gConfig.Colors.ListHeaderFg), 57 | )). 58 | SetColor(iToColorTcell(gConfig.Colors.ListHeaderFg)). 59 | SetSelectable(false) 60 | 61 | treeView.GetRoot().AddChild(localsHeader) 62 | treeView.GetRoot().AddChild(argsHeader) 63 | treeView.GetRoot().AddChild(returnsHeader) 64 | 65 | pageFrame := tview.NewFrame(treeView). 66 | SetBorders(0, 0, 0, 0, 0, 0). 67 | AddText(fmt.Sprintf("[%s::b]Current stack frame:", iToColorS(gConfig.Colors.HeaderFg)), 68 | true, 69 | tview.AlignLeft, 70 | tcell.ColorWhite, 71 | ) 72 | pageFrame.SetBackgroundColor(tcell.ColorDefault) 73 | 74 | return &VarsPage{ 75 | widget: pageFrame, 76 | 77 | treeView: treeView, 78 | locals: localsHeader, 79 | args: argsHeader, 80 | returns: returnsHeader, 81 | 82 | varHeaders: []*tview.TreeNode{localsHeader, argsHeader, returnsHeader}, 83 | varHeaderIdx: 0, 84 | expandedCache: make(map[uint64]bool), 85 | } 86 | } 87 | 88 | func (page *VarsPage) RenderVariables(args []api.Variable, locals []api.Variable, returns []api.Variable) { 89 | 90 | page.locals.ClearChildren() 91 | page.args.ClearChildren() 92 | page.returns.ClearChildren() 93 | 94 | page.AddVars(page.locals, locals) 95 | page.AddVars(page.args, args) 96 | page.AddVars(page.returns, returns) 97 | 98 | if !page.lastSelected.exists { 99 | foundSelectable := false 100 | page.treeView.GetRoot().Walk(func(node, parent *tview.TreeNode) bool { 101 | if !foundSelectable && node.GetReference() != nil { 102 | foundSelectable = true 103 | page.treeView.SetCurrentNode(node) 104 | } 105 | return !foundSelectable 106 | }) 107 | } 108 | } 109 | 110 | func getVarTitle(vr *api.Variable, expanded bool) string { 111 | namestr := fmt.Sprintf("[%s::b]%s", iToColorS(gConfig.Colors.VarNameFg), vr.Name) 112 | typestr := fmt.Sprintf("[%s]<%s>[%s:-:-]", 113 | iToColorS(gConfig.Colors.VarTypeFg), 114 | vr.RealType, 115 | iToColorS(gConfig.Colors.VarValueFg), 116 | ) 117 | valstr := "" 118 | addrstr := fmt.Sprintf("[%s] 0x%x", iToColorS(gConfig.Colors.VarAddrFg), vr.Addr) 119 | if vr.Value != "" { 120 | valstr += fmt.Sprintf(" %s", vr.Value) 121 | } 122 | suffix := "" 123 | if vr.Children != nil && len(vr.Children) > 0 { 124 | suffix = fmt.Sprintf(" [%s]", iToColorS(gConfig.Colors.ListExpand)) 125 | if expanded { 126 | suffix += "-" 127 | } else { 128 | suffix += "+" 129 | } 130 | } 131 | return namestr + typestr + valstr + suffix + addrstr 132 | } 133 | 134 | func (page *VarsPage) AddVars(parent *tview.TreeNode, vars []api.Variable) { 135 | 136 | addedLocals := 0 137 | addedArgs := 0 138 | for _, vr := range vars { 139 | newNode := tview.NewTreeNode(getVarTitle(&vr, page.expandedCache[vr.Addr])). 140 | SetReference(vr) 141 | newNode.SetSelectable(true) 142 | newNode.SetColor(tcell.ColorBlack) 143 | 144 | if vr.Addr == page.lastSelected.val.Addr { 145 | page.treeView.SetCurrentNode(newNode) 146 | } 147 | 148 | if parent == page.locals { 149 | addedLocals++ 150 | } else if parent == page.args { 151 | addedArgs++ 152 | } 153 | 154 | // If node has children, initially collapse. Expand on select. 155 | if vr.Children != nil && len(vr.Children) > 0 { 156 | page.AddVars(newNode, vr.Children) 157 | 158 | // Expand or collapse node according to what was cached from previous action. 159 | if !page.expandedCache[vr.Addr] { 160 | newNode.CollapseAll() 161 | } else { 162 | newNode.Expand() 163 | } 164 | 165 | newNode.SetSelectedFunc(func() { 166 | r := newNode.GetReference().(api.Variable) 167 | page.expandedCache[r.Addr] = !newNode.IsExpanded() 168 | if !newNode.IsExpanded() { 169 | newNode.Expand() 170 | } else { 171 | newNode.Collapse() 172 | } 173 | newNode.SetText(getVarTitle(&r, page.expandedCache[r.Addr])) 174 | }) 175 | } 176 | parent.AddChild(newNode) 177 | } 178 | } 179 | 180 | func (varsView *VarsPage) GetName() string { 181 | return "vars" 182 | } 183 | 184 | func (page *VarsPage) HandleKeyEvent(event *tcell.EventKey) *tcell.EventKey { 185 | 186 | page.treeView.InputHandler()(event, func(p tview.Primitive) {}) 187 | if page.treeView.GetCurrentNode() != nil && page.treeView.GetCurrentNode().GetReference() != nil { 188 | page.lastSelected.val = page.treeView.GetCurrentNode().GetReference().(api.Variable) 189 | page.lastSelected.exists = true 190 | } 191 | return nil 192 | } 193 | 194 | func (page *VarsPage) SetCommandHandler(ch *CommandHandler) { 195 | page.commandHandler = ch 196 | } 197 | 198 | func (page *VarsPage) GetWidget() tview.Primitive { 199 | return page.widget 200 | } 201 | --------------------------------------------------------------------------------