pry 0.9.12.6-i386-mingw32 → 0.10.0-i386-mingw32

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 (187) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +702 -0
  3. data/LICENSE +2 -2
  4. data/{README.markdown → README.md} +37 -31
  5. data/lib/pry.rb +38 -151
  6. data/lib/pry/cli.rb +35 -17
  7. data/lib/pry/code.rb +19 -63
  8. data/lib/pry/code/code_file.rb +103 -0
  9. data/lib/pry/code/code_range.rb +2 -1
  10. data/lib/pry/code/loc.rb +2 -2
  11. data/lib/pry/code_object.rb +40 -21
  12. data/lib/pry/color_printer.rb +55 -0
  13. data/lib/pry/command.rb +12 -9
  14. data/lib/pry/command_set.rb +81 -38
  15. data/lib/pry/commands.rb +1 -1
  16. data/lib/pry/commands/amend_line.rb +2 -2
  17. data/lib/pry/commands/bang.rb +1 -1
  18. data/lib/pry/commands/cat.rb +11 -2
  19. data/lib/pry/commands/cat/exception_formatter.rb +6 -7
  20. data/lib/pry/commands/cat/file_formatter.rb +15 -32
  21. data/lib/pry/commands/cat/input_expression_formatter.rb +1 -1
  22. data/lib/pry/commands/cd.rb +14 -3
  23. data/lib/pry/commands/change_inspector.rb +27 -0
  24. data/lib/pry/commands/change_prompt.rb +26 -0
  25. data/lib/pry/commands/code_collector.rb +4 -4
  26. data/lib/pry/commands/easter_eggs.rb +3 -3
  27. data/lib/pry/commands/edit.rb +10 -22
  28. data/lib/pry/commands/edit/exception_patcher.rb +2 -2
  29. data/lib/pry/commands/edit/file_and_line_locator.rb +0 -2
  30. data/lib/pry/commands/exit_program.rb +0 -1
  31. data/lib/pry/commands/find_method.rb +16 -22
  32. data/lib/pry/commands/gem_install.rb +5 -2
  33. data/lib/pry/commands/gem_open.rb +1 -1
  34. data/lib/pry/commands/gist.rb +10 -11
  35. data/lib/pry/commands/help.rb +14 -14
  36. data/lib/pry/commands/hist.rb +27 -8
  37. data/lib/pry/commands/install_command.rb +14 -12
  38. data/lib/pry/commands/list_inspectors.rb +35 -0
  39. data/lib/pry/commands/list_prompts.rb +35 -0
  40. data/lib/pry/commands/ls.rb +72 -296
  41. data/lib/pry/commands/ls/constants.rb +47 -0
  42. data/lib/pry/commands/ls/formatter.rb +49 -0
  43. data/lib/pry/commands/ls/globals.rb +48 -0
  44. data/lib/pry/commands/ls/grep.rb +21 -0
  45. data/lib/pry/commands/ls/instance_vars.rb +39 -0
  46. data/lib/pry/commands/ls/interrogatable.rb +18 -0
  47. data/lib/pry/commands/ls/jruby_hacks.rb +49 -0
  48. data/lib/pry/commands/ls/local_names.rb +35 -0
  49. data/lib/pry/commands/ls/local_vars.rb +39 -0
  50. data/lib/pry/commands/ls/ls_entity.rb +70 -0
  51. data/lib/pry/commands/ls/methods.rb +57 -0
  52. data/lib/pry/commands/ls/methods_helper.rb +46 -0
  53. data/lib/pry/commands/ls/self_methods.rb +32 -0
  54. data/lib/pry/commands/play.rb +44 -10
  55. data/lib/pry/commands/pry_backtrace.rb +1 -2
  56. data/lib/pry/commands/raise_up.rb +2 -2
  57. data/lib/pry/commands/reload_code.rb +16 -19
  58. data/lib/pry/commands/ri.rb +7 -3
  59. data/lib/pry/commands/shell_command.rb +18 -13
  60. data/lib/pry/commands/shell_mode.rb +2 -4
  61. data/lib/pry/commands/show_doc.rb +5 -0
  62. data/lib/pry/commands/show_info.rb +8 -13
  63. data/lib/pry/commands/show_source.rb +15 -3
  64. data/lib/pry/commands/simple_prompt.rb +1 -1
  65. data/lib/pry/commands/toggle_color.rb +8 -4
  66. data/lib/pry/commands/watch_expression.rb +105 -0
  67. data/lib/pry/commands/watch_expression/expression.rb +38 -0
  68. data/lib/pry/commands/whereami.rb +18 -10
  69. data/lib/pry/commands/wtf.rb +3 -3
  70. data/lib/pry/config.rb +20 -254
  71. data/lib/pry/config/behavior.rb +139 -0
  72. data/lib/pry/config/convenience.rb +26 -0
  73. data/lib/pry/config/default.rb +165 -0
  74. data/lib/pry/core_extensions.rb +31 -21
  75. data/lib/pry/editor.rb +107 -103
  76. data/lib/pry/exceptions.rb +77 -0
  77. data/lib/pry/helpers/base_helpers.rb +22 -109
  78. data/lib/pry/helpers/command_helpers.rb +10 -8
  79. data/lib/pry/helpers/documentation_helpers.rb +1 -2
  80. data/lib/pry/helpers/text.rb +4 -5
  81. data/lib/pry/history.rb +46 -45
  82. data/lib/pry/history_array.rb +6 -1
  83. data/lib/pry/hooks.rb +9 -29
  84. data/lib/pry/indent.rb +6 -6
  85. data/lib/pry/input_completer.rb +242 -0
  86. data/lib/pry/input_lock.rb +132 -0
  87. data/lib/pry/inspector.rb +27 -0
  88. data/lib/pry/last_exception.rb +61 -0
  89. data/lib/pry/method.rb +82 -87
  90. data/lib/pry/{commands/edit/method_patcher.rb → method/patcher.rb} +41 -38
  91. data/lib/pry/module_candidate.rb +4 -14
  92. data/lib/pry/object_path.rb +82 -0
  93. data/lib/pry/output.rb +50 -0
  94. data/lib/pry/pager.rb +193 -48
  95. data/lib/pry/plugins.rb +1 -1
  96. data/lib/pry/prompt.rb +26 -0
  97. data/lib/pry/pry_class.rb +149 -230
  98. data/lib/pry/pry_instance.rb +302 -413
  99. data/lib/pry/rbx_path.rb +1 -1
  100. data/lib/pry/repl.rb +202 -0
  101. data/lib/pry/repl_file_loader.rb +20 -26
  102. data/lib/pry/rubygem.rb +13 -5
  103. data/lib/pry/terminal.rb +2 -1
  104. data/lib/pry/test/helper.rb +26 -41
  105. data/lib/pry/version.rb +1 -1
  106. data/lib/pry/wrapped_module.rb +45 -59
  107. metadata +62 -225
  108. data/.document +0 -2
  109. data/.gitignore +0 -16
  110. data/.travis.yml +0 -25
  111. data/.yardopts +0 -1
  112. data/CHANGELOG +0 -534
  113. data/CONTRIBUTORS +0 -55
  114. data/Gemfile +0 -12
  115. data/Rakefile +0 -140
  116. data/TODO +0 -117
  117. data/lib/pry/completion.rb +0 -321
  118. data/lib/pry/custom_completions.rb +0 -6
  119. data/lib/pry/rbx_method.rb +0 -13
  120. data/man/pry.1 +0 -195
  121. data/man/pry.1.html +0 -204
  122. data/man/pry.1.ronn +0 -141
  123. data/pry.gemspec +0 -29
  124. data/spec/Procfile +0 -3
  125. data/spec/cli_spec.rb +0 -78
  126. data/spec/code_object_spec.rb +0 -277
  127. data/spec/code_spec.rb +0 -219
  128. data/spec/command_helpers_spec.rb +0 -29
  129. data/spec/command_integration_spec.rb +0 -644
  130. data/spec/command_set_spec.rb +0 -627
  131. data/spec/command_spec.rb +0 -821
  132. data/spec/commands/amend_line_spec.rb +0 -247
  133. data/spec/commands/bang_spec.rb +0 -19
  134. data/spec/commands/cat_spec.rb +0 -164
  135. data/spec/commands/cd_spec.rb +0 -250
  136. data/spec/commands/disable_pry_spec.rb +0 -25
  137. data/spec/commands/edit_spec.rb +0 -727
  138. data/spec/commands/exit_all_spec.rb +0 -34
  139. data/spec/commands/exit_program_spec.rb +0 -19
  140. data/spec/commands/exit_spec.rb +0 -34
  141. data/spec/commands/find_method_spec.rb +0 -70
  142. data/spec/commands/gem_list_spec.rb +0 -26
  143. data/spec/commands/gist_spec.rb +0 -79
  144. data/spec/commands/help_spec.rb +0 -56
  145. data/spec/commands/hist_spec.rb +0 -181
  146. data/spec/commands/jump_to_spec.rb +0 -15
  147. data/spec/commands/ls_spec.rb +0 -181
  148. data/spec/commands/play_spec.rb +0 -140
  149. data/spec/commands/raise_up_spec.rb +0 -56
  150. data/spec/commands/save_file_spec.rb +0 -177
  151. data/spec/commands/show_doc_spec.rb +0 -510
  152. data/spec/commands/show_input_spec.rb +0 -17
  153. data/spec/commands/show_source_spec.rb +0 -782
  154. data/spec/commands/whereami_spec.rb +0 -203
  155. data/spec/completion_spec.rb +0 -241
  156. data/spec/control_d_handler_spec.rb +0 -58
  157. data/spec/documentation_helper_spec.rb +0 -73
  158. data/spec/editor_spec.rb +0 -79
  159. data/spec/exception_whitelist_spec.rb +0 -21
  160. data/spec/fixtures/candidate_helper1.rb +0 -11
  161. data/spec/fixtures/candidate_helper2.rb +0 -8
  162. data/spec/fixtures/example.erb +0 -5
  163. data/spec/fixtures/example_nesting.rb +0 -33
  164. data/spec/fixtures/show_source_doc_examples.rb +0 -15
  165. data/spec/fixtures/testrc +0 -2
  166. data/spec/fixtures/testrcbad +0 -2
  167. data/spec/fixtures/whereami_helper.rb +0 -6
  168. data/spec/helper.rb +0 -34
  169. data/spec/helpers/bacon.rb +0 -86
  170. data/spec/helpers/mock_pry.rb +0 -43
  171. data/spec/helpers/table_spec.rb +0 -105
  172. data/spec/history_array_spec.rb +0 -67
  173. data/spec/hooks_spec.rb +0 -522
  174. data/spec/indent_spec.rb +0 -301
  175. data/spec/input_stack_spec.rb +0 -90
  176. data/spec/method_spec.rb +0 -482
  177. data/spec/prompt_spec.rb +0 -60
  178. data/spec/pry_defaults_spec.rb +0 -419
  179. data/spec/pry_history_spec.rb +0 -99
  180. data/spec/pry_output_spec.rb +0 -95
  181. data/spec/pry_spec.rb +0 -515
  182. data/spec/run_command_spec.rb +0 -25
  183. data/spec/sticky_locals_spec.rb +0 -157
  184. data/spec/syntax_checking_spec.rb +0 -81
  185. data/spec/wrapped_module_spec.rb +0 -261
  186. data/wiki/Customizing-pry.md +0 -397
  187. data/wiki/Home.md +0 -4
