Skip to content

Commit de6411b

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

File tree

7 files changed

+486
-182
lines changed

7 files changed

+486
-182
lines changed

Diff for: 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

Diff for: lib/debug/server_cdp.rb

+20-35
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

Diff for: lib/debug/server_dap.rb

+55-100
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
@@ -867,56 +862,28 @@ def process_dap args
867862
fid = args.shift
868863
frame = get_frame(fid)
869864
vars = collect_locals(frame).map do |var, val|
870-
variable(var, val)
865+
render_variable Variable.new(name: var, value: val)
871866
end
872867

873868
event! :protocol_result, :scope, req, variables: vars, tid: self.id
874869
when :variable
875870
vid = args.shift
876-
obj = @var_map[vid]
877-
if obj
878-
case req.dig('arguments', 'filter')
879-
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-
}
885-
else
886-
vars = []
887871

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
872+
if @var_map.has_key?(vid)
873+
obj = @var_map[vid]
912874

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
875+
members = case req.dig('arguments', 'filter')
876+
when 'indexed'
877+
VariableInspector.new.indexed_members_of(
878+
obj,
879+
start: req.dig('arguments', 'start') || 0,
880+
count: req.dig('arguments', 'count') || obj.size,
881+
)
882+
else
883+
VariableInspector.new.named_members_of(obj)
919884
end
885+
886+
vars = members.map { |member| render_variable member }
920887
end
921888
event! :protocol_result, :variable, req, variables: (vars || []), tid: self.id
922889

@@ -973,7 +940,13 @@ def process_dap args
973940
result = 'Error: Can not evaluate on this frame'
974941
end
975942

976-
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)
977950

978951
when :completions
979952
fid, text = args
@@ -1035,72 +1008,54 @@ def search_const b, expr
10351008
false
10361009
end
10371010

1038-
def evaluate_result r
1039-
variable nil, r
1040-
end
1011+
# Renders the given Member into a DAP Variable
1012+
# https://microsoft.github.io/debug-adapter-protocol/specification#variable
1013+
def render_variable member
1014+
indexedVariables, namedVariables = if Array === member.value
1015+
[member.value.size, 0]
1016+
else
1017+
[0, VariableInspector.new.named_members_of(member.value).count]
1018+
end
10411019

1042-
def type_name obj
1043-
klass = M_CLASS.bind_call(obj)
10441020

1045-
begin
1046-
M_NAME.bind_call(klass) || klass.to_s
1047-
rescue Exception => e
1048-
"<Error: #{e.message} (#{e.backtrace.first}>"
1021+
if member.value == false || member.value == true
1022+
require "awesome_print"
1023+
ap({ member:, indexedVariables:, namedVariables: })
10491024
end
1050-
end
10511025

1052-
def variable_ name, obj, indexedVariables: 0, namedVariables: 0
1026+
# > If `variablesReference` is > 0, the variable is structured and its children
1027+
# > can be retrieved by passing `variablesReference` to the `variables` request
1028+
# > as long as execution remains suspended.
10531029
if indexedVariables > 0 || namedVariables > 0
1030+
# This object has children that we might need to query, so we need to remember it by its vid
10541031
vid = @var_map.size + 1
1055-
@var_map[vid] = obj
1032+
@var_map[vid] = member.value
10561033
else
1034+
# This object has no children, so we don't need to remember it in the `@var_map`
10571035
vid = 0
10581036
end
10591037

1060-
namedVariables += M_INSTANCE_VARIABLES.bind_call(obj).size
1061-
1062-
if NaiveString === obj
1063-
str = obj.str.dump
1064-
vid = indexedVariables = namedVariables = 0
1065-
else
1066-
str = value_inspect(obj)
1067-
end
1068-
1069-
if name
1070-
{ name: name,
1071-
value: str,
1072-
type: type_name(obj),
1038+
variable = if member.name
1039+
# These two hashes are repeated so the "name" can come always come first, when available,
1040+
# which improves the readability of protocol responses.
1041+
{
1042+
name: member.name,
1043+
value: member.inspect_value,
1044+
type: member.value_type_name,
10731045
variablesReference: vid,
1074-
indexedVariables: indexedVariables,
1075-
namedVariables: namedVariables,
10761046
}
10771047
else
1078-
{ result: str,
1079-
type: type_name(obj),
1048+
{
1049+
value: member.inspect_value,
1050+
type: member.value_type_name,
10801051
variablesReference: vid,
1081-
indexedVariables: indexedVariables,
1082-
namedVariables: namedVariables,
10831052
}
10841053
end
1085-
end
10861054

1087-
def variable name, obj
1088-
case obj
1089-
when Array
1090-
variable_ name, obj, indexedVariables: obj.size
1091-
when Hash
1092-
variable_ name, obj, namedVariables: obj.size
1093-
when String
1094-
variable_ name, obj, namedVariables: 3 # #length, #encoding, #to_str
1095-
when Struct
1096-
variable_ name, obj, namedVariables: obj.size
1097-
when Class, Module
1098-
variable_ name, obj, namedVariables: 1 # %ancestors (#ancestors without self)
1099-
when Range
1100-
variable_ name, obj, namedVariables: 2 # #begin, #end
1101-
else
1102-
variable_ name, obj, namedVariables: 1 # #class
1103-
end
1055+
variable[:indexedVariables] = indexedVariables unless indexedVariables == 0
1056+
variable[:namedVariables] = namedVariables unless namedVariables == 0
1057+
1058+
variable
11041059
end
11051060
end
11061061
end

0 commit comments

Comments
 (0)