require "pathname" require "rsense-core" require_relative "./listeners/find_definition_event_listener" require_relative "./listeners/where_event_listener" require_relative "./command/special_meth" require_relative "./command/type_inference_method" module Rsense module Server Context = Struct.new(:project, :typeSet, :main, :feature, :loadPathLevel) { def clear @project = nil @typeSet = nil @main = false @feature = nil @loadPathLevel = 0 end } module Command TYPE_INFERENCE_METHOD_NAME = Rsense::CodeAssist::TYPE_INFERENCE_METHOD_NAME FIND_DEFINITION_METHOD_NAME_PREFIX = Rsense::CodeAssist::FIND_DEFINITION_METHOD_NAME_PREFIX PROJECT_CONFIG_NAME = ".rsense" end end end class Rsense::Server::Command::Command LoadResult = Java::org.cx4a.rsense::LoadResult CompletionCandidate = Struct.new( :completion, :qualified_name, :base_name, :kind ) attr_accessor :context, :options, :parser, :projects, :sandbox, :definitionFinder, :whereListener, :type_inference_method, :require_method, :require_next_method, :result, :graph def initialize(options) @context = Rsense::Server::Context.new @options = options @type_inference_method = Rsense::Server::Command::TypeInferenceMethod.new() @type_inference_method.context = @context @require_method = Rsense::Server::Command::SpecialMeth.new() do |runtime, receivers, args, blcck, result| if args feature = Java::org.cx4a.rsense.typing.vertex::Vertex.getString(args[0]) if feature rrequire(@context.project, feature, "UTF-8") end end end @require_next_method = Rsense::Server::Command::SpecialMeth.new() do |runtime, receivers, args, blcck, result| if @context.feature rrequire(@context.project, @context.feature, "UTF-8", @context.loadPathLevel + 1) end end clear() end def rload(project, file, encoding, prep) return LoadResult.alreadyLoaded() if project.loaded?(file) return if file.extname =~ /(\.so|\.dylib|\.dll|\.java|\.class|\.c$|\.h$|\.m$|\.js|\.html|\.css)/ project.loaded << file oldmain = @context.main if prep prepare(project) else @context.main = false end ast = @parser.parse_string(file.read, file.to_s) project.graph.load(ast) result = LoadResult.new result.setAST(ast) result end def rrequire(project, feature, encoding, loadPathLevel=0) return LoadResult.alreadyLoaded() unless project.loaded?(feature) project.loaded << feature stubs = stubs_matches(project, feature) stubs.each do |stub| rload(project, Pathname.new(stub), encoding, false) end lpmatches = load_path_matches(project, feature) lpmatches.each do |lp| rload(project, lp, encoding, false) end dependencies = project.dependencies dpmatches = dependency_matches(dependencies, feature) dpmatches.each do |dp| rload(project, dp, encoding, false) end unless lpmatches || dpmatches dep_paths = dependency_paths(dependencies) gem_path = project.gem_path.map {|gp| Pathname.new(gp) } checked = deep_check(gem_path, dep_paths, feature) checked.each do |cp| rload(project, cp, encoding, false) end end end def load_builtin(project) builtin = builtin_path(project) rload(project, builtin, "UTF-8", false) end def builtin_path(project) Pathname.new(project.stubs.select { |stub| stub.match(/builtin/)}.first) end def stub_matches(project, feature) project.stubs.select { |stub| stub.to_s =~ /#{feature}/ } end def dependency_paths(dependencies) dependencies.map { |d| Pathname.new(d.path.first).parent }.flatten end def dependency_matches(dependencies, feature) dmatch = dependencies.select { |d| d.name =~ /#{feature}/ } if dmatch dmatch.map { |dm| Pathname.new(dm.path.first) } end end def load_path_matches(project, feature) load_path = project.load_path load_path.map do |lp| Dir.glob(Pathname.new(lp).join("**/*#{feature}*")) end.flatten.compact end def deep_check(gem_path, dep_paths, feature) checkpaths = gem_path + dep_paths checkpaths.map do |p| Dir.glob(Pathname.new(p).join("**/*#{feature}*")) end.flatten.compact end def open_project(project) @projects[project.name] = project end def code_completion(file, location, code_str="") if code_str.empty? code = Rsense::Server::Code.new(Pathname.new(file).read) else code = Rsense::Server::Code.new(code_str) puts code end source = code.inject_inference_marker(location) ast = @parser.parse_string(source, file.to_s) @project.graph.load(ast) result = Java::org.cx4a.rsense::CodeCompletionResult.new result.setAST(ast) candidates = [] @receivers = [] @context.typeSet.each do |receiver| @receivers << receiver ruby_class = receiver.getMetaClass ruby_class.getMethods(true).each do |name| rmethod = ruby_class.searchMethod(name) candidates << CompletionCandidate.new(name, rmethod.toString(), rmethod.getModule().getMethodPath(nil), method_kind) end if receiver.to_java_object.java_kind_of?(Java::org.cx4a.rsense.ruby::RubyModule) rmodule = receiver rmodule.getConstants(true).each do |name| direct_module = rmodule.getConstantModule(name) constant = direct_module.getConstant(name) base_name = direct_module.toString() qname = "#{base_name}::#{name}" kind = kind_check(constant) candidates << CompletionCandidate.new(name, qname, base_name, kind) end end end candidates end def method_kind Java::org.cx4a.rsense::CodeCompletionResult::CompletionCandidate::Kind::METHOD end def kind_check(constant) if constant.class == Java::org.cx4a.rsense.ruby::RubyClass Java::org.cx4a.rsense::CodeCompletionResult::CompletionCandidate::Kind::CLASS elsif constant.class == Java::org.cx4a.rsense.ruby::RubyModule Java::org.cx4a.rsense::CodeCompletionResult::CompletionCandidate::Kind::MODULE else Java::org.cx4a.rsense::CodeCompletionResult::CompletionCandidate::Kind::CONSTANT end end def prepare(project) @context.project = project @context.typeSet = Java::org.cx4a.rsense.typing::TypeSet.new @context.main = true @type_inference_method.context = @context @graph = project.graph @graph.addSpecialMethod(Rsense::Server::Command::TYPE_INFERENCE_METHOD_NAME, @type_inference_method) @graph.addSpecialMethod("require", @require_method) @graph.addSpecialMethod("require_next", @require_next_method) load_builtin(project) end def clear @parser = Rsense::Server::Parser.new @context.clear() @projects = {} @sandbox = Rsense::Server::Project.new("(sandbox)", Pathname.new(".")) @definitionFinder = Rsense::Server::Listeners::FindDefinitionEventListener.new(@context) @whereListener = Rsense::Server::Listeners::WhereEventListener.new(@context) open_project(@sandbox) prepare_project() end def prepare_project() if @options.name name = @roptions.name else name = "(sandbox)" end file = @options.project_path @project = Rsense::Server::Project.new(name, file) prepare(@project) end end