reline 0.1.4 → 0.1.5

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: 41e0f73e51def110f060454c69c3a3a29d3b0cedce2c432cc594df4f6cccf012
4
- data.tar.gz: c47d69d20e28f8940c86eedba5349892ecf5e5745e83a8c701eab327e326c6ba
3
+ metadata.gz: cb0a1da0d5f51d50b82a78a3084a69ca37821b541e3c362bcb2b9a10a04664a3
4
+ data.tar.gz: bc1860cf76efa34984aef1dbafb60d1f7781b86ba44a9392dc5fbd8025631121
5
5
  SHA512:
6
- metadata.gz: b494460955ab34ccf2e4c0443e76598c692d2846162be5081ecc8234181b47771721e5969fa76acd54bcce7f6d2c3dbc7e6469b23897c144c6233db5c08c915b
7
- data.tar.gz: 7c0f5bd0caf7f79113b479e9785aa2260536356ff7196fa1d034fe669f60fca7078771575d8002edf18a5c256105e22d550d9b4e0382071e2c240ef0a34b0287
6
+ metadata.gz: 4691bf1855429c5f1c25b16f66bc1e4793290579ae9a7345d1dd97c2694863bec59bab30edb232665b38a1f302a4e601c99111c84e5c277651ab2e53d4353982
7
+ data.tar.gz: 9ba7b27a82ce1d823f394c78ba1d995b6c4f71ddecb13c17c27fb39a13b7503c3b84402feb0239eb33d19d87f00d06c0608e35439413f6c72d2bdea50e84d10e
@@ -7,6 +7,7 @@ require 'reline/key_actor'
7
7
  require 'reline/key_stroke'
8
8
  require 'reline/line_editor'
9
9
  require 'reline/history'
10
+ require 'rbconfig'
10
11
 
11
12
  module Reline
12
13
  FILENAME_COMPLETION_PROC = nil
@@ -98,22 +99,22 @@ module Reline
98
99
  end
99
100
 
100
101
  def completion_proc=(p)
101
- raise ArgumentError unless p.respond_to?(:call)
102
+ raise ArgumentError unless p.respond_to?(:call) or p.nil?
102
103
  @completion_proc = p
103
104
  end
104
105
 
105
106
  def output_modifier_proc=(p)
106
- raise ArgumentError unless p.respond_to?(:call)
107
+ raise ArgumentError unless p.respond_to?(:call) or p.nil?
107
108
  @output_modifier_proc = p
108
109
  end
109
110
 
110
111
  def prompt_proc=(p)
111
- raise ArgumentError unless p.respond_to?(:call)
112
+ raise ArgumentError unless p.respond_to?(:call) or p.nil?
112
113
  @prompt_proc = p
113
114
  end
114
115
 
115
116
  def auto_indent_proc=(p)
116
- raise ArgumentError unless p.respond_to?(:call)
117
+ raise ArgumentError unless p.respond_to?(:call) or p.nil?
117
118
  @auto_indent_proc = p
118
119
  end
119
120
 
@@ -122,7 +123,7 @@ module Reline
122
123
  end
123
124
 
124
125
  def dig_perfect_match_proc=(p)
125
- raise ArgumentError unless p.respond_to?(:call)
126
+ raise ArgumentError unless p.respond_to?(:call) or p.nil?
126
127
  @dig_perfect_match_proc = p
127
128
  end
128
129
 
@@ -222,7 +223,6 @@ module Reline
222
223
  line_editor.auto_indent_proc = auto_indent_proc
223
224
  line_editor.dig_perfect_match_proc = dig_perfect_match_proc
224
225
  line_editor.pre_input_hook = pre_input_hook
225
- line_editor.rerender
226
226
 
227
227
  unless config.test_mode
228
228
  config.read
@@ -232,6 +232,8 @@ module Reline
232
232
  end
233
233
  end
234
234
 
235
+ line_editor.rerender
236
+
235
237
  begin
236
238
  loop do
