reline 0.1.5 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +50 -0
- data/lib/reline/ansi.rb +206 -58
- data/lib/reline/config.rb +71 -21
- data/lib/reline/general_io.rb +31 -3
- data/lib/reline/key_actor/base.rb +12 -0
- data/lib/reline/key_actor/emacs.rb +2 -2
- data/lib/reline/key_actor/vi_command.rb +2 -2
- data/lib/reline/key_stroke.rb +64 -14
- data/lib/reline/kill_ring.rb +12 -0
- data/lib/reline/line_editor.rb +1332 -291
- data/lib/reline/sibori.rb +170 -0
- data/lib/reline/terminfo.rb +171 -0
- data/lib/reline/unicode/east_asian_width.rb +1149 -1130
- data/lib/reline/unicode.rb +103 -33
- data/lib/reline/version.rb +1 -1
- data/lib/reline/windows.rb +324 -109
- data/lib/reline.rb +184 -39
- data/license_of_rb-readline +25 -0
- metadata +6 -45
@@ -0,0 +1,170 @@
|
|
1
|
+
require 'reline/unicode'
|
2
|
+
|
3
|
+
=begin
|
4
|
+
|
5
|
+
\ |
|
6
|
+
\ | <--- whipped cream
|
7
|
+
\ |
|
8
|
+
\ |
|
9
|
+
\-~~|
|
10
|
+
\ | <--- shibori kutigane (piping nozzle in Japanese)
|
11
|
+
\Ml
|
12
|
+
(\ __ __
|
13
|
+
( \--( ) )
|
14
|
+
(__(__)__) <--- compressed whipped cream
|
15
|
+
=end
|
16
|
+
|
17
|
+
class Sibori
|
18
|
+
attr_writer :output
|
19
|
+
|
20
|
+
def initialize(width, height, cursor_pos)
|
21
|
+
@width = width
|
22
|
+
@height = height
|
23
|
+
@cursor_pos = cursor_pos
|
24
|
+
@screen = [String.new]
|
25
|
+
@line_index = 0
|
26
|
+
@byte_pointer_in_line = 0
|
27
|
+
@cleared = false
|
28
|
+
clone_screen
|
29
|
+
end
|
30
|
+
|
31
|
+
def clone_screen
|
32
|
+
@prev_screen = @screen.map { |line|
|
33
|
+
line.dup
|
34
|
+
}
|
35
|
+
@prev_cursor_pos = @cursor_pos.dup
|
36
|
+
@prev_line_index = @line_index
|
37
|
+
end
|
38
|
+
|
39
|
+
def print(str)
|
40
|
+
#$stderr.puts "print #{str.inspect}"
|
41
|
+
line = @screen[@line_index]
|
42
|
+
before = line.byteslice(0, @byte_pointer_in_line)
|
43
|
+
str_width = Reline::Unicode.calculate_width(str, true)
|
44
|
+
after_cursor = line.byteslice(@byte_pointer_in_line..-1)
|
45
|
+
after_cursor_width = Reline::Unicode.calculate_width(after_cursor, true)
|
46
|
+
rest = ''
|
47
|
+
if after_cursor_width > str_width
|
48
|
+
rest_byte_pointer = @byte_pointer_in_line + width_to_bytesize(after_cursor, str_width)
|
49
|
+
rest = line.byteslice(rest_byte_pointer..-1)
|
50
|
+
end
|
51
|
+
@screen[@line_index] = before + str + rest
|
52
|
+
@byte_pointer_in_line += str.bytesize
|
53
|
+
@cursor_pos.x += Reline::Unicode.calculate_width(str, true)
|
54
|
+
end
|
55
|
+
|
56
|
+
def move_cursor_column(col)
|
57
|
+
#$stderr.puts "move_cursor_column(#{col})"
|
58
|
+
@byte_pointer_in_line = width_to_bytesize(@screen[@line_index], col)
|
59
|
+
@cursor_pos.x = col
|
60
|
+
end
|
61
|
+
|
62
|
+
def move_cursor_up(val)
|
63
|
+
#$stderr.puts "move_cursor_up(#{val})"
|
64
|
+
if @line_index.positive?
|
65
|
+
@line_index -= val
|
66
|
+
@byte_pointer_in_line = width_to_bytesize(@screen[@line_index], @cursor_pos.x)
|
67
|
+
@cursor_pos.y -= val
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def move_cursor_down(val)
|
72
|
+
#$stderr.puts "move_cursor_down(#{val})"
|
73
|
+
if @line_index < @height - 1
|
74
|
+
#$stderr.puts "@line_index #{@line_index} @screen.size #{@screen.size} @height #{@height}"
|
75
|
+
#$stderr.puts @screen.inspect
|
76
|
+
@line_index += val
|
77
|
+
@screen[@line_index] = String.new if @line_index == @screen.size
|
78
|
+
@byte_pointer_in_line = width_to_bytesize(@screen[@line_index], @cursor_pos.x)
|
79
|
+
@cursor_pos.y += val
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def scroll_down(val)
|
84
|
+
#$stderr.puts "scroll_down(#{val})"
|
85
|
+
if val >= @height
|
86
|
+
clear_screen
|
87
|
+
@line_index = @screen.size - 1
|
88
|
+
return
|
89
|
+
end
|
90
|
+
@screen.size.times do |n|
|
91
|
+
if n < @screen.size - val
|
92
|
+
#$stderr.puts "A @screen[#{val} + #{n}] (#{@screen[val + n].inspect}) to @screen[#{n}]"
|
93
|
+
@screen[n] = @screen[val + n]
|
94
|
+
else
|
95
|
+
#$stderr.puts "B String.new to @screen[#{n}]"
|
96
|
+
@screen[n] = String.new
|
97
|
+
end
|
98
|
+
end
|
99
|
+
@line_index += val
|
100
|
+
end
|
101
|
+
|
102
|
+
def erase_after_cursor
|
103
|
+
#$stderr.puts "erase_after_cursor"
|
104
|
+
@screen[@line_index] = @screen[@line_index].byteslice(0, @byte_pointer_in_line)
|
105
|
+
end
|
106
|
+
|
107
|
+
def clear_screen
|
108
|
+
#$stderr.puts "clear_screen"
|
109
|
+
@screen = [String.new]
|
110
|
+
@line_index = 0
|
111
|
+
@byte_pointer_in_line = 0
|
112
|
+
@cursor_pos.x = @cursor_pos.y = 0
|
113
|
+
@cleared = true
|
114
|
+
Reline::IOGate.clear_screen
|
115
|
+
end
|
116
|
+
|
117
|
+
private def width_to_bytesize(str, width)
|
118
|
+
lines, _ = Reline::Unicode.split_by_width(str, width)
|
119
|
+
lines.first.bytesize
|
120
|
+
end
|
121
|
+
|
122
|
+
def render
|
123
|
+
#$stderr.puts ?* * 100
|
124
|
+
Reline::IOGate.move_cursor_up(@prev_line_index) if @prev_line_index.positive?
|
125
|
+
#$stderr.puts "! move_cursor_up(#{@prev_line_index})" if @prev_line_index.positive?
|
126
|
+
#$stderr.puts "@prev_line_index #{@prev_line_index} @line_index #{@line_index}"
|
127
|
+
if @screen.size > @prev_screen.size
|
128
|
+
#$stderr.puts ?a * 100
|
129
|
+
down = @screen.size - @prev_screen.size
|
130
|
+
#$stderr.puts "#{@prev_cursor_pos.y} #{down} #{@height}"
|
131
|
+
if @prev_cursor_pos.y + down > (@height - 1)
|
132
|
+
#$stderr.puts ?b * 100
|
133
|
+
scroll = (@prev_cursor_pos.y + down) - (@height - 1)
|
134
|
+
Reline::IOGate.scroll_down(scroll)
|
135
|
+
#$stderr.puts "! scroll_down(#{scroll})"
|
136
|
+
#$stderr.puts "down #{down}"
|
137
|
+
Reline::IOGate.move_cursor_up(@screen.size - 1 - scroll)
|
138
|
+
#$stderr.puts "! move_cursor_up(#{@screen.size - 1})"
|
139
|
+
else
|
140
|
+
#$stderr.puts ?c * 100
|
141
|
+
end
|
142
|
+
end
|
143
|
+
@screen.size.times do |n|
|
144
|
+
Reline::IOGate.move_cursor_column(0)
|
145
|
+
#$stderr.puts "! move_cursor_column(0)"
|
146
|
+
@output.write @screen[n]
|
147
|
+
#$stderr.puts "! print #{@screen[n].inspect}"
|
148
|
+
Reline::IOGate.erase_after_cursor
|
149
|
+
#$stderr.puts "! erase_after_cursor"
|
150
|
+
Reline::IOGate.move_cursor_down(1) if n != (@screen.size - 1)
|
151
|
+
#$stderr.puts "! move_cursor_down(1)" if n != (@screen.size - 1)
|
152
|
+
end
|
153
|
+
up = @screen.size - 1 - @line_index
|
154
|
+
Reline::IOGate.move_cursor_up(up) if up.positive?
|
155
|
+
#$stderr.puts "! move_cursor_up(#{up})" if up.positive?
|
156
|
+
column = Reline::Unicode.calculate_width(@screen[@line_index].byteslice(0, @byte_pointer_in_line), true)
|
157
|
+
Reline::IOGate.move_cursor_column(column)
|
158
|
+
#$stderr.puts "! move_cursor_column(#{column}) #{@byte_pointer_in_line}"
|
159
|
+
clone_screen
|
160
|
+
#$stderr.puts ?- * 10
|
161
|
+
end
|
162
|
+
|
163
|
+
def prep
|
164
|
+
Reline::IOGate.prep
|
165
|
+
end
|
166
|
+
|
167
|
+
def deprep
|
168
|
+
Reline::IOGate.deprep
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
begin
|
2
|
+
require 'fiddle'
|
3
|
+
require 'fiddle/import'
|
4
|
+
rescue LoadError
|
5
|
+
module Reline::Terminfo
|
6
|
+
def self.curses_dl
|
7
|
+
false
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module Reline::Terminfo
|
13
|
+
extend Fiddle::Importer
|
14
|
+
|
15
|
+
class TerminfoError < StandardError; end
|
16
|
+
|
17
|
+
def self.curses_dl_files
|
18
|
+
case RUBY_PLATFORM
|
19
|
+
when /mingw/, /mswin/
|
20
|
+
# aren't supported
|
21
|
+
[]
|
22
|
+
when /cygwin/
|
23
|
+
%w[cygncursesw-10.dll cygncurses-10.dll]
|
24
|
+
when /darwin/
|
25
|
+
%w[libncursesw.dylib libcursesw.dylib libncurses.dylib libcurses.dylib]
|
26
|
+
else
|
27
|
+
%w[libncursesw.so libcursesw.so libncurses.so libcurses.so]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
@curses_dl = false
|
32
|
+
def self.curses_dl
|
33
|
+
return @curses_dl unless @curses_dl == false
|
34
|
+
if RUBY_VERSION >= '3.0.0'
|
35
|
+
# Gem module isn't defined in test-all of the Ruby repository, and
|
36
|
+
# Fiddle in Ruby 3.0.0 or later supports Fiddle::TYPE_VARIADIC.
|
37
|
+
fiddle_supports_variadic = true
|
38
|
+
elsif Fiddle.const_defined?(:VERSION) and Gem::Version.create(Fiddle::VERSION) >= Gem::Version.create('1.0.1')
|
39
|
+
# Fiddle::TYPE_VARIADIC is supported from Fiddle 1.0.1.
|
40
|
+
fiddle_supports_variadic = true
|
41
|
+
else
|
42
|
+
fiddle_supports_variadic = false
|
43
|
+
end
|
44
|
+
if fiddle_supports_variadic and not Fiddle.const_defined?(:TYPE_VARIADIC)
|
45
|
+
# If the libffi version is not 3.0.5 or higher, there isn't TYPE_VARIADIC.
|
46
|
+
fiddle_supports_variadic = false
|
47
|
+
end
|
48
|
+
if fiddle_supports_variadic
|
49
|
+
curses_dl_files.each do |curses_name|
|
50
|
+
result = Fiddle::Handle.new(curses_name)
|
51
|
+
rescue Fiddle::DLError
|
52
|
+
next
|
53
|
+
else
|
54
|
+
@curses_dl = result
|
55
|
+
break
|
56
|
+
end
|
57
|
+
end
|
58
|
+
@curses_dl = nil if @curses_dl == false
|
59
|
+
@curses_dl
|
60
|
+
end
|
61
|
+
end if not Reline.const_defined?(:Terminfo) or not Reline::Terminfo.respond_to?(:curses_dl)
|
62
|
+
|
63
|
+
module Reline::Terminfo
|
64
|
+
dlload curses_dl
|
65
|
+
#extern 'int setupterm(char *term, int fildes, int *errret)'
|
66
|
+
@setupterm = Fiddle::Function.new(curses_dl['setupterm'], [Fiddle::TYPE_VOIDP, Fiddle::TYPE_INT, Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT)
|
67
|
+
#extern 'char *tigetstr(char *capname)'
|
68
|
+
@tigetstr = Fiddle::Function.new(curses_dl['tigetstr'], [Fiddle::TYPE_VOIDP], Fiddle::TYPE_VOIDP)
|
69
|
+
begin
|
70
|
+
#extern 'char *tiparm(const char *str, ...)'
|
71
|
+
@tiparm = Fiddle::Function.new(curses_dl['tiparm'], [Fiddle::TYPE_VOIDP, Fiddle::TYPE_VARIADIC], Fiddle::TYPE_VOIDP)
|
72
|
+
rescue Fiddle::DLError
|
73
|
+
# OpenBSD lacks tiparm
|
74
|
+
#extern 'char *tparm(const char *str, ...)'
|
75
|
+
@tiparm = Fiddle::Function.new(curses_dl['tparm'], [Fiddle::TYPE_VOIDP, Fiddle::TYPE_VARIADIC], Fiddle::TYPE_VOIDP)
|
76
|
+
end
|
77
|
+
begin
|
78
|
+
#extern 'int tigetflag(char *str)'
|
79
|
+
@tigetflag = Fiddle::Function.new(curses_dl['tigetflag'], [Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT)
|
80
|
+
rescue Fiddle::DLError
|
81
|
+
# OpenBSD lacks tigetflag
|
82
|
+
#extern 'int tgetflag(char *str)'
|
83
|
+
@tigetflag = Fiddle::Function.new(curses_dl['tgetflag'], [Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT)
|
84
|
+
end
|
85
|
+
begin
|
86
|
+
#extern 'int tigetnum(char *str)'
|
87
|
+
@tigetnum = Fiddle::Function.new(curses_dl['tigetnum'], [Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT)
|
88
|
+
rescue Fiddle::DLError
|
89
|
+
# OpenBSD lacks tigetnum
|
90
|
+
#extern 'int tgetnum(char *str)'
|
91
|
+
@tigetnum = Fiddle::Function.new(curses_dl['tgetnum'], [Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT)
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.setupterm(term, fildes)
|
95
|
+
errret_int = String.new("\x00" * 8, encoding: 'ASCII-8BIT')
|
96
|
+
ret = @setupterm.(term, fildes, errret_int)
|
97
|
+
errret = errret_int.unpack1('i')
|
98
|
+
case ret
|
99
|
+
when 0 # OK
|
100
|
+
0
|
101
|
+
when -1 # ERR
|
102
|
+
case errret
|
103
|
+
when 1
|
104
|
+
raise TerminfoError.new('The terminal is hardcopy, cannot be used for curses applications.')
|
105
|
+
when 0
|
106
|
+
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.')
|
107
|
+
when -1
|
108
|
+
raise TerminfoError.new('The terminfo database could not be found.')
|
109
|
+
else # unknown
|
110
|
+
-1
|
111
|
+
end
|
112
|
+
else # unknown
|
113
|
+
-2
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
class StringWithTiparm < String
|
118
|
+
def tiparm(*args) # for method chain
|
119
|
+
Reline::Terminfo.tiparm(self, *args)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def self.tigetstr(capname)
|
124
|
+
capability = @tigetstr.(capname)
|
125
|
+
case capability.to_i
|
126
|
+
when 0, -1
|
127
|
+
raise TerminfoError, "can't find capability: #{capname}"
|
128
|
+
end
|
129
|
+
StringWithTiparm.new(capability.to_s)
|
130
|
+
end
|
131
|
+
|
132
|
+
def self.tiparm(str, *args)
|
133
|
+
new_args = []
|
134
|
+
args.each do |a|
|
135
|
+
new_args << Fiddle::TYPE_INT << a
|
136
|
+
end
|
137
|
+
@tiparm.(str, *new_args).to_s
|
138
|
+
end
|
139
|
+
|
140
|
+
def self.tigetflag(capname)
|
141
|
+
flag = @tigetflag.(capname).to_i
|
142
|
+
case flag
|
143
|
+
when -1
|
144
|
+
raise TerminfoError, "not boolean capability: #{capname}"
|
145
|
+
when 0
|
146
|
+
raise TerminfoError, "can't find capability: #{capname}"
|
147
|
+
end
|
148
|
+
flag
|
149
|
+
end
|
150
|
+
|
151
|
+
def self.tigetnum(capname)
|
152
|
+
num = @tigetnum.(capname).to_i
|
153
|
+
case num
|
154
|
+
when -2
|
155
|
+
raise TerminfoError, "not numeric capability: #{capname}"
|
156
|
+
when -1
|
157
|
+
raise TerminfoError, "can't find capability: #{capname}"
|
158
|
+
end
|
159
|
+
num
|
160
|
+
end
|
161
|
+
|
162
|
+
def self.enabled?
|
163
|
+
true
|
164
|
+
end
|
165
|
+
end if Reline::Terminfo.curses_dl
|
166
|
+
|
167
|
+
module Reline::Terminfo
|
168
|
+
def self.enabled?
|
169
|
+
false
|
170
|
+
end
|
171
|
+
end unless Reline::Terminfo.curses_dl
|