katakata_irb 0.1.0
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 +7 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +23 -0
- data/LICENSE.txt +21 -0
- data/README.md +51 -0
- data/Rakefile +12 -0
- data/bin/console +10 -0
- data/bin/setup +8 -0
- data/exe/kirb +10 -0
- data/katakata_irb.gemspec +36 -0
- data/lib/katakata_irb/completor.rb +187 -0
- data/lib/katakata_irb/reline_patch.rb +40 -0
- data/lib/katakata_irb/reline_patches/escapeseq.patch +45 -0
- data/lib/katakata_irb/reline_patches/fullwidth.patch +200 -0
- data/lib/katakata_irb/reline_patches/indent.patch +25 -0
- data/lib/katakata_irb/reline_patches/raw.patch +95 -0
- data/lib/katakata_irb/reline_patches/scrollbar.patch +43 -0
- data/lib/katakata_irb/reline_patches/wholelines.patch +102 -0
- data/lib/katakata_irb/ruby_lex_patch.rb +197 -0
- data/lib/katakata_irb/trex.rb +207 -0
- data/lib/katakata_irb/type_simulator.rb +1108 -0
- data/lib/katakata_irb/types.rb +341 -0
- data/lib/katakata_irb/version.rb +5 -0
- data/lib/katakata_irb.rb +15 -0
- data/sig/katakata_irb.rbs +4 -0
- metadata +84 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 07b0eb3288cc4eec15c83de9bd27948379bca2c1dfa233104cba59dff6826c7c
|
4
|
+
data.tar.gz: 20d4feb90c4df7e10554e045addfb242af50205dd0b187635a81ac3bc5b073b2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7a376d7deb729f3a2e2f3b18041e89606d668e331ece73c32d5b7ae9ff6b8d87bffa89f4ab3d1dc66c8354b3af2ad733bee211868e01bd8814055f3cc72fe83b
|
7
|
+
data.tar.gz: 50eaa7061da8609623f1fccc5d0fbe12685f888d7a565eede06e19993b435aa766ce6458668fa787369bef62ce5601b6dd9c4118fa534457c89341881367722d
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
katakata_irb (0.1.0)
|
5
|
+
rbs
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
minitest (5.16.3)
|
11
|
+
rake (13.0.6)
|
12
|
+
rbs (2.7.0)
|
13
|
+
|
14
|
+
PLATFORMS
|
15
|
+
x86_64-darwin-20
|
16
|
+
|
17
|
+
DEPENDENCIES
|
18
|
+
katakata_irb!
|
19
|
+
minitest (~> 5.0)
|
20
|
+
rake (~> 13.0)
|
21
|
+
|
22
|
+
BUNDLED WITH
|
23
|
+
2.4.0.dev
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2022 tompng
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# KatakataIrb: IRB with Kata(型 Type) completion
|
2
|
+
|
3
|
+
KatakataIrb might provide a better autocompletion based on type analysis to irb.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
```
|
8
|
+
gem install katakata_irb
|
9
|
+
```
|
10
|
+
## Usage
|
11
|
+
|
12
|
+
```
|
13
|
+
% kirb
|
14
|
+
irb(main):001:0> [1,'a'].sample.a█
|
15
|
+
|[1,'a'].sample.abs |
|
16
|
+
|[1,'a'].sample.abs2 |
|
17
|
+
|[1,'a'].sample.allbits? |
|
18
|
+
|[1,'a'].sample.angle |
|
19
|
+
|[1,'a'].sample.anybits? |
|
20
|
+
|[1,'a'].sample.arg |
|
21
|
+
|[1,'a'].sample.ascii_only?|
|
22
|
+
```
|
23
|
+
|
24
|
+
```
|
25
|
+
% kirb
|
26
|
+
irb(main):001:0> a = 10
|
27
|
+
=> 10
|
28
|
+
irb(main):002:1* if true
|
29
|
+
irb(main):003:2* b = a.times.map do
|
30
|
+
irb(main):004:2* _1.to_s
|
31
|
+
irb(main):005:1* end
|
32
|
+
irb(main):006:1* b[0].a█
|
33
|
+
|b[0].ascii_only?|
|
34
|
+
```
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
require 'katakata_irb/completor'
|
38
|
+
KatakataIrb::Completor.setup
|
39
|
+
10.times do |i|
|
40
|
+
binding.irb
|
41
|
+
end
|
42
|
+
```
|
43
|
+
|
44
|
+
## Options
|
45
|
+
|
46
|
+
### `kirb --debug-output`
|
47
|
+
Show debug output if it meets unimplemented syntax or something
|
48
|
+
|
49
|
+
### `kirb --without-patch`
|
50
|
+
`kirb` will apply some patches to reline and irb/ruby-lex.rb by default. This option will disable it.
|
51
|
+
See `lib/katakata_irb/ruby_lex_patch.rb` and `lib/katakata_irb/reline_patches/*.patch`
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require_relative '../lib/katakata_irb'
|
3
|
+
require_relative '../lib/katakata_irb/reline_patch'
|
4
|
+
KatakataIrb::RelinePatch.require_patched_reline
|
5
|
+
KatakataIrb.log_output = STDERR
|
6
|
+
require 'bundler/setup'
|
7
|
+
require 'katakata_irb'
|
8
|
+
require 'katakata_irb/ruby_lex_patch'
|
9
|
+
KatakataIrb::RubyLexPatch.patch_to_ruby_lex
|
10
|
+
KatakataIrb.repl
|
data/bin/setup
ADDED
data/exe/kirb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
unless ARGV.delete '--without-patch'
|
3
|
+
require_relative '../lib/katakata_irb/reline_patch'
|
4
|
+
KatakataIrb::RelinePatch.require_patched_reline
|
5
|
+
require 'katakata_irb/ruby_lex_patch'
|
6
|
+
KatakataIrb::RubyLexPatch.patch_to_ruby_lex
|
7
|
+
end
|
8
|
+
require 'katakata_irb'
|
9
|
+
KatakataIrb.log_output = STDERR if ARGV.delete '--debug-output'
|
10
|
+
KatakataIrb.repl
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/katakata_irb/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "katakata_irb"
|
7
|
+
spec.version = KatakataIrb::VERSION
|
8
|
+
spec.authors = ["tompng"]
|
9
|
+
spec.email = ["tomoyapenguin@gmail.com"]
|
10
|
+
|
11
|
+
spec.summary = "IRB with Typed Completion"
|
12
|
+
spec.description = "IRB with Typed Completion"
|
13
|
+
spec.homepage = "http://github.com/tompng/katakata_irb"
|
14
|
+
spec.license = "MIT"
|
15
|
+
spec.required_ruby_version = ">= 3.1.0"
|
16
|
+
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
18
|
+
spec.metadata["source_code_uri"] = "http://github.com/tompng/katakata_irb"
|
19
|
+
|
20
|
+
# Specify which files should be added to the gem when it is released.
|
21
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
22
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
23
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
24
|
+
(f == __FILE__) || f.match(%r{\A(?:(?:test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
25
|
+
end
|
26
|
+
end
|
27
|
+
spec.bindir = "exe"
|
28
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
29
|
+
spec.require_paths = ["lib"]
|
30
|
+
|
31
|
+
# Uncomment to register a new dependency of your gem
|
32
|
+
spec.add_dependency 'rbs'
|
33
|
+
|
34
|
+
# For more information and examples about making a new gem, check out our
|
35
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
36
|
+
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
require_relative 'trex'
|
2
|
+
require_relative 'type_simulator'
|
3
|
+
require 'rbs'
|
4
|
+
require 'rbs/cli'
|
5
|
+
require 'irb'
|
6
|
+
|
7
|
+
module KatakataIrb::Completor
|
8
|
+
using KatakataIrb::TypeSimulator::LexerElemMatcher
|
9
|
+
|
10
|
+
HIDDEN_METHODS = %w[Namespace TypeName] # defined by rbs, should be hidden
|
11
|
+
|
12
|
+
def self.setup
|
13
|
+
completion_proc = ->(target, preposing = nil, postposing = nil) do
|
14
|
+
code = "#{preposing}#{target}"
|
15
|
+
irb_context = IRB.conf[:MAIN_CONTEXT]
|
16
|
+
binding = irb_context.workspace.binding
|
17
|
+
candidates = case analyze code, binding
|
18
|
+
in [:require | :require_relative => method, name]
|
19
|
+
if method == :require
|
20
|
+
IRB::InputCompletor.retrieve_files_to_require_from_load_path
|
21
|
+
else
|
22
|
+
IRB::InputCompletor.retrieve_files_to_require_relative_from_current_dir
|
23
|
+
end
|
24
|
+
in [:call_or_const, type, name, self_call]
|
25
|
+
((self_call ? type.all_methods: type.methods).map(&:to_s) - HIDDEN_METHODS) | type.constants
|
26
|
+
in [:const, type, name]
|
27
|
+
type.constants
|
28
|
+
in [:ivar, name, _scope]
|
29
|
+
# TODO: scope
|
30
|
+
ivars = binding.eval('self').instance_variables rescue []
|
31
|
+
cvars = (binding.eval('self').class_variables rescue nil) if name == '@'
|
32
|
+
ivars | (cvars || [])
|
33
|
+
in [:cvar, name, _scope]
|
34
|
+
# TODO: scope
|
35
|
+
binding.eval('self').class_variables rescue []
|
36
|
+
in [:gvar, name]
|
37
|
+
global_variables
|
38
|
+
in [:symbol, name]
|
39
|
+
Symbol.all_symbols
|
40
|
+
in [:call, type, name, self_call]
|
41
|
+
(self_call ? type.all_methods : type.methods).map(&:to_s) - HIDDEN_METHODS
|
42
|
+
in [:lvar_or_method, name, scope]
|
43
|
+
scope.self_type.all_methods.map(&:to_s) | scope.local_variables
|
44
|
+
else
|
45
|
+
[]
|
46
|
+
end
|
47
|
+
all_symbols_pattern = /\A[ -\/:-@\[-`\{-~]*\z/
|
48
|
+
candidates.map(&:to_s).select { !_1.match?(all_symbols_pattern) && _1.start_with?(name) }.uniq.sort.map do
|
49
|
+
target + _1[name.size..]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
IRB::InputCompletor::CompletionProc.define_singleton_method :call do |*args|
|
53
|
+
completion_proc.call(*args)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.analyze(code, binding = Kernel.binding)
|
58
|
+
lvars_code = binding.local_variables.map do |name|
|
59
|
+
"#{name}="
|
60
|
+
end.join + "nil;\n"
|
61
|
+
code = lvars_code + code
|
62
|
+
tokens = RubyLex.ripper_lex_without_warning code
|
63
|
+
tokens = KatakataIrb::TRex.interpolate_ripper_ignored_tokens code, tokens
|
64
|
+
last_opens, unclosed_heredocs = KatakataIrb::TRex.parse(tokens)
|
65
|
+
closings = (last_opens + unclosed_heredocs).map do |t,|
|
66
|
+
case t.tok
|
67
|
+
when /\A%.[<>]\z/
|
68
|
+
'>'
|
69
|
+
when '{', '#{', /\A%.?[{}]\z/
|
70
|
+
'}'
|
71
|
+
when '(', /\A%.?[()]\z/
|
72
|
+
')'
|
73
|
+
when '[', /\A%.?[\[\]]\z/
|
74
|
+
']'
|
75
|
+
when /\A%.?(.)\z/
|
76
|
+
$1
|
77
|
+
when '"', "'", '/', '`'
|
78
|
+
t.tok
|
79
|
+
when /\A<<(?:"(?<s>.+)"|'(?<s>.+)'|(?<s>.+))/
|
80
|
+
$3
|
81
|
+
else
|
82
|
+
'end'
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
return if code =~ /[!?]\z/
|
87
|
+
case tokens.last
|
88
|
+
in { event: :on_ignored_by_ripper, tok: '.' }
|
89
|
+
suffix = 'method'
|
90
|
+
name = ''
|
91
|
+
in { dot: true }
|
92
|
+
suffix = 'method'
|
93
|
+
name = ''
|
94
|
+
in { event: :on_ident | :on_kw, tok: }
|
95
|
+
return unless code.delete_suffix! tok
|
96
|
+
suffix = 'method'
|
97
|
+
name = tok
|
98
|
+
in { event: :on_const, tok: }
|
99
|
+
return unless code.delete_suffix! tok
|
100
|
+
suffix = 'Const'
|
101
|
+
name = tok
|
102
|
+
in { event: :on_tstring_content, tok: }
|
103
|
+
return unless code.delete_suffix! tok
|
104
|
+
suffix = 'string'
|
105
|
+
name = tok.rstrip
|
106
|
+
else
|
107
|
+
return
|
108
|
+
end
|
109
|
+
|
110
|
+
closings = $/ + closings.reverse.join($/)
|
111
|
+
sexp = Ripper.sexp code + suffix + closings
|
112
|
+
lines = code.lines
|
113
|
+
line_no = lines.size
|
114
|
+
col = lines.last.bytesize
|
115
|
+
if lines.last.end_with? "\n"
|
116
|
+
line_no += 1
|
117
|
+
col = 0
|
118
|
+
end
|
119
|
+
|
120
|
+
if sexp in [:program, [lvars_exp, *rest_statements]]
|
121
|
+
sexp = [:program, rest_statements]
|
122
|
+
end
|
123
|
+
|
124
|
+
*parents, expression, target = find_target sexp, line_no, col
|
125
|
+
in_class_module = parents&.any? { _1 in [:class | :module,] }
|
126
|
+
icvar_available = !in_class_module
|
127
|
+
return unless target in [type, String, [Integer, Integer]]
|
128
|
+
if target in [:@ivar,]
|
129
|
+
return [:ivar, name] if icvar_available
|
130
|
+
elsif target in [:@cvar,]
|
131
|
+
return [:cvar, name] if icvar_available
|
132
|
+
end
|
133
|
+
return unless expression
|
134
|
+
if (target in [:@tstring_content,]) && (parents[-4] in [:command, [:@ident, 'require' | 'require_relative' => require_method,],])
|
135
|
+
return [require_method.to_sym, name.rstrip]
|
136
|
+
end
|
137
|
+
calculate_scope = -> { KatakataIrb::TypeSimulator.calculate_binding_scope binding, parents, expression }
|
138
|
+
calculate_receiver = -> receiver { KatakataIrb::TypeSimulator.calculate_receiver binding, parents, receiver }
|
139
|
+
case expression
|
140
|
+
in [:vcall | :var_ref, [:@ident,]]
|
141
|
+
[:lvar_or_method, name, calculate_scope.call]
|
142
|
+
in [:symbol, [:@ident | :@const | :@op | :@kw,]]
|
143
|
+
[:symbol, name]
|
144
|
+
in [:var_ref | :const_ref, [:@const,]]
|
145
|
+
# TODO
|
146
|
+
[:const, KatakataIrb::Types::SingletonType.new(Object), name]
|
147
|
+
in [:var_ref, [:@gvar,]]
|
148
|
+
[:gvar, name]
|
149
|
+
in [:var_ref, [:@ivar,]]
|
150
|
+
[:ivar, name, calculate_scope.call.self_type] if icvar_available
|
151
|
+
in [:var_ref, [:@cvar,]]
|
152
|
+
[:cvar, name, calculate_scope.call.self_type] if icvar_available
|
153
|
+
in [:call, receiver, [:@period,] | [:@op, '&.',] | :'::' => dot, [:@ident | :@const,]]
|
154
|
+
self_call = (receiver in [:var_ref, [:@kw, 'self',]])
|
155
|
+
[dot == :'::' ? :call_or_const : :call, calculate_receiver.call(receiver), name, self_call]
|
156
|
+
in [:const_path_ref, receiver, [:@const,]]
|
157
|
+
[:const, calculate_receiver.call(receiver), name]
|
158
|
+
in [:top_const_ref, [:@const,]]
|
159
|
+
[:const, KatakataIrb::Types::SingletonType.new(Object), name]
|
160
|
+
in [:def,] | [:string_content,] | [:var_field,] | [:defs,] | [:rest_param,] | [:kwrest_param,] | [:blockarg,] | [[:@ident,],]
|
161
|
+
in [Array,] # `xstring`, /regexp/
|
162
|
+
else
|
163
|
+
KatakataIrb.log_puts
|
164
|
+
KatakataIrb.log_puts [:NEW_EXPRESSION, expression].inspect
|
165
|
+
KatakataIrb.log_puts
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def self.find_target(sexp, line, col, stack = [sexp])
|
170
|
+
return unless sexp.is_a? Array
|
171
|
+
sexp.each do |child|
|
172
|
+
case child
|
173
|
+
in [Symbol, String, [Integer => l, Integer => c]]
|
174
|
+
if l == line && c == col
|
175
|
+
stack << child
|
176
|
+
return stack
|
177
|
+
end
|
178
|
+
else
|
179
|
+
stack << child
|
180
|
+
result = find_target(child, line, col, stack)
|
181
|
+
return result if result
|
182
|
+
stack.pop
|
183
|
+
end
|
184
|
+
end
|
185
|
+
nil
|
186
|
+
end
|
187
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module KatakataIrb; end
|
2
|
+
module KatakataIrb::RelinePatch
|
3
|
+
module RelinePatchIseqLoader; end
|
4
|
+
def self.require_patched_reline
|
5
|
+
# Apply patches of unmerged pull-request to reline
|
6
|
+
patches = %w[wholelines escapeseq indent fullwidth raw scrollbar]
|
7
|
+
patched = {}
|
8
|
+
require 'reline/version.rb' # result of $LOAD_PATH.resolve_feature_path will change after this require
|
9
|
+
patches.each do |patch_name|
|
10
|
+
patch = File.read File.expand_path("reline_patches/#{patch_name}.patch", File.dirname(__FILE__))
|
11
|
+
current_patched = {}
|
12
|
+
patch.gsub(/^diff.+\nindex.+$/, '').split(/^--- a(.+)\n\+\+\+ b(.+)\n/).drop(1).each_slice(3) do |file, newfile, diff|
|
13
|
+
raise if file != newfile
|
14
|
+
_, path = $LOAD_PATH.resolve_feature_path file.sub(%r{^/lib/}, '')
|
15
|
+
code = current_patched[path] || patched[path] || File.read(path)
|
16
|
+
diff.split(/^@@.+\n/).drop(1).map(&:lines).each do |lines|
|
17
|
+
target = lines.reject { _1[0] == '+' }.map { _1[1..] }.join
|
18
|
+
replace = lines.reject { _1[0] == '-' }.map { _1[1..] }.join
|
19
|
+
raise unless code.include? target
|
20
|
+
code.sub! target, replace
|
21
|
+
end
|
22
|
+
current_patched[path] = code
|
23
|
+
end
|
24
|
+
patched.update current_patched
|
25
|
+
rescue
|
26
|
+
puts "Failed to apply katakata_irb/reline_patches/#{patch_name}.patch to reline"
|
27
|
+
end
|
28
|
+
|
29
|
+
RelinePatchIseqLoader.define_method :load_iseq do |fname|
|
30
|
+
if patched.key? fname
|
31
|
+
RubyVM::InstructionSequence.compile patched[fname], fname
|
32
|
+
else
|
33
|
+
RubyVM::InstructionSequence.compile_file fname
|
34
|
+
end
|
35
|
+
end
|
36
|
+
RubyVM::InstructionSequence.singleton_class.prepend RelinePatchIseqLoader
|
37
|
+
require 'reline'
|
38
|
+
RelinePatchIseqLoader.undef_method :load_iseq
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb
|
2
|
+
index 1c33a4b..bbf5e6c 100644
|
3
|
+
--- a/lib/reline/line_editor.rb
|
4
|
+
+++ b/lib/reline/line_editor.rb
|
5
|
+
@@ -663,8 +663,10 @@ class Reline::LineEditor
|
6
|
+
dialog.set_cursor_pos(cursor_column, @first_line_started_from + @started_from)
|
7
|
+
dialog_render_info = dialog.call(@last_key)
|
8
|
+
if dialog_render_info.nil? or dialog_render_info.contents.nil? or dialog_render_info.contents.empty?
|
9
|
+
+ lines = whole_lines
|
10
|
+
dialog.lines_backup = {
|
11
|
+
- lines: modify_lines(whole_lines),
|
12
|
+
+ unmodified_lines: lines,
|
13
|
+
+ lines: modify_lines(lines),
|
14
|
+
line_index: @line_index,
|
15
|
+
first_line_started_from: @first_line_started_from,
|
16
|
+
started_from: @started_from,
|
17
|
+
@@ -766,8 +768,10 @@ class Reline::LineEditor
|
18
|
+
Reline::IOGate.move_cursor_column(cursor_column)
|
19
|
+
move_cursor_up(dialog.vertical_offset + dialog.contents.size - 1)
|
20
|
+
Reline::IOGate.show_cursor
|
21
|
+
+ lines = whole_lines
|
22
|
+
dialog.lines_backup = {
|
23
|
+
- lines: modify_lines(whole_lines),
|
24
|
+
+ unmodified_lines: lines,
|
25
|
+
+ lines: modify_lines(lines),
|
26
|
+
line_index: @line_index,
|
27
|
+
first_line_started_from: @first_line_started_from,
|
28
|
+
started_from: @started_from,
|
29
|
+
@@ -777,7 +781,7 @@ class Reline::LineEditor
|
30
|
+
private def reset_dialog(dialog, old_dialog)
|
31
|
+
return if dialog.lines_backup.nil? or old_dialog.contents.nil?
|
32
|
+
- prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines])
|
33
|
+
+ prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:unmodified_lines])
|
34
|
+
visual_lines = []
|
35
|
+
visual_start = nil
|
36
|
+
dialog.lines_backup[:lines].each_with_index { |l, i|
|
37
|
+
@@ -888,7 +892,7 @@ class Reline::LineEditor
|
38
|
+
private def clear_each_dialog(dialog)
|
39
|
+
dialog.trap_key = nil
|
40
|
+
return unless dialog.contents
|
41
|
+
- prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines])
|
42
|
+
+ prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:unmodified_lines])
|
43
|
+
visual_lines = []
|
44
|
+
visual_lines_under_dialog = []
|
45
|
+
visual_start = nil
|
@@ -0,0 +1,200 @@
|
|
1
|
+
diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb
|
2
|
+
index c9e613e..6acf969 100644
|
3
|
+
--- a/lib/reline/line_editor.rb
|
4
|
+
+++ b/lib/reline/line_editor.rb
|
5
|
+
@@ -748,3 +741,3 @@ class Reline::LineEditor
|
6
|
+
end
|
7
|
+
str_width = dialog.width - (dialog.scrollbar_pos.nil? ? 0 : @block_elem_width)
|
8
|
+
- str = padding_space_with_escape_sequences(Reline::Unicode.take_range(item, 0, str_width), str_width)
|
9
|
+
+ str, = Reline::Unicode.take_range(item, 0, str_width, padding: true)
|
10
|
+
@@ -793,85 +786,42 @@ class Reline::LineEditor
|
11
|
+
end
|
12
|
+
visual_lines.concat(vl)
|
13
|
+
}
|
14
|
+
- old_y = dialog.lines_backup[:first_line_started_from] + dialog.lines_backup[:started_from]
|
15
|
+
- y = @first_line_started_from + @started_from
|
16
|
+
- y_diff = y - old_y
|
17
|
+
- if (old_y + old_dialog.vertical_offset) < (y + dialog.vertical_offset)
|
18
|
+
- # rerender top
|
19
|
+
- move_cursor_down(old_dialog.vertical_offset - y_diff)
|
20
|
+
- start = visual_start + old_dialog.vertical_offset
|
21
|
+
- line_num = dialog.vertical_offset - old_dialog.vertical_offset
|
22
|
+
- line_num.times do |i|
|
23
|
+
- Reline::IOGate.move_cursor_column(old_dialog.column)
|
24
|
+
- if visual_lines[start + i].nil?
|
25
|
+
- s = ' ' * old_dialog.width
|
26
|
+
- else
|
27
|
+
- s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, old_dialog.width)
|
28
|
+
- s = padding_space_with_escape_sequences(s, old_dialog.width)
|
29
|
+
- end
|
30
|
+
- @output.write "\e[0m#{s}\e[0m"
|
31
|
+
- move_cursor_down(1) if i < (line_num - 1)
|
32
|
+
- end
|
33
|
+
- move_cursor_up(old_dialog.vertical_offset + line_num - 1 - y_diff)
|
34
|
+
- end
|
35
|
+
- if (old_y + old_dialog.vertical_offset + old_dialog.contents.size) > (y + dialog.vertical_offset + dialog.contents.size)
|
36
|
+
- # rerender bottom
|
37
|
+
- move_cursor_down(dialog.vertical_offset + dialog.contents.size - y_diff)
|
38
|
+
- start = visual_start + dialog.vertical_offset + dialog.contents.size
|
39
|
+
- line_num = (old_dialog.vertical_offset + old_dialog.contents.size) - (dialog.vertical_offset + dialog.contents.size)
|
40
|
+
- line_num.times do |i|
|
41
|
+
- Reline::IOGate.move_cursor_column(old_dialog.column)
|
42
|
+
- if visual_lines[start + i].nil?
|
43
|
+
- s = ' ' * old_dialog.width
|
44
|
+
- else
|
45
|
+
- s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, old_dialog.width)
|
46
|
+
- s = padding_space_with_escape_sequences(s, old_dialog.width)
|
47
|
+
+ old_dialog_y = dialog.lines_backup[:first_line_started_from] + dialog.lines_backup[:started_from]
|
48
|
+
+ dialog_y = @first_line_started_from + @started_from
|
49
|
+
+
|
50
|
+
+ x_range = dialog.column...dialog.column + dialog.width
|
51
|
+
+ old_x_range = old_dialog.column...old_dialog.column + old_dialog.width
|
52
|
+
+ y_range = dialog_y + dialog.vertical_offset...dialog_y + dialog.vertical_offset + dialog.contents.size
|
53
|
+
+ old_y_range = old_dialog_y + old_dialog.vertical_offset...old_dialog_y + old_dialog.vertical_offset + old_dialog.contents.size
|
54
|
+
+
|
55
|
+
+ cursor_y = dialog_y
|
56
|
+
+ old_y_range.each do |y|
|
57
|
+
+ rerender_ranges = []
|
58
|
+
+ if y_range.cover?(y) && x_range.any?(old_x_range)
|
59
|
+
+ if old_x_range.begin < x_range.begin
|
60
|
+
+ # rerender left
|
61
|
+
+ rerender_ranges << [old_x_range.begin...[old_x_range.end, x_range.begin].min, true, false]
|
62
|
+
end
|
63
|
+
- @output.write "\e[0m#{s}\e[0m"
|
64
|
+
- move_cursor_down(1) if i < (line_num - 1)
|
65
|
+
- end
|
66
|
+
- move_cursor_up(dialog.vertical_offset + dialog.contents.size + line_num - 1 - y_diff)
|
67
|
+
- end
|
68
|
+
- if old_dialog.column < dialog.column
|
69
|
+
- # rerender left
|
70
|
+
- move_cursor_down(old_dialog.vertical_offset - y_diff)
|
71
|
+
- width = dialog.column - old_dialog.column
|
72
|
+
- start = visual_start + old_dialog.vertical_offset
|
73
|
+
- line_num = old_dialog.contents.size
|
74
|
+
- line_num.times do |i|
|
75
|
+
- Reline::IOGate.move_cursor_column(old_dialog.column)
|
76
|
+
- if visual_lines[start + i].nil?
|
77
|
+
- s = ' ' * width
|
78
|
+
- else
|
79
|
+
- s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, width)
|
80
|
+
- s = padding_space_with_escape_sequences(s, dialog.width)
|
81
|
+
+ if x_range.end < old_x_range.end
|
82
|
+
+ # rerender right
|
83
|
+
+ rerender_ranges << [[x_range.end, old_x_range.begin].max...old_x_range.end, false, true]
|
84
|
+
end
|
85
|
+
+ else
|
86
|
+
+ rerender_ranges << [old_x_range, true, true]
|
87
|
+
+ end
|
88
|
+
+
|
89
|
+
+ rerender_ranges.each do |range, cover_begin, cover_end|
|
90
|
+
+ move_cursor_down(y - cursor_y)
|
91
|
+
+ cursor_y = y
|
92
|
+
+ col = range.begin
|
93
|
+
+ width = range.end - range.begin
|
94
|
+
+ line = visual_lines[y + visual_start - old_dialog_y] || ''
|
95
|
+
+ s, col = Reline::Unicode.take_range(line, col, width, cover_begin: cover_begin, cover_end: cover_end, padding: true)
|
96
|
+
+ Reline::IOGate.move_cursor_column(col)
|
97
|
+
@output.write "\e[0m#{s}\e[0m"
|
98
|
+
- move_cursor_down(1) if i < (line_num - 1)
|
99
|
+
- end
|
100
|
+
- move_cursor_up(old_dialog.vertical_offset + line_num - 1 - y_diff)
|
101
|
+
- end
|
102
|
+
- if (old_dialog.column + old_dialog.width) > (dialog.column + dialog.width)
|
103
|
+
- # rerender right
|
104
|
+
- move_cursor_down(old_dialog.vertical_offset + y_diff)
|
105
|
+
- width = (old_dialog.column + old_dialog.width) - (dialog.column + dialog.width)
|
106
|
+
- start = visual_start + old_dialog.vertical_offset
|
107
|
+
- line_num = old_dialog.contents.size
|
108
|
+
- line_num.times do |i|
|
109
|
+
- Reline::IOGate.move_cursor_column(old_dialog.column + dialog.width)
|
110
|
+
- if visual_lines[start + i].nil?
|
111
|
+
- s = ' ' * width
|
112
|
+
- else
|
113
|
+
- s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column + dialog.width, width)
|
114
|
+
- rerender_width = old_dialog.width - dialog.width
|
115
|
+
- s = padding_space_with_escape_sequences(s, rerender_width)
|
116
|
+
- end
|
117
|
+
- Reline::IOGate.move_cursor_column(dialog.column + dialog.width)
|
118
|
+
- @output.write "\e[0m#{s}\e[0m"
|
119
|
+
- move_cursor_down(1) if i < (line_num - 1)
|
120
|
+
end
|
121
|
+
- move_cursor_up(old_dialog.vertical_offset + line_num - 1 + y_diff)
|
122
|
+
end
|
123
|
+
+ move_cursor_up(cursor_y - dialog_y)
|
124
|
+
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
125
|
+
end
|
126
|
+
|
127
|
+
@@ -912,9 +862,8 @@ class Reline::LineEditor
|
128
|
+
dialog_vertical_size = dialog.contents.size
|
129
|
+
dialog_vertical_size.times do |i|
|
130
|
+
if i < visual_lines_under_dialog.size
|
131
|
+
- Reline::IOGate.move_cursor_column(dialog.column)
|
132
|
+
- str = Reline::Unicode.take_range(visual_lines_under_dialog[i], dialog.column, dialog.width)
|
133
|
+
- str = padding_space_with_escape_sequences(str, dialog.width)
|
134
|
+
+ str, start_pos = Reline::Unicode.take_range(visual_lines_under_dialog[i], dialog.column, dialog.width, cover_begin: true, cover_end: true, padding: true)
|
135
|
+
+ Reline::IOGate.move_cursor_column(start_pos)
|
136
|
+
@output.write "\e[0m#{str}\e[0m"
|
137
|
+
else
|
138
|
+
Reline::IOGate.move_cursor_column(dialog.column)
|
139
|
+
diff --git a/lib/reline/unicode.rb b/lib/reline/unicode.rb
|
140
|
+
index 6000c9f..03074dd 100644
|
141
|
+
--- a/lib/reline/unicode.rb
|
142
|
+
+++ b/lib/reline/unicode.rb
|
143
|
+
@@ -194,17 +194,21 @@ class Reline::Unicode
|
144
|
+
end
|
145
|
+
|
146
|
+
# Take a chunk of a String cut by width with escape sequences.
|
147
|
+
- def self.take_range(str, start_col, max_width, encoding = str.encoding)
|
148
|
+
+ def self.take_range(str, start_col, width, encoding: str.encoding, cover_begin: false, cover_end: false, padding: false)
|
149
|
+
chunk = String.new(encoding: encoding)
|
150
|
+
total_width = 0
|
151
|
+
rest = str.encode(Encoding::UTF_8)
|
152
|
+
in_zero_width = false
|
153
|
+
+ chunk_start_col = nil
|
154
|
+
+ chunk_end_col = nil
|
155
|
+
rest.scan(WIDTH_SCANNER) do |gc|
|
156
|
+
case
|
157
|
+
when gc[NON_PRINTING_START_INDEX]
|
158
|
+
in_zero_width = true
|
159
|
+
+ chunk << NON_PRINTING_START
|
160
|
+
when gc[NON_PRINTING_END_INDEX]
|
161
|
+
in_zero_width = false
|
162
|
+
+ chunk << NON_PRINTING_END
|
163
|
+
when gc[CSI_REGEXP_INDEX]
|
164
|
+
chunk << gc[CSI_REGEXP_INDEX]
|
165
|
+
when gc[OSC_REGEXP_INDEX]
|
166
|
+
@@ -215,13 +219,31 @@ class Reline::Unicode
|
167
|
+
chunk << gc
|
168
|
+
else
|
169
|
+
mbchar_width = get_mbchar_width(gc)
|
170
|
+
+ prev_width = total_width
|
171
|
+
total_width += mbchar_width
|
172
|
+
- break if (start_col + max_width) < total_width
|
173
|
+
- chunk << gc if start_col < total_width
|
174
|
+
+ break if !cover_end && total_width > start_col + width
|
175
|
+
+ if cover_begin ? start_col < total_width : start_col <= prev_width
|
176
|
+
+ chunk << gc
|
177
|
+
+ chunk_start_col ||= prev_width
|
178
|
+
+ chunk_end_col = total_width
|
179
|
+
+ end
|
180
|
+
+ break if total_width >= start_col + width
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
- chunk
|
185
|
+
+ chunk_start_col ||= start_col
|
186
|
+
+ chunk_end_col ||= start_col
|
187
|
+
+ if padding
|
188
|
+
+ if start_col < chunk_start_col
|
189
|
+
+ chunk = ' ' * (chunk_start_col - start_col) + chunk
|
190
|
+
+ chunk_start_col = start_col
|
191
|
+
+ end
|
192
|
+
+ if chunk_end_col < start_col + width
|
193
|
+
+ chunk << ' ' * (start_col + width - chunk_end_col)
|
194
|
+
+ chunk_end_col = start_col + width
|
195
|
+
+ end
|
196
|
+
+ end
|
197
|
+
+ [chunk, chunk_start_col, chunk_end_col - chunk_start_col]
|
198
|
+
end
|
199
|
+
|
200
|
+
def self.get_next_mbchar_size(line, byte_pointer)
|