pry 0.9.12.6-i386-mswin32 → 0.10.0-i386-mswin32

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