ruby_jard 0.3.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
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"