Uh oh!
There was an error while loading. Please reload this page.
Share variable inspection logic between CDP and DAP - #1001
Conversation
| VariableInspector.new.named_members_of(obj) | ||
| end | ||
| vars = members.map { |member| variable(member.name, member.value) } |
There was a problem hiding this comment.
There's nothing implemented like CDP's internalProperty yet, but I think support for presentationHint can be added here eventually to achieve a similar effect.
| MAX_LENGTH = 180 | ||
| def value_inspect obj, short: true | ||
| # TODO: max length should be configurable? | ||
| str = DEBUGGER__.safe_inspect obj, short: short, max_length: MAX_LENGTH | ||
| if str.encoding == Encoding::UTF_8 | ||
| str.scrub | ||
| else | ||
| str.encode(Encoding::UTF_8, invalid: :replace, undef: :replace) | ||
| end | ||
| end |
There was a problem hiding this comment.
I just copied this snippet for now, but my eventual goal is to move all presentation logic out of ThreadClient and into a shared class like this.
| def named_members_of obj | ||
| members = case obj | ||
| when Array then obj.map.with_index { |o, i| Member.new(name: i.to_s, value: o) } | ||
| when Hash then obj.map { |k, v| Member.new(name: value_inspect(k), value: v) } |
There was a problem hiding this comment.
This used to be inconsistent before. VSCode would format Hash keys with value_inspect, but not Chrome. Now they will both us it.
| result += M_INSTANCE_VARIABLES.bind_call(obj).map{|iv| | ||
| variable(iv, M_INSTANCE_VARIABLE_GET.bind_call(obj, iv)) | ||
| } | ||
| prop += [internalProperty('#class', M_CLASS.bind_call(obj))] |
There was a problem hiding this comment.
For Chrome, #class is appended as the last prop, whereas for VSCode it was unshifted to the top.
1295842 to
6119aa8Compare34b1210 to
de6411bCompare| obj = @var_map[vid] | ||
| if obj |
There was a problem hiding this comment.
This had a small bug, where true would have its #class expanded, but not false or nil:

