Skip to content

Commit 1295842

Browse files
committed
Share variable inspection logic between CDP and DAP
1 parent 4ec9d7a commit 1295842

File tree

6 files changed

+412
-130
lines changed

6 files changed

+412
-130
lines changed

lib/debug/limited_pp.rb

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# frozen_string_literal: true
2+
3+
require "pp"
4+
5+
module DEBUGGER__
6+
class LimitedPP
7+
SHORT_INSPECT_LENGTH = 40
8+
9+
def self.pp(obj, max = 80)
10+
out = self.new(max)
11+
catch out do
12+
::PP.singleline_pp(obj, out)
13+
end
14+
out.buf
15+
end
16+
17+
attr_reader :buf
18+
19+
def initialize max
20+
@max = max
21+
@cnt = 0
22+
@buf = String.new
23+
end
24+
25+
def <<(other)
26+
@buf << other
27+
28+
if @buf.size >= @max
29+
@buf = @buf[0..@max] + '...'
30+
throw self
31+
end
32+
end
33+
34+
def self.safe_inspect obj, max_length: SHORT_INSPECT_LENGTH, short: false
35+
if short
36+
LimitedPP.pp(obj, max_length)
37+
else
38+
obj.inspect
39+
end
40+
rescue NoMethodError => e
41+
klass, oid = M_CLASS.bind_call(obj), M_OBJECT_ID.bind_call(obj)
42+
if obj == (r = e.receiver)
43+
"<\##{klass.name}#{oid} does not have \#inspect>"
44+
else
45+
rklass, roid = M_CLASS.bind_call(r), M_OBJECT_ID.bind_call(r)
46+
"<\##{klass.name}:#{roid} contains <\##{rklass}:#{roid} and it does not have #inspect>"
47+
end
48+
# rescue Exception => e
49+
# "<#inspect raises #{e.inspect}>"
50+
end
51+
end
52+
end

lib/debug/server_cdp.rb

+19-35
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
require 'tmpdir'
1010
require 'tempfile'
1111
require 'timeout'
12+
require_relative 'variable_inspector'
1213

1314
module DEBUGGER__
1415
module UI_CDP
@@ -1112,46 +1113,29 @@ def process_cdp args
11121113
event! :protocol_result, :scope, req, vars
11131114
when :properties
11141115
oid = args.shift
1115-
result = []
1116-
prop = []
11171116

11181117
if obj = @obj_map[oid]
1119-
case obj
1120-
when Array
1121-
result = obj.map.with_index{|o, i|
1122-
variable i.to_s, o
1123-
}
1124-
when Hash
1125-
result = obj.map{|k, v|
1126-
variable(k, v)
1127-
}
1128-
when Struct
1129-
result = obj.members.map{|m|
1130-
variable(m, obj[m])
1131-
}
1132-
when String
1133-
prop = [
1134-
internalProperty('#length', obj.length),
1135-
internalProperty('#encoding', obj.encoding)
1136-
]
1137-
when Class, Module
1138-
result = obj.instance_variables.map{|iv|
1139-
variable(iv, obj.instance_variable_get(iv))
1140-
}
1141-
prop = [internalProperty('%ancestors', obj.ancestors[1..])]
1142-
when Range
1143-
prop = [
1144-
internalProperty('#begin', obj.begin),
1145-
internalProperty('#end', obj.end),
1146-
]
1118+
members = if obj.is_a?(Array)
1119+
VariableInspector.new.indexed_members_of(obj, start: 0, count: obj.size)
1120+
else
1121+
VariableInspector.new.named_members_of(obj)
11471122
end
11481123

1149-
result += M_INSTANCE_VARIABLES.bind_call(obj).map{|iv|
1150-
variable(iv, M_INSTANCE_VARIABLE_GET.bind_call(obj, iv))
1151-
}
1152-
prop += [internalProperty('#class', M_CLASS.bind_call(obj))]
1124+
result = members.filter_map do |member|
1125+
next if member.internal?
1126+
variable(member.name, member.value)
1127+
end
1128+
1129+
internal_properties = members.filter_map do |member|
1130+
next unless member.internal?
1131+
internalProperty(member.name, member.value)
1132+
end
1133+
else
1134+
result = []
1135+
internal_properties = []
11531136
end
1154-
event! :protocol_result, :properties, req, result: result, internalProperties: prop
1137+
1138+
event! :protocol_result, :properties, req, result: result, internalProperties: internal_properties
11551139
when :exception
11561140
oid = args.shift
11571141
exc = nil

lib/debug/server_dap.rb