@@ -29,8 +29,8 @@ class Pry
29
29
  :number_of_candidates]
30
30
 
31
31
  def_delegators :@wrapper, *(private_delegates + public_delegates)
32
- private *private_delegates
33
- public *public_delegates
32
+ private(*private_delegates)
33
+ public(*public_delegates)
34
34
 
35
35
  # @raise [Pry::CommandError] If `rank` is out of bounds.
36
36
  # @param [Pry::WrappedModule] wrapper The associated
@@ -112,13 +112,13 @@ class Pry
112
112
  # @return [Array] The source location of the base method used to
113
113
  # calculate the source location of the candidate.
114
114
  def first_method_source_location
115
- @first_method_source_location ||= adjusted_source_location(method_candidates[@rank].first.source_location)
115
+ @first_method_source_location ||= method_candidates[@rank].first.source_location
116
116
  end
117
117
 
118
118
  # @return [Array] The source location of the last method in this
119
119
  # candidate's module definition.
120
120
  def last_method_source_location
121
- @end_method_source_location ||= adjusted_source_location(method_candidates[@rank].last.source_location)
121
+ @end_method_source_location ||= method_candidates[@rank].last.source_location
122
122
  end
123
123
 
124
124
  # Return the number of lines between the start of the class definition
@@ -131,16 +131,6 @@ class Pry
131
131
 
