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