Module: KatakataIrb::Completor
- Defined in:
- lib/katakata_irb/completor.rb
Constant Summary collapse
- HIDDEN_METHODS =
defined by rbs, should be hidden
%w[Namespace TypeName]
Instance Attribute Summary collapse
-
#prev_analyze_result ⇒ Object
Returns the value of attribute prev_analyze_result.
Class Method Summary collapse
- .analyze(code, binding = Object::TOPLEVEL_BINDING) ⇒ Object
- .candidates_from_result(result) ⇒ Object
- .find_target(node, position) ⇒ Object
- .setup ⇒ Object
- .setup_type_dialog ⇒ Object
- .type_dialog_content ⇒ Object
Instance Attribute Details
#prev_analyze_result ⇒ Object
Returns the value of attribute prev_analyze_result.
9 10 11 |
# File 'lib/katakata_irb/completor.rb', line 9 def prev_analyze_result @prev_analyze_result end |
Class Method Details
.analyze(code, binding = Object::TOPLEVEL_BINDING) ⇒ Object
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 |
# File 'lib/katakata_irb/completor.rb', line 209 def self.analyze(code, binding = Object::TOPLEVEL_BINDING) # Workaround for https://github.com/ruby/prism/issues/1592 return if code.match?(/%[qQ]\z/) lvars_code = binding.local_variables.map do |name| "#{name}=" end.join + "nil;\n" code = lvars_code + code ast = Prism.parse(code).value name = code[/(@@|@|\$)?\w*[!?=]?\z/] *parents, target_node = find_target ast, code.bytesize - name.bytesize return unless target_node calculate_scope = -> { KatakataIrb::TypeAnalyzer.calculate_target_type_scope(binding, parents, target_node).last } calculate_type_scope = ->(node) { KatakataIrb::TypeAnalyzer.calculate_target_type_scope binding, [*parents, target_node], node } case target_node when Prism::StringNode, Prism::InterpolatedStringNode call_node, args_node = parents.last(2) return unless call_node.is_a?(Prism::CallNode) && call_node.receiver.nil? return unless args_node.is_a?(Prism::ArgumentsNode) && args_node.arguments.size == 1 case call_node.name when :require [:require, name.rstrip] when :require_relative [:require_relative, name.rstrip] end when Prism::SymbolNode if parents.last.is_a? Prism::BlockArgumentNode # method(&:target) receiver_type, _scope = calculate_type_scope.call target_node [:call, receiver_type, name, false] else [:symbol, name] unless name.empty? end when Prism::CallNode return [:lvar_or_method, name, calculate_scope.call] if target_node.receiver.nil? self_call = target_node.receiver.is_a? Prism::SelfNode op = target_node.call_operator receiver_type, _scope = calculate_type_scope.call target_node.receiver receiver_type = receiver_type.nonnillable if op == '&.' [op == '::' ? :call_or_const : :call, receiver_type, name, self_call] when Prism::LocalVariableReadNode, Prism::LocalVariableTargetNode [:lvar_or_method, name, calculate_scope.call] when Prism::ConstantReadNode, Prism::ConstantTargetNode if parents.last.is_a? Prism::ConstantPathNode path_node = parents.last if path_node.parent # A::B receiver, scope = calculate_type_scope.call(path_node.parent) [:const, receiver, name, scope] else # ::A scope = calculate_scope.call [:const, KatakataIrb::Types::SingletonType.new(Object), name, scope] end else [:const, nil, name, calculate_scope.call] end when Prism::GlobalVariableReadNode, Prism::GlobalVariableTargetNode [:gvar, name, calculate_scope.call] when Prism::InstanceVariableReadNode, Prism::InstanceVariableTargetNode [:ivar, name, calculate_scope.call] when Prism::ClassVariableReadNode, Prism::ClassVariableTargetNode [:cvar, name, calculate_scope.call] end end |
.candidates_from_result(result) ⇒ Object
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
# File 'lib/katakata_irb/completor.rb', line 11 def self.candidates_from_result(result) candidates = case result in [:require | :require_relative => method, name] if IRB.const_defined? :RegexpCompletor # IRB::VERSION >= 1.8.2 path_completor = IRB::RegexpCompletor.new elsif IRB.const_defined? :InputCompletor # IRB::VERSION <= 1.8.1 path_completor = IRB::InputCompletor end if !path_completor [] elsif method == :require path_completor.retrieve_files_to_require_from_load_path else path_completor.retrieve_files_to_require_relative_from_current_dir end in [:call_or_const, type, name, self_call] ((self_call ? type.all_methods : type.methods).map(&:to_s) - HIDDEN_METHODS) | type.constants in [:const, type, name, scope] if type scope_constants = type.types.flat_map do |t| scope.table_module_constants(t.module_or_class) if t.is_a?(KatakataIrb::Types::SingletonType) end (scope_constants.compact | type.constants.map(&:to_s)).sort else scope.constants.sort end in [:ivar, name, scope] ivars = scope.instance_variables.sort name == '@' ? ivars + scope.class_variables.sort : ivars in [:cvar, name, scope] scope.class_variables in [:gvar, name, scope] scope.global_variables in [:symbol, name] Symbol.all_symbols.map { _1.inspect[1..] } in [:call, type, name, self_call] (self_call ? type.all_methods : type.methods).map(&:to_s) - HIDDEN_METHODS in [:lvar_or_method, name, scope] scope.self_type.all_methods.map(&:to_s) | scope.local_variables else [] end [name || '', candidates] end |
.find_target(node, position) ⇒ Object
276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 |
# File 'lib/katakata_irb/completor.rb', line 276 def self.find_target(node, position) location = ( case node when Prism::CallNode node. when Prism::SymbolNode node.value_loc when Prism::StringNode node.content_loc when Prism::InterpolatedStringNode node.closing_loc if node.parts.empty? end ) return [node] if location&.start_offset == position node.compact_child_nodes.each do |n| match = find_target(n, position) next unless match match.unshift node return match end [node] if node.location.start_offset == position end |
.setup ⇒ Object
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 |
# File 'lib/katakata_irb/completor.rb', line 56 def self.setup KatakataIrb::Types.preload_in_thread completion_proc = ->(preposing, target, _postposing, bind:) do verbose, $VERBOSE = $VERBOSE, nil code = "#{preposing}#{target}" result = analyze code, bind KatakataIrb::Completor.prev_analyze_result = result name, candidates = candidates_from_result(result).dup all_symbols_pattern = /\A[ -\/:-@\[-`\{-~]*\z/ candidates.map(&:to_s).select { !_1.match?(all_symbols_pattern) && _1.start_with?(name) }.uniq.sort.map do target + _1[name.size..] end rescue SyntaxError, StandardError => e KatakataIrb.last_completion_error = e KatakataIrb.log_puts KatakataIrb.log_puts "#{e.inspect} stored to KatakataIrb.last_completion_error" KatakataIrb.log_puts ensure $VERBOSE = verbose end doc_namespace_proc = -> input do name = input[/[a-zA-Z_0-9]*[!?=]?\z/] method_doc = -> type do type = type.types.find { _1.all_methods.include? name.to_sym } if type.is_a? KatakataIrb::Types::SingletonType "#{KatakataIrb::Types.class_name_of(type.module_or_class)}.#{name}" elsif type.is_a? KatakataIrb::Types::InstanceType "#{KatakataIrb::Types.class_name_of(type.klass)}##{name}" end end call_or_const_doc = -> type do if name =~ /\A[A-Z]/ type = type.types.grep(KatakataIrb::Types::SingletonType).find { _1.module_or_class.const_defined?(name) } type.module_or_class == Object ? name : "#{KatakataIrb::Types.class_name_of(type.module_or_class)}::#{name}" if type else method_doc.call(type) end end value_doc = -> type do return unless type type.types.each do |t| case t when KatakataIrb::Types::SingletonType return KatakataIrb::Types.class_name_of(t.module_or_class) when KatakataIrb::Types::InstanceType return KatakataIrb::Types.class_name_of(t.klass) end end nil end case KatakataIrb::Completor.prev_analyze_result in [:call_or_const, type, _name, _self_call] call_or_const_doc.call type in [:const, type, _name, scope] if type call_or_const_doc.call type else value_doc.call scope[name] end in [:gvar, _name, scope] value_doc.call scope["$#{name}"] in [:ivar, _name, scope] value_doc.call scope["@#{name}"] in [:cvar, _name, scope] value_doc.call scope["@@#{name}"] in [:call, type, _name, _self_call] method_doc.call type in [:lvar_or_method, _name, scope] if scope.local_variables.include?(name) value_doc.call scope[name] else method_doc.call scope.self_type end else end end if IRB.const_defined? :RegexpCompletor # IRB::VERSION >= 1.8.2 IRB::RegexpCompletor.class_eval do define_method :completion_candidates do |preposing, target, postposing, bind:| completion_proc.call(preposing, target, postposing, bind: bind) end define_method :doc_namespace do |_preposing, matched, _postposing, bind:| doc_namespace_proc.call matched end end elsif IRB.const_defined? :InputCompletor # IRB::VERSION <= 1.8.1 IRB::InputCompletor::CompletionProc.define_singleton_method :call do |target, preposing = '', postposing = ''| bind = IRB.conf[:MAIN_CONTEXT].workspace.binding completion_proc.call(preposing, target, postposing, bind: bind) end IRB::InputCompletor.singleton_class.prepend( Module.new do define_method :retrieve_completion_data do |input, doc_namespace: false, **kwargs| return super(input, doc_namespace: false, **kwargs) unless doc_namespace doc_namespace_proc.call input end end ) else puts 'Cannot activate katakata_irb' end setup_type_dialog end |
.setup_type_dialog ⇒ Object
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 |
# File 'lib/katakata_irb/completor.rb', line 192 def self.setup_type_dialog type_dialog_proc = -> { return if just_cursor_moving && completion_journey_data cursor_pos_to_render, _result, pointer, autocomplete_dialog = context.last(4) return unless cursor_pos_to_render && autocomplete_dialog&.width && pointer.nil? contents = KatakataIrb::Completor.type_dialog_content return unless contents&.any? width = contents.map { Reline::Unicode.calculate_width _1 }.max x = cursor_pos_to_render.x + autocomplete_dialog.width y = cursor_pos_to_render.y info = { pos: Reline::CursorPos.new(x, y), contents: contents, width: width, bg_color: 44, fg_color: 37 } Reline::DialogRenderInfo.new(**info.slice(*Reline::DialogRenderInfo.members)) } Reline.add_dialog_proc(:show_type, type_dialog_proc, Reline::DEFAULT_DIALOG_CONTEXT) end |
.type_dialog_content ⇒ Object
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 |
# File 'lib/katakata_irb/completor.rb', line 166 def self.type_dialog_content receiver_type = ( case KatakataIrb::Completor.prev_analyze_result in [:call_or_const, type, name, _self_call] if name.empty? type in [:call, type, name, _self_call] if name.empty? type else return end ) if KatakataIrb::Types.rbs_builder.nil? && !KatakataIrb::Types.rbs_load_error return [' Loading ', ' RBS... '] end types = receiver_type.types contents = types.filter_map do |type| case type when KatakataIrb::Types::InstanceType type.inspect_without_params else type.inspect end end.uniq contents if contents.any? end |