mirror of
https://github.com/go-delve/delve.git
synced 2025-11-03 22:08:33 +08:00
dap: add 'clipboard' support, and truncate a long value (#2513)
- add 'clipboard' capability - apply a larger string limit for 'hover' and 'clipboard' context - truncate the string representation of compound (or pointer of compound) type variable
This commit is contained in:
committed by
GitHub
parent
054e3f8ef2
commit
152c74e94e
@ -104,6 +104,7 @@ func (c *Client) ExpectInitializeResponseAndCapabilities(t *testing.T) *dap.Init
|
||||
SupportsSetVariable: true,
|
||||
SupportsFunctionBreakpoints: true,
|
||||
SupportsEvaluateForHovers: true,
|
||||
SupportsClipboardContext: true,
|
||||
}
|
||||
if !reflect.DeepEqual(initResp.Body, wantCapabilities) {
|
||||
t.Errorf("capabilities in initializeResponse: got %+v, want %v", pretty(initResp.Body), pretty(wantCapabilities))
|
||||
|
||||
@ -688,6 +688,7 @@ func (s *Server) onInitializeRequest(request *dap.InitializeRequest) {
|
||||
response.Body.SupportsExceptionInfoRequest = true
|
||||
response.Body.SupportsSetVariable = true
|
||||
response.Body.SupportsEvaluateForHovers = true
|
||||
response.Body.SupportsClipboardContext = true
|
||||
// TODO(polina): support these requests in addition to vscode-go feature parity
|
||||
response.Body.SupportsTerminateRequest = false
|
||||
response.Body.SupportsRestartRequest = false
|
||||
@ -1814,26 +1815,42 @@ func (s *Server) getTypeIfSupported(v *proc.Variable) string {
|
||||
// custom, a zero reference, reminiscent of a zero pointer, is used to indicate that
|
||||
// a scalar variable cannot be "dereferenced" to get its elements (as there are none).
|
||||
func (s *Server) convertVariable(v *proc.Variable, qualifiedNameOrExpr string) (value string, variablesReference int) {
|
||||
return s.convertVariableWithOpts(v, qualifiedNameOrExpr, false)
|
||||
return s.convertVariableWithOpts(v, qualifiedNameOrExpr, 0)
|
||||
}
|
||||
|
||||
func (s *Server) convertVariableToString(v *proc.Variable) string {
|
||||
val, _ := s.convertVariableWithOpts(v, "", true)
|
||||
val, _ := s.convertVariableWithOpts(v, "", skipRef)
|
||||
return val
|
||||
}
|
||||
|
||||
// defaultMaxValueLen is the max length of a string representation of a compound or reference
|
||||
// type variable.
|
||||
const defaultMaxValueLen = 1 << 8 // 256
|
||||
|
||||
// Flags for convertVariableWithOpts option.
|
||||
type convertVariableFlags uint8
|
||||
|
||||
const (
|
||||
skipRef convertVariableFlags = 1 << iota
|
||||
showFullValue
|
||||
)
|
||||
|
||||
// convertVariableWithOpts allows to skip reference generation in case all we need is
|
||||
// a string representation of the variable.
|
||||
func (s *Server) convertVariableWithOpts(v *proc.Variable, qualifiedNameOrExpr string, skipRef bool) (value string, variablesReference int) {
|
||||
// a string representation of the variable. When the variable is a compound or reference
|
||||
// type variable and its full string representation can be larger than defaultMaxValueLen,
|
||||
// this returns a truncated value unless showFull option flag is set.
|
||||
func (s *Server) convertVariableWithOpts(v *proc.Variable, qualifiedNameOrExpr string, opts convertVariableFlags) (value string, variablesReference int) {
|
||||
canHaveRef := false
|
||||
maybeCreateVariableHandle := func(v *proc.Variable) int {
|
||||
if skipRef {
|
||||
canHaveRef = true
|
||||
if opts&skipRef != 0 {
|
||||
return 0
|
||||
}
|
||||
return s.variableHandles.create(&fullyQualifiedVariable{v, qualifiedNameOrExpr, false /*not a scope*/})
|
||||
}
|
||||
value = api.ConvertVar(v).SinglelineString()
|
||||
if v.Unreadable != nil {
|
||||
return value, variablesReference
|
||||
return value, 0
|
||||
}
|
||||
|
||||
// Some of the types might be fully or partially not loaded based on LoadConfig.
|
||||
@ -1962,6 +1979,10 @@ func (s *Server) convertVariableWithOpts(v *proc.Variable, qualifiedNameOrExpr s
|
||||
variablesReference = maybeCreateVariableHandle(v)
|
||||
}
|
||||
}
|
||||
canTruncateValue := showFullValue&opts == 0
|
||||
if len(value) > defaultMaxValueLen && canTruncateValue && canHaveRef {
|
||||
value = value[:defaultMaxValueLen] + "..."
|
||||
}
|
||||
return value, variablesReference
|
||||
}
|
||||
|
||||
@ -2019,21 +2040,27 @@ func (s *Server) onEvaluateRequest(request *dap.EvaluateRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
if exprVar.Kind == reflect.String && (request.Arguments.Context == "repl" || request.Arguments.Context == "variables") {
|
||||
if strVal := constant.StringVal(exprVar.Value); exprVar.Len > int64(len(strVal)) {
|
||||
// reload string value with a bigger limit.
|
||||
loadCfg := DefaultLoadConfig
|
||||
loadCfg.MaxStringLen = 4 << 10
|
||||
if v, err := s.debugger.EvalVariableInScope(goid, frame, 0, request.Arguments.Expression, loadCfg); err != nil {
|
||||
s.log.Debugf("Failed to load more for %v: %v", request.Arguments.Expression, err)
|
||||
} else {
|
||||
exprVar = v
|
||||
ctxt := request.Arguments.Context
|
||||
switch ctxt {
|
||||
case "repl", "variables", "hover", "clipboard":
|
||||
if exprVar.Kind == reflect.String {
|
||||
if strVal := constant.StringVal(exprVar.Value); exprVar.Len > int64(len(strVal)) {
|
||||
// reload string value with a bigger limit.
|
||||
loadCfg := DefaultLoadConfig
|
||||
loadCfg.MaxStringLen = 4 << 10
|
||||
if v, err := s.debugger.EvalVariableInScope(goid, frame, 0, request.Arguments.Expression, loadCfg); err != nil {
|
||||
s.log.Debugf("Failed to load more for %v: %v", request.Arguments.Expression, err)
|
||||
} else {
|
||||
exprVar = v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO(polina): as far as I can tell, evaluateName is ignored by vscode for expression variables.
|
||||
// Should it be skipped altogether for all levels?
|
||||
exprVal, exprRef := s.convertVariable(exprVar, fmt.Sprintf("(%s)", request.Arguments.Expression))
|
||||
var opts convertVariableFlags
|
||||
if ctxt == "variables" || ctxt == "hover" || ctxt == "clipboard" {
|
||||
opts |= showFullValue
|
||||
}
|
||||
exprVal, exprRef := s.convertVariableWithOpts(exprVar, fmt.Sprintf("(%s)", request.Arguments.Expression), opts)
|
||||
response.Body = dap.EvaluateResponseBody{Result: exprVal, VariablesReference: exprRef}
|
||||
}
|
||||
s.send(response)
|
||||
|
||||
@ -1493,7 +1493,7 @@ func TestVariablesLoading(t *testing.T) {
|
||||
}
|
||||
|
||||
// Map partially missing based on LoadConfig.MaxArrayValues
|
||||
ref = checkVarRegex(t, locals, -1, "m1", "m1", `\(loaded 64/66\) map\[string\]main\.astruct \[.+\.\.\.\+2 more\]`, `map\[string\]main\.astruct`, hasChildren)
|
||||
ref = checkVarRegex(t, locals, -1, "m1", "m1", `\(loaded 64/66\) map\[string\]main\.astruct \[.+\.\.\.`, `map\[string\]main\.astruct`, hasChildren)
|
||||
if ref > 0 {
|
||||
client.VariablesRequest(ref)
|
||||
m1 := client.ExpectVariablesResponse(t)
|
||||
@ -1572,7 +1572,7 @@ func TestVariablesLoading(t *testing.T) {
|
||||
client.VariablesRequest(ref)
|
||||
tmV := client.ExpectVariablesResponse(t)
|
||||
checkChildren(t, tmV, "tm.v", 1)
|
||||
ref = checkVarRegex(t, tmV, 0, `\[0\]`, `tm\.v\[0\]`, `map\[string\]main\.astruct \[.+\.\.\.\+2 more\]`, `map\[string\]main\.astruct`, hasChildren)
|
||||
ref = checkVarRegex(t, tmV, 0, `\[0\]`, `tm\.v\[0\]`, `map\[string\]main\.astruct \[.+\.\.\.`, `map\[string\]main\.astruct`, hasChildren)
|
||||
if ref > 0 {
|
||||
client.VariablesRequest(ref)
|
||||
tmV0 := client.ExpectVariablesResponse(t)
|
||||
@ -1602,7 +1602,7 @@ func TestVariablesLoading(t *testing.T) {
|
||||
tmV := client.ExpectVariablesResponse(t)
|
||||
checkChildren(t, tmV, "tm.v", 1)
|
||||
// TODO(polina): this evaluate name is not usable - it should be empty
|
||||
ref = checkVarRegex(t, tmV, 0, `\[0\]`, `\[0\]`, `map\[string\]main\.astruct \[.+\.\.\.\+2 more\]`, `map\[string\]main\.astruct`, hasChildren)
|
||||
ref = checkVarRegex(t, tmV, 0, `\[0\]`, `\[0\]`, `map\[string\]main\.astruct \[.+\.\.\.`, `map\[string\]main\.astruct`, hasChildren)
|
||||
if ref > 0 {
|
||||
client.VariablesRequest(ref)
|
||||
tmV0 := client.ExpectVariablesResponse(t)
|
||||
@ -2723,13 +2723,18 @@ func TestEvaluateRequest(t *testing.T) {
|
||||
if erres.Body.Error.Format != "Unable to evaluate expression: could not find symbol value for a1" {
|
||||
t.Errorf("\ngot %#v\nwant Format=\"Unable to evaluate expression: could not find symbol value for a1\"", erres)
|
||||
}
|
||||
client.EvaluateRequest("a1", 1002, "clipboard")
|
||||
erres = client.ExpectVisibleErrorResponse(t)
|
||||
if erres.Body.Error.Format != "Unable to evaluate expression: could not find symbol value for a1" {
|
||||
t.Errorf("\ngot %#v\nwant Format=\"Unable to evaluate expression: could not find symbol value for a1\"", erres)
|
||||
}
|
||||
},
|
||||
disconnect: false,
|
||||
}})
|
||||
})
|
||||
}
|
||||
|
||||
func TestEvaluateRequestLongStr(t *testing.T) {
|
||||
func TestEvaluateRequestLongStrLargeValue(t *testing.T) {
|
||||
runTest(t, "testvariables2", func(client *daptest.Client, fixture protest.Fixture) {
|
||||
runDebugSessionWithBPs(t, client, "launch",
|
||||
// Launch
|
||||
@ -2741,22 +2746,24 @@ func TestEvaluateRequestLongStr(t *testing.T) {
|
||||
[]onBreakpoint{{
|
||||
execute: func() {
|
||||
|
||||
longstr := `"very long string 0123456789a0123456789b0123456789c0123456789d0123456789e0123456789f0123456789g012345678h90123456789i0123456789j0123456789"`
|
||||
longstrTruncated := `"very long string 0123456789a0123456789b0123456789c0123456789d012...+73 more"`
|
||||
|
||||
checkStop(t, client, 1, "main.main", -1)
|
||||
|
||||
client.VariablesRequest(1001) // Locals
|
||||
locals := client.ExpectVariablesResponse(t)
|
||||
|
||||
// reflect.Kind == String, load with longer load limit if evaluated in repl/variables context.
|
||||
for _, evalContext := range []string{"", "watch", "repl", "variables", "somethingelse"} {
|
||||
for _, evalContext := range []string{"", "watch", "repl", "variables", "hover", "clipboard", "somethingelse"} {
|
||||
t.Run(evalContext, func(t *testing.T) {
|
||||
// long string
|
||||
|
||||
longstr := `"very long string 0123456789a0123456789b0123456789c0123456789d0123456789e0123456789f0123456789g012345678h90123456789i0123456789j0123456789"`
|
||||
longstrTruncated := `"very long string 0123456789a0123456789b0123456789c0123456789d012...+73 more"`
|
||||
|
||||
client.EvaluateRequest("longstr", 0, evalContext)
|
||||
got := client.ExpectEvaluateResponse(t)
|
||||
want := longstrTruncated
|
||||
if evalContext == "repl" || evalContext == "variables" {
|
||||
switch evalContext {
|
||||
case "repl", "variables", "hover", "clipboard":
|
||||
want = longstr
|
||||
}
|
||||
checkEval(t, got, want, false)
|
||||
@ -2769,9 +2776,22 @@ func TestEvaluateRequestLongStr(t *testing.T) {
|
||||
// variables are not affected.
|
||||
checkVarExact(t, locals, -1, "longstr", "longstr", longstrTruncated, "string", noChildren)
|
||||
checkVarExact(t, locals, -1, "m6", "m6", `main.C {s: `+longstrTruncated+`}`, "main.C", hasChildren)
|
||||
|
||||
// large array
|
||||
m1 := `\(loaded 64/66\) map\[string\]main\.astruct \[.+\.\.\.\+2 more\]`
|
||||
m1Truncated := `\(loaded 64/66\) map\[string\]main\.astruct \[.+\.\.\.`
|
||||
|
||||
client.EvaluateRequest("m1", 0, evalContext)
|
||||
got3 := client.ExpectEvaluateResponse(t)
|
||||
want3 := m1Truncated
|
||||
switch evalContext {
|
||||
case "variables", "hover", "clipboard":
|
||||
want3 = m1
|
||||
}
|
||||
checkEvalRegex(t, got3, want3, true)
|
||||
checkVarRegex(t, locals, -1, "m1", "m1", m1Truncated, `map\[string\]main\.astruct`, true)
|
||||
})
|
||||
}
|
||||
|
||||
},
|
||||
disconnect: true,
|
||||
}})
|
||||
|
||||
Reference in New Issue
Block a user