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.
- checksums.yaml +4 -4
- data/.github/FUNDING.yml +3 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +32 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- data/.github/workflows/documentation.yml +65 -0
- data/.github/workflows/rspec.yml +96 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +90 -2
- data/CHANGELOG.md +112 -0
- data/Gemfile +14 -4
- data/README.md +95 -3
- data/benchmark/path_filter_bench.rb +58 -0
- data/bin/console +1 -2
- data/lib/ruby_jard.rb +68 -32
- data/lib/ruby_jard/box_drawer.rb +175 -0
- data/lib/ruby_jard/color_scheme.rb +28 -0
- data/lib/ruby_jard/color_schemes.rb +54 -0
- data/lib/ruby_jard/color_schemes/256_color_scheme.rb +50 -0
- data/lib/ruby_jard/color_schemes/256_light_color_scheme.rb +50 -0
- data/lib/ruby_jard/color_schemes/deep_space_color_scheme.rb +49 -0
- data/lib/ruby_jard/color_schemes/gruvbox_color_scheme.rb +48 -0
- data/lib/ruby_jard/color_schemes/one_half_dark_color_scheme.rb +47 -0
- data/lib/ruby_jard/color_schemes/one_half_light_color_scheme.rb +49 -0
- data/lib/ruby_jard/column.rb +26 -0
- data/lib/ruby_jard/commands/color_helpers.rb +32 -0
- data/lib/ruby_jard/commands/continue_command.rb +4 -9
- data/lib/ruby_jard/commands/down_command.rb +9 -8
- data/lib/ruby_jard/commands/exit_command.rb +27 -0
- data/lib/ruby_jard/commands/frame_command.rb +13 -11
- data/lib/ruby_jard/commands/jard/color_scheme_command.rb +74 -0
- data/lib/ruby_jard/commands/jard/filter_command.rb +136 -0
- data/lib/ruby_jard/commands/jard/hide_command.rb +40 -0
- data/lib/ruby_jard/commands/jard/output_command.rb +36 -0
- data/lib/ruby_jard/commands/jard/show_command.rb +41 -0
- data/lib/ruby_jard/commands/jard_command.rb +52 -0
- data/lib/ruby_jard/commands/list_command.rb +31 -0
- data/lib/ruby_jard/commands/next_command.rb +11 -8
- data/lib/ruby_jard/commands/step_command.rb +11 -8
- data/lib/ruby_jard/commands/step_out_command.rb +34 -0
- data/lib/ruby_jard/commands/up_command.rb +10 -8
- data/lib/ruby_jard/commands/validation_helpers.rb +50 -0
- data/lib/ruby_jard/config.rb +61 -0
- data/lib/ruby_jard/console.rb +158 -0
- data/lib/ruby_jard/control_flow.rb +73 -0
- data/lib/ruby_jard/decorators/array_decorator.rb +79 -0
- data/lib/ruby_jard/decorators/attributes_decorator.rb +172 -0
- data/lib/ruby_jard/decorators/color_decorator.rb +80 -0
- data/lib/ruby_jard/decorators/hash_decorator.rb +74 -0
- data/lib/ruby_jard/decorators/inspection_decorator.rb +109 -0
- data/lib/ruby_jard/decorators/loc_decorator.rb +108 -119
- data/lib/ruby_jard/decorators/object_decorator.rb +122 -0
- data/lib/ruby_jard/decorators/path_decorator.rb +56 -60
- data/lib/ruby_jard/decorators/rails_decorator.rb +194 -0
- data/lib/ruby_jard/decorators/source_decorator.rb +3 -1
- data/lib/ruby_jard/decorators/string_decorator.rb +41 -0
- data/lib/ruby_jard/decorators/struct_decorator.rb +79 -0
- data/lib/ruby_jard/frame.rb +68 -0
- data/lib/ruby_jard/key_binding.rb +14 -0
- data/lib/ruby_jard/key_bindings.rb +96 -0
- data/lib/ruby_jard/keys.rb +48 -0
- data/lib/ruby_jard/layout.rb +17 -88
- data/lib/ruby_jard/layout_calculator.rb +168 -0
- data/lib/ruby_jard/layout_picker.rb +34 -0
- data/lib/ruby_jard/layouts.rb +52 -0
- data/lib/ruby_jard/layouts/narrow_horizontal_layout.rb +32 -0
- data/lib/ruby_jard/layouts/narrow_vertical_layout.rb +32 -0
- data/lib/ruby_jard/layouts/tiny_layout.rb +29 -0
- data/lib/ruby_jard/layouts/wide_layout.rb +50 -0
- data/lib/ruby_jard/pager.rb +112 -0
- data/lib/ruby_jard/path_classifier.rb +133 -0
- data/lib/ruby_jard/path_filter.rb +125 -0
- data/lib/ruby_jard/reflection.rb +97 -0
- data/lib/ruby_jard/repl_processor.rb +151 -89
- data/lib/ruby_jard/repl_proxy.rb +337 -0
- data/lib/ruby_jard/row.rb +31 -0
- data/lib/ruby_jard/row_renderer.rb +119 -0
- data/lib/ruby_jard/screen.rb +14 -41
- data/lib/ruby_jard/screen_adjuster.rb +104 -0
- data/lib/ruby_jard/screen_drawer.rb +25 -0
- data/lib/ruby_jard/screen_manager.rb +167 -82
- data/lib/ruby_jard/screen_renderer.rb +152 -0
- data/lib/ruby_jard/screens.rb +31 -12
- data/lib/ruby_jard/screens/backtrace_screen.rb +118 -116
- data/lib/ruby_jard/screens/menu_screen.rb +73 -45
- data/lib/ruby_jard/screens/source_screen.rb +86 -106
- data/lib/ruby_jard/screens/threads_screen.rb +103 -78
- data/lib/ruby_jard/screens/variables_screen.rb +224 -142
- data/lib/ruby_jard/session.rb +151 -16
- data/lib/ruby_jard/span.rb +23 -0
- data/lib/ruby_jard/templates/layout_template.rb +35 -0
- data/lib/ruby_jard/templates/screen_template.rb +34 -0
- data/lib/ruby_jard/thread_info.rb +69 -0
- data/lib/ruby_jard/version.rb +1 -1
- data/ruby_jard.gemspec +7 -8
- metadata +84 -50
- data/.travis.yml +0 -6
- data/lib/ruby_jard/commands/finish_command.rb +0 -31
- data/lib/ruby_jard/decorators/text_decorator.rb +0 -61
- data/lib/ruby_jard/layout_template.rb +0 -101
- data/lib/ruby_jard/screens/breakpoints_screen.rb +0 -23
- data/lib/ruby_jard/screens/empty_screen.rb +0 -13
- 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 '
|
3
|
+
source 'http://rubygems.org'
|
4
4
|
|
5
5
|
gemspec
|
6
6
|
|
7
|
-
|
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 '
|
13
|
+
gem 'jard_merge_sort', require: false
|
10
14
|
gem 'rake', '~> 12.0'
|
11
15
|
gem 'rspec', '~> 3.0'
|
12
|
-
gem 'rubocop', '~> 0.
|
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
|
-
|
1
|
+
[<img src="./website/static/img/logo/logo-full.png" width="400" />](https://rubyjard.org/)
|
2
2
|
|
3
|
-
|
3
|
+
   [](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
|
+
[](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
|
+

|
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/
|
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)
|
data/bin/console
CHANGED
data/lib/ruby_jard.rb
CHANGED
@@ -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 '
|
8
|
-
require '
|
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.
|
50
|
-
@
|
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.
|
107
|
+
RubyJard::Session.instance.attach
|
59
108
|
end
|
60
|
-
end
|
61
109
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
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
|