/
script_runner_frame.rb
1828 lines (1600 loc) · 58 KB
/
script_runner_frame.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# encoding: ascii-8bit
# Copyright 2014 Ball Aerospace & Technologies Corp.
# All Rights Reserved.
#
# This program is free software; you can modify and/or redistribute it
# under the terms of the GNU General Public License
# as published by the Free Software Foundation; version 3 with
# attribution addendums as found in the LICENSE.txt
require 'cosmos'
require 'cosmos/script'
require 'cosmos/gui/utilities/script_module_gui'
require 'cosmos/gui/dialogs/splash'
require 'cosmos/gui/dialogs/exception_dialog'
require 'cosmos/gui/text/completion'
require 'cosmos/gui/text/completion_line_edit'
require 'cosmos/gui/text/ruby_editor'
require 'cosmos/gui/dialogs/progress_dialog'
require 'cosmos/gui/widgets/realtime_button_bar'
require 'cosmos/gui/dialogs/find_replace_dialog'
require 'cosmos/gui/choosers/file_chooser'
require 'cosmos/io/stdout'
require 'cosmos/io/stderr'
module Cosmos
Cosmos.disable_warnings do
module Script
def prompt_for_script_abort
ScriptRunnerFrame.instance.perform_pause
return false # Not aborted - Retry
end
end
end
class ScriptRunnerDialog < Qt::Dialog
attr_reader :script_runner_frame
def initialize(parent,
title,
default_tab_text = 'Untitled')
super(parent)
setWindowTitle(title)
setMinimumWidth(parent.width * 0.8)
setMinimumHeight(parent.height * 0.6)
# Create script runner frame
@script_runner_frame = ScriptRunnerFrame.new(self, default_tab_text)
layout = Qt::VBoxLayout.new
layout.addWidget(@script_runner_frame)
setLayout(layout)
end
# Executes the given text and closes when complete
def execute_text_and_close_on_complete(text, text_binding = nil)
@script_runner_frame.set_text(text)
@script_runner_frame.run_and_close_on_complete(text_binding)
exec
dispose
end
def reject
# Don't allow the dialog to close if we're running
return if @script_runner_frame.running?
super
end
end # class ScriptRunnerDialog
class ScriptRunnerFrame < Qt::Widget
slots 'context_menu(const QPoint&)'
slots 'undo_available(bool)'
slots 'breakpoint_set(int)'
slots 'breakpoint_cleared(int)'
slots 'breakpoints_cleared()'
signals 'undoAvailable(bool)'
signals 'modificationChanged(bool)'
CMD_KEYWORDS = %w(cmd cmd_no_range_check cmd_no_hazardous_check cmd_no_checks)
TLM_KEYWORDS = %w(tlm tlm_raw tlm_formatted tlm_with_units limits_enabled? \
enable_limits disable_limits wait_tolerance wait_tolerance_raw \
check_tolerance check_tolerance_raw wait_check_tolerance \
wait_check_tolerance_raw)
SET_TLM_KEYWORDS = %w(set_tlm set_tlm_raw)
CHECK_KEYWORDS = %w(check check_raw wait wait_raw wait_check wait_check_raw)
attr_accessor :use_instrumentation
attr_accessor :change_callback
attr_accessor :run_callback
attr_accessor :stop_callback
attr_accessor :error_callback
attr_accessor :pause_callback
attr_reader :filename
attr_accessor :continue_after_error
attr_accessor :exceptions
attr_accessor :script_binding
attr_accessor :inline_return
attr_accessor :inline_return_params
attr_reader :message_log
attr_reader :script_class
attr_reader :top_level_instrumented_cache
attr_accessor :stdout_max_lines
attr_reader :script
@@instance = nil
@@run_thread = nil
@@breakpoints = {}
@@step_mode = false
@@line_delay = 0.1
@@instrumented_cache = {}
@@file_cache = {}
@@output_thread = nil
@@limits_monitor_thread = nil
@@pause_on_error = true
@@monitor_limits = false
@@pause_on_red = false
@@show_backtrace = false
@@error = nil
@@output_sleeper = Sleeper.new
@@limits_sleeper = Sleeper.new
@@cancel_output = false
@@cancel_limits = false
@@file_number = 1
def initialize(parent, default_tab_text = 'Untitled')
super(parent)
@default_tab_text = ' ' + default_tab_text + ' '
# Keep track of whether this frame has been fully initialized
@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)
# Add Realtime Button Bar
@realtime_button_bar = RealtimeButtonBar.new(self)
@realtime_button_bar.state = 'Stopped'
@realtime_button_bar.start_callback = method(:handle_start_go_button)
@realtime_button_bar.pause_callback = method(:handle_pause_retry_button)
@realtime_button_bar.stop_callback = method(:handle_stop_button)
@layout.addWidget(@realtime_button_bar)
# Create a splitter to hold the script text area and the script output text area.
@splitter = Qt::Splitter.new(Qt::Vertical, self)
@layout.addWidget(@splitter)
@top_widget = Qt::Widget.new(@splitter)
@top_widget.setContentsMargins(0,0,0,0)
@top_frame = Qt::VBoxLayout.new(@top_widget)
@top_frame.setContentsMargins(0,0,0,0)
# Add Initial Text Window
@script = create_ruby_editor()
@script.filename = unique_filename()
@script.connect(SIGNAL('modificationChanged(bool)')) do |changed|
emit modificationChanged(changed)
end
@top_frame.addWidget(@script)
# Set self as the gui window to allow prompts and other popups to appear
set_cmd_tlm_gui_window(self)
# Add change handlers
connect(@script,
SIGNAL('undoAvailable(bool)'),
self,
SLOT('undo_available(bool)'))
# Add Output Text
@bottom_frame = Qt::Widget.new
@bottom_layout = Qt::VBoxLayout.new
@bottom_layout.setContentsMargins(0,0,0,0)
@bottom_layout_label = Qt::Label.new("Script Output:")
@bottom_layout.addWidget(@bottom_layout_label)
@output = Qt::PlainTextEdit.new
@output.setReadOnly(true)
@output.setMaximumBlockCount(100)
@bottom_layout.addWidget(@output)
@bottom_frame.setLayout(@bottom_layout)
@splitter.addWidget(@bottom_frame)
@splitter.setStretchFactor(0,10)
@splitter.setStretchFactor(1,1)
setLayout(@layout)
# Configure Variables
@line_offset = 0
@output_io = StringIO.new('', 'r+')
@output_io_mutex = Mutex.new
@change_callback = nil
@run_callback = nil
@stop_callback = nil
@error_callback = nil
@pause_callback = nil
@key_press_callback = nil
@allow_start = true
@continue_after_error = true
@debug_text = nil
@debug_history = []
@debug_code_completion = nil
@top_level_instrumented_cache = nil
@output_time = Time.now
initialize_variables()
# Redirect $stdout and $stderr
redirect_io()
# Create Tabbook
@tab_book = Qt::TabWidget.new
@tab_book_shown = false
@find_dialog = nil
@replace_dialog = nil
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
return @tab_book.widget(@tab_book.currentIndex).filename
else
return @script.filename
end
end
def create_ruby_editor
# Add Initial Text Window
script = RubyEditor.new(self)
script.enable_breakpoints = true if @debug_frame
connect(script,
SIGNAL('breakpoint_set(int)'),
self,
SLOT('breakpoint_set(int)'))
connect(script,
SIGNAL('breakpoint_cleared(int)'),
self,
SLOT('breakpoint_cleared(int)'))
connect(script,
SIGNAL('breakpoints_cleared()'),
self,
SLOT('breakpoints_cleared()'))
# Add right click menu
script.setContextMenuPolicy(Qt::CustomContextMenu)
connect(script,
SIGNAL('customContextMenuRequested(const QPoint&)'),
self,
SLOT('context_menu(const QPoint&)'))
return script
end
def stop_message_log
@message_log.stop if @message_log
@message_log = nil
end
def filename=(filename)
# Stop the message log so a new one will be created with the new filename
stop_message_log()
@filename = 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
@script.document.isModified()
end
def modified=(bool)
@script.document.setModified(bool)
end
def undo_available(bool)
emit undoAvailable(bool)
end
def key_press_callback=(callback)
@script.keyPressCallback = callback
end
def setFocus
@script.setFocus
end
def allow_start=(value)
@allow_start = value
if @allow_start
@realtime_button_bar.start_button.setEnabled(true)
elsif not running?
@realtime_button_bar.start_button.setEnabled(false)
@script.setReadOnly(true)
end
end
def clear
self.set_text('')
self.filename = ''
@script.filename = unique_filename()
self.modified = false
end
def self.instance
@@instance
end
def self.instance=(value)
@@instance = value
end
def self.step_mode
@@step_mode
end
def self.step_mode=(value)
@@step_mode = value
if self.instance
if value
self.instance.pause
else
self.instance.go
end
end
end
def self.line_delay
@@line_delay
end
def self.line_delay=(value)
@@line_delay = value
end
def self.instrumented_cache
@@instrumented_cache
end
def self.instrumented_cache=(value)
@@instrumented_cache = value
end
def self.pause_on_error
@@pause_on_error
end
def self.pause_on_error=(value)
@@pause_on_error = value
end
def self.monitor_limits
@@monitor_limits
end
def self.monitor_limits=(value)
@@monitor_limits = value
end
def self.pause_on_red
@@pause_on_red
end
def self.pause_on_red=(value)
@@pause_on_red = value
end
def self.show_backtrace
@@show_backtrace
end
def self.show_backtrace=(value)
@@show_backtrace = value
if @@show_backtrace and @@error
puts Time.now.formatted + " (SCRIPTRUNNER): " + "Most recent exception:\n" + @@error.formatted
end
end
def text
@script.toPlainText.gsub("\r", '')
end
def set_text(text, filename = '')
unless running?()
@script.setPlainText(text)
@script.stop_highlight
@filename = 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
end
def self.running?
if @@run_thread then true else false end
end
def running?
if @@instance == self and ScriptRunnerFrame.running?() then true else false end
end
def go
@go = true
@pause = false unless @@step_mode
end
def go?
temp = @go
@go = false
temp
end
def pause
@pause = true
@go = false
end
def pause?
@pause
end
def self.stop!
if @@run_thread
Cosmos.kill_thread(nil, @@run_thread)
@@run_thread = nil
end
end
def stop?
@stop
end
def retry_needed?
@retry_needed
end
def retry_needed
@retry_needed = true
end
def disable_retry
@realtime_button_bar.start_button.setText('Skip')
@realtime_button_bar.pause_button.setDisabled(true)
end
def enable_retry
@realtime_button_bar.start_button.setText('Go')
@realtime_button_bar.pause_button.setDisabled(false)
end
def run
unless self.class.running?()
run_text(@script.toPlainText)
end
end
def run_and_close_on_complete(text_binding = nil)
run_text(@script.toPlainText, 0, text_binding, true)
end
def self.instrument_script(text, filename, mark_private = false)
if filename and !filename.empty?
@@file_cache[filename] = text.clone
end
ruby_lex_utils = RubyLexUtils.new
instrumented_text = ''
Qt.execute_in_main_thread(true) do
window = Qt::CoreApplication.instance.activeWindow
@cancel_instrumentation = false
ProgressDialog.execute(window, # parent
"Instrumenting: #{File.basename(filename.to_s)}",
500, # width
100, # height
true, # show overall progress
false, # don't show step progress
false, # don't show text
false, # don't show done
true) do |progress_dialog| # show cancel
progress_dialog.cancel_callback = lambda {|dialog| @cancel_instrumentation = true; [true, false]}
progress_dialog.enable_cancel_button
comments_removed_text = ruby_lex_utils.remove_comments(text)
num_lines = comments_removed_text.num_lines.to_f
num_lines = 1 if num_lines < 1
instrumented_text =
instrument_script_implementation(ruby_lex_utils,
comments_removed_text,
num_lines,
progress_dialog,
filename,
mark_private)
progress_dialog.close_done
end
end
Kernel.raise StopScript if @cancel_instrumentation or ProgressDialog.canceled?
instrumented_text
end
def self.instrument_script_implementation(ruby_lex_utils,
comments_removed_text,
num_lines,
progress_dialog,
filename,
mark_private = false)
if mark_private
instrumented_text = 'private; '
else
instrumented_text = ''
end
ruby_lex_utils.each_lexed_segment(comments_removed_text) do |segment, instrumentable, inside_begin, line_no|
return nil if @cancel_instrumentation
instrumented_line = ''
if instrumentable
# If not inside a begin block then create one to catch exceptions
unless inside_begin
instrumented_line << 'begin; '
end
# Add preline instrumentation
instrumented_line << "ScriptRunnerFrame.instance.script_binding = binding(); if ScriptRunnerFrame.instance.inline_return then ScriptRunnerFrame.instance.inline_return = nil; return ScriptRunnerFrame.instance.inline_return_params; end; ScriptRunnerFrame.instance.pre_line_instrumentation('#{filename}', #{line_no}); "
# Add the actual line
instrumented_line << segment
instrumented_line.chomp!
# Add postline instrumentation
instrumented_line << "; ScriptRunnerFrame.instance.post_line_instrumentation('#{filename}', #{line_no})"
# Complete begin block to catch exceptions
unless inside_begin
instrumented_line << "; rescue Exception => eval_error; retry if ScriptRunnerFrame.instance.exception_instrumentation(eval_error, '#{filename}', #{line_no}); end"
end
instrumented_line << "\n"
else
unless segment =~ /^\s*end\s*$/ or segment =~ /^\s*when .*$/
num_left_brackets = segment.count('{')
num_right_brackets = segment.count('}')
num_left_square_brackets = segment.count('[')
num_right_square_brackets = segment.count(']')
if (num_right_brackets > num_left_brackets) ||
(num_right_square_brackets > num_left_square_brackets)
instrumented_line = segment
else
instrumented_line = "ScriptRunnerFrame.instance.pre_line_instrumentation('#{filename}', #{line_no}); " + segment
end
else
instrumented_line = segment
end
end
instrumented_text << instrumented_line
progress_dialog.set_overall_progress(line_no / num_lines) if progress_dialog and line_no
end
instrumented_text
end
def pre_line_instrumentation(filename, line_number)
@current_filename = filename
@current_line_number = line_number
if @use_instrumentation
# Clear go
@go = false
# Handle stopping mid-script if necessary
Kernel.raise StopScript if @stop
# Handle needing to change tabs
handle_potential_tab_change(filename)
# Adjust line number for offset in main script
line_number = line_number + @line_offset if @active_script.object_id == @script.object_id
detail_string = nil
if filename
detail_string = File.basename(filename) << ':' << line_number.to_s
end
Logger.detail_string = detail_string
# Highlight the line that is about to run
Qt.execute_in_main_thread(true) {@active_script.highlight_line(line_number)}
# Handle pausing the script
handle_pause(filename, line_number)
# Handle delay between lines
handle_line_delay()
end
end
def post_line_instrumentation(filename, line_number)
if @use_instrumentation
line_number = line_number + @line_offset if @active_script.object_id == @script.object_id
handle_output_io(filename, line_number)
end
end
def exception_instrumentation(error, filename, line_number)
if error.class == StopScript || error.class == SkipTestCase || !@use_instrumentation
Kernel.raise error
else
line_number = line_number + @line_offset if @active_script.object_id == @script.object_id
handle_exception(error, false, filename, line_number)
end
end
def perform_pause
mark_paused()
wait_for_go_or_stop()
end
def perform_breakpoint(filename, line_number)
mark_breakpoint()
scriptrunner_puts "Hit Breakpoint at #{filename}:#{line_number}"
handle_output_io(filename, line_number)
wait_for_go_or_stop()
end
# Prompts the user that a script is running before they close the app
def prompt_if_running_on_close
safe_to_continue = true
if running?
case Qt::MessageBox.warning(
self, # parent
'Warning', # title
'A Script is Running! Close Anyways?', # text
Qt::MessageBox::Yes | Qt::MessageBox::No, # buttons
Qt::MessageBox::No) # default button
when Qt::MessageBox::Yes
safe_to_continue = true
ScriptRunnerFrame.stop!
else
safe_to_continue = false
end
end
return safe_to_continue
end
######################################
# Implement the breakpoint callbacks from the RubyEditor
######################################
def breakpoint_set(line)
ScriptRunnerFrame.set_breakpoint(current_tab_filename(), line)
end
def breakpoint_cleared(line)
ScriptRunnerFrame.clear_breakpoint(current_tab_filename(), line)
end
def breakpoints_cleared
ScriptRunnerFrame.clear_breakpoints(current_tab_filename())
end
######################################
# Implement edit functionality in the frame (cut, copy, paste, etc)
######################################
def undo
@script.undo unless running?()
end
def redo
@script.redo unless running?()
end
def cut
@script.cut unless running?()
end
def copy
@script.copy unless running?()
end
def paste
@script.paste unless running?()
end
def select_all
@script.select_all unless running?()
end
def comment_or_uncomment_lines
@script.comment_or_uncomment_lines unless running?()
end
##################################################################################
# Implement Script functionality in the frame (run selection, run from cursor, etc
##################################################################################
def run_selection
unless self.class.running?()
selection = @script.selected_lines
if selection
start_line_number = @script.selection_start_line
end_line_number = @script.selection_end_line
scriptrunner_puts "Running script lines #{start_line_number+1}-#{end_line_number+1}: #{File.basename(@filename)}"
handle_output_io()
run_text(selection, start_line_number)
end
end
end
def run_selection_while_paused
current_script = @tab_book.widget(@tab_book.currentIndex)
selection = current_script.selected_lines
if selection
start_line_number = current_script.selection_start_line
end_line_number = current_script.selection_end_line
scriptrunner_puts "Debug: Running selected lines #{start_line_number+1}-#{end_line_number+1}: #{@tab_book.tabText(@tab_book.currentIndex)}"
handle_output_io()
dialog = ScriptRunnerDialog.new(self, 'Executing Selected Lines While Paused')
dialog.execute_text_and_close_on_complete(selection, @script_binding)
handle_output_io()
end
end
def run_from_cursor
unless self.class.running?()
line_number = @script.selection_start_line
text = @script.toPlainText.split("\n")[line_number..-1].join("\n")
scriptrunner_puts "Running script from line #{line_number}: #{File.basename(@filename)}"
handle_output_io()
run_text(text, line_number)
end
end
def ruby_syntax_check_selection
unless self.class.running?()
selection = @script.selected_lines
ruby_syntax_check_text(selection) if selection
end
end
def ruby_syntax_check_text(selection = nil)
unless self.class.running?()
selection = text() unless selection
check_process = IO.popen("ruby -c -rubygems 2>&1", 'r+')
check_process.write("require 'cosmos'; require 'cosmos/script'; " + selection)
check_process.close_write
results = check_process.gets
check_process.close
if results
if results =~ /Syntax OK/
Qt::MessageBox.information(self, 'Syntax Check Successful', results)
else
# Results is a string like this: ":2: syntax error ..."
# Normally the procedure comes before the first colon but since we
# are writing to the process this is blank so we throw it away
_, line_no, error = results.split(':')
Qt::MessageBox.warning(self,
'Syntax Check Failed',
"Error on line #{line_no}: #{error.strip}")
end
else
Qt::MessageBox.critical(self,
'Syntax Check Exception',
'Ruby syntax check unexpectedly returned nil')
end
end
end
def mnemonic_check_selection
unless self.class.running?()
selection = @script.selected_lines
mnemonic_check_text(selection, @script.selection_start_line+1) if selection
end
end
def mnemonic_check_text(text, start_line_number = 1)
results = []
line_number = start_line_number
text.each_line do |line|
if line =~ /\(/
result = nil
keyword = line.split('(')[0].split[-1]
if CMD_KEYWORDS.include? keyword
result = mnemonic_check_cmd_line(keyword, line_number, line)
elsif TLM_KEYWORDS.include? keyword
result = mnemonic_check_tlm_line(keyword, line_number, line)
elsif SET_TLM_KEYWORDS.include? keyword
result = mnemonic_check_set_tlm_line(keyword, line_number, line)
elsif CHECK_KEYWORDS.include? keyword
result = mnemonic_check_check_line(keyword, line_number, line)
end
results << result if result
end
line_number += 1
end
if results.empty?
Qt::MessageBox.information(self,
'Mnemonic Check Successful',
'Mnemonic Check Found No Errors')
else
dialog = Qt::Dialog.new(self) do |box|
box.setWindowTitle('Mnemonic Check Failed')
text = Qt::PlainTextEdit.new
text.setReadOnly(true)
text.setPlainText(results.join("\n"))
frame = Qt::VBoxLayout.new(box)
ok = Qt::PushButton.new('Ok')
ok.setDefault(true)
ok.connect(SIGNAL('clicked(bool)')) { box.accept }
frame.addWidget(text)
frame.addWidget(ok)
end
dialog.exec
dialog.dispose
end
end
######################################################
# Implement the debug capability
######################################################
def toggle_debug(debug = nil)
if debug.nil?
if @debug_frame
hide_debug()
else
show_debug()
end
else
if debug
if !@debug_frame
show_debug()
end
else
if @debug_frame
hide_debug()
end
end
end
end
def show_debug
unless @debug_frame
@script.enable_breakpoints = true
if @tab_book_shown
if @tab_book.count > 0
(0..(@tab_book.count - 1)).each do |index|
@tab_book.widget(index).enable_breakpoints = true
end
end
end
@debug_frame = Qt::HBoxLayout.new
@debug_frame.setContentsMargins(0,0,0,0)
@debug_frame_label = Qt::Label.new("Debug:")
@debug_frame.addWidget(@debug_frame_label)
@debug_text = CompletionLineEdit.new(self)
@debug_text.setFocus(Qt::OtherFocusReason)
@debug_text.keyPressCallback = lambda { |event|
case event.key
when Qt::Key_Return, Qt::Key_Enter
begin
debug_text = @debug_text.toPlainText
@debug_history.unshift(debug_text)
@debug_history_index = 0
@debug_text.setPlainText('')
scriptrunner_puts "Debug: #{debug_text}"
handle_output_io()
if not running?
# Capture STDOUT and STDERR
$stdout.add_stream(@output_io)
$stderr.add_stream(@output_io)
end
if @script_binding
eval(debug_text, @script_binding, 'debug', 1)
else
Object.class_eval(debug_text, 'debug', 1)
end
handle_output_io()
rescue Exception => error
if error.class == DRb::DRbConnError
Logger.error("Error Connecting to Command and Telemetry Server")
else
Logger.error(error.class.to_s.split('::')[-1] + ' : ' + error.message)
end
handle_output_io()
ensure
if not running?
# Capture STDOUT and STDERR
$stdout.remove_stream(@output_io)
$stderr.remove_stream(@output_io)
end
end
when Qt::Key_Up
if @debug_history.length > 0
@debug_text.setPlainText(@debug_history[@debug_history_index])
@debug_history_index += 1
if @debug_history_index == @debug_history.length
@debug_history_index = @debug_history.length-1
end
end
when Qt::Key_Down
if @debug_history.length > 0
@debug_text.setPlainText(@debug_history[@debug_history_index])
@debug_history_index -= 1
@debug_history_index = 0 if @debug_history_index < 0
end
when Qt::Key_Escape
@debug_text.setPlainText("")
end
}
@debug_frame.addWidget(@debug_text)
@toggle_button = Qt::PushButton.new('Toggle Run/Step')
@debug_frame.addWidget(@toggle_button)
@toggle_button.connect(SIGNAL('clicked(bool)')) do
if @@step_mode
scriptrunner_puts "Debug: run_mode"
handle_output_io()
self.class.step_mode = false
else
scriptrunner_puts "Debug: step_mode"
handle_output_io()
self.class.step_mode = true
end
end
@return_button = Qt::PushButton.new('Insert Return')
@debug_frame.addWidget(@return_button)
@return_button.connect(SIGNAL('clicked(bool)')) do
scriptrunner_puts "Debug: insert_return(); ScriptRunnerFrame.instance.go()"
handle_output_io()
insert_return()
go()
end
@bottom_frame.layout.addLayout(@debug_frame)
end
end
def hide_debug
# Since we're disabling debug, clear the breakpoints and disable them
ScriptRunnerFrame.clear_breakpoints()
@script.clear_breakpoints
@script.enable_breakpoints = false
if @tab_book_shown
if @tab_book.count > 0
(0..(@tab_book.count - 1)).each do |index|
@tab_book.widget(index).enable_breakpoints = false
end
end
end
# Remove the debug frame
@bottom_frame.layout.takeAt(@bottom_frame.layout.count - 1) if @debug_frame
@debug_frame.removeAll
@debug_frame.dispose
@debug_frame = nil
end
def self.set_breakpoint(filename, line_number)
@@breakpoints[filename] ||= {}
@@breakpoints[filename][line_number] = true
end
def self.clear_breakpoint(filename, line_number)
@@breakpoints[filename] ||= {}
@@breakpoints[filename].delete(line_number) if @@breakpoints[filename][line_number]
end
def self.clear_breakpoints(filename = nil)
if filename == nil or filename.empty?
@@breakpoints = {}
else
@@breakpoints.delete(filename)
end
end