<commit_before>require "listen"
require "rspec-live/key_handler"
require "rspec-live/result_detail"

module RSpecLive
  class Monitor
    def initialize(suite, runner, display, detail, file_watcher)
      @suite = suite
      @runner = runner
      @display = display
      @detail = detail
      @file_watcher = file_watcher
      @quit = false
    end

    def start
      @runner.on_update { @display.update @suite }
      @suite.reset
      while !@quit do
        if services.any?(&:updates_available?)
          key_handler.process_updates
          @suite.process_updates
          @display.update @suite
        end
        sleep 0.05
      end
    end

    private

    def services
      [key_handler, @file_watcher]
    end

    def key_handler
      @key_handler ||= KeyHandler.new.tap do |handler|
        handler.on("a") { @detail.toggle_all }
        handler.on("n") { @detail.focus_next }
        handler.on("q", :interrupt) { @quit = true }
        handler.on("r") { @suite.reset }
        handler.on("v") { @detail.cycle_verbosity }
      end
    end
  end
end<commit_msg>Refactor update loop and handle interrupts in Monitor<commit_after>require "listen"
require "rspec-live/key_handler"
require "rspec-live/result_detail"

module RSpecLive
  class Monitor
    def initialize(suite, runner, display, detail, file_watcher)
      @suite = suite
      @runner = runner
      @display = display
      @detail = detail
      @file_watcher = file_watcher
      @quit = false
    end

    def start
      @runner.on_update { @display.update @suite }
      @suite.reset
      while !@quit do
        process_updates if updates_available?
        sleep 0.05
      end
      rescue Interrupt
    end

    private

    def process_updates
      key_handler.process_updates
      @suite.process_updates
      @display.update @suite
    end

    def updates_available?
      [key_handler, @file_watcher].any?(&:updates_available?)
    end

    def key_handler
      @key_handler ||= KeyHandler.new.tap do |handler|
        handler.on("a") { @detail.toggle_all }
        handler.on("n") { @detail.focus_next }
        handler.on("q") { @quit = true }
        handler.on("r") { @suite.reset }
        handler.on("v") { @detail.cycle_verbosity }
      end
    end
  end
end
{'fraction_non_alphanumeric': 0.10105448154657294, 'fraction_numerical': 0.0026362038664323375, 'mean_word_length': 2.353460972017673, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2455380', 'n_tokens_mistral': 721, 'n_tokens_neox': 699, 'n_words': 164}