canopus 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +55 -0
- data/README.md +191 -19
- data/assets/keymaps/emacs.jsonc +5 -0
- data/assets/keymaps/jetbrains.jsonc +5 -0
- data/assets/keymaps/sublime.jsonc +5 -0
- data/assets/keymaps/vscode.jsonc +5 -0
- data/docs/debugging.md +143 -0
- data/docs/distribution.md +40 -9
- data/docs/lsp.md +131 -0
- data/docs/performance.md +18 -0
- data/docs/providers.md +36 -0
- data/docs/release-log.md +12 -0
- data/docs/tasks.md +64 -0
- data/docs/testing.md +49 -0
- data/docs/vim.md +3 -0
- data/exe/canopus +4 -1
- data/lib/canopus/buffer.rb +291 -50
- data/lib/canopus/cli.rb +5 -2
- data/lib/canopus/command.rb +13 -4
- data/lib/canopus/controller.rb +183 -40
- data/lib/canopus/debug/breakpoints.rb +550 -0
- data/lib/canopus/debug/configuration.rb +291 -0
- data/lib/canopus/debug/console.rb +243 -0
- data/lib/canopus/debug/panel.rb +487 -0
- data/lib/canopus/debug/session.rb +354 -0
- data/lib/canopus/decoration.rb +4 -2
- data/lib/canopus/denebola_compat.rb +27 -0
- data/lib/canopus/diagnostics.rb +173 -0
- data/lib/canopus/display_map/overlay_map.rb +6 -2
- data/lib/canopus/editor/snippet_expandable.rb +3 -2
- data/lib/canopus/editor.rb +122 -31
- data/lib/canopus/git/state.rb +100 -0
- data/lib/canopus/language/background_analysis.rb +93 -14
- data/lib/canopus/language/document.rb +70 -34
- data/lib/canopus/language/symbol.rb +1 -0
- data/lib/canopus/language/syntax_worker.rb +61 -9
- data/lib/canopus/language.rb +3 -0
- data/lib/canopus/minimap.rb +201 -0
- data/lib/canopus/patch.rb +3 -1
- data/lib/canopus/plugins/api.rb +4 -2
- data/lib/canopus/plugins/isolated_runtime.rb +64 -7
- data/lib/canopus/plugins/registry.rb +114 -3
- data/lib/canopus/plugins.rb +8 -7
- data/lib/canopus/project.rb +15 -6
- data/lib/canopus/provider.rb +192 -0
- data/lib/canopus/regexp_compat.rb +36 -4
- data/lib/canopus/selection.rb +12 -0
- data/lib/canopus/settings/schema.rb +201 -0
- data/lib/canopus/settings.rb +328 -49
- data/lib/canopus/task/configuration.rb +356 -0
- data/lib/canopus/task/problem_matcher.rb +264 -0
- data/lib/canopus/task/runner.rb +314 -0
- data/lib/canopus/test_runner/bounded_ignore.rb +144 -0
- data/lib/canopus/test_runner/discovery.rb +97 -0
- data/lib/canopus/test_runner/execution.rb +194 -0
- data/lib/canopus/test_runner/minitest.rb +116 -0
- data/lib/canopus/test_runner/rspec.rb +103 -0
- data/lib/canopus/test_runner/safe_walker.rb +37 -0
- data/lib/canopus/test_runner.rb +54 -0
- data/lib/canopus/theme/import.rb +131 -0
- data/lib/canopus/theme.rb +28 -4
- data/lib/canopus/version.rb +1 -1
- data/lib/canopus/vim/motionable.rb +1 -1
- data/lib/canopus/vim.rb +10 -16
- data/lib/canopus/workspace/auto_savable.rb +83 -0
- data/lib/canopus/workspace/debug_aware.rb +600 -0
- data/lib/canopus/workspace/edit/plan.rb +2 -2
- data/lib/canopus/workspace/editor_configurable.rb +113 -0
- data/lib/canopus/workspace/file_change_aware.rb +11 -3
- data/lib/canopus/workspace/git_aware.rb +80 -34
- data/lib/canopus/workspace/git_blame.rb +175 -0
- data/lib/canopus/workspace/git_conflict_resolution.rb +410 -0
- data/lib/canopus/workspace/git_diff_view.rb +641 -0
- data/lib/canopus/workspace/git_history.rb +171 -0
- data/lib/canopus/workspace/git_remote.rb +267 -0
- data/lib/canopus/workspace/git_staging.rb +583 -0
- data/lib/canopus/workspace/hierarchy_aware.rb +477 -0
- data/lib/canopus/workspace/keymap_aware.rb +66 -0
- data/lib/canopus/workspace/language_aware.rb +3721 -135
- data/lib/canopus/workspace/language_server_configurable.rb +287 -71
- data/lib/canopus/workspace/persistent_undo.rb +169 -0
- data/lib/canopus/workspace/problems_aware.rb +131 -0
- data/lib/canopus/workspace/project_searchable.rb +88 -11
- data/lib/canopus/workspace/project_tree_editable.rb +3 -1
- data/lib/canopus/workspace/recoverable.rb +204 -0
- data/lib/canopus/workspace/session_persistable.rb +85 -13
- data/lib/canopus/workspace/settings_aware.rb +76 -2
- data/lib/canopus/workspace/task_aware.rb +194 -0
- data/lib/canopus/workspace/test_aware.rb +394 -0
- data/lib/canopus/workspace/trust.rb +73 -0
- data/lib/canopus/workspace/view/terminal_presentable.rb +298 -47
- data/lib/canopus/workspace/view.rb +399 -74
- data/lib/canopus/workspace.rb +688 -44
- data/lib/canopus.rb +20 -2
- data/sig/canopus.rbs +138 -31
- data/sig/controller.rbs +10 -3
- data/sig/debug.rbs +121 -0
- data/sig/decoration.rbs +12 -2
- data/sig/diagnostics.rbs +26 -0
- data/sig/minimap.rbs +20 -0
- data/sig/provider.rbs +29 -0
- data/sig/services.rbs +1 -16
- data/sig/task.rbs +92 -0
- data/sig/test_runner.rbs +109 -0
- data/sig/trust.rbs +17 -0
- data/sig/workspace_edits.rbs +2 -2
- data/sig/workspace_services.rbs +234 -2
- data/tools/check_dependencies.rb +21 -2
- data/tools/package.rb +172 -14
- data/tools/package_signature_test.rb +33 -0
- data/tools/package_test.rb +29 -0
- data/tools/sign_package.rb +123 -0
- data/tools/update.rb +189 -0
- data/tools/update_test.rb +71 -0
- metadata +206 -31
- data/lib/canopus/lsp/client.rb +0 -307
- data/lib/canopus/lsp/error.rb +0 -7
- data/lib/canopus/lsp/future/subscription.rb +0 -9
- data/lib/canopus/lsp/future.rb +0 -87
- data/lib/canopus/lsp/protocol.rb +0 -83
- data/lib/canopus/lsp/server_error.rb +0 -13
- data/lib/canopus/lsp/timeout.rb +0 -7
- data/lib/canopus/lsp/transport.rb +0 -123
- data/lib/canopus/lsp.rb +0 -19
- data/lib/canopus/project/search.rb +0 -164
- data/lib/canopus/project/search_worker/cancelled.rb +0 -3
- data/lib/canopus/project/search_worker/runner.rb +0 -9
- data/lib/canopus/project/search_worker.rb +0 -108
- data/lib/canopus/terminal/cell.rb +0 -7
- data/lib/canopus/terminal/grid.rb +0 -321
- data/lib/canopus/terminal/pty.rb +0 -178
- data/lib/canopus/terminal/scrollback.rb +0 -38
- data/lib/canopus/terminal/vt.rb +0 -398
- data/lib/canopus/terminal.rb +0 -13
- data/sig/lsp.rbs +0 -99
- data/sig/terminal.rbs +0 -130
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "set"
|
|
4
|
+
require "zaniah/ui"
|
|
5
|
+
|
|
6
|
+
module Canopus
|
|
7
|
+
module Debug
|
|
8
|
+
class Panel
|
|
9
|
+
ITEM_LIMIT = 1_000
|
|
10
|
+
WATCH_LIMIT = 100
|
|
11
|
+
TEXT_LIMIT = 4_096
|
|
12
|
+
DEPTH_LIMIT = 32
|
|
13
|
+
ROOT_STACK = [:debug, :stack].freeze
|
|
14
|
+
ROOT_VARIABLES = [:debug, :variables].freeze
|
|
15
|
+
ROOT_WATCHES = [:debug, :watches].freeze
|
|
16
|
+
ROOT_BREAKPOINTS = [:debug, :breakpoints].freeze
|
|
17
|
+
ROOTS = [ROOT_STACK, ROOT_VARIABLES, ROOT_WATCHES, ROOT_BREAKPOINTS].freeze
|
|
18
|
+
|
|
19
|
+
attr_reader :tree, :watches, :expanded_paths, :selected_frame
|
|
20
|
+
|
|
21
|
+
def initialize(breakpoints:, post:, select_frame:, select_breakpoint:, report:, request_frame:)
|
|
22
|
+
callbacks = [post, select_frame, select_breakpoint, report, request_frame]
|
|
23
|
+
raise ArgumentError, "debug panel callbacks must be callable" unless callbacks.all? { |item| item.respond_to?(:call) }
|
|
24
|
+
|
|
25
|
+
@breakpoints = breakpoints
|
|
26
|
+
@post, @select_frame, @select_breakpoint = post, select_frame, select_breakpoint
|
|
27
|
+
@report, @request_frame = report, request_frame
|
|
28
|
+
@generation = 0
|
|
29
|
+
@pending = Set.new
|
|
30
|
+
@watches = [].freeze
|
|
31
|
+
@watch_tokens = {}
|
|
32
|
+
@expanded_paths = Set.new
|
|
33
|
+
@expanded_roots = ROOTS.to_set
|
|
34
|
+
@stack_frames, @watch_results = [], {}
|
|
35
|
+
@tree = Zaniah::UI::TreeView.new(nodes, height: 600)
|
|
36
|
+
.on_select { |value, _event, _context| select(value) }
|
|
37
|
+
.on_toggle { |id, open| remember_expansion(id, open) }
|
|
38
|
+
restore_expansions
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def stopped(session, frame)
|
|
42
|
+
reset_requests
|
|
43
|
+
@session, @selected_frame = session, frame
|
|
44
|
+
@stack_frames = [frame].freeze
|
|
45
|
+
@watch_results = @watches.to_h { |expression| [expression, :loading] }
|
|
46
|
+
@tree.replace(nodes)
|
|
47
|
+
restore_expansions
|
|
48
|
+
request_stack
|
|
49
|
+
evaluate_watches
|
|
50
|
+
@request_frame.call
|
|
51
|
+
self
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def continued(session)
|
|
55
|
+
return false unless @session.equal?(session)
|
|
56
|
+
|
|
57
|
+
reset_requests
|
|
58
|
+
@session = @selected_frame = nil
|
|
59
|
+
@stack_frames = [].freeze
|
|
60
|
+
@watch_results = {}
|
|
61
|
+
@tree.replace(nodes)
|
|
62
|
+
restore_expansions
|
|
63
|
+
@request_frame.call
|
|
64
|
+
true
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def clear
|
|
68
|
+
reset_requests
|
|
69
|
+
@session = @selected_frame = nil
|
|
70
|
+
@stack_frames = [].freeze
|
|
71
|
+
@watch_results = {}
|
|
72
|
+
@tree.replace(nodes)
|
|
73
|
+
restore_expansions
|
|
74
|
+
@request_frame.call
|
|
75
|
+
nil
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def refresh_breakpoints
|
|
79
|
+
@tree.replace_children(ROOT_BREAKPOINTS, breakpoint_nodes)
|
|
80
|
+
@request_frame.call
|
|
81
|
+
nil
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def add_watch(expression)
|
|
85
|
+
expression = watch_expression(expression)
|
|
86
|
+
return false if @watches.include?(expression)
|
|
87
|
+
raise Error, "too many watch expressions" if @watches.length >= WATCH_LIMIT
|
|
88
|
+
|
|
89
|
+
@watches = [*@watches, expression].freeze
|
|
90
|
+
token = @watch_tokens[expression] = Object.new.freeze
|
|
91
|
+
@watch_results[expression] = @session && @selected_frame ? :loading : nil
|
|
92
|
+
@tree.replace_children(ROOT_WATCHES, watch_nodes)
|
|
93
|
+
evaluate_watch(expression, token) if @session && @selected_frame
|
|
94
|
+
@request_frame.call
|
|
95
|
+
true
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def remove_watch(expression)
|
|
99
|
+
replacement = @watches.reject { |item| item == expression }
|
|
100
|
+
return false if replacement.length == @watches.length
|
|
101
|
+
|
|
102
|
+
@watches = replacement.freeze
|
|
103
|
+
@watch_tokens.delete(expression)
|
|
104
|
+
@watch_results.delete(expression)
|
|
105
|
+
@expanded_paths.delete_if { |path| path.first == :watch && path.dig(1, 0) == expression }
|
|
106
|
+
@tree.replace_children(ROOT_WATCHES, watch_nodes)
|
|
107
|
+
@request_frame.call
|
|
108
|
+
true
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
private
|
|
112
|
+
|
|
113
|
+
def nodes
|
|
114
|
+
[root_node(ROOT_STACK, "Call Stack", ->(_value) { stack_nodes }),
|
|
115
|
+
root_node(ROOT_VARIABLES, "Variables", ->(_value) { load_scopes }),
|
|
116
|
+
root_node(ROOT_WATCHES, "Watch", ->(_value) { watch_nodes }),
|
|
117
|
+
root_node(ROOT_BREAKPOINTS, "Breakpoints", ->(_value) { breakpoint_nodes })].freeze
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def root_node(id, label, loader) = {id: id, label: label, value: nil, children: loader}.freeze
|
|
121
|
+
|
|
122
|
+
def stack_nodes
|
|
123
|
+
return [status_node(:stack, "Not stopped")] if @stack_frames.empty?
|
|
124
|
+
|
|
125
|
+
@stack_frames.each_with_index.map do |frame, index|
|
|
126
|
+
path = frame.source && (frame.source["path"] || frame.source[:path])
|
|
127
|
+
label = path ? text(frame.name, " · ", display_basename(path), ":", frame.line) : text(frame.name)
|
|
128
|
+
{id: [:debug_frame, index].freeze, label: label,
|
|
129
|
+
value: {kind: :frame, generation: @generation, frame: frame}.freeze}.freeze
|
|
130
|
+
end.freeze
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def breakpoint_nodes
|
|
134
|
+
entries = @breakpoints.entries.first(ITEM_LIMIT)
|
|
135
|
+
return [status_node(:breakpoints, "No breakpoints")] if entries.empty?
|
|
136
|
+
|
|
137
|
+
entries.map do |entry|
|
|
138
|
+
state = entry.enabled ? "" : " (disabled)"
|
|
139
|
+
{id: [:debug_breakpoint, entry.path, entry.line].freeze,
|
|
140
|
+
label: text(entry.path, ":", entry.line, state),
|
|
141
|
+
value: {kind: :breakpoint, entry: entry}.freeze}.freeze
|
|
142
|
+
end.freeze
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def watch_nodes
|
|
146
|
+
return [status_node(:watches, "No watch expressions")] if @watches.empty?
|
|
147
|
+
|
|
148
|
+
@watches.map do |expression|
|
|
149
|
+
result = @watch_results[expression]
|
|
150
|
+
label = case result
|
|
151
|
+
when :loading then text(expression, " = …")
|
|
152
|
+
when Hash then text(expression, " = ", result.fetch(:label))
|
|
153
|
+
else text(expression, " = Not available")
|
|
154
|
+
end
|
|
155
|
+
path = [expression_component(expression)].freeze
|
|
156
|
+
node = {id: watch_variable_id(path), label: label,
|
|
157
|
+
value: {kind: :watch, expression: expression}.freeze}
|
|
158
|
+
if result.is_a?(Hash) && result[:expandable]
|
|
159
|
+
id = watch_variable_id(path)
|
|
160
|
+
node[:children] = watch_variables_loader(expression, path, id, @watch_tokens.fetch(expression))
|
|
161
|
+
end
|
|
162
|
+
node.freeze
|
|
163
|
+
end.freeze
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def request_stack
|
|
167
|
+
generation = @generation
|
|
168
|
+
await(@session.stack_frames(levels: 100), generation) do |frames, error|
|
|
169
|
+
if error
|
|
170
|
+
@tree.replace_children(ROOT_STACK, [status_node(:stack_error, "Unavailable")])
|
|
171
|
+
@report.call(text("Cannot load call stack: ", error.message))
|
|
172
|
+
else
|
|
173
|
+
frames = frames.first(ITEM_LIMIT)
|
|
174
|
+
@stack_frames = (frames.empty? ? [@selected_frame] : frames).freeze
|
|
175
|
+
@tree.replace_children(ROOT_STACK, stack_nodes)
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
rescue StandardError => error
|
|
179
|
+
@report.call(text("Cannot load call stack: ", error.message))
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def load_scopes
|
|
183
|
+
return [status_node(:variables, "Not stopped")] unless active?
|
|
184
|
+
|
|
185
|
+
generation, session, frame = @generation, @session, @selected_frame
|
|
186
|
+
await(session.scopes(frame.id), generation) do |scopes, error|
|
|
187
|
+
if error
|
|
188
|
+
replace_unavailable(ROOT_VARIABLES, error)
|
|
189
|
+
else
|
|
190
|
+
@tree.replace_children(ROOT_VARIABLES, scope_nodes(scopes))
|
|
191
|
+
restore_expansions
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
[status_node(:variables_loading, "Loading…")]
|
|
195
|
+
rescue StandardError => error
|
|
196
|
+
unavailable(error)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def scope_nodes(scopes)
|
|
200
|
+
occurrences = Hash.new(0)
|
|
201
|
+
values = scopes.first(ITEM_LIMIT).each_with_index.filter_map do |scope, index|
|
|
202
|
+
component = path_component(scope.name, occurrences)
|
|
203
|
+
next oversized_node(:scope, index, scope.name) unless component
|
|
204
|
+
|
|
205
|
+
path = [component].freeze
|
|
206
|
+
node = {id: variable_id(path), label: text(scope.name), value: {kind: :scope}.freeze}
|
|
207
|
+
node[:children] = variables_loader(path, variable_id(path)) unless scope.variables_reference.zero?
|
|
208
|
+
node.freeze
|
|
209
|
+
end
|
|
210
|
+
values.empty? ? [status_node(:variables_empty, "No variables")] : values.freeze
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def load_variables(path, node_id)
|
|
214
|
+
return [status_node([:stale, node_id], "Not stopped")] unless active?
|
|
215
|
+
return [status_node([:depth, node_id], "Maximum depth reached")] if path.length >= DEPTH_LIMIT
|
|
216
|
+
|
|
217
|
+
generation = @generation
|
|
218
|
+
resolve_reference(path, generation) do |reference, error|
|
|
219
|
+
if error
|
|
220
|
+
replace_unavailable(node_id, error)
|
|
221
|
+
else
|
|
222
|
+
await(@session.variables(reference), generation) do |variables, request_error|
|
|
223
|
+
if request_error
|
|
224
|
+
replace_unavailable(node_id, request_error)
|
|
225
|
+
else
|
|
226
|
+
@tree.replace_children(node_id, variable_nodes(variables, path, :variables))
|
|
227
|
+
restore_expansions
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
[status_node([:loading, node_id], "Loading…")]
|
|
233
|
+
rescue StandardError => error
|
|
234
|
+
unavailable(error)
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def load_watch_variables(expression, path, node_id, token)
|
|
238
|
+
return [status_node([:stale, node_id], "Not stopped")] unless active? && watch_current?(expression, token)
|
|
239
|
+
return [status_node([:depth, node_id], "Maximum depth reached")] if path.length >= DEPTH_LIMIT
|
|
240
|
+
|
|
241
|
+
generation = @generation
|
|
242
|
+
await(@session.evaluate(expression, frame_id: @selected_frame.id), generation) do |variable, error|
|
|
243
|
+
next unless watch_current?(expression, token)
|
|
244
|
+
|
|
245
|
+
if error
|
|
246
|
+
replace_unavailable(node_id, error)
|
|
247
|
+
else
|
|
248
|
+
traverse_reference(variable.variables_reference, path.drop(1), generation) do |reference, resolve_error|
|
|
249
|
+
next unless watch_current?(expression, token)
|
|
250
|
+
|
|
251
|
+
if resolve_error
|
|
252
|
+
replace_unavailable(node_id, resolve_error)
|
|
253
|
+
else
|
|
254
|
+
await(@session.variables(reference), generation) do |variables, request_error|
|
|
255
|
+
next unless watch_current?(expression, token)
|
|
256
|
+
|
|
257
|
+
if request_error
|
|
258
|
+
replace_unavailable(node_id, request_error)
|
|
259
|
+
else
|
|
260
|
+
@tree.replace_children(node_id, variable_nodes(variables, path, :watch))
|
|
261
|
+
restore_expansions
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
[status_node([:loading, node_id], "Loading…")]
|
|
269
|
+
rescue StandardError => error
|
|
270
|
+
unavailable(error)
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def variable_nodes(variables, parent, kind)
|
|
274
|
+
occurrences = Hash.new(0)
|
|
275
|
+
values = variables.first(ITEM_LIMIT).each_with_index.filter_map do |variable, index|
|
|
276
|
+
component = path_component(variable.name, occurrences)
|
|
277
|
+
next oversized_node(:variable, [parent, index], variable.name) unless component
|
|
278
|
+
|
|
279
|
+
path = [*parent, component].freeze
|
|
280
|
+
id = kind == :watch ? watch_variable_id(path) : variable_id(path)
|
|
281
|
+
label = text(variable.name, " = ", variable.value, variable.type && " · ", variable.type)
|
|
282
|
+
node = {id: id, label: label, value: {kind: :variable}.freeze}
|
|
283
|
+
unless variable.variables_reference.zero?
|
|
284
|
+
node[:children] = if kind == :watch
|
|
285
|
+
expression = parent.first.first
|
|
286
|
+
watch_variables_loader(expression, path, id, @watch_tokens.fetch(expression))
|
|
287
|
+
else
|
|
288
|
+
variables_loader(path, id)
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
node.freeze
|
|
292
|
+
end
|
|
293
|
+
values.empty? ? [status_node([:empty, parent], "No variables")] : values.freeze
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def resolve_reference(path, generation, &done)
|
|
297
|
+
session, frame = @session, @selected_frame
|
|
298
|
+
await(session.scopes(frame.id), generation) do |scopes, error|
|
|
299
|
+
if error
|
|
300
|
+
done.call(nil, error)
|
|
301
|
+
else
|
|
302
|
+
scope = occurrence(scopes, path.first)
|
|
303
|
+
if !scope || scope.variables_reference.zero?
|
|
304
|
+
done.call(nil, Error.new("variable path is no longer available"))
|
|
305
|
+
else
|
|
306
|
+
traverse_reference(scope.variables_reference, path.drop(1), generation, &done)
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def traverse_reference(reference, path, generation, &done)
|
|
313
|
+
return done.call(reference, nil) if path.empty?
|
|
314
|
+
return done.call(nil, Error.new("variable path is no longer available")) if reference.zero?
|
|
315
|
+
|
|
316
|
+
await(@session.variables(reference), generation) do |variables, error|
|
|
317
|
+
if error
|
|
318
|
+
done.call(nil, error)
|
|
319
|
+
else
|
|
320
|
+
variable = occurrence(variables, path.first)
|
|
321
|
+
variable ? traverse_reference(variable.variables_reference, path.drop(1), generation, &done) :
|
|
322
|
+
done.call(nil, Error.new("variable path is no longer available"))
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
def evaluate_watches
|
|
328
|
+
@watches.each { |expression| evaluate_watch(expression, @watch_tokens.fetch(expression)) }
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
def evaluate_watch(expression, token)
|
|
332
|
+
generation, session, frame = @generation, @session, @selected_frame
|
|
333
|
+
await(session.evaluate(expression, frame_id: frame.id), generation) do |variable, error|
|
|
334
|
+
next unless watch_current?(expression, token)
|
|
335
|
+
|
|
336
|
+
@watch_results[expression] = if error
|
|
337
|
+
{label: text("Unavailable: ", error.message), expandable: false}.freeze
|
|
338
|
+
else
|
|
339
|
+
{label: text(variable.value, variable.type && " · ", variable.type),
|
|
340
|
+
expandable: !variable.variables_reference.zero?}.freeze
|
|
341
|
+
end
|
|
342
|
+
@tree.replace_children(ROOT_WATCHES, watch_nodes)
|
|
343
|
+
restore_expansions
|
|
344
|
+
@request_frame.call
|
|
345
|
+
end
|
|
346
|
+
rescue StandardError => error
|
|
347
|
+
return unless watch_current?(expression, token)
|
|
348
|
+
|
|
349
|
+
@watch_results[expression] = {label: text("Unavailable: ", error.message), expandable: false}.freeze
|
|
350
|
+
@tree.replace_children(ROOT_WATCHES, watch_nodes)
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
def await(future, generation, &accept)
|
|
354
|
+
@pending.add(future)
|
|
355
|
+
future.on_complete do |value, error|
|
|
356
|
+
@post.call do
|
|
357
|
+
@pending.delete(future)
|
|
358
|
+
accept.call(value, error) if generation == @generation && active?
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
future
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
def reset_requests
|
|
365
|
+
@generation += 1
|
|
366
|
+
pending, @pending = @pending, Set.new
|
|
367
|
+
pending.each(&:cancel)
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
def active? = !!(@session && @selected_frame)
|
|
371
|
+
|
|
372
|
+
def select(value)
|
|
373
|
+
return false unless value.is_a?(Hash)
|
|
374
|
+
|
|
375
|
+
case value[:kind]
|
|
376
|
+
when :frame
|
|
377
|
+
return false unless value[:generation] == @generation && @session
|
|
378
|
+
|
|
379
|
+
frame = value.fetch(:frame)
|
|
380
|
+
return true if @selected_frame == frame
|
|
381
|
+
|
|
382
|
+
reset_requests
|
|
383
|
+
@selected_frame = frame
|
|
384
|
+
@tree.replace_children(ROOT_STACK, stack_nodes)
|
|
385
|
+
@select_frame.call(@session, @selected_frame)
|
|
386
|
+
@watch_results = @watches.to_h { |expression| [expression, :loading] }
|
|
387
|
+
@tree.invalidate(ROOT_VARIABLES)
|
|
388
|
+
restore_expansions
|
|
389
|
+
@tree.replace_children(ROOT_WATCHES, watch_nodes)
|
|
390
|
+
evaluate_watches
|
|
391
|
+
true
|
|
392
|
+
when :breakpoint
|
|
393
|
+
@select_breakpoint.call(value.fetch(:entry))
|
|
394
|
+
else false
|
|
395
|
+
end
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
def remember_expansion(id, open)
|
|
399
|
+
if ROOTS.include?(id)
|
|
400
|
+
open ? @expanded_roots.add(id) : @expanded_roots.delete(id)
|
|
401
|
+
elsif id.is_a?(Array) && id.first == :debug_variable
|
|
402
|
+
remember_path([:variables, *id.drop(1)].freeze, open)
|
|
403
|
+
elsif id.is_a?(Array) && id.first == :debug_watch_variable
|
|
404
|
+
remember_path([:watch, *id.drop(1)].freeze, open)
|
|
405
|
+
end
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
def remember_path(path, open)
|
|
409
|
+
open ? @expanded_paths.add(path) : @expanded_paths.delete(path)
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
def restore_expansions
|
|
413
|
+
@expanded_roots.each { |id| @tree.expand(id) }
|
|
414
|
+
@expanded_paths.sort_by(&:length).each do |path|
|
|
415
|
+
id = path.first == :variables ? variable_id(path.drop(1)) : watch_variable_id(path.drop(1))
|
|
416
|
+
@tree.expand(id)
|
|
417
|
+
end
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
def replace_unavailable(node_id, error)
|
|
421
|
+
@tree.replace_children(node_id, [status_node([:error, node_id], "Unavailable")])
|
|
422
|
+
@report.call(text("Cannot load variables: ", error.message))
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
def unavailable(error)
|
|
426
|
+
@report.call(text("Cannot load variables: ", error.message))
|
|
427
|
+
[status_node([:error, error.object_id], "Unavailable")]
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
def path_component(name, occurrences)
|
|
431
|
+
return unless name.bytesize <= TEXT_LIMIT
|
|
432
|
+
|
|
433
|
+
index = occurrences[name]
|
|
434
|
+
occurrences[name] += 1
|
|
435
|
+
[name.dup.freeze, index].freeze
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
def expression_component(expression) = [expression, 0].freeze
|
|
439
|
+
|
|
440
|
+
def occurrence(values, component)
|
|
441
|
+
name, index = component
|
|
442
|
+
values.select { |value| value.name == name }[index]
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
def variable_id(path) = [:debug_variable, *path].freeze
|
|
446
|
+
def watch_variable_id(path) = [:debug_watch_variable, *path].freeze
|
|
447
|
+
|
|
448
|
+
def variables_loader(path, node_id) = ->(_value) { load_variables(path, node_id) }
|
|
449
|
+
|
|
450
|
+
def watch_variables_loader(expression, path, node_id, token)
|
|
451
|
+
->(_value) { load_watch_variables(expression, path, node_id, token) }
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
def watch_current?(expression, token) = @watch_tokens[expression].equal?(token)
|
|
455
|
+
|
|
456
|
+
def oversized_node(kind, index, name)
|
|
457
|
+
{id: [:debug_oversized, kind, index].freeze, label: text(name), value: nil}.freeze
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
def status_node(id, label)
|
|
461
|
+
{id: [:debug_status, id].freeze, label: label, value: nil}.freeze
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
def watch_expression(value)
|
|
465
|
+
valid = value.is_a?(String) && value.encoding == Encoding::UTF_8 && value.valid_encoding? &&
|
|
466
|
+
value.bytesize.between?(1, TEXT_LIMIT) && !value.include?("\0") && !value.strip.empty?
|
|
467
|
+
raise ArgumentError, "invalid watch expression" unless valid
|
|
468
|
+
|
|
469
|
+
value.dup.freeze
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
def display_basename(value)
|
|
473
|
+
path = bounded_text(value).delete("\0")
|
|
474
|
+
path.empty? ? "" : File.basename(path)
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
def bounded_text(value)
|
|
478
|
+
value.to_s.byteslice(0, TEXT_LIMIT).to_s.dup.force_encoding(Encoding::UTF_8).scrub("")
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
def text(*values)
|
|
482
|
+
value = values.compact.map { |item| bounded_text(item) }.join.gsub(/\s+/, " ").strip
|
|
483
|
+
value.length > 200 ? value.each_char.first(199).join + "…" : value
|
|
484
|
+
end
|
|
485
|
+
end
|
|
486
|
+
end
|
|
487
|
+
end
|