ruby_jard 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +3 -0
  3. data/.github/ISSUE_TEMPLATE/bug_report.md +32 -0
  4. data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  5. data/.github/workflows/documentation.yml +65 -0
  6. data/.github/workflows/rspec.yml +96 -0
  7. data/.gitignore +1 -0
  8. data/.rubocop.yml +90 -2
  9. data/CHANGELOG.md +112 -0
  10. data/Gemfile +14 -4
  11. data/README.md +95 -3
  12. data/benchmark/path_filter_bench.rb +58 -0
  13. data/bin/console +1 -2
  14. data/lib/ruby_jard.rb +68 -32
  15. data/lib/ruby_jard/box_drawer.rb +175 -0
  16. data/lib/ruby_jard/color_scheme.rb +28 -0
  17. data/lib/ruby_jard/color_schemes.rb +54 -0
  18. data/lib/ruby_jard/color_schemes/256_color_scheme.rb +50 -0
  19. data/lib/ruby_jard/color_schemes/256_light_color_scheme.rb +50 -0
  20. data/lib/ruby_jard/color_schemes/deep_space_color_scheme.rb +49 -0
  21. data/lib/ruby_jard/color_schemes/gruvbox_color_scheme.rb +48 -0
  22. data/lib/ruby_jard/color_schemes/one_half_dark_color_scheme.rb +47 -0
  23. data/lib/ruby_jard/color_schemes/one_half_light_color_scheme.rb +49 -0
  24. data/lib/ruby_jard/column.rb +26 -0
  25. data/lib/ruby_jard/commands/color_helpers.rb +32 -0
  26. data/lib/ruby_jard/commands/continue_command.rb +4 -9
  27. data/lib/ruby_jard/commands/down_command.rb +9 -8
  28. data/lib/ruby_jard/commands/exit_command.rb +27 -0
  29. data/lib/ruby_jard/commands/frame_command.rb +13 -11
  30. data/lib/ruby_jard/commands/jard/color_scheme_command.rb +74 -0
  31. data/lib/ruby_jard/commands/jard/filter_command.rb +136 -0
  32. data/lib/ruby_jard/commands/jard/hide_command.rb +40 -0
  33. data/lib/ruby_jard/commands/jard/output_command.rb +36 -0
  34. data/lib/ruby_jard/commands/jard/show_command.rb +41 -0
  35. data/lib/ruby_jard/commands/jard_command.rb +52 -0
  36. data/lib/ruby_jard/commands/list_command.rb +31 -0
  37. data/lib/ruby_jard/commands/next_command.rb +11 -8
  38. data/lib/ruby_jard/commands/step_command.rb +11 -8
  39. data/lib/ruby_jard/commands/step_out_command.rb +34 -0
  40. data/lib/ruby_jard/commands/up_command.rb +10 -8
  41. data/lib/ruby_jard/commands/validation_helpers.rb +50 -0
  42. data/lib/ruby_jard/config.rb +61 -0
  43. data/lib/ruby_jard/console.rb +158 -0
  44. data/lib/ruby_jard/control_flow.rb +73 -0
  45. data/lib/ruby_jard/decorators/array_decorator.rb +79 -0
  46. data/lib/ruby_jard/decorators/attributes_decorator.rb +172 -0
  47. data/lib/ruby_jard/decorators/color_decorator.rb +80 -0
  48. data/lib/ruby_jard/decorators/hash_decorator.rb +74 -0
  49. data/lib/ruby_jard/decorators/inspection_decorator.rb +109 -0
  50. data/lib/ruby_jard/decorators/loc_decorator.rb +108 -119
  51. data/lib/ruby_jard/decorators/object_decorator.rb +122 -0
  52. data/lib/ruby_jard/decorators/path_decorator.rb +56 -60
  53. data/lib/ruby_jard/decorators/rails_decorator.rb +194 -0
  54. data/lib/ruby_jard/decorators/source_decorator.rb +3 -1
  55. data/lib/ruby_jard/decorators/string_decorator.rb +41 -0
  56. data/lib/ruby_jard/decorators/struct_decorator.rb +79 -0
  57. data/lib/ruby_jard/frame.rb +68 -0
  58. data/lib/ruby_jard/key_binding.rb +14 -0
  59. data/lib/ruby_jard/key_bindings.rb +96 -0
  60. data/lib/ruby_jard/keys.rb +48 -0
  61. data/lib/ruby_jard/layout.rb +17 -88
  62. data/lib/ruby_jard/layout_calculator.rb +168 -0
  63. data/lib/ruby_jard/layout_picker.rb +34 -0
  64. data/lib/ruby_jard/layouts.rb +52 -0
  65. data/lib/ruby_jard/layouts/narrow_horizontal_layout.rb +32 -0
  66. data/lib/ruby_jard/layouts/narrow_vertical_layout.rb +32 -0
  67. data/lib/ruby_jard/layouts/tiny_layout.rb +29 -0
  68. data/lib/ruby_jard/layouts/wide_layout.rb +50 -0
  69. data/lib/ruby_jard/pager.rb +112 -0
  70. data/lib/ruby_jard/path_classifier.rb +133 -0
  71. data/lib/ruby_jard/path_filter.rb +125 -0
  72. data/lib/ruby_jard/reflection.rb +97 -0
  73. data/lib/ruby_jard/repl_processor.rb +151 -89
  74. data/lib/ruby_jard/repl_proxy.rb +337 -0
  75. data/lib/ruby_jard/row.rb +31 -0
  76. data/lib/ruby_jard/row_renderer.rb +119 -0
  77. data/lib/ruby_jard/screen.rb +14 -41
  78. data/lib/ruby_jard/screen_adjuster.rb +104 -0
  79. data/lib/ruby_jard/screen_drawer.rb +25 -0
  80. data/lib/ruby_jard/screen_manager.rb +167 -82
  81. data/lib/ruby_jard/screen_renderer.rb +152 -0
  82. data/lib/ruby_jard/screens.rb +31 -12
  83. data/lib/ruby_jard/screens/backtrace_screen.rb +118 -116
  84. data/lib/ruby_jard/screens/menu_screen.rb +73 -45
  85. data/lib/ruby_jard/screens/source_screen.rb +86 -106
  86. data/lib/ruby_jard/screens/threads_screen.rb +103 -78
  87. data/lib/ruby_jard/screens/variables_screen.rb +224 -142
  88. data/lib/ruby_jard/session.rb +151 -16
  89. data/lib/ruby_jard/span.rb +23 -0
  90. data/lib/ruby_jard/templates/layout_template.rb +35 -0
  91. data/lib/ruby_jard/templates/screen_template.rb +34 -0
  92. data/lib/ruby_jard/thread_info.rb +69 -0
  93. data/lib/ruby_jard/version.rb +1 -1
  94. data/ruby_jard.gemspec +7 -8
  95. metadata +84 -50
  96. data/.travis.yml +0 -6
  97. data/lib/ruby_jard/commands/finish_command.rb +0 -31
  98. data/lib/ruby_jard/decorators/text_decorator.rb +0 -61
  99. data/lib/ruby_jard/layout_template.rb +0 -101
  100. data/lib/ruby_jard/screens/breakpoints_screen.rb +0 -23
  101. data/lib/ruby_jard/screens/empty_screen.rb +0 -13
  102. data/lib/ruby_jard/screens/expressions_sreen.rb +0 -22
