reline 0.1.4 → 0.1.5

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: 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: []