class Scryglass::Session
Constants
- CSI
- CURSOR_CHARACTER
- KEY_MAP
- METHOD_NAME_PROMPT
- PATIENT_ACTIONS
- SEARCH_PROMPT
- SUBJECT_TYPES
- VARNAME_PROMPT
Attributes
all_ros[RW]
content_shape_changed[RW]
current_lens[RW]
current_panel_type[RW]
current_ro[RW]
current_subject_type[RW]
current_view_coords[RW]
current_warning_messages[RW]
last_search[RW]
number_to_move[RW]
previous_screen_dimensions[RW]
progress_bar[RW]
session_is_current[RW]
session_manager[RW]
session_view_start_time[RW]
signal_to_manager[RW]
special_command_targets[RW]
tab_icon[RW]
user_signals[RW]
view_panels[RW]
Public Class Methods
new(seed)
click to toggle source
# File lib/scryglass/session.rb, line 94 def initialize(seed) self.all_ros = [] self.current_lens = 0 self.current_subject_type = :value self.current_panel_type = :tree self.special_command_targets = [] self.number_to_move = '' self.user_signals = [] self.progress_bar = Prog::Pipe.new self.current_warning_messages = [] self.session_manager = nil self.signal_to_manager = nil self.tab_icon = nil self.session_is_current = false self.session_view_start_time = nil self.content_shape_changed = true self.previous_screen_dimensions = $stdout.winsize top_ro = roify(seed, parent_ro: nil, depth: 1) top_ro.has_cursor = true self.current_ro = top_ro expand!(top_ro) self.view_panels = { tree: Scryglass::TreePanel.new(scry_session: self), lens: Scryglass::LensPanel.new(scry_session: self), } end
Public Instance Methods
last_keypress()
click to toggle source
# File lib/scryglass/session.rb, line 128 def last_keypress last_two_signals = user_signals.last(2) last_two_signals.last || last_two_signals.first end
run_scry_ui()
click to toggle source
# File lib/scryglass/session.rb, line 133 def run_scry_ui redraw = true self.signal_to_manager = nil self.session_view_start_time = Time.now # For this particular tab/session ## On hold: Record/Playback Functionality: # case actions # when :record # $scry_session_actions_performed = [] # when :playback # if $scry_session_actions_performed.blank? # raise 'Could not find recording of previous session\'s actions' # end # @input_stack = $scry_session_actions_performed.dup # end # We print a full screen of lines so the first call of draw_screen doesn't # write over any previous valuable content the user had in the console. print Hexes.opacify_screen_string(Hexes.simple_screen_slice(boot_screen)) while true draw_screen if redraw redraw = true ## On hold: Record/Playback Functionality: # case actions # when :record # self.user_input = $stdin.getch # $scry_session_actions_performed << user_input # when :playback # if @input_stack.any? # (IV to be easily accessible for debugging) # self.user_input = @input_stack.shift # sleep 0.05 # else # self.user_input = $stdin.getch # end # else # self.user_input = $stdin.getch # end new_signal = fetch_user_signal wait_start_time = Time.now case new_signal when nil when KEY_MAP[:escape] case current_panel_type when :lens self.current_panel_type = :tree when :tree clear_tracked_values end when KEY_MAP[:ctrl_c] set_console_cursor_below_content raise IRB::Abort, 'Ctrl+C Detected' when KEY_MAP[:quit_session] self.signal_to_manager = :quit return when KEY_MAP[:delete_session_tab] self.signal_to_manager = :delete return when KEY_MAP[:control_screen] remain_in_scry_session = run_help_screen_ui unless remain_in_scry_session self.signal_to_manager = :quit_from_help return end when KEY_MAP[:digit_1] self.number_to_move += '1' # This allows you to type multi-digit number very # quickly and still have it process all the digits: redraw = false when KEY_MAP[:digit_2] self.number_to_move += '2' redraw = false when KEY_MAP[:digit_3] self.number_to_move += '3' redraw = false when KEY_MAP[:digit_4] self.number_to_move += '4' redraw = false when KEY_MAP[:digit_5] self.number_to_move += '5' redraw = false when KEY_MAP[:digit_6] self.number_to_move += '6' redraw = false when KEY_MAP[:digit_7] self.number_to_move += '7' redraw = false when KEY_MAP[:digit_8] self.number_to_move += '8' redraw = false when KEY_MAP[:digit_9] self.number_to_move += '9' redraw = false when KEY_MAP[:digit_0] if number_to_move[0] # You can append zeros to existing number_to_move... self.number_to_move += '0' redraw = false else # ...but otherwise it's understood to be a view||cursor reset. reset_the_view_or_cursor end when KEY_MAP[:move_cursor_up] move_cursor_up_action when KEY_MAP[:move_cursor_down] move_cursor_down_action when KEY_MAP[:open_bucket] expand_targets when KEY_MAP[:close_bucket] collapse_targets when KEY_MAP[:homerow_move_cursor_up] move_cursor_up_action when KEY_MAP[:homerow_move_cursor_up_fast] move_cursor_up_action(12) # 12 matches the digits provided by shift+up when KEY_MAP[:homerow_move_cursor_down] move_cursor_down_action when KEY_MAP[:homerow_move_cursor_down_fast] move_cursor_down_action(12) # 12 matches the digits provided by shift+down when KEY_MAP[:homerow_open_bucket] expand_targets when KEY_MAP[:homerow_close_bucket] collapse_targets when KEY_MAP[:toggle_view_panel] toggle_view_panel when KEY_MAP[:switch_lens] scroll_lens_type when KEY_MAP[:switch_subject_type] toggle_current_subject_type when KEY_MAP[:move_view_up] current_view_panel.move_view_up(5) when KEY_MAP[:move_view_down] current_view_panel.move_view_down(5) when KEY_MAP[:move_view_left] current_view_panel.move_view_left(5) when KEY_MAP[:move_view_right] current_view_panel.move_view_right(5) when KEY_MAP[:move_view_up_fast] current_view_panel.move_view_up(50) when KEY_MAP[:move_view_down_fast] current_view_panel.move_view_down(50) when KEY_MAP[:move_view_left_fast] current_view_panel.move_view_left(50) when KEY_MAP[:move_view_right_fast] current_view_panel.move_view_right(50) when KEY_MAP[:build_instance_variables] build_instance_variables_for_target_ros self.content_shape_changed = true tree_view.slide_view_to_cursor # Just a nice-to-have when KEY_MAP[:build_ar_relations] build_activerecord_relations_for_target_ros self.content_shape_changed = true tree_view.slide_view_to_cursor # Just a nice-to-have when KEY_MAP[:build_enum_children] build_enum_children_for_target_ros self.content_shape_changed = true tree_view.slide_view_to_cursor # Just a nice-to-have when KEY_MAP[:smart_open] smart_open_target_ros self.content_shape_changed = true tree_view.slide_view_to_cursor # Just a nice-to-have when KEY_MAP[:build_method_results] build_method_result_ros self.content_shape_changed = true tree_view.slide_view_to_cursor # Just a nice-to-have when KEY_MAP[:select_siblings] sibling_ros = if current_ro.top_ro? [top_ro] else current_ro.parent_ro.sub_ros.dup # ^If we don't dup, # then '-' can remove ros from `sub_ros`. end if special_command_targets.sort == sibling_ros.sort self.special_command_targets = [] else self.special_command_targets = sibling_ros end when KEY_MAP[:select_all] all_the_ros = all_ros.dup # ^If we don't dup, # then '-' can remove ros from all_ros. if special_command_targets.sort == all_the_ros.sort self.special_command_targets = [] else self.special_command_targets = all_the_ros end when KEY_MAP[:select_current] if special_command_targets.include?(current_ro) special_command_targets.delete(current_ro) else special_command_targets << current_ro end when KEY_MAP[:start_search] initiate_search when KEY_MAP[:continue_search] # TODO: extract in separate commit if last_search go_to_next_search_result else message = { text: 'No Search has been entered', end_time: Time.now + 2 } self.current_warning_messages << message end when KEY_MAP[:start_new_session_from_target] self.signal_to_manager = :start_new_session_from_target return subjects_of_target_ros when KEY_MAP[:restart_session_from_target] self.signal_to_manager = :restart_session_from_target return subjects_of_target_ros when KEY_MAP[:change_session_right] self.signal_to_manager = :change_session_right return when KEY_MAP[:change_session_left] self.signal_to_manager = :change_session_left return when KEY_MAP[:name_objects] name_subjects_of_target_ros when KEY_MAP[:return_objects] self.signal_to_manager = :return return subjects_of_target_ros end beep_if_user_had_to_wait(wait_start_time) end end
set_console_cursor_below_content(floor_the_cursor:)
click to toggle source
# File lib/scryglass/session.rb, line 368 def set_console_cursor_below_content(floor_the_cursor:) if floor_the_cursor screen_height, _screen_width = $stdout.winsize $stdout.write "#{CSI}#{screen_height};1H\n" # (Moves console cursor to bottom left corner, then one more) return end bare_screen_string = current_view_panel.visible_header_string + "\n" + current_view_panel.visible_body_string split_lines = bare_screen_string.split("\n") rows_filled = split_lines.count $stdout.write "#{CSI}#{rows_filled};1H\n" # Moves console cursor to bottom # of *content*, then one more. end
subjects_of_target_ros()
click to toggle source
# File lib/scryglass/session.rb, line 394 def subjects_of_target_ros if special_command_targets.any? return special_command_targets.map(&:current_subject) end current_ro.current_subject end
tab_string()
click to toggle source
# File lib/scryglass/session.rb, line 384 def tab_string top_ro_preview = top_ro.value_string tab = if session_is_current "\e[7m #{tab_icon}: #{top_ro_preview} \e[00m" else " \e[7m#{tab_icon}:\e[00m #{top_ro_preview} " end tab end
top_ro()
click to toggle source
# File lib/scryglass/session.rb, line 124 def top_ro all_ros.first end
Private Instance Methods
beep_if_user_had_to_wait(wait_start_time)
click to toggle source
# File lib/scryglass/session.rb, line 404 def beep_if_user_had_to_wait(wait_start_time) patient_keys = KEY_MAP.slice(*PATIENT_ACTIONS).values user_has_waited_at_least_four_seconds = Time.now - wait_start_time > 4 && !patient_keys.include?(last_keypress) print "\a" if user_has_waited_at_least_four_seconds # (Audio 'beep') end
boot_screen()
click to toggle source
# File lib/scryglass/session.rb, line 828 def boot_screen screen_height, screen_width = $stdout.winsize stars = (1..(screen_height * screen_width)) .to_a .map { rand(100).zero? ? '.' : ' ' } stars.each_slice(screen_width).map { |set| set.join('') }.join("\n") end
build_method_result_ros()
click to toggle source
# File lib/scryglass/session.rb, line 685 def build_method_result_ros method_text = get_method_text_from_user if method_text.empty? message = { text: 'Call text cannot be blank', end_time: Time.now + 2 } self.current_warning_messages << message print "\a" # (Audio 'beep') return end if method_text[0] =~ /[a-z]|[A-Z]/ message = { text: 'Call text must start with \'.\' or other symbol', end_time: Time.now + 3 } self.current_warning_messages << message print "\a" # (Audio 'beep') return end build_method_results_for_target_ros(method_text) self.special_command_targets = [] end
clear_tracked_values()
click to toggle source
# File lib/scryglass/session.rb, line 447 def clear_tracked_values self.special_command_targets = [] self.last_search = nil self.number_to_move = '' end
collapse!(ro)
click to toggle source
# File lib/scryglass/session.rb, line 779 def collapse!(ro) ro.expanded = false if ro.expanded end
collapse_targets()
click to toggle source
# File lib/scryglass/session.rb, line 601 def collapse_targets if special_command_targets.any? target_ros = special_command_targets.dup # dup because some commands # create ros which are added to all_ros and then this process starts # adding them to the list of things it tries to act on! target_ros.each { |target_ro| collapse!(target_ro) } self.special_command_targets = [] elsif current_ro.expanded collapse!(current_ro) elsif current_ro.parent_ro collapse!(current_ro.parent_ro) end move_cursor_to(current_ro.parent_ro) until current_ro.visible? self.content_shape_changed = true tree_view.slide_view_to_cursor end
current_view_panel()
click to toggle source
# File lib/scryglass/session.rb, line 481 def current_view_panel view_panels[current_panel_type] end
display_active_searching_indicator()
click to toggle source
# File lib/scryglass/session.rb, line 493 def display_active_searching_indicator $stdout.write "#{CSI}1;1H" # (Moves console cursor to top left corner) message = ' Searching... ' pad = SEARCH_PROMPT.length - message.length wing = '-' * (pad / 2) $stdout.write "\e[7m#{wing + message + wing}\e[00m" end
draw_screen()
click to toggle source
# File lib/scryglass/session.rb, line 641 def draw_screen current_screen_dimensions = $stdout.winsize screen_size_changed = current_screen_dimensions != previous_screen_dimensions self.previous_screen_dimensions = current_screen_dimensions if content_shape_changed || screen_size_changed current_view_panel.recalculate_boundaries # ^This no longer happens at every screen draw, but only when # determined necessary. self.content_shape_changed = false end current_view_panel.ensure_correct_view_coords screen_string = current_view_panel.screen_string Hexes.overwrite_screen(screen_string) $stdout.write "#{CSI}1;1H" # Moves terminal cursor to top left corner, # mostly for consistency. print_current_warning_messages print_session_tabs_bar_if_changed end
expand!(ro)
click to toggle source
# File lib/scryglass/session.rb, line 775 def expand!(ro) ro.expanded = true if ro.sub_ros.any? end
expand_targets()
click to toggle source
# File lib/scryglass/session.rb, line 619 def expand_targets if special_command_targets.any? target_ros = special_command_targets.dup # dup because some commands # create ros which are added to all_ros and then this process starts # adding them to the list of things it tries to act on! target_ros.each { |target_ro| expand!(target_ro) } self.special_command_targets = [] else expand!(current_ro) end self.content_shape_changed = true end
fetch_user_signal()
click to toggle source
# File lib/scryglass/session.rb, line 539 def fetch_user_signal previous_signal = user_signals.last new_signal = begin Timeout.timeout(0.3) { $stdin.getch } rescue Timeout::Error nil end ## Since many keys, including arrow keys, result in several signals being ## sent (e.g. DOWN: "\e" then "[" then "B" in RAPID succession), the ## *pause* after a genuine escape key press (also "\e") is the only way ## to distinguish it precisely. genuine_escape_key_press = new_signal.nil? && previous_signal == "\e" if genuine_escape_key_press new_signal = 'esc' end user_signals << new_signal unless new_signal.nil? && previous_signal.nil? new_signal end
get_method_text_from_user()
click to toggle source
# File lib/scryglass/session.rb, line 674 def get_method_text_from_user _screen_height, screen_width = $stdout.winsize $stdout.write "#{CSI}1;1H" # (Moves console cursor to top left corner) $stdout.print ' ' * screen_width $stdout.write "#{CSI}1;1H" # (Moves console cursor to top left corner) $stdout.print METHOD_NAME_PROMPT $stdout.write "#{CSI}1;#{METHOD_NAME_PROMPT.ansiless_length + 1}H" # (Moves # console cursor to just after the search prompt, before user types) $stdin.gets.chomp end
get_subject_name_from_user()
click to toggle source
# File lib/scryglass/session.rb, line 663 def get_subject_name_from_user _screen_height, screen_width = $stdout.winsize $stdout.write "#{CSI}1;1H" # (Moves console cursor to top left corner) $stdout.print ' ' * screen_width $stdout.write "#{CSI}1;1H" # (Moves console cursor to top left corner) $stdout.print VARNAME_PROMPT $stdout.write "#{CSI}1;#{VARNAME_PROMPT.ansiless_length + 1}H" # (Moves # console cursor to just after the varname prompt, before user types) $stdin.gets.chomp end
go_to_next_search_result()
click to toggle source
# File lib/scryglass/session.rb, line 502 def go_to_next_search_result display_active_searching_indicator cut_point = current_ro.index search_set = ((cut_point + 1)...all_ros.count).to_a + (0...cut_point).to_a task = Prog::Task.new(max_count: search_set.count) progress_bar << task index_of_next_match = search_set.find do |index| scanned_ro = all_ros[index] task.tick print_progress_bar scanned_ro.key_string =~ /#{last_search}/ || (scanned_ro.nugget? && scanned_ro.value_string =~ /#{last_search}/) end task.force_finish if index_of_next_match next_found_ro = all_ros[index_of_next_match] move_cursor_to(next_found_ro) scanning_ro = next_found_ro while scanning_ro.parent_ro expand!(scanning_ro.parent_ro) scanning_ro = scanning_ro.parent_ro end self.content_shape_changed = true # Needed here even if ros weren't expanded. tree_view.current_view_coords = { y: 0, x: 0 } tree_view.slide_view_to_cursor else message = { text: 'No Match Found', end_time: Time.now + 2 } self.current_warning_messages << message end end
initiate_search()
click to toggle source
# File lib/scryglass/session.rb, line 412 def initiate_search _screen_height, screen_width = $stdout.winsize $stdout.write "#{CSI}1;1H" # (Moves console cursor to top left corner) $stdout.print ' ' * screen_width $stdout.write "#{CSI}1;1H" # (Moves console cursor to top left corner) $stdout.print SEARCH_PROMPT $stdout.write "#{CSI}1;#{SEARCH_PROMPT.ansiless_length + 1}H" # (Moves # console cursor to just after the search prompt, before user types) query = $stdin.gets.chomp unless query.empty? self.last_search = query go_to_next_search_result end end
lens_view()
click to toggle source
# File lib/scryglass/session.rb, line 489 def lens_view view_panels[:lens] end
move_cursor_down_action(action_count = nil)
click to toggle source
# File lib/scryglass/session.rb, line 437 def move_cursor_down_action(action_count = nil) action_count ||= !number_to_move.empty? ? number_to_move.to_i : 1 navigate_down_multiple(action_count) self.number_to_move = '' tree_view.slide_view_to_cursor self.content_shape_changed = true if current_panel_type == :lens end
move_cursor_to(new_ro)
click to toggle source
# File lib/scryglass/session.rb, line 812 def move_cursor_to(new_ro) current_ro.has_cursor = false new_ro.has_cursor = true self.current_ro = new_ro end
move_cursor_up_action(action_count = nil)
click to toggle source
# File lib/scryglass/session.rb, line 427 def move_cursor_up_action(action_count = nil) action_count ||= !number_to_move.empty? ? number_to_move.to_i : 1 navigate_up_multiple(action_count) self.number_to_move = '' tree_view.slide_view_to_cursor self.content_shape_changed = true if current_panel_type == :lens end
name_subjects_of_target_ros()
click to toggle source
# File lib/scryglass/session.rb, line 709 def name_subjects_of_target_ros typed_name = get_subject_name_from_user typed_name = typed_name.tr(' ', '') if typed_name.empty? message = { text: 'Instance Variable name cannot be blank', end_time: Time.now + 2 } self.current_warning_messages << message print "\a" # (Audio 'beep') return end current_console_binding = session_manager.current_console_binding preexisting_iv_names = current_console_binding .eval('instance_variables') # Different than just `.instance_variables` .map { |iv| iv.to_s.tr('@', '') } all_method_names = preexisting_iv_names | current_console_binding.methods | current_console_binding.singleton_methods | current_console_binding.private_methods conflicting_method_name = all_method_names.find do |method_name| pure_method_name = method_name.to_s.tr('=', '') typed_name == pure_method_name end if conflicting_method_name message = { text: 'Instance Variable name conflict', end_time: Time.now + 2 } self.current_warning_messages << message print "\a" # (Audio 'beep') return end set_iv_name_in_console = "@#{typed_name} = " \ "$scry_session_manager.current_session.subjects_of_target_ros" current_console_binding.eval(set_iv_name_in_console) session_manager.current_binding_tracker.user_named_variables << "@#{typed_name}" message = { text: "#{subjects_of_target_ros.class} assigned to: @#{typed_name}", end_time: Time.now + 3 } self.current_warning_messages << message self.special_command_targets = [] end
print_current_warning_messages()
click to toggle source
# File lib/scryglass/session.rb, line 460 def print_current_warning_messages return if current_warning_messages.empty? $stdout.write "#{CSI}1;1H" # (Moves console cursor to top left corner) wing = ' ' * 3 self.current_warning_messages.reject! { |message| Time.now > message[:end_time] } messages = current_warning_messages.map { |message| message[:text] } print messages.map { |message| "\e[7m#{wing + message + wing}\e[00m" }.join("\n") $stdout.write "#{CSI}1;1H" # (Moves console cursor to top left corner) end
print_progress_bar()
click to toggle source
# File lib/scryglass/session.rb, line 453 def print_progress_bar screen_height, _screen_width = $stdout.winsize bar = progress_bar.to_s $stdout.write "#{CSI}#{screen_height};1H" # (Moves console cursor to bottom left corner) print bar unless bar.tr(' ', '').empty? end
print_session_tabs_bar_if_changed()
click to toggle source
# File lib/scryglass/session.rb, line 472 def print_session_tabs_bar_if_changed seconds_in_tab = Time.now - session_view_start_time if seconds_in_tab < 2 $stdout.write "#{CSI}1;1H" # (Moves console cursor to top left corner) print session_manager.session_tabs_bar $stdout.write "#{CSI}1;1H" # (Moves console cursor to top left corner) end end
reset_the_view_or_cursor()
click to toggle source
# File lib/scryglass/session.rb, line 633 def reset_the_view_or_cursor if current_view_panel.current_view_coords != { x: 0, y: 0 } current_view_panel.current_view_coords = { x: 0, y: 0 } elsif current_panel_type == :tree move_cursor_to(top_ro) end end
run_help_screen_ui()
click to toggle source
# File lib/scryglass/session.rb, line 562 def run_help_screen_ui screen_height, _screen_width = $stdout.winsize in_help_screen = true current_help_screen_index = 0 help_screens = [Scryglass::HELP_SCREEN, Scryglass::HELP_SCREEN_ADVANCED] while in_help_screen current_help_screen = help_screens[current_help_screen_index] sliced_help_screen = Hexes.simple_screen_slice(current_help_screen) help_screen_string = Hexes.opacify_screen_string(sliced_help_screen) Hexes.overwrite_screen(help_screen_string) new_signal = fetch_user_signal case new_signal when nil when KEY_MAP[:escape] return true when KEY_MAP[:control_screen] current_help_screen_index += 1 when KEY_MAP[:quit_session] $stdout.write "#{CSI}#{screen_height};1H" # (Moves console cursor to # bottom left corner). This helps 'q' not print the console prompt at # the top of the screen, overlapping with the old display. return false when KEY_MAP[:ctrl_c] screen_height, _screen_width = $stdout.winsize puts "\n" * screen_height raise IRB::Abort, 'Ctrl+C Detected' end current_help_screen = help_screens[current_help_screen_index] unless current_help_screen return true end end end
scroll_lens_type()
click to toggle source
# File lib/scryglass/session.rb, line 807 def scroll_lens_type self.current_lens += 1 self.content_shape_changed = true end
toggle_current_subject_type()
click to toggle source
# File lib/scryglass/session.rb, line 795 def toggle_current_subject_type self.current_subject_type = case current_subject_type when :value :key when :key :value end self.content_shape_changed = true end
toggle_view_panel()
click to toggle source
# File lib/scryglass/session.rb, line 783 def toggle_view_panel self.current_panel_type = case current_panel_type when :tree :lens when :lens :tree end self.content_shape_changed = true end
tree_view()
click to toggle source
# File lib/scryglass/session.rb, line 485 def tree_view view_panels[:tree] end