data/Gemfile CHANGED
@@ -1,12 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- source 'https://rubygems.org'
3
+ source 'http://rubygems.org'
4
4
 
5
5
  gemspec
6
6
 
7
- gem 'aruba', '~> 1.0.2'
7
+ if Gem.path.all? { |path| Dir[File.join(path, 'gems/jard_merge_sort*')].empty? }
8
+ puts '[HACK] Install jard_merge_sort'
9
+ puts `gem install ./spec/examples/jard_merge_sort/jard_merge_sort-0.1.0.gem`
10
+ end
11
+
8
12
  gem 'byebug', '~> 11.1.0'
9
- gem 'pry', '~> 0.13.0'
13
+ gem 'jard_merge_sort', require: false
10
14
  gem 'rake', '~> 12.0'
11
15
  gem 'rspec', '~> 3.0'
12
- gem 'rubocop', '~> 0.86'
16
+ gem 'rubocop', '~> 0.89.1'
17
+ gem 'rubocop-rspec', '~> 1.43.1', require: false
18
+
19
+ group :test do
20
+ gem 'parallel_tests'
21
+ gem 'rspec-retry'
22
+ end
data/README.md CHANGED
@@ -1,10 +1,102 @@
1
- # RubyJard
1
+ [<img src="./website/static/img/logo/logo-full.png" width="400" />](https://rubyjard.org/)
2
2
 
3
- Just another ruby debugger. Jard provides an unified experience debugging Ruby source code in different platforms and editors. Jard is heavily under development. So, nothing to see here :smile:. When everything is ready, I'll update this file.
3
+ ![Gem](https://img.shields.io/gem/v/ruby_jard?label=Latest%20version&style=for-the-badge) ![GitHub Workflow Status (branch)](https://img.shields.io/github/workflow/status/nguyenquangminh0711/ruby_jard/Rspec/master?label=Build&style=for-the-badge) ![GitHub stars](https://img.shields.io/github/stars/nguyenquangminh0711/ruby_jard?style=for-the-badge) [![From Vietnam with <3](https://raw.githubusercontent.com/webuild-community/badge/master/svg/love-modern.svg)](https://webuild.community)
4
+
5
+ Ruby Jard provides a rich Terminal UI that visualizes everything your need, navigates your program with pleasure, stops at matter places only, reduces manual and mental efforts. You can now focus on real debugging.
6
+
7
+ Please visit [https://rubyjard.org/](https://rubyjard.org/) for more information.
8
+
9
+ [![RubyJard Demo](https://asciinema.org/a/358874.svg)](https://asciinema.org/a/358874)
10
+
11
+ *[(Click for demo video)](https://asciinema.org/a/358874)*
12
+
13
+ **Note**: Ruby Jard is still under heavy development. Bugs and weird behaviors are expected. If you see one, please don't hesitate to [open an issue](https://github.com/nguyenquangminh0711/ruby_jard/issues). I'll try my best to fix.
14
+
15
+ ## Install Ruby Jard
16
+
17
+ ### Bundler
18
+
19
+ Add one of those lines into your Gemfile. **Note**: Ruby Jard is discouraged to use on production environment.
20
+
21
+ ```ruby
22
+ gem 'ruby_jard', group: :development
23
+ ```
24
+
25
+ ```bash
26
+ ❯ bundle install
27
+ ```
28
+
29
+ If you would like to use Ruby Jard to debug a test, you can add to group test too.
30
+
31
+
32
+ ```ruby
33
+ gem 'ruby_jard', group: [:development, :test]
34
+ ```
35
+
36
+ If you would like to use edged developing version of Ruby Jard:
37
+
38
+ ```ruby
39
+ gem 'ruby_jard', group: :development, git: 'https://github.com/nguyenquangminh0711/ruby_jard'
40
+ ```
41
+
42
+ ### Ruby Gem
43
+
44
+ If you want to install Ruby Jard independently from bundler:
45
+
46
+ ```bash
47
+ gem install ruby_jard
48
+ ```
49
+
50
+ If you want to install a specific version published on [Ruby gems](https://rubygems.org/gems/ruby_jard):
51
+
52
+ ```bash
53
+ gem install ruby_jard@0.2.3
54
+ ```
55
+
56
+ ## Run your program with Ruby Jard
57
+
58
+ ![How to run your program with Ruby Jard](./website/static/img/getting_started/how-to-use.gif)
59
+
60
+ To use Ruby Jard, you just need to put `jard` magic method **before** any places you want to stop. Jard supports stopping at anywhere, including top level binding, instance methods, class methods, string evaluation, or even inside a class declaration.
61
+
62
+ ```ruby
63
+ def test_method(input)
64
+ a = 1
65
+ b = 2
66
+ jard # Debugger will stop here
67
+ c = a + b + input
68
+ end
69
+
70
+ class TestClass
71
+ jard # Yes, it can stop here too
72
+ @dirty_class_method = 1 + 1
73
+
74
+ def test_method
75
+ jard
76
+ end
77
+
78
+ def self.test_class_method
79
+ jard
80
+ end
81
+ end
82
+
83
+ jard
84
+ test_method(5)
85
+ ```
86
+
87
+ Afterward, run your program, just like normally. If your program meets `jard` execution break point, it gonna stop, show the UI, and let you debug.
88
+
89
+ In case you meet error `undefined local variable or method jard`, please require ruby_jard manually at initializing scripts. If you use Ruby Jard with famous frameworks, ruby_jard will be loaded by default
90
+
91
+ ```ruby
92
+ require 'ruby_jard'
93
+ ```
94
+
95
+ Please visit [https://rubyjard.org/](https://rubyjard.org/) for more information.
4
96
 
5
97
  ## Contributing
6
98
 
7
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/ruby_jard. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/ruby_jard/blob/master/CODE_OF_CONDUCT.md).
99
+ Bug reports and pull requests are welcome on GitHub at https://github.com/nguyenquangminh0711/ruby_jard. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/nguyenquangminh0711/ruby_jard/blob/master/CODE_OF_CONDUCT.md).
8
100
 
9
101
 
10
102
  ## License
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'benchmark'
5
+ require 'ruby_jard'
6
+ require 'securerandom'
7
+ require 'jard_merge_sort'
8
+
9
+ ##
10
+ # Benchmark lib/ruby_jard/path_filter.rb
11
+ class PathFilterBench
12
+ include Benchmark
13
+
14
+ def initialize
15
+ config = RubyJard::Config.new
16
+ config.filter = :application
17
+ @path_filter = RubyJard::PathFilter.new(config: config)
18
+ end
19
+
20
+ def execute
21
+ n = 500_000
22
+ file = File.expand_path('../lib/ruby_jard/path_filter.rb')
23
+ gem_file = JardMergeSort::Sorter.instance_method(:sort).source_location[0]
24
+ stdlib_file = SecureRandom.method(:uuid).source_location[0]
25
+
26
+ Benchmark.benchmark(CAPTION, 7, FORMAT) do |x|
27
+ x.report('App file ') { n.times { @path_filter.match?(file) } }
28
+ x.report('Gem file ') { n.times { @path_filter.match?(gem_file) } }
29
+ x.report('Stdlib file ') { n.times { @path_filter.match?(stdlib_file) } }
30
+ x.report('(eval) ') { n.times { @path_filter.match?('(eval)') } }
31
+ x.report('<internal:gc>') { n.times { @path_filter.match?('<internal:gc>') } }
32
+ x.report('nil ') { n.times { @path_filter.match?(nil) } }
33
+ x.report('Other file ') { n.times { @path_filter.match?(file) } }
34
+ x.report('Base line ') { n.times { File.expand_path('abc/def.rb').start_with?('abc/') } }
35
+ end
36
+ end
37
+ end
38
+
39
+ PathFilterBench.new.execute
40
+
41
+ # Commit 9858faa422a35b4a7a13c5ac986e35bf16fb8029
42
+ # App file 4.528750 0.168117 4.696867 ( 4.698018)
43
+ # Gem file 6.003084 0.235965 6.239049 ( 6.239139)
44
+ # Stdlib file 6.198815 0.203962 6.402777 ( 6.402885)
45
+ # (eval) 0.830737 0.000104 0.830841 ( 0.830878)
46
+ # <internal:gc> 0.747695 0.000000 0.747695 ( 0.747713)
47
+ # Other file 4.317924 0.191915 4.509839 ( 4.509897)
48
+ # Base line 0.026612 0.000000 0.026612 ( 0.026614)
49
+
50
+ # Latest
51
+ # App file 1.217831 0.147700 1.365531 ( 1.365815)
52
+ # Gem file 2.569692 0.180220 2.749912 ( 2.750003)
53
+ # Stdlib file 2.450189 0.171845 2.622034 ( 2.622073)
54
+ # (eval) 0.312905 0.000080 0.312985 ( 0.312986)
55
+ # <internal:gc> 0.457472 0.000116 0.457588 ( 0.457589)
56
+ # nil 0.191749 0.000000 0.191749 ( 0.191761)
57
+ # Other file 1.207719 0.143965 1.351684 ( 1.351699)
58
+ # Base line 0.631581 0.143966 0.775547 ( 0.775573)
@@ -11,5 +11,4 @@ require 'ruby_jard'
11
11
  # require "pry"
12
12
  # Pry.start
13
13
 
14
- require 'pry'
15
- Pry.start(__FILE__)
14
+ IRB.start(__FILE__)
@@ -1,24 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'pry'
4
- require 'coderay'
5
4
  require 'byebug/core'
6
5
  require 'byebug/attacher'
7
- require 'tty-cursor'
8
- require 'tty-box'
9
- require 'tty-screen'
10
-
11
- require 'ruby_jard/commands/continue_command'
12
- require 'ruby_jard/commands/up_command'
13
- require 'ruby_jard/commands/down_command'
14
- require 'ruby_jard/commands/next_command'
15
- require 'ruby_jard/commands/step_command'
16
- require 'ruby_jard/commands/finish_command'
17
- require 'ruby_jard/commands/frame_command'
6
+ require 'forwardable'
7
+ require 'benchmark'
18
8
 
9
+ require 'ruby_jard/path_classifier'
10
+ require 'ruby_jard/path_filter'
11
+ require 'ruby_jard/control_flow'
12
+ require 'ruby_jard/config'
13
+ require 'ruby_jard/keys'
14
+ require 'ruby_jard/key_binding'
15
+ require 'ruby_jard/key_bindings'
16
+ require 'ruby_jard/repl_proxy'
19
17
  require 'ruby_jard/repl_processor'
20
18
  require 'ruby_jard/screen_manager'
19
+ require 'ruby_jard/reflection'
21
20
 
21
+ require 'ruby_jard/frame'
22
+ require 'ruby_jard/thread_info'
22
23
  require 'ruby_jard/session'
23
24
  require 'ruby_jard/version'
24
25
 
@@ -46,8 +47,56 @@ require 'ruby_jard/version'
46
47
  module RubyJard
47
48
  class Error < StandardError; end
48
49
 
49
- def self.current_session
50
- @current_session ||= RubyJard::Session.new
50
+ def self.benchmark(name)
51
+ @benchmark_depth ||= 0
52
+ @benchmark_depth += 1
53
+ return_value = nil
54
+ time = Benchmark.realtime { return_value = yield }
55
+ debug("#{' ' * @benchmark_depth}Benchmark `#{name}`: #{time}")
56
+ @benchmark_depth -= 1
57
+ return_value
58
+ end
59
+
60
+ def self.debug(*info)
61
+ @debug_info ||= []
62
+ @debug_info += info
63
+ File.open('./jard_debugs.txt', 'a') do |f|
64
+ info.each do |line|
65
+ f.puts line
66
+ end
67
+ end
68
+ end
69
+
70
+ def self.error(exception)
71
+ File.open('./jard_errors.txt', 'a') do |f|
72
+ f.puts '--- Error ---'
73
+ f.puts exception.message
74
+ f.puts exception.backtrace
75
+ end
76
+ rescue StandardError
77
+ # Ignore
78
+ end
79
+
80
+ def self.debug_info
81
+ @debug_info ||= []
82
+ end
83
+
84
+ def self.clear_debug
85
+ @debug_info = []
86
+ end
87
+
88
+ def self.global_key_bindings
89
+ return @global_key_bindings if defined?(@global_key_bindings)
90
+
91
+ @global_key_bindings = RubyJard::KeyBindings.new
92
+ RubyJard::Keys::DEFAULT_KEY_BINDINGS.each do |sequence, action|
93
+ @global_key_bindings.push(sequence, action)
94
+ end
95
+ @global_key_bindings
96
+ end
97
+
98
+ def self.config
99
+ @config ||= RubyJard::Config.smart_load
51
100
  end
52
101
  end
53
102
 
@@ -55,23 +104,10 @@ end
55
104
  # Monkey-patch Kernel module to allow putting jard command anywhere.
56
105
  module Kernel
57
106
  def jard
58
- RubyJard.current_session.attach
107
+ RubyJard::Session.instance.attach
59
108
  end
60
- end
61
109
 
62
- ##
63
- # Globally configure Byebug. Byebug doesn't allow configuration by instance.
64
- # So, I have no choice.
65
- # TODO: Byebug autoloaded configuration may override those values.
66
- Byebug::Setting[:autolist] = false
67
- Byebug::Setting[:autoirb] = false
68
- Byebug::Setting[:autopry] = false
69
- Byebug::Context.processor = RubyJard::ReplProcessor
70
- # Exclude all files in Ruby Jard source code from the stacktrace.
71
- Byebug::Context.ignored_files = Byebug::Context.all_files + Dir.glob(
72
- File.join(
73
- File.expand_path(__dir__, '../lib'),
74
- '**',
75
- '*.rb'
76
- )
77
- )
110
+ if RubyJard.config.alias_to_debugger
111
+ alias_method :debugger, :jard
112
+ end
113
+ end
@@ -0,0 +1,175 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyJard
4
+ ##
5
+ # Drawer to draw a nice single-line box that maximize screen area
6
+ #
7
+ # Each screen has 4 corners and clock-wise corresponding ID:
8
+ # - Top-left => 1
9
+ # - Top-right => 2
10
+ # - Bottom-right => 3
11
+ # - Bottom-left => 4
12
+ #
13
+ # For each screen, add each point to a coordinator hash.
14
+ # - If a point is occupied by 1 screen, draw normal box corner symbol.
15
+ # - If a point is occupied by 2 screens, look up in a map, and draw corresponding intersection corner symbol.
16
+ # - If a point is occupied by 3 or more screens, it's definitely a + symbol.
17
+ #
18
+ # The corner list at each point (x, y) is unique. If 2 screens overlap a point with same corner ID, it means
19
+ # 2 screens overlap, and have same corner symbol.
20
+ class BoxDrawer
21
+ CORNERS = [
22
+ TOP_LEFT = 1,
23
+ TOP_RIGHT = 2,
24
+ BOTTOM_RIGHT = 3,
25
+ BOTTOM_LEFT = 4
26
+ ].freeze
27
+
28
+ HORIZONTAL_LINE = '─'
29
+ VERTICAL_LINE = '│'
30
+ CROSS_CORNER = '┼'
31
+
32
+ NORMALS_CORNERS = {
33
+ TOP_LEFT => '┌',
34
+ TOP_RIGHT => '┐',
35
+ BOTTOM_RIGHT => '┘',
36
+ BOTTOM_LEFT => '└'
37
+ }.freeze
38
+
39
+ OVERLAPPED_CORNERS = {
40
+ [TOP_LEFT, TOP_RIGHT] => '┬',
41
+ [TOP_LEFT, BOTTOM_RIGHT] => '┼',
42
+ [TOP_LEFT, BOTTOM_LEFT] => '├',
43
+ [TOP_RIGHT, BOTTOM_RIGHT] => '┤',
44
+ [TOP_RIGHT, BOTTOM_LEFT] => '┼',
45
+ [BOTTOM_RIGHT, BOTTOM_LEFT] => '┴'
46
+ }.freeze
47
+
48
+ def initialize(output:, screens:, color_scheme:)
49
+ @output = output
50
+ @screens = screens
51
+ @color_decorator = RubyJard::Decorators::ColorDecorator.new(color_scheme)
52
+ end
53
+
54
+ def draw
55
+ draw_basic_lines
56
+ corners = calculate_corners
57
+ draw_corners(corners)
58
+ draw_titles
59
+ end
60
+
61
+ private
62
+
63
+ def draw_basic_lines
64
+ # Exclude the corners
65
+ @screens.each do |screen|
66
+ RubyJard::Console.move_to(
67
+ @output,
68
+ screen.layout.box_x + 1,
69
+ screen.layout.box_y
70
+ )
71
+ @output.print colorize_border(HORIZONTAL_LINE * (screen.layout.box_width - 2))
72
+
73
+ RubyJard::Console.move_to(
74
+ @output,
75
+ screen.layout.box_x + 1,
76
+ screen.layout.box_y + screen.layout.box_height - 1
77
+ )
78
+ @output.print colorize_border(HORIZONTAL_LINE * (screen.layout.box_width - 2))
79
+
80
+ (screen.layout.box_y + 1..screen.layout.box_y + screen.layout.box_height - 2).each do |moving_y|
81
+ RubyJard::Console.move_to(@output, screen.layout.box_x, moving_y)
82
+ @output.print colorize_border(VERTICAL_LINE)
83
+
84
+ RubyJard::Console.move_to(@output, screen.layout.box_x + screen.layout.box_width - 1, moving_y)
85
+ @output.print colorize_border(VERTICAL_LINE)
86
+ end
87
+ end
88
+ end
89
+
90
+ def draw_corners(corners)
91
+ corners.each do |x, corners_x|
92
+ corners_x.each do |y, ids|
93
+ RubyJard::Console.move_to(@output, x, y)
94
+
95
+ case ids.length
96
+ when 1
97
+ @output.print colorize_border(NORMALS_CORNERS[ids.first])
98
+ when 2
99
+ ids = ids.sort
100
+ @output.print colorize_border(OVERLAPPED_CORNERS[ids])
101
+ else
102
+ @output.print colorize_border(CROSS_CORNER)
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ def draw_titles
109
+ @screens.each do |screen|
110
+ next unless screen.respond_to?(:title)
111
+
112
+ RubyJard::Console.move_to(@output, screen.layout.box_x + 1, screen.layout.box_y)
113
+ total_length = 0
114
+ title_parts = Array(screen.title)
115
+ title_parts.each_with_index do |title_part, index|
116
+ break if total_length >= screen.layout.box_width
117
+
118
+ title_part = title_part[0..screen.layout.box_width - total_length - 2 - 1 - 2]
119
+ if index == 0
120
+ @output.print @color_decorator.decorate(:title, " #{title_part} ")
121
+ else
122
+ @output.print @color_decorator.decorate(:title_secondary, " #{title_part} ")
123
+ end
124
+ total_length += title_part.length + 2
125
+ end
126
+ title_background = screen.layout.box_width - total_length - 2
127
+ @output.print @color_decorator.decorate(
128
+ :title_background,
129
+ HORIZONTAL_LINE * (title_background < 0 ? 0 : title_background)
130
+ )
131
+ end
132
+ end
133
+
134
+ def calculate_corners
135
+ corners = {}
136
+ @screens.each do |screen|
137
+ mark_corner(
138
+ corners,
139
+ screen.layout.box_x,
140
+ screen.layout.box_y,
141
+ TOP_LEFT
142
+ )
143
+ mark_corner(
144
+ corners,
145
+ screen.layout.box_x + screen.layout.box_width - 1,
146
+ screen.layout.box_y,
147
+ TOP_RIGHT
148
+ )
149
+ mark_corner(
150
+ corners,
151
+ screen.layout.box_x + screen.layout.box_width - 1,
152
+ screen.layout.box_y + screen.layout.box_height - 1,
153
+ BOTTOM_RIGHT
154
+ )
155
+ mark_corner(
156
+ corners,
157
+ screen.layout.box_x,
158
+ screen.layout.box_y + screen.layout.box_height - 1,
159
+ BOTTOM_LEFT
160
+ )
161
+ end
162
+ corners
163
+ end
164
+
165
+ def mark_corner(corners, x, y, id)
166
+ corners[x] ||= {}
167
+ corners[x][y] ||= []
168
+ corners[x][y] << id unless corners[x][y].include?(id)
169
+ end
170
+
171
+ def colorize_border(content)
172
+ @color_decorator.decorate(:border, content)
173
+ end
174
+ end
175
+ end