summaryrefslogtreecommitdiff
path: root/lib/reline
diff options
context:
space:
mode:
Diffstat (limited to 'lib/reline')
-rw-r--r--lib/reline/ansi.rb145
-rw-r--r--lib/reline/config.rb108
-rw-r--r--lib/reline/face.rb199
-rw-r--r--lib/reline/general_io.rb32
-rw-r--r--lib/reline/history.rb2
-rw-r--r--lib/reline/key_actor/emacs.rb22
-rw-r--r--lib/reline/key_actor/vi_command.rb46
-rw-r--r--lib/reline/key_actor/vi_insert.rb4
-rw-r--r--lib/reline/key_stroke.rb57
-rw-r--r--lib/reline/kill_ring.rb4
-rw-r--r--lib/reline/line_editor.rb2861
-rw-r--r--lib/reline/reline.gemspec5
-rw-r--r--lib/reline/terminfo.rb37
-rw-r--r--lib/reline/unicode.rb93
-rw-r--r--lib/reline/unicode/east_asian_width.rb150
-rw-r--r--lib/reline/version.rb2
-rw-r--r--lib/reline/windows.rb12
17 files changed, 1548 insertions, 2231 deletions
diff --git a/lib/reline/ansi.rb b/lib/reline/ansi.rb
index ab147a6185..a3719f502c 100644
--- a/lib/reline/ansi.rb
+++ b/lib/reline/ansi.rb
@@ -1,20 +1,35 @@
require 'io/console'
require 'io/wait'
-require 'timeout'
require_relative 'terminfo'
class Reline::ANSI
+ RESET_COLOR = "\e[0m"
+
CAPNAME_KEY_BINDINGS = {
'khome' => :ed_move_to_beg,
'kend' => :ed_move_to_end,
+ 'kdch1' => :key_delete,
+ 'kpp' => :ed_search_prev_history,
+ 'knp' => :ed_search_next_history,
'kcuu1' => :ed_prev_history,
'kcud1' => :ed_next_history,
'kcuf1' => :ed_next_char,
'kcub1' => :ed_prev_char,
- 'cuu' => :ed_prev_history,
- 'cud' => :ed_next_history,
- 'cuf' => :ed_next_char,
- 'cub' => :ed_prev_char,
+ }
+
+ ANSI_CURSOR_KEY_BINDINGS = {
+ # Up
+ 'A' => [:ed_prev_history, {}],
+ # Down
+ 'B' => [:ed_next_history, {}],
+ # Right
+ 'C' => [:ed_next_char, { ctrl: :em_next_word, meta: :em_next_word }],
+ # Left
+ 'D' => [:ed_prev_char, { ctrl: :ed_prev_word, meta: :ed_prev_word }],
+ # End
+ 'F' => [:ed_move_to_end, {}],
+ # Home
+ 'H' => [:ed_move_to_beg, {}],
}
if Reline::Terminfo.enabled?
@@ -29,24 +44,14 @@ class Reline::ANSI
false
end
- def self.set_default_key_bindings(config)
- if Reline::Terminfo.enabled?
+ def self.set_default_key_bindings(config, allow_terminfo: true)
+ set_default_key_bindings_ansi_cursor(config)
+ if allow_terminfo && Reline::Terminfo.enabled?
set_default_key_bindings_terminfo(config)
else
set_default_key_bindings_comprehensive_list(config)
end
{
- # extended entries of terminfo
- [27, 91, 49, 59, 53, 67] => :em_next_word, # Ctrl+→, extended entry
- [27, 91, 49, 59, 53, 68] => :ed_prev_word, # Ctrl+←, extended entry
- [27, 91, 49, 59, 51, 67] => :em_next_word, # Meta+→, extended entry
- [27, 91, 49, 59, 51, 68] => :ed_prev_word, # Meta+←, extended entry
- }.each_pair do |key, func|
- config.add_default_key_binding_by_keymap(:emacs, key, func)
- config.add_default_key_binding_by_keymap(:vi_insert, key, func)
- config.add_default_key_binding_by_keymap(:vi_command, key, func)
- end
- {
[27, 91, 90] => :completion_journey_up, # S-Tab
}.each_pair do |key, func|
config.add_default_key_binding_by_keymap(:emacs, key, func)
@@ -61,18 +66,33 @@ class Reline::ANSI
end
end
+ def self.set_default_key_bindings_ansi_cursor(config)
+ ANSI_CURSOR_KEY_BINDINGS.each do |char, (default_func, modifiers)|
+ bindings = [["\e[#{char}", default_func]] # CSI + char
+ if modifiers[:ctrl]
+ # CSI + ctrl_key_modifier + char
+ bindings << ["\e[1;5#{char}", modifiers[:ctrl]]
+ end
+ if modifiers[:meta]
+ # CSI + meta_key_modifier + char
+ bindings << ["\e[1;3#{char}", modifiers[:meta]]
+ # Meta(ESC) + CSI + char
+ bindings << ["\e\e[#{char}", modifiers[:meta]]
+ end
+ bindings.each do |sequence, func|
+ key = sequence.bytes
+ config.add_default_key_binding_by_keymap(:emacs, key, func)
+ config.add_default_key_binding_by_keymap(:vi_insert, key, func)
+ config.add_default_key_binding_by_keymap(:vi_command, key, func)
+ end
+ end
+ end
+
def self.set_default_key_bindings_terminfo(config)
key_bindings = CAPNAME_KEY_BINDINGS.map do |capname, key_binding|
begin
key_code = Reline::Terminfo.tigetstr(capname)
- case capname
- # Escape sequences that omit the move distance and are set to defaults
- # value 1 may be sometimes sent by pressing the arrow-key.
- when 'cuu', 'cud', 'cuf', 'cub'
- [ key_code.sub(/%p1%d/, '').bytes, key_binding ]
- else
- [ key_code.bytes, key_binding ]
- end
+ [ key_code.bytes, key_binding ]
rescue Reline::Terminfo::TerminfoError
# capname is undefined
end
@@ -91,14 +111,8 @@ class Reline::ANSI
[27, 91, 49, 126] => :ed_move_to_beg, # Home
[27, 91, 52, 126] => :ed_move_to_end, # End
[27, 91, 51, 126] => :key_delete, # Del
- [27, 91, 65] => :ed_prev_history, # ↑
- [27, 91, 66] => :ed_next_history, # ↓
- [27, 91, 67] => :ed_next_char, # →
- [27, 91, 68] => :ed_prev_char, # ←
# KDE
- [27, 91, 72] => :ed_move_to_beg, # Home
- [27, 91, 70] => :ed_move_to_end, # End
# Del is 0x08
[27, 71, 65] => :ed_prev_history, # ↑
[27, 71, 66] => :ed_next_history, # ↓
@@ -115,12 +129,6 @@ class Reline::ANSI
# Del is 0x08
# Arrow keys are the same of KDE
- # iTerm2
- [27, 27, 91, 67] => :em_next_word, # Option+→, extended entry
- [27, 27, 91, 68] => :ed_prev_word, # Option+←, extended entry
- [195, 166] => :em_next_word, # Option+f
- [195, 162] => :ed_prev_word, # Option+b
-
[27, 79, 65] => :ed_prev_history, # ↑
[27, 79, 66] => :ed_next_history, # ↓
[27, 79, 67] => :ed_next_char, # →
@@ -142,15 +150,27 @@ class Reline::ANSI
@@output = val
end
+ def self.with_raw_input
+ if @@input.tty?
+ @@input.raw(intr: true) { yield }
+ else
+ yield
+ end
+ end
+
@@buf = []
- def self.inner_getc
+ def self.inner_getc(timeout_second)
unless @@buf.empty?
return @@buf.shift
end
- until c = @@input.raw(intr: true) { @@input.wait_readable(0.1) && @@input.getbyte }
- Reline.core.line_editor.resize
+ until @@input.wait_readable(0.01)
+ timeout_second -= 0.01
+ return nil if timeout_second <= 0
+
+ Reline.core.line_editor.handle_signal
end
- (c == 0x16 && @@input.raw(min: 0, tim: 0, &:getbyte)) || c
+ c = @@input.getbyte
+ (c == 0x16 && @@input.raw(min: 0, time: 0, &:getbyte)) || c
rescue Errno::EIO
# Maybe the I/O has been closed.
nil
@@ -161,45 +181,43 @@ class Reline::ANSI
@@in_bracketed_paste_mode = false
START_BRACKETED_PASTE = String.new("\e[200~,", encoding: Encoding::ASCII_8BIT)
END_BRACKETED_PASTE = String.new("\e[200~.", encoding: Encoding::ASCII_8BIT)
- def self.getc_with_bracketed_paste
+ def self.getc_with_bracketed_paste(timeout_second)
buffer = String.new(encoding: Encoding::ASCII_8BIT)
- buffer << inner_getc
+ buffer << inner_getc(timeout_second)
while START_BRACKETED_PASTE.start_with?(buffer) or END_BRACKETED_PASTE.start_with?(buffer) do
if START_BRACKETED_PASTE == buffer
@@in_bracketed_paste_mode = true
- return inner_getc
+ return inner_getc(timeout_second)
elsif END_BRACKETED_PASTE == buffer
@@in_bracketed_paste_mode = false
ungetc(-1)
- return inner_getc
+ return inner_getc(timeout_second)
end
- begin
- succ_c = nil
- Timeout.timeout(Reline.core.config.keyseq_timeout * 100) {
- succ_c = inner_getc
- }
- rescue Timeout::Error
- break
- else
+ succ_c = inner_getc(Reline.core.config.keyseq_timeout)
+
+ if succ_c
buffer << succ_c
+ else
+ break
end
end
buffer.bytes.reverse_each do |ch|
ungetc ch
end
- inner_getc
+ inner_getc(timeout_second)
end
- def self.getc
+ # if the usage expects to wait indefinitely, use Float::INFINITY for timeout_second
+ def self.getc(timeout_second)
if Reline.core.config.enable_bracketed_paste
- getc_with_bracketed_paste
+ getc_with_bracketed_paste(timeout_second)
else
- inner_getc
+ inner_getc(timeout_second)
end
end
def self.in_pasting?
- @@in_bracketed_paste_mode or (not Reline::IOGate.empty_buffer?)
+ @@in_bracketed_paste_mode or (not empty_buffer?)
end
def self.empty_buffer?
@@ -297,7 +315,7 @@ class Reline::ANSI
end
def self.hide_cursor
- if Reline::Terminfo.enabled?
+ if Reline::Terminfo.enabled? && Reline::Terminfo.term_supported?
begin
@@output.write Reline::Terminfo.tigetstr('civis')
rescue Reline::Terminfo::TerminfoError
@@ -309,7 +327,7 @@ class Reline::ANSI
end
def self.show_cursor
- if Reline::Terminfo.enabled?
+ if Reline::Terminfo.enabled? && Reline::Terminfo.term_supported?
begin
@@output.write Reline::Terminfo.tigetstr('cnorm')
rescue Reline::Terminfo::TerminfoError
@@ -324,9 +342,12 @@ class Reline::ANSI
@@output.write "\e[K"
end
+ # This only works when the cursor is at the bottom of the scroll range
+ # For more details, see https://github.com/ruby/reline/pull/577#issuecomment-1646679623
def self.scroll_down(x)
return if x.zero?
- @@output.write "\e[#{x}S"
+ # We use `\n` instead of CSI + S because CSI + S would cause https://github.com/ruby/reline/issues/576
+ @@output.write "\n" * x
end
def self.clear_screen
diff --git a/lib/reline/config.rb b/lib/reline/config.rb
index ffd1765e4f..d7564ba4b7 100644
--- a/lib/reline/config.rb
+++ b/lib/reline/config.rb
@@ -46,10 +46,6 @@ class Reline::Config
end
attr_accessor :autocompletion
- attr_reader :dialog_default_bg_color_sequence,
- :dialog_default_fg_color_sequence,
- :dialog_highlight_bg_color_sequence,
- :dialog_highlight_fg_color_sequence
def initialize
@additional_key_bindings = {} # from inputrc
@@ -57,8 +53,6 @@ class Reline::Config
@additional_key_bindings[:vi_insert] = {}
@additional_key_bindings[:vi_command] = {}
@oneshot_key_bindings = {}
- @skip_section = nil
- @if_stack = nil
@editing_mode_label = :emacs
@keymap_label = :emacs
@keymap_prefix = []
@@ -75,10 +69,6 @@ class Reline::Config
@test_mode = false
@autocompletion = false
@convert_meta = true if seven_bit_encoding?(Reline::IOGate.encoding)
- @dialog_default_bg_color_sequence = nil
- @dialog_highlight_bg_color_sequence = nil
- @dialog_default_fg_color_sequence = nil
- @dialog_highlight_fg_color_sequence = nil
end
def reset
@@ -101,66 +91,7 @@ class Reline::Config
end
def editing_mode_is?(*val)
- (val.respond_to?(:any?) ? val : [val]).any?(@editing_mode_label)
- end
-
- def dialog_default_bg_color=(color)
- @dialog_default_bg_color_sequence = dialog_color_to_code(:bg, color)
- end
-
- def dialog_default_fg_color=(color)
- @dialog_default_fg_color_sequence = dialog_color_to_code(:fg, color)
- end
-
- def dialog_highlight_bg_color=(color)
- @dialog_highlight_bg_color_sequence = dialog_color_to_code(:bg, color)
- end
-
- def dialog_highlight_fg_color=(color)
- @dialog_highlight_fg_color_sequence = dialog_color_to_code(:fg, color)
- end
-
- def dialog_default_bg_color
- dialog_code_to_color(:bg, @dialog_default_bg_color_sequence)
- end
-
- def dialog_default_fg_color
- dialog_code_to_color(:fg, @dialog_default_fg_color_sequence)
- end
-
- def dialog_highlight_bg_color
- dialog_code_to_color(:bg, @dialog_highlight_bg_color_sequence)
- end
-
- def dialog_highlight_fg_color
- dialog_code_to_color(:fg, @dialog_highlight_fg_color_sequence)
- end
-
- COLORS = [
- :black,
- :red,
- :green,
- :yellow,
- :blue,
- :magenta,
- :cyan,
- :white
- ].freeze
-
- private def dialog_color_to_code(type, color)
- base = type == :bg ? 40 : 30
- c = COLORS.index(color.to_sym)
-
- if c
- base + c
- else
- raise ArgumentError.new("Unknown color: #{color}.\nAvailable colors: #{COLORS.join(", ")}")
- end
- end
-
- private def dialog_code_to_color(type, code)
- base = type == :bg ? 40 : 30
- COLORS[code - base]
+ val.any?(@editing_mode_label)
end
def keymap
@@ -257,9 +188,7 @@ class Reline::Config
end
end
end
- conditions = [@skip_section, @if_stack]
- @skip_section = nil
- @if_stack = []
+ if_stack = []
lines.each_with_index do |line, no|
next if line.match(/\A\s*#/)
@@ -268,11 +197,11 @@ class Reline::Config
line = line.chomp.lstrip
if line.start_with?('$')
- handle_directive(line[1..-1], file, no)
+ handle_directive(line[1..-1], file, no, if_stack)
next
end
- next if @skip_section
+ next if if_stack.any? { |_no, skip| skip }
case line
when /^set +([^ ]+) +([^ ]+)/i
@@ -286,14 +215,12 @@ class Reline::Config
@additional_key_bindings[@keymap_label][@keymap_prefix + keystroke] = func
end
end
- unless @if_stack.empty?
- raise InvalidInputrc, "#{file}:#{@if_stack.last[1]}: unclosed if"
+ unless if_stack.empty?
+ raise InvalidInputrc, "#{file}:#{if_stack.last[0]}: unclosed if"
end
- ensure
- @skip_section, @if_stack = conditions
end
- def handle_directive(directive, file, no)
+ def handle_directive(directive, file, no, if_stack)
directive, args = directive.split(' ')
case directive
when 'if'
@@ -306,20 +233,19 @@ class Reline::Config
condition = true if args == 'Ruby'
condition = true if args == 'Reline'
end
- @if_stack << [file, no, @skip_section]
- @skip_section = !condition
+ if_stack << [no, !condition]
when 'else'
- if @if_stack.empty?
+ if if_stack.empty?
raise InvalidInputrc, "#{file}:#{no}: unmatched else"
end
- @skip_section = !@skip_section
+ if_stack.last[1] = !if_stack.last[1]
when 'endif'
- if @if_stack.empty?
+ if if_stack.empty?
raise InvalidInputrc, "#{file}:#{no}: unmatched endif"
end
- @skip_section = @if_stack.pop
+ if_stack.pop
when 'include'
- read(args)
+ read(File.expand_path(args))
end
end
@@ -395,14 +321,6 @@ class Reline::Config
@vi_ins_mode_string = retrieve_string(value)
when 'emacs-mode-string'
@emacs_mode_string = retrieve_string(value)
- when 'dialog-default-bg-color'
- self.dialog_default_bg_color = value
- when 'dialog-default-fg-color'
- self.dialog_default_fg_color = value
- when 'dialog-highlight-bg-color'
- self.dialog_highlight_bg_color = value
- when 'dialog-highlight-fg-color'
- self.dialog_highlight_fg_color = value
when *VARIABLE_NAMES then
variable_name = :"@#{name.tr(?-, ?_)}"
instance_variable_set(variable_name, value.nil? || value == '1' || value == 'on')
diff --git a/lib/reline/face.rb b/lib/reline/face.rb
new file mode 100644
index 0000000000..d07196e2e7
--- /dev/null
+++ b/lib/reline/face.rb
@@ -0,0 +1,199 @@
+# frozen_string_literal: true
+
+class Reline::Face
+ SGR_PARAMETERS = {
+ foreground: {
+ black: 30,
+ red: 31,
+ green: 32,
+ yellow: 33,
+ blue: 34,
+ magenta: 35,
+ cyan: 36,
+ white: 37,
+ bright_black: 90,
+ gray: 90,
+ bright_red: 91,
+ bright_green: 92,
+ bright_yellow: 93,
+ bright_blue: 94,
+ bright_magenta: 95,
+ bright_cyan: 96,
+ bright_white: 97
+ },
+ background: {
+ black: 40,
+ red: 41,
+ green: 42,
+ yellow: 43,
+ blue: 44,
+ magenta: 45,
+ cyan: 46,
+ white: 47,
+ bright_black: 100,
+ gray: 100,
+ bright_red: 101,
+ bright_green: 102,
+ bright_yellow: 103,
+ bright_blue: 104,
+ bright_magenta: 105,
+ bright_cyan: 106,
+ bright_white: 107,
+ },
+ style: {
+ reset: 0,
+ bold: 1,
+ faint: 2,
+ italicized: 3,
+ underlined: 4,
+ slowly_blinking: 5,
+ blinking: 5,
+ rapidly_blinking: 6,
+ negative: 7,
+ concealed: 8,
+ crossed_out: 9
+ }
+ }.freeze
+
+ class Config
+ ESSENTIAL_DEFINE_NAMES = %i(default enhanced scrollbar).freeze
+ RESET_SGR = "\e[0m".freeze
+
+ def initialize(name, &block)
+ @definition = {}
+ block.call(self)
+ ESSENTIAL_DEFINE_NAMES.each do |name|
+ @definition[name] ||= { style: :reset, escape_sequence: RESET_SGR }
+ end
+ end
+
+ attr_reader :definition
+
+ def define(name, **values)
+ values[:escape_sequence] = format_to_sgr(values.to_a).freeze
+ @definition[name] = values
+ end
+
+ def reconfigure
+ @definition.each_value do |values|
+ values.delete(:escape_sequence)
+ values[:escape_sequence] = format_to_sgr(values.to_a).freeze
+ end
+ end
+
+ def [](name)
+ @definition.dig(name, :escape_sequence) or raise ArgumentError, "unknown face: #{name}"
+ end
+
+ private
+
+ def sgr_rgb(key, value)
+ return nil unless rgb_expression?(value)
+ if Reline::Face.truecolor?
+ sgr_rgb_truecolor(key, value)
+ else
+ sgr_rgb_256color(key, value)
+ end
+ end
+
+ def sgr_rgb_truecolor(key, value)
+ case key
+ when :foreground
+ "38;2;"
+ when :background
+ "48;2;"
+ end + value[1, 6].scan(/../).map(&:hex).join(";")
+ end
+
+ def sgr_rgb_256color(key, value)
+ # 256 colors are
+ # 0..15: standard colors, hight intensity colors
+ # 16..232: 216 colors (R, G, B each 6 steps)
+ # 233..255: grayscale colors (24 steps)
+ # This methods converts rgb_expression to 216 colors
+ rgb = value[1, 6].scan(/../).map(&:hex)
+ # Color steps are [0, 95, 135, 175, 215, 255]
+ r, g, b = rgb.map { |v| v <= 95 ? v / 48 : (v - 35) / 40 }
+ color = (16 + 36 * r + 6 * g + b)
+ case key
+ when :foreground
+ "38;5;#{color}"
+ when :background
+ "48;5;#{color}"
+ end
+ end
+
+ def format_to_sgr(ordered_values)
+ sgr = "\e[" + ordered_values.map do |key_value|
+ key, value = key_value
+ case key
+ when :foreground, :background
+ case value
+ when Symbol
+ SGR_PARAMETERS[key][value]
+ when String
+ sgr_rgb(key, value)
+ end
+ when :style
+ [ value ].flatten.map do |style_name|
+ SGR_PARAMETERS[:style][style_name]
+ end.then do |sgr_parameters|
+ sgr_parameters.include?(nil) ? nil : sgr_parameters
+ end
+ end.then do |rendition_expression|
+ unless rendition_expression
+ raise ArgumentError, "invalid SGR parameter: #{value.inspect}"
+ end
+ rendition_expression
+ end
+ end.join(';') + "m"
+ sgr == RESET_SGR ? RESET_SGR : RESET_SGR + sgr
+ end
+
+ def rgb_expression?(color)
+ color.respond_to?(:match?) and color.match?(/\A#[0-9a-fA-F]{6}\z/)
+ end
+ end
+
+ private_constant :SGR_PARAMETERS, :Config
+
+ def self.truecolor?
+ @force_truecolor || %w[truecolor 24bit].include?(ENV['COLORTERM'])
+ end
+
+ def self.force_truecolor
+ @force_truecolor = true
+ @configs&.each_value(&:reconfigure)
+ end
+
+ def self.[](name)
+ @configs[name]
+ end
+
+ def self.config(name, &block)
+ @configs ||= {}
+ @configs[name] = Config.new(name, &block)
+ end
+
+ def self.configs
+ @configs.transform_values(&:definition)
+ end
+
+ def self.load_initial_configs
+ config(:default) do |conf|
+ conf.define :default, style: :reset
+ conf.define :enhanced, style: :reset
+ conf.define :scrollbar, style: :reset
+ end
+ config(:completion_dialog) do |conf|
+ conf.define :default, foreground: :bright_white, background: :gray
+ conf.define :enhanced, foreground: :black, background: :white
+ conf.define :scrollbar, foreground: :white, background: :gray
+ end
+ end
+
+ def self.reset_to_initial_configs
+ @configs = {}
+ load_initial_configs
+ end
+end
diff --git a/lib/reline/general_io.rb b/lib/reline/general_io.rb
index 3fafad5c6e..d52151ad3c 100644
--- a/lib/reline/general_io.rb
+++ b/lib/reline/general_io.rb
@@ -1,10 +1,15 @@
-require 'timeout'
require 'io/wait'
class Reline::GeneralIO
+ RESET_COLOR = '' # Do not send color reset sequence
+
def self.reset(encoding: nil)
@@pasting = false
- @@encoding = encoding
+ if encoding
+ @@encoding = encoding
+ elsif defined?(@@encoding)
+ remove_class_variable(:@@encoding)
+ end
end
def self.encoding
@@ -31,12 +36,17 @@ class Reline::GeneralIO
@@input = val
end
- def self.getc
+ def self.with_raw_input
+ yield
+ end
+
+ def self.getc(_timeout_second)
unless @@buf.empty?
return @@buf.shift
end
c = nil
loop do
+ Reline.core.line_editor.handle_signal
result = @@input.wait_readable(0.1)
next if result.nil?
c = @@input.read(1)
@@ -50,13 +60,19 @@ class Reline::GeneralIO
end
def self.get_screen_size
- [1, 1]
+ [24, 80]
end
def self.cursor_pos
Reline::CursorPos.new(1, 1)
end
+ def self.hide_cursor
+ end
+
+ def self.show_cursor
+ end
+
def self.move_cursor_column(val)
end
@@ -87,14 +103,6 @@ class Reline::GeneralIO
@@pasting
end
- def self.start_pasting
- @@pasting = true
- end
-
- def self.finish_pasting
- @@pasting = false
- end
-
def self.prep
end
diff --git a/lib/reline/history.rb b/lib/reline/history.rb
index 7a1ed6b90b..3f3b65fea6 100644
--- a/lib/reline/history.rb
+++ b/lib/reline/history.rb
@@ -62,7 +62,7 @@ class Reline::History < Array
private def check_index(index)
index += size if index < 0
if index < -2147483648 or 2147483647 < index
- raise RangeError.new("integer #{index} too big to convert to `int'")
+ raise RangeError.new("integer #{index} too big to convert to 'int'")
end
# If history_size is negative, history size is unlimited.
if @config.history_size.positive?
diff --git a/lib/reline/key_actor/emacs.rb b/lib/reline/key_actor/emacs.rb
index a561feee57..5d0a7fb63d 100644
--- a/lib/reline/key_actor/emacs.rb
+++ b/lib/reline/key_actor/emacs.rb
@@ -49,13 +49,13 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base
# 23 ^W
:em_kill_region,
# 24 ^X
- :ed_sequence_lead_in,
+ :ed_unassigned,
# 25 ^Y
:em_yank,
# 26 ^Z
:ed_ignore,
# 27 ^[
- :em_meta_next,
+ :ed_unassigned,
# 28 ^\
:ed_ignore,
# 29 ^]
@@ -319,9 +319,9 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base
# 158 M-^^
:ed_unassigned,
# 159 M-^_
- :em_copy_prev_word,
- # 160 M-SPACE
:ed_unassigned,
+ # 160 M-SPACE
+ :em_set_mark,
# 161 M-!
:ed_unassigned,
# 162 M-"
@@ -415,7 +415,7 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base
# 206 M-N
:vi_search_next,
# 207 M-O
- :ed_sequence_lead_in,
+ :ed_unassigned,
# 208 M-P
:vi_search_prev,
# 209 M-Q
@@ -431,15 +431,15 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base
# 214 M-V
:ed_unassigned,
# 215 M-W
- :em_copy_region,
+ :ed_unassigned,
# 216 M-X
- :ed_command,
- # 217 M-Y
:ed_unassigned,
+ # 217 M-Y
+ :em_yank_pop,
# 218 M-Z
:ed_unassigned,
# 219 M-[
- :ed_sequence_lead_in,
+ :ed_unassigned,
# 220 M-\
:ed_unassigned,
# 221 M-]
@@ -495,9 +495,9 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base
# 246 M-v
:ed_unassigned,
# 247 M-w
- :em_copy_region,
+ :ed_unassigned,
# 248 M-x
- :ed_command,
+ :ed_unassigned,
# 249 M-y
:ed_unassigned,
# 250 M-z
diff --git a/lib/reline/key_actor/vi_command.rb b/lib/reline/key_actor/vi_command.rb
index 98146d2f77..06bb0ba8e4 100644
--- a/lib/reline/key_actor/vi_command.rb
+++ b/lib/reline/key_actor/vi_command.rb
@@ -17,7 +17,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
# 7 ^G
:ed_unassigned,
# 8 ^H
- :ed_unassigned,
+ :ed_prev_char,
# 9 ^I
:ed_unassigned,
# 10 ^J
@@ -41,7 +41,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
# 19 ^S
:ed_ignore,
# 20 ^T
- :ed_unassigned,
+ :ed_transpose_chars,
# 21 ^U
:vi_kill_line_prev,
# 22 ^V
@@ -51,7 +51,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
# 24 ^X
:ed_unassigned,
# 25 ^Y
- :ed_unassigned,
+ :em_yank,
# 26 ^Z
:ed_unassigned,
# 27 ^[
@@ -75,7 +75,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
# 36 $
:ed_move_to_end,
# 37 %
- :vi_match,
+ :ed_unassigned,
# 38 &
:ed_unassigned,
# 39 '
@@ -89,11 +89,11 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
# 43 +
:ed_next_history,
# 44 ,
- :vi_repeat_prev_char,
+ :ed_unassigned,
# 45 -
:ed_prev_history,
# 46 .
- :vi_redo,
+ :ed_unassigned,
# 47 /
:vi_search_prev,
# 48 0
@@ -117,9 +117,9 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
# 57 9
:ed_argument_digit,
# 58 :
- :ed_command,
+ :ed_unassigned,
# 59 ;
- :vi_repeat_next_char,
+ :ed_unassigned,
# 60 <
:ed_unassigned,
# 61 =
@@ -157,21 +157,21 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
# 77 M
:ed_unassigned,
# 78 N
- :vi_repeat_search_prev,
+ :ed_unassigned,
# 79 O
- :ed_sequence_lead_in,
+ :ed_unassigned,
# 80 P
:vi_paste_prev,
# 81 Q
:ed_unassigned,
# 82 R
- :vi_replace_mode,
+ :ed_unassigned,
# 83 S
- :vi_substitute_line,
+ :ed_unassigned,
# 84 T
:vi_to_prev_char,
# 85 U
- :vi_undo_line,
+ :ed_unassigned,
# 86 V
:ed_unassigned,
# 87 W
@@ -179,11 +179,11 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
# 88 X
:ed_delete_prev_char,
# 89 Y
- :vi_yank_end,
+ :ed_unassigned,
# 90 Z
:ed_unassigned,
# 91 [
- :ed_sequence_lead_in,
+ :ed_unassigned,
# 92 \
:ed_unassigned,
# 93 ]
@@ -191,7 +191,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
# 94 ^
:vi_first_print,
# 95 _
- :vi_history_word,
+ :ed_unassigned,
# 96 `
:ed_unassigned,
# 97 a
@@ -221,7 +221,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
# 109 m
:ed_unassigned,
# 110 n
- :vi_repeat_search_next,
+ :ed_unassigned,
# 111 o
:ed_unassigned,
# 112 p
@@ -231,11 +231,11 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
# 114 r
:vi_replace_char,
# 115 s
- :vi_substitute_char,
+ :ed_unassigned,
# 116 t
:vi_to_next_char,
# 117 u
- :vi_undo,
+ :ed_unassigned,
# 118 v
:vi_histedit,
# 119 w
@@ -253,9 +253,9 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
# 125 }
:ed_unassigned,
# 126 ~
- :vi_change_case,
- # 127 ^?
:ed_unassigned,
+ # 127 ^?
+ :em_delete_prev_char,
# 128 M-^@
:ed_unassigned,
# 129 M-^A
@@ -415,7 +415,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
# 206 M-N
:ed_unassigned,
# 207 M-O
- :ed_sequence_lead_in,
+ :ed_unassigned,
# 208 M-P
:ed_unassigned,
# 209 M-Q
@@ -439,7 +439,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
# 218 M-Z
:ed_unassigned,
# 219 M-[
- :ed_sequence_lead_in,
+ :ed_unassigned,
# 220 M-\
:ed_unassigned,
# 221 M-]
diff --git a/lib/reline/key_actor/vi_insert.rb b/lib/reline/key_actor/vi_insert.rb
index b8e89f81d8..c3d7f9c12d 100644
--- a/lib/reline/key_actor/vi_insert.rb
+++ b/lib/reline/key_actor/vi_insert.rb
@@ -41,7 +41,7 @@ class Reline::KeyActor::ViInsert < Reline::KeyActor::Base
# 19 ^S
:vi_search_next,
# 20 ^T
- :ed_insert,
+ :ed_transpose_chars,
# 21 ^U
:vi_kill_line_prev,
# 22 ^V
@@ -51,7 +51,7 @@ class Reline::KeyActor::ViInsert < Reline::KeyActor::Base
# 24 ^X
:ed_insert,
# 25 ^Y
- :ed_insert,
+ :em_yank,
# 26 ^Z
:ed_insert,
# 27 ^[
diff --git a/lib/reline/key_stroke.rb b/lib/reline/key_stroke.rb
index c1c61513a9..bceffbb53f 100644
--- a/lib/reline/key_stroke.rb
+++ b/lib/reline/key_stroke.rb
@@ -1,4 +1,8 @@
class Reline::KeyStroke
+ ESC_BYTE = 27
+ CSI_PARAMETER_BYTES_RANGE = 0x30..0x3f
+ CSI_INTERMEDIATE_BYTES_RANGE = (0x20..0x2f)
+
def initialize(config)
@config = config
end
@@ -73,17 +77,26 @@ class Reline::KeyStroke
return :matched if it.max_by(&:size)&.size&.< input.size
return :matching if it.size > 1
}
- key_mapping.keys.select { |lhs|
- start_with?(input, lhs)
- }.tap { |it|
- return it.size > 0 ? :matched : :unmatched
- }
+ if key_mapping.keys.any? { |lhs| start_with?(input, lhs) }
+ :matched
+ else
+ match_unknown_escape_sequence(input).first
+ end
end
def expand(input)
- input = compress_meta_key(input)
lhs = key_mapping.keys.select { |item| start_with?(input, item) }.sort_by(&:size).last
- return input unless lhs
+ unless lhs
+ status, size = match_unknown_escape_sequence(input)
+ case status
+ when :matched
+ return [:ed_unassigned] + expand(input.drop(size))
+ when :matching
+ return [:ed_unassigned]
+ else
+ return input
+ end
+ end
rhs = key_mapping[lhs]
case rhs
@@ -99,6 +112,36 @@ class Reline::KeyStroke
private
+ # returns match status of CSI/SS3 sequence and matched length
+ def match_unknown_escape_sequence(input)
+ idx = 0
+ return [:unmatched, nil] unless input[idx] == ESC_BYTE
+ idx += 1
+ idx += 1 if input[idx] == ESC_BYTE
+
+ case input[idx]
+ when nil
+ return [:matching, nil]
+ when 91 # == '['.ord
+ # CSI sequence
+ idx += 1
+ idx += 1 while idx < input.size && CSI_PARAMETER_BYTES_RANGE.cover?(input[idx])
+ idx += 1 while idx < input.size && CSI_INTERMEDIATE_BYTES_RANGE.cover?(input[idx])
+ input[idx] ? [:matched, idx + 1] : [:matching, nil]
+ when 79 # == 'O'.ord
+ # SS3 sequence
+ input[idx + 1] ? [:matched, idx + 2] : [:matching, nil]
+ else
+ if idx == 1
+ # `ESC char`, make it :unmatched so that it will be handled correctly in `read_2nd_character_of_key_sequence`
+ [:unmatched, nil]
+ else
+ # `ESC ESC char`
+ [:matched, idx + 1]
+ end
+ end
+ end
+
def key_mapping
@config.key_bindings
end
diff --git a/lib/reline/kill_ring.rb b/lib/reline/kill_ring.rb
index bb3684b42b..201f6f3ca0 100644
--- a/lib/reline/kill_ring.rb
+++ b/lib/reline/kill_ring.rb
@@ -14,7 +14,7 @@ class Reline::KillRing
end
def ==(other)
- object_id == other.object_id
+ equal?(other)
end
end
@@ -68,7 +68,7 @@ class Reline::KillRing
def append(string, before_p = false)
case @state
when State::FRESH, State::YANK
- @ring << RingPoint.new(string)
+ @ring << RingPoint.new(+string)
@state = State::CONTINUED
when State::CONTINUED, State::PROCESSED
if before_p
diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb
index 8d0719ef7c..81413505d7 100644
--- a/lib/reline/line_editor.rb
+++ b/lib/reline/line_editor.rb
@@ -6,7 +6,6 @@ require 'tempfile'
class Reline::LineEditor
# TODO: undo
# TODO: Use "private alias_method" idiom after drop Ruby 2.5.
- attr_reader :line
attr_reader :byte_pointer
attr_accessor :confirm_multiline_termination_proc
attr_accessor :completion_proc
@@ -14,7 +13,6 @@ class Reline::LineEditor
attr_accessor :output_modifier_proc
attr_accessor :prompt_proc
attr_accessor :auto_indent_proc
- attr_accessor :pre_input_hook
attr_accessor :dig_perfect_match_proc
attr_writer :output
@@ -35,98 +33,93 @@ class Reline::LineEditor
vi_next_big_word
vi_prev_big_word
vi_end_big_word
- vi_repeat_next_char
- vi_repeat_prev_char
}
module CompletionState
NORMAL = :normal
COMPLETION = :completion
MENU = :menu
- JOURNEY = :journey
MENU_WITH_PERFECT_MATCH = :menu_with_perfect_match
PERFECT_MATCH = :perfect_match
end
- CompletionJourneyData = Struct.new('CompletionJourneyData', :preposing, :postposing, :list, :pointer)
- MenuInfo = Struct.new('MenuInfo', :target, :list)
+ RenderedScreen = Struct.new(:base_y, :lines, :cursor_y, keyword_init: true)
- PROMPT_LIST_CACHE_TIMEOUT = 0.5
+ CompletionJourneyState = Struct.new(:line_index, :pre, :target, :post, :list, :pointer)
+
+ class MenuInfo
+ attr_reader :list
+
+ def initialize(list)
+ @list = list
+ end
+
+ def lines(screen_width)
+ return [] if @list.empty?
+
+ list = @list.sort
+ sizes = list.map { |item| Reline::Unicode.calculate_width(item) }
+ item_width = sizes.max + 2
+ num_cols = [screen_width / item_width, 1].max
+ num_rows = list.size.fdiv(num_cols).ceil
+ list_with_padding = list.zip(sizes).map { |item, size| item + ' ' * (item_width - size) }
+ aligned = (list_with_padding + [nil] * (num_rows * num_cols - list_with_padding.size)).each_slice(num_rows).to_a.transpose
+ aligned.map do |row|
+ row.join.rstrip
+ end
+ end
+ end
+
+ MINIMUM_SCROLLBAR_HEIGHT = 1
def initialize(config, encoding)
@config = config
@completion_append_character = ''
+ @screen_size = Reline::IOGate.get_screen_size
reset_variables(encoding: encoding)
end
- def set_pasting_state(in_pasting)
- @in_pasting = in_pasting
+ def io_gate
+ Reline::IOGate
end
- def simplified_rendering?
- if finished?
- false
- elsif @just_cursor_moving and not @rerender_all
- true
- else
- not @rerender_all and not finished? and @in_pasting
- end
+ def set_pasting_state(in_pasting)
+ # While pasting, text to be inserted is stored to @continuous_insertion_buffer.
+ # After pasting, this buffer should be force inserted.
+ process_insert(force: true) if @in_pasting && !in_pasting
+ @in_pasting = in_pasting
end
private def check_mode_string
- mode_string = nil
if @config.show_mode_in_prompt
if @config.editing_mode_is?(:vi_command)
- mode_string = @config.vi_cmd_mode_string
+ @config.vi_cmd_mode_string
elsif @config.editing_mode_is?(:vi_insert)
- mode_string = @config.vi_ins_mode_string
+ @config.vi_ins_mode_string
elsif @config.editing_mode_is?(:emacs)
- mode_string = @config.emacs_mode_string
+ @config.emacs_mode_string
else
- mode_string = '?'
+ '?'
end
end
- if mode_string != @prev_mode_string
- @rerender_all = true
- end
- @prev_mode_string = mode_string
- mode_string
end
- private def check_multiline_prompt(buffer)
+ private def check_multiline_prompt(buffer, mode_string)
if @vi_arg
prompt = "(arg: #{@vi_arg}) "
- @rerender_all = true
elsif @searching_prompt
prompt = @searching_prompt
- @rerender_all = true
else
prompt = @prompt
end
- if simplified_rendering?
+ if !@is_multiline
mode_string = check_mode_string
prompt = mode_string + prompt if mode_string
- return [prompt, calculate_width(prompt, true), [prompt] * buffer.size]
- end
- if @prompt_proc
- use_cached_prompt_list = false
- if @cached_prompt_list
- if @just_cursor_moving
- use_cached_prompt_list = true
- elsif Time.now.to_f < (@prompt_cache_time + PROMPT_LIST_CACHE_TIMEOUT) and buffer.size == @cached_prompt_list.size
- use_cached_prompt_list = true
- end
- end
- use_cached_prompt_list = false if @rerender_all
- if use_cached_prompt_list
- prompt_list = @cached_prompt_list
- else
- prompt_list = @cached_prompt_list = @prompt_proc.(buffer).map { |pr| pr.gsub("\n", "\\n") }
- @prompt_cache_time = Time.now.to_f
- end
+ [prompt] + [''] * (buffer.size - 1)
+ elsif @prompt_proc
+ prompt_list = @prompt_proc.(buffer).map { |pr| pr.gsub("\n", "\\n") }
prompt_list.map!{ prompt } if @vi_arg or @searching_prompt
prompt_list = [prompt] if prompt_list.empty?
- mode_string = check_mode_string
prompt_list = prompt_list.map{ |pr| mode_string + pr } if mode_string
prompt = prompt_list[@line_index]
prompt = prompt_list[0] if prompt.nil?
@@ -136,24 +129,17 @@ class Reline::LineEditor
prompt_list << prompt_list.last
end
end
- prompt_width = calculate_width(prompt, true)
- [prompt, prompt_width, prompt_list]
+ prompt_list
else
- mode_string = check_mode_string
prompt = mode_string + prompt if mode_string
- prompt_width = calculate_width(prompt, true)
- [prompt, prompt_width, nil]
+ [prompt] * buffer.size
end
end
def reset(prompt = '', encoding:)
- @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
@screen_size = Reline::IOGate.get_screen_size
- @screen_height = @screen_size.first
reset_variables(prompt, encoding: encoding)
- Reline::IOGate.set_winch_handler do
- @resized = true
- end
+ @rendered_screen.base_y = Reline::IOGate.cursor_pos.y
if ENV.key?('RELINE_ALT_SCROLLBAR')
@full_block = '::'
@upper_half_block = "''"
@@ -177,82 +163,57 @@ class Reline::LineEditor
end
end
- def resize
+ def handle_signal
+ handle_interrupted
+ handle_resized
+ end
+
+ private def handle_resized
return unless @resized
- @resized = false
- @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
- old_screen_size = @screen_size
+
@screen_size = Reline::IOGate.get_screen_size
- @screen_height = @screen_size.first
- if old_screen_size.last < @screen_size.last # columns increase
- @rerender_all = true
- rerender
+ @resized = false
+ scroll_into_view
+ Reline::IOGate.move_cursor_up @rendered_screen.cursor_y
+ @rendered_screen.base_y = Reline::IOGate.cursor_pos.y
+ @rendered_screen.lines = []
+ @rendered_screen.cursor_y = 0
+ render_differential
+ end
+
+ private def handle_interrupted
+ return unless @interrupted
+
+ @interrupted = false
+ clear_dialogs
+ scrolldown = render_differential
+ Reline::IOGate.scroll_down scrolldown
+ Reline::IOGate.move_cursor_column 0
+ @rendered_screen.lines = []
+ @rendered_screen.cursor_y = 0
+ case @old_trap
+ when 'DEFAULT', 'SYSTEM_DEFAULT'
+ raise Interrupt
+ when 'IGNORE'
+ # Do nothing
+ when 'EXIT'
+ exit
else
- back = 0
- new_buffer = whole_lines
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer)
- new_buffer.each_with_index do |line, index|
- prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
- width = prompt_width + calculate_width(line)
- height = calculate_height_by_width(width)
- back += height
- end
- @highest_in_all = back
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
- @first_line_started_from =
- if @line_index.zero?
- 0
- else
- calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
- end
- if @prompt_proc
- prompt = prompt_list[@line_index]
- prompt_width = calculate_width(prompt, true)
- end
- calculate_nearest_cursor
- @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
- @rerender_all = true
+ @old_trap.call if @old_trap.respond_to?(:call)
end
end
def set_signal_handlers
- @old_trap = Signal.trap('INT') {
- clear_dialog
- if @scroll_partial_screen
- move_cursor_down(@screen_height - (@line_index - @scroll_partial_screen) - 1)
- else
- move_cursor_down(@highest_in_all - @line_index - 1)
- end
- Reline::IOGate.move_cursor_column(0)
- scroll_down(1)
- case @old_trap
- when 'DEFAULT', 'SYSTEM_DEFAULT'
- raise Interrupt
- when 'IGNORE'
- # Do nothing
- when 'EXIT'
- exit
- else
- @old_trap.call if @old_trap.respond_to?(:call)
- end
- }
- begin
- @old_tstp_trap = Signal.trap('TSTP') {
- Reline::IOGate.ungetc("\C-z".ord)
- @old_tstp_trap.call if @old_tstp_trap.respond_to?(:call)
- }
- rescue ArgumentError
+ Reline::IOGate.set_winch_handler do
+ @resized = true
+ end
+ @old_trap = Signal.trap('INT') do
+ @interrupted = true
end
end
def finalize
Signal.trap('INT', @old_trap)
- begin
- Signal.trap('TSTP', @old_tstp_trap)
- rescue ArgumentError
- end
end
def eof?
@@ -265,55 +226,42 @@ class Reline::LineEditor
@encoding = encoding
@is_multiline = false
@finished = false
- @cleared = false
- @rerender_all = false
@history_pointer = nil
@kill_ring ||= Reline::KillRing.new
@vi_clipboard = ''
@vi_arg = nil
@waiting_proc = nil
- @waiting_operator_proc = nil
- @waiting_operator_vi_arg = nil
- @completion_journey_data = nil
+ @vi_waiting_operator = nil
+ @vi_waiting_operator_arg = nil
+ @completion_journey_state = nil
@completion_state = CompletionState::NORMAL
+ @completion_occurs = false
@perfect_matched = nil
@menu_info = nil
- @first_prompt = true
@searching_prompt = nil
@first_char = true
- @add_newline_to_end_of_buffer = false
- @just_cursor_moving = nil
- @cached_prompt_list = nil
- @prompt_cache_time = nil
+ @just_cursor_moving = false
@eof = false
@continuous_insertion_buffer = String.new(encoding: @encoding)
- @scroll_partial_screen = nil
- @prev_mode_string = nil
+ @scroll_partial_screen = 0
@drop_terminate_spaces = false
@in_pasting = false
@auto_indent_proc = nil
@dialogs = []
- @last_key = nil
+ @interrupted = false
@resized = false
+ @cache = {}
+ @rendered_screen = RenderedScreen.new(base_y: 0, lines: [], cursor_y: 0)
reset_line
end
def reset_line
- @cursor = 0
- @cursor_max = 0
@byte_pointer = 0
@buffer_of_lines = [String.new(encoding: @encoding)]
@line_index = 0
- @previous_line_index = nil
- @line = @buffer_of_lines[0]
- @first_line_started_from = 0
- @move_up = 0
- @started_from = 0
- @highest_in_this = 1
- @highest_in_all = 1
+ @cache.clear
@line_backup_in_history = nil
@multibyte_buffer = String.new(encoding: 'ASCII-8BIT')
- @check_new_auto_indent = false
end
def multiline_on
@@ -324,68 +272,44 @@ class Reline::LineEditor
@is_multiline = false
end
- private def calculate_height_by_lines(lines, prompt)
- result = 0
- prompt_list = prompt.is_a?(Array) ? prompt : nil
- lines.each_with_index { |line, i|
- prompt = prompt_list[i] if prompt_list and prompt_list[i]
- result += calculate_height_by_width(calculate_width(prompt, true) + calculate_width(line))
- }
- result
- end
-
private def insert_new_line(cursor_line, next_line)
- @line = cursor_line
@buffer_of_lines.insert(@line_index + 1, String.new(next_line, encoding: @encoding))
- @previous_line_index = @line_index
+ @buffer_of_lines[@line_index] = cursor_line
@line_index += 1
- @just_cursor_moving = false
- end
-
- private def calculate_height_by_width(width)
- width.div(@screen_size.last) + 1
- end
-
- private def split_by_width(str, max_width)
- Reline::Unicode.split_by_width(str, max_width, @encoding)
- end
-
- private def scroll_down(val)
- if val <= @rest_height
- Reline::IOGate.move_cursor_down(val)
- @rest_height -= val
- else
- Reline::IOGate.move_cursor_down(@rest_height)
- Reline::IOGate.scroll_down(val - @rest_height)
- @rest_height = 0
+ @byte_pointer = 0
+ if @auto_indent_proc && !@in_pasting
+ if next_line.empty?
+ (
+ # For compatibility, use this calculation instead of just `process_auto_indent @line_index - 1, cursor_dependent: false`
+ indent1 = @auto_indent_proc.(@buffer_of_lines.take(@line_index - 1).push(''), @line_index - 1, 0, true)
+ indent2 = @auto_indent_proc.(@buffer_of_lines.take(@line_index), @line_index - 1, @buffer_of_lines[@line_index - 1].bytesize, false)
+ indent = indent2 || indent1
+ @buffer_of_lines[@line_index - 1] = ' ' * indent + @buffer_of_lines[@line_index - 1].gsub(/\A */, '')
+ )
+ process_auto_indent @line_index, add_newline: true
+ else
+ process_auto_indent @line_index - 1, cursor_dependent: false
+ process_auto_indent @line_index, add_newline: true # Need for compatibility
+ process_auto_indent @line_index, cursor_dependent: false
+ end
end
end
- private def move_cursor_up(val)
- if val > 0
- Reline::IOGate.move_cursor_up(val)
- @rest_height += val
- elsif val < 0
- move_cursor_down(-val)
- end
+ private def split_by_width(str, max_width, offset: 0)
+ Reline::Unicode.split_by_width(str, max_width, @encoding, offset: offset)
end
- private def move_cursor_down(val)
- if val > 0
- Reline::IOGate.move_cursor_down(val)
- @rest_height -= val
- @rest_height = 0 if @rest_height < 0
- elsif val < 0
- move_cursor_up(-val)
- end
+ def current_byte_pointer_cursor
+ calculate_width(current_line.byteslice(0, @byte_pointer))
end
- private def calculate_nearest_cursor(line_to_calc = @line, cursor = @cursor, started_from = @started_from, byte_pointer = @byte_pointer, update = true)
+ private def calculate_nearest_cursor(cursor)
+ line_to_calc = current_line
new_cursor_max = calculate_width(line_to_calc)
new_cursor = 0
new_byte_pointer = 0
height = 1
- max_width = @screen_size.last
+ max_width = screen_width
if @config.editing_mode_is?(:vi_command)
last_byte_size = Reline::Unicode.get_prev_mbchar_size(line_to_calc, line_to_calc.bytesize)
if last_byte_size > 0
@@ -411,120 +335,237 @@ class Reline::LineEditor
end
new_byte_pointer += gc.bytesize
end
- new_started_from = height - 1
- if update
- @cursor = new_cursor
- @cursor_max = new_cursor_max
- @started_from = new_started_from
- @byte_pointer = new_byte_pointer
- else
- [new_cursor, new_cursor_max, new_started_from, new_byte_pointer]
+ @byte_pointer = new_byte_pointer
+ end
+
+ def with_cache(key, *deps)
+ cached_deps, value = @cache[key]
+ if cached_deps != deps
+ @cache[key] = [deps, value = yield(*deps, cached_deps, value)]
end
+ value
end
- def rerender_all
- @rerender_all = true
- process_insert(force: true)
- rerender
+ def modified_lines
+ with_cache(__method__, whole_lines, finished?) do |whole, complete|
+ modify_lines(whole, complete)
+ end
end
- def rerender
- return if @line.nil?
- if @menu_info
- scroll_down(@highest_in_all - @first_line_started_from)
- @rerender_all = true
+ def prompt_list
+ with_cache(__method__, whole_lines, check_mode_string, @vi_arg, @searching_prompt) do |lines, mode_string|
+ check_multiline_prompt(lines, mode_string)
end
- if @menu_info
- show_menu
- @menu_info = nil
+ end
+
+ def screen_height
+ @screen_size.first
+ end
+
+ def screen_width
+ @screen_size.last
+ end
+
+ def screen_scroll_top
+ @scroll_partial_screen
+ end
+
+ def wrapped_prompt_and_input_lines
+ with_cache(__method__, @buffer_of_lines.size, modified_lines, prompt_list, screen_width) do |n, lines, prompts, width, prev_cache_key, cached_value|
+ prev_n, prev_lines, prev_prompts, prev_width = prev_cache_key
+ cached_wraps = {}
+ if prev_width == width
+ prev_n.times do |i|
+ cached_wraps[[prev_prompts[i], prev_lines[i]]] = cached_value[i]
+ end
+ end
+
+ n.times.map do |i|
+ prompt = prompts[i] || ''
+ line = lines[i] || ''
+ if (cached = cached_wraps[[prompt, line]])
+ next cached
+ end
+ *wrapped_prompts, code_line_prompt = split_by_width(prompt, width).first.compact
+ wrapped_lines = split_by_width(line, width, offset: calculate_width(code_line_prompt)).first.compact
+ wrapped_prompts.map { |p| [p, ''] } + [[code_line_prompt, wrapped_lines.first]] + wrapped_lines.drop(1).map { |c| ['', c] }
+ end
end
- prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
- if @cleared
- clear_screen_buffer(prompt, prompt_list, prompt_width)
- @cleared = false
- return
+ end
+
+ def calculate_overlay_levels(overlay_levels)
+ levels = []
+ overlay_levels.each do |x, w, l|
+ levels.fill(l, x, w)
end
- if @is_multiline and finished? and @scroll_partial_screen
- # Re-output all code higher than the screen when finished.
- Reline::IOGate.move_cursor_up(@first_line_started_from + @started_from - @scroll_partial_screen)
- Reline::IOGate.move_cursor_column(0)
- @scroll_partial_screen = nil
- prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
- if @previous_line_index
- new_lines = whole_lines(index: @previous_line_index, line: @line)
+ levels
+ end
+
+ def render_line_differential(old_items, new_items)
+ old_levels = calculate_overlay_levels(old_items.zip(new_items).each_with_index.map {|((x, w, c), (nx, _nw, nc)), i| [x, w, c == nc && x == nx ? i : -1] if x }.compact)
+ new_levels = calculate_overlay_levels(new_items.each_with_index.map { |(x, w), i| [x, w, i] if x }.compact).take(screen_width)
+ base_x = 0
+ new_levels.zip(old_levels).chunk { |n, o| n == o ? :skip : n || :blank }.each do |level, chunk|
+ width = chunk.size
+ if level == :skip
+ # do nothing
+ elsif level == :blank
+ Reline::IOGate.move_cursor_column base_x
+ @output.write "#{Reline::IOGate::RESET_COLOR}#{' ' * width}"
else
- new_lines = whole_lines
+ x, w, content = new_items[level]
+ content = Reline::Unicode.take_range(content, base_x - x, width) unless x == base_x && w == width
+ Reline::IOGate.move_cursor_column base_x
+ @output.write "#{Reline::IOGate::RESET_COLOR}#{content}#{Reline::IOGate::RESET_COLOR}"
end
- modify_lines(new_lines).each_with_index do |line, index|
- @output.write "#{prompt_list ? prompt_list[index] : prompt}#{line}\n"
- Reline::IOGate.erase_after_cursor
+ base_x += width
+ end
+ if old_levels.size > new_levels.size
+ Reline::IOGate.move_cursor_column new_levels.size
+ Reline::IOGate.erase_after_cursor
+ end
+ end
+
+ # Calculate cursor position in word wrapped content.
+ def wrapped_cursor_position
+ prompt_width = calculate_width(prompt_list[@line_index], true)
+ line_before_cursor = whole_lines[@line_index].byteslice(0, @byte_pointer)
+ wrapped_line_before_cursor = split_by_width(' ' * prompt_width + line_before_cursor, screen_width).first.compact
+ wrapped_cursor_y = wrapped_prompt_and_input_lines[0...@line_index].sum(&:size) + wrapped_line_before_cursor.size - 1
+ wrapped_cursor_x = calculate_width(wrapped_line_before_cursor.last)
+ [wrapped_cursor_x, wrapped_cursor_y]
+ end
+
+ def clear_dialogs
+ @dialogs.each do |dialog|
+ dialog.contents = nil
+ dialog.trap_key = nil
+ end
+ end
+
+ def update_dialogs(key = nil)
+ wrapped_cursor_x, wrapped_cursor_y = wrapped_cursor_position
+ @dialogs.each do |dialog|
+ dialog.trap_key = nil
+ update_each_dialog(dialog, wrapped_cursor_x, wrapped_cursor_y - screen_scroll_top, key)
+ end
+ end
+
+ def render_finished
+ clear_rendered_lines
+ render_full_content
+ end
+
+ def clear_rendered_lines
+ Reline::IOGate.move_cursor_up @rendered_screen.cursor_y
+ Reline::IOGate.move_cursor_column 0
+
+ num_lines = @rendered_screen.lines.size
+ return unless num_lines && num_lines >= 1
+
+ Reline::IOGate.move_cursor_down num_lines - 1
+ (num_lines - 1).times do
+ Reline::IOGate.erase_after_cursor
+ Reline::IOGate.move_cursor_up 1
+ end
+ Reline::IOGate.erase_after_cursor
+ @rendered_screen.lines = []
+ @rendered_screen.cursor_y = 0
+ end
+
+ def render_full_content
+ lines = @buffer_of_lines.size.times.map do |i|
+ line = prompt_list[i] + modified_lines[i]
+ wrapped_lines, = split_by_width(line, screen_width)
+ wrapped_lines.last.empty? ? "#{line} " : line
+ end
+ @output.puts lines.map { |l| "#{l}\r\n" }.join
+ end
+
+ def print_nomultiline_prompt(prompt)
+ return unless prompt && !@is_multiline
+
+ # Readline's test `TestRelineAsReadline#test_readline` requires first output to be prompt, not cursor reset escape sequence.
+ @rendered_screen.lines = [[[0, Reline::Unicode.calculate_width(prompt, true), prompt]]]
+ @rendered_screen.cursor_y = 0
+ @output.write prompt
+ end
+
+ def render_differential
+ wrapped_cursor_x, wrapped_cursor_y = wrapped_cursor_position
+
+ rendered_lines = @rendered_screen.lines
+ new_lines = wrapped_prompt_and_input_lines.flatten(1)[screen_scroll_top, screen_height].map do |prompt, line|
+ prompt_width = Reline::Unicode.calculate_width(prompt, true)
+ [[0, prompt_width, prompt], [prompt_width, Reline::Unicode.calculate_width(line, true), line]]
+ end
+ if @menu_info
+ @menu_info.lines(screen_width).each do |item|
+ new_lines << [[0, Reline::Unicode.calculate_width(item), item]]
end
- @output.flush
- clear_dialog
- return
+ @menu_info = nil # TODO: do not change state here
end
- new_highest_in_this = calculate_height_by_width(prompt_width + calculate_width(@line.nil? ? '' : @line))
- rendered = false
- if @add_newline_to_end_of_buffer
- clear_dialog_with_content
- rerender_added_newline(prompt, prompt_width)
- @add_newline_to_end_of_buffer = false
- else
- if @just_cursor_moving and not @rerender_all
- clear_dialog_with_content
- rendered = just_move_cursor
- @just_cursor_moving = false
- return
- elsif @previous_line_index or new_highest_in_this != @highest_in_this
- clear_dialog_with_content
- rerender_changed_current_line
- @previous_line_index = nil
- rendered = true
- elsif @rerender_all
- rerender_all_lines
- @rerender_all = false
- rendered = true
- else
+
+ @dialogs.each_with_index do |dialog, index|
+ next unless dialog.contents
+
+ x_range, y_range = dialog_range dialog, wrapped_cursor_y - screen_scroll_top
+ y_range.each do |row|
+ next if row < 0 || row >= screen_height
+ dialog_rows = new_lines[row] ||= []
+ # index 0 is for prompt, index 1 is for line, index 2.. is for dialog
+ dialog_rows[index + 2] = [x_range.begin, dialog.width, dialog.contents[row - y_range.begin]]
end
end
- if @is_multiline
- if finished?
- # Always rerender on finish because output_modifier_proc may return a different output.
- if @previous_line_index
- new_lines = whole_lines(index: @previous_line_index, line: @line)
- else
- new_lines = whole_lines
- end
- line = modify_lines(new_lines)[@line_index]
- clear_dialog
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines)
- render_partial(prompt, prompt_width, line, @first_line_started_from)
- move_cursor_down(@highest_in_all - (@first_line_started_from + @highest_in_this - 1) - 1)
- scroll_down(1)
- Reline::IOGate.move_cursor_column(0)
- Reline::IOGate.erase_after_cursor
- else
- if not rendered and not @in_pasting
- line = modify_lines(whole_lines)[@line_index]
- prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
- render_partial(prompt, prompt_width, line, @first_line_started_from)
- end
- render_dialog((prompt_width + @cursor) % @screen_size.last)
+
+ cursor_y = @rendered_screen.cursor_y
+ if new_lines != rendered_lines
+ # Hide cursor while rendering to avoid cursor flickering.
+ Reline::IOGate.hide_cursor
+ num_lines = [[new_lines.size, rendered_lines.size].max, screen_height].min
+ if @rendered_screen.base_y + num_lines > screen_height
+ Reline::IOGate.scroll_down(num_lines - cursor_y - 1)
+ @rendered_screen.base_y = screen_height - num_lines
+ cursor_y = num_lines - 1
end
- @buffer_of_lines[@line_index] = @line
- @rest_height = 0 if @scroll_partial_screen
- else
- line = modify_lines(whole_lines)[@line_index]
- render_partial(prompt, prompt_width, line, 0)
- if finished?
- scroll_down(1)
- Reline::IOGate.move_cursor_column(0)
- Reline::IOGate.erase_after_cursor
+ num_lines.times do |i|
+ rendered_line = rendered_lines[i] || []
+ line_to_render = new_lines[i] || []
+ next if rendered_line == line_to_render
+
+ Reline::IOGate.move_cursor_down i - cursor_y
+ cursor_y = i
+ unless rendered_lines[i]
+ Reline::IOGate.move_cursor_column 0
+ Reline::IOGate.erase_after_cursor
+ end
+ render_line_differential(rendered_line, line_to_render)
end
+ @rendered_screen.lines = new_lines
+ Reline::IOGate.show_cursor
end
+ y = wrapped_cursor_y - screen_scroll_top
+ Reline::IOGate.move_cursor_column wrapped_cursor_x
+ Reline::IOGate.move_cursor_down y - cursor_y
+ @rendered_screen.cursor_y = y
+ new_lines.size - y
+ end
+
+ def upper_space_height(wrapped_cursor_y)
+ wrapped_cursor_y - screen_scroll_top
+ end
+
+ def rest_height(wrapped_cursor_y)
+ screen_height - wrapped_cursor_y + screen_scroll_top - @rendered_screen.base_y - 1
+ end
+
+ def rerender
+ render_differential unless @in_pasting
end
class DialogProcScope
+ CompletionJourneyData = Struct.new(:preposing, :postposing, :list, :pointer)
+
def initialize(line_editor, config, proc_to_exec, context)
@line_editor = line_editor
@config = config
@@ -575,11 +616,20 @@ class Reline::LineEditor
end
def screen_width
- @line_editor.instance_variable_get(:@screen_size).last
+ @line_editor.screen_width
+ end
+
+ def screen_height
+ @line_editor.screen_height
+ end
+
+ def preferred_dialog_height
+ _wrapped_cursor_x, wrapped_cursor_y = @line_editor.wrapped_cursor_position
+ [@line_editor.upper_space_height(wrapped_cursor_y), @line_editor.rest_height(wrapped_cursor_y), (screen_height + 6) / 5].max
end
def completion_journey_data
- @line_editor.instance_variable_get(:@completion_journey_data)
+ @line_editor.dialog_proc_scope_completion_journey_data
end
def config
@@ -593,7 +643,7 @@ class Reline::LineEditor
class Dialog
attr_reader :name, :contents, :width
- attr_accessor :scroll_top, :scrollbar_pos, :pointer, :column, :vertical_offset, :lines_backup, :trap_key
+ attr_accessor :scroll_top, :pointer, :column, :vertical_offset, :trap_key
def initialize(name, config, proc_scope)
@name = name
@@ -648,49 +698,38 @@ class Reline::LineEditor
end
DIALOG_DEFAULT_HEIGHT = 20
- private def render_dialog(cursor_column)
- @dialogs.each do |dialog|
- render_each_dialog(dialog, cursor_column)
- end
- end
private def padding_space_with_escape_sequences(str, width)
- str + (' ' * (width - calculate_width(str, true)))
+ padding_width = width - calculate_width(str, true)
+ # padding_width should be only positive value. But macOS and Alacritty returns negative value.
+ padding_width = 0 if padding_width < 0
+ str + (' ' * padding_width)
end
- private def render_each_dialog(dialog, cursor_column)
- if @in_pasting
- clear_each_dialog(dialog)
- dialog.contents = nil
- dialog.trap_key = nil
- return
- end
- dialog.set_cursor_pos(cursor_column, @first_line_started_from + @started_from)
- dialog_render_info = dialog.call(@last_key)
+ private def dialog_range(dialog, dialog_y)
+ x_range = dialog.column...dialog.column + dialog.width
+ y_range = dialog_y + dialog.vertical_offset...dialog_y + dialog.vertical_offset + dialog.contents.size
+ [x_range, y_range]
+ end
+
+ private def update_each_dialog(dialog, cursor_column, cursor_row, key = nil)
+ dialog.set_cursor_pos(cursor_column, cursor_row)
+ dialog_render_info = dialog.call(key)
if dialog_render_info.nil? or dialog_render_info.contents.nil? or dialog_render_info.contents.empty?
- dialog.lines_backup = {
- lines: modify_lines(whole_lines),
- line_index: @line_index,
- first_line_started_from: @first_line_started_from,
- started_from: @started_from,
- byte_pointer: @byte_pointer
- }
- clear_each_dialog(dialog)
dialog.contents = nil
dialog.trap_key = nil
return
end
- old_dialog = dialog.clone
- dialog.contents = dialog_render_info.contents
+ contents = dialog_render_info.contents
pointer = dialog.pointer
if dialog_render_info.width
dialog.width = dialog_render_info.width
else
- dialog.width = dialog.contents.map { |l| calculate_width(l, true) }.max
+ dialog.width = contents.map { |l| calculate_width(l, true) }.max
end
height = dialog_render_info.height || DIALOG_DEFAULT_HEIGHT
- height = dialog.contents.size if dialog.contents.size < height
- if dialog.contents.size > height
+ height = contents.size if contents.size < height
+ if contents.size > height
if dialog.pointer
if dialog.pointer < 0
dialog.scroll_top = 0
@@ -700,592 +739,77 @@ class Reline::LineEditor
dialog.scroll_top = dialog.pointer
end
pointer = dialog.pointer - dialog.scroll_top
+ else
+ dialog.scroll_top = 0
end
- dialog.contents = dialog.contents[dialog.scroll_top, height]
- end
- if dialog.contents and dialog.scroll_top >= dialog.contents.size
- dialog.scroll_top = dialog.contents.size - height
+ contents = contents[dialog.scroll_top, height]
end
if dialog_render_info.scrollbar and dialog_render_info.contents.size > height
bar_max_height = height * 2
moving_distance = (dialog_render_info.contents.size - height) * 2
position_ratio = dialog.scroll_top.zero? ? 0.0 : ((dialog.scroll_top * 2).to_f / moving_distance)
- bar_height = (bar_max_height * ((dialog.contents.size * 2).to_f / (dialog_render_info.contents.size * 2))).floor.to_i
- dialog.scrollbar_pos = ((bar_max_height - bar_height) * position_ratio).floor.to_i
+ bar_height = (bar_max_height * ((contents.size * 2).to_f / (dialog_render_info.contents.size * 2))).floor.to_i
+ bar_height = MINIMUM_SCROLLBAR_HEIGHT if bar_height < MINIMUM_SCROLLBAR_HEIGHT
+ scrollbar_pos = ((bar_max_height - bar_height) * position_ratio).floor.to_i
else
- dialog.scrollbar_pos = nil
+ scrollbar_pos = nil
end
- upper_space = @first_line_started_from - @started_from
dialog.column = dialog_render_info.pos.x
- dialog.width += @block_elem_width if dialog.scrollbar_pos
- diff = (dialog.column + dialog.width) - (@screen_size.last)
+ dialog.width += @block_elem_width if scrollbar_pos
+ diff = (dialog.column + dialog.width) - screen_width
if diff > 0
dialog.column -= diff
end
- if (@rest_height - dialog_render_info.pos.y) >= height
+ if rest_height(screen_scroll_top + cursor_row) - dialog_render_info.pos.y >= height
dialog.vertical_offset = dialog_render_info.pos.y + 1
- elsif upper_space >= height
+ elsif cursor_row >= height
dialog.vertical_offset = dialog_render_info.pos.y - height
else
- if (@rest_height - dialog_render_info.pos.y) < height
- scroll_down(height + dialog_render_info.pos.y)
- move_cursor_up(height + dialog_render_info.pos.y)
- end
dialog.vertical_offset = dialog_render_info.pos.y + 1
end
- Reline::IOGate.hide_cursor
if dialog.column < 0
dialog.column = 0
- dialog.width = @screen_size.last
- end
- reset_dialog(dialog, old_dialog)
- move_cursor_down(dialog.vertical_offset)
- Reline::IOGate.move_cursor_column(dialog.column)
- dialog.contents.each_with_index do |item, i|
- if i == pointer
- fg_color = dialog_render_info.pointer_fg_color
- bg_color = dialog_render_info.pointer_bg_color
- else
- fg_color = dialog_render_info.fg_color
- bg_color = dialog_render_info.bg_color
- end
- str_width = dialog.width - (dialog.scrollbar_pos.nil? ? 0 : @block_elem_width)
+ dialog.width = screen_width
+ end
+ face = Reline::Face[dialog_render_info.face || :default]
+ scrollbar_sgr = face[:scrollbar]
+ default_sgr = face[:default]
+ enhanced_sgr = face[:enhanced]
+ dialog.contents = contents.map.with_index do |item, i|
+ line_sgr = i == pointer ? enhanced_sgr : default_sgr
+ str_width = dialog.width - (scrollbar_pos.nil? ? 0 : @block_elem_width)
str = padding_space_with_escape_sequences(Reline::Unicode.take_range(item, 0, str_width), str_width)
- @output.write "\e[#{bg_color}m\e[#{fg_color}m#{str}"
- if dialog.scrollbar_pos and (dialog.scrollbar_pos != old_dialog.scrollbar_pos or dialog.column != old_dialog.column)
- @output.write "\e[37m"
- if dialog.scrollbar_pos <= (i * 2) and (i * 2 + 1) < (dialog.scrollbar_pos + bar_height)
- @output.write @full_block
- elsif dialog.scrollbar_pos <= (i * 2) and (i * 2) < (dialog.scrollbar_pos + bar_height)
- @output.write @upper_half_block
- str += ''
- elsif dialog.scrollbar_pos <= (i * 2 + 1) and (i * 2) < (dialog.scrollbar_pos + bar_height)
- @output.write @lower_half_block
- else
- @output.write ' ' * @block_elem_width
- end
- end
- @output.write "\e[0m"
- Reline::IOGate.move_cursor_column(dialog.column)
- move_cursor_down(1) if i < (dialog.contents.size - 1)
- end
- Reline::IOGate.move_cursor_column(cursor_column)
- move_cursor_up(dialog.vertical_offset + dialog.contents.size - 1)
- Reline::IOGate.show_cursor
- dialog.lines_backup = {
- lines: modify_lines(whole_lines),
- line_index: @line_index,
- first_line_started_from: @first_line_started_from,
- started_from: @started_from,
- byte_pointer: @byte_pointer
- }
- end
-
- private def reset_dialog(dialog, old_dialog)
- return if dialog.lines_backup.nil? or old_dialog.contents.nil?
- prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines])
- visual_lines = []
- visual_start = nil
- dialog.lines_backup[:lines].each_with_index { |l, i|
- pr = prompt_list ? prompt_list[i] : prompt
- vl, _ = split_by_width(pr + l, @screen_size.last)
- vl.compact!
- if i == dialog.lines_backup[:line_index]
- visual_start = visual_lines.size + dialog.lines_backup[:started_from]
- end
- visual_lines.concat(vl)
- }
- old_y = dialog.lines_backup[:first_line_started_from] + dialog.lines_backup[:started_from]
- y = @first_line_started_from + @started_from
- y_diff = y - old_y
- if (old_y + old_dialog.vertical_offset) < (y + dialog.vertical_offset)
- # rerender top
- move_cursor_down(old_dialog.vertical_offset - y_diff)
- start = visual_start + old_dialog.vertical_offset
- line_num = dialog.vertical_offset - old_dialog.vertical_offset
- line_num.times do |i|
- Reline::IOGate.move_cursor_column(old_dialog.column)
- if visual_lines[start + i].nil?
- s = ' ' * old_dialog.width
- else
- s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, old_dialog.width)
- s = padding_space_with_escape_sequences(s, old_dialog.width)
- end
- @output.write "\e[0m#{s}\e[0m"
- move_cursor_down(1) if i < (line_num - 1)
- end
- move_cursor_up(old_dialog.vertical_offset + line_num - 1 - y_diff)
- end
- if (old_y + old_dialog.vertical_offset + old_dialog.contents.size) > (y + dialog.vertical_offset + dialog.contents.size)
- # rerender bottom
- move_cursor_down(dialog.vertical_offset + dialog.contents.size - y_diff)
- start = visual_start + dialog.vertical_offset + dialog.contents.size
- line_num = (old_dialog.vertical_offset + old_dialog.contents.size) - (dialog.vertical_offset + dialog.contents.size)
- line_num.times do |i|
- Reline::IOGate.move_cursor_column(old_dialog.column)
- if visual_lines[start + i].nil?
- s = ' ' * old_dialog.width
- else
- s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, old_dialog.width)
- s = padding_space_with_escape_sequences(s, old_dialog.width)
- end
- @output.write "\e[0m#{s}\e[0m"
- move_cursor_down(1) if i < (line_num - 1)
- end
- move_cursor_up(dialog.vertical_offset + dialog.contents.size + line_num - 1 - y_diff)
- end
- if old_dialog.column < dialog.column
- # rerender left
- move_cursor_down(old_dialog.vertical_offset - y_diff)
- width = dialog.column - old_dialog.column
- start = visual_start + old_dialog.vertical_offset
- line_num = old_dialog.contents.size
- line_num.times do |i|
- Reline::IOGate.move_cursor_column(old_dialog.column)
- if visual_lines[start + i].nil?
- s = ' ' * width
- else
- s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, width)
- s = padding_space_with_escape_sequences(s, dialog.width)
- end
- @output.write "\e[0m#{s}\e[0m"
- move_cursor_down(1) if i < (line_num - 1)
- end
- move_cursor_up(old_dialog.vertical_offset + line_num - 1 - y_diff)
- end
- if (old_dialog.column + old_dialog.width) > (dialog.column + dialog.width)
- # rerender right
- move_cursor_down(old_dialog.vertical_offset + y_diff)
- width = (old_dialog.column + old_dialog.width) - (dialog.column + dialog.width)
- start = visual_start + old_dialog.vertical_offset
- line_num = old_dialog.contents.size
- line_num.times do |i|
- Reline::IOGate.move_cursor_column(old_dialog.column + dialog.width)
- if visual_lines[start + i].nil?
- s = ' ' * width
- else
- s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column + dialog.width, width)
- rerender_width = old_dialog.width - dialog.width
- s = padding_space_with_escape_sequences(s, rerender_width)
- end
- Reline::IOGate.move_cursor_column(dialog.column + dialog.width)
- @output.write "\e[0m#{s}\e[0m"
- move_cursor_down(1) if i < (line_num - 1)
- end
- move_cursor_up(old_dialog.vertical_offset + line_num - 1 + y_diff)
- end
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
- end
-
- private def clear_dialog
- @dialogs.each do |dialog|
- clear_each_dialog(dialog)
- end
- end
-
- private def clear_dialog_with_content
- @dialogs.each do |dialog|
- clear_each_dialog(dialog)
- dialog.contents = nil
- dialog.trap_key = nil
- end
- end
-
- private def clear_each_dialog(dialog)
- dialog.trap_key = nil
- return unless dialog.contents
- prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines])
- visual_lines = []
- visual_lines_under_dialog = []
- visual_start = nil
- dialog.lines_backup[:lines].each_with_index { |l, i|
- pr = prompt_list ? prompt_list[i] : prompt
- vl, _ = split_by_width(pr + l, @screen_size.last)
- vl.compact!
- if i == dialog.lines_backup[:line_index]
- visual_start = visual_lines.size + dialog.lines_backup[:started_from] + dialog.vertical_offset
- end
- visual_lines.concat(vl)
- }
- visual_lines_under_dialog = visual_lines[visual_start, dialog.contents.size]
- visual_lines_under_dialog = [] if visual_lines_under_dialog.nil?
- Reline::IOGate.hide_cursor
- move_cursor_down(dialog.vertical_offset)
- dialog_vertical_size = dialog.contents.size
- dialog_vertical_size.times do |i|
- if i < visual_lines_under_dialog.size
- Reline::IOGate.move_cursor_column(dialog.column)
- str = Reline::Unicode.take_range(visual_lines_under_dialog[i], dialog.column, dialog.width)
- str = padding_space_with_escape_sequences(str, dialog.width)
- @output.write "\e[0m#{str}\e[0m"
- else
- Reline::IOGate.move_cursor_column(dialog.column)
- @output.write "\e[0m#{' ' * dialog.width}\e[0m"
- end
- move_cursor_down(1) if i < (dialog_vertical_size - 1)
- end
- move_cursor_up(dialog_vertical_size - 1 + dialog.vertical_offset)
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
- Reline::IOGate.show_cursor
- end
-
- private def calculate_scroll_partial_screen(highest_in_all, cursor_y)
- if @screen_height < highest_in_all
- old_scroll_partial_screen = @scroll_partial_screen
- if cursor_y == 0
- @scroll_partial_screen = 0
- elsif cursor_y == (highest_in_all - 1)
- @scroll_partial_screen = highest_in_all - @screen_height
- else
- if @scroll_partial_screen
- if cursor_y <= @scroll_partial_screen
- @scroll_partial_screen = cursor_y
- elsif (@scroll_partial_screen + @screen_height - 1) < cursor_y
- @scroll_partial_screen = cursor_y - (@screen_height - 1)
- end
- else
- if cursor_y > (@screen_height - 1)
- @scroll_partial_screen = cursor_y - (@screen_height - 1)
- else
- @scroll_partial_screen = 0
- end
- end
- end
- if @scroll_partial_screen != old_scroll_partial_screen
- @rerender_all = true
- end
- else
- if @scroll_partial_screen
- @rerender_all = true
- end
- @scroll_partial_screen = nil
- end
- end
-
- private def rerender_added_newline(prompt, prompt_width)
- scroll_down(1)
- @buffer_of_lines[@previous_line_index] = @line
- @line = @buffer_of_lines[@line_index]
- unless @in_pasting
- render_partial(prompt, prompt_width, @line, @first_line_started_from + @started_from + 1, with_control: false)
- end
- @cursor = @cursor_max = calculate_width(@line)
- @byte_pointer = @line.bytesize
- @highest_in_all += @highest_in_this
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
- @first_line_started_from += @started_from + 1
- @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
- @previous_line_index = nil
- end
-
- def just_move_cursor
- prompt, prompt_width, prompt_list = check_multiline_prompt(@buffer_of_lines)
- move_cursor_up(@started_from)
- new_first_line_started_from =
- if @line_index.zero?
- 0
- else
- calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
- end
- first_line_diff = new_first_line_started_from - @first_line_started_from
- new_cursor, new_cursor_max, new_started_from, new_byte_pointer = calculate_nearest_cursor(@buffer_of_lines[@line_index], @cursor, @started_from, @byte_pointer, false)
- new_started_from = calculate_height_by_width(prompt_width + new_cursor) - 1
- calculate_scroll_partial_screen(@highest_in_all, new_first_line_started_from + new_started_from)
- @previous_line_index = nil
- if @rerender_all
- @line = @buffer_of_lines[@line_index]
- rerender_all_lines
- @rerender_all = false
- true
- else
- @line = @buffer_of_lines[@line_index]
- @first_line_started_from = new_first_line_started_from
- @started_from = new_started_from
- @cursor = new_cursor
- @cursor_max = new_cursor_max
- @byte_pointer = new_byte_pointer
- move_cursor_down(first_line_diff + @started_from)
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
- false
- end
- end
-
- private def rerender_changed_current_line
- if @previous_line_index
- new_lines = whole_lines(index: @previous_line_index, line: @line)
- else
- new_lines = whole_lines
- end
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines)
- all_height = calculate_height_by_lines(new_lines, prompt_list || prompt)
- diff = all_height - @highest_in_all
- move_cursor_down(@highest_in_all - @first_line_started_from - @started_from - 1)
- if diff > 0
- scroll_down(diff)
- move_cursor_up(all_height - 1)
- elsif diff < 0
- (-diff).times do
- Reline::IOGate.move_cursor_column(0)
- Reline::IOGate.erase_after_cursor
- move_cursor_up(1)
- end
- move_cursor_up(all_height - 1)
- else
- move_cursor_up(all_height - 1)
- end
- @highest_in_all = all_height
- back = render_whole_lines(new_lines, prompt_list || prompt, prompt_width)
- move_cursor_up(back)
- if @previous_line_index
- @buffer_of_lines[@previous_line_index] = @line
- @line = @buffer_of_lines[@line_index]
- end
- @first_line_started_from =
- if @line_index.zero?
- 0
- else
- calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
- end
- if @prompt_proc
- prompt = prompt_list[@line_index]
- prompt_width = calculate_width(prompt, true)
- end
- move_cursor_down(@first_line_started_from)
- calculate_nearest_cursor
- @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
- move_cursor_down(@started_from)
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
- end
-
- private def rerender_all_lines
- move_cursor_up(@first_line_started_from + @started_from)
- Reline::IOGate.move_cursor_column(0)
- back = 0
- new_buffer = whole_lines
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer)
- new_buffer.each_with_index do |line, index|
- prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
- width = prompt_width + calculate_width(line)
- height = calculate_height_by_width(width)
- back += height
- end
- old_highest_in_all = @highest_in_all
- if @line_index.zero?
- new_first_line_started_from = 0
- else
- new_first_line_started_from = calculate_height_by_lines(new_buffer[0..(@line_index - 1)], prompt_list || prompt)
- end
- new_started_from = calculate_height_by_width(prompt_width + @cursor) - 1
- calculate_scroll_partial_screen(back, new_first_line_started_from + new_started_from)
- if @scroll_partial_screen
- move_cursor_up(@first_line_started_from + @started_from)
- scroll_down(@screen_height - 1)
- move_cursor_up(@screen_height)
- Reline::IOGate.move_cursor_column(0)
- elsif back > old_highest_in_all
- scroll_down(back - 1)
- move_cursor_up(back - 1)
- elsif back < old_highest_in_all
- scroll_down(back)
- Reline::IOGate.erase_after_cursor
- (old_highest_in_all - back - 1).times do
- scroll_down(1)
- Reline::IOGate.erase_after_cursor
- end
- move_cursor_up(old_highest_in_all - 1)
- end
- render_whole_lines(new_buffer, prompt_list || prompt, prompt_width)
- if @prompt_proc
- prompt = prompt_list[@line_index]
- prompt_width = calculate_width(prompt, true)
- end
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
- @highest_in_all = back
- @first_line_started_from = new_first_line_started_from
- @started_from = new_started_from
- if @scroll_partial_screen
- Reline::IOGate.move_cursor_up(@screen_height - (@first_line_started_from + @started_from - @scroll_partial_screen) - 1)
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
- else
- move_cursor_down(@first_line_started_from + @started_from - back + 1)
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
- end
- end
-
- private def render_whole_lines(lines, prompt, prompt_width)
- rendered_height = 0
- modify_lines(lines).each_with_index do |line, index|
- if prompt.is_a?(Array)
- line_prompt = prompt[index]
- prompt_width = calculate_width(line_prompt, true)
- else
- line_prompt = prompt
- end
- height = render_partial(line_prompt, prompt_width, line, rendered_height, with_control: false)
- if index < (lines.size - 1)
- if @scroll_partial_screen
- if (@scroll_partial_screen - height) < rendered_height and (@scroll_partial_screen + @screen_height - 1) >= (rendered_height + height)
- move_cursor_down(1)
- end
+ colored_content = "#{line_sgr}#{str}"
+ if scrollbar_pos
+ if scrollbar_pos <= (i * 2) and (i * 2 + 1) < (scrollbar_pos + bar_height)
+ colored_content + scrollbar_sgr + @full_block
+ elsif scrollbar_pos <= (i * 2) and (i * 2) < (scrollbar_pos + bar_height)
+ colored_content + scrollbar_sgr + @upper_half_block
+ elsif scrollbar_pos <= (i * 2 + 1) and (i * 2) < (scrollbar_pos + bar_height)
+ colored_content + scrollbar_sgr + @lower_half_block
else
- scroll_down(1)
+ colored_content + scrollbar_sgr + ' ' * @block_elem_width
end
- rendered_height += height
else
- rendered_height += height - 1
+ colored_content
end
end
- rendered_height
end
- private def render_partial(prompt, prompt_width, line_to_render, this_started_from, with_control: true)
- visual_lines, height = split_by_width(line_to_render.nil? ? prompt : prompt + line_to_render, @screen_size.last)
- cursor_up_from_last_line = 0
- if @scroll_partial_screen
- last_visual_line = this_started_from + (height - 1)
- last_screen_line = @scroll_partial_screen + (@screen_height - 1)
- if (@scroll_partial_screen - this_started_from) >= height
- # Render nothing because this line is before the screen.
- visual_lines = []
- elsif this_started_from > last_screen_line
- # Render nothing because this line is after the screen.
- visual_lines = []
- else
- deleted_lines_before_screen = []
- if @scroll_partial_screen > this_started_from and last_visual_line >= @scroll_partial_screen
- # A part of visual lines are before the screen.
- deleted_lines_before_screen = visual_lines.shift((@scroll_partial_screen - this_started_from) * 2)
- deleted_lines_before_screen.compact!
- end
- if this_started_from <= last_screen_line and last_screen_line < last_visual_line
- # A part of visual lines are after the screen.
- visual_lines.pop((last_visual_line - last_screen_line) * 2)
- end
- move_cursor_up(deleted_lines_before_screen.size - @started_from)
- cursor_up_from_last_line = @started_from - deleted_lines_before_screen.size
- end
- end
- if with_control
- if height > @highest_in_this
- diff = height - @highest_in_this
- scroll_down(diff)
- @highest_in_all += diff
- @highest_in_this = height
- move_cursor_up(diff)
- elsif height < @highest_in_this
- diff = @highest_in_this - height
- @highest_in_all -= diff
- @highest_in_this = height
- end
- move_cursor_up(@started_from)
- @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
- cursor_up_from_last_line = height - 1 - @started_from
- end
- if Reline::Unicode::CSI_REGEXP.match?(prompt + line_to_render)
- @output.write "\e[0m" # clear character decorations
- end
- visual_lines.each_with_index do |line, index|
- Reline::IOGate.move_cursor_column(0)
- if line.nil?
- if calculate_width(visual_lines[index - 1], true) == Reline::IOGate.get_screen_size.last
- # reaches the end of line
- if Reline::IOGate.win? and Reline::IOGate.win_legacy_console?
- # A newline is automatically inserted if a character is rendered at
- # eol on command prompt.
- else
- # When the cursor is at the end of the line and erases characters
- # after the cursor, some terminals delete the character at the
- # cursor position.
- move_cursor_down(1)
- Reline::IOGate.move_cursor_column(0)
- end
- else
- Reline::IOGate.erase_after_cursor
- move_cursor_down(1)
- Reline::IOGate.move_cursor_column(0)
- end
- next
- end
- @output.write line
- if Reline::IOGate.win? and Reline::IOGate.win_legacy_console? and calculate_width(line, true) == Reline::IOGate.get_screen_size.last
- # A newline is automatically inserted if a character is rendered at eol on command prompt.
- @rest_height -= 1 if @rest_height > 0
- end
- @output.flush
- if @first_prompt
- @first_prompt = false
- @pre_input_hook&.call
- end
- end
- unless visual_lines.empty?
- Reline::IOGate.erase_after_cursor
- Reline::IOGate.move_cursor_column(0)
- end
- if with_control
- # Just after rendring, so the cursor is on the last line.
- if finished?
- Reline::IOGate.move_cursor_column(0)
- else
- # Moves up from bottom of lines to the cursor position.
- move_cursor_up(cursor_up_from_last_line)
- # This logic is buggy if a fullwidth char is wrapped because there is only one halfwidth at end of a line.
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
- end
- end
- height
- end
-
- private def modify_lines(before)
- return before if before.nil? || before.empty? || simplified_rendering?
-
- if after = @output_modifier_proc&.call("#{before.join("\n")}\n", complete: finished?)
+ private def modify_lines(before, complete)
+ if after = @output_modifier_proc&.call("#{before.join("\n")}\n", complete: complete)
after.lines("\n").map { |l| l.chomp('') }
else
- before
+ before.map { |l| Reline::Unicode.escape_for_print(l) }
end
end
- private def show_menu
- scroll_down(@highest_in_all - @first_line_started_from)
- @rerender_all = true
- @menu_info.list.sort!.each do |item|
- Reline::IOGate.move_cursor_column(0)
- @output.write item
- @output.flush
- scroll_down(1)
- end
- scroll_down(@highest_in_all - 1)
- move_cursor_up(@highest_in_all - 1 - @first_line_started_from)
- end
-
- private def clear_screen_buffer(prompt, prompt_list, prompt_width)
- Reline::IOGate.clear_screen
- back = 0
- modify_lines(whole_lines).each_with_index do |line, index|
- if @prompt_proc
- pr = prompt_list[index]
- height = render_partial(pr, calculate_width(pr), line, back, with_control: false)
- else
- height = render_partial(prompt, prompt_width, line, back, with_control: false)
- end
- if index < (@buffer_of_lines.size - 1)
- move_cursor_down(1)
- back += height
- end
- end
- move_cursor_up(back)
- move_cursor_down(@first_line_started_from + @started_from)
- @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
- end
-
def editing_mode
@config.editing_mode
end
- private def menu(target, list)
- @menu_info = MenuInfo.new(target, list)
+ private def menu(_target, list)
+ @menu_info = MenuInfo.new(list)
end
private def complete_internal_proc(list, is_menu)
@@ -1313,7 +837,7 @@ class Reline::LineEditor
item_mbchars = item.grapheme_clusters
end
size = [memo_mbchars.size, item_mbchars.size].min
- result = ''
+ result = +''
size.times do |i|
if @config.completion_ignore_case
if memo_mbchars[i].casecmp?(item_mbchars[i])
@@ -1334,9 +858,9 @@ class Reline::LineEditor
[target, preposing, completed, postposing]
end
- private def complete(list, just_show_list = false)
+ private def complete(list, just_show_list)
case @completion_state
- when CompletionState::NORMAL, CompletionState::JOURNEY
+ when CompletionState::NORMAL
@completion_state = CompletionState::COMPLETION
when CompletionState::PERFECT_MATCH
@dig_perfect_match_proc&.(@perfect_matched)
@@ -1363,100 +887,79 @@ class Reline::LineEditor
@completion_state = CompletionState::PERFECT_MATCH
else
@completion_state = CompletionState::MENU_WITH_PERFECT_MATCH
+ complete(list, true) if @config.show_all_if_ambiguous
end
@perfect_matched = completed
else
@completion_state = CompletionState::MENU
+ complete(list, true) if @config.show_all_if_ambiguous
end
if not just_show_list and target < completed
- @line = preposing + completed + completion_append_character.to_s + postposing
- line_to_pointer = preposing + completed + completion_append_character.to_s
- @cursor_max = calculate_width(@line)
- @cursor = calculate_width(line_to_pointer)
+ @buffer_of_lines[@line_index] = (preposing + completed + completion_append_character.to_s + postposing).split("\n")[@line_index] || String.new(encoding: @encoding)
+ line_to_pointer = (preposing + completed + completion_append_character.to_s).split("\n")[@line_index] || String.new(encoding: @encoding)
@byte_pointer = line_to_pointer.bytesize
end
end
end
- private def move_completed_list(list, direction)
- case @completion_state
- when CompletionState::NORMAL, CompletionState::COMPLETION,
- CompletionState::MENU, CompletionState::MENU_WITH_PERFECT_MATCH
- @completion_state = CompletionState::JOURNEY
- result = retrieve_completion_block
- return if result.nil?
- preposing, target, postposing = result
- @completion_journey_data = CompletionJourneyData.new(
- preposing, postposing,
- [target] + list.select{ |item| item.start_with?(target) }, 0)
- if @completion_journey_data.list.size == 1
- @completion_journey_data.pointer = 0
- else
- case direction
- when :up
- @completion_journey_data.pointer = @completion_journey_data.list.size - 1
- when :down
- @completion_journey_data.pointer = 1
- end
- end
- @completion_state = CompletionState::JOURNEY
- else
- case direction
- when :up
- @completion_journey_data.pointer -= 1
- if @completion_journey_data.pointer < 0
- @completion_journey_data.pointer = @completion_journey_data.list.size - 1
- end
- when :down
- @completion_journey_data.pointer += 1
- if @completion_journey_data.pointer >= @completion_journey_data.list.size
- @completion_journey_data.pointer = 0
- end
- end
+ def dialog_proc_scope_completion_journey_data
+ return nil unless @completion_journey_state
+ line_index = @completion_journey_state.line_index
+ pre_lines = @buffer_of_lines[0...line_index].map { |line| line + "\n" }
+ post_lines = @buffer_of_lines[(line_index + 1)..-1].map { |line| line + "\n" }
+ DialogProcScope::CompletionJourneyData.new(
+ pre_lines.join + @completion_journey_state.pre,
+ @completion_journey_state.post + post_lines.join,
+ @completion_journey_state.list,
+ @completion_journey_state.pointer
+ )
+ end
+
+ private def move_completed_list(direction)
+ @completion_journey_state ||= retrieve_completion_journey_state
+ return false unless @completion_journey_state
+
+ if (delta = { up: -1, down: +1 }[direction])
+ @completion_journey_state.pointer = (@completion_journey_state.pointer + delta) % @completion_journey_state.list.size
end
- completed = @completion_journey_data.list[@completion_journey_data.pointer]
- new_line = (@completion_journey_data.preposing + completed + @completion_journey_data.postposing).split("\n")[@line_index]
- @line = new_line.nil? ? String.new(encoding: @encoding) : new_line
- line_to_pointer = (@completion_journey_data.preposing + completed).split("\n").last
- line_to_pointer = String.new(encoding: @encoding) if line_to_pointer.nil?
- @cursor_max = calculate_width(@line)
- @cursor = calculate_width(line_to_pointer)
- @byte_pointer = line_to_pointer.bytesize
+ completed = @completion_journey_state.list[@completion_journey_state.pointer]
+ set_current_line(@completion_journey_state.pre + completed + @completion_journey_state.post, @completion_journey_state.pre.bytesize + completed.bytesize)
+ true
+ end
+
+ private def retrieve_completion_journey_state
+ preposing, target, postposing = retrieve_completion_block
+ list = call_completion_proc
+ return unless list.is_a?(Array)
+
+ candidates = list.select{ |item| item.start_with?(target) }
+ return if candidates.empty?
+
+ pre = preposing.split("\n", -1).last || ''
+ post = postposing.split("\n", -1).first || ''
+ CompletionJourneyState.new(
+ @line_index, pre, target, post, [target] + candidates, 0
+ )
end
private def run_for_operators(key, method_symbol, &block)
- if @waiting_operator_proc
+ if @vi_waiting_operator
if VI_MOTIONS.include?(method_symbol)
- old_cursor, old_byte_pointer = @cursor, @byte_pointer
- @vi_arg = @waiting_operator_vi_arg if @waiting_operator_vi_arg > 1
+ old_byte_pointer = @byte_pointer
+ @vi_arg = (@vi_arg || 1) * @vi_waiting_operator_arg
block.(true)
unless @waiting_proc
- cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer
- @cursor, @byte_pointer = old_cursor, old_byte_pointer
- @waiting_operator_proc.(cursor_diff, byte_pointer_diff)
- else
- old_waiting_proc = @waiting_proc
- old_waiting_operator_proc = @waiting_operator_proc
- current_waiting_operator_proc = @waiting_operator_proc
- @waiting_proc = proc { |k|
- old_cursor, old_byte_pointer = @cursor, @byte_pointer
- old_waiting_proc.(k)
- cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer
- @cursor, @byte_pointer = old_cursor, old_byte_pointer
- current_waiting_operator_proc.(cursor_diff, byte_pointer_diff)
- @waiting_operator_proc = old_waiting_operator_proc
- }
+ byte_pointer_diff = @byte_pointer - old_byte_pointer
+ @byte_pointer = old_byte_pointer
+ send(@vi_waiting_operator, byte_pointer_diff)
+ cleanup_waiting
end
else
# Ignores operator when not motion is given.
block.(false)
+ cleanup_waiting
end
- @waiting_operator_proc = nil
- @waiting_operator_vi_arg = nil
- if @vi_arg
- @rerender_all = true
- @vi_arg = nil
- end
+ @vi_arg = nil
else
block.(false)
end
@@ -1473,7 +976,7 @@ class Reline::LineEditor
end
def wrap_method_call(method_symbol, method_obj, key, with_operator = false)
- if @config.editing_mode_is?(:emacs, :vi_insert) and @waiting_proc.nil? and @waiting_operator_proc.nil?
+ if @config.editing_mode_is?(:emacs, :vi_insert) and @vi_waiting_operator.nil?
not_insertion = method_symbol != :ed_insert
process_insert(force: not_insertion)
end
@@ -1492,11 +995,32 @@ class Reline::LineEditor
end
end
+ private def cleanup_waiting
+ @waiting_proc = nil
+ @vi_waiting_operator = nil
+ @vi_waiting_operator_arg = nil
+ @searching_prompt = nil
+ @drop_terminate_spaces = false
+ end
+
private def process_key(key, method_symbol)
+ if key.is_a?(Symbol)
+ cleanup_waiting
+ elsif @waiting_proc
+ old_byte_pointer = @byte_pointer
+ @waiting_proc.call(key)
+ if @vi_waiting_operator
+ byte_pointer_diff = @byte_pointer - old_byte_pointer
+ @byte_pointer = old_byte_pointer
+ send(@vi_waiting_operator, byte_pointer_diff)
+ cleanup_waiting
+ end
+ @kill_ring.process
+ return
+ end
+
if method_symbol and respond_to?(method_symbol, true)
method_obj = method(method_symbol)
- else
- method_obj = nil
end
if method_symbol and key.is_a?(Symbol)
if @vi_arg and argumentable?(method_obj)
@@ -1508,7 +1032,6 @@ class Reline::LineEditor
end
@kill_ring.process
if @vi_arg
- @rerender_al = true
@vi_arg = nil
end
elsif @vi_arg
@@ -1519,8 +1042,6 @@ class Reline::LineEditor
run_for_operators(key, method_symbol) do |with_operator|
wrap_method_call(method_symbol, method_obj, key, with_operator)
end
- elsif @waiting_proc
- @waiting_proc.(key)
elsif method_obj
wrap_method_call(method_symbol, method_obj, key)
else
@@ -1528,13 +1049,9 @@ class Reline::LineEditor
end
@kill_ring.process
if @vi_arg
- @rerender_all = true
@vi_arg = nil
end
end
- elsif @waiting_proc
- @waiting_proc.(key)
- @kill_ring.process
elsif method_obj
if method_symbol == :ed_argument_digit
wrap_method_call(method_symbol, method_obj, key)
@@ -1550,7 +1067,6 @@ class Reline::LineEditor
end
private def normal_char(key)
- method_symbol = method_obj = nil
if key.combined_char.is_a?(Symbol)
process_key(key.combined_char, key.combined_char)
return
@@ -1568,97 +1084,104 @@ class Reline::LineEditor
return if key.char >= 128 # maybe, first byte of multi byte
method_symbol = @config.editing_mode.get_method(key.combined_char)
if key.with_meta and method_symbol == :ed_unassigned
- # split ESC + key
- method_symbol = @config.editing_mode.get_method("\e".ord)
- process_key("\e".ord, method_symbol)
- method_symbol = @config.editing_mode.get_method(key.char)
- process_key(key.char, method_symbol)
+ if @config.editing_mode_is?(:vi_command, :vi_insert)
+ # split ESC + key in vi mode
+ method_symbol = @config.editing_mode.get_method("\e".ord)
+ process_key("\e".ord, method_symbol)
+ method_symbol = @config.editing_mode.get_method(key.char)
+ process_key(key.char, method_symbol)
+ end
else
process_key(key.combined_char, method_symbol)
end
@multibyte_buffer.clear
end
- if @config.editing_mode_is?(:vi_command) and @cursor > 0 and @cursor == @cursor_max
- byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
+ if @config.editing_mode_is?(:vi_command) and @byte_pointer > 0 and @byte_pointer == current_line.bytesize
+ byte_size = Reline::Unicode.get_prev_mbchar_size(@buffer_of_lines[@line_index], @byte_pointer)
@byte_pointer -= byte_size
- mbchar = @line.byteslice(@byte_pointer, byte_size)
- width = Reline::Unicode.get_mbchar_width(mbchar)
- @cursor -= width
+ end
+ end
+
+ def update(key)
+ modified = input_key(key)
+ unless @in_pasting
+ scroll_into_view
+ @just_cursor_moving = !modified
+ update_dialogs(key)
+ @just_cursor_moving = false
end
end
def input_key(key)
- @last_key = key
@config.reset_oneshot_key_bindings
@dialogs.each do |dialog|
if key.char.instance_of?(Symbol) and key.char == dialog.name
return
end
end
- @just_cursor_moving = nil
if key.char.nil?
if @first_char
- @line = nil
+ @eof = true
end
finish
return
end
- old_line = @line.dup
+ old_lines = @buffer_of_lines.dup
@first_char = false
- completion_occurs = false
+ @completion_occurs = false
if @config.editing_mode_is?(:emacs, :vi_insert) and key.char == "\C-i".ord
- unless @config.disable_completion
- result = call_completion_proc
- if result.is_a?(Array)
- completion_occurs = true
- process_insert
- if @config.autocompletion
- move_completed_list(result, :down)
- else
- complete(result)
+ if !@config.disable_completion
+ process_insert(force: true)
+ if @config.autocompletion
+ @completion_state = CompletionState::NORMAL
+ @completion_occurs = move_completed_list(:down)
+ else
+ @completion_journey_state = nil
+ result = call_completion_proc
+ if result.is_a?(Array)
+ @completion_occurs = true
+ complete(result, false)
end
end
end
- elsif @config.editing_mode_is?(:emacs, :vi_insert) and key.char == :completion_journey_up
- if not @config.disable_completion and @config.autocompletion
- result = call_completion_proc
- if result.is_a?(Array)
- completion_occurs = true
- process_insert
- move_completed_list(result, :up)
- end
- end
- elsif not @config.disable_completion and @config.editing_mode_is?(:vi_insert) and ["\C-p".ord, "\C-n".ord].include?(key.char)
- unless @config.disable_completion
- result = call_completion_proc
- if result.is_a?(Array)
- completion_occurs = true
- process_insert
- move_completed_list(result, "\C-p".ord == key.char ? :up : :down)
- end
+ elsif @config.editing_mode_is?(:vi_insert) and ["\C-p".ord, "\C-n".ord].include?(key.char)
+ # In vi mode, move completed list even if autocompletion is off
+ if not @config.disable_completion
+ process_insert(force: true)
+ @completion_state = CompletionState::NORMAL
+ @completion_occurs = move_completed_list("\C-p".ord == key.char ? :up : :down)
end
elsif Symbol === key.char and respond_to?(key.char, true)
process_key(key.char, key.char)
else
normal_char(key)
end
- unless completion_occurs
+ unless @completion_occurs
@completion_state = CompletionState::NORMAL
- @completion_journey_data = nil
+ @completion_journey_state = nil
end
- if not @in_pasting and @just_cursor_moving.nil?
- if @previous_line_index and @buffer_of_lines[@previous_line_index] == @line
- @just_cursor_moving = true
- elsif @previous_line_index.nil? and @buffer_of_lines[@line_index] == @line and old_line == @line
- @just_cursor_moving = true
- else
- @just_cursor_moving = false
- end
- else
- @just_cursor_moving = false
+
+ if @in_pasting
+ clear_dialogs
+ return
+ end
+
+ modified = old_lines != @buffer_of_lines
+ if !@completion_occurs && modified && !@config.disable_completion && @config.autocompletion
+ # Auto complete starts only when edited
+ process_insert(force: true)
+ @completion_journey_state = retrieve_completion_journey_state
+ end
+ modified
+ end
+
+ def scroll_into_view
+ _wrapped_cursor_x, wrapped_cursor_y = wrapped_cursor_position
+ if wrapped_cursor_y < screen_scroll_top
+ @scroll_partial_screen = wrapped_cursor_y
end
- if @is_multiline and @auto_indent_proc and not simplified_rendering?
- process_auto_indent
+ if wrapped_cursor_y >= screen_scroll_top + screen_height
+ @scroll_partial_screen = wrapped_cursor_y - screen_height + 1
end
end
@@ -1692,46 +1215,40 @@ class Reline::LineEditor
result
end
- private def process_auto_indent
- return if not @check_new_auto_indent and @previous_line_index # move cursor up or down
- if @check_new_auto_indent and @previous_line_index and @previous_line_index > 0 and @line_index > @previous_line_index
- # Fix indent of a line when a newline is inserted to the next
- new_lines = whole_lines(index: @previous_line_index, line: @line)
- new_indent = @auto_indent_proc.(new_lines[0..-3].push(''), @line_index - 1, 0, true)
- md = @line.match(/\A */)
- prev_indent = md[0].count(' ')
- @line = ' ' * new_indent + @line.lstrip
+ private def process_auto_indent(line_index = @line_index, cursor_dependent: true, add_newline: false)
+ return if @in_pasting
+ return unless @auto_indent_proc
- new_indent = nil
- result = @auto_indent_proc.(new_lines[0..-2], @line_index - 1, (new_lines[-2].size + 1), false)
- if result
- new_indent = result
- end
- if new_indent&.>= 0
- @line = ' ' * new_indent + @line.lstrip
- end
+ line = @buffer_of_lines[line_index]
+ byte_pointer = cursor_dependent && @line_index == line_index ? @byte_pointer : line.bytesize
+ new_indent = @auto_indent_proc.(@buffer_of_lines.take(line_index + 1).push(''), line_index, byte_pointer, add_newline)
+ return unless new_indent
+
+ new_line = ' ' * new_indent + line.lstrip
+ @buffer_of_lines[line_index] = new_line
+ if @line_index == line_index
+ indent_diff = new_line.bytesize - line.bytesize
+ @byte_pointer = [@byte_pointer + indent_diff, 0].max
end
- if @previous_line_index
- new_lines = whole_lines(index: @previous_line_index, line: @line)
+ end
+
+ def line()
+ @buffer_of_lines.join("\n") unless eof?
+ end
+
+ def current_line
+ @buffer_of_lines[@line_index]
+ end
+
+ def set_current_line(line, byte_pointer = nil)
+ cursor = current_byte_pointer_cursor
+ @buffer_of_lines[@line_index] = line
+ if byte_pointer
+ @byte_pointer = byte_pointer
else
- new_lines = whole_lines
- end
- new_indent = @auto_indent_proc.(new_lines, @line_index, @byte_pointer, @check_new_auto_indent)
- new_indent = @cursor_max if new_indent&.> @cursor_max
- if new_indent&.>= 0
- md = new_lines[@line_index].match(/\A */)
- prev_indent = md[0].count(' ')
- if @check_new_auto_indent
- @buffer_of_lines[@line_index] = ' ' * new_indent + @buffer_of_lines[@line_index].lstrip
- @cursor = new_indent
- @byte_pointer = new_indent
- else
- @line = ' ' * new_indent + @line.lstrip
- @cursor += new_indent - prev_indent
- @byte_pointer += new_indent - prev_indent
- end
+ calculate_nearest_cursor(cursor)
end
- @check_new_auto_indent = false
+ process_auto_indent
end
def retrieve_completion_block(set_completion_quote_character = false)
@@ -1745,7 +1262,7 @@ class Reline::LineEditor
else
quote_characters_regexp = /\A[#{Regexp.escape(Reline.completer_quote_characters)}]/
end
- before = @line.byteslice(0, @byte_pointer)
+ before = current_line.byteslice(0, @byte_pointer)
rest = nil
break_pointer = nil
quote = nil
@@ -1753,7 +1270,7 @@ class Reline::LineEditor
escaped_quote = nil
i = 0
while i < @byte_pointer do
- slice = @line.byteslice(i, @byte_pointer - i)
+ slice = current_line.byteslice(i, @byte_pointer - i)
unless slice.valid_encoding?
i += 1
next
@@ -1775,15 +1292,15 @@ class Reline::LineEditor
elsif word_break_regexp and not quote and slice =~ word_break_regexp
rest = $'
i += 1
- before = @line.byteslice(i, @byte_pointer - i)
+ before = current_line.byteslice(i, @byte_pointer - i)
break_pointer = i
else
i += 1
end
end
- postposing = @line.byteslice(@byte_pointer, @line.bytesize - @byte_pointer)
+ postposing = current_line.byteslice(@byte_pointer, current_line.bytesize - @byte_pointer)
if rest
- preposing = @line.byteslice(0, break_pointer)
+ preposing = current_line.byteslice(0, break_pointer)
target = rest
if set_completion_quote_character and quote
Reline.core.instance_variable_set(:@completion_quote_character, quote)
@@ -1794,133 +1311,81 @@ class Reline::LineEditor
else
preposing = ''
if break_pointer
- preposing = @line.byteslice(0, break_pointer)
+ preposing = current_line.byteslice(0, break_pointer)
else
preposing = ''
end
target = before
end
- if @is_multiline
- if @previous_line_index
- lines = whole_lines(index: @previous_line_index, line: @line)
- else
- lines = whole_lines
- end
- if @line_index > 0
- preposing = lines[0..(@line_index - 1)].join("\n") + "\n" + preposing
- end
- if (lines.size - 1) > @line_index
- postposing = postposing + "\n" + lines[(@line_index + 1)..-1].join("\n")
- end
+ lines = whole_lines
+ if @line_index > 0
+ preposing = lines[0..(@line_index - 1)].join("\n") + "\n" + preposing
+ end
+ if (lines.size - 1) > @line_index
+ postposing = postposing + "\n" + lines[(@line_index + 1)..-1].join("\n")
end
[preposing.encode(@encoding), target.encode(@encoding), postposing.encode(@encoding)]
end
def confirm_multiline_termination
temp_buffer = @buffer_of_lines.dup
- if @previous_line_index and @line_index == (@buffer_of_lines.size - 1)
- temp_buffer[@previous_line_index] = @line
- else
- temp_buffer[@line_index] = @line
- end
@confirm_multiline_termination_proc.(temp_buffer.join("\n") + "\n")
end
def insert_text(text)
- width = calculate_width(text)
- if @cursor == @cursor_max
- @line += text
+ if @buffer_of_lines[@line_index].bytesize == @byte_pointer
+ @buffer_of_lines[@line_index] += text
else
- @line = byteinsert(@line, @byte_pointer, text)
+ @buffer_of_lines[@line_index] = byteinsert(@buffer_of_lines[@line_index], @byte_pointer, text)
end
@byte_pointer += text.bytesize
- @cursor += width
- @cursor_max += width
+ process_auto_indent
end
def delete_text(start = nil, length = nil)
if start.nil? and length.nil?
- if @is_multiline
- if @buffer_of_lines.size == 1
- @line&.clear
- @byte_pointer = 0
- @cursor = 0
- @cursor_max = 0
- elsif @line_index == (@buffer_of_lines.size - 1) and @line_index > 0
- @buffer_of_lines.pop
- @line_index -= 1
- @line = @buffer_of_lines[@line_index]
- @byte_pointer = 0
- @cursor = 0
- @cursor_max = calculate_width(@line)
- elsif @line_index < (@buffer_of_lines.size - 1)
- @buffer_of_lines.delete_at(@line_index)
- @line = @buffer_of_lines[@line_index]
- @byte_pointer = 0
- @cursor = 0
- @cursor_max = calculate_width(@line)
- end
- else
- @line&.clear
+ if @buffer_of_lines.size == 1
+ @buffer_of_lines[@line_index] = ''
+ @byte_pointer = 0
+ elsif @line_index == (@buffer_of_lines.size - 1) and @line_index > 0
+ @buffer_of_lines.pop
+ @line_index -= 1
+ @byte_pointer = 0
+ elsif @line_index < (@buffer_of_lines.size - 1)
+ @buffer_of_lines.delete_at(@line_index)
@byte_pointer = 0
- @cursor = 0
- @cursor_max = 0
end
elsif not start.nil? and not length.nil?
- if @line
- before = @line.byteslice(0, start)
- after = @line.byteslice(start + length, @line.bytesize)
- @line = before + after
- @byte_pointer = @line.bytesize if @byte_pointer > @line.bytesize
- str = @line.byteslice(0, @byte_pointer)
- @cursor = calculate_width(str)
- @cursor_max = calculate_width(@line)
+ if current_line
+ before = current_line.byteslice(0, start)
+ after = current_line.byteslice(start + length, current_line.bytesize)
+ set_current_line(before + after)
end
elsif start.is_a?(Range)
range = start
first = range.first
last = range.last
- last = @line.bytesize - 1 if last > @line.bytesize
- last += @line.bytesize if last < 0
- first += @line.bytesize if first < 0
+ last = current_line.bytesize - 1 if last > current_line.bytesize
+ last += current_line.bytesize if last < 0
+ first += current_line.bytesize if first < 0
range = range.exclude_end? ? first...last : first..last
- @line = @line.bytes.reject.with_index{ |c, i| range.include?(i) }.map{ |c| c.chr(Encoding::ASCII_8BIT) }.join.force_encoding(@encoding)
- @byte_pointer = @line.bytesize if @byte_pointer > @line.bytesize
- str = @line.byteslice(0, @byte_pointer)
- @cursor = calculate_width(str)
- @cursor_max = calculate_width(@line)
+ line = current_line.bytes.reject.with_index{ |c, i| range.include?(i) }.map{ |c| c.chr(Encoding::ASCII_8BIT) }.join.force_encoding(@encoding)
+ set_current_line(line)
else
- @line = @line.byteslice(0, start)
- @byte_pointer = @line.bytesize if @byte_pointer > @line.bytesize
- str = @line.byteslice(0, @byte_pointer)
- @cursor = calculate_width(str)
- @cursor_max = calculate_width(@line)
+ set_current_line(current_line.byteslice(0, start))
end
end
def byte_pointer=(val)
@byte_pointer = val
- str = @line.byteslice(0, @byte_pointer)
- @cursor = calculate_width(str)
- @cursor_max = calculate_width(@line)
end
- def whole_lines(index: @line_index, line: @line)
- temp_lines = @buffer_of_lines.dup
- temp_lines[index] = line
- temp_lines
+ def whole_lines
+ @buffer_of_lines.dup
end
def whole_buffer
- if @buffer_of_lines.size == 1 and @line.nil?
- nil
- else
- if @previous_line_index
- whole_lines(index: @previous_line_index, line: @line).join("\n")
- else
- whole_lines.join("\n")
- end
- end
+ whole_lines.join("\n")
end
def finished?
@@ -1929,7 +1394,6 @@ class Reline::LineEditor
def finish
@finished = true
- @rerender_all = true
@config.reset
end
@@ -1951,40 +1415,36 @@ class Reline::LineEditor
end
private def key_delete(key)
- if @config.editing_mode_is?(:vi_insert, :emacs)
+ if @config.editing_mode_is?(:vi_insert)
ed_delete_next_char(key)
+ elsif @config.editing_mode_is?(:emacs)
+ em_delete(key)
end
end
private def key_newline(key)
if @is_multiline
- if (@buffer_of_lines.size - 1) == @line_index and @line.bytesize == @byte_pointer
- @add_newline_to_end_of_buffer = true
- end
- next_line = @line.byteslice(@byte_pointer, @line.bytesize - @byte_pointer)
- cursor_line = @line.byteslice(0, @byte_pointer)
+ next_line = current_line.byteslice(@byte_pointer, current_line.bytesize - @byte_pointer)
+ cursor_line = current_line.byteslice(0, @byte_pointer)
insert_new_line(cursor_line, next_line)
- @cursor = 0
- @check_new_auto_indent = true unless @in_pasting
end
end
+ private def completion_journey_up(key)
+ if not @config.disable_completion and @config.autocompletion
+ @completion_state = CompletionState::NORMAL
+ @completion_occurs = move_completed_list(:up)
+ end
+ end
+ alias_method :menu_complete_backward, :completion_journey_up
+
# Editline:: +ed-unassigned+ This editor command always results in an error.
# GNU Readline:: There is no corresponding macro.
private def ed_unassigned(key) end # do nothing
private def process_insert(force: false)
return if @continuous_insertion_buffer.empty? or (@in_pasting and not force)
- width = Reline::Unicode.calculate_width(@continuous_insertion_buffer)
- bytesize = @continuous_insertion_buffer.bytesize
- if @cursor == @cursor_max
- @line += @continuous_insertion_buffer
- else
- @line = byteinsert(@line, @byte_pointer, @continuous_insertion_buffer)
- end
- @byte_pointer += bytesize
- @cursor += width
- @cursor_max += width
+ insert_text(@continuous_insertion_buffer)
@continuous_insertion_buffer.clear
end
@@ -2002,9 +1462,6 @@ class Reline::LineEditor
# million.
# GNU Readline:: +self-insert+ (a, b, A, 1, !, …) Insert yourself.
private def ed_insert(key)
- str = nil
- width = nil
- bytesize = nil
if key.instance_of?(String)
begin
key.encode(Encoding::UTF_8)
@@ -2012,7 +1469,6 @@ class Reline::LineEditor
return
end
str = key
- bytesize = key.bytesize
else
begin
key.chr.encode(Encoding::UTF_8)
@@ -2020,7 +1476,6 @@ class Reline::LineEditor
return
end
str = key.chr
- bytesize = 1
end
if @in_pasting
@continuous_insertion_buffer << str
@@ -2028,28 +1483,8 @@ class Reline::LineEditor
elsif not @continuous_insertion_buffer.empty?
process_insert
end
- width = Reline::Unicode.get_mbchar_width(str)
- if @cursor == @cursor_max
- @line += str
- else
- @line = byteinsert(@line, @byte_pointer, str)
- end
- last_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
- @byte_pointer += bytesize
- last_mbchar = @line.byteslice((@byte_pointer - bytesize - last_byte_size), last_byte_size)
- combined_char = last_mbchar + str
- if last_byte_size != 0 and combined_char.grapheme_clusters.size == 1
- # combined char
- last_mbchar_width = Reline::Unicode.get_mbchar_width(last_mbchar)
- combined_char_width = Reline::Unicode.get_mbchar_width(combined_char)
- if combined_char_width > last_mbchar_width
- width = combined_char_width - last_mbchar_width
- else
- width = 0
- end
- end
- @cursor += width
- @cursor_max += width
+
+ insert_text(str)
end
alias_method :ed_digit, :ed_insert
alias_method :self_insert, :ed_insert
@@ -2071,18 +1506,11 @@ class Reline::LineEditor
alias_method :quoted_insert, :ed_quoted_insert
private def ed_next_char(key, arg: 1)
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
- if (@byte_pointer < @line.bytesize)
- mbchar = @line.byteslice(@byte_pointer, byte_size)
- width = Reline::Unicode.get_mbchar_width(mbchar)
- @cursor += width if width
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
+ if (@byte_pointer < current_line.bytesize)
@byte_pointer += byte_size
- elsif @is_multiline and @config.editing_mode_is?(:emacs) and @byte_pointer == @line.bytesize and @line_index < @buffer_of_lines.size - 1
- next_line = @buffer_of_lines[@line_index + 1]
- @cursor = 0
+ elsif @config.editing_mode_is?(:emacs) and @byte_pointer == current_line.bytesize and @line_index < @buffer_of_lines.size - 1
@byte_pointer = 0
- @cursor_max = calculate_width(next_line)
- @previous_line_index = @line_index
@line_index += 1
end
arg -= 1
@@ -2091,19 +1519,12 @@ class Reline::LineEditor
alias_method :forward_char, :ed_next_char
private def ed_prev_char(key, arg: 1)
- if @cursor > 0
- byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
+ if @byte_pointer > 0
+ byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer)
@byte_pointer -= byte_size
- mbchar = @line.byteslice(@byte_pointer, byte_size)
- width = Reline::Unicode.get_mbchar_width(mbchar)
- @cursor -= width
- elsif @is_multiline and @config.editing_mode_is?(:emacs) and @byte_pointer == 0 and @line_index > 0
- prev_line = @buffer_of_lines[@line_index - 1]
- @cursor = calculate_width(prev_line)
- @byte_pointer = prev_line.bytesize
- @cursor_max = calculate_width(prev_line)
- @previous_line_index = @line_index
+ elsif @config.editing_mode_is?(:emacs) and @byte_pointer == 0 and @line_index > 0
@line_index -= 1
+ @byte_pointer = current_line.bytesize
end
arg -= 1
ed_prev_char(key, arg: arg) if arg > 0
@@ -2111,157 +1532,109 @@ class Reline::LineEditor
alias_method :backward_char, :ed_prev_char
private def vi_first_print(key)
- @byte_pointer, @cursor = Reline::Unicode.vi_first_print(@line)
+ @byte_pointer, = Reline::Unicode.vi_first_print(current_line)
end
private def ed_move_to_beg(key)
- @byte_pointer = @cursor = 0
+ @byte_pointer = 0
end
alias_method :beginning_of_line, :ed_move_to_beg
+ alias_method :vi_zero, :ed_move_to_beg
private def ed_move_to_end(key)
- @byte_pointer = 0
- @cursor = 0
- byte_size = 0
- while @byte_pointer < @line.bytesize
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
- if byte_size > 0
- mbchar = @line.byteslice(@byte_pointer, byte_size)
- @cursor += Reline::Unicode.get_mbchar_width(mbchar)
- end
- @byte_pointer += byte_size
- end
+ @byte_pointer = current_line.bytesize
end
alias_method :end_of_line, :ed_move_to_end
- private def generate_searcher
- Fiber.new do |first_key|
- prev_search_key = first_key
- search_word = String.new(encoding: @encoding)
- multibyte_buf = String.new(encoding: 'ASCII-8BIT')
- last_hit = nil
- case first_key
- when "\C-r".ord
- prompt_name = 'reverse-i-search'
- when "\C-s".ord
- prompt_name = 'i-search'
+ private def generate_searcher(search_key)
+ search_word = String.new(encoding: @encoding)
+ multibyte_buf = String.new(encoding: 'ASCII-8BIT')
+ hit_pointer = nil
+ lambda do |key|
+ search_again = false
+ case key
+ when "\C-h".ord, "\C-?".ord
+ grapheme_clusters = search_word.grapheme_clusters
+ if grapheme_clusters.size > 0
+ grapheme_clusters.pop
+ search_word = grapheme_clusters.join
+ end
+ when "\C-r".ord, "\C-s".ord
+ search_again = true if search_key == key
+ search_key = key
+ else
+ multibyte_buf << key
+ if multibyte_buf.dup.force_encoding(@encoding).valid_encoding?
+ search_word << multibyte_buf.dup.force_encoding(@encoding)
+ multibyte_buf.clear
+ end
end
- loop do
- key = Fiber.yield(search_word)
- search_again = false
- case key
- when -1 # determined
- Reline.last_incremental_search = search_word
- break
- when "\C-h".ord, "\C-?".ord
- grapheme_clusters = search_word.grapheme_clusters
- if grapheme_clusters.size > 0
- grapheme_clusters.pop
- search_word = grapheme_clusters.join
- end
- when "\C-r".ord, "\C-s".ord
- search_again = true if prev_search_key == key
- prev_search_key = key
- else
- multibyte_buf << key
- if multibyte_buf.dup.force_encoding(@encoding).valid_encoding?
- search_word << multibyte_buf.dup.force_encoding(@encoding)
- multibyte_buf.clear
+ hit = nil
+ if not search_word.empty? and @line_backup_in_history&.include?(search_word)
+ hit_pointer = Reline::HISTORY.size
+ hit = @line_backup_in_history
+ else
+ if search_again
+ if search_word.empty? and Reline.last_incremental_search
+ search_word = Reline.last_incremental_search
end
- end
- hit = nil
- if not search_word.empty? and @line_backup_in_history&.include?(search_word)
- @history_pointer = nil
- hit = @line_backup_in_history
- else
- if search_again
- if search_word.empty? and Reline.last_incremental_search
- search_word = Reline.last_incremental_search
- end
- if @history_pointer
- case prev_search_key
- when "\C-r".ord
- history_pointer_base = 0
- history = Reline::HISTORY[0..(@history_pointer - 1)]
- when "\C-s".ord
- history_pointer_base = @history_pointer + 1
- history = Reline::HISTORY[(@history_pointer + 1)..-1]
- end
- else
- history_pointer_base = 0
- history = Reline::HISTORY
- end
- elsif @history_pointer
- case prev_search_key
+ if @history_pointer
+ case search_key
when "\C-r".ord
history_pointer_base = 0
- history = Reline::HISTORY[0..@history_pointer]
+ history = Reline::HISTORY[0..(@history_pointer - 1)]
when "\C-s".ord
- history_pointer_base = @history_pointer
- history = Reline::HISTORY[@history_pointer..-1]
+ history_pointer_base = @history_pointer + 1
+ history = Reline::HISTORY[(@history_pointer + 1)..-1]
end
else
history_pointer_base = 0
history = Reline::HISTORY
end
- case prev_search_key
+ elsif @history_pointer
+ case search_key
when "\C-r".ord
- hit_index = history.rindex { |item|
- item.include?(search_word)
- }
+ history_pointer_base = 0
+ history = Reline::HISTORY[0..@history_pointer]
when "\C-s".ord
- hit_index = history.index { |item|
- item.include?(search_word)
- }
- end
- if hit_index
- @history_pointer = history_pointer_base + hit_index
- hit = Reline::HISTORY[@history_pointer]
+ history_pointer_base = @history_pointer
+ history = Reline::HISTORY[@history_pointer..-1]
end
+ else
+ history_pointer_base = 0
+ history = Reline::HISTORY
end
- case prev_search_key
+ case search_key
when "\C-r".ord
- prompt_name = 'reverse-i-search'
+ hit_index = history.rindex { |item|
+ item.include?(search_word)
+ }
when "\C-s".ord
- prompt_name = 'i-search'
+ hit_index = history.index { |item|
+ item.include?(search_word)
+ }
end
- if hit
- if @is_multiline
- @buffer_of_lines = hit.split("\n")
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
- @line_index = @buffer_of_lines.size - 1
- @line = @buffer_of_lines.last
- @byte_pointer = @line.bytesize
- @cursor = @cursor_max = calculate_width(@line)
- @rerender_all = true
- @searching_prompt = "(%s)`%s'" % [prompt_name, search_word]
- else
- @line = hit
- @searching_prompt = "(%s)`%s': %s" % [prompt_name, search_word, hit]
- end
- last_hit = hit
- else
- if @is_multiline
- @rerender_all = true
- @searching_prompt = "(failed %s)`%s'" % [prompt_name, search_word]
- else
- @searching_prompt = "(failed %s)`%s': %s" % [prompt_name, search_word, last_hit]
- end
+ if hit_index
+ hit_pointer = history_pointer_base + hit_index
+ hit = Reline::HISTORY[hit_pointer]
end
end
+ case search_key
+ when "\C-r".ord
+ prompt_name = 'reverse-i-search'
+ when "\C-s".ord
+ prompt_name = 'i-search'
+ end
+ prompt_name = "failed #{prompt_name}" unless hit
+ [search_word, prompt_name, hit_pointer]
end
end
private def incremental_search_history(key)
unless @history_pointer
- if @is_multiline
- @line_backup_in_history = whole_buffer
- else
- @line_backup_in_history = @line
- end
+ @line_backup_in_history = whole_buffer
end
- searcher = generate_searcher
- searcher.resume(key)
+ searcher = generate_searcher(key)
@searching_prompt = "(reverse-i-search)`': "
termination_keys = ["\C-j".ord]
termination_keys.concat(@config.isearch_terminators&.chars&.map(&:ord)) if @config.isearch_terminators
@@ -2273,67 +1646,41 @@ class Reline::LineEditor
else
buffer = @line_backup_in_history
end
- if @is_multiline
- @buffer_of_lines = buffer.split("\n")
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
- @line_index = @buffer_of_lines.size - 1
- @line = @buffer_of_lines.last
- @rerender_all = true
- else
- @line = buffer
- end
+ @buffer_of_lines = buffer.split("\n")
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
+ @line_index = @buffer_of_lines.size - 1
@searching_prompt = nil
@waiting_proc = nil
- @cursor_max = calculate_width(@line)
- @cursor = @byte_pointer = 0
- @rerender_all = true
- @cached_prompt_list = nil
- searcher.resume(-1)
+ @byte_pointer = 0
when "\C-g".ord
- if @is_multiline
- @buffer_of_lines = @line_backup_in_history.split("\n")
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
- @line_index = @buffer_of_lines.size - 1
- @line = @buffer_of_lines.last
- @rerender_all = true
- else
- @line = @line_backup_in_history
- end
- @history_pointer = nil
+ @buffer_of_lines = @line_backup_in_history.split("\n")
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
+ @line_index = @buffer_of_lines.size - 1
+ move_history(nil, line: :end, cursor: :end, save_buffer: false)
@searching_prompt = nil
@waiting_proc = nil
- @line_backup_in_history = nil
- @cursor_max = calculate_width(@line)
- @cursor = @byte_pointer = 0
- @rerender_all = true
+ @byte_pointer = 0
else
chr = k.is_a?(String) ? k : k.chr(Encoding::ASCII_8BIT)
if chr.match?(/[[:print:]]/) or k == "\C-h".ord or k == "\C-?".ord or k == "\C-r".ord or k == "\C-s".ord
- searcher.resume(k)
+ search_word, prompt_name, hit_pointer = searcher.call(k)
+ Reline.last_incremental_search = search_word
+ @searching_prompt = "(%s)`%s'" % [prompt_name, search_word]
+ @searching_prompt += ': ' unless @is_multiline
+ move_history(hit_pointer, line: :end, cursor: :end, save_buffer: false) if hit_pointer
else
if @history_pointer
line = Reline::HISTORY[@history_pointer]
else
line = @line_backup_in_history
end
- if @is_multiline
- @line_backup_in_history = whole_buffer
- @buffer_of_lines = line.split("\n")
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
- @line_index = @buffer_of_lines.size - 1
- @line = @buffer_of_lines.last
- @rerender_all = true
- else
- @line_backup_in_history = @line
- @line = line
- end
+ @line_backup_in_history = whole_buffer
+ @buffer_of_lines = line.split("\n")
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
+ @line_index = @buffer_of_lines.size - 1
@searching_prompt = nil
@waiting_proc = nil
- @cursor_max = calculate_width(@line)
- @cursor = @byte_pointer = 0
- @rerender_all = true
- @cached_prompt_list = nil
- searcher.resume(-1)
+ @byte_pointer = 0
end
end
}
@@ -2349,199 +1696,95 @@ class Reline::LineEditor
end
alias_method :forward_search_history, :vi_search_next
- private def ed_search_prev_history(key, arg: 1)
- history = nil
- h_pointer = nil
- line_no = nil
- substr = @line.slice(0, @byte_pointer)
- if @history_pointer.nil?
- return if not @line.empty? and substr.empty?
- history = Reline::HISTORY
- elsif @history_pointer.zero?
- history = nil
- h_pointer = nil
- else
- history = Reline::HISTORY.slice(0, @history_pointer)
- end
- return if history.nil?
- if @is_multiline
- h_pointer = history.rindex { |h|
- h.split("\n").each_with_index { |l, i|
- if l.start_with?(substr)
- line_no = i
- break
- end
- }
- not line_no.nil?
- }
- else
- h_pointer = history.rindex { |l|
- l.start_with?(substr)
- }
- end
- return if h_pointer.nil?
- @history_pointer = h_pointer
- if @is_multiline
- @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
- @line_index = line_no
- @line = @buffer_of_lines[@line_index]
- @rerender_all = true
- else
- @line = Reline::HISTORY[@history_pointer]
+ private def search_history(prefix, pointer_range)
+ pointer_range.each do |pointer|
+ lines = Reline::HISTORY[pointer].split("\n")
+ lines.each_with_index do |line, index|
+ return [pointer, index] if line.start_with?(prefix)
+ end
end
- @cursor_max = calculate_width(@line)
+ nil
+ end
+
+ private def ed_search_prev_history(key, arg: 1)
+ substr = current_line.byteslice(0, @byte_pointer)
+ return if @history_pointer == 0
+ return if @history_pointer.nil? && substr.empty? && !current_line.empty?
+
+ history_range = 0...(@history_pointer || Reline::HISTORY.size)
+ h_pointer, line_index = search_history(substr, history_range.reverse_each)
+ return unless h_pointer
+ move_history(h_pointer, line: line_index || :start, cursor: @byte_pointer)
arg -= 1
ed_search_prev_history(key, arg: arg) if arg > 0
end
alias_method :history_search_backward, :ed_search_prev_history
private def ed_search_next_history(key, arg: 1)
- substr = @line.slice(0, @byte_pointer)
- if @history_pointer.nil?
- return
- elsif @history_pointer == (Reline::HISTORY.size - 1) and not substr.empty?
- return
- end
- history = Reline::HISTORY.slice((@history_pointer + 1)..-1)
- h_pointer = nil
- line_no = nil
- if @is_multiline
- h_pointer = history.index { |h|
- h.split("\n").each_with_index { |l, i|
- if l.start_with?(substr)
- line_no = i
- break
- end
- }
- not line_no.nil?
- }
- else
- h_pointer = history.index { |l|
- l.start_with?(substr)
- }
- end
- h_pointer += @history_pointer + 1 if h_pointer and @history_pointer
+ substr = current_line.byteslice(0, @byte_pointer)
+ return if @history_pointer.nil?
+
+ history_range = @history_pointer + 1...Reline::HISTORY.size
+ h_pointer, line_index = search_history(substr, history_range)
return if h_pointer.nil? and not substr.empty?
- @history_pointer = h_pointer
- if @is_multiline
- if @history_pointer.nil? and substr.empty?
- @buffer_of_lines = []
- @line_index = 0
- else
- @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
- @line_index = line_no
- end
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
- @line = @buffer_of_lines[@line_index]
- @rerender_all = true
- else
- if @history_pointer.nil? and substr.empty?
- @line = ''
- else
- @line = Reline::HISTORY[@history_pointer]
- end
- end
- @cursor_max = calculate_width(@line)
+
+ move_history(h_pointer, line: line_index || :start, cursor: @byte_pointer)
arg -= 1
ed_search_next_history(key, arg: arg) if arg > 0
end
alias_method :history_search_forward, :ed_search_next_history
- private def ed_prev_history(key, arg: 1)
- if @is_multiline and @line_index > 0
- @previous_line_index = @line_index
- @line_index -= 1
- return
- end
- if Reline::HISTORY.empty?
- return
+ private def move_history(history_pointer, line:, cursor:, save_buffer: true)
+ history_pointer ||= Reline::HISTORY.size
+ return if history_pointer < 0 || history_pointer > Reline::HISTORY.size
+ old_history_pointer = @history_pointer || Reline::HISTORY.size
+ if old_history_pointer == Reline::HISTORY.size
+ @line_backup_in_history = save_buffer ? whole_buffer : ''
+ else
+ Reline::HISTORY[old_history_pointer] = whole_buffer if save_buffer
end
- if @history_pointer.nil?
- @history_pointer = Reline::HISTORY.size - 1
- if @is_multiline
- @line_backup_in_history = whole_buffer
- @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
- @line_index = @buffer_of_lines.size - 1
- @line = @buffer_of_lines.last
- @rerender_all = true
- else
- @line_backup_in_history = @line
- @line = Reline::HISTORY[@history_pointer]
- end
- elsif @history_pointer.zero?
- return
+ if history_pointer == Reline::HISTORY.size
+ buf = @line_backup_in_history
+ @history_pointer = @line_backup_in_history = nil
else
- if @is_multiline
- Reline::HISTORY[@history_pointer] = whole_buffer
- @history_pointer -= 1
- @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
- @line_index = @buffer_of_lines.size - 1
- @line = @buffer_of_lines.last
- @rerender_all = true
- else
- Reline::HISTORY[@history_pointer] = @line
- @history_pointer -= 1
- @line = Reline::HISTORY[@history_pointer]
- end
+ buf = Reline::HISTORY[history_pointer]
+ @history_pointer = history_pointer
end
- if @config.editing_mode_is?(:emacs, :vi_insert)
- @cursor_max = @cursor = calculate_width(@line)
- @byte_pointer = @line.bytesize
- elsif @config.editing_mode_is?(:vi_command)
- @byte_pointer = @cursor = 0
- @cursor_max = calculate_width(@line)
+ @buffer_of_lines = buf.split("\n")
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
+ @line_index = line == :start ? 0 : line == :end ? @buffer_of_lines.size - 1 : line
+ @byte_pointer = cursor == :start ? 0 : cursor == :end ? current_line.bytesize : cursor
+ end
+
+ private def ed_prev_history(key, arg: 1)
+ if @line_index > 0
+ cursor = current_byte_pointer_cursor
+ @line_index -= 1
+ calculate_nearest_cursor(cursor)
+ return
end
+ move_history(
+ (@history_pointer || Reline::HISTORY.size) - 1,
+ line: :end,
+ cursor: @config.editing_mode_is?(:vi_command) ? :start : :end,
+ )
arg -= 1
ed_prev_history(key, arg: arg) if arg > 0
end
alias_method :previous_history, :ed_prev_history
private def ed_next_history(key, arg: 1)
- if @is_multiline and @line_index < (@buffer_of_lines.size - 1)
- @previous_line_index = @line_index
+ if @line_index < (@buffer_of_lines.size - 1)
+ cursor = current_byte_pointer_cursor
@line_index += 1
+ calculate_nearest_cursor(cursor)
return
end
- if @history_pointer.nil?
- return
- elsif @history_pointer == (Reline::HISTORY.size - 1)
- if @is_multiline
- @history_pointer = nil
- @buffer_of_lines = @line_backup_in_history.split("\n")
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
- @line_index = 0
- @line = @buffer_of_lines.first
- @rerender_all = true
- else
- @history_pointer = nil
- @line = @line_backup_in_history
- end
- else
- if @is_multiline
- Reline::HISTORY[@history_pointer] = whole_buffer
- @history_pointer += 1
- @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
- @line_index = 0
- @line = @buffer_of_lines.first
- @rerender_all = true
- else
- Reline::HISTORY[@history_pointer] = @line
- @history_pointer += 1
- @line = Reline::HISTORY[@history_pointer]
- end
- end
- @line = '' unless @line
- if @config.editing_mode_is?(:emacs, :vi_insert)
- @cursor_max = @cursor = calculate_width(@line)
- @byte_pointer = @line.bytesize
- elsif @config.editing_mode_is?(:vi_command)
- @byte_pointer = @cursor = 0
- @cursor_max = calculate_width(@line)
- end
+ move_history(
+ (@history_pointer || Reline::HISTORY.size) + 1,
+ line: :start,
+ cursor: @config.editing_mode_is?(:vi_command) ? :start : :end,
+ )
arg -= 1
ed_next_history(key, arg: arg) if arg > 0
end
@@ -2566,40 +1809,29 @@ class Reline::LineEditor
end
else
# should check confirm_multiline_termination to finish?
- @previous_line_index = @line_index
@line_index = @buffer_of_lines.size - 1
+ @byte_pointer = current_line.bytesize
finish
end
end
else
- if @history_pointer
- Reline::HISTORY[@history_pointer] = @line
- @history_pointer = nil
- end
finish
end
end
private def em_delete_prev_char(key, arg: 1)
- if @is_multiline and @cursor == 0 and @line_index > 0
- @buffer_of_lines[@line_index] = @line
- @cursor = calculate_width(@buffer_of_lines[@line_index - 1])
- @byte_pointer = @buffer_of_lines[@line_index - 1].bytesize
- @buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index)
- @line_index -= 1
- @line = @buffer_of_lines[@line_index]
- @cursor_max = calculate_width(@line)
- @rerender_all = true
- elsif @cursor > 0
- byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
- @byte_pointer -= byte_size
- @line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
- width = Reline::Unicode.get_mbchar_width(mbchar)
- @cursor -= width
- @cursor_max -= width
+ arg.times do
+ if @byte_pointer == 0 and @line_index > 0
+ @byte_pointer = @buffer_of_lines[@line_index - 1].bytesize
+ @buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index)
+ @line_index -= 1
+ elsif @byte_pointer > 0
+ byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer)
+ line, = byteslice!(current_line, @byte_pointer - byte_size, byte_size)
+ set_current_line(line, @byte_pointer - byte_size)
+ end
end
- arg -= 1
- em_delete_prev_char(key, arg: arg) if arg > 0
+ process_auto_indent
end
alias_method :backward_delete_char, :em_delete_prev_char
@@ -2609,23 +1841,23 @@ class Reline::LineEditor
# the line. With a negative numeric argument, kill backward
# from the cursor to the beginning of the current line.
private def ed_kill_line(key)
- if @line.bytesize > @byte_pointer
- @line, deleted = byteslice!(@line, @byte_pointer, @line.bytesize - @byte_pointer)
- @byte_pointer = @line.bytesize
- @cursor = @cursor_max = calculate_width(@line)
+ if current_line.bytesize > @byte_pointer
+ line, deleted = byteslice!(current_line, @byte_pointer, current_line.bytesize - @byte_pointer)
+ set_current_line(line, line.bytesize)
@kill_ring.append(deleted)
- elsif @is_multiline and @byte_pointer == @line.bytesize and @buffer_of_lines.size > @line_index + 1
- @cursor = calculate_width(@line)
- @byte_pointer = @line.bytesize
- @line += @buffer_of_lines.delete_at(@line_index + 1)
- @cursor_max = calculate_width(@line)
- @buffer_of_lines[@line_index] = @line
- @rerender_all = true
- @rest_height += 1
+ elsif @byte_pointer == current_line.bytesize and @buffer_of_lines.size > @line_index + 1
+ set_current_line(current_line + @buffer_of_lines.delete_at(@line_index + 1), current_line.bytesize)
end
end
alias_method :kill_line, :ed_kill_line
+ # Editline:: +vi_change_to_eol+ (vi command: +C+) + Kill and change from the cursor to the end of the line.
+ private def vi_change_to_eol(key)
+ ed_kill_line(key)
+
+ @config.editing_mode = :vi_insert
+ end
+
# Editline:: +vi-kill-line-prev+ (vi: +Ctrl-U+) Delete the string from the
# beginning of the edit buffer to the cursor and save it to the
# cut buffer.
@@ -2633,11 +1865,9 @@ class Reline::LineEditor
# to the beginning of the current line.
private def vi_kill_line_prev(key)
if @byte_pointer > 0
- @line, deleted = byteslice!(@line, 0, @byte_pointer)
- @byte_pointer = 0
+ line, deleted = byteslice!(current_line, 0, @byte_pointer)
+ set_current_line(line, 0)
@kill_ring.append(deleted, true)
- @cursor_max = calculate_width(@line)
- @cursor = 0
end
end
alias_method :unix_line_discard, :vi_kill_line_prev
@@ -2647,47 +1877,32 @@ class Reline::LineEditor
# GNU Readline:: +kill-whole-line+ (not bound) Kill all characters on the
# current line, no matter where point is.
private def em_kill_line(key)
- if @line.size > 0
- @kill_ring.append(@line.dup, true)
- @line.clear
- @byte_pointer = 0
- @cursor_max = 0
- @cursor = 0
+ if current_line.size > 0
+ @kill_ring.append(current_line.dup, true)
+ set_current_line('', 0)
end
end
alias_method :kill_whole_line, :em_kill_line
private def em_delete(key)
- if (not @is_multiline and @line.empty?) or (@is_multiline and @line.empty? and @buffer_of_lines.size == 1)
- @line = nil
- if @buffer_of_lines.size > 1
- scroll_down(@highest_in_all - @first_line_started_from)
- end
- Reline::IOGate.move_cursor_column(0)
+ if current_line.empty? and @buffer_of_lines.size == 1 and key == "\C-d".ord
@eof = true
finish
- elsif @byte_pointer < @line.bytesize
- splitted_last = @line.byteslice(@byte_pointer, @line.bytesize)
+ elsif @byte_pointer < current_line.bytesize
+ splitted_last = current_line.byteslice(@byte_pointer, current_line.bytesize)
mbchar = splitted_last.grapheme_clusters.first
- width = Reline::Unicode.get_mbchar_width(mbchar)
- @cursor_max -= width
- @line, = byteslice!(@line, @byte_pointer, mbchar.bytesize)
- elsif @is_multiline and @byte_pointer == @line.bytesize and @buffer_of_lines.size > @line_index + 1
- @cursor = calculate_width(@line)
- @byte_pointer = @line.bytesize
- @line += @buffer_of_lines.delete_at(@line_index + 1)
- @cursor_max = calculate_width(@line)
- @buffer_of_lines[@line_index] = @line
- @rerender_all = true
- @rest_height += 1
+ line, = byteslice!(current_line, @byte_pointer, mbchar.bytesize)
+ set_current_line(line)
+ elsif @byte_pointer == current_line.bytesize and @buffer_of_lines.size > @line_index + 1
+ set_current_line(current_line + @buffer_of_lines.delete_at(@line_index + 1), current_line.bytesize)
end
end
alias_method :delete_char, :em_delete
private def em_delete_or_list(key)
- if @line.empty? or @byte_pointer < @line.bytesize
+ if current_line.empty? or @byte_pointer < current_line.bytesize
em_delete(key)
- else # show completed list
+ elsif !@config.autocompletion # show completed list
result = call_completion_proc
if result.is_a?(Array)
complete(result, true)
@@ -2698,162 +1913,136 @@ class Reline::LineEditor
private def em_yank(key)
yanked = @kill_ring.yank
- if yanked
- @line = byteinsert(@line, @byte_pointer, yanked)
- yanked_width = calculate_width(yanked)
- @cursor += yanked_width
- @cursor_max += yanked_width
- @byte_pointer += yanked.bytesize
- end
+ insert_text(yanked) if yanked
end
alias_method :yank, :em_yank
private def em_yank_pop(key)
yanked, prev_yank = @kill_ring.yank_pop
if yanked
- prev_yank_width = calculate_width(prev_yank)
- @cursor -= prev_yank_width
- @cursor_max -= prev_yank_width
- @byte_pointer -= prev_yank.bytesize
- @line, = byteslice!(@line, @byte_pointer, prev_yank.bytesize)
- @line = byteinsert(@line, @byte_pointer, yanked)
- yanked_width = calculate_width(yanked)
- @cursor += yanked_width
- @cursor_max += yanked_width
- @byte_pointer += yanked.bytesize
+ line, = byteslice!(current_line, @byte_pointer - prev_yank.bytesize, prev_yank.bytesize)
+ set_current_line(line, @byte_pointer - prev_yank.bytesize)
+ insert_text(yanked)
end
end
alias_method :yank_pop, :em_yank_pop
private def ed_clear_screen(key)
- @cleared = true
+ Reline::IOGate.clear_screen
+ @screen_size = Reline::IOGate.get_screen_size
+ @rendered_screen.lines = []
+ @rendered_screen.base_y = 0
+ @rendered_screen.cursor_y = 0
end
alias_method :clear_screen, :ed_clear_screen
private def em_next_word(key)
- if @line.bytesize > @byte_pointer
- byte_size, width = Reline::Unicode.em_forward_word(@line, @byte_pointer)
+ if current_line.bytesize > @byte_pointer
+ byte_size, _ = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
@byte_pointer += byte_size
- @cursor += width
end
end
alias_method :forward_word, :em_next_word
private def ed_prev_word(key)
if @byte_pointer > 0
- byte_size, width = Reline::Unicode.em_backward_word(@line, @byte_pointer)
+ byte_size, _ = Reline::Unicode.em_backward_word(current_line, @byte_pointer)
@byte_pointer -= byte_size
- @cursor -= width
end
end
alias_method :backward_word, :ed_prev_word
private def em_delete_next_word(key)
- if @line.bytesize > @byte_pointer
- byte_size, width = Reline::Unicode.em_forward_word(@line, @byte_pointer)
- @line, word = byteslice!(@line, @byte_pointer, byte_size)
+ if current_line.bytesize > @byte_pointer
+ byte_size, _ = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
+ line, word = byteslice!(current_line, @byte_pointer, byte_size)
+ set_current_line(line)
@kill_ring.append(word)
- @cursor_max -= width
end
end
+ alias_method :kill_word, :em_delete_next_word
private def ed_delete_prev_word(key)
if @byte_pointer > 0
- byte_size, width = Reline::Unicode.em_backward_word(@line, @byte_pointer)
- @line, word = byteslice!(@line, @byte_pointer - byte_size, byte_size)
+ byte_size, _ = Reline::Unicode.em_backward_word(current_line, @byte_pointer)
+ line, word = byteslice!(current_line, @byte_pointer - byte_size, byte_size)
+ set_current_line(line, @byte_pointer - byte_size)
@kill_ring.append(word, true)
- @byte_pointer -= byte_size
- @cursor -= width
- @cursor_max -= width
end
end
+ alias_method :backward_kill_word, :ed_delete_prev_word
private def ed_transpose_chars(key)
if @byte_pointer > 0
- if @cursor_max > @cursor
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
- mbchar = @line.byteslice(@byte_pointer, byte_size)
- width = Reline::Unicode.get_mbchar_width(mbchar)
- @cursor += width
+ if @byte_pointer < current_line.bytesize
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
@byte_pointer += byte_size
end
- back1_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
+ back1_byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer)
if (@byte_pointer - back1_byte_size) > 0
- back2_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer - back1_byte_size)
+ back2_byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer - back1_byte_size)
back2_pointer = @byte_pointer - back1_byte_size - back2_byte_size
- @line, back2_mbchar = byteslice!(@line, back2_pointer, back2_byte_size)
- @line = byteinsert(@line, @byte_pointer - back2_byte_size, back2_mbchar)
+ line, back2_mbchar = byteslice!(current_line, back2_pointer, back2_byte_size)
+ set_current_line(byteinsert(line, @byte_pointer - back2_byte_size, back2_mbchar))
end
end
end
alias_method :transpose_chars, :ed_transpose_chars
private def ed_transpose_words(key)
- left_word_start, middle_start, right_word_start, after_start = Reline::Unicode.ed_transpose_words(@line, @byte_pointer)
- before = @line.byteslice(0, left_word_start)
- left_word = @line.byteslice(left_word_start, middle_start - left_word_start)
- middle = @line.byteslice(middle_start, right_word_start - middle_start)
- right_word = @line.byteslice(right_word_start, after_start - right_word_start)
- after = @line.byteslice(after_start, @line.bytesize - after_start)
+ left_word_start, middle_start, right_word_start, after_start = Reline::Unicode.ed_transpose_words(current_line, @byte_pointer)
+ before = current_line.byteslice(0, left_word_start)
+ left_word = current_line.byteslice(left_word_start, middle_start - left_word_start)
+ middle = current_line.byteslice(middle_start, right_word_start - middle_start)
+ right_word = current_line.byteslice(right_word_start, after_start - right_word_start)
+ after = current_line.byteslice(after_start, current_line.bytesize - after_start)
return if left_word.empty? or right_word.empty?
- @line = before + right_word + middle + left_word + after
from_head_to_left_word = before + right_word + middle + left_word
- @byte_pointer = from_head_to_left_word.bytesize
- @cursor = calculate_width(from_head_to_left_word)
+ set_current_line(from_head_to_left_word + after, from_head_to_left_word.bytesize)
end
alias_method :transpose_words, :ed_transpose_words
private def em_capitol_case(key)
- if @line.bytesize > @byte_pointer
- byte_size, _, new_str = Reline::Unicode.em_forward_word_with_capitalization(@line, @byte_pointer)
- before = @line.byteslice(0, @byte_pointer)
- after = @line.byteslice((@byte_pointer + byte_size)..-1)
- @line = before + new_str + after
- @byte_pointer += new_str.bytesize
- @cursor += calculate_width(new_str)
+ if current_line.bytesize > @byte_pointer
+ byte_size, _, new_str = Reline::Unicode.em_forward_word_with_capitalization(current_line, @byte_pointer)
+ before = current_line.byteslice(0, @byte_pointer)
+ after = current_line.byteslice((@byte_pointer + byte_size)..-1)
+ set_current_line(before + new_str + after, @byte_pointer + new_str.bytesize)
end
end
alias_method :capitalize_word, :em_capitol_case
private def em_lower_case(key)
- if @line.bytesize > @byte_pointer
- byte_size, = Reline::Unicode.em_forward_word(@line, @byte_pointer)
- part = @line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
+ if current_line.bytesize > @byte_pointer
+ byte_size, = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
+ part = current_line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
mbchar =~ /[A-Z]/ ? mbchar.downcase : mbchar
}.join
- rest = @line.byteslice((@byte_pointer + byte_size)..-1)
- @line = @line.byteslice(0, @byte_pointer) + part
- @byte_pointer = @line.bytesize
- @cursor = calculate_width(@line)
- @cursor_max = @cursor + calculate_width(rest)
- @line += rest
+ rest = current_line.byteslice((@byte_pointer + byte_size)..-1)
+ line = current_line.byteslice(0, @byte_pointer) + part
+ set_current_line(line + rest, line.bytesize)
end
end
alias_method :downcase_word, :em_lower_case
private def em_upper_case(key)
- if @line.bytesize > @byte_pointer
- byte_size, = Reline::Unicode.em_forward_word(@line, @byte_pointer)
- part = @line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
+ if current_line.bytesize > @byte_pointer
+ byte_size, = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
+ part = current_line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
mbchar =~ /[a-z]/ ? mbchar.upcase : mbchar
}.join
- rest = @line.byteslice((@byte_pointer + byte_size)..-1)
- @line = @line.byteslice(0, @byte_pointer) + part
- @byte_pointer = @line.bytesize
- @cursor = calculate_width(@line)
- @cursor_max = @cursor + calculate_width(rest)
- @line += rest
+ rest = current_line.byteslice((@byte_pointer + byte_size)..-1)
+ line = current_line.byteslice(0, @byte_pointer) + part
+ set_current_line(line + rest, line.bytesize)
end
end
alias_method :upcase_word, :em_upper_case
private def em_kill_region(key)
if @byte_pointer > 0
- byte_size, width = Reline::Unicode.em_big_backward_word(@line, @byte_pointer)
- @line, deleted = byteslice!(@line, @byte_pointer - byte_size, byte_size)
- @byte_pointer -= byte_size
- @cursor -= width
- @cursor_max -= width
+ byte_size, _ = Reline::Unicode.em_big_backward_word(current_line, @byte_pointer)
+ line, deleted = byteslice!(current_line, @byte_pointer - byte_size, byte_size)
+ set_current_line(line, @byte_pointer - byte_size)
@kill_ring.append(deleted, true)
end
end
@@ -2881,10 +2070,9 @@ class Reline::LineEditor
alias_method :vi_movement_mode, :vi_command_mode
private def vi_next_word(key, arg: 1)
- if @line.bytesize > @byte_pointer
- byte_size, width = Reline::Unicode.vi_forward_word(@line, @byte_pointer, @drop_terminate_spaces)
+ if current_line.bytesize > @byte_pointer
+ byte_size, _ = Reline::Unicode.vi_forward_word(current_line, @byte_pointer, @drop_terminate_spaces)
@byte_pointer += byte_size
- @cursor += width
end
arg -= 1
vi_next_word(key, arg: arg) if arg > 0
@@ -2892,38 +2080,32 @@ class Reline::LineEditor
private def vi_prev_word(key, arg: 1)
if @byte_pointer > 0
- byte_size, width = Reline::Unicode.vi_backward_word(@line, @byte_pointer)
+ byte_size, _ = Reline::Unicode.vi_backward_word(current_line, @byte_pointer)
@byte_pointer -= byte_size
- @cursor -= width
end
arg -= 1
vi_prev_word(key, arg: arg) if arg > 0
end
private def vi_end_word(key, arg: 1, inclusive: false)
- if @line.bytesize > @byte_pointer
- byte_size, width = Reline::Unicode.vi_forward_end_word(@line, @byte_pointer)
+ if current_line.bytesize > @byte_pointer
+ byte_size, _ = Reline::Unicode.vi_forward_end_word(current_line, @byte_pointer)
@byte_pointer += byte_size
- @cursor += width
end
arg -= 1
if inclusive and arg.zero?
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
if byte_size > 0
- c = @line.byteslice(@byte_pointer, byte_size)
- width = Reline::Unicode.get_mbchar_width(c)
@byte_pointer += byte_size
- @cursor += width
end
end
vi_end_word(key, arg: arg) if arg > 0
end
private def vi_next_big_word(key, arg: 1)
- if @line.bytesize > @byte_pointer
- byte_size, width = Reline::Unicode.vi_big_forward_word(@line, @byte_pointer)
+ if current_line.bytesize > @byte_pointer
+ byte_size, _ = Reline::Unicode.vi_big_forward_word(current_line, @byte_pointer)
@byte_pointer += byte_size
- @cursor += width
end
arg -= 1
vi_next_big_word(key, arg: arg) if arg > 0
@@ -2931,50 +2113,39 @@ class Reline::LineEditor
private def vi_prev_big_word(key, arg: 1)
if @byte_pointer > 0
- byte_size, width = Reline::Unicode.vi_big_backward_word(@line, @byte_pointer)
+ byte_size, _ = Reline::Unicode.vi_big_backward_word(current_line, @byte_pointer)
@byte_pointer -= byte_size
- @cursor -= width
end
arg -= 1
vi_prev_big_word(key, arg: arg) if arg > 0
end
private def vi_end_big_word(key, arg: 1, inclusive: false)
- if @line.bytesize > @byte_pointer
- byte_size, width = Reline::Unicode.vi_big_forward_end_word(@line, @byte_pointer)
+ if current_line.bytesize > @byte_pointer
+ byte_size, _ = Reline::Unicode.vi_big_forward_end_word(current_line, @byte_pointer)
@byte_pointer += byte_size
- @cursor += width
end
arg -= 1
if inclusive and arg.zero?
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
if byte_size > 0
- c = @line.byteslice(@byte_pointer, byte_size)
- width = Reline::Unicode.get_mbchar_width(c)
@byte_pointer += byte_size
- @cursor += width
end
end
vi_end_big_word(key, arg: arg) if arg > 0
end
private def vi_delete_prev_char(key)
- if @is_multiline and @cursor == 0 and @line_index > 0
- @buffer_of_lines[@line_index] = @line
- @cursor = calculate_width(@buffer_of_lines[@line_index - 1])
+ if @byte_pointer == 0 and @line_index > 0
@byte_pointer = @buffer_of_lines[@line_index - 1].bytesize
@buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index)
@line_index -= 1
- @line = @buffer_of_lines[@line_index]
- @cursor_max = calculate_width(@line)
- @rerender_all = true
- elsif @cursor > 0
- byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
+ process_auto_indent cursor_dependent: false
+ elsif @byte_pointer > 0
+ byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer)
@byte_pointer -= byte_size
- @line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
- width = Reline::Unicode.get_mbchar_width(mbchar)
- @cursor -= width
- @cursor_max -= width
+ line, _ = byteslice!(current_line, @byte_pointer, byte_size)
+ set_current_line(line)
end
end
@@ -2989,78 +2160,81 @@ class Reline::LineEditor
end
private def ed_delete_prev_char(key, arg: 1)
- deleted = ''
+ deleted = +''
arg.times do
- if @cursor > 0
- byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
+ if @byte_pointer > 0
+ byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer)
@byte_pointer -= byte_size
- @line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
+ line, mbchar = byteslice!(current_line, @byte_pointer, byte_size)
+ set_current_line(line)
deleted.prepend(mbchar)
- width = Reline::Unicode.get_mbchar_width(mbchar)
- @cursor -= width
- @cursor_max -= width
end
end
copy_for_vi(deleted)
end
- private def vi_zero(key)
- @byte_pointer = 0
- @cursor = 0
- end
-
- private def vi_change_meta(key, arg: 1)
- @drop_terminate_spaces = true
- @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
- if byte_pointer_diff > 0
- @line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff)
- elsif byte_pointer_diff < 0
- @line, cut = byteslice!(@line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff)
- end
- copy_for_vi(cut)
- @cursor += cursor_diff if cursor_diff < 0
- @cursor_max -= cursor_diff.abs
- @byte_pointer += byte_pointer_diff if byte_pointer_diff < 0
- @config.editing_mode = :vi_insert
- @drop_terminate_spaces = false
- }
- @waiting_operator_vi_arg = arg
+ private def vi_change_meta(key, arg: nil)
+ if @vi_waiting_operator
+ set_current_line('', 0) if @vi_waiting_operator == :vi_change_meta_confirm && arg.nil?
+ @vi_waiting_operator = nil
+ @vi_waiting_operator_arg = nil
+ else
+ @drop_terminate_spaces = true
+ @vi_waiting_operator = :vi_change_meta_confirm
+ @vi_waiting_operator_arg = arg || 1
+ end
end
- private def vi_delete_meta(key, arg: 1)
- @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
- if byte_pointer_diff > 0
- @line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff)
- elsif byte_pointer_diff < 0
- @line, cut = byteslice!(@line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff)
- end
- copy_for_vi(cut)
- @cursor += cursor_diff if cursor_diff < 0
- @cursor_max -= cursor_diff.abs
- @byte_pointer += byte_pointer_diff if byte_pointer_diff < 0
- }
- @waiting_operator_vi_arg = arg
+ private def vi_change_meta_confirm(byte_pointer_diff)
+ vi_delete_meta_confirm(byte_pointer_diff)
+ @config.editing_mode = :vi_insert
+ @drop_terminate_spaces = false
end
- private def vi_yank(key, arg: 1)
- @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
- if byte_pointer_diff > 0
- cut = @line.byteslice(@byte_pointer, byte_pointer_diff)
- elsif byte_pointer_diff < 0
- cut = @line.byteslice(@byte_pointer + byte_pointer_diff, -byte_pointer_diff)
- end
- copy_for_vi(cut)
- }
- @waiting_operator_vi_arg = arg
+ private def vi_delete_meta(key, arg: nil)
+ if @vi_waiting_operator
+ set_current_line('', 0) if @vi_waiting_operator == :vi_delete_meta_confirm && arg.nil?
+ @vi_waiting_operator = nil
+ @vi_waiting_operator_arg = nil
+ else
+ @vi_waiting_operator = :vi_delete_meta_confirm
+ @vi_waiting_operator_arg = arg || 1
+ end
+ end
+
+ private def vi_delete_meta_confirm(byte_pointer_diff)
+ if byte_pointer_diff > 0
+ line, cut = byteslice!(current_line, @byte_pointer, byte_pointer_diff)
+ elsif byte_pointer_diff < 0
+ line, cut = byteslice!(current_line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff)
+ end
+ copy_for_vi(cut)
+ set_current_line(line || '', @byte_pointer + (byte_pointer_diff < 0 ? byte_pointer_diff : 0))
+ end
+
+ private def vi_yank(key, arg: nil)
+ if @vi_waiting_operator
+ copy_for_vi(current_line) if @vi_waiting_operator == :vi_yank_confirm && arg.nil?
+ @vi_waiting_operator = nil
+ @vi_waiting_operator_arg = nil
+ else
+ @vi_waiting_operator = :vi_yank_confirm
+ @vi_waiting_operator_arg = arg || 1
+ end
+ end
+
+ private def vi_yank_confirm(byte_pointer_diff)
+ if byte_pointer_diff > 0
+ cut = current_line.byteslice(@byte_pointer, byte_pointer_diff)
+ elsif byte_pointer_diff < 0
+ cut = current_line.byteslice(@byte_pointer + byte_pointer_diff, -byte_pointer_diff)
+ end
+ copy_for_vi(cut)
end
private def vi_list_or_eof(key)
- if (not @is_multiline and @line.empty?) or (@is_multiline and @line.empty? and @buffer_of_lines.size == 1)
- @line = nil
- if @buffer_of_lines.size > 1
- scroll_down(@highest_in_all - @first_line_started_from)
- end
- Reline::IOGate.move_cursor_column(0)
+ if current_line.empty? and @buffer_of_lines.size == 1
+ set_current_line('', 0)
@eof = true
finish
else
@@ -3071,18 +2245,15 @@ class Reline::LineEditor
alias_method :vi_eof_maybe, :vi_list_or_eof
private def ed_delete_next_char(key, arg: 1)
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
- unless @line.empty? || byte_size == 0
- @line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
+ unless current_line.empty? || byte_size == 0
+ line, mbchar = byteslice!(current_line, @byte_pointer, byte_size)
copy_for_vi(mbchar)
- width = Reline::Unicode.get_mbchar_width(mbchar)
- @cursor_max -= width
- if @cursor > 0 and @cursor >= @cursor_max
- byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
- mbchar = @line.byteslice(@byte_pointer - byte_size, byte_size)
- width = Reline::Unicode.get_mbchar_width(mbchar)
- @byte_pointer -= byte_size
- @cursor -= width
+ if @byte_pointer > 0 && current_line.bytesize == @byte_pointer + byte_size
+ byte_size = Reline::Unicode.get_prev_mbchar_size(line, @byte_pointer)
+ set_current_line(line, @byte_pointer - byte_size)
+ else
+ set_current_line(line, @byte_pointer)
end
end
arg -= 1
@@ -3093,54 +2264,25 @@ class Reline::LineEditor
if Reline::HISTORY.empty?
return
end
- if @history_pointer.nil?
- @history_pointer = 0
- @line_backup_in_history = @line
- @line = Reline::HISTORY[@history_pointer]
- @cursor_max = calculate_width(@line)
- @cursor = 0
- @byte_pointer = 0
- elsif @history_pointer.zero?
- return
- else
- Reline::HISTORY[@history_pointer] = @line
- @history_pointer = 0
- @line = Reline::HISTORY[@history_pointer]
- @cursor_max = calculate_width(@line)
- @cursor = 0
- @byte_pointer = 0
- end
+ move_history(0, line: :start, cursor: :start)
end
private def vi_histedit(key)
path = Tempfile.open { |fp|
- if @is_multiline
- fp.write whole_lines.join("\n")
- else
- fp.write @line
- end
+ fp.write whole_lines.join("\n")
fp.path
}
system("#{ENV['EDITOR']} #{path}")
- if @is_multiline
- @buffer_of_lines = File.read(path).split("\n")
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
- @line_index = 0
- @line = @buffer_of_lines[@line_index]
- @rerender_all = true
- else
- @line = File.read(path)
- end
+ @buffer_of_lines = File.read(path).split("\n")
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
+ @line_index = 0
finish
end
private def vi_paste_prev(key, arg: 1)
if @vi_clipboard.size > 0
- @line = byteinsert(@line, @byte_pointer, @vi_clipboard)
- @cursor_max += calculate_width(@vi_clipboard)
cursor_point = @vi_clipboard.grapheme_clusters[0..-2].join
- @cursor += calculate_width(cursor_point)
- @byte_pointer += cursor_point.bytesize
+ set_current_line(byteinsert(current_line, @byte_pointer, @vi_clipboard), @byte_pointer + cursor_point.bytesize)
end
arg -= 1
vi_paste_prev(key, arg: arg) if arg > 0
@@ -3148,11 +2290,9 @@ class Reline::LineEditor
private def vi_paste_next(key, arg: 1)
if @vi_clipboard.size > 0
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
- @line = byteinsert(@line, @byte_pointer + byte_size, @vi_clipboard)
- @cursor_max += calculate_width(@vi_clipboard)
- @cursor += calculate_width(@vi_clipboard)
- @byte_pointer += @vi_clipboard.bytesize
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
+ line = byteinsert(current_line, @byte_pointer + byte_size, @vi_clipboard)
+ set_current_line(line, @byte_pointer + @vi_clipboard.bytesize)
end
arg -= 1
vi_paste_next(key, arg: arg) if arg > 0
@@ -3176,43 +2316,33 @@ class Reline::LineEditor
end
private def vi_to_column(key, arg: 0)
- @byte_pointer, @cursor = @line.grapheme_clusters.inject([0, 0]) { |total, gc|
- # total has [byte_size, cursor]
+ # Implementing behavior of vi, not Readline's vi-mode.
+ @byte_pointer, = current_line.grapheme_clusters.inject([0, 0]) { |(total_byte_size, total_width), gc|
mbchar_width = Reline::Unicode.get_mbchar_width(gc)
- if (total.last + mbchar_width) >= arg
- break total
- elsif (total.last + mbchar_width) >= @cursor_max
- break total
- else
- total = [total.first + gc.bytesize, total.last + mbchar_width]
- total
- end
+ break [total_byte_size, total_width] if (total_width + mbchar_width) >= arg
+ [total_byte_size + gc.bytesize, total_width + mbchar_width]
}
end
private def vi_replace_char(key, arg: 1)
@waiting_proc = ->(k) {
if arg == 1
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
- before = @line.byteslice(0, @byte_pointer)
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
+ before = current_line.byteslice(0, @byte_pointer)
remaining_point = @byte_pointer + byte_size
- after = @line.byteslice(remaining_point, @line.bytesize - remaining_point)
- @line = before + k.chr + after
- @cursor_max = calculate_width(@line)
+ after = current_line.byteslice(remaining_point, current_line.bytesize - remaining_point)
+ set_current_line(before + k.chr + after)
@waiting_proc = nil
elsif arg > 1
byte_size = 0
arg.times do
- byte_size += Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer + byte_size)
+ byte_size += Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer + byte_size)
end
- before = @line.byteslice(0, @byte_pointer)
+ before = current_line.byteslice(0, @byte_pointer)
remaining_point = @byte_pointer + byte_size
- after = @line.byteslice(remaining_point, @line.bytesize - remaining_point)
+ after = current_line.byteslice(remaining_point, current_line.bytesize - remaining_point)
replaced = k.chr * arg
- @line = before + replaced + after
- @byte_pointer += replaced.bytesize
- @cursor += calculate_width(replaced)
- @cursor_max = calculate_width(@line)
+ set_current_line(before + replaced + after, @byte_pointer + replaced.bytesize)
@waiting_proc = nil
end
}
@@ -3235,7 +2365,7 @@ class Reline::LineEditor
prev_total = nil
total = nil
found = false
- @line.byteslice(@byte_pointer..-1).grapheme_clusters.each do |mbchar|
+ current_line.byteslice(@byte_pointer..-1).grapheme_clusters.each do |mbchar|
# total has [byte_size, cursor]
unless total
# skip cursor point
@@ -3255,21 +2385,16 @@ class Reline::LineEditor
end
end
if not need_prev_char and found and total
- byte_size, width = total
+ byte_size, _ = total
@byte_pointer += byte_size
- @cursor += width
elsif need_prev_char and found and prev_total
- byte_size, width = prev_total
+ byte_size, _ = prev_total
@byte_pointer += byte_size
- @cursor += width
end
if inclusive
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
if byte_size > 0
- c = @line.byteslice(@byte_pointer, byte_size)
- width = Reline::Unicode.get_mbchar_width(c)
@byte_pointer += byte_size
- @cursor += width
end
end
@waiting_proc = nil
@@ -3292,7 +2417,7 @@ class Reline::LineEditor
prev_total = nil
total = nil
found = false
- @line.byteslice(0..@byte_pointer).grapheme_clusters.reverse_each do |mbchar|
+ current_line.byteslice(0..@byte_pointer).grapheme_clusters.reverse_each do |mbchar|
# total has [byte_size, cursor]
unless total
# skip cursor point
@@ -3312,26 +2437,19 @@ class Reline::LineEditor
end
end
if not need_next_char and found and total
- byte_size, width = total
+ byte_size, _ = total
@byte_pointer -= byte_size
- @cursor -= width
elsif need_next_char and found and prev_total
- byte_size, width = prev_total
+ byte_size, _ = prev_total
@byte_pointer -= byte_size
- @cursor -= width
end
@waiting_proc = nil
end
private def vi_join_lines(key, arg: 1)
- if @is_multiline and @buffer_of_lines.size > @line_index + 1
- @cursor = calculate_width(@line)
- @byte_pointer = @line.bytesize
- @line += ' ' + @buffer_of_lines.delete_at(@line_index + 1).lstrip
- @cursor_max = calculate_width(@line)
- @buffer_of_lines[@line_index] = @line
- @rerender_all = true
- @rest_height += 1
+ if @buffer_of_lines.size > @line_index + 1
+ next_line = @buffer_of_lines.delete_at(@line_index + 1).lstrip
+ set_current_line(current_line + ' ' + next_line, current_line.bytesize)
end
arg -= 1
vi_join_lines(key, arg: arg) if arg > 0
@@ -3345,11 +2463,16 @@ class Reline::LineEditor
private def em_exchange_mark(key)
return unless @mark_pointer
new_pointer = [@byte_pointer, @line_index]
- @previous_line_index = @line_index
@byte_pointer, @line_index = @mark_pointer
- @cursor = calculate_width(@line.byteslice(0, @byte_pointer))
- @cursor_max = calculate_width(@line)
@mark_pointer = new_pointer
end
alias_method :exchange_point_and_mark, :em_exchange_mark
+
+ private def emacs_editing_mode(key)
+ @config.editing_mode = :emacs
+ end
+
+ private def vi_editing_mode(key)
+ @config.editing_mode = :vi_insert
+ end
end
diff --git a/lib/reline/reline.gemspec b/lib/reline/reline.gemspec
index 7bf1f8758b..dfaf966728 100644
--- a/lib/reline/reline.gemspec
+++ b/lib/reline/reline.gemspec
@@ -18,6 +18,11 @@ Gem::Specification.new do |spec|
spec.files = Dir['BSDL', 'COPYING', 'README.md', 'license_of_rb-readline', 'lib/**/*']
spec.require_paths = ['lib']
+ spec.metadata = {
+ "bug_tracker_uri" => "https://github.com/ruby/reline/issues",
+ "changelog_uri" => "https://github.com/ruby/reline/releases",
+ "source_code_uri" => "https://github.com/ruby/reline"
+ }
spec.required_ruby_version = Gem::Requirement.new('>= 2.6')
diff --git a/lib/reline/terminfo.rb b/lib/reline/terminfo.rb
index f53642b919..6885a0c6be 100644
--- a/lib/reline/terminfo.rb
+++ b/lib/reline/terminfo.rb
@@ -31,21 +31,7 @@ module Reline::Terminfo
@curses_dl = false
def self.curses_dl
return @curses_dl unless @curses_dl == false
- if RUBY_VERSION >= '3.0.0'
- # Gem module isn't defined in test-all of the Ruby repository, and
- # Fiddle in Ruby 3.0.0 or later supports Fiddle::TYPE_VARIADIC.
- fiddle_supports_variadic = true
- elsif Fiddle.const_defined?(:VERSION,false) and Gem::Version.create(Fiddle::VERSION) >= Gem::Version.create('1.0.1')
- # Fiddle::TYPE_VARIADIC is supported from Fiddle 1.0.1.
- fiddle_supports_variadic = true
- else
- fiddle_supports_variadic = false
- end
- if fiddle_supports_variadic and not Fiddle.const_defined?(:TYPE_VARIADIC)
- # If the libffi version is not 3.0.5 or higher, there isn't TYPE_VARIADIC.
- fiddle_supports_variadic = false
- end
- if fiddle_supports_variadic
+ if Fiddle.const_defined?(:TYPE_VARIADIC)
curses_dl_files.each do |curses_name|
result = Fiddle::Handle.new(curses_name)
rescue Fiddle::DLError
@@ -94,23 +80,11 @@ module Reline::Terminfo
def self.setupterm(term, fildes)
errret_int = Fiddle::Pointer.malloc(Fiddle::SIZEOF_INT)
ret = @setupterm.(term, fildes, errret_int)
- errret = errret_int[0, Fiddle::SIZEOF_INT].unpack1('i')
case ret
when 0 # OK
- 0
+ @term_supported = true
when -1 # ERR
- case errret
- when 1
- raise TerminfoError.new('The terminal is hardcopy, cannot be used for curses applications.')
- when 0
- raise TerminfoError.new('The terminal could not be found, or that it is a generic type, having too little information for curses applications to run.')
- when -1
- raise TerminfoError.new('The terminfo database could not be found.')
- else # unknown
- -1
- end
- else # unknown
- -2
+ @term_supported = false
end
end
@@ -162,9 +136,14 @@ module Reline::Terminfo
num
end
+ # NOTE: This means Fiddle and curses are enabled.
def self.enabled?
true
end
+
+ def self.term_supported?
+ @term_supported
+ end
end if Reline::Terminfo.curses_dl
module Reline::Terminfo
diff --git a/lib/reline/unicode.rb b/lib/reline/unicode.rb
index 6000c9f82a..7f94e95287 100644
--- a/lib/reline/unicode.rb
+++ b/lib/reline/unicode.rb
@@ -38,33 +38,8 @@ class Reline::Unicode
NON_PRINTING_START = "\1"
NON_PRINTING_END = "\2"
CSI_REGEXP = /\e\[[\d;]*[ABCDEFGHJKSTfminsuhl]/
- OSC_REGEXP = /\e\]\d+(?:;[^;]+)*\a/
+ OSC_REGEXP = /\e\]\d+(?:;[^;\a\e]+)*(?:\a|\e\\)/
WIDTH_SCANNER = /\G(?:(#{NON_PRINTING_START})|(#{NON_PRINTING_END})|(#{CSI_REGEXP})|(#{OSC_REGEXP})|(\X))/o
- NON_PRINTING_START_INDEX = 0
- NON_PRINTING_END_INDEX = 1
- CSI_REGEXP_INDEX = 2
- OSC_REGEXP_INDEX = 3
- GRAPHEME_CLUSTER_INDEX = 4
-
- def self.get_mbchar_byte_size_by_first_char(c)
- # Checks UTF-8 character byte size
- case c.ord
- # 0b0xxxxxxx
- when ->(code) { (code ^ 0b10000000).allbits?(0b10000000) } then 1
- # 0b110xxxxx
- when ->(code) { (code ^ 0b00100000).allbits?(0b11100000) } then 2
- # 0b1110xxxx
- when ->(code) { (code ^ 0b00010000).allbits?(0b11110000) } then 3
- # 0b11110xxx
- when ->(code) { (code ^ 0b00001000).allbits?(0b11111000) } then 4
- # 0b111110xx
- when ->(code) { (code ^ 0b00000100).allbits?(0b11111100) } then 5
- # 0b1111110x
- when ->(code) { (code ^ 0b00000010).allbits?(0b11111110) } then 6
- # successor of mbchar
- else 0
- end
- end
def self.escape_for_print(str)
str.chars.map! { |gr|
@@ -132,15 +107,14 @@ class Reline::Unicode
width = 0
rest = str.encode(Encoding::UTF_8)
in_zero_width = false
- rest.scan(WIDTH_SCANNER) do |gc|
+ rest.scan(WIDTH_SCANNER) do |non_printing_start, non_printing_end, csi, osc, gc|
case
- when gc[NON_PRINTING_START_INDEX]
+ when non_printing_start
in_zero_width = true
- when gc[NON_PRINTING_END_INDEX]
+ when non_printing_end
in_zero_width = false
- when gc[CSI_REGEXP_INDEX], gc[OSC_REGEXP_INDEX]
- when gc[GRAPHEME_CLUSTER_INDEX]
- gc = gc[GRAPHEME_CLUSTER_INDEX]
+ when csi, osc
+ when gc
unless in_zero_width
width += get_mbchar_width(gc)
end
@@ -154,30 +128,40 @@ class Reline::Unicode
end
end
- def self.split_by_width(str, max_width, encoding = str.encoding)
+ def self.split_by_width(str, max_width, encoding = str.encoding, offset: 0)
lines = [String.new(encoding: encoding)]
height = 1
- width = 0
+ width = offset
rest = str.encode(Encoding::UTF_8)
in_zero_width = false
- rest.scan(WIDTH_SCANNER) do |gc|
+ seq = String.new(encoding: encoding)
+ rest.scan(WIDTH_SCANNER) do |non_printing_start, non_printing_end, csi, osc, gc|
case
- when gc[NON_PRINTING_START_INDEX]
+ when non_printing_start
in_zero_width = true
- when gc[NON_PRINTING_END_INDEX]
+ lines.last << NON_PRINTING_START
+ when non_printing_end
in_zero_width = false
- when gc[CSI_REGEXP_INDEX]
- lines.last << gc[CSI_REGEXP_INDEX]
- when gc[OSC_REGEXP_INDEX]
- lines.last << gc[OSC_REGEXP_INDEX]
- when gc[GRAPHEME_CLUSTER_INDEX]
- gc = gc[GRAPHEME_CLUSTER_INDEX]
+ lines.last << NON_PRINTING_END
+ when csi
+ lines.last << csi
+ unless in_zero_width
+ if csi == -"\e[m" || csi == -"\e[0m"
+ seq.clear
+ else
+ seq << csi
+ end
+ end
+ when osc
+ lines.last << osc
+ seq << osc
+ when gc
unless in_zero_width
mbchar_width = get_mbchar_width(gc)
if (width += mbchar_width) > max_width
width = mbchar_width
lines << nil
- lines << String.new(encoding: encoding)
+ lines << seq.dup
height += 1
end
end
@@ -194,23 +178,22 @@ class Reline::Unicode
end
# Take a chunk of a String cut by width with escape sequences.
- def self.take_range(str, start_col, max_width, encoding = str.encoding)
- chunk = String.new(encoding: encoding)
+ def self.take_range(str, start_col, max_width)
+ chunk = String.new(encoding: str.encoding)
total_width = 0
rest = str.encode(Encoding::UTF_8)
in_zero_width = false
- rest.scan(WIDTH_SCANNER) do |gc|
+ rest.scan(WIDTH_SCANNER) do |non_printing_start, non_printing_end, csi, osc, gc|
case
- when gc[NON_PRINTING_START_INDEX]
+ when non_printing_start
in_zero_width = true
- when gc[NON_PRINTING_END_INDEX]
+ when non_printing_end
in_zero_width = false
- when gc[CSI_REGEXP_INDEX]
- chunk << gc[CSI_REGEXP_INDEX]
- when gc[OSC_REGEXP_INDEX]
- chunk << gc[OSC_REGEXP_INDEX]
- when gc[GRAPHEME_CLUSTER_INDEX]
- gc = gc[GRAPHEME_CLUSTER_INDEX]
+ when csi
+ chunk << csi
+ when osc
+ chunk << osc
+ when gc
if in_zero_width
chunk << gc
else
diff --git a/lib/reline/unicode/east_asian_width.rb b/lib/reline/unicode/east_asian_width.rb
index 89bc9d9435..fa16a1bb56 100644
--- a/lib/reline/unicode/east_asian_width.rb
+++ b/lib/reline/unicode/east_asian_width.rb
@@ -1,6 +1,6 @@
class Reline::Unicode::EastAsianWidth
# This is based on EastAsianWidth.txt
- # EastAsianWidth.txt
+ # UNICODE_VERSION = '15.1.0'
# Fullwidth
TYPE_F = /^[#{ %W(
@@ -60,14 +60,14 @@ class Reline::Unicode::EastAsianWidth
\u{2E80}-\u{2E99}
\u{2E9B}-\u{2EF3}
\u{2F00}-\u{2FD5}
- \u{2FF0}-\u{2FFB}
+ \u{2FF0}-\u{2FFF}
\u{3001}-\u{303E}
\u{3041}-\u{3096}
\u{3099}-\u{30FF}
\u{3105}-\u{312F}
\u{3131}-\u{318E}
\u{3190}-\u{31E3}
- \u{31F0}-\u{321E}
+ \u{31EF}-\u{321E}
\u{3220}-\u{3247}
\u{3250}-\u{4DBF}
\u{4E00}-\u{A48C}
@@ -84,8 +84,13 @@ class Reline::Unicode::EastAsianWidth
\u{17000}-\u{187F7}
\u{18800}-\u{18CD5}
\u{18D00}-\u{18D08}
- \u{1B000}-\u{1B11E}
+ \u{1AFF0}-\u{1AFF3}
+ \u{1AFF5}-\u{1AFFB}
+ \u{1AFFD}-\u{1AFFE}
+ \u{1B000}-\u{1B122}
+ \u{1B132}
\u{1B150}-\u{1B152}
+ \u{1B155}
\u{1B164}-\u{1B167}
\u{1B170}-\u{1B2FB}
\u{1F004}
@@ -119,21 +124,21 @@ class Reline::Unicode::EastAsianWidth
\u{1F6CC}
\u{1F6D0}-\u{1F6D2}
\u{1F6D5}-\u{1F6D7}
+ \u{1F6DC}-\u{1F6DF}
\u{1F6EB}-\u{1F6EC}
\u{1F6F4}-\u{1F6FC}
\u{1F7E0}-\u{1F7EB}
+ \u{1F7F0}
\u{1F90C}-\u{1F93A}
\u{1F93C}-\u{1F945}
- \u{1F947}-\u{1F978}
- \u{1F97A}-\u{1F9CB}
- \u{1F9CD}-\u{1F9FF}
- \u{1FA70}-\u{1FA74}
- \u{1FA78}-\u{1FA7A}
- \u{1FA80}-\u{1FA86}
- \u{1FA90}-\u{1FAA8}
- \u{1FAB0}-\u{1FAB6}
- \u{1FAC0}-\u{1FAC2}
- \u{1FAD0}-\u{1FAD6}
+ \u{1F947}-\u{1F9FF}
+ \u{1FA70}-\u{1FA7C}
+ \u{1FA80}-\u{1FA88}
+ \u{1FA90}-\u{1FABD}
+ \u{1FABF}-\u{1FAC5}
+ \u{1FACE}-\u{1FADB}
+ \u{1FAE0}-\u{1FAE8}
+ \u{1FAF0}-\u{1FAF8}
\u{20000}-\u{2FFFD}
\u{30000}-\u{3FFFD}
).join }]/
@@ -403,8 +408,7 @@ class Reline::Unicode::EastAsianWidth
\u{0591}-\u{05C7}
\u{05D0}-\u{05EA}
\u{05EF}-\u{05F4}
- \u{0600}-\u{061C}
- \u{061E}-\u{070D}
+ \u{0600}-\u{070D}
\u{070F}-\u{074A}
\u{074D}-\u{07B1}
\u{07C0}-\u{07FA}
@@ -413,9 +417,9 @@ class Reline::Unicode::EastAsianWidth
\u{0840}-\u{085B}
\u{085E}
\u{0860}-\u{086A}
- \u{08A0}-\u{08B4}
- \u{08B6}-\u{08C7}
- \u{08D3}-\u{0983}
+ \u{0870}-\u{088E}
+ \u{0890}-\u{0891}
+ \u{0898}-\u{0983}
\u{0985}-\u{098C}
\u{098F}-\u{0990}
\u{0993}-\u{09A8}
@@ -493,11 +497,12 @@ class Reline::Unicode::EastAsianWidth
\u{0C0E}-\u{0C10}
\u{0C12}-\u{0C28}
\u{0C2A}-\u{0C39}
- \u{0C3D}-\u{0C44}
+ \u{0C3C}-\u{0C44}
\u{0C46}-\u{0C48}
\u{0C4A}-\u{0C4D}
\u{0C55}-\u{0C56}
\u{0C58}-\u{0C5A}
+ \u{0C5D}
\u{0C60}-\u{0C63}
\u{0C66}-\u{0C6F}
\u{0C77}-\u{0C8C}
@@ -509,10 +514,10 @@ class Reline::Unicode::EastAsianWidth
\u{0CC6}-\u{0CC8}
\u{0CCA}-\u{0CCD}
\u{0CD5}-\u{0CD6}
- \u{0CDE}
+ \u{0CDD}-\u{0CDE}
\u{0CE0}-\u{0CE3}
\u{0CE6}-\u{0CEF}
- \u{0CF1}-\u{0CF2}
+ \u{0CF1}-\u{0CF3}
\u{0D00}-\u{0D0C}
\u{0D0E}-\u{0D10}
\u{0D12}-\u{0D44}
@@ -542,7 +547,7 @@ class Reline::Unicode::EastAsianWidth
\u{0EA7}-\u{0EBD}
\u{0EC0}-\u{0EC4}
\u{0EC6}
- \u{0EC8}-\u{0ECD}
+ \u{0EC8}-\u{0ECE}
\u{0ED0}-\u{0ED9}
\u{0EDC}-\u{0EDF}
\u{0F00}-\u{0F47}
@@ -577,9 +582,8 @@ class Reline::Unicode::EastAsianWidth
\u{13F8}-\u{13FD}
\u{1400}-\u{169C}
\u{16A0}-\u{16F8}
- \u{1700}-\u{170C}
- \u{170E}-\u{1714}
- \u{1720}-\u{1736}
+ \u{1700}-\u{1715}
+ \u{171F}-\u{1736}
\u{1740}-\u{1753}
\u{1760}-\u{176C}
\u{176E}-\u{1770}
@@ -587,8 +591,7 @@ class Reline::Unicode::EastAsianWidth
\u{1780}-\u{17DD}
\u{17E0}-\u{17E9}
\u{17F0}-\u{17F9}
- \u{1800}-\u{180E}
- \u{1810}-\u{1819}
+ \u{1800}-\u{1819}
\u{1820}-\u{1878}
\u{1880}-\u{18AA}
\u{18B0}-\u{18F5}
@@ -607,9 +610,9 @@ class Reline::Unicode::EastAsianWidth
\u{1A7F}-\u{1A89}
\u{1A90}-\u{1A99}
\u{1AA0}-\u{1AAD}
- \u{1AB0}-\u{1AC0}
- \u{1B00}-\u{1B4B}
- \u{1B50}-\u{1B7C}
+ \u{1AB0}-\u{1ACE}
+ \u{1B00}-\u{1B4C}
+ \u{1B50}-\u{1B7E}
\u{1B80}-\u{1BF3}
\u{1BFC}-\u{1C37}
\u{1C3B}-\u{1C49}
@@ -617,8 +620,7 @@ class Reline::Unicode::EastAsianWidth
\u{1C90}-\u{1CBA}
\u{1CBD}-\u{1CC7}
\u{1CD0}-\u{1CFA}
- \u{1D00}-\u{1DF9}
- \u{1DFB}-\u{1F15}
+ \u{1D00}-\u{1F15}
\u{1F18}-\u{1F1D}
\u{1F20}-\u{1F45}
\u{1F48}-\u{1F4D}
@@ -653,7 +655,7 @@ class Reline::Unicode::EastAsianWidth
\u{2090}-\u{209C}
\u{20A0}-\u{20A8}
\u{20AA}-\u{20AB}
- \u{20AD}-\u{20BF}
+ \u{20AD}-\u{20C0}
\u{20D0}-\u{20F0}
\u{2100}-\u{2102}
\u{2104}
@@ -767,9 +769,7 @@ class Reline::Unicode::EastAsianWidth
\u{2B51}-\u{2B54}
\u{2B5A}-\u{2B73}
\u{2B76}-\u{2B95}
- \u{2B97}-\u{2C2E}
- \u{2C30}-\u{2C5E}
- \u{2C60}-\u{2CF3}
+ \u{2B97}-\u{2CF3}
\u{2CF9}-\u{2D25}
\u{2D27}
\u{2D2D}
@@ -784,14 +784,16 @@ class Reline::Unicode::EastAsianWidth
\u{2DC8}-\u{2DCE}
\u{2DD0}-\u{2DD6}
\u{2DD8}-\u{2DDE}
- \u{2DE0}-\u{2E52}
+ \u{2DE0}-\u{2E5D}
\u{303F}
\u{4DC0}-\u{4DFF}
\u{A4D0}-\u{A62B}
\u{A640}-\u{A6F7}
- \u{A700}-\u{A7BF}
- \u{A7C2}-\u{A7CA}
- \u{A7F5}-\u{A82C}
+ \u{A700}-\u{A7CA}
+ \u{A7D0}-\u{A7D1}
+ \u{A7D3}
+ \u{A7D5}-\u{A7D9}
+ \u{A7F2}-\u{A82C}
\u{A830}-\u{A839}
\u{A840}-\u{A877}
\u{A880}-\u{A8C5}
@@ -823,11 +825,11 @@ class Reline::Unicode::EastAsianWidth
\u{FB3E}
\u{FB40}-\u{FB41}
\u{FB43}-\u{FB44}
- \u{FB46}-\u{FBC1}
- \u{FBD3}-\u{FD3F}
- \u{FD50}-\u{FD8F}
+ \u{FB46}-\u{FBC2}
+ \u{FBD3}-\u{FD8F}
\u{FD92}-\u{FDC7}
- \u{FDF0}-\u{FDFD}
+ \u{FDCF}
+ \u{FDF0}-\u{FDFF}
\u{FE20}-\u{FE2F}
\u{FE70}-\u{FE74}
\u{FE76}-\u{FEFC}
@@ -861,10 +863,20 @@ class Reline::Unicode::EastAsianWidth
\u{104D8}-\u{104FB}
\u{10500}-\u{10527}
\u{10530}-\u{10563}
- \u{1056F}
+ \u{1056F}-\u{1057A}
+ \u{1057C}-\u{1058A}
+ \u{1058C}-\u{10592}
+ \u{10594}-\u{10595}
+ \u{10597}-\u{105A1}
+ \u{105A3}-\u{105B1}
+ \u{105B3}-\u{105B9}
+ \u{105BB}-\u{105BC}
\u{10600}-\u{10736}
\u{10740}-\u{10755}
\u{10760}-\u{10767}
+ \u{10780}-\u{10785}
+ \u{10787}-\u{107B0}
+ \u{107B2}-\u{107BA}
\u{10800}-\u{10805}
\u{10808}
\u{1080A}-\u{10835}
@@ -906,13 +918,14 @@ class Reline::Unicode::EastAsianWidth
\u{10E80}-\u{10EA9}
\u{10EAB}-\u{10EAD}
\u{10EB0}-\u{10EB1}
- \u{10F00}-\u{10F27}
+ \u{10EFD}-\u{10F27}
\u{10F30}-\u{10F59}
+ \u{10F70}-\u{10F89}
\u{10FB0}-\u{10FCB}
\u{10FE0}-\u{10FF6}
\u{11000}-\u{1104D}
- \u{11052}-\u{1106F}
- \u{1107F}-\u{110C1}
+ \u{11052}-\u{11075}
+ \u{1107F}-\u{110C2}
\u{110CD}
\u{110D0}-\u{110E8}
\u{110F0}-\u{110F9}
@@ -922,7 +935,7 @@ class Reline::Unicode::EastAsianWidth
\u{11180}-\u{111DF}
\u{111E1}-\u{111F4}
\u{11200}-\u{11211}
- \u{11213}-\u{1123E}
+ \u{11213}-\u{11241}
\u{11280}-\u{11286}
\u{11288}
\u{1128A}-\u{1128D}
@@ -954,11 +967,11 @@ class Reline::Unicode::EastAsianWidth
\u{11600}-\u{11644}
\u{11650}-\u{11659}
\u{11660}-\u{1166C}
- \u{11680}-\u{116B8}
+ \u{11680}-\u{116B9}
\u{116C0}-\u{116C9}
\u{11700}-\u{1171A}
\u{1171D}-\u{1172B}
- \u{11730}-\u{1173F}
+ \u{11730}-\u{11746}
\u{11800}-\u{1183B}
\u{118A0}-\u{118F2}
\u{118FF}-\u{11906}
@@ -974,7 +987,8 @@ class Reline::Unicode::EastAsianWidth
\u{119DA}-\u{119E4}
\u{11A00}-\u{11A47}
\u{11A50}-\u{11AA2}
- \u{11AC0}-\u{11AF8}
+ \u{11AB0}-\u{11AF8}
+ \u{11B00}-\u{11B09}
\u{11C00}-\u{11C08}
\u{11C0A}-\u{11C36}
\u{11C38}-\u{11C45}
@@ -996,19 +1010,23 @@ class Reline::Unicode::EastAsianWidth
\u{11D93}-\u{11D98}
\u{11DA0}-\u{11DA9}
\u{11EE0}-\u{11EF8}
+ \u{11F00}-\u{11F10}
+ \u{11F12}-\u{11F3A}
+ \u{11F3E}-\u{11F59}
\u{11FB0}
\u{11FC0}-\u{11FF1}
\u{11FFF}-\u{12399}
\u{12400}-\u{1246E}
\u{12470}-\u{12474}
\u{12480}-\u{12543}
- \u{13000}-\u{1342E}
- \u{13430}-\u{13438}
+ \u{12F90}-\u{12FF2}
+ \u{13000}-\u{13455}
\u{14400}-\u{14646}
\u{16800}-\u{16A38}
\u{16A40}-\u{16A5E}
\u{16A60}-\u{16A69}
- \u{16A6E}-\u{16A6F}
+ \u{16A6E}-\u{16ABE}
+ \u{16AC0}-\u{16AC9}
\u{16AD0}-\u{16AED}
\u{16AF0}-\u{16AF5}
\u{16B00}-\u{16B45}
@@ -1025,10 +1043,14 @@ class Reline::Unicode::EastAsianWidth
\u{1BC80}-\u{1BC88}
\u{1BC90}-\u{1BC99}
\u{1BC9C}-\u{1BCA3}
+ \u{1CF00}-\u{1CF2D}
+ \u{1CF30}-\u{1CF46}
+ \u{1CF50}-\u{1CFC3}
\u{1D000}-\u{1D0F5}
\u{1D100}-\u{1D126}
- \u{1D129}-\u{1D1E8}
+ \u{1D129}-\u{1D1EA}
\u{1D200}-\u{1D245}
+ \u{1D2C0}-\u{1D2D3}
\u{1D2E0}-\u{1D2F3}
\u{1D300}-\u{1D356}
\u{1D360}-\u{1D378}
@@ -1055,17 +1077,27 @@ class Reline::Unicode::EastAsianWidth
\u{1D7CE}-\u{1DA8B}
\u{1DA9B}-\u{1DA9F}
\u{1DAA1}-\u{1DAAF}
+ \u{1DF00}-\u{1DF1E}
+ \u{1DF25}-\u{1DF2A}
\u{1E000}-\u{1E006}
\u{1E008}-\u{1E018}
\u{1E01B}-\u{1E021}
\u{1E023}-\u{1E024}
\u{1E026}-\u{1E02A}
+ \u{1E030}-\u{1E06D}
+ \u{1E08F}
\u{1E100}-\u{1E12C}
\u{1E130}-\u{1E13D}
\u{1E140}-\u{1E149}
\u{1E14E}-\u{1E14F}
+ \u{1E290}-\u{1E2AE}
\u{1E2C0}-\u{1E2F9}
\u{1E2FF}
+ \u{1E4D0}-\u{1E4F9}
+ \u{1E7E0}-\u{1E7E6}
+ \u{1E7E8}-\u{1E7EB}
+ \u{1E7ED}-\u{1E7EE}
+ \u{1E7F0}-\u{1E7FE}
\u{1E800}-\u{1E8C4}
\u{1E8C7}-\u{1E8D6}
\u{1E900}-\u{1E94B}
@@ -1142,8 +1174,8 @@ class Reline::Unicode::EastAsianWidth
\u{1F6D3}-\u{1F6D4}
\u{1F6E0}-\u{1F6EA}
\u{1F6F0}-\u{1F6F3}
- \u{1F700}-\u{1F773}
- \u{1F780}-\u{1F7D8}
+ \u{1F700}-\u{1F776}
+ \u{1F77B}-\u{1F7D9}
\u{1F800}-\u{1F80B}
\u{1F810}-\u{1F847}
\u{1F850}-\u{1F859}
diff --git a/lib/reline/version.rb b/lib/reline/version.rb
index 1bb1c02f3d..d68c7d203b 100644
--- a/lib/reline/version.rb
+++ b/lib/reline/version.rb
@@ -1,3 +1,3 @@
module Reline
- VERSION = '0.3.1'
+ VERSION = '0.5.3'
end
diff --git a/lib/reline/windows.rb b/lib/reline/windows.rb
index b952329911..ee3f73e383 100644
--- a/lib/reline/windows.rb
+++ b/lib/reline/windows.rb
@@ -1,6 +1,8 @@
require 'fiddle/import'
class Reline::Windows
+ RESET_COLOR = "\e[0m"
+
def self.encoding
Encoding::UTF_8
end
@@ -85,7 +87,7 @@ class Reline::Windows
def call(*args)
import = @proto.split("")
args.each_with_index do |x, i|
- args[i], = [x == 0 ? nil : x].pack("p").unpack(POINTER_TYPE) if import[i] == "S"
+ args[i], = [x == 0 ? nil : +x].pack("p").unpack(POINTER_TYPE) if import[i] == "S"
args[i], = [x].pack("I").unpack("i") if import[i] == "I"
end
ret, = @func.call(*args)
@@ -257,7 +259,7 @@ class Reline::Windows
def self.check_input_event
num_of_events = 0.chr * 8
while @@output_buf.empty?
- Reline.core.line_editor.resize
+ Reline.core.line_editor.handle_signal
if @@WaitForSingleObject.(@@hConsoleInputHandle, 100) != 0 # max 0.1 sec
# prevent for background consolemode change
@@legacy_console = (getconsolemode() & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0)
@@ -291,7 +293,11 @@ class Reline::Windows
end
end
- def self.getc
+ def self.with_raw_input
+ yield
+ end
+
+ def self.getc(_timeout_second)
check_input_event
@@output_buf.shift
end