|
4 | 4 | require 'irb/completion' |
5 | 5 | require 'tmpdir' |
6 | 6 | require 'fileutils' |
| 7 | +require_relative 'variable' |
| 8 | +require_relative 'variable_inspector' |
7 | 9 |
|
8 | 10 | module DEBUGGER__ |
9 | 11 | module UI_DAP |
@@ -765,18 +767,11 @@ def register_vars vars, tid |
765 | 767 | end |
766 | 768 | end |
767 | 769 |
|
768 | | - class NaiveString |
769 | | - attr_reader :str |
770 | | - def initialize str |
771 | | - @str = str |
772 | | - end |
773 | | - end |
774 | | - |
775 | 770 | class ThreadClient |
776 | 771 | MAX_LENGTH = 180 |
777 | 772 |
|
778 | 773 | def value_inspect obj, short: true |
779 | | - # TODO: max length should be configuarable? |
| 774 | + # TODO: max length should be configurable? |
780 | 775 | str = DEBUGGER__.safe_inspect obj, short: short, max_length: MAX_LENGTH |
781 | 776 |
|
782 | 777 | if str.encoding == Encoding::UTF_8 |
@@ -867,57 +862,26 @@ def process_dap args |
867 | 862 | fid = args.shift |
868 | 863 | frame = get_frame(fid) |
869 | 864 | vars = collect_locals(frame).map do |var, val| |
870 | | - variable(var, val) |
| 865 | + render_variable Variable.new(name: var, value: val) |
871 | 866 | end |
872 | 867 |
|
873 | 868 | event! :protocol_result, :scope, req, variables: vars, tid: self.id |
874 | 869 | when :variable |
875 | 870 | vid = args.shift |
876 | 871 | obj = @var_map[vid] |
877 | 872 | if obj |
878 | | - case req.dig('arguments', 'filter') |
| 873 | + members = case req.dig('arguments', 'filter') |
879 | 874 | 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 | + ) |
886 | 880 | 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) |
920 | 882 | end |
| 883 | + |
| 884 | + vars = members.map { |member| render_variable member } |
921 | 885 | end |
922 | 886 | event! :protocol_result, :variable, req, variables: (vars || []), tid: self.id |
923 | 887 |
|
@@ -974,7 +938,13 @@ def process_dap args |
974 | 938 | result = 'Error: Can not evaluate on this frame' |
975 | 939 | end |
976 | 940 |
|
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) |
978 | 948 |
|
979 | 949 | when :completions |
980 | 950 | fid, text = args |
@@ -1036,72 +1006,48 @@ def search_const b, expr |
1036 | 1006 | false |
1037 | 1007 | end |
1038 | 1008 |
|
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] |
1050 | 1016 | end |
1051 | | - end |
1052 | 1017 |
|
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. |
1054 | 1021 | 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 |
1055 | 1023 | vid = @var_map.size + 1 |
1056 | | - @var_map[vid] = obj |
| 1024 | + @var_map[vid] = member.value |
1057 | 1025 | else |
| 1026 | + # This object has no children, so we don't need to remember it in the `@var_map` |
1058 | 1027 | vid = 0 |
1059 | 1028 | end |
1060 | 1029 |
|
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, |
1074 | 1037 | variablesReference: vid, |
1075 | | - indexedVariables: indexedVariables, |
1076 | | - namedVariables: namedVariables, |
1077 | 1038 | } |
1078 | 1039 | else |
1079 | | - { result: str, |
1080 | | - type: type_name(obj), |
| 1040 | + { |
| 1041 | + value: member.inspect_value, |
| 1042 | + type: member.value_type_name, |
1081 | 1043 | variablesReference: vid, |
1082 | | - indexedVariables: indexedVariables, |
1083 | | - namedVariables: namedVariables, |
1084 | 1044 | } |
1085 | 1045 | end |
1086 | | - end |
1087 | 1046 |
|
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 |
1105 | 1051 | end |
1106 | 1052 | end |
1107 | 1053 | end |
0 commit comments