237
239
  read_io(config.keyseq_timeout) { |inputs|
@@ -243,6 +245,8 @@ module Reline
243
245
  break if line_editor.finished?
244
246
  end
245
247
  Reline::IOGate.move_cursor_column(0)
248
+ rescue Errno::EIO
249
+ # Maybe the I/O has been closed.
246
250
  rescue StandardError => e
247
251
  line_editor.finalize
248
252
  Reline::IOGate.deprep(otio)
@@ -28,12 +28,22 @@ class Reline::ANSI
28
28
  [27, 71, 67] => :ed_next_char, # →
29
29
  [27, 71, 68] => :ed_prev_char, # ←
30
30
 
31
+ # urxvt / exoterm
32
+ [27, 91, 55, 126] => :ed_move_to_beg, # Home
33
+ [27, 91, 56, 126] => :ed_move_to_end, # End
34
+
31
35
  # GNOME
32
36
  [27, 79, 72] => :ed_move_to_beg, # Home
33
37
  [27, 79, 70] => :ed_move_to_end, # End
34
38
  # Del is 0x08
35
39
  # Arrow keys are the same of KDE
36
40
 
41
+ # iTerm2
42
+ [27, 27, 91, 67] => :em_next_word, # Option+→
43
+ [27, 27, 91, 68] => :ed_prev_word, # Option+←
44
+ [195, 166] => :em_next_word, # Option+f
45
+ [195, 162] => :ed_prev_word, # Option+b
46
+
37
47
  # others
38
48
  [27, 32] => :em_set_mark, # M-<space>
39
49
  [24, 24] => :em_exchange_mark, # C-x C-x TODO also add Windows
@@ -61,8 +71,13 @@ class Reline::ANSI
61
71
  unless @@buf.empty?
62
72
  return @@buf.shift
63
73
  end
64
- c = @@input.raw(intr: true, &:getbyte)
74
+ until c = @@input.raw(intr: true, &:getbyte)
75
+ sleep 0.1
76
+ end
65
77
  (c == 0x16 && @@input.raw(min: 0, tim: 0, &:getbyte)) || c
78
+ rescue Errno::EIO
79
+ # Maybe the I/O has been closed.
80
+ nil
66
81
  end
67
82
 
68
83
  def self.ungetc(c)
@@ -105,10 +120,13 @@ class Reline::ANSI
105
120
  @@input.raw do |stdin|
106
121
  @@output << "\e[6n"
107
122
  @@output.flush
108
- while (c = stdin.getc) != 'R'
109
- res << c if c
123
+ loop do
124
+ c = stdin.getc
125
+ next if c.nil?
126
+ res << c
127
+ m = res.match(/\e\[(?<row>\d+);(?<column>\d+)R/)
128
+ break if m
110
129
  end
111
- m = res.match(/\e\[(?<row>\d+);(?<column>\d+)/)
112
130
  (m.pre_match + m.post_match).chars.reverse_each do |ch|
113
131
  stdin.ungetc ch
114
132
  end
@@ -116,9 +134,16 @@ class Reline::ANSI
116
134
  column = m[:column].to_i - 1
117
135
  row = m[:row].to_i - 1
118
136
  rescue Errno::ENOTTY
119
- buf = @@output.pread(@@output.pos, 0)
120
- row = buf.count("\n")
121
- column = buf.rindex("\n") ? (buf.size - buf.rindex("\n")) - 1 : 0
137
+ begin
138
+ buf = @@output.pread(@@output.pos, 0)
139
+ row = buf.count("\n")
140
+ column = buf.rindex("\n") ? (buf.size - buf.rindex("\n")) - 1 : 0
141
+ rescue Errno::ESPIPE
142
+ # Just returns column 1 for ambiguous width because this I/O is not
143
+ # tty and can't seek.
144
+ row = 0
145
+ column = 1
146
+ end
122
147
  end
123
148
  Reline::CursorPos.new(column, row)
124
149
  end
@@ -1,5 +1,3 @@
1
- require 'pathname'
2
-
3
1
  class Reline::Config
4
2
  attr_reader :test_mode
5
3
 
@@ -35,6 +33,10 @@ class Reline::Config
35
33
  show-all-if-ambiguous
36
34
  show-all-if-unmodified
37
35
  visible-stats
36
+ show-mode-in-prompt
37
+ vi-cmd-mode-icon
38
+ vi-ins-mode-icon
39
+ emacs-mode-string
38
40
  }
39
41
  VARIABLE_NAME_SYMBOLS = VARIABLE_NAMES.map { |v| :"#{v.tr(?-, ?_)}" }
40
42
  VARIABLE_NAME_SYMBOLS.each do |v|
@@ -52,6 +54,10 @@ class Reline::Config
52
54
  @key_actors[:emacs] = Reline::KeyActor::Emacs.new
53
55
  @key_actors[:vi_insert] = Reline::KeyActor::ViInsert.new
54
56
  @key_actors[:vi_command] = Reline::KeyActor::ViCommand.new
57
+ @vi_cmd_mode_icon = '(cmd)'
58
+ @vi_ins_mode_icon = '(ins)'
59
+ @emacs_mode_string = '@'
60
+ # https://tiswww.case.edu/php/chet/readline/readline.html#IDX25
55
61
  @history_size = -1 # unlimited
56
62
  @keyseq_timeout = 500
57
63
  @test_mode = false
@@ -159,7 +165,7 @@ class Reline::Config
159
165
 
160
166
  case line
161
167
  when /^set +([^ ]+) +([^ ]+)/i
162
- var, value = $1.downcase, $2.downcase
168
+ var, value = $1.downcase, $2
163
169
  bind_variable(var, value)
164
170
  next
165
171
  when /\s*("#{KEYSEQ_PATTERN}+")\s*:\s*(.*)\s*$/o
@@ -209,7 +215,11 @@ class Reline::Config
209
215
  def bind_variable(name, value)
210
216
  case name
211
217
  when 'history-size'
212
- @history_size = value.to_i
218
+ begin
219
+ @history_size = Integer(value)
220
+ rescue ArgumentError
221
+ @history_size = 500
222
+ end
213
223
  when 'bell-style'
214
224
  @bell_style =
215
225
  case value
@@ -248,12 +258,35 @@ class Reline::Config
248
258
  end
249
259
  when 'keyseq-timeout'
250
260
  @keyseq_timeout = value.to_i
261
+ when 'show-mode-in-prompt'
262
+ case value
263
+ when 'off'
264
+ @show_mode_in_prompt = false
265
+ when 'on'
266
+ @show_mode_in_prompt = true
267
+ else
268
+ @show_mode_in_prompt = false
269
+ end
270
+ when 'vi-cmd-mode-string'
271
+ @vi_cmd_mode_icon = retrieve_string(value)
272
+ when 'vi-ins-mode-string'
273
+ @vi_ins_mode_icon = retrieve_string(value)
274
+ when 'emacs-mode-string'
275
+ @emacs_mode_string = retrieve_string(value)
251
276
  when *VARIABLE_NAMES then
252
277
  variable_name = :"@#{name.tr(?-, ?_)}"
253
278
  instance_variable_set(variable_name, value.nil? || value == '1' || value == 'on')
254
279
  end
255
280
  end
256
281
 
282
+ def retrieve_string(str)
283
+ if str =~ /\A"(.*)"\z/
284
+ parse_keyseq($1).map(&:chr).join
285
+ else
286
+ parse_keyseq(str).map(&:chr).join
287
+ end
288
+ end
289
+
257
290
  def bind_key(key, func_name)
258
291
  if key =~ /\A"(.*)"\z/
259
292
  keyseq = parse_keyseq($1)
@@ -42,6 +42,8 @@ class Reline::KeyStroke
42
42
  expand(expand(rhs_bytes) + expand(input.drop(lhs.size)))
43
43
  when Symbol
44
44
  [rhs] + expand(input.drop(lhs.size))
45
+ when Array
46
+ rhs
45
47
  end
46
48
  end
47
49
 
@@ -2,7 +2,6 @@ require 'reline/kill_ring'
2
2
  require 'reline/unicode'
3
3
 
4
4
  require 'tempfile'
5
- require 'pathname'
6
5
 
7
6
  class Reline::LineEditor
8
7
  # TODO: undo
@@ -51,12 +50,6 @@ class Reline::LineEditor
51
50
  CompletionJourneyData = Struct.new('CompletionJourneyData', :preposing, :postposing, :list, :pointer)
52
51
  MenuInfo = Struct.new('MenuInfo', :target, :list)
53
52
 
54
- CSI_REGEXP = /\e\[[\d;]*[ABCDEFGHJKSTfminsuhl]/
55
- OSC_REGEXP = /\e\]\d+(?:;[^;]+)*\a/
56
- NON_PRINTING_START = "\1"
57
- NON_PRINTING_END = "\2"
58
- WIDTH_SCANNER = /\G(?:#{NON_PRINTING_START}|#{NON_PRINTING_END}|#{CSI_REGEXP}|#{OSC_REGEXP}|\X)/
59
-
60
53
  def initialize(config, encoding)
61
54
  @config = config
62
55
  @completion_append_character = ''
@@ -76,11 +69,35 @@ class Reline::LineEditor
76
69
  if @prompt_proc
77
70
  prompt_list = @prompt_proc.(buffer)
78
71
  prompt_list.map!{ prompt } if @vi_arg or @searching_prompt
72
+ if @config.show_mode_in_prompt
73
+ if @config.editing_mode_is?(:vi_command)
74
+ mode_icon = @config.vi_cmd_mode_icon
75
+ elsif @config.editing_mode_is?(:vi_insert)
76
+ mode_icon = @config.vi_ins_mode_icon
77
+ elsif @config.editing_mode_is?(:emacs)
78
+ mode_icon = @config.emacs_mode_string
79
+ else
80
+ mode_icon = '?'
81
+ end
82
+ prompt_list.map!{ |pr| mode_icon + pr }
83
+ end
79
84
  prompt = prompt_list[@line_index]
80
85
  prompt_width = calculate_width(prompt, true)
81
86
  [prompt, prompt_width, prompt_list]
82
87
  else
83
88
  prompt_width = calculate_width(prompt, true)
89
+ if @config.show_mode_in_prompt
90
+ if @config.editing_mode_is?(:vi_command)
91
+ mode_icon = @config.vi_cmd_mode_icon
92
+ elsif @config.editing_mode_is?(:vi_insert)
93
+ mode_icon = @config.vi_ins_mode_icon
94
+ elsif @config.editing_mode_is?(:emacs)
95
+ mode_icon = @config.emacs_mode_string
96
+ else
97
+ mode_icon = '?'
98
+ end
99
+ prompt = mode_icon + prompt
100
+ end
84
101
  [prompt, prompt_width, nil]
85
102
  end
86
103
  end
@@ -116,7 +133,7 @@ class Reline::LineEditor
116
133
  if @line_index.zero?
117
134
  0
118
135
  else
119
- calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list)
136
+ calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
120
137
  end
121
138
  if @prompt_proc
122
139
  prompt = prompt_list[@line_index]
@@ -190,10 +207,10 @@ class Reline::LineEditor
190
207
  @is_multiline = false
191
208
  end
192
209
 
193
- private def calculate_height_by_lines(lines, prompt_list)
210
+ private def calculate_height_by_lines(lines, prompt)
194
211
  result = 0
212
+ prompt_list = prompt.is_a?(Array) ? prompt : nil
195
213
  lines.each_with_index { |line, i|
196
- prompt = ''
197
214
  prompt = prompt_list[i] if prompt_list and prompt_list[i]
198
215
  result += calculate_height_by_width(calculate_width(prompt, true) + calculate_width(line))
199
216
  }
@@ -211,40 +228,8 @@ class Reline::LineEditor
211
228
  width.div(@screen_size.last) + 1
212
229
  end
213
230
 
214
- private def split_by_width(prompt, str, max_width)
215
- lines = [String.new(encoding: @encoding)]
216
- height = 1
217
- width = 0
218
- rest = "#{prompt}#{str}".encode(Encoding::UTF_8)
219
- in_zero_width = false
220
- rest.scan(WIDTH_SCANNER) do |gc|
221
- case gc
222
- when NON_PRINTING_START
223
- in_zero_width = true
224
- when NON_PRINTING_END
225
- in_zero_width = false
226
- when CSI_REGEXP, OSC_REGEXP
227
- lines.last << gc
228
- else
229
- unless in_zero_width
230
- mbchar_width = Reline::Unicode.get_mbchar_width(gc)
231
- if (width += mbchar_width) > max_width
232
- width = mbchar_width
233
- lines << nil
234
- lines << String.new(encoding: @encoding)
235
- height += 1
236
- end
237
- end
238
- lines.last << gc
239
- end
240
- end
241
- # The cursor moves to next line in first
242
- if width == max_width
243
- lines << nil
244
- lines << String.new(encoding: @encoding)
245
- height += 1
246
- end
247
- [lines, height]
231
+ private def split_by_width(str, max_width)
232
+ Reline::Unicode.split_by_width(str, max_width, @encoding)
248
233
  end
249
234
 
250
235
  private def scroll_down(val)
@@ -358,7 +343,7 @@ class Reline::LineEditor
358
343
  new_lines = whole_lines
359
344
  end
360
345
  prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
361
- all_height = calculate_height_by_lines(new_lines, prompt_list)
346
+ all_height = calculate_height_by_lines(new_lines, prompt_list || prompt)
362
347
  diff = all_height - @highest_in_all
363
348
  move_cursor_down(@highest_in_all - @first_line_started_from - @started_from - 1)
364
349
  if diff > 0
@@ -398,7 +383,7 @@ class Reline::LineEditor
398
383
  if @line_index.zero?
399
384
  0
400
385
  else
401
- calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list)
386
+ calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
402
387
  end
403
388
  if @prompt_proc
404
389
  prompt = prompt_list[@line_index]
@@ -457,7 +442,7 @@ class Reline::LineEditor
457
442
  if @line_index.zero?
458
443
  0
459
444
  else
460
- calculate_height_by_lines(new_buffer[0..(@line_index - 1)], prompt_list)
445
+ calculate_height_by_lines(new_buffer[0..(@line_index - 1)], prompt_list || prompt)
461
446
  end
462
447
  @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
463
448
  move_cursor_down(@first_line_started_from + @started_from)
@@ -488,7 +473,7 @@ class Reline::LineEditor
488
473
  end
489
474
 
490
475
  private def render_partial(prompt, prompt_width, line_to_render, with_control = true)
491
- visual_lines, height = split_by_width(prompt, line_to_render.nil? ? '' : line_to_render, @screen_size.last)
476
+ visual_lines, height = split_by_width(line_to_render.nil? ? prompt : prompt + line_to_render, @screen_size.last)
492
477
  if with_control
493
478
  if height > @highest_in_this
494
479
  diff = height - @highest_in_this
@@ -507,8 +492,18 @@ class Reline::LineEditor
507
492
  Reline::IOGate.move_cursor_column(0)
508
493
  visual_lines.each_with_index do |line, index|
509
494
  if line.nil?
510
- if Reline::IOGate.win? and calculate_width(visual_lines[index - 1], true) == Reline::IOGate.get_screen_size.last
511
- # A newline is automatically inserted if a character is rendered at eol on command prompt.
495
+ if calculate_width(visual_lines[index - 1], true) == Reline::IOGate.get_screen_size.last
496
+ # reaches the end of line
497
+ if Reline::IOGate.win?
498
+ # A newline is automatically inserted if a character is rendered at
499
+ # eol on command prompt.
500
+ else
501
+ # When the cursor is at the end of the line and erases characters
502
+ # after the cursor, some terminals delete the character at the
503
+ # cursor position.
504
+ move_cursor_down(1)
505
+ Reline::IOGate.move_cursor_column(0)
506
+ end
512
507
  else
513
508
  Reline::IOGate.erase_after_cursor
514
509
  move_cursor_down(1)
@@ -529,12 +524,14 @@ class Reline::LineEditor
529
524
  end
530
525
  Reline::IOGate.erase_after_cursor
531
526
  if with_control
532
- move_cursor_up(height - 1)
527
+ # Just after rendring, so the cursor is on the last line.
533
528
  if finished?
534
- move_cursor_down(@started_from)
529
+ Reline::IOGate.move_cursor_column(0)
530
+ else
531
+ # Moves up from bottom of lines to the cursor position.
532
+ move_cursor_up(height - 1 - @started_from)
533
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
535
534
  end
536
- move_cursor_down(@started_from)
537
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
538
535
  end
539
536
  height
540
537
  end
@@ -543,7 +540,7 @@ class Reline::LineEditor
543
540
  return before if before.nil? || before.empty?
544
541
 
545
542
  if after = @output_modifier_proc&.call("#{before.join("\n")}\n", complete: finished?)
546
- after.lines("\n", chomp: true)
543
+ after.lines("\n").map { |l| l.chomp('') }
547
544
  else
548
545
  before
549
546
  end
@@ -1058,29 +1055,7 @@ class Reline::LineEditor
1058
1055
  end
1059
1056
 
1060
1057
  private def calculate_width(str, allow_escape_code = false)
1061
- if allow_escape_code
1062
- width = 0
1063
- rest = str.encode(Encoding::UTF_8)
1064
- in_zero_width = false
1065
- rest.scan(WIDTH_SCANNER) do |gc|
1066
- case gc
1067
- when NON_PRINTING_START
1068
- in_zero_width = true
1069
- when NON_PRINTING_END
1070
- in_zero_width = false
1071
- when CSI_REGEXP, OSC_REGEXP
1072
- else
1073
- unless in_zero_width
1074
- width += Reline::Unicode.get_mbchar_width(gc)
1075
- end
1076
- end
1077
- end
1078
- width
1079
- else
1080
- str.encode(Encoding::UTF_8).grapheme_clusters.inject(0) { |w, gc|
1081
- w + Reline::Unicode.get_mbchar_width(gc)
1082
- }
1083
- end
1058
+ Reline::Unicode.calculate_width(str, allow_escape_code)
1084
1059
  end
1085
1060
 
1086
1061
  private def key_delete(key)
@@ -1258,7 +1233,7 @@ class Reline::LineEditor
1258
1233
  if search_word.empty? and Reline.last_incremental_search
1259
1234
  search_word = Reline.last_incremental_search
1260
1235
  end
1261
- if @history_pointer # TODO
1236
+ if @history_pointer
1262
1237
  case prev_search_key
1263
1238
  when "\C-r".ord
1264
1239
  history_pointer_base = 0
@@ -2121,7 +2096,7 @@ class Reline::LineEditor
2121
2096
  fp.path
2122
2097
  }
