Skip to content

Commit 3b10ade

Browse files
committed
Impl adhoc provider
1 parent d0a7ab7 commit 3b10ade

File tree

10 files changed

+478
-24
lines changed

10 files changed

+478
-24
lines changed

language_server.gemspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,5 @@ Gem::Specification.new do |spec|
3333
spec.add_development_dependency "pry-byebug"
3434
spec.add_development_dependency "minitest-power_assert"
3535
spec.add_development_dependency "m"
36+
spec.add_development_dependency "awesome_print"
3637
end

lib/language_server.rb

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
require "language_server/protocol/stdio"
55
require "language_server/linter/ruby_wc"
66
require "language_server/completion_provider/rcodetools"
7+
require "language_server/completion_provider/ad_hoc"
78
require "language_server/file_store"
9+
require "language_server/project"
810

911
require "json"
1012
require "logger"
@@ -18,7 +20,7 @@ def logger
1820
def run
1921
writer = Protocol::Stdio::Writer.new
2022
reader = Protocol::Stdio::Reader.new
21-
file_store = FileStore.new
23+
variables = {}
2224

2325
reader.read do |request|
2426
method = request[:method].to_sym
@@ -30,10 +32,11 @@ def run
3032
}
3133

3234
if subscriber
35+
keys = subscriber.parameters.map(&:last)
3336
result = subscriber.call(
3437
{
35-
request: request, notifier: writer.method(:notify), file_store: file_store
36-
}.select {|k, _| subscriber.parameters.map(&:last).include?(k) }
38+
request: request, notifier: writer.method(:notify), variables: variables
39+
}.merge(variables).select {|k, _| keys.include?(k) }
3740
)
3841

3942
if request[:id]
@@ -54,7 +57,10 @@ def on(method, &callback)
5457
end
5558
end
5659