132
132
  end_method_line - line
133
133
  end
134
-
135
- def adjusted_source_location(sl)
136
- file, line = sl
137
-
138
- if file && RbxPath.is_core_path?(file)
139
- file = RbxPath.convert_path_to_full(file)
140
- end
141
-
142
- [file, line]
143
- end
144
134
  end
145
135
  end
146
136
  end
@@ -0,0 +1,82 @@
1
+ class Pry
2
+ # `ObjectPath` implements the resolution of "object paths", which are strings
3
+ # that are similar to filesystem paths but meant for traversing Ruby objects.
4
+ # Examples of valid object paths include:
5
+ #
6
+ # x
7
+ # @foo/@bar
8
+ # "string"/upcase
9
+ # Pry/Method
10
+ #
11
+ # Object paths are mostly relevant in the context of the `cd` command.
12
+ # @see https://github.com/pry/pry/wiki/State-navigation
13
+ class ObjectPath
14
+ SPECIAL_TERMS = ["", "::", ".", ".."]
15
+
16
+ # @param [String] path_string The object path expressed as a string.
17
+ # @param [Array<Binding>] current_stack The current state of the binding
18
+ # stack.
19
+ def initialize(path_string, current_stack)
20
+ @path_string = path_string
21
+ @current_stack = current_stack
22
+ end
23
+
24
+ # @return [Array<Binding>] a new stack resulting from applying the given
25
+ # path to the current stack.
26
+ def resolve
27
+ scanner = StringScanner.new(@path_string.strip)
28
+ stack = @current_stack.dup
29
+
30
+ begin
31
+ next_segment = ""
32
+
33
+ loop do
34
+ # Scan for as long as we don't see a slash
35
+ next_segment << scanner.scan(/[^\/]*/)
36
+
37
+ if complete?(next_segment) || scanner.eos?
38
+ scanner.getch # consume the slash
39
+ break
40
+ else
41
+ next_segment << scanner.getch # append the slash
42
+ end
43
+ end
44
+
45
+ case next_segment.chomp
46
+ when ""
47
+ stack = [stack.first]
48
+ when "::"
49
+ stack.push(TOPLEVEL_BINDING)
50
+ when "."
51
+ next
52
+ when ".."
53
+ stack.pop unless stack.size == 1
54
+ else
55
+ stack.push(Pry.binding_for(stack.last.eval(next_segment)))
56
+ end
57
+ rescue RescuableException => e
58
+ return handle_failure(next_segment, e)
59
+ end until scanner.eos?
60
+
61
+ stack
62
+ end
63
+
64
+ private
65
+
66
+ def complete?(segment)
67
+ SPECIAL_TERMS.include?(segment) || Pry::Code.complete_expression?(segment)
68
+ end
69
+
70
+ def handle_failure(context, err)
71
+ msg = [
72
+ "Bad object path: #{@path_string.inspect}",
73
+ "Failed trying to resolve: #{context.inspect}",
74
+ "Exception: #{err.inspect}"
75
+ ].join("\n")
76
+
77
+ raise CommandError.new(msg).tap { |e|
78
+ e.set_backtrace err.backtrace
79
+ }
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,50 @@
1
+ class Pry
2
+ class Output
3
+ attr_reader :_pry_
4
+
5
+ def initialize(_pry_)
6
+ @_pry_ = _pry_
7
+ end
8
+
9
+ def puts(*objs)
10
+ return print "\n" if objs.empty?
11
+
12
+ objs.each do |obj|
13
+ if ary = Array.try_convert(obj)
14
+ puts(*ary)
15
+ else
16
+ print "#{obj.to_s.chomp}\n"
17
+ end
18
+ end
19
+
20
+ nil
21
+ end
22
+
23
+ def print(*objs)
24
+ objs.each do |obj|
25
+ _pry_.config.output.print decolorize_maybe(obj.to_s)
26
+ end
27
+
28
+ nil
29
+ end
30
+ alias << print
31
+ alias write print
32
+
33
+ # If _pry_.config.color is currently false, removes ansi escapes from the string.
34
+ def decolorize_maybe(str)
35
+ if _pry_.config.color
36
+ str
37
+ else
38
+ Helpers::Text.strip_color str
39
+ end
40
+ end
41
+
42
+ def method_missing(name, *args, &block)
43
+ _pry_.config.output.send(name, *args, &block)
44
+ end
45
+
46
+ def respond_to_missing?(*a)
47
+ _pry_.config.respond_to?(*a)
48
+ end
49
+ end
50
+ end
@@ -1,63 +1,132 @@
1
+ require 'pry/terminal'
2
+
3
+ # A pager is an `IO`-like object that accepts text and either prints it
4
+ # immediately, prints it one page at a time, or streams it to an external
5
+ # program to print one page at a time.
1
6
  class Pry::Pager