+12-48
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
require 'irb/completion'
55
require 'tmpdir'
66
require 'fileutils'
7+
require_relative 'variable_inspector'
78

89
module DEBUGGER__
910
module UI_DAP
@@ -765,18 +766,11 @@ def register_vars vars, tid
765766
end
766767
end
767768

768-
class NaiveString
769-
attr_reader :str
770-
def initialize str
771-
@str = str
772-
end
773-
end
774-
775769
class ThreadClient
776770
MAX_LENGTH = 180
777771

778772
def value_inspect obj, short: true
779-
# TODO: max length should be configuarable?
773+
# TODO: max length should be configurable?
780774
str = DEBUGGER__.safe_inspect obj, short: short, max_length: MAX_LENGTH
781775

782776
if str.encoding == Encoding::UTF_8
@@ -875,48 +869,18 @@ def process_dap args
875869
vid = args.shift
876870
obj = @var_map[vid]
877871
if obj
878-
case req.dig('arguments', 'filter')
872+
members = case req.dig('arguments', 'filter')
879873
when 'indexed'
880-
start = req.dig('arguments', 'start') || 0
881-
count = req.dig('arguments', 'count') || obj.size
882-
vars = (start ... (start + count)).map{|i|
883-
variable(i.to_s, obj[i])
884-
}
874+
VariableInspector.new.indexed_members_of(
875+
obj,
876+
start: req.dig('arguments', 'start') || 0,
877+
count: req.dig('arguments', 'count') || obj.size,
878+
)
885879
else
886-
vars = []
887-
888-
case obj
889-
when Hash
890-
vars = obj.map{|k, v|
891-
variable(value_inspect(k), v,)
892-
}
893-
when Struct
894-
vars = obj.members.map{|m|
895-
variable(m, obj[m])
896-
}
897-
when String
898-
vars = [
899-
variable('#length', obj.length),
900-
variable('#encoding', obj.encoding),
901-
]
902-
printed_str = value_inspect(obj)
903-
vars << variable('#dump', NaiveString.new(obj)) if printed_str.end_with?('...')
904-
when Class, Module
905-
vars << variable('%ancestors', obj.ancestors[1..])
906-
when Range
907-
vars = [
908-
variable('#begin', obj.begin),
909-
variable('#end', obj.end),
910-
]
911-
end
912-
913-
unless NaiveString === obj
914-
vars += M_INSTANCE_VARIABLES.bind_call(obj).sort.map{|iv|
915-
variable(iv, M_INSTANCE_VARIABLE_GET.bind_call(obj, iv))
916-
}
917-
vars.unshift variable('#class', M_CLASS.bind_call(obj))
918-
end
880+
VariableInspector.new.named_members_of(obj)
919881
end
882+
883+
vars = members.map { |member| variable(member.name, member.value) }
920884
end
921885
event! :protocol_result, :variable, req, variables: (vars || []), tid: self.id
922886

@@ -1059,7 +1023,7 @@ def variable_ name, obj, indexedVariables: 0, namedVariables: 0
10591023

10601024
namedVariables += M_INSTANCE_VARIABLES.bind_call(obj).size
10611025

1062-
if NaiveString === obj
1026+
if VariableInspector::NaiveString === obj
10631027
str = obj.str.dump
10641028
vid = indexedVariables = namedVariables = 0
10651029
else

lib/debug/session.rb

+3-47
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
require_relative 'source_repository'
3535
require_relative 'breakpoint'
3636
require_relative 'tracer'
37+
require_relative 'limited_pp'
3738

3839
# To prevent loading old lib/debug.rb in Ruby 2.6 to 3.0
3940
$LOADED_FEATURES << 'debug.rb'
@@ -2302,53 +2303,8 @@ def self.load_rc
23022303
end
23032304
end
23042305

