language_server 0.9.0 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +1 -0
- data/.rubocop.yml +45 -0
- data/CHANGELOG.md +5 -0
- data/Dockerfile +2 -0
- data/Dockerfile.development +1 -1
- data/Gemfile +4 -1
- data/Guardfile +1 -1
- data/README.md +2 -0
- data/Rakefile +1 -1
- data/benchmark/rubocop +25 -0
- data/benchmark/ruby_wc +3 -3
- data/bin/ci +7 -0
- data/bin/rubocop +17 -0
- data/circle.yml +10 -2
- data/exe/language_server-ruby +23 -1
- data/language_server.gemspec +13 -14
- data/lib/language_server.rb +42 -25
- data/lib/language_server/completion_provider/ad_hoc.rb +7 -7
- data/lib/language_server/completion_provider/rcodetools.rb +4 -4
- data/lib/language_server/definition_provider/ad_hoc.rb +5 -5
- data/lib/language_server/file_store.rb +21 -21
- data/lib/language_server/linter/rubocop.rb +16 -18
- data/lib/language_server/linter/ruby_wc.rb +48 -37
- data/lib/language_server/logger.rb +12 -12
- data/lib/language_server/project.rb +34 -34
- data/lib/language_server/project/node.rb +15 -12
- data/lib/language_server/project/parser.rb +54 -54
- data/lib/language_server/version.rb +1 -1
- metadata +44 -54
@@ -11,35 +11,35 @@ module LanguageServer
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def call
|
14
|
-
(project.constants(uri: uri, line: line, character: character).map {|c|
|
14
|
+
(project.constants(uri: uri, line: line, character: character).map { |c|
|
15
15
|
Protocol::Interface::CompletionItem.new(
|
16
16
|
label: c.name,
|
17
17
|
detail: c.full_name,
|
18
18
|
documentation: "#{c.remote_path}##{c.lineno}",
|
19
|
-
kind: Protocol::Constant::CompletionItemKind::ENUM
|
19
|
+
kind: Protocol::Constant::CompletionItemKind::ENUM,
|
20
20
|
)
|
21
21
|
} +
|
22
|
-
project.classes(uri: uri, line: line, character: character).map {|c|
|
22
|
+
project.classes(uri: uri, line: line, character: character).map { |c|
|
23
23
|
Protocol::Interface::CompletionItem.new(
|
24
24
|
label: c.name,
|
25
25
|
detail: c.full_name,
|
26
26
|
documentation: "#{c.remote_path}##{c.lineno}",
|
27
|
-
kind: Protocol::Constant::CompletionItemKind::CLASS
|
27
|
+
kind: Protocol::Constant::CompletionItemKind::CLASS,
|
28
28
|
)
|
29
29
|
} +
|
30
|
-
project.modules(uri: uri, line: line, character: character).map {|m|
|
30
|
+
project.modules(uri: uri, line: line, character: character).map { |m|
|
31
31
|
Protocol::Interface::CompletionItem.new(
|
32
32
|
label: m.name,
|
33
33
|
detail: m.full_name,
|
34
34
|
documentation: "#{m.remote_path}##{m.lineno}",
|
35
|
-
kind: Protocol::Constant::CompletionItemKind::MODULE
|
35
|
+
kind: Protocol::Constant::CompletionItemKind::MODULE,
|
36
36
|
)
|
37
37
|
}).uniq(&:label)
|
38
38
|
end
|
39
39
|
|
40
40
|
private
|
41
41
|
|
42
|
-
|
42
|
+
attr_reader :uri, :line, :character, :project
|
43
43
|
end
|
44
44
|
end
|
45
45
|
end
|
@@ -28,16 +28,16 @@ module LanguageServer
|
|
28
28
|
Protocol::Interface::CompletionItem.new(
|
29
29
|
label: method_name,
|
30
30
|
detail: description,
|
31
|
-
kind: Protocol::Constant::CompletionItemKind::METHOD
|
31
|
+
kind: Protocol::Constant::CompletionItemKind::METHOD,
|
32
32
|
)
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
36
|
private
|
37
37
|
|
38
|
-
|
39
|
-
|
40
|
-
|
38
|
+
def source
|
39
|
+
@file_store.read_remote_uri(@uri)
|
40
|
+
end
|
41
41
|
end
|
42
42
|
end
|
43
43
|
end
|
@@ -15,20 +15,20 @@ module LanguageServer
|
|
15
15
|
range: Protocol::Interface::Range.new(
|
16
16
|
start: Protocol::Interface::Position.new(
|
17
17
|
line: n.lines.begin,
|
18
|
-
character: 0
|
18
|
+
character: 0,
|
19
19
|
),
|
20
20
|
end: Protocol::Interface::Position.new(
|
21
21
|
line: n.lines.end,
|
22
|
-
character: 0
|
23
|
-
)
|
24
|
-
)
|
22
|
+
character: 0,
|
23
|
+
),
|
24
|
+
),
|
25
25
|
)
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
29
|
private
|
30
30
|
|
31
|
-
|
31
|
+
attr_reader :uri, :line, :character, :project
|
32
32
|
end
|
33
33
|
end
|
34
34
|
end
|
@@ -64,36 +64,36 @@ module LanguageServer
|
|
64
64
|
|
65
65
|
def each(&block)
|
66
66
|
all_paths.each do |path|
|
67
|
-
|
67
|
+
yield(read(path), path)
|
68
68
|
end
|
69
69
|
end
|
70
70
|
|
71
71
|
private
|
72
72
|
|
73
|
-
|
73
|
+
attr_reader :load_paths, :remote_root, :local_root
|
74
74
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
75
|
+
def all_paths
|
76
|
+
(cache_store.keys + load_paths.flat_map { |path|
|
77
|
+
Dir.glob(File.join(path, "**", "*.rb"))
|
78
|
+
}.map { |path|
|
79
|
+
FilePath.new(local_root: local_root, remote_root: remote_root, local_path: path)
|
80
|
+
}).uniq
|
81
|
+
end
|
82
82
|
|
83
|
-
|
84
|
-
|
85
|
-
|
83
|
+
def exists_on_cache?(path)
|
84
|
+
cache_store.has_key?(path)
|
85
|
+
end
|
86
86
|
|
87
|
-
|
88
|
-
|
89
|
-
|
87
|
+
def read_from_cache(path)
|
88
|
+
cache_store[path]
|
89
|
+
end
|
90
90
|
|
91
|
-
|
92
|
-
|
93
|
-
|
91
|
+
def read_from_local(path)
|
92
|
+
File.read(path.local_path)
|
93
|
+
end
|
94
94
|
|
95
|
-
|
96
|
-
|
97
|
-
|
95
|
+
def cache_store
|
96
|
+
@cache_store ||= {}
|
97
|
+
end
|
98
98
|
end
|
99
99
|
end
|
@@ -1,22 +1,21 @@
|
|
1
1
|
begin
|
2
|
-
require
|
2
|
+
require "rubocop"
|
3
3
|
rescue LoadError
|
4
4
|
end
|
5
5
|
|
6
6
|
module LanguageServer
|
7
7
|
module Linter
|
8
8
|
class Rubocop
|
9
|
-
def initialize(source, config_path="")
|
9
|
+
def initialize(source, config_path = "")
|
10
10
|
@source = source
|
11
11
|
@config_path = config_path
|
12
12
|
end
|
13
13
|
|
14
|
-
|
15
14
|
def call
|
16
15
|
return [] unless defined? ::RuboCop
|
17
16
|
args = []
|
18
|
-
args += ["--config", @config_path] if @config_path !=
|
19
|
-
args += ["--format", "json","--stdin", "lsp_buffer.rb"]
|
17
|
+
args += ["--config", @config_path] if @config_path != ""
|
18
|
+
args += ["--format", "json", "--stdin", "lsp_buffer.rb"]
|
20
19
|
o = nil
|
21
20
|
begin
|
22
21
|
$stdin = StringIO.new(@source)
|
@@ -32,24 +31,23 @@ module LanguageServer
|
|
32
31
|
$stdout = STDOUT
|
33
32
|
end
|
34
33
|
return [] unless o
|
35
|
-
JSON
|
36
|
-
|
37
|
-
.
|
38
|
-
|
34
|
+
JSON.
|
35
|
+
parse(o)["files"].map { |v| v["offenses"] }.
|
36
|
+
flatten.
|
37
|
+
map { |v| Error.new(line_num: v["location"]["line"].to_i - 1, message: v["message"], type: convert_type(v["severity"])) }
|
39
38
|
end
|
40
39
|
|
41
40
|
private
|
42
41
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
42
|
+
def convert_type(type)
|
43
|
+
case type
|
44
|
+
when "refactor" then "warning"
|
45
|
+
when "convention" then "warning"
|
46
|
+
when "warning" then "warning"
|
47
|
+
when "error" then "error"
|
48
|
+
when "fatal" then "error"
|
49
|
+
end
|
50
50
|
end
|
51
|
-
end
|
52
|
-
|
53
51
|
end
|
54
52
|
end
|
55
53
|
end
|
@@ -3,10 +3,11 @@ require "stringio"
|
|
3
3
|
module LanguageServer
|
4
4
|
module Linter
|
5
5
|
class Error
|
6
|
-
attr_reader :line_num, :message, :type
|
6
|
+
attr_reader :line_num, :characters, :message, :type
|
7
7
|
|
8
|
-
def initialize(line_num:, message:, type:)
|
8
|
+
def initialize(line_num:, characters: 0..0, message:, type:)
|
9
9
|
@line_num = line_num
|
10
|
+
@characters = characters
|
10
11
|
@message = message
|
11
12
|
@type = type
|
12
13
|
end
|
@@ -16,7 +17,7 @@ module LanguageServer
|
|
16
17
|
end
|
17
18
|
|
18
19
|
def ==(other)
|
19
|
-
line_num == other.line_num && message == other.message
|
20
|
+
line_num == other.line_num && characters == other.characters && message == other.message
|
20
21
|
end
|
21
22
|
end
|
22
23
|
|
@@ -26,55 +27,65 @@ module LanguageServer
|
|
26
27
|
end
|
27
28
|
|
28
29
|
def call
|
29
|
-
error_message.scan(/.+:(\d+):\s*(.+?)[,:]\s(.+)/).map do |line_num, type,
|
30
|
-
Error.new(line_num: line_num.to_i - 1, message: message, type: type)
|
30
|
+
error_message.scan(/.+:(\d+):\s*(.+?)[,:]\s(.+)/).map do |line_num, type, message|
|
31
|
+
Error.new(line_num: line_num.to_i - 1, characters: get_characters_from_error_message(error_message, line_num.to_i - 1), message: message, type: type)
|
31
32
|
end
|
32
33
|
end
|
33
34
|
|
34
35
|
private
|
35
36
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
37
|
+
# Since Ruby 2.4, syntax error information is outputted to Exception#message instead of stderr
|
38
|
+
if begin; stderr = $stderr; $stderr = StringIO.new; RubyVM::InstructionSequence.compile("="); rescue SyntaxError => e; e.message != "compile error"; ensure; $stderr = stderr; end
|
39
|
+
def error_message
|
40
|
+
with_verbose do
|
41
|
+
begin
|
42
|
+
capture_stderr { RubyVM::InstructionSequence.compile(@source) }
|
43
|
+
rescue SyntaxError => e
|
44
|
+
e.message
|
45
|
+
end
|
44
46
|
end
|
45
47
|
end
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
48
|
+
else
|
49
|
+
def error_message
|
50
|
+
with_verbose do
|
51
|
+
capture_stderr do
|
52
|
+
begin
|
53
|
+
RubyVM::InstructionSequence.compile(@source)
|
54
|
+
rescue SyntaxError
|
55
|
+
end
|
54
56
|
end
|
55
57
|
end
|
56
58
|
end
|
57
59
|
end
|
58
|
-
end
|
59
60
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
61
|
+
def with_verbose
|
62
|
+
origin = $VERBOSE
|
63
|
+
$VERBOSE = true
|
64
|
+
yield
|
65
|
+
ensure
|
66
|
+
$VERBOSE = origin
|
67
|
+
end
|
67
68
|
|
68
|
-
|
69
|
-
|
70
|
-
|
69
|
+
def capture_stderr
|
70
|
+
origin = $stderr
|
71
|
+
$stderr = StringIO.new
|
71
72
|
|
72
|
-
|
73
|
+
yield
|
73
74
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
75
|
+
$stderr.string
|
76
|
+
ensure
|
77
|
+
$stderr = origin
|
78
|
+
end
|
79
|
+
|
80
|
+
def get_characters_from_error_message(error_message, line_index)
|
81
|
+
error_mark_included_line = error_message.split("\n")[2]
|
82
|
+
|
83
|
+
if !error_mark_included_line.nil? && character_start = error_mark_included_line.index("^")
|
84
|
+
Range.new(character_start, character_start + 1)
|
85
|
+
else
|
86
|
+
Range.new(0, @source.split("\n")[line_index].length - 1)
|
87
|
+
end
|
88
|
+
end
|
78
89
|
end
|
79
90
|
end
|
80
91
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "logger"
|
2
2
|
|
3
3
|
module LanguageServer
|
4
4
|
class << self
|
@@ -8,16 +8,16 @@ module LanguageServer
|
|
8
8
|
end
|
9
9
|
|
10
10
|
class Formatter
|
11
|
-
RESET = "\e[0m"
|
12
|
-
RED = "\e[31m"
|
13
|
-
YELLOW = "\e[33m"
|
11
|
+
RESET = "\e[0m".freeze
|
12
|
+
RED = "\e[31m".freeze
|
13
|
+
YELLOW = "\e[33m".freeze
|
14
14
|
|
15
15
|
def call(severity, *rest)
|
16
16
|
msg = default_message(severity, *rest)
|
17
17
|
case severity
|
18
|
-
when
|
18
|
+
when "ERROR"
|
19
19
|
RED + msg + RESET
|
20
|
-
when
|
20
|
+
when "WARN"
|
21
21
|
YELLOW + msg + RESET
|
22
22
|
else
|
23
23
|
msg
|
@@ -26,12 +26,12 @@ module LanguageServer
|
|
26
26
|
|
27
27
|
private
|
28
28
|
|
29
|
-
|
30
|
-
|
31
|
-
|
29
|
+
def default_message(*args)
|
30
|
+
default_formatter.call(*args)
|
31
|
+
end
|
32
32
|
|
33
|
-
|
34
|
-
|
35
|
-
|
33
|
+
def default_formatter
|
34
|
+
@default_formatter ||= ::Logger::Formatter.new
|
35
|
+
end
|
36
36
|
end
|
37
37
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "language_server/project/parser"
|
2
2
|
|
3
3
|
module LanguageServer
|
4
4
|
class Project
|
@@ -12,11 +12,11 @@ module LanguageServer
|
|
12
12
|
def find_definitions(uri:, line:, character:)
|
13
13
|
result = result_store[file_store.path_from_remote_uri(uri)]
|
14
14
|
|
15
|
-
ref = result.refs.select {|node| node.lines.include?(line) && node.characters.include?(character) }.min_by {|node| node.characters.size }
|
15
|
+
ref = result.refs.select { |node| node.lines.include?(line) && node.characters.include?(character) }.min_by { |node| node.characters.size }
|
16
16
|
|
17
17
|
return [] unless ref
|
18
18
|
|
19
|
-
lazy_modules.select {|n| n.full_name == ref.full_name }.force + lazy_classes.select {|n| n.full_name == ref.full_name }.force
|
19
|
+
lazy_modules.select { |n| n.full_name == ref.full_name }.force + lazy_classes.select { |n| n.full_name == ref.full_name }.force
|
20
20
|
end
|
21
21
|
|
22
22
|
def recalculate_result(uri)
|
@@ -27,58 +27,58 @@ module LanguageServer
|
|
27
27
|
def constants(uri: nil, line: nil, character: nil)
|
28
28
|
node = find_nearest_node(uri: uri, line: line, character: character) if uri && line && character
|
29
29
|
|
30
|
-
lazy_constants.select {|n| n.names[0..-2] == Array(node && node.names).first(n.names.size - 1) }.force
|
30
|
+
lazy_constants.select { |n| n.names[0..-2] == Array(node && node.names).first(n.names.size - 1) }.force
|
31
31
|
end
|
32
32
|
|
33
33
|
def modules(uri: nil, line: nil, character: nil)
|
34
34
|
node = find_nearest_node(uri: uri, line: line, character: character) if uri && line && character
|
35
35
|
|
36
|
-
lazy_modules.select {|n| n.names[0..-2] == Array(node && node.names).first(n.names.size - 1) }.force
|
36
|
+
lazy_modules.select { |n| n.names[0..-2] == Array(node && node.names).first(n.names.size - 1) }.force
|
37
37
|
end
|
38
38
|
|
39
39
|
def classes(uri: nil, line: nil, character: nil)
|
40
40
|
node = find_nearest_node(uri: uri, line: line, character: character) if uri && line && character
|
41
41
|
|
42
|
-
lazy_classes.select {|n| n.names[0..-2] == Array(node && node.names).first(n.names.size - 1) }.force
|
42
|
+
lazy_classes.select { |n| n.names[0..-2] == Array(node && node.names).first(n.names.size - 1) }.force
|
43
43
|
end
|
44
44
|
|
45
45
|
private
|
46
46
|
|
47
|
-
|
47
|
+
attr_reader :file_store, :result_store
|
48
48
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
def lazy_modules
|
54
|
-
result_store.each_value.lazy.flat_map(&:modules)
|
55
|
-
end
|
49
|
+
def lazy_constants
|
50
|
+
result_store.each_value.lazy.flat_map(&:constants)
|
51
|
+
end
|
56
52
|
|
57
|
-
|
58
|
-
|
59
|
-
|
53
|
+
def lazy_modules
|
54
|
+
result_store.each_value.lazy.flat_map(&:modules)
|
55
|
+
end
|
60
56
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
}
|
65
|
-
end
|
57
|
+
def lazy_classes
|
58
|
+
result_store.each_value.lazy.flat_map(&:classes)
|
59
|
+
end
|
66
60
|
|
67
|
-
|
68
|
-
|
61
|
+
def fetch_result
|
62
|
+
file_store.each { |content, path|
|
63
|
+
calculate(content, path)
|
64
|
+
}
|
65
|
+
end
|
69
66
|
|
70
|
-
(
|
71
|
-
|
67
|
+
def find_nearest_node(uri:, line:, character:)
|
68
|
+
result = result_store[file_store.path_from_remote_uri(uri)]
|
72
69
|
|
73
|
-
|
74
|
-
begin
|
75
|
-
result = Parser.parse(content, path)
|
76
|
-
rescue => e
|
77
|
-
LanguageServer.logger.warn("Parse failed (local: #{path.local_path}, remote: #{path.remote_path})")
|
78
|
-
LanguageServer.logger.warn(e)
|
70
|
+
(result.modules + result.classes).select { |node| node.lines.include?(line) }.min_by { |node| node.lines.size }
|
79
71
|
end
|
80
72
|
|
81
|
-
|
82
|
-
|
73
|
+
def calculate(content, path)
|
74
|
+
begin
|
75
|
+
result = Parser.parse(content, path)
|
76
|
+
rescue => e
|
77
|
+
LanguageServer.logger.warn("Parse failed (local: #{path.local_path}, remote: #{path.remote_path})")
|
78
|
+
LanguageServer.logger.warn(e)
|
79
|
+
end
|
80
|
+
|
81
|
+
result_store[path] = result if result
|
82
|
+
end
|
83
83
|
end
|
84
84
|
end
|