2
- # @param [String] text
3
- # A piece of text to run through a pager.
4
- # @param [Symbol?] pager
5
- # `:simple` -- Use the pure ruby pager.
6
- # `:system` -- Use the system pager (less) or the environment variable
7
- # $PAGER if set.
8
- # `nil` -- Infer what pager to use from the environment. What this
9
- # really means is that JRuby and systems that do not have
10
- # access to 'less' will run through the pure ruby pager.
11
- def self.page(text, pager = nil)
12
- case pager
13
- when nil
14
- no_pager = !SystemPager.available?
15
- if no_pager || Pry::Helpers::BaseHelpers.jruby?
16
- SimplePager.new(text).page
17
- else
18
- SystemPager.new(text).page
19
- end
20
- when :simple
21
- SimplePager.new(text).page
22
- when :system
23
- SystemPager.new(text).page
24
- else
25
- raise "'#{pager}' is not a recognized pager."
7
+ class StopPaging < StandardError
8
+ end
9
+
10
+ attr_reader :_pry_
11
+
12
+ def initialize(_pry_)
13
+ @_pry_ = _pry_
14
+ end
15
+
16
+ # Send the given text through the best available pager (if `Pry.config.pager` is
17
+ # enabled).
18
+ # If you want to send text through in chunks as you generate it, use `open` to
19
+ # get a writable object instead.
20
+ # @param [String] text A piece of text to run through a pager.
21
+ # @param [IO] output (`$stdout`) An object to send output to.
22
+ def page(text)
23
+ open do |pager|
24
+ pager << text
26
25
  end
