solargraph 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,31 @@
1
+ module Solargraph
2
+ class Mapper
3
+ def initialize
4
+ @default_api_map = Solargraph::ApiMap.new
5
+ stub = Parser::CurrentRuby.parse(Solargraph::LiveParser.parse(nil))
6
+ @default_api_map.merge(stub)
7
+ @default_api_map.freeze
8
+ @require_nodes = {}
9
+ end
10
+
11
+ def get filename, text
12
+ workspace = find_workspace(filename)
13
+ CodeMap.new(text, api_map: @default_api_map, workspace: workspace, require_nodes: @require_nodes)
14
+ end
15
+
16
+ def find_workspace filename
17
+ dirname = filename
18
+ lastname = nil
19
+ result = nil
20
+ until dirname == lastname
21
+ if File.file?("#{dirname}/Gemfile")
22
+ result = dirname
23
+ break
24
+ end
25
+ lastname = dirname
26
+ dirname = File.dirname(dirname)
27
+ end
28
+ result || File.dirname(filename)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,48 @@
1
+ module Solargraph
2
+ module NodeMethods
3
+ def unpack_name(node)
4
+ pack_name(node).join("::")
5
+ end
6
+
7
+ def pack_name(node)
8
+ parts = []
9
+ node.children.each { |n|
10
+ if n.kind_of?(AST::Node)
11
+ if n.type == :cbase
12
+ parts = pack_name(n)
13
+ else
14
+ parts += pack_name(n)
15
+ end
16
+ else
17
+ parts.push n unless n.nil?
18
+ end
19
+ }
20
+ parts
21
+ end
22
+
23
+ def infer node
24
+ if node.type == :str
25
+ return 'String'
26
+ elsif node.type == :array
27
+ return 'Array'
28
+ elsif node.type == :hash
29
+ return 'Hash'
30
+ elsif node.type == :int
31
+ return 'Integer'
32
+ elsif node.type == :float
33
+ return 'Float'
34
+ elsif node.type == :send
35
+ if node.children[0].nil?
36
+ # TODO Another local variable or method or something? sheesh
37
+ else
38
+ ns = unpack_name(node.children[0])
39
+ if node.children[1] == :new
40
+ return ns
41
+ end
42
+ end
43
+ elsif node.type == :cbase or node.type == :const
44
+ unpack_name node
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,21 @@
1
+ require 'sinatra/base'
2
+
3
+ module Solargraph
4
+ class Server < Sinatra::Base
5
+ set :port, 0
6
+
7
+ post '/suggest' do
8
+ content_type :json
9
+ begin
10
+ code_map = CodeMap.new(code: params['text'], filename: params['filename'])
11
+ offset = code_map.get_offset(params['line'].to_i, params['col'].to_i)
12
+ sugg = code_map.suggest_at(offset, with_snippets: true, filtered: true)
13
+ { "status" => "ok", "suggestions" => sugg }.to_json
14
+ rescue Exception => e
15
+ STDERR.puts e
16
+ STDERR.puts e.backtrace.join("\n")
17
+ { "status" => "err", "message" => e.message }.to_json
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,84 @@
1
+ require 'thor'
2
+ require 'json'
3
+ require 'fileutils'
4
+ require 'rubygems/package'
5
+ require 'zlib'
6
+
7
+ module Solargraph
8
+ class Shell < Thor
9
+ desc 'prepare', 'Cache YARD files for the current environment'
10
+ option :force, type: :boolean, aliases: :f, desc: 'Force download of YARDOC files if they already exist'
11
+ option :host, type: :string, aliases: :h, desc: 'The host that provides YARDOC files for download', default: 'yardoc.solargraph.org'
12
+ def prepare
13
+ cache_dir = File.join(Dir.home, '.solargraph', 'cache')
14
+ version_dir = File.join(cache_dir, '2.0.0')
15
+ unless File.exist?(version_dir) or options[:force]
16
+ FileUtils.mkdir_p cache_dir
17
+ require 'net/http'
18
+ puts 'Downloading 2.0.0...'
19
+ Net::HTTP.start(options[:host]) do |http|
20
+ resp = http.get("/2.0.0.tar.gz")
21
+ open(File.join(cache_dir, '2.0.0.tar.gz'), "wb") do |file|
22
+ file.write(resp.body)
23
+ end
24
+ puts 'Uncompressing archives...'
25
+ FileUtils.rm_rf version_dir if File.exist?(version_dir)
26
+ tar_extract = Gem::Package::TarReader.new(Zlib::GzipReader.open(File.join(cache_dir, '2.0.0.tar.gz')))
27
+ tar_extract.rewind
28
+ tar_extract.each do |entry|
29
+ if entry.directory?
30
+ FileUtils.mkdir_p File.join(cache_dir, entry.full_name)
31
+ else
32
+ FileUtils.mkdir_p File.join(cache_dir, File.dirname(entry.full_name))
33
+ File.open(File.join(cache_dir, entry.full_name), 'wb') do |f|
34
+ f << entry.read
35
+ end
36
+ end
37
+ end
38
+ tar_extract.close
39
+ FileUtils.rm File.join(cache_dir, '2.0.0.tar.gz')
40
+ puts 'Done.'
41
+ end
42
+ end
43
+ end
44
+
45
+ desc 'bundled', 'Get a list of bundled gems'
46
+ def bundled
47
+ Bundler.load.specs.each { |s|
48
+ puts s.name
49
+ }
50
+ end
51
+
52
+ desc 'server', 'Start a Solargraph server'
53
+ def server
54
+ Solargraph::Server.run!
55
+ end
56
+
57
+ desc 'suggest', 'Get code suggestions for the provided input'
58
+ long_desc <<-LONGDESC
59
+ Analyze a Ruby file and output a list of code suggestions in JSON format.
60
+ LONGDESC
61
+ option :line, type: :numeric, aliases: :l, desc: 'Zero-based line number', required: true
62
+ option :column, type: :numeric, aliases: [:c, :col], desc: 'Zero-based column number', required: true
63
+ option :filename, type: :string, aliases: :f, desc: 'File name', required: false
64
+ def suggest(*filenames)
65
+ # HACK: The ARGV array needs to be manipulated for ARGF.read to work
66
+ ARGV.clear
67
+ ARGV.concat filenames
68
+ text = ARGF.read
69
+ filename = options[:filename] || filenames[0]
70
+ begin
71
+ code_map = CodeMap.new(code: text, filename: filename)
72
+ offset = code_map.get_offset(options[:line], options[:column])
73
+ sugg = code_map.suggest_at(offset, with_snippets: true, filtered: true)
74
+ result = { "status" => "ok", "suggestions" => sugg }.to_json
75
+ STDOUT.puts result
76
+ rescue Exception => e
77
+ STDERR.puts e
78
+ STDERR.puts e.backtrace.join("\n")
79
+ result = { "status" => "err", "message" => e.message }.to_json
80
+ STDOUT.puts result
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,186 @@
1
+ require 'json'
2
+
3
+ module Solargraph
4
+ module Snippets
5
+ def self.definitions
6
+ @definitions ||= JSON.parse('{
7
+ "Exception block": {
8
+ "prefix": "begin",
9
+ "body": [
10
+ "begin",
11
+ "\t$1",
12
+ "rescue => exception",
13
+ "\t",
14
+ "end"
15
+ ]
16
+ },
17
+ "Exception block with ensure": {
18
+ "prefix": "begin ensure",
19
+ "body": [
20
+ "begin",
21
+ "\t$1",
22
+ "rescue => exception",
23
+ "\t",
24
+ "ensure",
25
+ "\t",
26
+ "end"
27
+ ]
28
+ },
29
+ "Exception block with else": {
30
+ "prefix": "begin else",
31
+ "body": [
32
+ "begin",
33
+ "\t$1",
34
+ "rescue => exception",
35
+ "\t",
36
+ "else",
37
+ "\t",
38
+ "end"
39
+ ]
40
+ },
41
+ "Exception block with else and ensure": {
42
+ "prefix": "begin else ensure",
43
+ "body": [
44
+ "begin",
45
+ "\t$1",
46
+ "rescue => exception",
47
+ "\t",
48
+ "else",
49
+ "\t",
50
+ "ensure",
51
+ "\t",
52
+ "end"
53
+ ]
54
+ },
55
+ "Class definition with initialize": {
56
+ "prefix": "class init",
57
+ "body": [
58
+ "class ${ClassName}",
59
+ "\tdef initialize",
60
+ "\t\t$0",
61
+ "\tend",
62
+ "end"
63
+ ]
64
+ },
65
+ "Class definition": {
66
+ "prefix": "class",
67
+ "body": [
68
+ "class ${ClassName}",
69
+ "\t$0",
70
+ "end"
71
+ ]
72
+ },
73
+ "for loop": {
74
+ "prefix": "for",
75
+ "body": [
76
+ "for ${value} in ${enumerable} do",
77
+ "\t$0",
78
+ "end"
79
+ ]
80
+ },
81
+ "if": {
82
+ "prefix": "if",
83
+ "body": [
84
+ "if ${test}",
85
+ "\t$0",
86
+ "end"
87
+ ]
88
+ },
89
+ "if else": {
90
+ "prefix": "if else",
91
+ "body": [
92
+ "if ${test}",
93
+ "\t$0",
94
+ "else",
95
+ "\t",
96
+ "end"
97
+ ]
98
+ },
99
+ "if elsif": {
100
+ "prefix": "if elsif",
101
+ "body": [
102
+ "if ${test}",
103
+ "\t$0",
104
+ "elsif ",
105
+ "\t",
106
+ "end"
107
+ ]
108
+ },
109
+ "if elsif else": {
110
+ "prefix": "if elsif else",
111
+ "body": [
112
+ "if ${test}",
113
+ "\t$0",
114
+ "elsif ",
115
+ "\t",
116
+ "else",
117
+ "\t",
118
+ "end"
119
+ ]
120
+ },
121
+ "forever loop": {
122
+ "prefix": "loop",
123
+ "body": [
124
+ "loop do",
125
+ "\t$0",
126
+ "end"
127
+ ]
128
+ },
129
+ "Module definition": {
130
+ "prefix": "module",
131
+ "body": [
132
+ "module ${ModuleName}",
133
+ "\t$0",
134
+ "end"
135
+ ]
136
+ },
137
+ "unless": {
138
+ "prefix": "unless",
139
+ "body": [
140
+ "unless ${test}",
141
+ "\t$0",
142
+ "end"
143
+ ]
144
+ },
145
+ "until loop": {
146
+ "prefix": "until",
147
+ "body": [
148
+ "until ${test}",
149
+ "\t$0",
150
+ "end"
151
+ ]
152
+ },
153
+ "while loop": {
154
+ "prefix": "while",
155
+ "body": [
156
+ "while ${test}",
157
+ "\t$0",
158
+ "end"
159
+ ]
160
+ },
161
+ "method definition": {
162
+ "prefix": "def",
163
+ "body": [
164
+ "def ${method_name}",
165
+ "\t$0",
166
+ "end"
167
+ ],
168
+ "description": "Snippet for method definitions"
169
+ },
170
+ "defined?": {
171
+ "prefix": "defined?",
172
+ "body": [
173
+ "defined?"
174
+ ],
175
+ "description": "Something silly"
176
+ }
177
+ }')
178
+ end
179
+
180
+ def self.keywords
181
+ @keywords ||= [
182
+ 'begin', 'class', 'for', 'if', 'module', 'unless', 'until', 'while', 'def', 'defined?'
183
+ ]
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,40 @@
1
+ require 'json'
2
+
3
+ module Solargraph
4
+
5
+ class Suggestion
6
+ CLASS = 'Class'
7
+ KEYWORD = 'Keyword'
8
+ MODULE = 'Module'
9
+ METHOD = 'Method'
10
+ VARIABLE = 'Variable'
11
+ SNIPPET = 'Snippet'
12
+
13
+ attr_reader :label, :kind, :insert, :detail, :documentation
14
+
15
+ def initialize label, kind: KEYWORD, insert: nil, detail: nil, documentation: nil, code_object: nil
16
+ @label = label.to_s
17
+ @kind = kind
18
+ @insert = insert || @label
19
+ @detail = detail
20
+ @code_object = code_object
21
+ @documentation = documentation
22
+ end
23
+
24
+ def to_s
25
+ label
26
+ end
27
+
28
+ def to_json args={}
29
+ {
30
+ label: @label,
31
+ kind: @kind,
32
+ insert: @insert,
33
+ detail: @detail,
34
+ #documentation: (@documentation.nil? ? nil : @documentation.all)
35
+ documentation: (@code_object.nil? ? nil : YARD::Templates::Engine.render(format: :text, object: @code_object))
36
+ }.to_json(args)
37
+ end
38
+ end
39
+
40
+ end
@@ -0,0 +1,3 @@
1
+ module Solargraph
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,119 @@
1
+ require 'rubygems'
2
+ require 'parser/current'
3
+ require 'yard'
4
+
5
+ module Solargraph
6
+
7
+ class YardMap
8
+ def initialize required: [], workspace: nil
9
+ unless workspace.nil?
10
+ wsy = File.join(workspace, '.yardoc')
11
+ yardocs.push wsy if File.exist?(wsy)
12
+ end
13
+ required.each { |r|
14
+ gy = YARD::Registry.yardoc_file_for_gem(r)
15
+ yardocs.push gy unless gy.nil?
16
+ }
17
+ yardocs.push File.join(Dir.home, '.solargraph', 'cache', '2.0.0', 'yardoc')
18
+ #yardocs.push File.join(Dir.home, '.solargraph', 'cache', '2.0.0', 'yardoc-stdlib')
19
+ end
20
+
21
+ def yardocs
22
+ @yardocs ||= []
23
+ end
24
+
25
+ def get_constants namespace, scope = ''
26
+ consts = []
27
+ result = []
28
+ yardocs.each { |y|
29
+ yard = YARD::Registry.load! y
30
+ unless yard.nil?
31
+ ns = nil
32
+ if scope == ''
33
+ ns = yard.at(namespace)
34
+ else
35
+ ns = yard.resolve(P(scope), namespace)
36
+ end
37
+ consts += ns.children unless ns.nil?
38
+ end
39
+ }
40
+ consts.each { |c|
41
+ result.push Suggestion.new(c.to_s.split('::').last, kind: Suggestion::CLASS)
42
+ }
43
+ result
44
+ end
45
+
46
+ def at signature
47
+ yardocs.each { |y|
48
+ yard = YARD::Registry.load! y
49
+ unless yard.nil?
50
+ obj = yard.at(signature)
51
+ return obj unless obj.nil?
52
+ end
53
+ }
54
+ nil
55
+ end
56
+
57
+ def resolve signature, scope
58
+ yardocs.each { |y|
59
+ yard = YARD::Registry.load! y
60
+ unless yard.nil?
61
+ obj = yard.resolve(P(scope), signature)
62
+ return obj unless obj.nil?
63
+ end
64
+ }
65
+ nil
66
+ end
67
+
68
+ def get_methods namespace, scope = ''
69
+ meths = []
70
+ yardocs.each { |y|
71
+ yard = YARD::Registry.load! y
72
+ unless yard.nil?
73
+ ns = nil
74
+ if scope == ''
75
+ ns = yard.at(namespace)
76
+ else
77
+ ns = yard.resolve(P(scope), namespace)
78
+ end
79
+ unless ns.nil?
80
+ ns.meths(scope: :class, visibility: [:public]).each { |m|
81
+ n = m.to_s.split('.').last
82
+ meths.push Suggestion.new("#{n}", kind: Suggestion::METHOD) if n.to_s.match(/^[a-z]/i)
83
+ }
84
+ if ns.kind_of?(YARD::CodeObjects::ClassObject) and namespace != 'Class'
85
+ meths += get_instance_methods('Class')
86
+ end
87
+ end
88
+ end
89
+ }
90
+ meths
91
+ end
92
+
93
+ def get_instance_methods namespace, scope = ''
94
+ meths = []
95
+ yardocs.each { |y|
96
+ yard = YARD::Registry.load! y
97
+ unless yard.nil?
98
+ ns = nil
99
+ if scope == ''
100
+ ns = yard.at(namespace)
101
+ else
102
+ ns = yard.resolve(P(scope), namespace)
103
+ end
104
+ unless ns.nil?
105
+ ns.meths(scope: :instance, visibility: [:public]).each { |m|
106
+ n = m.to_s.split('#').last
107
+ meths.push Suggestion.new("#{n}", kind: Suggestion::METHOD, documentation: m.docstring, code_object: m) if n.to_s.match(/^[a-z]/i) and !m.to_s.start_with?('Kernel#') and !m.docstring.to_s.include?(':nodoc:')
108
+ }
109
+ if ns.kind_of?(YARD::CodeObjects::ClassObject) and namespace != 'Object'
110
+ meths += get_instance_methods('Object')
111
+ end
112
+ end
113
+ end
114
+ }
115
+ meths
116
+ end
117
+ end
118
+
119
+ end