Skip to content

Commit bd1baa4

Browse files
committed
Share variable inspection logic between CDP and DAP
1 parent 31452f9 commit bd1baa4

File tree

7 files changed

+481
-172
lines changed

7 files changed

+481
-172
lines changed

lib/debug/limited_pp.rb

Lines changed: 52 additions & 0 deletions
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

Lines changed: 20 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
require 'tmpdir'
1010
require 'tempfile'
1111
require 'timeout'
12+
require_relative 'variable'
13+
require_relative 'variable_inspector'
1214

1315
module DEBUGGER__
1416
module UI_CDP
@@ -1112,46 +1114,29 @@ def process_cdp args
11121114
event! :protocol_result, :scope, req, vars
11131115
when :properties
11141116
oid = args.shift
1115-
result = []
1116-
prop = []
11171117

11181118
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-
]
1119+
members = if Array === obj
1120+
VariableInspector.new.indexed_members_of(obj, start: 0, count: obj.size)
1121+
else
1122+
VariableInspector.new.named_members_of(obj)
11471123
end
11481124

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))]
1125+
result = members.filter_map do |member|
1126+
next if member.internal?
1127+
variable(member.name, member.value)
1128+
end
1129+
1130+
internal_properties = members.filter_map do |member|
1131+
next unless member.internal?
1132+
internalProperty(member.name, member.value)
1133+
end
1134+
else
1135+
result = []
1136+
internal_properties = []
11531137
end
1154-
event! :protocol_result, :properties, req, result: result, internalProperties: prop
1138+
1139+
event! :protocol_result, :properties, req, result: result, internalProperties: internal_properties
11551140
when :exception
11561141
oid = args.shift
11571142
exc = nil

lib/debug/server_dap.rb

Lines changed: 49 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
require 'irb/completion'
55
require 'tmpdir'
66
require 'fileutils'
7+
require_relative 'variable'
8+
require_relative 'variable_inspector'
79

810
module DEBUGGER__
911
module UI_DAP
@@ -765,18 +767,11 @@ def register_vars vars, tid
765767
end
766768
end
767769

768-
class NaiveString
769-
attr_reader :str
770-
def initialize str
771-
@str = str
772-
end
773-
end
774-
775770
class ThreadClient
776771
MAX_LENGTH = 180
777772

778773
def value_inspect obj, short: true
779-
# TODO: max length should be configuarable?
774+
# TODO: max length should be configurable?
780775
str = DEBUGGER__.safe_inspect obj, short: short, max_length: MAX_LENGTH
781776

782777
if str.encoding == Encoding::UTF_8
@@ -869,57 +864,26 @@ def process_dap args
869864
fid = args.shift
870865
frame = get_frame(fid)
871866
vars = collect_locals(frame).map do |var, val|
872-
variable(var, val)
867+
render_variable Variable.new(name: var, value: val)
873868
end
874869

875870
event! :protocol_result, :scope, req, variables: vars, tid: self.id
876871
when :variable
877872
vid = args.shift
878873
obj = @var_map[vid]
879874
if obj
880-
case req.dig('arguments', 'filter')
875+
members = case req.dig('arguments', 'filter')
881876
when 'indexed'
882-
start = req.dig('arguments', 'start') || 0
883-
# FIXME: `req.dig('arguments', 'count')` needs to be capped to be `<= obj.size`.
884-
count = req.dig('arguments', 'count') || obj.size
885-
vars = (start ... (start + count)).map{|i|
886-
variable(i.to_s, obj[i])
887-
}
877+
VariableInspector.new.indexed_members_of(
878+
obj,
879+
start: req.dig('arguments', 'start') || 0,
880+
count: req.dig('arguments', 'count') || obj.size,
881+
)
888882
else
889-
vars = []
890-
891-
case obj
892-
when Hash
893-
vars = obj.map{|k, v|
894-
variable(value_inspect(k), v,)
895-
}
896-
when Struct
897-
vars = obj.members.map{|m|
898-
variable(m, obj[m])
899-
}
900-
when String
901-
vars = [
902-
variable('#length', obj.length),
903-
variable('#encoding', obj.encoding),
904-
]
905-
printed_str = value_inspect(obj)
906-
vars << variable('#dump', NaiveString.new(obj)) if printed_str.end_with?('...')
907-
when Class, Module
908-
vars << variable('%ancestors', obj.ancestors[1..])
909-
when Range
910-
vars = [
911-
variable('#begin', obj.begin),
912-
variable('#end', obj.end),
913-
]
914-
end
915-
916-
unless NaiveString === obj
917-
vars += M_INSTANCE_VARIABLES.bind_call(obj).sort.map{|iv|
918-
variable(iv, M_INSTANCE_VARIABLE_GET.bind_call(obj, iv))
919-
}
920-
vars.unshift variable('#class', M_CLASS.bind_call(obj))
921-
end
883+
VariableInspector.new.named_members_of(obj)
922884
end
885+
886+
vars = members.map { |member| render_variable member }
923887
end
924888
event! :protocol_result, :variable, req, variables: (vars || []), tid: self.id
925889

@@ -976,7 +940,13 @@ def process_dap args
976940
result = 'Error: Can not evaluate on this frame'
977941
end
978942

979-
event! :protocol_result, :evaluate, req, message: message, tid: self.id, **evaluate_result(result)
943+
result_variable = Variable.new(name: nil, value: result)
944+
945+
event! :protocol_result, :evaluate, req,
946+
message: message,
947+
tid: self.id,
948+
result: result_variable.inspect_value,
949+
**render_variable(result_variable)
980950

