ruby_jard 0.3.0 → 0.3.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e5a458f6dbabbed48b8cb322d6ac9a3c52e664309b1a1bfde1e90f91921092b6
4
- data.tar.gz: e54b2ddcebc06c72fd78b4cc34dc99aba60eb1ba7d20d6815e0c4d88c39d2076
3
+ metadata.gz: 00607fcc7c6b4de0a2bb272624357ef7f9c8e3a0aa4a46783f127b094fc0a6f9
4
+ data.tar.gz: 6bd10f20ba123f290b786a875eb5879cf343e126a1a31d420387691e7ee6d5e5
5
5
  SHA512:
6
- metadata.gz: '079110d831bc4729fe3c5ac9a33726d396cd4df50b889890f5b7e602d7e8d6717cf6e48b6038c4e653e2de2ae2ae4d44da6a0790d6f63be67e573475d59a0980'
7
- data.tar.gz: fe4711967e8d0f3a81962038b1d7d40b319934f323ed6d942bde0f918125971e76bc49a73c603347c4e6be07618bf3950236ef0e868b07c587a2d1583cf7cbe6
6
+ metadata.gz: 64ca81f056c51fd717cb1b1caae5f8490bb71ea8f5b93b587451ab8115e546fea617eb0eb0d4357a281c255ff862d33a024ccae0301a3148800dff1332242662
7
+ data.tar.gz: b1063583e9f8c31d9ec3e092e8d5e99b339b3d016b2516d2c4e9b27843071099916f8444c5bc648ba506e00d794bdecc030c06b838895385aff61981d696a6ca
@@ -0,0 +1,11 @@
1
+ # To get started with Dependabot version updates, you'll need to specify which
2
+ # package ecosystems to update and where the package manifests are located.
3
+ # Please see the documentation for all configuration options:
4
+ # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5
+
6
+ version: 2
7
+ updates:
8
+ - package-ecosystem: "bundler" # See documentation for possible values
9
+ directory: "/" # Location of package manifests
10
+ schedule:
11
+ interval: "daily"
@@ -32,8 +32,17 @@ jobs:
32
32
  uses: ruby/setup-ruby@v1
33
33
  with:
34
34
  ruby-version: ${{ matrix.ruby }}
35
+ - uses: actions/checkout@v2
36
+ with:
37
+ repository: tmux/tmux
38
+ path: 'tmux'
39
+ ref: '3.1b'
40
+ - name: Install dependencies for tmux
41
+ run: sudo apt install -y libevent-dev
35
42
  - name: Install tmux
36
- run: sudo apt install -y tmux
43
+ run: cd tmux && sh autogen.sh && ./configure && make && sudo make install
44
+ - name: Tmux version
45
+ run: tmux -V
37
46
  - name: Start tmux
38
47
  run: tmux start-server
39
48
  - name: Start dummy tmux session
@@ -42,6 +51,8 @@ jobs:
42
51
  run: ruby spec/wait_for_tmux.rb
43
52
  - name: Install dependencies
44
53
  run: bundle install
54
+ - name: Kill tmux
55
+ run: tmux kill-server || true
45
56
  - name: Run tests
46
57
  run: bundle exec parallel_rspec spec/
47
58
  test-macos:
@@ -69,28 +80,39 @@ jobs:
69
80
  - name: Run tests
70
81
  run: bundle exec parallel_rspec -n 2 spec/
71
82
  test-byebug:
72
- runs-on: ubuntu-latest
73
83
  strategy:
74
84
  fail-fast: false
75
85
  matrix:
76
86
  byebug: [9.1.0, 10.0.2]
77
87
  env:
78
88
  BUNDLE_GEMFILE: "./spec/gemfiles/Gemfile-byebug-${{ matrix.byebug }}"
89
+ runs-on: ubuntu-latest
79
90
  steps:
80
91
  - uses: actions/checkout@v2
81
92
  - name: Set up Ruby
82
93
  uses: ruby/setup-ruby@v1
83
94
  with:
84
95
  ruby-version: 2.5
96
+ - uses: actions/checkout@v2
97
+ with:
98
+ repository: tmux/tmux
99
+ path: 'tmux'
100
+ ref: '3.1b'
101
+ - name: Install dependencies for tmux
102
+ run: sudo apt install -y libevent-dev
85
103
  - name: Install tmux
