Class: Canopus::Debug::Breakpoints
- Inherits:
-
Object
- Object
- Canopus::Debug::Breakpoints
- Defined in:
- lib/canopus/debug/breakpoints.rb
Defined Under Namespace
Classes: Entry
Constant Summary collapse
- VERSION =
2- RELATIVE_PATH =
File.join(".canopus", "breakpoints.json")
- MAX_BYTES =
1_048_576- MAX_ENTRIES =
10_000- MAX_PATH_BYTES =
4_096- MAX_CONDITION_BYTES =
4_096- MAX_HIT_CONDITION_BYTES =
4_096- MAX_LOG_MESSAGE_BYTES =
4_096- MAX_LINE =
2_147_483_647
Instance Attribute Summary collapse
-
#entries ⇒ Object
readonly
Returns the value of attribute entries.
-
#root ⇒ Object
readonly
Returns the value of attribute root.
Instance Method Summary collapse
- #add(path, line, condition: nil, hit_condition: nil, log_message: nil, enabled: true) ⇒ Object
- #attach(buffer) ⇒ Object
- #close ⇒ Object
- #detach(buffer) ⇒ Object
- #error ⇒ Object
- #flush ⇒ Object
- #for_path(path) ⇒ Object
-
#initialize(root:, &on_change) ⇒ Breakpoints
constructor
A new instance of Breakpoints.
- #remove(path, line) ⇒ Object
- #toggle(path, line, condition: nil, hit_condition: nil, log_message: nil, enabled: true) ⇒ Object
- #update(path, line, new_line: line, condition: UNSET, hit_condition: UNSET, log_message: UNSET, enabled: UNSET) ⇒ Object
Constructor Details
#initialize(root:, &on_change) ⇒ Breakpoints
Returns a new instance of Breakpoints.
29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
# File 'lib/canopus/debug/breakpoints.rb', line 29 def initialize(root:, &on_change) @root = canonical_root(root) @on_change = on_change @attachments = {} @closed = false @entries = load_entries @state_lock = Mutex.new @save_lock, @save_wake = Mutex.new, ConditionVariable.new @save_generation = @saved_generation = 0 @pending_save = @save_error = nil @saving = @save_stopping = false @save_worker = Thread.new { save_loop } @save_worker.name = "canopus-breakpoints" end |
Instance Attribute Details
#entries ⇒ Object (readonly)
Returns the value of attribute entries.
27 28 29 |
# File 'lib/canopus/debug/breakpoints.rb', line 27 def entries @entries end |
#root ⇒ Object (readonly)
Returns the value of attribute root.
27 28 29 |
# File 'lib/canopus/debug/breakpoints.rb', line 27 def root @root end |
Instance Method Details
#add(path, line, condition: nil, hit_condition: nil, log_message: nil, enabled: true) ⇒ Object
49 50 51 52 53 54 55 56 57 58 59 |
# File 'lib/canopus/debug/breakpoints.rb', line 49 def add(path, line, condition: nil, hit_condition: nil, log_message: nil, enabled: true) @state_lock.synchronize do ensure_open! entry = build_entry(path, line, condition, hit_condition, , enabled) existing = entries.find { |item| item.path == entry.path && item.line == entry.line } return existing if existing == entry replacement = entries.reject { |item| item.path == entry.path && item.line == entry.line } commit([*replacement, entry], changed_path: entry.path) entry end end |
#attach(buffer) ⇒ Object
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
# File 'lib/canopus/debug/breakpoints.rb', line 97 def attach(buffer) @state_lock.synchronize do ensure_open! raise Error, "breakpoints require a file-backed buffer" unless buffer.is_a?(Buffer) && buffer.path return self if @attachments.key?(buffer) path = normalize_path(buffer.path) (buffer, path) = {snapshots: ObjectSpace::WeakMap.new, states: {}.compare_by_identity, path: path, revision: 0, gc_count: GC.count} store_snapshot(, buffer.rope, entries_for(path)) subscription = buffer.on_edit { |patch| edited(buffer, patch) } [:subscription] = subscription @attachments[buffer] = end self end |
#close ⇒ Object
121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'lib/canopus/debug/breakpoints.rb', line 121 def close = @state_lock.synchronize do return nil if @closed @closed = true values = @attachments.values @attachments.clear values end .each { || .fetch(:subscription).detach } flush nil ensure stop_save_worker if end |
#detach(buffer) ⇒ Object
114 115 116 117 118 119 |
# File 'lib/canopus/debug/breakpoints.rb', line 114 def detach(buffer) = @state_lock.synchronize { @attachments.delete(buffer) } &.fetch(:subscription)&.detach flush if !.nil? end |
#error ⇒ Object
149 150 151 |
# File 'lib/canopus/debug/breakpoints.rb', line 149 def error @save_lock.synchronize { @save_error&.last } end |
#flush ⇒ Object
136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/canopus/debug/breakpoints.rb', line 136 def flush target = @save_lock.synchronize { @save_generation } @save_lock.synchronize do until @saved_generation >= target if !@saving && !@pending_save && @save_error && @save_error.first >= target raise @save_error.last end @save_wake.wait(@save_lock) end end self end |
#for_path(path) ⇒ Object
44 45 46 47 |
# File 'lib/canopus/debug/breakpoints.rb', line 44 def for_path(path) relative = normalize_path(path) entries.select { |entry| entry.path == relative }.freeze end |
#remove(path, line) ⇒ Object
61 62 63 64 65 66 67 68 69 70 |
# File 'lib/canopus/debug/breakpoints.rb', line 61 def remove(path, line) @state_lock.synchronize do ensure_open! relative, line = normalize_path(path), validate_line(line) replacement = entries.reject { |entry| entry.path == relative && entry.line == line } return false if replacement.length == entries.length commit(replacement, changed_path: relative) true end end |
#toggle(path, line, condition: nil, hit_condition: nil, log_message: nil, enabled: true) ⇒ Object
72 73 74 75 76 |
# File 'lib/canopus/debug/breakpoints.rb', line 72 def toggle(path, line, condition: nil, hit_condition: nil, log_message: nil, enabled: true) existing = for_path(path).find { |entry| entry.line == line } return nil if existing && remove(path, line) add(path, line, condition: condition, hit_condition: hit_condition, log_message: , enabled: enabled) end |
#update(path, line, new_line: line, condition: UNSET, hit_condition: UNSET, log_message: UNSET, enabled: UNSET) ⇒ Object
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/canopus/debug/breakpoints.rb', line 78 def update(path, line, new_line: line, condition: UNSET, hit_condition: UNSET, log_message: UNSET, enabled: UNSET) @state_lock.synchronize do ensure_open! relative, line = normalize_path(path), validate_line(line) current = entries.find { |entry| entry.path == relative && entry.line == line } raise Error, "breakpoint does not exist" unless current condition = current.condition if condition.equal?(UNSET) hit_condition = current.hit_condition if hit_condition.equal?(UNSET) = current. if .equal?(UNSET) enabled = current.enabled if enabled.equal?(UNSET) replacement = build_entry(relative, new_line, condition, hit_condition, , enabled) collision = entries.any? { |entry| !entry.equal?(current) && entry.path == relative && entry.line == replacement.line } raise Error, "breakpoint already exists" if collision return current if current == replacement commit(entries.map { |entry| entry.equal?(current) ? replacement : entry }, changed_path: relative) replacement end end |