27
26
  end
28
27
 
29
- def self.page_size
30
- @page_size ||= Pry::Terminal.height!
28
+ # Yields a pager object (`NullPager`, `SimplePager`, or `SystemPager`). All
29
+ # pagers accept output with `#puts`, `#print`, `#write`, and `#<<`.
30
+ # @param [IO] output (`$stdout`) An object to send output to.
31
+ def open
32
+ pager = best_available
33
+ yield pager
34
+ rescue StopPaging
35
+ ensure
36
+ pager.close if pager
31
37
  end
32
38
 
33
- def initialize(text)
34
- @text = text
39
+ private
40
+
41
+ attr_reader :output
42
+ def enabled?; !!@enabled; end
43
+
44
+ # Return an instance of the "best" available pager class -- `SystemPager` if
45
+ # possible, `SimplePager` if `SystemPager` isn't available, and `NullPager`
46
+ # if the user has disabled paging. All pagers accept output with `#puts`,
47
+ # `#print`, `#write`, and `#<<`. You must call `#close` when you're done
48
+ # writing output to a pager, and you must rescue `Pry::Pager::StopPaging`.
49
+ # These requirements can be avoided by using `.open` instead.
50
+ # @param [#<<] output ($stdout) An object to send output to.
51
+ def best_available
52
+ if !_pry_.config.pager
53
+ NullPager.new(_pry_.output)
54
+ elsif !SystemPager.available? || Pry::Helpers::BaseHelpers.jruby?
55
+ SimplePager.new(_pry_.output)
56
+ else
57
+ SystemPager.new(_pry_.output)
58
+ end
35
59
  end
36
60
 
37
- class SimplePager < Pry::Pager
38
- def page
39
- # The pager size minus the number of lines used by the simple pager info bar.
40
- page_size = Pry::Pager.page_size - 3
41
- text_array = @text.lines.to_a
42
-
43
- text_array.each_slice(page_size) do |chunk|
44
- puts chunk.join
45
- break if chunk.size < page_size
46
- if text_array.size > page_size
47
- puts "\n<page break> --- Press enter to continue ( q<enter> to break ) --- <page break>"
48
- break if Readline.readline.chomp == "q"
61
+ # `NullPager` is a "pager" that actually just prints all output as it comes
62
+ # in. Used when `Pry.config.pager` is false.
63
+ class NullPager
64
+ def initialize(out)
65
+ @out = out
66
+ end
67
+
68
+ def puts(str)
69
+ print "#{str.chomp}\n"
70
+ end
71
+
72
+ def print(str)
73
+ write str
74
+ end
75
+ alias << print
76
+
77
+ def write(str)
78
+ @out.write str
79
+ end
80
+
81
+ def close
82
+ end
83
+
84
+ private
85
+
86
+ def height
87
+ @height ||= Pry::Terminal.height!
88
+ end
89
+
90
+ def width
91
+ @width ||= Pry::Terminal.width!
92
+ end
93
+ end
94
+
95
+ # `SimplePager` is a straightforward pure-Ruby pager. We use it on JRuby and
96
+ # when we can't find a usable external pager.
97
+ class SimplePager < NullPager
98
+ def initialize(*)
99
+ super
100
+ @tracker = PageTracker.new(height - 3, width)
101
+ end
102
+
103
+ def write(str)
104
+ str.lines.each do |line|
105
+ @out.print line
106
+ @tracker.record line
107
+
108
+ if @tracker.page?
109
+ @out.print "\n"
110
+ @out.print "\e[0m"
111
+ @out.print "<page break> --- Press enter to continue " \
112
+ "( q<enter> to break ) --- <page break>\n"
113
+ raise StopPaging if Readline.readline("").chomp == "q"
114
+ @tracker.reset
49
115
  end
