Repository: go-delve/delve
Pull request #2403 (2021-03-24 18:06:37)

service/dap: switch goroutines when stepping
The step in, next, and step out requests all include a specified argument of a threadId.
This change will switch to the specified goroutine, if necessary, before executing
the step command.


Patch:
diff --git a/Documentation/cli/README.md b/Documentation/cli/README.md
index cb9f5ce8..5ecb1087 100644
--- a/Documentation/cli/README.md
+++ b/Documentation/cli/README.md
@@ -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
diff --git a/pkg/terminal/command.go b/pkg/terminal/command.go
index 5ba6dfb2..4f5e0665 100644
--- a/pkg/terminal/command.go
+++ b/pkg/terminal/command.go
@@ -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):
diff --git a/pkg/terminal/terminal.go b/pkg/terminal/terminal.go
index fa00006d..d484337d 100644
--- a/pkg/terminal/terminal.go
+++ b/pkg/terminal/terminal.go
@@ -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)
 		}
 	}
diff --git a/service/api/prettyprint.go b/service/api/prettyprint.go
index e8ec44df..b230ba24 100644
--- a/service/api/prettyprint.go
+++ b/service/api/prettyprint.go
@@ -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, ",")
 		}
diff --git a/service/dap/server.go b/service/dap/server.go
index d2c45d6c..c43828b4 100644
--- a/service/dap/server.go
+++ b/service/dap/server.go
@@ -159,7 +159,12 @@ func (s *Server) Stop() {
 	if s.debugger != nil {
 		kill := s.config.Debugger.AttachPid == 0
 		if err := s.debugger.Detach(kill); err != nil {
-			s.log.Error(err)
+			switch err.(type) {
+			case *proc.ErrProcessExited:
+				s.log.Debug(err)
+			default:
+				s.log.Error(err)
+			}
 		}
 	}
 }
