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:
parent
054e3f8ef2
commit
152c74e94e
@ -104,6 +104,7 @@ func (c *Client) ExpectInitializeResponseAndCapabilities(t *testing.T) *dap.Init
|
|||||||
SupportsSetVariable: true,
|
SupportsSetVariable: true,
|
||||||
SupportsFunctionBreakpoints: true,
|
SupportsFunctionBreakpoints: true,
|
||||||
SupportsEvaluateForHovers: true,
|
SupportsEvaluateForHovers: true,
|
||||||
|
SupportsClipboardContext: true,
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(initResp.Body, wantCapabilities) {
|
if !reflect.DeepEqual(initResp.Body, wantCapabilities) {
|
||||||
t.Errorf("capabilities in initializeResponse: got %+v, want %v", pretty(initResp.Body), pretty(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.SupportsExceptionInfoRequest = true
|
||||||
response.Body.SupportsSetVariable = true
|
response.Body.SupportsSetVariable = true
|
||||||
response.Body.SupportsEvaluateForHovers = true
|
response.Body.SupportsEvaluateForHovers = true
|
||||||
|
response.Body.SupportsClipboardContext = true
|
||||||
// TODO(polina): support these requests in addition to vscode-go feature parity
|
// TODO(polina): support these requests in addition to vscode-go feature parity
|
||||||
response.Body.SupportsTerminateRequest = false
|
response.Body.SupportsTerminateRequest = false
|
||||||
response.Body.SupportsRestartRequest = 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
|
// 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).
|
// 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) {
|
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 {
|
func (s *Server) convertVariableToString(v *proc.Variable) string {
|
||||||
val, _ := s.convertVariableWithOpts(v, "", true)
|
val, _ := s.convertVariableWithOpts(v, "", skipRef)
|
||||||
return val
|
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
|
// convertVariableWithOpts allows to skip reference generation in case all we need is
|
||||||
// a string representation of the variable.
|
// a string representation of the variable. When the variable is a compound or reference
|
||||||
func (s *Server) convertVariableWithOpts(v *proc.Variable, qualifiedNameOrExpr string, skipRef bool) (value string, variablesReference int) {
|
// 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 {
|
maybeCreateVariableHandle := func(v *proc.Variable) int {
|
||||||
if skipRef {
|
canHaveRef = true
|
||||||
|
if opts&skipRef != 0 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return s.variableHandles.create(&fullyQualifiedVariable{v, qualifiedNameOrExpr, false /*not a scope*/})
|
return s.variableHandles.create(&fullyQualifiedVariable{v, qualifiedNameOrExpr, false /*not a scope*/})
|
||||||
}
|
}
|
||||||
value = api.ConvertVar(v).SinglelineString()
|
value = api.ConvertVar(v).SinglelineString()
|
||||||
if v.Unreadable != nil {
|
if v.Unreadable != nil {
|
||||||
return value, variablesReference
|
return value, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some of the types might be fully or partially not loaded based on LoadConfig.
|
// 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)
|
variablesReference = maybeCreateVariableHandle(v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
canTruncateValue := showFullValue&opts == 0
|
||||||
|
if len(value) > defaultMaxValueLen && canTruncateValue && canHaveRef {
|
||||||
|
value = value[:defaultMaxValueLen] + "..."
|
||||||
|
}
|
||||||
return value, variablesReference
|
return value, variablesReference
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2019,21 +2040,27 @@ func (s *Server) onEvaluateRequest(request *dap.EvaluateRequest) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if exprVar.Kind == reflect.String && (request.Arguments.Context == "repl" || request.Arguments.Context == "variables") {
|
ctxt := request.Arguments.Context
|
||||||
if strVal := constant.StringVal(exprVar.Value); exprVar.Len > int64(len(strVal)) {
|
switch ctxt {
|
||||||
// reload string value with a bigger limit.
|
case "repl", "variables", "hover", "clipboard":
|
||||||
loadCfg := DefaultLoadConfig
|
if exprVar.Kind == reflect.String {
|
||||||
loadCfg.MaxStringLen = 4 << 10
|
if strVal := constant.StringVal(exprVar.Value); exprVar.Len > int64(len(strVal)) {
|
||||||
if v, err := s.debugger.EvalVariableInScope(goid, frame, 0, request.Arguments.Expression, loadCfg); err != nil {
|
// reload string value with a bigger limit.
|
||||||
s.log.Debugf("Failed to load more for %v: %v", request.Arguments.Expression, err)
|
loadCfg := DefaultLoadConfig
|
||||||
} else {
|
loadCfg.MaxStringLen = 4 << 10
|
||||||
exprVar = v
|
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.
|
var opts convertVariableFlags
|
||||||
// Should it be skipped altogether for all levels?
|
if ctxt == "variables" || ctxt == "hover" || ctxt == "clipboard" {
|
||||||
exprVal, exprRef := s.convertVariable(exprVar, fmt.Sprintf("(%s)", request.Arguments.Expression))
|
opts |= showFullValue
|
||||||
|
}
|
||||||
|
exprVal, exprRef := s.convertVariableWithOpts(exprVar, fmt.Sprintf("(%s)", request.Arguments.Expression), opts)
|
||||||
response.Body = dap.EvaluateResponseBody{Result: exprVal, VariablesReference: exprRef}
|
response.Body = dap.EvaluateResponseBody{Result: exprVal, VariablesReference: exprRef}
|
||||||
}
|
}
|
||||||
s.send(response)
|
s.send(response)
|
||||||
|
|||||||
@ -1493,7 +1493,7 @@ func TestVariablesLoading(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Map partially missing based on LoadConfig.MaxArrayValues
|
// 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 {
|
if ref > 0 {
|
||||||
client.VariablesRequest(ref)
|
client.VariablesRequest(ref)
|
||||||
m1 := client.ExpectVariablesResponse(t)
|
m1 := client.ExpectVariablesResponse(t)
|
||||||
@ -1572,7 +1572,7 @@ func TestVariablesLoading(t *testing.T) {
|
|||||||
client.VariablesRequest(ref)
|
client.VariablesRequest(ref)
|
||||||
tmV := client.ExpectVariablesResponse(t)
|
tmV := client.ExpectVariablesResponse(t)
|
||||||
checkChildren(t, tmV, "tm.v", 1)
|
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 {
|
if ref > 0 {
|
||||||
client.VariablesRequest(ref)
|
client.VariablesRequest(ref)
|
||||||
tmV0 := client.ExpectVariablesResponse(t)
|
tmV0 := client.ExpectVariablesResponse(t)
|
||||||
@ -1602,7 +1602,7 @@ func TestVariablesLoading(t *testing.T) {
|
|||||||
tmV := client.ExpectVariablesResponse(t)
|
tmV := client.ExpectVariablesResponse(t)
|
||||||
checkChildren(t, tmV, "tm.v", 1)
|
checkChildren(t, tmV, "tm.v", 1)
|
||||||
// TODO(polina): this evaluate name is not usable - it should be empty
|
// 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 {
|
if ref > 0 {
|
||||||
client.VariablesRequest(ref)
|
client.VariablesRequest(ref)
|
||||||
tmV0 := client.ExpectVariablesResponse(t)
|
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" {
|
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)
|
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,
|
disconnect: false,
|
||||||
}})
|
}})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEvaluateRequestLongStr(t *testing.T) {
|
func TestEvaluateRequestLongStrLargeValue(t *testing.T) {
|
||||||
runTest(t, "testvariables2", func(client *daptest.Client, fixture protest.Fixture) {
|
runTest(t, "testvariables2", func(client *daptest.Client, fixture protest.Fixture) {
|
||||||
runDebugSessionWithBPs(t, client, "launch",
|
runDebugSessionWithBPs(t, client, "launch",
|
||||||
// Launch
|
// Launch
|
||||||
@ -2741,22 +2746,24 @@ func TestEvaluateRequestLongStr(t *testing.T) {
|
|||||||
[]onBreakpoint{{
|
[]onBreakpoint{{
|
||||||
execute: func() {
|
execute: func() {
|
||||||
|
|
||||||
longstr := `"very long string 0123456789a0123456789b0123456789c0123456789d0123456789e0123456789f0123456789g012345678h90123456789i0123456789j0123456789"`
|
|
||||||
longstrTruncated := `"very long string 0123456789a0123456789b0123456789c0123456789d012...+73 more"`
|
|
||||||
|
|
||||||
checkStop(t, client, 1, "main.main", -1)
|
checkStop(t, client, 1, "main.main", -1)
|
||||||
|
|
||||||
client.VariablesRequest(1001) // Locals
|
client.VariablesRequest(1001) // Locals
|
||||||
locals := client.ExpectVariablesResponse(t)
|
locals := client.ExpectVariablesResponse(t)
|
||||||
|
|
||||||
// reflect.Kind == String, load with longer load limit if evaluated in repl/variables context.
|
// 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) {
|
t.Run(evalContext, func(t *testing.T) {
|
||||||
// long string
|
// long string
|
||||||
|
|
||||||
|
longstr := `"very long string 0123456789a0123456789b0123456789c0123456789d0123456789e0123456789f0123456789g012345678h90123456789i0123456789j0123456789"`
|
||||||
|
longstrTruncated := `"very long string 0123456789a0123456789b0123456789c0123456789d012...+73 more"`
|
||||||
|
|
||||||
client.EvaluateRequest("longstr", 0, evalContext)
|
client.EvaluateRequest("longstr", 0, evalContext)
|
||||||
got := client.ExpectEvaluateResponse(t)
|
got := client.ExpectEvaluateResponse(t)
|
||||||
want := longstrTruncated
|
want := longstrTruncated
|
||||||
if evalContext == "repl" || evalContext == "variables" {
|
switch evalContext {
|
||||||
|
case "repl", "variables", "hover", "clipboard":
|
||||||
want = longstr
|
want = longstr
|
||||||
}
|
}
|
||||||
checkEval(t, got, want, false)
|
checkEval(t, got, want, false)
|
||||||
@ -2769,9 +2776,22 @@ func TestEvaluateRequestLongStr(t *testing.T) {
|
|||||||
// variables are not affected.
|
// variables are not affected.
|
||||||
checkVarExact(t, locals, -1, "longstr", "longstr", longstrTruncated, "string", noChildren)
|
checkVarExact(t, locals, -1, "longstr", "longstr", longstrTruncated, "string", noChildren)
|
||||||
checkVarExact(t, locals, -1, "m6", "m6", `main.C {s: `+longstrTruncated+`}`, "main.C", hasChildren)
|
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,
|
disconnect: true,
|
||||||
}})
|
}})
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user