57-
on :initialize do
60+
on :initialize do |request:, variables:|
61+
variables[:file_store] = FileStore.new(load_paths: $LOAD_PATH, remote_root: request[:params][:rootPath], local_root: Dir.getwd)
62+
variables[:project] = Project.new(variables[:file_store])
63+
5864
Protocol::Interfaces::InitializeResult.new(
5965
capabilities: Protocol::Interfaces::ServerCapabilities.new(
6066
text_document_sync: Protocol::Interfaces::TextDocumentSyncOptions.new(
@@ -72,10 +78,11 @@ def on(method, &callback)
7278
exit
7379
end
7480

75-
on :"textDocument/didChange" do |request:, notifier:, file_store:|
81+
on :"textDocument/didChange" do |request:, notifier:, file_store:, project:|
7682
uri = request[:params][:textDocument][:uri]
7783
text = request[:params][:contentChanges][0][:text]
7884
file_store.cache(uri, text)
85+
project.recalculate_result(uri)
7986

8087
diagnostics = Linter::RubyWC.new(text).call.map do |error|
8188
Protocol::Interfaces::Diagnostic.new(
@@ -103,12 +110,13 @@ def on(method, &callback)
103110
)
104111
end
105112

106-
on :"textDocument/completion" do |request:, file_store:|
113+
on :"textDocument/completion" do |request:, file_store:, project:|
107114
uri = request[:params][:textDocument][:uri]
108-
line, character = request[:params][:position].fetch_values(:line, :character)
115+
line, character = request[:params][:position].fetch_values(:line, :character).map(&:to_i)
109116

110117
[
111-
CompletionProvider::Rcodetools.new(uri: uri, line: line.to_i, character: character.to_i, file_store: file_store)
118+
CompletionProvider::AdHoc.new(uri: uri, line: line, character: character, project: project),
119+
CompletionProvider::Rcodetools.new(uri: uri, line: line, character: character, file_store: file_store)
112120
].flat_map(&:call)
113121
end
114122
end
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
require "language_server/project"
2+
3+
module LanguageServer
4+
module CompletionProvider
5+
class AdHoc
6+
def initialize(uri:, line:, character:, project:)
7+
@uri = uri
8+
@line = line
9+
@character = character
10+
@project = project
11+
end
12+
13+
def call
14+
(project.constants(uri: uri, line: line, character: character).map {|c|
15+
Protocol::Interfaces::CompletionItem.new(
16+
label: c.name,
17+
detail: c.full_name,
18+
documentation: "#{c.remote_path}##{c.lineno}",
19+
kind: Protocol::Constants::CompletionItemKind::ENUM
20+
)
21+
} +
22+
project.classes(uri: uri, line: line, character: character).map {|c|
23+
Protocol::Interfaces::CompletionItem.new(
24+
label: c.name,
25+
detail: c.full_name,
26+
documentation: "#{c.remote_path}##{c.lineno}",
27+
kind: Protocol::Constants::CompletionItemKind::CLASS
28+
)
29+
} +
30+
project.modules(uri: uri, line: line, character: character).map {|m|
31+
Protocol::Interfaces::CompletionItem.new(
32+
label: m.name,
33+
detail: m.full_name,
34+
documentation: "#{m.remote_path}##{m.lineno}",
35+
kind: Protocol::Constants::CompletionItemKind::MODULE
36+
)
37+
}).uniq(&:label)
38+
end
39+
40+
private
41+
42+
attr_reader :uri, :line, :character, :project
43+
end
44+
end
45+
end

lib/language_server/completion_provider/rcodetools.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def call
3838
private
3939

4040
def source
41-
@file_store.read(@uri)
41+
@file_store.read_remote_uri(@uri)
4242
end
4343
end
4444
end

lib/language_server/file_store.rb

Lines changed: 55 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,40 +4,80 @@ module LanguageServer
44
class FileStore
55
include Enumerable
66

7-
def initialize(load_paths = [])
7+
class FilePath
8+
class << self
9+
def from_remote_uri(remote_root:, local_root:, remote_uri:)
10+
new(remote_root: remote_root, local_root: local_root, local_path: URI(remote_uri).path.sub(remote_root, local_root))
11+
end
12+
end
13+
14+
attr_reader :local_root, :remote_root, :local_path
15+
16+
def initialize(remote_root:, local_root:, local_path:)
17+
@remote_root = remote_root
18+
@local_root = local_root
19+
@local_path = local_path
20+
end
21+
22+
def remote_path
23+
@remote_path ||= local_path.sub(local_root, remote_root)
24+
end
25+
26+
def eql?(other)
27+
self.class == other.class && remote_path == other.remote_path
28+
end
29+
30+
def ==(other)
31+
eql?(other)
32+
end
33+
34+
def hash
35+
self.remote_path.hash
36+
end
37+
end
38+
39+
def initialize(load_paths: [], remote_root: Dir.getwd, local_root: Dir.getwd)
840
@load_paths = load_paths
41+
@remote_root = remote_root
42+
@local_root = local_root
943
end
1044

11-
def cache(uri_or_path, content)
12-
cache_store[to_path(uri_or_path)] = content
45+
def cache(remote_uri, content)
46+
cache_store[path_from_remote_uri(remote_uri)] = content
1347
end
1448

15-
def read(uri_or_path)
16-
path = to_path(uri_or_path)
49+
def path_from_remote_uri(remote_uri)
50+
FilePath.from_remote_uri(local_root: local_root, remote_root: remote_root, remote_uri: remote_uri)
51+
end
1752

53+
def read(path)
1854
if exists_on_cache?(path)
1955
read_from_cache(path)
2056
else
2157
read_from_local(path)
2258
end
2359
end
2460

25-
def each(&block)
26-
all_file_paths.each do |path|
27-
block.call(read(path))
28-
end
61+
def read_remote_uri(remote_uri)
62+
read(path_from_remote_uri(remote_uri))
2963
end
3064

31-
def all_file_paths
32-
cache_store.keys + load_paths.flat_map {|path| Dir.glob(File.join(path, "**", "*.rb")) }
65+
def each(&block)
66+
all_paths.each do |path|
67+
block.call(read(path), path)
68+
end
3369
end
3470

3571
private
3672

37-
attr_reader :load_paths
73+
attr_reader :load_paths, :remote_root, :local_root
3874

39-
def to_path(uri_or_path)
40-
URI(uri_or_path).path
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
4181
end
4282

4383
def exists_on_cache?(path)
@@ -49,7 +89,7 @@ def read_from_cache(path)
4989
end
5090

5191
def read_from_local(path)
52-
File.read(path)
92+
File.read(path.local_path)
5393
end
5494

5595
def cache_store

lib/language_server/project.rb

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
require 'language_server/project/parser'
2+
3+
module LanguageServer
4+
class Project
5+
def initialize(file_store)
6+
@file_store = file_store
7+
@result_store = {}
8+
9+
fetch_result
10+
end
11+
12+
def recalculate_result(uri)
13+
path = file_store.path_from_remote_uri(uri)
14+
result_store[path] = calculate(file_store.read(path), path)
15+
end
16+
17+
def constants(uri: nil, line: nil, character: nil)
18+
node = find_nearest_node(uri: uri, line: line, character: character) if uri && line && character
19+
20+
lazy_constants.select {|n| n.names[0..-2] == Array(node && node.names).first(n.names.size - 1) }.force
21+
end
22+
23+
def modules(uri: nil, line: nil, character: nil)
24+
node = find_nearest_node(uri: uri, line: line, character: character) if uri && line && character
25+
26+
lazy_modules.select {|n| n.names[0..-2] == Array(node && node.names).first(n.names.size - 1) }.force
27+
end
28+
29+
def classes(uri: nil, line: nil, character: nil)
30+
node = find_nearest_node(uri: uri, line: line, character: character) if uri && line && character
31+
32+
lazy_classes.select {|n| n.names[0..-2] == Array(node && node.names).first(n.names.size - 1) }.force
33+
end
34+
35+
private
36+
37+
attr_reader :file_store, :result_store
38+
39+
def lazy_constants
40+
result_store.each_value.lazy.flat_map(&:constants)
41+
end
42+
43+
def lazy_modules
44+
result_store.each_value.lazy.flat_map(&:modules)
45+
end
46+
47+
def lazy_classes
48+
result_store.each_value.lazy.flat_map(&:classes)
49+
end
50+
51+
def fetch_result
52+
file_store.each {|content, path|
53+
result_store[path] = calculate(content, path)
54+
}
55+
end
56+
57+
def find_nearest_node(uri:, line:, character:)
58+
result = result_store[file_store.path_from_remote_uri(uri)]
59+
60+
(result.modules + result.classes).select {|node| node.lines.include?(line) }.min_by {|node| node.lines.size }
61+
end
62+
63+
def calculate(content, path)
64+
Parser.parse(content, path)
65+
end
66+
end
67+
end

0 commit comments

Comments
 (0)