未验证 提交 b120b11c 编写于 作者: A Alessandro Arzilli 提交者: GitHub

terminal: add optional format argument to print, display (#2398)

Changes print so a format argument can be specified by using '%' as
prefix. For example:

    print %x d

will print variable 'd' in hexadecimal. The interpretarion of the
format argument is the same as that of fmt's package.

Fixes #1038
Fixes #1800
Fixes #2159
上级 2414dcdd
......@@ -242,7 +242,7 @@ Aliases: disass
## display
Print value of an expression every time the program stops.
display -a <expression>
display -a [%format] <expression>
display -d <number>
The '-a' option adds an expression to the list of expression printed every time the program stops. The '-d' option removes the specified expression from the list.
......@@ -409,10 +409,12 @@ Supported commands: print, stack and goroutine)
## print
Evaluate an expression.
[goroutine <n>] [frame <m>] print <expression>
[goroutine <n>] [frame <m>] print [%format] <expression>
See [Documentation/cli/expr.md](//github.com/go-delve/delve/tree/master/Documentation/cli/expr.md) for a description of supported expressions.
The optional format argument is a format specifier, like the ones used by the fmt package. For example "print %x v" will print v as an hexadecimal number.
Aliases: p
## rebuild
......
......@@ -229,9 +229,11 @@ Called with more arguments it will execute a command on the specified goroutine.
{aliases: []string{"breakpoints", "bp"}, group: breakCmds, cmdFn: breakpoints, helpMsg: "Print out info for active breakpoints."},
{aliases: []string{"print", "p"}, group: dataCmds, allowedPrefixes: onPrefix | deferredPrefix, cmdFn: printVar, helpMsg: `Evaluate an expression.
[goroutine <n>] [frame <m>] print <expression>
[goroutine <n>] [frame <m>] print [%format] <expression>
See $GOPATH/src/github.com/go-delve/delve/Documentation/cli/expr.md for a description of supported expressions.`},
See $GOPATH/src/github.com/go-delve/delve/Documentation/cli/expr.md for a description of supported expressions.
The optional format argument is a format specifier, like the ones used by the fmt package. For example "print %x v" will print v as an hexadecimal number.`},
{aliases: []string{"whatis"}, group: dataCmds, cmdFn: whatisCommand, helpMsg: `Prints type of an expression.
whatis <expression>`},
......@@ -417,7 +419,7 @@ For example:
{aliases: []string{"display"}, group: dataCmds, cmdFn: display, helpMsg: `Print value of an expression every time the program stops.
display -a <expression>
display -a [%format] <expression>
display -d <number>
The '-a' option adds an expression to the list of expression printed every time the program stops. The '-d' option removes the specified expression from the list.
......@@ -1743,6 +1745,17 @@ func examineMemoryCmd(t *Term, ctx callContext, args string) error {
return nil
}
func parseFormatArg(args string) (fmtstr, argsOut string) {
if len(args) < 1 || args[0] != '%' {
return "", args
}
v := strings.SplitN(args, " ", 2)
if len(v) == 1 {
return v[0], ""
}
return v[0], v[1]
}
func printVar(t *Term, ctx callContext, args string) error {
if len(args) == 0 {
return fmt.Errorf("not enough arguments")
......@@ -1751,12 +1764,13 @@ func printVar(t *Term, ctx callContext, args string) error {
ctx.Breakpoint.Variables = append(ctx.Breakpoint.Variables, args)
return nil
}
fmtstr, args := parseFormatArg(args)
val, err := t.client.EvalVariable(ctx.Scope, args, t.loadConfig())
if err != nil {
return err
}
fmt.Println(val.MultilineString(""))
fmt.Println(val.MultilineString("", fmtstr))
return nil
}
......@@ -1816,7 +1830,7 @@ func printFilteredVariables(varType string, vars []api.Variable, filter string,
if cfg == ShortLoadConfig {
fmt.Printf("%s = %s\n", name, v.SinglelineString())
} else {
fmt.Printf("%s = %s\n", name, v.MultilineString(""))
fmt.Printf("%s = %s\n", name, v.MultilineString("", ""))
}
}
}
......@@ -2326,7 +2340,7 @@ func printReturnValues(th *api.Thread) {
}
fmt.Println("Values returned:")
for _, v := range th.ReturnValues {
fmt.Printf("\t%s: %s\n", v.Name, v.MultilineString("\t"))
fmt.Printf("\t%s: %s\n", v.Name, v.MultilineString("\t", ""))
}
fmt.Println()
}
......@@ -2426,13 +2440,13 @@ func printBreakpointInfo(t *Term, th *api.Thread, tracepointOnNewline bool) {
for _, v := range bpi.Variables {
tracepointnl()
fmt.Printf("\t%s: %s\n", v.Name, v.MultilineString("\t"))
fmt.Printf("\t%s: %s\n", v.Name, v.MultilineString("\t", ""))
}
for _, v := range bpi.Locals {
tracepointnl()
if *bp.LoadLocals == longLoadConfig {
fmt.Printf("\t%s: %s\n", v.Name, v.MultilineString("\t"))
fmt.Printf("\t%s: %s\n", v.Name, v.MultilineString("\t", ""))
} else {
fmt.Printf("\t%s: %s\n", v.Name, v.SinglelineString())
}
......@@ -2441,7 +2455,7 @@ func printBreakpointInfo(t *Term, th *api.Thread, tracepointOnNewline bool) {
if bp.LoadArgs != nil && *bp.LoadArgs == longLoadConfig {
for _, v := range bpi.Arguments {
tracepointnl()
fmt.Printf("\t%s: %s\n", v.Name, v.MultilineString("\t"))
fmt.Printf("\t%s: %s\n", v.Name, v.MultilineString("\t", ""))
}
}
......@@ -2667,10 +2681,11 @@ func display(t *Term, ctx callContext, args string) error {
case strings.HasPrefix(args, addOption):
args = strings.TrimSpace(args[len(addOption):])
fmtstr, args := parseFormatArg(args)
if args == "" {
return fmt.Errorf("not enough arguments")
}
t.addDisplay(args)
t.addDisplay(args, fmtstr)
t.printDisplay(len(t.displays) - 1)
case strings.HasPrefix(args, delOption):
......
......@@ -1151,3 +1151,13 @@ func TestContinueUntil(t *testing.T) {
listIsAt(t, term, "continue main.sayhi", 12, -1, -1)
})
}
func TestPrintFormat(t *testing.T) {
withTestTerminal("testvariables2", t, func(term *FakeTerminal) {
term.MustExec("continue")
out := term.MustExec("print %#x m2[1].B")
if !strings.Contains(out, "0xb\n") {
t.Fatalf("output did not contain '0xb': %q", out)
}
})
}
......@@ -54,7 +54,7 @@ type Term struct {
cmds *Commands
stdout io.Writer
InitFile string
displays []string
displays []displayEntry
colorEscapes map[colorize.Style]string
historyFile *os.File
......@@ -74,6 +74,11 @@ type Term struct {
quitting bool
}
type displayEntry struct {
expr string
fmtstr string
}
// New returns a new Term.
func New(client service.Client, conf *config.Config) *Term {
cmds := DebugCommands(client)
......@@ -456,9 +461,9 @@ func (t *Term) removeDisplay(n int) error {
if n < 0 || n >= len(t.displays) {
return fmt.Errorf("%d is out of range", n)
}
t.displays[n] = ""
t.displays[n] = displayEntry{"", ""}
for i := len(t.displays) - 1; i >= 0; i-- {
if t.displays[i] != "" {
if t.displays[i].expr != "" {
t.displays = t.displays[:i+1]
return nil
}
......@@ -467,12 +472,12 @@ func (t *Term) removeDisplay(n int) error {
return nil
}
func (t *Term) addDisplay(expr string) {
t.displays = append(t.displays, expr)
func (t *Term) addDisplay(expr, fmtstr string) {
t.displays = append(t.displays, displayEntry{expr: expr, fmtstr: fmtstr})
}
func (t *Term) printDisplay(i int) {
expr := t.displays[i]
expr, fmtstr := t.displays[i].expr, t.displays[i].fmtstr
val, err := t.client.EvalVariable(api.EvalScope{GoroutineID: -1}, expr, ShortLoadConfig)
if err != nil {
if isErrProcessExited(err) {
......@@ -481,12 +486,12 @@ func (t *Term) printDisplay(i int) {
fmt.Printf("%d: %s = error %v\n", i, expr, err)
return
}
fmt.Printf("%d: %s = %s\n", i, val.Name, val.SinglelineString())
fmt.Printf("%d: %s = %s\n", i, val.Name, val.SinglelineStringFormatted(fmtstr))
}
func (t *Term) printDisplays() {
for i := range t.displays {
if t.displays[i] != "" {
if t.displays[i].expr != "" {
t.printDisplay(i)
}
}
......
......@@ -20,18 +20,25 @@ const (
// SinglelineString returns a representation of v on a single line.
func (v *Variable) SinglelineString() string {
var buf bytes.Buffer
v.writeTo(&buf, true, false, true, "")
v.writeTo(&buf, true, false, true, "", "")
return buf.String()
}
// SinglelineStringFormatted returns a representation of v on a single line, using the format specified by fmtstr.
func (v *Variable) SinglelineStringFormatted(fmtstr string) string {
var buf bytes.Buffer
v.writeTo(&buf, true, false, true, "", fmtstr)
return buf.String()
}
// MultilineString returns a representation of v on multiple lines.
func (v *Variable) MultilineString(indent string) string {
func (v *Variable) MultilineString(indent, fmtstr string) string {
var buf bytes.Buffer
v.writeTo(&buf, true, true, true, indent)
v.writeTo(&buf, true, true, true, indent, fmtstr)
return buf.String()
}
func (v *Variable) writeTo(buf io.Writer, top, newlines, includeType bool, indent string) {
func (v *Variable) writeTo(buf io.Writer, top, newlines, includeType bool, indent, fmtstr string) {
if v.Unreadable != "" {
fmt.Fprintf(buf, "(unreadable %s)", v.Unreadable)
return
......@@ -48,9 +55,9 @@ func (v *Variable) writeTo(buf io.Writer, top, newlines, includeType bool, inden
switch v.Kind {
case reflect.Slice:
v.writeSliceTo(buf, newlines, includeType, indent)
v.writeSliceTo(buf, newlines, includeType, indent, fmtstr)
case reflect.Array:
v.writeArrayTo(buf, newlines, includeType, indent)
v.writeArrayTo(buf, newlines, includeType, indent, fmtstr)
case reflect.Ptr:
if v.Type == "" || len(v.Children) == 0 {
fmt.Fprint(buf, "nil")
......@@ -62,7 +69,7 @@ func (v *Variable) writeTo(buf io.Writer, top, newlines, includeType bool, inden
}
} else {
fmt.Fprint(buf, "*")
v.Children[0].writeTo(buf, false, newlines, includeType, indent)
v.Children[0].writeTo(buf, false, newlines, includeType, indent, fmtstr)
}
case reflect.UnsafePointer:
if len(v.Children) == 0 {
......@@ -70,11 +77,9 @@ func (v *Variable) writeTo(buf io.Writer, top, newlines, includeType bool, inden
} else {
fmt.Fprintf(buf, "unsafe.Pointer(%#x)", v.Children[0].Addr)
}
case reflect.String:
v.writeStringTo(buf)
case reflect.Chan:
if newlines {
v.writeStructTo(buf, newlines, includeType, indent)
v.writeStructTo(buf, newlines, includeType, indent, fmtstr)
} else {
if len(v.Children) == 0 {
fmt.Fprintf(buf, "%s nil", v.Type)
......@@ -83,7 +88,7 @@ func (v *Variable) writeTo(buf io.Writer, top, newlines, includeType bool, inden
}
}
case reflect.Struct:
v.writeStructTo(buf, newlines, includeType, indent)
v.writeStructTo(buf, newlines, includeType, indent, fmtstr)
case reflect.Interface:
if v.Addr == 0 {
// an escaped interface variable that points to nil, this shouldn't
......@@ -111,7 +116,7 @@ func (v *Variable) writeTo(buf io.Writer, top, newlines, includeType bool, inden
} else if data.Children[0].OnlyAddr {
fmt.Fprintf(buf, "0x%x", v.Children[0].Addr)
} else {
v.Children[0].writeTo(buf, false, newlines, !includeType, indent)
v.Children[0].writeTo(buf, false, newlines, !includeType, indent, fmtstr)
}
} else if data.OnlyAddr {
if strings.Contains(v.Type, "/") {
......@@ -120,36 +125,84 @@ func (v *Variable) writeTo(buf io.Writer, top, newlines, includeType bool, inden
fmt.Fprintf(buf, "*(*%s)(%#x)", v.Type, v.Addr)
}
} else {
v.Children[0].writeTo(buf, false, newlines, !includeType, indent)
v.Children[0].writeTo(buf, false, newlines, !includeType, indent, fmtstr)
}
case reflect.Map:
v.writeMapTo(buf, newlines, includeType, indent)
v.writeMapTo(buf, newlines, includeType, indent, fmtstr)
case reflect.Func:
if v.Value == "" {
fmt.Fprint(buf, "nil")
} else {
fmt.Fprintf(buf, "%s", v.Value)
}
case reflect.Complex64, reflect.Complex128:
fmt.Fprintf(buf, "(%s + %si)", v.Children[0].Value, v.Children[1].Value)
default:
if v.Value != "" {
buf.Write([]byte(v.Value))
} else {
fmt.Fprintf(buf, "(unknown %s)", v.Kind)
}
v.writeBasicType(buf, fmtstr)
}
}
func (v *Variable) writeStringTo(buf io.Writer) {
s := v.Value
if len(s) != int(v.Len) {
s = fmt.Sprintf("%s...+%d more", s, int(v.Len)-len(s))
func (v *Variable) writeBasicType(buf io.Writer, fmtstr string) {
if v.Value == "" && v.Kind != reflect.String {
fmt.Fprintf(buf, "(unknown %s)", v.Kind)
return
}
switch v.Kind {
case reflect.Bool:
if fmtstr == "" {
buf.Write([]byte(v.Value))
return
}
var b bool = v.Value == "true"
fmt.Fprintf(buf, fmtstr, b)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if fmtstr == "" {
buf.Write([]byte(v.Value))
return
}
n, _ := strconv.ParseInt(v.Value, 10, 64)
fmt.Fprintf(buf, fmtstr, n)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
if fmtstr == "" {
buf.Write([]byte(v.Value))
return
}
n, _ := strconv.ParseUint(v.Value, 10, 64)
fmt.Fprintf(buf, fmtstr, n)
case reflect.Float32, reflect.Float64:
if fmtstr == "" {
buf.Write([]byte(v.Value))
return
}
x, _ := strconv.ParseFloat(v.Value, 64)
fmt.Fprintf(buf, fmtstr, x)
case reflect.Complex64, reflect.Complex128:
if fmtstr == "" {
fmt.Fprintf(buf, "(%s + %si)", v.Children[0].Value, v.Children[1].Value)
return
}
real, _ := strconv.ParseFloat(v.Children[0].Value, 64)
imag, _ := strconv.ParseFloat(v.Children[1].Value, 64)
var x complex128 = complex(real, imag)
fmt.Fprintf(buf, fmtstr, x)
case reflect.String:
if fmtstr == "" {
s := v.Value
if len(s) != int(v.Len) {
s = fmt.Sprintf("%s...+%d more", s, int(v.Len)-len(s))
}
fmt.Fprintf(buf, "%q", s)
return
}
fmt.Fprintf(buf, fmtstr, v.Value)
}
fmt.Fprintf(buf, "%q", s)
}
func (v *Variable) writeSliceTo(buf io.Writer, newlines, includeType bool, indent string) {
func (v *Variable) writeSliceTo(buf io.Writer, newlines, includeType bool, indent, fmtstr string) {
if includeType {
fmt.Fprintf(buf, "%s len: %d, cap: %d, ", v.Type, v.Len, v.Cap)
}
......@@ -157,17 +210,17 @@ func (v *Variable) writeSliceTo(buf io.Writer, newlines, includeType bool, inden
fmt.Fprintf(buf, "nil")
return
}
v.writeSliceOrArrayTo(buf, newlines, indent)
v.writeSliceOrArrayTo(buf, newlines, indent, fmtstr)
}
func (v *Variable) writeArrayTo(buf io.Writer, newlines, includeType bool, indent string) {
func (v *Variable) writeArrayTo(buf io.Writer, newlines, includeType bool, indent, fmtstr string) {
if includeType {
fmt.Fprintf(buf, "%s ", v.Type)
}
v.writeSliceOrArrayTo(buf, newlines, indent)
v.writeSliceOrArrayTo(buf, newlines, indent, fmtstr)
}
func (v *Variable) writeStructTo(buf io.Writer, newlines, includeType bool, indent string) {
func (v *Variable) writeStructTo(buf io.Writer, newlines, includeType bool, indent, fmtstr string) {
if int(v.Len) != len(v.Children) && len(v.Children) == 0 {
if strings.Contains(v.Type, "/") {
fmt.Fprintf(buf, "(*%q)(%#x)", v.Type, v.Addr)
......@@ -190,7 +243,7 @@ func (v *Variable) writeStructTo(buf io.Writer, newlines, includeType bool, inde
fmt.Fprintf(buf, "\n%s%s", indent, indentString)
}
fmt.Fprintf(buf, "%s: ", v.Children[i].Name)
v.Children[i].writeTo(buf, false, nl, true, indent+indentString)
v.Children[i].writeTo(buf, false, nl, true, indent+indentString, fmtstr)
if i != len(v.Children)-1 || nl {
fmt.Fprint(buf, ",")
if !nl {
......@@ -211,7 +264,7 @@ func (v *Variable) writeStructTo(buf io.Writer, newlines, includeType bool, inde
fmt.Fprint(buf, "}")
}
func (v *Variable) writeMapTo(buf io.Writer, newlines, includeType bool, indent string) {
func (v *Variable) writeMapTo(buf io.Writer, newlines, includeType bool, indent, fmtstr string) {
if includeType {
fmt.Fprintf(buf, "%s ", v.Type)
}
......@@ -232,9 +285,9 @@ func (v *Variable) writeMapTo(buf io.Writer, newlines, includeType bool, indent
fmt.Fprintf(buf, "\n%s%s", indent, indentString)
}
key.writeTo(buf, false, false, false, indent+indentString)
key.writeTo(buf, false, false, false, indent+indentString, fmtstr)
fmt.Fprint(buf, ": ")
value.writeTo(buf, false, nl, false, indent+indentString)
value.writeTo(buf, false, nl, false, indent+indentString, fmtstr)
if i != len(v.Children)-1 || nl {
fmt.Fprint(buf, ", ")
}
......@@ -326,7 +379,7 @@ func (v *Variable) shouldNewlineStruct(newlines bool) bool {
return false
}
func (v *Variable) writeSliceOrArrayTo(buf io.Writer, newlines bool, indent string) {
func (v *Variable) writeSliceOrArrayTo(buf io.Writer, newlines bool, indent, fmtstr string) {
nl := v.shouldNewlineArray(newlines)
fmt.Fprint(buf, "[")
......@@ -334,7 +387,7 @@ func (v *Variable) writeSliceOrArrayTo(buf io.Writer, newlines bool, indent stri
if nl {
fmt.Fprintf(buf, "\n%s%s", indent, indentString)
}
v.Children[i].writeTo(buf, false, nl, false, indent+indentString)
v.Children[i].writeTo(buf, false, nl, false, indent+indentString, fmtstr)
if i != len(v.Children)-1 || nl {
fmt.Fprint(buf, ",")
}
......
......@@ -1061,7 +1061,7 @@ func Test1Issue406(t *testing.T) {
assertNoError(state.Err, t, "Continue()")
v, err := c.EvalVariable(api.EvalScope{GoroutineID: -1}, "cfgtree")
assertNoError(err, t, "EvalVariable()")
vs := v.MultilineString("")
vs := v.MultilineString("", "")
t.Logf("cfgtree formats to: %s\n", vs)
})
}
......@@ -1441,7 +1441,7 @@ func TestIssue406(t *testing.T) {
assertNoError(state.Err, t, "Continue()")
v, err := c.EvalVariable(api.EvalScope{GoroutineID: -1}, "cfgtree", normalLoadConfig)
assertNoError(err, t, "EvalVariable()")
vs := v.MultilineString("")
vs := v.MultilineString("", "")
t.Logf("cfgtree formats to: %s\n", vs)
})
}
......
......@@ -390,7 +390,7 @@ func TestMultilineVariableEvaluation(t *testing.T) {
for _, tc := range testcases {
variable, err := evalVariable(p, tc.name, pnormalLoadConfig)
assertNoError(err, t, "EvalVariable() returned an error")
if ms := api.ConvertVar(variable).MultilineString(""); !matchStringOrPrefix(ms, tc.value) {
if ms := api.ConvertVar(variable).MultilineString("", ""); !matchStringOrPrefix(ms, tc.value) {
t.Fatalf("Expected %s got %s (variable %s)\n", tc.value, ms, variable.Name)
}
}
......@@ -871,7 +871,6 @@ func TestEvalExpression(t *testing.T) {
t.Fatalf("Unexpected error. Expected %s got %s", tc.err.Error(), err.Error())
}
}
}
})
}
......@@ -910,7 +909,7 @@ func TestMapEvaluation(t *testing.T) {
m1v, err := evalVariable(p, "m1", pnormalLoadConfig)
assertNoError(err, t, "EvalVariable()")
m1 := api.ConvertVar(m1v)
t.Logf("m1 = %v", m1.MultilineString(""))
t.Logf("m1 = %v", m1.MultilineString("", ""))
if m1.Type != "map[string]main.astruct" {
t.Fatalf("Wrong type: %s", m1.Type)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册