reline 0.2.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 +46 -0
- data/lib/reline/ansi.rb +153 -62
- data/lib/reline/config.rb +62 -14
- data/lib/reline/general_io.rb +14 -4
- data/lib/reline/key_actor/base.rb +12 -0
- data/lib/reline/key_actor/emacs.rb +1 -1
- data/lib/reline/key_stroke.rb +64 -14
- data/lib/reline/line_editor.rb +638 -74
- data/lib/reline/terminfo.rb +171 -0
- data/lib/reline/unicode.rb +42 -3
- data/lib/reline/version.rb +1 -1
- data/lib/reline/windows.rb +281 -112
- data/lib/reline.rb +150 -35
- metadata +4 -59
@@ -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
|
data/lib/reline/unicode.rb
CHANGED
@@ -79,6 +79,8 @@ class Reline::Unicode
|
|
79
79
|
|
80
80
|
require 'reline/unicode/east_asian_width'
|
81
81
|
|
82
|
+
HalfwidthDakutenHandakuten = /[\u{FF9E}\u{FF9F}]/
|
83
|
+
|
82
84
|
MBCharWidthRE = /
|
83
85
|
(?<width_2_1>
|
84
86
|
[#{ EscapedChars.map {|c| "\\x%02x" % c.ord }.join }] (?# ^ + char, such as ^M, ^H, ^[, ...)
|
@@ -93,6 +95,12 @@ class Reline::Unicode
|
|
93
95
|
#{ EastAsianWidth::TYPE_H }
|
94
96
|
| #{ EastAsianWidth::TYPE_NA }
|
95
97
|
| #{ EastAsianWidth::TYPE_N }
|
98
|
+
)(?!#{ HalfwidthDakutenHandakuten })
|
99
|
+
| (?<width_2_3>
|
100
|
+
(?: #{ EastAsianWidth::TYPE_H }
|
101
|
+
| #{ EastAsianWidth::TYPE_NA }
|
102
|
+
| #{ EastAsianWidth::TYPE_N })
|
103
|
+
#{ HalfwidthDakutenHandakuten }
|
96
104
|
)
|
97
105
|
| (?<ambiguous_width>
|
98
106
|
#{EastAsianWidth::TYPE_A}
|
@@ -101,15 +109,15 @@ class Reline::Unicode
|
|
101
109
|
|
102
110
|
def self.get_mbchar_width(mbchar)
|
103
111
|
ord = mbchar.ord
|
104
|
-
if (0x00 <= ord and ord <= 0x1F)
|
112
|
+
if (0x00 <= ord and ord <= 0x1F) # in EscapedPairs
|
105
113
|
return 2
|
106
|
-
elsif (0x20 <= ord and ord <= 0x7E)
|
114
|
+
elsif (0x20 <= ord and ord <= 0x7E) # printable ASCII chars
|
107
115
|
return 1
|
108
116
|
end
|
109
117
|
m = mbchar.encode(Encoding::UTF_8).match(MBCharWidthRE)
|
110
118
|
case
|
111
119
|
when m.nil? then 1 # TODO should be U+FFFD � REPLACEMENT CHARACTER
|
112
|
-
when m[:width_2_1], m[:width_2_2] then 2
|
120
|
+
when m[:width_2_1], m[:width_2_2], m[:width_2_3] then 2
|
113
121
|
when m[:width_3] then 3
|
114
122
|
when m[:width_0] then 0
|
115
123
|
when m[:width_1] then 1
|
@@ -185,6 +193,37 @@ class Reline::Unicode
|
|
185
193
|
[lines, height]
|
186
194
|
end
|
187
195
|
|
196
|
+
# Take a chunk of a String cut by width with escape sequences.
|
197
|
+
def self.take_range(str, start_col, max_width, encoding = str.encoding)
|
198
|
+
chunk = String.new(encoding: encoding)
|
199
|
+
total_width = 0
|
200
|
+
rest = str.encode(Encoding::UTF_8)
|
201
|
+
in_zero_width = false
|
202
|
+
rest.scan(WIDTH_SCANNER) do |gc|
|
203
|
+
case
|
204
|
+
when gc[NON_PRINTING_START_INDEX]
|
205
|
+
in_zero_width = true
|
206
|
+
when gc[NON_PRINTING_END_INDEX]
|
207
|
+
in_zero_width = false
|
208
|
+
when gc[CSI_REGEXP_INDEX]
|
209
|
+
chunk << gc[CSI_REGEXP_INDEX]
|
210
|
+
when gc[OSC_REGEXP_INDEX]
|
211
|
+
chunk << gc[OSC_REGEXP_INDEX]
|
212
|
+
when gc[GRAPHEME_CLUSTER_INDEX]
|
213
|
+
gc = gc[GRAPHEME_CLUSTER_INDEX]
|
214
|
+
if in_zero_width
|
215
|
+
chunk << gc
|
216
|
+
else
|
217
|
+
mbchar_width = get_mbchar_width(gc)
|
218
|
+
total_width += mbchar_width
|
219
|
+
break if (start_col + max_width) < total_width
|
220
|
+
chunk << gc if start_col < total_width
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
chunk
|
225
|
+
end
|
226
|
+
|
188
227
|
def self.get_next_mbchar_size(line, byte_pointer)
|
189
228
|
grapheme = line.byteslice(byte_pointer..-1).grapheme_clusters.first
|
190
229
|
grapheme ? grapheme.bytesize : 0
|
data/lib/reline/version.rb
CHANGED