50
116
  end
51
117
  end
52
118
  end
53
119
 
54
- class SystemPager < Pry::Pager
120
+ # `SystemPager` buffers output until we're pretty sure it's at least a page
121
+ # long, then invokes an external pager and starts streaming output to it. If
122
+ # `#close` is called before then, it just prints out the buffered content.
123
+ class SystemPager < NullPager
55
124
  def self.default_pager
56
125
  pager = ENV["PAGER"] || ""
57
126
 
58
127
  # Default to less, and make sure less is being passed the correct options
59
- if pager.strip.empty? or pager =~ /^less\s*/
60
- pager = "less -R -S -F -X"
128
+ if pager.strip.empty? or pager =~ /^less\b/
129
+ pager = "less -R -F -X"
61
130
  end
62
131
 
63
132
  pager
@@ -67,7 +136,8 @@ class Pry::Pager
67
136
  if @system_pager.nil?
68
137
  @system_pager = begin
69
138
  pager_executable = default_pager.split(' ').first
70
- `which #{ pager_executable }`
139
+ `which #{pager_executable}`
140
+ $?.success?
71
141
  rescue
72
142
  false
73
143
  end
@@ -78,13 +148,88 @@ class Pry::Pager
78
148
 
79
149
  def initialize(*)
80
150
  super
81
- @pager = SystemPager.default_pager
151
+ @tracker = PageTracker.new(height, width)
152
+ @buffer = ""
153
+ end
154
+
155
+ def write(str)
156
+ if invoked_pager?
157
+ write_to_pager str
158
+ else
159
+ @tracker.record str
160
+ @buffer << str
161
+
162
+ if @tracker.page?
163
+ write_to_pager @buffer
164
+ end
165
+ end
166
+ rescue Errno::EPIPE
167
+ raise StopPaging
82
168
  end
83
169
 
84
- def page
85
- IO.popen(@pager, 'w') do |io|
86
- io.write @text
170
+ def close
171
+ if invoked_pager?
172
+ pager.close
173
+ else
174
+ @out.puts @buffer
87
175
  end
88
176
  end
177
+
178
+ private
179
+
180
+ def write_to_pager(text)
181
+ pager.write @out.decolorize_maybe(text)
182
+ end
183
+
184
+ def invoked_pager?
185
+ @pager
186
+ end
187
+
188
+ def pager
189
+ @pager ||= IO.popen(self.class.default_pager, 'w')
190
+ end
191
+ end
192
+
193
+ # `PageTracker` tracks output to determine whether it's likely to take up a
194
+ # whole page. This doesn't need to be super precise, but we can use it for
195
+ # `SimplePager` and to avoid invoking the system pager unnecessarily.
196
+ #
197
+ # One simplifying assumption is that we don't need `#page?` to return `true`
198
+ # on the basis of an incomplete line. Long lines should be counted as
199
+ # multiple lines, but we don't have to transition from `false` to `true`
200
+ # until we see a newline.
201
+ class PageTracker
202
+ def initialize(rows, cols)
203
+ @rows, @cols = rows, cols
204
+ reset
205
+ end
206
+
207
+ def record(str)
208
+ str.lines.each do |line|
209
+ if line.end_with? "\n"
210
+ @row += ((@col + line_length(line) - 1) / @cols) + 1
211
+ @col = 0
212
+ else
213
+ @col += line_length(line)
214
+ end
215
+ end
216
+ end
217
+
218
+ def page?
219
+ @row >= @rows
220
+ end
221
+
222
+ def reset
223
+ @row = 0
224
+ @col = 0
225
+ end
226
+
227
+ private
228
+
229
+ # Approximation of the printable length of a given line, without the
230
+ # newline and without ANSI color codes.
231
+ def line_length(line)
232
+ line.chomp.gsub(/\e\[[\d;]*m/, '').length
233
+ end
89
234
  end
90
235
  end