86
- run: sudo apt install -y tmux
104
+ run: cd tmux && sh autogen.sh && ./configure && make && sudo make install
105
+ - name: Tmux version
106
+ run: tmux -V
87
107
  - name: Start tmux
88
108
  run: tmux start-server
89
- - name: start dummy tmux session
109
+ - name: Start dummy tmux session
90
110
  run: tmux new-session -t dummy -d
91
111
  - name: Wait for tmux
92
112
  run: ruby spec/wait_for_tmux.rb
93
113
  - name: Install dependencies
94
114
  run: bundle install
115
+ - name: Kill tmux
116
+ run: tmux kill-server || true
95
117
  - name: Run tests
96
118
  run: bundle exec parallel_rspec spec/
data/.gitignore CHANGED
@@ -11,3 +11,6 @@
11
11
  .rspec_status
12
12
  .ruby-version
13
13
  Gemfile.lock
14
+ **/.byebug_history
15
+ .byebug_history
16
+ coverage
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.1]
4
+ This release fixes bunch of bugs, and performance issues reported by the users after beta launch. No new features are introduced.
5
+
6
+ - Pry and Byebug backward compatibility: [#39](https://github.com/nguyenquangminh0711/ruby_jard/issues/39), [#45](https://github.com/nguyenquangminh0711/ruby_jard/issues/45)
7
+ - Error with non-UTF8 encoding in the output: [#55](https://github.com/nguyenquangminh0711/ruby_jard/issues/55)
8
+ - Ctrl+D not working: [#34](https://github.com/nguyenquangminh0711/ruby_jard/issues/34)
9
+ - Errors if putting jard with `<%= jard %>` in ERB: [#35](https://github.com/nguyenquangminh0711/ruby_jard/issues/35)
10
+ - Handle standard stream redirections, and prevent Jard from attachment in invalid TTY device: [#38](https://github.com/nguyenquangminh0711/ruby_jard/issues/38), [#46](https://github.com/nguyenquangminh0711/ruby_jard/issues/46), [#53](https://github.com/nguyenquangminh0711/ruby_jard/issues/53)
11
+ - Bring back auto-resize when window size changes: [#40](https://github.com/nguyenquangminh0711/ruby_jard/issues/40)
12
+ - Improve performance after `exit` command: [#49](https://github.com/nguyenquangminh0711/ruby_jard/issues/49)
13
+ - Handle edge cases in Jard color decorator: [#54](https://github.com/nguyenquangminh0711/ruby_jard/issues/54)
14
+ - Escape all special characters and line feeds before printing stuff into the screen: [#57](https://github.com/nguyenquangminh0711/ruby_jard/issues/57)
15
+
3
16
  ## [0.3.0 - Beta 1]
4
17
  - Filter feature
5
18
  - New variable screen look and feel
data/Gemfile CHANGED
@@ -17,6 +17,9 @@ gem 'rubocop', '~> 0.89.1'
17
17
  gem 'rubocop-rspec', '~> 1.43.1', require: false
18
18
 
19
19
  group :test do
20
+ gem 'activerecord'
20
21
  gem 'parallel_tests'
21
22
  gem 'rspec-retry'
23
+ gem 'simplecov', require: false
24
+ gem 'sqlite3'
22
25
  end
@@ -14,7 +14,6 @@ require 'ruby_jard/keys'
14
14
  require 'ruby_jard/key_binding'
15
15
  require 'ruby_jard/key_bindings'
16
16
  require 'ruby_jard/repl_proxy'
17
- require 'ruby_jard/repl_processor'
18
17
  require 'ruby_jard/screen_manager'
19
18
  require 'ruby_jard/reflection'
20
19
 
@@ -98,13 +97,20 @@ module RubyJard
98
97
  def self.config
99
98
  @config ||= RubyJard::Config.smart_load
100
99
  end
100
+
101
+ def self.all_files
102
+ Dir.glob(File.join(File.expand_path(__dir__, './lib'), '**', '*.rb')) +
103
+ Dir.glob(File.join(File.expand_path(__dir__, './lib'), '*.rb')) +
104
+ Dir.glob(File.join(File.expand_path(__dir__, './bin'), '**', '*.rb')) +
105
+ Dir.glob(File.join(File.expand_path(__dir__, './bin'), '*.rb'))
106
+ end
101
107
  end
102
108
 
103
109
  ##
104
110
  # Monkey-patch Kernel module to allow putting jard command anywhere.
105
111
  module Kernel
106
112
  def jard
107
- RubyJard::Session.instance.attach
113
+ RubyJard::Session.attach
108
114
  end
109
115
 
110
116
  if RubyJard.config.alias_to_debugger
@@ -14,7 +14,7 @@ module RubyJard
14
14
  Examples:
15
15
  continue
16
16
 
17
- Continue program execution. The program will stop at the next breakpoint, or run until it finishes.
17
+ Continue the execution of your program to the end, or stop at the first dynamic break point or `jard` attachment command.
18
18
  BANNER
19
19
 
20
20
  def process
@@ -14,7 +14,7 @@ module RubyJard
14
14
  Examples:
15
15
  exit
16
16
 
17
- Exit program execution. The program will stop at the next breakpoint, or run until it finishes.
17
+ Exit the execution of the program. Interally, when `jard` receives this command, it removes all debugging hooks, and triggers `::Kernel.exit`. Some long-running processes like `puma` or `sidekiq` may capture this event, treat it as an error, and recover to keep the processes running. In such cases, it's recommended to use `continue` command instead.
18
18
  BANNER
19
19
 
20
20
  def process
@@ -19,6 +19,7 @@ module RubyJard
19
19
  end
20
20
 
21
21
  def process
22
+ sleep 0.25 # Cool down
22
23
  pry_instance.pager.open(
23
24
  force_open: true,
24
25
  pager_start_at_the_end: true,
@@ -8,6 +8,14 @@ module RubyJard
8
8
  def smart_load
9
9
  config = RubyJard::Config.new
10
10
 
11
+ unless ENV['JARD_CONFIG_FILE'].nil?
12
+ unless File.exist?(ENV['JARD_CONFIG_FILE'])
13
+ raise "Config file '#{ENV['JARD_CONFIG_FILE']}' does not exist"
14
+ end
15
+
16
+ return load_config(config, ENV['JARD_CONFIG_FILE'])
17
+ end
18
+
11
19
  path = File.expand_path(File.join(Dir.pwd, CONFIG_FILE_NAME))
12
20
  load_config(config, path) if File.exist?(path)
13
21
 
@@ -15,10 +23,6 @@ module RubyJard
15
23
  load_config(config, path) if File.exist?(path)
16
24
 
17
25
  config
18
- rescue StandardError => e
19
- # Fallback to default setting
20
- STDOUT.puts "Fail to load jard configurations at #{path}. Error: #{e}"
21
- RubyJard::Config.new
22
26
  end
23
27
 
24
28
  private
@@ -28,6 +32,9 @@ module RubyJard
28
32
  config.instance_eval(config_content)
29
33
 
30
34
  config
35
+ rescue SyntaxError, StandardError => e
36
+ # Fallback to default setting
37
+ raise "Fail to load jard configurations at #{path}. Error: #{e}"
31
38
  end
32
39
  end
33
40
 
@@ -8,22 +8,45 @@ module RubyJard
8
8
  # Wrapper for utilities to control screen
9
9
  class Console
10
10
  class << self
11
- def start_alternative_terminal(output)
12
- return unless output.tty?
11
+ def attachable?
12
+ return false unless output.tty?
13
13
 
14
- output.print tput('smcup')
15
- rescue RubyJard::Error
16
- # If tput not found, fallback to hard-coded sequence.
17
- output.print "\e[?1049h\e[22;0;0t"
14
+ width, height = screen_size(output)
15
+ width != 0 && height != 0
18
16
  end
19
17
 
20
- def stop_alternative_terminal(output)
21
- return unless output.tty?
18
+ def redirected?
19
+ output != $stdout
20
+ end
22
21
 
23
- output.print tput('rmcup')
24
- rescue RubyJard::Error
25
- # If tput not found, fallback to hard-coded sequence.
26
- output.print "\e[?1049l\e[23;0;0t"
22
+ def output
23
+ return @output if defined?(@output)
24
+
25
+ @output =
26
+ if STDOUT.tty?
27
+ STDOUT
28
+ else
29
+ begin
30
+ File.open('/dev/tty', 'w+')
31
+ rescue StandardError
32
+ STDOUT # Give up now.
33
+ end
34
+ end
35
+ end
36
+
37
+ def input
38
+ return @input if defined?(@input)
39
+
40
+ @input =
41
+ if STDIN.tty?
42
+ STDIN
43
+ else
44
+ begin
45
+ File.open('/dev/tty', 'r+')
46
+ rescue StandardError
47
+ STDIN # Give up.
48
+ end
49
+ end
27
50
  end
28
51
 
29
52
  def move_to(output, x, y)
@@ -35,7 +58,12 @@ module RubyJard
35
58
  def screen_size(output)
36
59
  return [0, 0] unless output.tty?
37
60
 
38
- [TTY::Screen.width, TTY::Screen.height]
61
+ if output.respond_to?(:winsize)
62
+ height, width = output.winsize
63
+ [width, height]
64
+ else
65
+ [TTY::Screen.width, TTY::Screen.height]
66
+ end
39
67
  end
40
68
 
41
69
  def clear_screen(output)
@@ -50,7 +78,7 @@ module RubyJard
50
78
  output.print "\e[0J"
51
79
  end
52
80
 
53
- def disable_cursor!(output = STDOUT)
81
+ def disable_cursor!(output)
54
82
  return unless output.tty?
55
83
 
56
84
  output.print tput('civis')
@@ -59,7 +87,7 @@ module RubyJard
59
87
  output.print "\e[?25l"
60
88
  end
61
89
 
62
- def enable_cursor!(output = STDOUT)
90
+ def enable_cursor!(output)
63
91
  return unless output.tty?
64
92
 
65
93
  output.print tput('cnorm')
@@ -81,7 +109,7 @@ module RubyJard
81
109
  nil
82
110
  end
83
111
 
84
- def raw!(output = STDOUT)
112
+ def raw!(output)
85
113
  return unless output.tty?
86
114
 
87
115
  begin
@@ -91,7 +119,7 @@ module RubyJard
91
119
  end
92
120
  end
93
121
 
94
- def cooked!(output = STDOUT)
122
+ def cooked!(output)
95
123
  return unless output.tty?
96
124
 
97
125
  begin
@@ -102,7 +130,7 @@ module RubyJard
102
130
  end
103
131
  end
104
132
 
105
- def disable_echo!(output = STDOUT)
133
+ def disable_echo!(output)
106
134
  return unless output.tty?
107
135
 
108
136
  begin
@@ -113,7 +141,7 @@ module RubyJard
113
141
  end
114
142
  end
115
143
 
116
- def enable_echo!(output = STDOUT)
144
+ def enable_echo!(output)
117
145
  return unless output.tty?
118
146
 
119
147
  begin
@@ -21,7 +21,7 @@ module RubyJard
21
21
 
22
22
  attr_reader :command, :arguments
23
23
 
24
- def initialize(command, arguments)
24
+ def initialize(command, arguments = {})
25
25
  @command = command
26
26
  @arguments = arguments
27
27
 
@@ -37,17 +37,28 @@ module RubyJard
37
37
 
38
38
  def decorate(style_names, content)
39
39
  attributes = nil
40
+ foreground = nil
41
+ background = nil
42
+
40
43
  if style_names.is_a?(Symbol)
41
44
  styles = @color_scheme.styles_for(style_names)
42
45
  else
43
46
  attributes = translate_styles(style_names)
44
47
  styles = @color_scheme.styles_for(style_names.shift)
45
48
  end
46
- foreground = translate_color(styles.shift, true)
47
- background = translate_color(styles.shift, false)
49
+
50
+ if styles.is_a?(Array)
51
+ foreground = translate_color(styles.shift, true)
52
+ background = translate_color(styles.shift, false)
53
+ elsif !styles.nil?
54
+ raise RubyJard::Error, "Styles for #{style_names.inspect} must be an array"
55
+ end
56
+
48
57
  "#{foreground}#{background}#{attributes}#{content}#{CSI_RESET}"
49
58
  end
50
59
 
60
+ private
61
+
51
62
  def translate_color(color, foreground)
52
63
  if (matches = HEX_PATTERN_6.match(color.to_s))
53
64
  red = matches[1].to_i(16)
@@ -70,8 +81,6 @@ module RubyJard
70
81
  end
71
82
  end
72
83
 
73
- private
74
-
75
84
  def translate_styles(styles = [])
76
85
  styles.map { |key| STYLES_CSI_MAP[key] }.compact.join
77
86
  end
@@ -89,7 +89,9 @@ module RubyJard
89
89
  if variable.respond_to?(:loaded?) && variable.loaded?
90
90
  spans = []
91
91
  label = RubyJard::Span.new(
92
- content: RubyJard::Reflection.call_to_s(variable).chomp('>'), styles: :text_primary
92
+ content: RubyJard::Reflection.call_to_s(variable).chomp('>'),
93
+ styles: :text_primary,
94
+ margin_right: variable.length >= 1 ? 1 : 0
93
95
  )
94
96
  spans << label
95
97
  spans += @attributes_decorator.inline_values(
@@ -99,6 +101,10 @@ module RubyJard
99
101
  )
100
102
  spans << RubyJard::Span.new(content: '>', styles: :text_primary)
101
103
 
104
+ if variable.length <= 0
105
+ spans << RubyJard::Span.new(content: '(empty)', margin_left: 1, styles: :text_primary)
106
+ end
107
+
102
108
  spans
103
109
  else
104
110
  relation_summary(variable, line_limit)
@@ -138,7 +144,7 @@ module RubyJard
138
144
  width = overview.length + 1 + 12
139
145
  spans = [RubyJard::Span.new(content: overview, styles: :text_primary)]
140
146
  if RubyJard::Reflection.call_respond_to?(variable, :to_sql) && width < line_limit
141
- detail = variable.to_sql
147
+ detail = variable.to_sql.inspect
142
148
  detail = detail[0..line_limit - width - 2] + '…' if width + detail.length < line_limit
143
149
  spans << RubyJard::Span.new(content: detail, styles: :text_dim, margin_left: 1)
144
150
  end
@@ -42,7 +42,8 @@ module RubyJard
42
42
  F7 => (ACTION_STEP = :step),
43
43
  SHIFT_F7 => (ACTION_STEP_OUT = :step_out),
44
44
  F8 => (ACTION_NEXT = :next),
45
- F9 => (ACTION_CONTINUE = :continue)
45
+ F9 => (ACTION_CONTINUE = :continue),
46
+ CTRL_D => ACTION_CONTINUE
46
47
  }.freeze
47
48
  end
48
49
  end
@@ -46,7 +46,13 @@ module RubyJard
46
46
  @pager_start_at_the_end = pager_start_at_the_end
47
47
  @prompt = prompt
48
48
 
49
- @window_width, @window_height = RubyJard::Console.screen_size(@pry_instance.output)
49
+ # There are two cases:
50
+ # - If the real pager (less) is triggered, it works on a real tty (fetched
51
+ # from /dev/tty), in which, the same as RubyJard::Console.output
52
+ # - Otherwise, it writes directly into pry's REPL output.
53
+ # That's why there should be two output here
54
+ @tty_output = RubyJard::Console.redirected? ? RubyJard::Console.output : pry_instance.output
55
+ @window_width, @window_height = RubyJard::Console.screen_size(RubyJard::Console.output)
50
56
  @tracker = JardPageTracker.new(@window_height, @window_width)
51
57
  @pager = force_open ? open_pager : nil
52
58
  end
@@ -93,7 +99,7 @@ module RubyJard
93
99
 
94
100
  IO.popen(
95
101
  less_command.join(' '), 'w',
96
- out: @pry_instance.output, err: @pry_instance.output
102
+ out: @tty_output, err: @tty_output
97
103
  )
98
104
  end
99
105
 
@@ -29,7 +29,8 @@ module RubyJard
29
29
  @repl_proxy = RubyJard::ReplProxy.new(
30
30
  key_bindings: RubyJard.global_key_bindings
31
31
  )
32
- @previous_flow = nil
32
+ @previous_flow = RubyJard::ControlFlow.new(:next)
33
+ @output = RubyJard::Console.output
33
34
  end
34
35
 
35
36
  def at_line
@@ -123,10 +124,10 @@ module RubyJard
123
124
  next_frame = find_frame(options[:frame].to_i)
124
125
  if next_frame.nil?
125
126
  # There must be an error in outer validators
126
- RubyJard::ScreenManager.puts 'Error: Frame not found. There should be an error with Jard.'
127
+ @output.puts 'Error: Frame not found. There should be an error with Jard.'
127
128
  process_commands(false)
128
129
  elsif next_frame.c_frame?
129
- RubyJard::ScreenManager.puts "Error: Frame #{next_frame} is a c-frame. Not able to inspect c layer!"
130
+ @output.puts "Error: Frame #{next_frame} is a c-frame. Not able to inspect c layer!"
130
131
  process_commands(false)
131
132
  else
132
133
  RubyJard::Session.frame = next_frame.real_pos
@@ -135,11 +136,12 @@ module RubyJard
135
136
  end
136
137
 
137
138
  def handle_continue_command(_options = {})
138
- RubyJard::ScreenManager.puts '▸▸ Program resumed ▸▸'
139
+ @output.puts '▸▸ Program resumed ▸▸'
139
140
  Byebug.stop if Byebug.stoppable?
140
141
  end
141
142
 
142
143
  def handle_exit_command(_options = {})
144
+ Byebug.stop if Byebug.stoppable?
143
145
  Kernel.exit
144
146
  end
145
147
 
@@ -123,7 +123,10 @@ module RubyJard
123
123
  end
124
124
  end
125
125
 
126
- def initialize(key_bindings: nil)
126
+ def initialize(key_bindings: nil, input: RubyJard::Console.input, output: RubyJard::Console.output)
127
+ @input = input
128
+ @output = output
129
+
127
130
  @state = ReplState.new
128
131
 
129
132
  @pry_input_pipe_read, @pry_input_pipe_write = IO.pipe
@@ -137,15 +140,23 @@ module RubyJard
137
140
 
138
141
  @pry_pty_output_thread = Thread.new { pry_pty_output }
139
142
  @pry_pty_output_thread.name = '<<Jard: Pty Output Thread>>'
143
+
144
+ Signal.trap('SIGWINCH') do
145
+ @main_thread.raise FlowInterrupt.new('Resize event', RubyJard::ControlFlow.new(:list))
146
+ end
140
147
  end
141
148
 
149
+ # rubocop:disable Metrics/MethodLength
142
150
  def repl(current_binding)
143
151
  @state.ready!
144
152
  @openning_pager = false
145
153
 
146
- RubyJard::Console.disable_echo!
147
- RubyJard::Console.raw!
154
+ RubyJard::Console.disable_echo!(@output)
155
+ RubyJard::Console.raw!(@output)
148
156
 
157
+ # Internally, Pry sneakily updates Readline to global output config
158
+ # when STDOUT is piping regardless of what I pass into Pry instance.
159
+ Pry.config.output = @pry_output_pty_write
149
160
  Readline.input = @pry_input_pipe_read
150
161
  Readline.output = @pry_output_pty_write
151
162
  @pry.binding_stack.clear
@@ -168,41 +179,47 @@ module RubyJard
168
179
  sleep PTY_OUTPUT_TIMEOUT until @state.exited?
169
180
  RubyJard::ControlFlow.dispatch(e.flow)
170
181
  ensure
171
- RubyJard::Console.enable_echo!
172
- RubyJard::Console.cooked!
173
- Readline.input = STDIN
174
- Readline.output = STDOUT
182
+ RubyJard::Console.enable_echo!(@output)
183
+ RubyJard::Console.cooked!(@output)
184
+ Readline.input = @input
185
+ Readline.output = @output
186
+ Pry.config.output = @output
175
187
  @key_listen_thread&.exit if @key_listen_thread&.alive?
176
188
  @pry_input_thread&.exit if @pry_input_thread&.alive?
177
189
  @state.exited!
178
190
  end
191
+ # rubocop:enable Metrics/MethodLength
179
192
 
180
193
  private
181
194
 
182
195
  def read_key
183
- RubyJard::Console.getch(STDIN, KEY_READ_TIMEOUT)
196
+ RubyJard::Console.getch(@input, KEY_READ_TIMEOUT)
184
197
  end
185
198
 
186
199
  def pry_pty_output
187
200
  loop do
188
201
  if @state.exiting?
189
202
  if @pry_output_pty_read.ready?
190
- STDOUT.write @pry_output_pty_read.read_nonblock(2048), from_jard: true
203
+ write_output(@pry_output_pty_read.read_nonblock(2048))
191
204
  else
192
205
  @state.exited!
193
206
  end
194
207
  elsif @state.exited?
195
208
  sleep PTY_OUTPUT_TIMEOUT
196
209
  else
197
- output = @pry_output_pty_read.read_nonblock(2048)
198
- unless output.nil?
199
- STDOUT.write output, from_jard: true
210
+ content = @pry_output_pty_read.read_nonblock(2048)
211
+ unless content.nil?
212
+ write_output(content)
200
213
  end
201
214
  end
202
215
  rescue IO::WaitReadable, IO::WaitWritable
203
216
  # Retry
204
217
  sleep PTY_OUTPUT_TIMEOUT
205
218
  end
219
+ rescue StandardError
220
+ # This thread shoud never die, or the user may be freezed, and cannot type anything
221
+ sleep 0.5
222
+ retry
206
223
  end
207
224
 
208
225
  def pry_repl(current_binding)
@@ -312,25 +329,33 @@ module RubyJard
312
329
  def pry_hooks
313
330
  hooks = Pry::Hooks.default
314
331
  hooks.add_hook(:after_read, :jard_proxy_acquire_lock) do |_read_string, _pry|
315
- RubyJard::Console.cooked!
332
+ RubyJard::Console.cooked!(@output)
316
333
  @state.processing!
317
334
  # Sleep 2 ticks, wait for pry to print out all existing output in the queue
318
335
  sleep PTY_OUTPUT_TIMEOUT * 2
319
336
  end
320
337
  hooks.add_hook(:after_handle_line, :jard_proxy_release_lock) do
321
- RubyJard::Console.raw!
338
+ RubyJard::Console.raw!(@output)
322
339
  @state.ready!
323
340
  end
324
341
  hooks.add_hook(:before_pager, :jard_proxy_before_pager) do
325
342
  @openning_pager = true
326
343
 
327
344
  @state.processing!
328
- RubyJard::Console.cooked!
345
+ RubyJard::Console.cooked!(@output)
329
346
  end
330
347
  hooks.add_hook(:after_pager, :jard_proxy_after_pager) do
331
348
  @openning_pager = false
332
349
  @state.ready!
333
- RubyJard::Console.raw!
350
+ RubyJard::Console.raw!(@output)
351
+ end
352
+ end
353
+
354
+ def write_output(content)
355
+ if RubyJard::Console.redirected?
356
+ @output.write content.force_encoding('UTF-8')
357
+ else
358
+ @output.write content.force_encoding('UTF-8'), from_jard: true
334
359
  end
335
360
  end
336
361
  end
@@ -38,7 +38,7 @@ module RubyJard
38
38
 
39
39
  attr_reader :output, :output_storage
40
40
 
41
- def initialize(output: STDOUT)
41
+ def initialize(output: RubyJard::Console.output)
42
42
  @output = output
43
43
  @screens = {}
44
44
  @started = false
@@ -68,9 +68,9 @@ module RubyJard
68
68
 
69
69
  @started = false
70
70
 
71
- RubyJard::Console.cooked!
72
- RubyJard::Console.enable_echo!
73
- RubyJard::Console.enable_cursor!
71
+ RubyJard::Console.cooked!(@output)
72
+ RubyJard::Console.enable_echo!(@output)
73
+ RubyJard::Console.enable_cursor!(@output)
74
74
  end
75
75
 
76
76
  def draw_screens
@@ -78,7 +78,7 @@ module RubyJard
78
78
  @updating = true
79
79
 
80
80
  RubyJard::Console.clear_screen(@output)
81
- RubyJard::Console.disable_cursor!
81
+ RubyJard::Console.disable_cursor!(@output)
82
82
  width, height = RubyJard::Console.screen_size(@output)
83
83
 
84
84
  @layouts = calculate_layouts(width, height)
@@ -102,9 +102,9 @@ module RubyJard
102
102
  draw_error(e, height)
103
103
  ensure
104
104
  # You don't want to mess up previous user TTY no matter happens
105
- RubyJard::Console.cooked!
106
- RubyJard::Console.enable_echo!
107
- RubyJard::Console.enable_cursor!
105
+ RubyJard::Console.cooked!(@output)
106
+ RubyJard::Console.enable_echo!(@output)
107
+ RubyJard::Console.enable_cursor!(@output)
108
108
  @updating = false
109
109
  end
110
110
 
@@ -128,10 +128,6 @@ module RubyJard
128
128
  RubyJard.error(exception)
129
129
  end
130
130
 
131
- def puts(content)
132
- @output.write "#{content}\n", from_jard: true
133
- end
134
-
135
131
  private
136
132
 
137
133
  def calculate_layouts(width, height)
@@ -13,7 +13,7 @@ module RubyJard
13
13
  extend Forwardable
14
14
 
15
15
  def_delegators :instance,
16
- :attach, :lock,
16
+ :lock,
17
17
  :sync, :should_stop?,
18
18
  :step_over, :step_into, :frame=,
19
19
  :threads, :current_frame, :current_thread, :current_backtrace,
@@ -22,6 +22,21 @@ module RubyJard
22
22
  def instance
23
23
  @instance ||= new
24
24
  end
25
+
26
+ def attach
27
+ unless RubyJard::Console.attachable?
28
+ $stdout.puts 'Failed to attach. Jard could not detect a valid tty device.'
29
+ $stdout.puts 'This bug occurs when the process Jard trying to access is a non-interactive environment '\
30
+ ' such as docker, daemon, sub-processes, etc.'
31
+ $stdout.puts 'If you are confused, please submit an issue in https://github.com/nguyenquangminh0711/ruby_jard/issues.'
32
+ return
33
+ end
34
+
35
+ instance.start unless instance.started?
36
+
37
+ Byebug.attach
38
+ Byebug.current_context.step_out(3, true)
39
+ end
25
40
  end
26
41
 
27
42
  OUTPUT_BUFFER_LENGTH = 10_000 # 10k lines
@@ -52,31 +67,28 @@ module RubyJard
52
67
  Byebug::Setting[:autolist] = false
53
68
  Byebug::Setting[:autoirb] = false
54
69
  Byebug::Setting[:autopry] = false
70
+
71
+ require 'ruby_jard/repl_processor'
55
72
  Byebug::Context.processor = RubyJard::ReplProcessor
56
73
  # Exclude all files in Ruby Jard source code from the stacktrace.
57
- Byebug::Context.ignored_files = Byebug::Context.all_files + Dir.glob(
58
- File.join(
59
- File.expand_path(__dir__, '../lib'),
60
- '**',
61
- '*.rb'
62
- )
63
- )
64
- # rubocop:disable Lint/NestedMethodDefinition
65
- def $stdout.write(*string, from_jard: false)
66
- # NOTE: `RubyJard::ScreenManager.instance` is a must. Jard doesn't work well with delegator
67
- # TODO: Debug and fix the issues permanently
68
- if from_jard
69
- super(*string)
70
- return
71
- end
74
+ Byebug::Context.ignored_files = Byebug::Context.all_files + RubyJard.all_files
72
75
 
73
- unless RubyJard::ScreenManager.instance.updating?
74
- RubyJard::Session.instance.append_output_buffer(string)
75
- end
76
+ $stdout.send(:instance_eval, <<-CODE)
77
+ def write(*string, from_jard: false)
78
+ # NOTE: `RubyJard::ScreenManager.instance` is a must. Jard doesn't work well with delegator
79
+ # TODO: Debug and fix the issues permanently
80
+ if from_jard
81
+ super(*string)
82
+ return
83
+ end
76
84
 
77
- super(*string)
78
- end
79
- # rubocop:enable Lint/NestedMethodDefinition
85
+ unless RubyJard::ScreenManager.instance.updating?
86
+ RubyJard::Session.instance.append_output_buffer(string)
87
+ end
88
+
89
+ super(*string)
90
+ end
91
+ CODE
80
92
 
81
93
  at_exit { stop }
82
94
 
@@ -98,13 +110,6 @@ module RubyJard
98
110
  @started == true
99
111
  end
100
112
 
101
- def attach
102
- start unless started?
103
-
104
- Byebug.attach
105
- Byebug.current_context.step_out(3, true)
106
- end
107
-
108
113
  def should_stop?
109
114
  @path_filter.match?(@current_context.frame_file)
110
115
  end
@@ -15,7 +15,7 @@ module RubyJard
15
15
  content += ' ' * margin_right if margin_right > 0
16
16
  end
17
17
 
18
- @content = content.to_s
18
+ @content = content.to_s.gsub(/\r\n/, '\n').gsub(/\n/, '\n')
19
19
  @content_length = content_length || @content.length
20
20
  @styles = styles
21
21
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  # Semantic versionn
4
4
  module RubyJard
5
- VERSION = '0.3.0'
5
+ VERSION = '0.3.1'
6
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_jard
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Minh Nguyen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-09-12 00:00:00.000000000 Z
11
+ date: 2020-09-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: byebug
@@ -70,6 +70,7 @@ files:
70
70
  - ".github/FUNDING.yml"
71
71
  - ".github/ISSUE_TEMPLATE/bug_report.md"
72
72
  - ".github/ISSUE_TEMPLATE/feature_request.md"
73
+ - ".github/dependabot.yml"
73
74
  - ".github/workflows/documentation.yml"
74
75
  - ".github/workflows/rspec.yml"
75
76
  - ".gitignore"