Skip to content

Commit 93c0efb

Browse files
committed
Share variable inspection logic between CDP and DAP
1 parent 1b7a027 commit 93c0efb

7 files changed

Lines changed: 479 additions & 183 deletions

File tree

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

@@ -974,7 +938,13 @@ def process_dap args
974938
result = 'Error: Can not evaluate on this frame'
975939
end
976940

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

979949
when :completions
980950
fid, text = args
@@ -1036,72 +1006,48 @@ def search_const b, expr
10361006
false
10371007
end
10381008

1039-
def evaluate_result r
1040-
variable nil, r
1041-
end
1042-
1043-
def type_name obj
1044-
klass = M_CLASS.bind_call(obj)
1045-
1046-
begin
1047-
M_NAME.bind_call(klass) || klass.to_s
1048-
rescue Exception => e
1049-
"<Error: #{e.message} (#{e.backtrace.first}>"
1009+
# Renders the given Member into a DAP Variable
1010+
# https://microsoft.github.io/debug-adapter-protocol/specification#variable
1011+
def render_variable member
1012+
indexedVariables, namedVariables = if Array === member.value
1013+
[member.value.size, 0]
1014+
else
1015+
[0, VariableInspector.new.named_members_of(member.value).count]
10501016
end
1051-
end
10521017

1053-
def variable_ name, obj, indexedVariables: 0, namedVariables: 0
1018+
# > If `variablesReference` is > 0, the variable is structured and its children
1019+
# > can be retrieved by passing `variablesReference` to the `variables` request
1020+
# > as long as execution remains suspended.
10541021
if indexedVariables > 0 || namedVariables > 0
1022+
# This object has children that we might need to query, so we need to remember it by its vid
10551023
vid = @var_map.size + 1
1056-
@var_map[vid] = obj
1024+
@var_map[vid] = member.value
10571025
else
1026+
# This object has no children, so we don't need to remember it in the `@var_map`
10581027
vid = 0
10591028
end
10601029

1061-
namedVariables += M_INSTANCE_VARIABLES.bind_call(obj).size
1062-
1063-
if NaiveString === obj
1064-
str = obj.str.dump
1065-
vid = indexedVariables = namedVariables = 0
1066-
else
1067-
str = value_inspect(obj)
1068-
end
1069-
1070-
if name
1071-
{ name: name,
1072-
value: str,
1073-
type: type_name(obj),
1030+
variable = if member.name
1031+
# These two hashes are repeated so the "name" can come always come first, when available,
1032+
# which improves the readability of protocol responses.
1033+
{
1034+
name: member.name,
1035+
value: member.inspect_value,
1036+
type: member.value_type_name,
10741037
variablesReference: vid,
1075-
indexedVariables: indexedVariables,
1076-
namedVariables: namedVariables,
10771038
}
10781039
else
1079-
{ result: str,
1080-
type: type_name(obj),
1040+
{
1041+
value: member.inspect_value,
1042+
type: member.value_type_name,
10811043
variablesReference: vid,
1082-
indexedVariables: indexedVariables,
1083-
namedVariables: namedVariables,
10841044
}
10851045
end
1086-
end
10871046

1088-
def variable name, obj
1089-
case obj
1090-
when Array
1091-
variable_ name, obj, indexedVariables: obj.size
1092-
when Hash
1093-
variable_ name, obj, namedVariables: obj.size
1094-
when String
1095-
variable_ name, obj, namedVariables: 3 # #length, #encoding, #to_str
1096-
when Struct
1097-
variable_ name, obj, namedVariables: obj.size
1098-
when Class, Module
1099-
variable_ name, obj, namedVariables: 1 # %ancestors (#ancestors without self)
1100-
when Range
1101-
variable_ name, obj, namedVariables: 2 # #begin, #end
1102-
else
1103-
variable_ name, obj, namedVariables: 1 # #class
1104-
end
1047+
variable[:indexedVariables] = indexedVariables unless indexedVariables == 0
1048+
variable[:namedVariables] = namedVariables unless namedVariables == 0
1049+
1050+
variable
11051051
end
11061052
end
11071053
end

0 commit comments

Comments
 (0)