├── .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 |
--------------------------------------------------------------------------------