981951
when :completions
982952
fid, text = args
@@ -1052,59 +1022,48 @@ def type_name obj
10521022
end
10531023
end
10541024

1055-
def variable_ name, obj, indexedVariables: 0, namedVariables: 0
1056-
if indexedVariables > 0 || namedVariables > 0
1057-
vid = @var_map.size + 1
1058-
@var_map[vid] = obj
1059-
File.write("/tmp/server_dap_debug_log1.txt", "Assigned id #{vid} to name=#{name.inspect} value=#{obj.inspect}\n", mode: 'a+')
1025+
# Renders the given Member into a DAP Variable
1026+
# https://microsoft.github.io/debug-adapter-protocol/specification#variable
1027+
def render_variable member
1028+
indexedVariables, namedVariables = if Array === member.value
1029+
[member.value.size, 0]
10601030
else
1061-
vid = 0
1031+
[0, VariableInspector.new.named_members_of(member.value).count]
10621032
end
10631033

1064-
namedVariables += M_INSTANCE_VARIABLES.bind_call(obj).size
1065-
1066-
if NaiveString === obj
1067-
str = obj.str.dump
1068-
vid = indexedVariables = namedVariables = 0
1034+
# > If `variablesReference` is > 0, the variable is structured and its children
1035+
# > can be retrieved by passing `variablesReference` to the `variables` request
1036+
# > as long as execution remains suspended.
1037+
if indexedVariables > 0 || namedVariables > 0
1038+
# This object has children that we might need to query, so we need to remember it by its vid
1039+
vid = @var_map.size + 1
1040+
@var_map[vid] = member.value
10691041
else
1070-
str = value_inspect(obj)
1042+
# This object has no children, so we don't need to remember it in the `@var_map`
1043+
vid = 0
10711044
end
10721045

1073-
if name
1074-
{ name: name,
1075-
value: str,
1076-
type: type_name(obj),
1046+
variable = if member.name
1047+
# These two hashes are repeated so the "name" can come always come first, when available,
1048+
# which improves the readability of protocol responses.
1049+
{
1050+
name: member.name,
1051+
value: member.inspect_value,
1052+
type: member.value_type_name,
10771053
variablesReference: vid,
1078-
indexedVariables: indexedVariables,
1079-
namedVariables: namedVariables,
10801054
}
10811055
else
1082-
{ result: str,
1083-
type: type_name(obj),
1056+
{
1057+
value: member.inspect_value,
1058+
type: member.value_type_name,
10841059
variablesReference: vid,
1085-
indexedVariables: indexedVariables,
1086-
namedVariables: namedVariables,
10871060
}
10881061
end
1089-
end
10901062

1091-
def variable name, obj
1092-
case obj
1093-
when Array
1094-
variable_ name, obj, indexedVariables: obj.size
1095-
when Hash
1096-
variable_ name, obj, namedVariables: obj.size
1097-
when String
1098-
variable_ name, obj, namedVariables: 3 # #length, #encoding, #to_str
1099-
when Struct
1100-
variable_ name, obj, namedVariables: obj.size
1101-
when Class, Module
1102-
variable_ name, obj, namedVariables: 1 # %ancestors (#ancestors without self)
1103-
when Range
1104-
variable_ name, obj, namedVariables: 2 # #begin, #end
1105-
else
1106-
variable_ name, obj, namedVariables: 1 # #class
1107-
end
1063+
variable[:indexedVariables] = indexedVariables unless indexedVariables == 0
1064+
variable[:namedVariables] = namedVariables unless namedVariables == 0
1065+
1066+
variable
11081067
end
11091068
end
11101069
end

lib/debug/session.rb

Lines changed: 3 additions & 47 deletions
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'
@@ -2319,53 +2320,8 @@ def self.load_rc
23192320
end
23202321
end
23212322

2322-
# Inspector
2323-
2324-
SHORT_INSPECT_LENGTH = 40
2325-
2326-
class LimitedPP
2327-
def self.pp(obj, max=80)
2328-
out = self.new(max)
2329-
catch out do
2330-
PP.singleline_pp(obj, out)
2331-
end
2332-
out.buf
2333-
end
2334-
2335-
attr_reader :buf
2336-
2337-
def initialize max
2338-
@max = max
2339-
@cnt = 0
2340-
@buf = String.new
2341-
end
2342-
2343-
def <<(other)
2344-
@buf << other
2345-
2346-
if @buf.size >= @max
2347-
@buf = @buf[0..@max] + '...'
2348-
throw self
2349-
end
2350-
end
2351-
end
2352-
2353-
def self.safe_inspect obj, max_length: SHORT_INSPECT_LENGTH, short: false
2354-
if short
2355-
LimitedPP.pp(obj, max_length)
2356-
else
2357-
obj.inspect
2358-
end
2359-
rescue NoMethodError => e
2360-
klass, oid = M_CLASS.bind_call(obj), M_OBJECT_ID.bind_call(obj)
2361-
if obj == (r = e.receiver)
2362-
"<\##{klass.name}#{oid} does not have \#inspect>"
2363-
else
2364-
rklass, roid = M_CLASS.bind_call(r), M_OBJECT_ID.bind_call(r)
2365-
"<\##{klass.name}:#{roid} contains <\##{rklass}:#{roid} and it does not have #inspect>"
2366-
end
2367-
rescue Exception => e
2368-
"<#inspect raises #{e.inspect}>"
2323+
def self.safe_inspect obj, max_length: LimitedPP::SHORT_INSPECT_LENGTH, short: false
2324+
LimitedPP.safe_inspect(obj, max_length: max_length, short: short)
23692325
end
23702326

23712327
def self.warn msg

0 commit comments

Comments
 (0)