@@ -575,7 +580,7 @@ func (s *Server) onLaunchRequest(request *dap.LaunchRequest) {
 }
 
 func (s *Server) runWithoutDebug(program string, targetArgs []string, wd string) error {
-	s.log.Println("Running without debug: ", program)
+	s.log.Debugln("Running without debug: ", program)
 
 	cmd := exec.Command(program, targetArgs...)
 	// TODO: send stdin/out/err as OutputEvent messages
@@ -597,7 +602,7 @@ func (s *Server) runWithoutDebug(program string, targetArgs []string, wd string)
 
 	// block until the process terminates.
 	if err := cmd.Wait(); err != nil {
-		s.log.Errorf("process exited with %v", err)
+		s.log.Debugf("process exited with %v", err)
 	}
 
 	s.mu.Lock()
@@ -616,7 +621,7 @@ func (s *Server) stopNoDebugProcess() {
 
 	// TODO(hyangah): gracefully terminate the process and its children processes.
 	if err := s.noDebugProcess.Process.Kill(); err != nil {
-		s.log.Errorf("killing process (pid=%v) failed: %v", s.noDebugProcess.Process.Pid, err)
+		s.log.Debugf("killing process (pid=%v) failed: %v", s.noDebugProcess.Process.Pid, err)
 	}
 }
 
@@ -638,7 +643,12 @@ func (s *Server) onDisconnectRequest(request *dap.DisconnectRequest) {
 	if s.debugger != nil {
 		_, err := s.debugger.Command(&api.DebuggerCommand{Name: api.Halt}, nil)
 		if err != nil {
-			s.log.Error(err)
+			switch err.(type) {
+			case *proc.ErrProcessExited:
+				s.log.Debug(err)
+			default:
+				s.log.Error(err)
+			}
 		}
 		// We always kill launched programs
 		kill := s.config.Debugger.AttachPid == 0
@@ -650,7 +660,12 @@ func (s *Server) onDisconnectRequest(request *dap.DisconnectRequest) {
 		}
 		err = s.debugger.Detach(kill)
 		if err != nil {
-			s.log.Error(err)
+			switch err.(type) {
+			case *proc.ErrProcessExited:
+				s.log.Debug(err)
+			default:
+				s.log.Error(err)
+			}
 		}
 	} else {
 		s.stopNoDebugProcess()
@@ -848,28 +863,44 @@ func (s *Server) onAttachRequest(request *dap.AttachRequest) {
 // onNextRequest handles 'next' request.
 // This is a mandatory request to support.
 func (s *Server) onNextRequest(request *dap.NextRequest) {
-	// This ignores threadId argument to match the original vscode-go implementation.
-	// TODO(polina): use SwitchGoroutine to change the current goroutine.
 	s.send(&dap.NextResponse{Response: *newResponse(request.Request)})
-	s.doCommand(api.Next)
+	s.doStepCommand(api.Next, request.Arguments.ThreadId)
 }
 
 // onStepInRequest handles 'stepIn' request
 // This is a mandatory request to support.
 func (s *Server) onStepInRequest(request *dap.StepInRequest) {
-	// This ignores threadId argument to match the original vscode-go implementation.
-	// TODO(polina): use SwitchGoroutine to change the current goroutine.
 	s.send(&dap.StepInResponse{Response: *newResponse(request.Request)})
-	s.doCommand(api.Step)
+	s.doStepCommand(api.Step, request.Arguments.ThreadId)
 }
 
 // onStepOutRequest handles 'stepOut' request
 // This is a mandatory request to support.
 func (s *Server) onStepOutRequest(request *dap.StepOutRequest) {
-	// This ignores threadId argument to match the original vscode-go implementation.
-	// TODO(polina): use SwitchGoroutine to change the current goroutine.
 	s.send(&dap.StepOutResponse{Response: *newResponse(request.Request)})
-	s.doCommand(api.StepOut)
+	s.doStepCommand(api.StepOut, request.Arguments.ThreadId)
+}
+
+func (s *Server) doStepCommand(command string, threadId int) {
+	// Use SwitchGoroutine to change the current goroutine.
+	state, err := s.debugger.Command(&api.DebuggerCommand{Name: api.SwitchGoroutine, GoroutineID: threadId}, nil)
+	if err != nil {
+		s.log.Errorf("Error switching goroutines while stepping: %e", err)
+		// If we encounter an error, we will have to send a stopped event
+		// since we already sent the step response.
+		stopped := &dap.StoppedEvent{Event: *newEvent("stopped")}
+		stopped.Body.AllThreadsStopped = true
+		if state.SelectedGoroutine != nil {
+			stopped.Body.ThreadId = state.SelectedGoroutine.ID
+		} else if state.CurrentThread != nil {
+			stopped.Body.ThreadId = state.CurrentThread.GoroutineID
+		}
+		stopped.Body.Reason = "error"
+		stopped.Body.Text = err.Error()
+		s.send(stopped)
+		return
+	}
+	s.doCommand(command)
 }
 
 // onPauseRequest sends a not-yet-implemented error response.
@@ -1062,7 +1093,10 @@ func (s *Server) onVariablesRequest(request *dap.VariablesRequest) {
 					Value:        val,
 				}
 				if keyref != 0 { // key is a type to be expanded
-					kvvar.Name = fmt.Sprintf("%s(%#x)", kvvar.Name, keyv.Addr) // Make the name unique
+					if len(key) > DefaultLoadConfig.MaxStringLen {
+						// Truncate and make unique
+						kvvar.Name = fmt.Sprintf("%s... @ %#x", key[0:DefaultLoadConfig.MaxStringLen], keyv.Addr)
+					}
 					kvvar.VariablesReference = keyref
 				} else if valref != 0 { // val is a type to be expanded
 					kvvar.VariablesReference = valref
@@ -1414,7 +1448,7 @@ func (s *Server) onCancelRequest(request *dap.CancelRequest) {
 	s.sendNotYetImplementedErrorResponse(request.Request)
 }
 
-// sendERrorResponseWithOpts offers configuration options.
+// sendErrorResponseWithOpts offers configuration options.
 //   showUser - if true, the error will be shown to the user (e.g. via a visible pop-up)
 func (s *Server) sendErrorResponseWithOpts(request dap.Request, id int, summary, details string, showUser bool) {
 	er := &dap.ErrorResponse{}
@@ -1426,7 +1460,7 @@ func (s *Server) sendErrorResponseWithOpts(request dap.Request, id int, summary,
 	er.Body.Error.Id = id
 	er.Body.Error.Format = fmt.Sprintf("%s: %s", summary, details)
 	er.Body.Error.ShowUser = showUser
-	s.log.Error(er.Body.Error.Format)
+	s.log.Debug(er.Body.Error.Format)
 	s.send(er)
 }
 
@@ -1446,18 +1480,18 @@ func (s *Server) sendInternalErrorResponse(seq int, details string) {
 	er.Message = "Internal Error"
 	er.Body.Error.Id = InternalError
 	er.Body.Error.Format = fmt.Sprintf("%s: %s", er.Message, details)
-	s.log.Error(er.Body.Error.Format)
+	s.log.Debug(er.Body.Error.Format)
 	s.send(er)
 }
 
 func (s *Server) sendUnsupportedErrorResponse(request dap.Request) {
 	s.sendErrorResponse(request, UnsupportedCommand, "Unsupported command",
-		fmt.Sprintf("cannot process '%s' request", request.Command))
+		fmt.Sprintf("cannot process %q request", request.Command))
 }
 
 func (s *Server) sendNotYetImplementedErrorResponse(request dap.Request) {
 	s.sendErrorResponse(request, NotYetImplemented, "Not yet implemented",
-		fmt.Sprintf("cannot process '%s' request", request.Command))
+		fmt.Sprintf("cannot process %q request", request.Command))
 }
 
 func newResponse(request dap.Request) *dap.Response {


Test patch:
diff --git a/_fixtures/testvariables2.go b/_fixtures/testvariables2.go
index a3a6deb7..7ffeaec0 100644
--- a/_fixtures/testvariables2.go
+++ b/_fixtures/testvariables2.go
@@ -326,6 +326,7 @@ func main() {
 	runearray := [4]rune{116, 232, 115, 116}
 
 	longstr := "very long string 0123456789a0123456789b0123456789c0123456789d0123456789e0123456789f0123456789g012345678h90123456789i0123456789j0123456789"
+	m5 := map[C]int{{longstr}: 1}
 
 	var nilstruct *astruct = nil
 
@@ -361,5 +362,5 @@ func main() {
 	longslice := make([]int, 100, 100)
 
 	runtime.Breakpoint()
-	fmt.Println(i1, i2, i3, p1, pp1, amb1, s1, s3, a0, a1, p2, p3, s2, as1, str1, f1, fn1, fn2, nilslice, nilptr, ch1, chnil, m1, mnil, m2, m3, m4, upnil, up1, i4, i5, i6, err1, err2, errnil, iface1, iface2, ifacenil, arr1, parr, cpx1, const1, iface3, iface4, recursive1, recursive1.x, iface5, iface2fn1, iface2fn2, bencharr, benchparr, mapinf, mainMenu, b, b2, sd, anonstruct1, anonstruct2, anoniface1, anonfunc, mapanonstruct1, ifacearr, efacearr, ni8, ni16, ni32, ni64, pinf, ninf, nan, zsvmap, zsslice, zsvar, tm, errtypednil, emptyslice, emptymap, byteslice, runeslice, bytearray, runearray, longstr, nilstruct, as2, as2.NonPointerRecieverMethod, s4, iface2map, issue1578, ll, unread, w2, w3, w4, w5, longarr, longslice)
+	fmt.Println(i1, i2, i3, p1, pp1, amb1, s1, s3, a0, a1, p2, p3, s2, as1, str1, f1, fn1, fn2, nilslice, nilptr, ch1, chnil, m1, mnil, m2, m3, m4, m5, upnil, up1, i4, i5, i6, err1, err2, errnil, iface1, iface2, ifacenil, arr1, parr, cpx1, const1, iface3, iface4, recursive1, recursive1.x, iface5, iface2fn1, iface2fn2, bencharr, benchparr, mapinf, mainMenu, b, b2, sd, anonstruct1, anonstruct2, anoniface1, anonfunc, mapanonstruct1, ifacearr, efacearr, ni8, ni16, ni32, ni64, pinf, ninf, nan, zsvmap, zsslice, zsvar, tm, errtypednil, emptyslice, emptymap, byteslice, runeslice, bytearray, runearray, longstr, nilstruct, as2, as2.NonPointerRecieverMethod, s4, iface2map, issue1578, ll, unread, w2, w3, w4, w5, longarr, longslice)
 }
diff --git a/pkg/terminal/command_test.go b/pkg/terminal/command_test.go
index c55ee3cf..14069a5a 100644
--- a/pkg/terminal/command_test.go
+++ b/pkg/terminal/command_test.go
@@ -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)
+		}
+	})
+}
diff --git a/service/dap/server_test.go b/service/dap/server_test.go
index 67d18025..261416a8 100644
--- a/service/dap/server_test.go
+++ b/service/dap/server_test.go
@@ -6,6 +6,7 @@ import (
 	"fmt"
 	"io"
 	"io/ioutil"
+	"math/rand"
 	"net"
 	"os"
 	"os/exec"
@@ -1191,7 +1192,7 @@ func TestScopesAndVariablesRequests2(t *testing.T) {
 						client.VariablesRequest(ref)
 						m3 := client.ExpectVariablesResponse(t)
 						expectChildren(t, m3, "m3", 2) // each key-value represented by a single child
-						ref = expectVarRegex(t, m3, 0, `main\.astruct {A: 1, B: 1}\(0x[0-9a-f]+\)`, `m3\[\(\*\(\*"main.astruct"\)\(0x[0-9a-f]+\)\)\]`, "42", hasChildren)
+						ref = expectVarRegex(t, m3, 0, `main\.astruct {A: 1, B: 1}`, `m3\[\(\*\(\*"main.astruct"\)\(0x[0-9a-f]+\)\)\]`, "42", hasChildren)
 						if ref > 0 {
 							client.VariablesRequest(ref)
 							m3kv0 := client.ExpectVariablesResponse(t)
@@ -1199,7 +1200,7 @@ func TestScopesAndVariablesRequests2(t *testing.T) {
 							expectVarRegex(t, m3kv0, 0, "A", `\(*\(*"main\.astruct"\)\(0x[0-9a-f]+\)\)\.A`, "1", noChildren)
 							validateEvaluateName(t, client, m3kv0, 0)
 						}
-						ref = expectVarRegex(t, m3, 1, `main\.astruct {A: 2, B: 2}\(0x[0-9a-f]+\)`, `m3\[\(\*\(\*"main.astruct"\)\(0x[0-9a-f]+\)\)\]`, "43", hasChildren)
+						ref = expectVarRegex(t, m3, 1, `main\.astruct {A: 2, B: 2}`, `m3\[\(\*\(\*"main.astruct"\)\(0x[0-9a-f]+\)\)\]`, "43", hasChildren)
 						if ref > 0 { // inspect another key from another key-value child
 							client.VariablesRequest(ref)
 							m3kv1 := client.ExpectVariablesResponse(t)
@@ -1208,6 +1209,14 @@ func TestScopesAndVariablesRequests2(t *testing.T) {
 							validateEvaluateName(t, client, m3kv1, 1)
 						}
 					}
+					// key - compound + truncated, value - scalar
+					ref = expectVarExact(t, locals, -1, "m5", "m5", `map[main.C]int [{s: "very long string 0123456789a0123456789b0123456789c0123456789d012...+73 more"}: 1, ]`, hasChildren)
+					if ref > 0 {
+						client.VariablesRequest(ref)
+						m5 := client.ExpectVariablesResponse(t)
+						expectChildren(t, m5, "m5", 1)
+						expectVarRegex(t, m5, 0, `main\.C {s: "very long string 0123456789a0123456789b0123456789c01\.\.\. @ 0x[0-9a-f]+`, `m5\[\(\*\(\*"main\.C"\)\(0x[0-9a-f]+\)\)\]`, "1", hasChildren)
+					}
 					// key - compound, value - compound
 					ref = expectVarExact(t, locals, -1, "m4", "m4", "map[main.astruct]main.astruct [{A: 1, B: 1}: {A: 11, B: 11}, {A: 2, B: 2}: {A: 22, B: 22}, ]", hasChildren)
 					if ref > 0 {
@@ -2060,6 +2069,181 @@ func TestNextAndStep(t *testing.T) {
 	})
 }
 
+func TestNextParked(t *testing.T) {
+	if runtime.GOOS == "freebsd" {
+		t.SkipNow()
+	}
+	runTest(t, "parallel_next", func(client *daptest.Client, fixture protest.Fixture) {
+		runDebugSessionWithBPs(t, client, "launch",
+			// Launch
+			func() {
+				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
+			},
+			// Set breakpoints
+			fixture.Source, []int{15},
+			[]onBreakpoint{{ // Stop at line 15
+				execute: func() {
+					goroutineId := testStepParkedHelper(t, client, fixture)
+
+					client.NextRequest(goroutineId)
+					client.ExpectNextResponse(t)
+
+					se := client.ExpectStoppedEvent(t)
+					if se.Body.ThreadId != goroutineId {
+						t.Fatalf("Next did not continue on the selected goroutine, expected %d got %d", goroutineId, se.Body.ThreadId)
+					}
+				},
+				disconnect: false,
+			}})
+	})
+}
+
+func TestStepInParked(t *testing.T) {
+	if runtime.GOOS == "freebsd" {
+		t.SkipNow()
+	}
+	runTest(t, "parallel_next", func(client *daptest.Client, fixture protest.Fixture) {
+		runDebugSessionWithBPs(t, client, "launch",
+			// Launch
+			func() {
+				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
+			},
+			// Set breakpoints
+			fixture.Source, []int{15},
+			[]onBreakpoint{{ // Stop at line 15
+				execute: func() {
+					goroutineId := testStepParkedHelper(t, client, fixture)
+
+					client.StepInRequest(goroutineId)
+					client.ExpectStepInResponse(t)
+
+					se := client.ExpectStoppedEvent(t)
+					if se.Body.ThreadId != goroutineId {
+						t.Fatalf("StepIn did not continue on the selected goroutine, expected %d got %d", goroutineId, se.Body.ThreadId)
+					}
+				},
+				disconnect: false,
+			}})
+	})
+}
+
+func testStepParkedHelper(t *testing.T, client *daptest.Client, fixture protest.Fixture) int {
+	t.Helper()
+	// Set a breakpoint at main.sayHi
+	client.SetBreakpointsRequest(fixture.Source, []int{8})
+	client.ExpectSetBreakpointsResponse(t)
+
+	var goroutineId = -1
+	for goroutineId < 0 {
+		client.ContinueRequest(1)
+		client.ExpectContinueResponse(t)
+
+		se := client.ExpectStoppedEvent(t)
+
+		client.ThreadsRequest()
+		threads := client.ExpectThreadsResponse(t)
+
+		// Search for a parked goroutine that we know for sure will have to be
+		// resumed before the program can exit. This is a parked goroutine that:
+		// 1. is executing main.sayhi
+		// 2. hasn't called wg.Done yet
+		// 3. is not the currently selected goroutine
+		for _, g := range threads.Body.Threads {
+			// We do not need to check the thread that the program
+			// is currently stopped on.
+			if g.Id == se.Body.ThreadId {
+				continue
+			}
+			client.StackTraceRequest(g.Id, 0, 5)
+			frames := client.ExpectStackTraceResponse(t)
+			for _, frame := range frames.Body.StackFrames {
+				// line 11 is the line where wg.Done is called
+				if frame.Name == "main.sayhi" && frame.Line < 11 {
+					goroutineId = g.Id
+					break
+				}
+			}
+			if goroutineId >= 0 {
+				break
+			}
+		}
+	}
+
+	// Clear all breakpoints.
+	client.SetBreakpointsRequest(fixture.Source, []int{})
+	client.ExpectSetBreakpointsResponse(t)
+
+	return goroutineId
+}
+
+func TestStepOutPreservesGoroutine(t *testing.T) {
+	// Checks that StepOut preserves the currently selected goroutine.
+	if runtime.GOOS == "freebsd" {
+		t.SkipNow()
+	}
+	rand.Seed(time.Now().Unix())
+	runTest(t, "issue2113", func(client *daptest.Client, fixture protest.Fixture) {
+		runDebugSessionWithBPs(t, client, "launch",
+			// Launch
+			func() {
+				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
+			},
+			// Set breakpoints
+			fixture.Source, []int{25},
+			[]onBreakpoint{{ // Stop at line 25
+				execute: func() {
+					client.ContinueRequest(1)
+					client.ExpectContinueResponse(t)
+
+					// The program contains runtime.Breakpoint()
+					se := client.ExpectStoppedEvent(t)
+
+					client.ThreadsRequest()
+					gs := client.ExpectThreadsResponse(t)
+
+					candg := []int{}
+					bestg := []int{}
+					for _, g := range gs.Body.Threads {
+						// We do not need to check the thread that the program
+						// is currently stopped on.
+						if g.Id == se.Body.ThreadId {
+							continue
+						}
+
+						client.StackTraceRequest(g.Id, 0, 20)
+						frames := client.ExpectStackTraceResponse(t)
+						for _, frame := range frames.Body.StackFrames {
+							if frame.Name == "main.coroutine" {
+								candg = append(candg, g.Id)
+								if strings.HasPrefix(frames.Body.StackFrames[0].Name, "runtime.") {
+									bestg = append(bestg, g.Id)
+								}
+								break
+							}
+						}
+					}
+					var goroutineId int
+					if len(bestg) > 0 {
+						goroutineId = bestg[rand.Intn(len(bestg))]
+						t.Logf("selected goroutine %d (best)\n", goroutineId)
+					} else {
+						goroutineId = candg[rand.Intn(len(candg))]
+						t.Logf("selected goroutine %d\n", goroutineId)
+
+					}
+					client.StepOutRequest(goroutineId)
+					client.ExpectStepOutResponse(t)
+
+					se = client.ExpectStoppedEvent(t)
+					if se.Body.ThreadId != goroutineId {
+						t.Fatalf("StepIn did not continue on the selected goroutine, expected %d got %d", goroutineId, se.Body.ThreadId)
+					}
+				},
+				disconnect: false,
+			}})
+	})
+}
+
 func TestBadAccess(t *testing.T) {
 	if runtime.GOOS != "darwin" || testBackend != "lldb" {
 		t.Skip("not applicable")
diff --git a/service/test/integration1_test.go b/service/test/integration1_test.go
index bde8f9fd..2e77edb2 100644
--- a/service/test/integration1_test.go
+++ b/service/test/integration1_test.go
@@ -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)
 	})
 }
diff --git a/service/test/integration2_test.go b/service/test/integration2_test.go
index bf35e191..51502c3f 100644
--- a/service/test/integration2_test.go
+++ b/service/test/integration2_test.go
@@ -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)
 	})
 }
diff --git a/service/test/variables_test.go b/service/test/variables_test.go
index c2087930..4bab82a9 100644
--- a/service/test/variables_test.go
+++ b/service/test/variables_test.go
@@ -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)