2305-
# Inspector
2306-
2307-
SHORT_INSPECT_LENGTH = 40
2308-
2309-
class LimitedPP
2310-
def self.pp(obj, max=80)
2311-
out = self.new(max)
2312-
catch out do
2313-
PP.singleline_pp(obj, out)
2314-
end
2315-
out.buf
2316-
end
2317-
2318-
attr_reader :buf
2319-
2320-
def initialize max
2321-
@max = max
2322-
@cnt = 0
2323-
@buf = String.new
2324-
end
2325-
2326-
def <<(other)
2327-
@buf << other
2328-
2329-
if @buf.size >= @max
2330-
@buf = @buf[0..@max] + '...'
2331-
throw self
2332-
end
2333-
end
2334-
end
2335-
2336-
def self.safe_inspect obj, max_length: SHORT_INSPECT_LENGTH, short: false
2337-
if short
2338-
LimitedPP.pp(obj, max_length)
2339-
else
2340-
obj.inspect
2341-
end
2342-
rescue NoMethodError => e
2343-
klass, oid = M_CLASS.bind_call(obj), M_OBJECT_ID.bind_call(obj)
2344-
if obj == (r = e.receiver)
2345-
"<\##{klass.name}#{oid} does not have \#inspect>"
2346-
else
2347-
rklass, roid = M_CLASS.bind_call(r), M_OBJECT_ID.bind_call(r)
2348-
"<\##{klass.name}:#{roid} contains <\##{rklass}:#{roid} and it does not have #inspect>"
2349-
end
2350-
rescue Exception => e
2351-
"<#inspect raises #{e.inspect}>"
2306+
def self.safe_inspect obj, max_length: LimitedPP::SHORT_INSPECT_LENGTH, short: false
2307+
LimitedPP.safe_inspect(obj, max_length: max_length, short: short)
23522308
end
23532309

23542310
def self.warn msg

lib/debug/variable_inspector.rb

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# frozen_string_literal: true
2+
3+
require_relative 'limited_pp'
4+
5+
module DEBUGGER__
6+
class VariableInspector
7+
class Member
8+
attr_reader :name, :value
9+
10+
def initialize(name:, value:, internal: false)
11+
@name = name
12+
@value = value
13+
@is_internal = internal
14+
end
15+
16+
def internal?
17+
@is_internal
18+
end
19+
20+
def self.internal name:, value:
21+
new(name:, value:, internal: true)
22+
end
23+
24+
def ==(other)
25+
other.instance_of?(self.class) &&
26+
@name == other.name &&
27+
@value == other.value &&
28+
@is_internal == other.internal?
29+
end
30+
31+
def inspect
32+
"#<Member name=#{@name.inspect} value=#{@value.inspect}#{@is_internal ? " internal" : ""}>"
33+
end
34+
end
35+
36+
def indexed_members_of obj, start:, count:
37+
return [] if start > (obj.length - 1)
38+
39+
capped_count = [count, obj.length - start].min
40+
41+
(start...(start + capped_count)).map do |i|
42+
Member.new(name: i.to_s, value: obj[i])
43+
end
44+
end
45+
46+
def named_members_of obj
47+
members = case obj
48+
when Hash then obj.map { |k, v| Member.new(name: value_inspect(k), value: v) }
49+
when Struct then obj.members.map { |name| Member.new(name:, value: obj[name]) }
50+
when String
51+
members = [
52+
Member.internal(name: '#length', value: obj.length),
53+
Member.internal(name: '#encoding', value: obj.encoding),
54+
]
55+
56+
printed_str = value_inspect(obj)
57+
members << Member.internal(name: "#dump", value: NaiveString.new(obj)) if printed_str.end_with?('...')
58+
59+
members
60+
when Class, Module then [Member.internal(name: "%ancestors", value: obj.ancestors[1..])]
61+
when Range then [
62+
Member.internal(name: "#begin", value: obj.begin),
63+
Member.internal(name: "#end", value: obj.end),
64+
]
65+
else []
66+
end
67+
68+
unless NaiveString === obj
69+
members += M_INSTANCE_VARIABLES.bind_call(obj).sort.map{|iv|
70+
Member.new(name: iv, value: M_INSTANCE_VARIABLE_GET.bind_call(obj, iv))
71+
}
72+
members.unshift Member.internal(name: '#class', value: M_CLASS.bind_call(obj))
73+
end
74+
75+
members
76+
end
77+
78+
private
79+
80+
MAX_LENGTH = 180
81+
82+
def value_inspect obj, short: true
83+
# TODO: max length should be configurable?
84+
str = LimitedPP.safe_inspect obj, short: short, max_length: MAX_LENGTH
85+
86+
if str.encoding == Encoding::UTF_8
87+
str.scrub
88+
else
89+
str.encode(Encoding::UTF_8, invalid: :replace, undef: :replace)
90+
end
91+
end
92+
93+
# TODO: Replace with Reflection helpers once they are merged
94+
# https://github.com/ruby/debug/pull/1002
95+
M_INSTANCE_VARIABLES = method(:instance_variables).unbind
96+
M_INSTANCE_VARIABLE_GET = method(:instance_variable_get).unbind
97+
M_CLASS = method(:class).unbind
98+
99+
class NaiveString
100+
attr_reader :str
101+
def initialize str
102+
@str = str
103+
end
104+
105+
def == other
106+
other.instance_of?(self.class) && @str == other.str
107+
end
108+
end
109+
end
110+
end

0 commit comments

Comments
 (0)