yoda-language-server 0.6.2 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/client/atom/main.js +19 -12
- data/lib/yoda/commands.rb +18 -0
- data/lib/yoda/instrument.rb +15 -3
- data/lib/yoda/server.rb +23 -221
- data/lib/yoda/server/concurrent_writer.rb +16 -0
- data/lib/yoda/server/file_store.rb +57 -0
- data/lib/yoda/server/lifecycle_handler.rb +148 -0
- data/lib/yoda/server/notifier.rb +13 -9
- data/lib/yoda/server/providers.rb +41 -0
- data/lib/yoda/server/providers/base.rb +45 -0
- data/lib/yoda/server/providers/completion.rb +86 -0
- data/lib/yoda/server/providers/definition.rb +44 -0
- data/lib/yoda/server/providers/hover.rb +47 -0
- data/lib/yoda/server/providers/signature.rb +56 -0
- data/lib/yoda/server/providers/text_document_did_change.rb +19 -0
- data/lib/yoda/server/providers/text_document_did_open.rb +19 -0
- data/lib/yoda/server/providers/text_document_did_save.rb +19 -0
- data/lib/yoda/server/root_handler.rb +134 -0
- data/lib/yoda/server/session.rb +0 -55
- data/lib/yoda/store/project.rb +6 -5
- data/lib/yoda/store/project/cache.rb +3 -4
- data/lib/yoda/store/registry.rb +48 -27
- data/lib/yoda/version.rb +1 -1
- data/yoda-language-server.gemspec +2 -1
- metadata +31 -9
- data/lib/yoda/server/completion_provider.rb +0 -78
- data/lib/yoda/server/definition_provider.rb +0 -36
- data/lib/yoda/server/hover_provider.rb +0 -39
- data/lib/yoda/server/initialization_provider.rb +0 -96
- data/lib/yoda/server/signature_provider.rb +0 -46
@@ -0,0 +1,56 @@
|
|
1
|
+
module Yoda
|
2
|
+
class Server
|
3
|
+
module Providers
|
4
|
+
class Signature < Base
|
5
|
+
def self.provider_method
|
6
|
+
:'textDocument/signatureHelp'
|
7
|
+
end
|
8
|
+
|
9
|
+
# @param params [Hash]
|
10
|
+
def provide(params)
|
11
|
+
calculate(params[:text_document][:uri], params[:position])
|
12
|
+
end
|
13
|
+
|
14
|
+
def timeout
|
15
|
+
10
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
# @params uri [String]
|
21
|
+
# @params position [{Symbol => Integer}]
|
22
|
+
def calculate(uri, position)
|
23
|
+
source = session.file_store.get(uri)
|
24
|
+
location = Parsing::Location.of_language_server_protocol_position(line: position[:line], character: position[:character])
|
25
|
+
cut_source = Parsing::SourceCutter.new(source, location).error_recovered_source
|
26
|
+
|
27
|
+
signature_worker = Evaluation::SignatureDiscovery.new(session.registry, cut_source, location)
|
28
|
+
|
29
|
+
functions = signature_worker.method_candidates
|
30
|
+
create_signature_help(functions)
|
31
|
+
end
|
32
|
+
|
33
|
+
# @param code_objects [Array<Model::FunctionSignatures::Base>]
|
34
|
+
def create_signature_help(functions)
|
35
|
+
signatures = functions.map { |func| Model::Descriptions::FunctionDescription.new(func) }
|
36
|
+
LanguageServer::Protocol::Interface::SignatureHelp.new(
|
37
|
+
signatures: signatures.map { |signature| create_signature_info(signature) },
|
38
|
+
)
|
39
|
+
end
|
40
|
+
|
41
|
+
# @param signature [Evaluation::Descriptions::FunctionDescription]
|
42
|
+
def create_signature_info(signature)
|
43
|
+
LanguageServer::Protocol::Interface::SignatureInformation.new(
|
44
|
+
label: signature.title.to_s,
|
45
|
+
documentation: signature.to_markdown,
|
46
|
+
parameters: signature.parameter_names.map do |parameter|
|
47
|
+
LanguageServer::Protocol::Interface::ParameterInformation.new(
|
48
|
+
label: parameter,
|
49
|
+
)
|
50
|
+
end,
|
51
|
+
)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Yoda
|
2
|
+
class Server
|
3
|
+
module Providers
|
4
|
+
class TextDocumentDidChange < Base
|
5
|
+
def self.provider_method
|
6
|
+
:'textDocument/didChange'
|
7
|
+
end
|
8
|
+
|
9
|
+
def provide(params)
|
10
|
+
uri = params[:text_document][:uri]
|
11
|
+
text = params[:content_changes].first[:text]
|
12
|
+
session.file_store.store(uri, text)
|
13
|
+
|
14
|
+
NO_RESPONSE
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Yoda
|
2
|
+
class Server
|
3
|
+
module Providers
|
4
|
+
class TextDocumentDidOpen < Base
|
5
|
+
def self.provider_method
|
6
|
+
:'textDocument/didOpen'
|
7
|
+
end
|
8
|
+
|
9
|
+
def provide(params)
|
10
|
+
uri = params[:text_document][:uri]
|
11
|
+
text = params[:text_document][:text]
|
12
|
+
session.file_store.store(uri, text)
|
13
|
+
|
14
|
+
NO_RESPONSE
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Yoda
|
2
|
+
class Server
|
3
|
+
module Providers
|
4
|
+
class TextDocumentDidSave < Base
|
5
|
+
def self.provider_method
|
6
|
+
:'textDocument/didSave'
|
7
|
+
end
|
8
|
+
|
9
|
+
def provide(params)
|
10
|
+
uri = params[:text_document][:uri]
|
11
|
+
|
12
|
+
session.reparse_doc(uri)
|
13
|
+
|
14
|
+
NO_RESPONSE
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'concurrent'
|
2
|
+
require 'forwardable'
|
3
|
+
|
4
|
+
module Yoda
|
5
|
+
class Server
|
6
|
+
class RootHandler
|
7
|
+
extend Forwardable
|
8
|
+
NOT_INITIALIZED = :not_initialized
|
9
|
+
NotImplementedMethod = Struct.new(:method_name)
|
10
|
+
|
11
|
+
delegate session: :lifecycle_handler
|
12
|
+
|
13
|
+
# @return [ConcurrentWriter]
|
14
|
+
attr_reader :writer
|
15
|
+
|
16
|
+
# @return [Concurrent::ThreadPoolExecutor]
|
17
|
+
attr_reader :thread_pool
|
18
|
+
|
19
|
+
# @return [Concurrent::ThreadPoolExecutor]
|
20
|
+
def self.default_thread_pool
|
21
|
+
Concurrent.global_fast_executor
|
22
|
+
end
|
23
|
+
|
24
|
+
# @param writer [ConcurrentWriter]
|
25
|
+
# @param thread_pool [Concurrent::ThreadPoolExecutor]
|
26
|
+
def initialize(writer:, thread_pool: nil)
|
27
|
+
@thread_pool = thread_pool || self.class.default_thread_pool
|
28
|
+
@writer = writer
|
29
|
+
@future_map = Concurrent::Map.new
|
30
|
+
end
|
31
|
+
|
32
|
+
# @param id [String]
|
33
|
+
# @param method [Symbol]
|
34
|
+
# @param params [Hash]
|
35
|
+
# @return [Concurrent::Future, nil]
|
36
|
+
def handle(id:, method:, params:)
|
37
|
+
if lifecycle_handler.handle?(method)
|
38
|
+
return write_response(id, lifecycle_handler.handle(method: method, params: params))
|
39
|
+
end
|
40
|
+
|
41
|
+
return write_response(id, build_error_response(NOT_INITIALIZED)) unless session
|
42
|
+
|
43
|
+
if provider = Providers.build_provider(notifier: notifier, session: session, method: method)
|
44
|
+
provide_async(provider: provider, id: id, method: method, params: params)
|
45
|
+
else
|
46
|
+
write_response(id, build_error_response(NotImplementedMethod.new(method)))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# @return [Notifier]
|
51
|
+
def notifier
|
52
|
+
@notifier ||= Notifier.new(writer)
|
53
|
+
end
|
54
|
+
|
55
|
+
# @param id [String]
|
56
|
+
def cancel_request(id)
|
57
|
+
future_map[id]&.cancel
|
58
|
+
end
|
59
|
+
|
60
|
+
def cancel_all_requests
|
61
|
+
future_map.each_value { |future| future&.cancel }
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
# @return [Concurrent::Map{ String => Future }]
|
67
|
+
attr_reader :future_map
|
68
|
+
|
69
|
+
# @return [LifecycleHandler]
|
70
|
+
def lifecycle_handler
|
71
|
+
@lifecycle_handler ||= LifecycleHandler.new(self)
|
72
|
+
end
|
73
|
+
|
74
|
+
# @return [Concurrent::Future]
|
75
|
+
def provide_async(provider:, id:, method:, params:)
|
76
|
+
future = Concurrent::Future.new(executor: thread_pool) do
|
77
|
+
notifier.busy(type: method, id: id) { provider.provide(params) }
|
78
|
+
end
|
79
|
+
future.add_observer do |_time, value, reason|
|
80
|
+
begin
|
81
|
+
reason ? write_response(id, build_error_response(reason)) : write_response(id, value)
|
82
|
+
ensure
|
83
|
+
future_map.delete(id)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
future_map.put_if_absent(id, future)
|
87
|
+
Concurrent::ScheduledTask.execute(provider.timeout, executor: thread_pool) { future.cancel } if provider.timeout
|
88
|
+
|
89
|
+
future.execute
|
90
|
+
future
|
91
|
+
end
|
92
|
+
|
93
|
+
# @param id [Object]
|
94
|
+
# @param result [Object]
|
95
|
+
# @return [nil]
|
96
|
+
def write_response(id, result)
|
97
|
+
return if result == NO_RESPONSE
|
98
|
+
if result.is_a?(LanguageServer::Protocol::Interface::ResponseError)
|
99
|
+
writer.write(id: id, error: result)
|
100
|
+
else
|
101
|
+
writer.write(id: id, result: result)
|
102
|
+
end
|
103
|
+
|
104
|
+
nil
|
105
|
+
end
|
106
|
+
|
107
|
+
# @param reason [Exception, Symbol, Struct]
|
108
|
+
def build_error_response(reason)
|
109
|
+
case reason
|
110
|
+
when Concurrent::CancelledOperationError
|
111
|
+
LanguageServer::Protocol::Interface::ResponseError.new(
|
112
|
+
code: LanguageServer::Protocol::Constant::ErrorCodes::REQUEST_CANCELLED,
|
113
|
+
message: 'Request is canceled',
|
114
|
+
)
|
115
|
+
when NOT_INITIALIZED
|
116
|
+
LanguageServer::Protocol::Interface::ResponseError.new(
|
117
|
+
code: LanguageServer::Protocol::Constant::ErrorCodes::SERVER_NOT_INITIALIZED,
|
118
|
+
message: "Server is not initialized",
|
119
|
+
)
|
120
|
+
when NotImplementedMethod
|
121
|
+
LanguageServer::Protocol::Interface::ResponseError.new(
|
122
|
+
code: LanguageServer::Protocol::Constant::ErrorCodes::METHOD_NOT_FOUND,
|
123
|
+
message: "Method (#{reason.method_name}) is not implemented",
|
124
|
+
)
|
125
|
+
else
|
126
|
+
LanguageServer::Protocol::Interface::ResponseError.new(
|
127
|
+
code: LanguageServer::Protocol::Constant::ErrorCodes::INTERNAL_ERROR,
|
128
|
+
message: reason.respond_to?(:message) ? message : 'Internal error',
|
129
|
+
)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
data/lib/yoda/server/session.rb
CHANGED
@@ -12,14 +12,11 @@ module Yoda
|
|
12
12
|
# @return [Store::Project]
|
13
13
|
attr_reader :project
|
14
14
|
|
15
|
-
attr_accessor :client_initialized
|
16
|
-
|
17
15
|
# @param root_uri [String] an uri expression of project root path
|
18
16
|
def initialize(root_uri)
|
19
17
|
@root_uri = root_uri
|
20
18
|
@file_store = FileStore.new
|
21
19
|
@project = Store::Project.new(root_path)
|
22
|
-
@client_initialized = false
|
23
20
|
end
|
24
21
|
|
25
22
|
def root_path
|
@@ -48,58 +45,6 @@ module Yoda
|
|
48
45
|
path = FileStore.path_of_uri(uri)
|
49
46
|
project.read_source(path)
|
50
47
|
end
|
51
|
-
|
52
|
-
class FileStore
|
53
|
-
def initialize
|
54
|
-
@cache = {}
|
55
|
-
end
|
56
|
-
|
57
|
-
# @param uri_string [String]
|
58
|
-
# @return [String, nil]
|
59
|
-
def get(uri_string)
|
60
|
-
@cache[uri_string]
|
61
|
-
end
|
62
|
-
|
63
|
-
# @param uri_string [String]
|
64
|
-
# @param text [String]
|
65
|
-
def store(uri_string, text)
|
66
|
-
return unless program_file_uri?(uri_string)
|
67
|
-
@cache[uri_string] = text
|
68
|
-
end
|
69
|
-
|
70
|
-
# @param uri_string [String]
|
71
|
-
def load(uri_string)
|
72
|
-
store(uri_string, read(uri_string))
|
73
|
-
end
|
74
|
-
|
75
|
-
# @param uri_string [String]
|
76
|
-
def read(uri_string)
|
77
|
-
path = self.class.path_of_uri(uri_string)
|
78
|
-
fail ArgumentError unless path
|
79
|
-
File.read(path)
|
80
|
-
end
|
81
|
-
|
82
|
-
# @param path [String]
|
83
|
-
def self.uri_of_path(path)
|
84
|
-
"file://#{File.expand_path(path)}"
|
85
|
-
end
|
86
|
-
|
87
|
-
# @param uri_string [String]
|
88
|
-
def self.path_of_uri(uri_string)
|
89
|
-
uri = URI.parse(uri_string)
|
90
|
-
return nil unless uri.scheme == 'file'
|
91
|
-
uri.path
|
92
|
-
rescue URI::InvalidURIError
|
93
|
-
nil
|
94
|
-
end
|
95
|
-
|
96
|
-
# @param uri_string [String]
|
97
|
-
def program_file_uri?(uri_string)
|
98
|
-
%w(.c .rb).include?(File.extname(URI.parse(uri_string).path))
|
99
|
-
rescue URI::InvalidURIError => _e
|
100
|
-
false
|
101
|
-
end
|
102
|
-
end
|
103
48
|
end
|
104
49
|
end
|
105
50
|
end
|
data/lib/yoda/store/project.rb
CHANGED
@@ -6,10 +6,10 @@ module Yoda
|
|
6
6
|
require 'yoda/store/project/cache'
|
7
7
|
require 'yoda/store/project/library_doc_loader'
|
8
8
|
|
9
|
-
# @
|
9
|
+
# @return [String]
|
10
10
|
attr_reader :root_path
|
11
11
|
|
12
|
-
# @
|
12
|
+
# @return [Registry, nil]
|
13
13
|
attr_reader :registry
|
14
14
|
|
15
15
|
# @param root_path [String]
|
@@ -17,17 +17,18 @@ module Yoda
|
|
17
17
|
fail ArgumentError, root_path unless root_path.is_a?(String)
|
18
18
|
|
19
19
|
@root_path = File.absolute_path(root_path)
|
20
|
-
@registry = Registry.new
|
21
20
|
end
|
22
21
|
|
23
22
|
def setup
|
23
|
+
return if registry
|
24
24
|
make_dir
|
25
|
-
cache.
|
25
|
+
@registry = cache.prepare_registry
|
26
26
|
end
|
27
27
|
|
28
|
+
# Delete all data from registry
|
28
29
|
def clear
|
29
30
|
setup
|
30
|
-
registry.
|
31
|
+
registry.clear
|
31
32
|
end
|
32
33
|
|
33
34
|
# @return [Array<BaseError>]
|
@@ -45,11 +45,10 @@ module Yoda
|
|
45
45
|
File.exist?(cache_path)
|
46
46
|
end
|
47
47
|
|
48
|
-
# @
|
49
|
-
def
|
50
|
-
return if registry.adapter
|
48
|
+
# @return [Registry]
|
49
|
+
def prepare_registry
|
51
50
|
make_cache_dir
|
52
|
-
|
51
|
+
Registry.new(Adapters.default_adapter_class.for(cache_path))
|
53
52
|
end
|
54
53
|
|
55
54
|
# @return [String]
|
data/lib/yoda/store/registry.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'concurrent'
|
1
2
|
require 'yard'
|
2
3
|
|
3
4
|
module Yoda
|
@@ -6,15 +7,6 @@ module Yoda
|
|
6
7
|
# @note This number must be updated when breaking change is added.
|
7
8
|
REGISTRY_VERSION = 1
|
8
9
|
|
9
|
-
# @return [Adapters::LmdbAdapter, nil]
|
10
|
-
attr_reader :adapter
|
11
|
-
|
12
|
-
# @param adapter [Adapters::LmdbAdapter]
|
13
|
-
attr_writer :adapter
|
14
|
-
|
15
|
-
# @return [Objects::PatchSet]
|
16
|
-
attr_reader :patch_set
|
17
|
-
|
18
10
|
PROJECT_STATUS_KEY = '%project_status'
|
19
11
|
|
20
12
|
def initialize(adapter = nil)
|
@@ -24,54 +16,83 @@ module Yoda
|
|
24
16
|
|
25
17
|
# @return [Objects::ProjectStatus, nil]
|
26
18
|
def project_status
|
27
|
-
|
19
|
+
lock.with_read_lock do
|
20
|
+
adapter&.exist?(PROJECT_STATUS_KEY) && adapter.get(PROJECT_STATUS_KEY)
|
21
|
+
end
|
28
22
|
end
|
29
23
|
|
30
24
|
# @param new_project_status [Objects::ProjectStatus]
|
31
25
|
def save_project_status(new_project_status)
|
32
|
-
|
26
|
+
lock.with_write_lock do
|
27
|
+
adapter.put(PROJECT_STATUS_KEY, new_project_status)
|
28
|
+
end
|
33
29
|
end
|
34
30
|
|
35
31
|
# @param path [String]
|
36
32
|
# @return [Objects::Base, nil]
|
37
33
|
def find(path)
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
34
|
+
lock.with_read_lock do
|
35
|
+
if adapter&.exist?(path)
|
36
|
+
patch_set.patch(adapter.get(path))
|
37
|
+
else
|
38
|
+
patch_set.find(path)
|
39
|
+
end
|
42
40
|
end
|
43
41
|
end
|
44
42
|
|
45
43
|
# @param patch [Patch]
|
46
44
|
def add_patch(patch)
|
47
|
-
|
45
|
+
lock.with_write_lock do
|
46
|
+
patch_set.register(patch)
|
47
|
+
end
|
48
48
|
end
|
49
49
|
|
50
50
|
# @param path [String]
|
51
51
|
# @return [true, false]
|
52
52
|
def has_key?(path)
|
53
|
-
|
53
|
+
lock.with_read_lock do
|
54
|
+
adapter&.exists?(path) || patch_set.has_key?(path)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def clear
|
59
|
+
lock.with_write_lock do
|
60
|
+
adapter.clear
|
61
|
+
end
|
54
62
|
end
|
55
63
|
|
56
64
|
# Store patch set data to the database.
|
57
65
|
# old data in the database are discarded.
|
58
66
|
def compress_and_save
|
59
67
|
return unless adapter
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
68
|
+
lock.with_write_lock do
|
69
|
+
el_keys = patch_set.keys
|
70
|
+
progress = Instrument::Progress.new(el_keys.length) { |length:, index:| Instrument.instance.registry_dump(index: index, length: length) }
|
71
|
+
|
72
|
+
data = Enumerator.new do |yielder|
|
73
|
+
el_keys.each { |key| yielder << [key, patch_set.find(key)] }
|
74
|
+
end
|
75
|
+
|
76
|
+
adapter.batch_write(data, progress)
|
77
|
+
adapter.sync
|
78
|
+
Logger.info "saved #{el_keys.length} keys."
|
79
|
+
@patch_set = Objects::PatchSet.new
|
65
80
|
end
|
66
|
-
|
67
|
-
adapter.batch_write(data, progress)
|
68
|
-
adapter.sync
|
69
|
-
Logger.info "saved #{el_keys.length} keys."
|
70
|
-
@patch_set = Objects::PatchSet.new
|
71
81
|
end
|
72
82
|
|
73
83
|
private
|
74
84
|
|
85
|
+
# @return [Adapters::LmdbAdapter, nil]
|
86
|
+
attr_reader :adapter
|
87
|
+
|
88
|
+
# @return [Objects::PatchSet]
|
89
|
+
attr_reader :patch_set
|
90
|
+
|
91
|
+
# @return [Concurrent::ReentrantReadWriteLock]
|
92
|
+
def lock
|
93
|
+
@lock ||= Concurrent::ReentrantReadWriteLock.new
|
94
|
+
end
|
95
|
+
|
75
96
|
def keys
|
76
97
|
Set.new(adapter&.keys.map(&:to_s) || []).union(patch_set.keys)
|
77
98
|
end
|