From f84176536fa74884e64439a8a234aa69d2c14156 Mon Sep 17 00:00:00 2001 From: Donald Hall Date: Thu, 4 May 2017 10:35:55 -0600 Subject: [PATCH] re #189 Update script runner so debug breakpoints move intelligently when code is inserted/deleted in front of the breakpoint --- lib/cosmos/gui/text/ruby_editor.rb | 58 +++++++++++++++-- .../tools/script_runner/script_runner.rb | 2 + .../script_runner/script_runner_frame.rb | 64 +++++++++++++------ 3 files changed, 99 insertions(+), 25 deletions(-) diff --git a/lib/cosmos/gui/text/ruby_editor.rb b/lib/cosmos/gui/text/ruby_editor.rb index 27686ce80..9904bdcc2 100644 --- a/lib/cosmos/gui/text/ruby_editor.rb +++ b/lib/cosmos/gui/text/ruby_editor.rb @@ -17,7 +17,7 @@ module Cosmos class RubyEditor < CompletionTextEdit # private slot used to connect to the blockCountChanged signal - slots 'line_count_changed()' + slots 'line_count_changed(int)' # private slot used to connect to the updateRequest signal slots 'update_line_number_area(const QRect &, int)' @@ -26,6 +26,7 @@ class RubyEditor < CompletionTextEdit signals 'breakpoints_cleared()' attr_accessor :enable_breakpoints + attr_accessor :filename # This works but slows down the GUI significantly when # pasting a large (10k line) block of code into the editor @@ -153,6 +154,8 @@ def paintEvent(event) end CHAR_57 = Qt::Char.new(57) + BREAKPOINT_SET = 1 + BREAKPOINT_CLEAR = -1 def initialize(parent) super(parent) @@ -171,10 +174,10 @@ def initialize(parent) @syntax = RubySyntax.new(document()) @lineNumberArea = LineNumberArea.new(self) - connect(self, SIGNAL('blockCountChanged(int)'), self, SLOT('line_count_changed()')) + connect(self, SIGNAL('blockCountChanged(int)'), self, SLOT('line_count_changed(int)')) connect(self, SIGNAL('updateRequest(const QRect &, int)'), self, SLOT('update_line_number_area(const QRect &, int)')) - line_count_changed() + line_count_changed(-1) end def dispose @@ -196,19 +199,59 @@ def context_menu(point) def add_breakpoint(line) @breakpoints << line + block = document.findBlockByNumber(line-1) + block.setUserState(BREAKPOINT_SET) + block.dispose + block = nil @lineNumberArea.repaint end def clear_breakpoint(line) @breakpoints.delete(line) + block = document.findBlockByNumber(line-1) + block.setUserState(BREAKPOINT_CLEAR) + block.dispose + block = nil @lineNumberArea.repaint end def clear_breakpoints @breakpoints = [] + block = document.firstBlock() + while (block.isValid()) + block.setUserState(BREAKPOINT_CLEAR) + next_block = block.next() + block.dispose + block = next_block + end + block.dispose + block = nil @lineNumberArea.repaint end + def update_breakpoints + breakpoints = [] + block = document.firstBlock() + while (block.isValid()) + if block.userState() == BREAKPOINT_SET + line = block.firstLineNumber() + 1 + breakpoints << line + end + next_block = block.next() + block.dispose + block = next_block + end + block.dispose + block = nil + + # Only emit signals if the breakpoints have changed. + if @breakpoints.sort != breakpoints.sort + emit breakpoints_cleared + breakpoints.each {|line| emit breakpoint_set(line)} + @breakpoints = breakpoints + end + end + def comment_or_uncomment_lines cursor = textCursor no_selection = cursor.hasSelection ? false : true @@ -281,7 +324,7 @@ def line_number_area_paint_event(event) Qt::AlignRight, # flags number.to_s) # text - if @enable_breakpoints and @breakpoints.include?(number) + if @enable_breakpoints and block.userState() == BREAKPOINT_SET painter.setBrush(Cosmos::RED) painter.drawEllipse(2, top+2, @@ -312,7 +355,10 @@ def line_number_area_paint_event(event) private - def line_count_changed + def line_count_changed(new_block_count) + if new_block_count >= 0 + update_breakpoints() + end setViewportMargins(line_number_area_width(), 0, 0, 0) update end @@ -325,7 +371,7 @@ def update_line_number_area(rect, dy) end my_viewport = viewport() viewport_rect = my_viewport.rect() - line_count_changed() if (rect.contains(viewport_rect)) + line_count_changed(-1) if (rect.contains(viewport_rect)) viewport_rect.dispose end diff --git a/lib/cosmos/tools/script_runner/script_runner.rb b/lib/cosmos/tools/script_runner/script_runner.rb index dd79c93f6..798e86fec 100644 --- a/lib/cosmos/tools/script_runner/script_runner.rb +++ b/lib/cosmos/tools/script_runner/script_runner.rb @@ -465,6 +465,7 @@ def file_close close_active_tab() else active_script_runner_frame().stop_message_log + active_script_runner_frame().clear_breakpoints @tab_book.setTabText(0, ' Untitled ') @tab_book.currentTab.clear end @@ -849,6 +850,7 @@ def create_tab(filename = '') def close_active_tab if @tab_book.count > 1 active_script_runner_frame().stop_message_log + active_script_runner_frame().clear_breakpoints tab_index = @tab_book.currentIndex @tab_book.removeTab(tab_index) if tab_index >= 1 diff --git a/lib/cosmos/tools/script_runner/script_runner_frame.rb b/lib/cosmos/tools/script_runner/script_runner_frame.rb index cf2b96b27..eb497ef68 100644 --- a/lib/cosmos/tools/script_runner/script_runner_frame.rb +++ b/lib/cosmos/tools/script_runner/script_runner_frame.rb @@ -120,6 +120,7 @@ class ScriptRunnerFrame < Qt::Widget @@limits_sleeper = Sleeper.new @@cancel_output = false @@cancel_limits = false + @@file_number = 1 def initialize(parent, default_tab_text = 'Untitled') super(parent) @@ -128,6 +129,11 @@ def initialize(parent, default_tab_text = 'Untitled') @initialized = false @debug_frame = nil + # Keep track of a unique file number so we can differentiate untitled tabs + @file_number = @@file_number + @@file_number +=1 + @filename = '' + @layout = Qt::VBoxLayout.new @layout.setContentsMargins(0,0,0,0) @@ -149,6 +155,7 @@ def initialize(parent, default_tab_text = 'Untitled') # Add Initial Text Window @script = create_ruby_editor() + @script.filename = unique_filename() @script.connect(SIGNAL('modificationChanged(bool)')) do |changed| emit modificationChanged(changed) end @@ -182,7 +189,6 @@ def initialize(parent, default_tab_text = 'Untitled') # Configure Variables @line_offset = 0 - @filename = '' @output_io = StringIO.new('', 'r+') @output_io_mutex = Mutex.new @change_callback = nil @@ -210,19 +216,22 @@ def initialize(parent, default_tab_text = 'Untitled') @find_dialog = nil @replace_dialog = nil - mark_breakpoints('') + mark_breakpoints(@script.filename) + end + + def unique_filename + if @filename and !@filename.empty? + return @filename + else + return @default_tab_text.strip + @file_number.to_s + end end def current_tab_filename if @tab_book_shown - filename = @tab_book.tabText(@tab_book.currentIndex) - if filename == @default_tab_text - return '' - else - return filename.strip - end + return @tab_book.widget(@tab_book.currentIndex).filename else - return @filename + return @script.filename end end @@ -262,7 +271,17 @@ def filename=(filename) # Stop the message log so a new one will be created with the new filename stop_message_log() @filename = filename - mark_breakpoints(filename) + + # Deal with breakpoints created under the previous filename. + bkpt_filename = unique_filename() + if @@breakpoints[bkpt_filename].nil? + @@breakpoints[bkpt_filename] = @@breakpoints[@script.filename] + end + if bkpt_filename != @script.filename + @@breakpoints.delete(@script.filename) + @script.filename = bkpt_filename + end + mark_breakpoints(@script.filename) end def modified @@ -298,6 +317,7 @@ def allow_start=(value) def clear self.set_text('') self.filename = '' + @script.filename = unique_filename() self.modified = false end @@ -384,13 +404,15 @@ def set_text(text, filename = '') @script.setPlainText(text) @script.stop_highlight @filename = filename - mark_breakpoints(filename) + @script.filename = unique_filename() + mark_breakpoints(@script.filename) end end def set_text_from_file(filename) unless running?() @@file_cache[filename] = nil + @@breakpoints[filename] = nil load_file_into_script(filename) @filename = filename end @@ -960,26 +982,27 @@ def hide_debug end def self.set_breakpoint(filename, line_number) - filename = File.basename(filename) @@breakpoints[filename] ||= {} @@breakpoints[filename][line_number] = true end def self.clear_breakpoint(filename, line_number) - filename = File.basename(filename) @@breakpoints[filename] ||= {} @@breakpoints[filename].delete(line_number) if @@breakpoints[filename][line_number] end def self.clear_breakpoints(filename = nil) - filename = File.basename(filename) unless filename.nil? if filename == nil or filename.empty? @@breakpoints = {} else - @@breakpoints[filename] = {} + @@breakpoints.delete(filename) end end + def clear_breakpoints + ScriptRunnerFrame.clear_breakpoints(unique_filename()) + end + def select_tab_and_destroy_tabs_after_index(index) Qt.execute_in_main_thread(true) do if @tab_book_shown @@ -1352,6 +1375,7 @@ def handle_potential_tab_change(filename) else # new file # Create new tab new_script = create_ruby_editor() + new_script.filename = filename @tab_book.addTab(new_script, ' ' + File.basename(filename) + ' ') @call_stack.push(filename.dup) @@ -1373,9 +1397,12 @@ def show_active_tab end def handle_pause(filename, line_number) - filename = File.basename(filename) + bkpt_filename = '' + Qt.execute_in_main_thread(true) {bkpt_filename = @active_script.filename} breakpoint = false - breakpoint = true if @@breakpoints[filename] and @@breakpoints[filename][line_number] + breakpoint = true if @@breakpoints[bkpt_filename] and @@breakpoints[bkpt_filename][line_number] + + filename = File.basename(filename) if @pause @pause = false unless @@step_mode if breakpoint @@ -1544,8 +1571,7 @@ def load_file_into_script(filename) end def mark_breakpoints(filename) - @active_script.clear_breakpoints - breakpoints = @@breakpoints[File.basename(filename)] + breakpoints = @@breakpoints[filename] if breakpoints breakpoints.each do |line_number, present| @active_script.add_breakpoint(line_number) if present