2123
2098
  system("#{ENV['EDITOR']} #{path}")
2124
- @line = Pathname.new(path).read
2099
+ @line = File.read(path)
2125
2100
  finish
2126
2101
  end
2127
2102
 
@@ -2321,7 +2296,6 @@ class Reline::LineEditor
2321
2296
  new_pointer = [@byte_pointer, @line_index]
2322
2297
  @previous_line_index = @line_index
2323
2298
  @byte_pointer, @line_index = @mark_pointer
2324
- @byte_pointer, @line_index = @mark_pointer
2325
2299
  @cursor = calculate_width(@line.byteslice(0, @byte_pointer))
2326
2300
  @cursor_max = calculate_width(@line)
2327
2301
  @mark_pointer = new_pointer
@@ -35,6 +35,12 @@ class Reline::Unicode
35
35
  }
36
36
  EscapedChars = EscapedPairs.keys.map(&:chr)
37
37
 
38
+ CSI_REGEXP = /\e\[[\d;]*[ABCDEFGHJKSTfminsuhl]/
39
+ OSC_REGEXP = /\e\]\d+(?:;[^;]+)*\a/
40
+ NON_PRINTING_START = "\1"
41
+ NON_PRINTING_END = "\2"
42
+ WIDTH_SCANNER = /\G(?:#{NON_PRINTING_START}|#{NON_PRINTING_END}|#{CSI_REGEXP}|#{OSC_REGEXP}|\X)/
43
+
38
44
  def self.get_mbchar_byte_size_by_first_char(c)
39
45
  # Checks UTF-8 character byte size
40
46
  case c.ord
@@ -85,6 +91,68 @@ class Reline::Unicode
85
91
  end
86
92
  end
87
93
 
94
+ def self.calculate_width(str, allow_escape_code = false)
95
+ if allow_escape_code
96
+ width = 0
97
+ rest = str.encode(Encoding::UTF_8)
98
+ in_zero_width = false
99
+ rest.scan(WIDTH_SCANNER) do |gc|
100
+ case gc
101
+ when NON_PRINTING_START
102
+ in_zero_width = true
103
+ when NON_PRINTING_END
104
+ in_zero_width = false
105
+ when CSI_REGEXP, OSC_REGEXP
106
+ else
107
+ unless in_zero_width
108
+ width += get_mbchar_width(gc)
109
+ end
110
+ end
111
+ end
112
+ width
113
+ else
114
+ str.encode(Encoding::UTF_8).grapheme_clusters.inject(0) { |w, gc|
115
+ w + get_mbchar_width(gc)
116
+ }
117
+ end
118
+ end
119
+
120
+ def self.split_by_width(str, max_width, encoding = str.encoding)
121
+ lines = [String.new(encoding: encoding)]
122
+ height = 1
123
+ width = 0
124
+ rest = str.encode(Encoding::UTF_8)
125
+ in_zero_width = false
126
+ rest.scan(WIDTH_SCANNER) do |gc|
127
+ case gc
128
+ when NON_PRINTING_START
129
+ in_zero_width = true
130
+ when NON_PRINTING_END
131
+ in_zero_width = false
132
+ when CSI_REGEXP, OSC_REGEXP
133
+ lines.last << gc
134
+ else
135
+ unless in_zero_width
136
+ mbchar_width = get_mbchar_width(gc)
137
+ if (width += mbchar_width) > max_width
138
+ width = mbchar_width
139
+ lines << nil
140
+ lines << String.new(encoding: encoding)
141
+ height += 1
142
+ end
143
+ end
144
+ lines.last << gc
145
+ end
146
+ end
147
+ # The cursor moves to next line in first
148
+ if width == max_width
149
+ lines << nil
150
+ lines << String.new(encoding: encoding)
151
+ height += 1
152
+ end
153
+ [lines, height]
154
+ end
155
+
88
156
  def self.get_next_mbchar_size(line, byte_pointer)
89
157
  grapheme = line.byteslice(byte_pointer..-1).grapheme_clusters.first
90
158
  grapheme ? grapheme.bytesize : 0
@@ -1,3 +1,3 @@
1
1
  module Reline
2
- VERSION = '0.1.4'
2
+ VERSION = '0.1.5'
3
3
  end
@@ -92,6 +92,7 @@ class Reline::Windows
92
92
  @@ReadConsoleInput = Win32API.new('kernel32', 'ReadConsoleInput', ['L', 'P', 'L', 'P'], 'L')
93
93
  @@GetFileType = Win32API.new('kernel32', 'GetFileType', ['L'], 'L')
94
94
  @@GetFileInformationByHandleEx = Win32API.new('kernel32', 'GetFileInformationByHandleEx', ['L', 'I', 'P', 'L'], 'I')
95
+ @@FillConsoleOutputAttribute = Win32API.new('kernel32', 'FillConsoleOutputAttribute', ['L', 'L', 'L', 'L', 'P'], 'L')
95
96
 
96
97
  @@input_buf = []
97
98
  @@output_buf = []
@@ -249,9 +250,17 @@ class Reline::Windows
249
250
  end
250
251
 
251
252
  def self.clear_screen
252
- # TODO: Use FillConsoleOutputCharacter and FillConsoleOutputAttribute
253
- write "\e[2J"
254
- write "\e[1;1H"
253
+ csbi = 0.chr * 22
254
+ return if @@GetConsoleScreenBufferInfo.call(@@hConsoleHandle, csbi) == 0
255
+ buffer_width = csbi[0, 2].unpack('S').first
256
+ attributes = csbi[8, 2].unpack('S').first
257
+ _window_left, window_top, _window_right, window_bottom = *csbi[10,8].unpack('S*')
258
+ fill_length = buffer_width * (window_bottom - window_top + 1)
259
+ screen_topleft = window_top * 65536
260
+ written = 0.chr * 4
261
+ @@FillConsoleOutputCharacter.call(@@hConsoleHandle, 0x20, fill_length, screen_topleft, written)
262
+ @@FillConsoleOutputAttribute.call(@@hConsoleHandle, attributes, fill_length, screen_topleft, written)
263
+ @@SetConsoleCursorPosition.call(@@hConsoleHandle, screen_topleft)
255
264
  end
256
265
 
257
266
  def self.set_screen_size(rows, columns)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: reline
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - aycabta
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-04-25 00:00:00.000000000 Z
11
+ date: 2020-09-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: io-console
@@ -95,9 +95,9 @@ files:
95
95
  - lib/reline/windows.rb
96
96
  homepage: https://github.com/ruby/reline
97
97
  licenses:
98
- - Ruby License
98
+ - Ruby
99
99
  metadata: {}
100
- post_install_message:
100
+ post_install_message:
101
101
  rdoc_options: []
102
102
  require_paths:
103
103
  - lib
@@ -113,7 +113,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
113
113
  version: '0'
114
114
  requirements: []
115
115
  rubygems_version: 3.1.2
116
- signing_key:
116
+ signing_key:
117
117
  specification_version: 4
118
118
  summary: Alternative GNU Readline or Editline implementation by pure Ruby.
119
119
  test_files: []