Notice that false and nil still have disclosure triangles. This is because of another bug, in variable_ (lines 1001-1002), which assumed that all objects would have at least 1 named member (#class), which wasn't the case here.
The new check fixes it:

There was a problem hiding this comment.
... this change allocates one more Member than before:
#<Member name="#class" value=NilClass internal>
Which bumps some of the variablesReference numbers in test/protocol/step_back_raw_dap_test.rb by 1.
See 63517f5
| result: result_variable.inspect_value, | ||
| **render_variable(result_variable) |
There was a problem hiding this comment.
This result: value used to come from the variable_ method when name was nil. That was pretty tricky, so I moved it here
| namedVariables += M_INSTANCE_VARIABLES.bind_call(obj).size | ||
| if NaiveString === obj | ||
| str = obj.str.dump | ||
| vid = indexedVariables = namedVariables = 0 | ||
| else | ||
| str = value_inspect(obj) | ||
| end |
There was a problem hiding this comment.
This just becomes a natural part of VariableInspecto#named_members_of, so we don't have to worry about it here anymore.
| str = value_inspect(obj) | ||
| end | ||
| if name |
There was a problem hiding this comment.
This if name check is really checking if this is going to be used in a VariablesResponse (with a name:). If name is nil, then this was called from #evaluate_result for a EvaluateResponse (with a result:).
I moved this logic to the evaluate code path instead.
| variable[:indexedVariables] = indexedVariables unless indexedVariables == 0 | ||
| variable[:namedVariables] = namedVariables unless namedVariables == 0 |
There was a problem hiding this comment.
This is kind of nice, it declutters the response logging a lot, so it's easier to debug.
| when String | ||
| variable_ name, obj, namedVariables: 3 # #length, #encoding, #to_str |
There was a problem hiding this comment.
This separation between the code that answers "what are the members?" and "how many members are there?" made it quite difficult to make changes to the presentation objects correctly.
Now both have a single source of truth, in VariableInspector's #indexed_members_of and #named_members_of.
| rescue Exception => e | ||
| "<#inspect raises #{e.inspect}>" | ||
| def self.safe_inspect obj, max_length: LimitedPP::SHORT_INSPECT_LENGTH, short: false | ||
| LimitedPP.safe_inspect(obj, max_length: max_length, short: short) |
There was a problem hiding this comment.
I extracted this out so it can be used in VariableInspector (without needing to require the whole session.rb, such as in the unit test).
I left this stub to minimize the churn here
de6411b to
ee133fdCompareee133fd to
fb05fe3Compareko1
commented
Sep 25, 2023
|
I've already run this with CDP, it works great.
@ko1 Done. I still call it |
| module DEBUGGER__ | ||
| class VariableInspector | ||
| def indexed_members_of(obj, start:, count:) | ||
| return [] if start > (obj.length - 1) |
There was a problem hiding this comment.
In this implementation, the #class will disappear when displaying Array. Is this intentional behavior?
There was a problem hiding this comment.
That makes sense (in the same spirit as #1004), though perhaps we should keep the #class if it's a non-standard subclass of Array. I'll make that in a separate PR.
Also, some tests are failed in protocol part. Can you fix them? |
amomchilov
commented
Dec 14, 2023
@ono-max I can have a look over the holiday break next week :) |
a0a8b3b to
2e850bcCompareamomchilov
commented
Jan 4, 2024
Hey @ono-max, Happy new year! I've fixed the protocol tests, and rebased onto the latest master. There's 2 test failures in |
2e850bc to
6837459Compare| internalProperty('#begin', obj.begin), | ||
| internalProperty('#end', obj.end), | ||
| ] | ||
| if @obj_map.key?(oid) |
There was a problem hiding this comment.
Good catch. I missed the edge case for falsy value.
| def indexed_members_of(obj, start:, count:) | ||
| return [] if start > (obj.length - 1) | ||
| capped_count = [count, obj.length - start].min |
There was a problem hiding this comment.
Can you add the comment why we need to compare count and obj.length - start and choose the minimum one? Sorry, I could not understand the reason.
There was a problem hiding this comment.
It's to keep the iteration from running off the end of the array. This used to give extra nils at the end. See 6837459
I can add a comment.
Could you please run the CI?
It seems to be that some protocol tests are still failed. Can you please check them? If you need help, please let me know 👍 I believe that https://github.com/ruby/debug/actions/runs/7405610000/job/20183471986?pr=1001 and https://github.com/ruby/debug/actions/runs/7405610000/job/20183471690?pr=1001 are not flakey tests. |
amomchilov
commented
Jan 6, 2024
@ono-max Hmmm I think we have one of those "it works on my machine" situations I'm running on Ruby 3.3.0 on macOS Sonoma 14.2.1. I suspect these errors are coming from the ancestors chains being in different in CI, but I don't know why that would be. Could you please help me investigate this? I don't have access to a Linux machine to compare against. |
ono-max
commented
Jan 7, 2024
I investigated the cause of the failed tests. I believe that the reason is because ancestor classes are changed from Ruby 3.1. Thus, we can skip the test if the Ruby version is lower than 3.1, e.g. 6f154ac |
amomchilov
commented
Jan 8, 2024
@ono-max Fantastic find! I pulled that commit into this PR, so you get credit for it :) Is this ready to merge? |
ono-max
commented
Jan 8, 2024
I appreciate your dedicated efforts and hard work. @ko1-san will merge this PR 👍 |
amomchilov
commented
Jan 8, 2024
@ono-max Thanks! Could you run the CI one more time, so we're sure it's green before merging? |
@ono-max I think the remaining test failures are just flakey tests. I can reproduce them Ruby 3.1.4, but they fail less than 1/10 times. |
amomchilov
commented
Jan 19, 2024
@ko1 Can you please review and merge this? |
6f154ac to
48d487dCompare✅ All Tests passed!✖️no tests failed ✔️668 tests passed(2 flakes) |
amomchilov
commented
Nov 14, 2024
@ko1 Are these flaky tests? I don't understand what went wrong, but it doesn't seem related to these changes |
48d487d to
5239b66Compare5239b66 to
c790c31Compare
The logic that decides how variables are presented is repeated between the CDP and DAP implementations. This is error prone and leads to inconsistencies, where improvements are made to one or the other, but not both. E.g.:
#dumpmember forStringonly exists in VSCode DAP: show#dumpwhen pp'ed string is too long #869#classmembers only exists for VSCode DAP: add the ability to hide the #class item for each object #986value_inspect, but not Chrome#classappears at the end, whereas DAP adds#classto the start.This PR extracts the variable inspection logic into a new
VariableInspectorclass, which can be shared between VSCode and Chrome. This gives a central place to make future improvements.I tried to make only the minimal behaviour changes necessary to unify the two implementations.
This PR then unlocks the ability to make some changes really nice/easily:
:sym: "value"→sym: "value") #1003#classfor simple values #1004