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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3c06ab67892292a50ff422a252b794cf49f38dad60a8b32e7faf14936a2b648e
|
4
|
+
data.tar.gz: 20546678432670fae9b613a6d55e23f6ee3b2c544d6a6b62ff3ff80d6bd3e376
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0eac21b1b1b73f3d3864c2cdb37c2f8df39c9be6841518b59282d5b89c8b64d54972c9c8b9318d35273250c9c740465d7c2156098e0629ee0df831a05ad41e2a
|
7
|
+
data.tar.gz: 9b9991e147783643687d6c259c45c9ab45cfa009df5a60a23f1dce36571c5dc9a61d8a12bb70d57a79f30bf2288ed24933ea84e4a54c0df583b820ed54357cc6
|
data/client/atom/main.js
CHANGED
@@ -3,10 +3,10 @@ const { spawn } = require('child_process')
|
|
3
3
|
const { resolve } = require('path')
|
4
4
|
|
5
5
|
const busyMessages = {
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
'textDocument/hover': 'Preparing hover help',
|
7
|
+
'textDocument/signatureHelp': 'Preparing signature help',
|
8
|
+
'textDocument/completion': 'Completing',
|
9
|
+
'textDocument/definition': 'Finding definition',
|
10
10
|
};
|
11
11
|
|
12
12
|
class YodaClient extends AutoLanguageClient {
|
@@ -57,10 +57,10 @@ class YodaClient extends AutoLanguageClient {
|
|
57
57
|
switch (eventBody.type) {
|
58
58
|
case 'initialization':
|
59
59
|
return this.handleInitializationEvent(eventBody);
|
60
|
-
case '
|
61
|
-
case '
|
62
|
-
case '
|
63
|
-
case '
|
60
|
+
case 'textDocument/hover':
|
61
|
+
case 'textDocument/completion':
|
62
|
+
case 'textDocument/signatureHelp':
|
63
|
+
case 'textDocument/completion':
|
64
64
|
return this.handleBusyEvent(eventBody.type, eventBody);
|
65
65
|
default:
|
66
66
|
}
|
@@ -74,17 +74,24 @@ class YodaClient extends AutoLanguageClient {
|
|
74
74
|
}
|
75
75
|
}
|
76
76
|
|
77
|
-
handleBusyEvent(handlerName, { phase,
|
77
|
+
handleBusyEvent(handlerName, { phase, id }) {
|
78
78
|
switch (phase) {
|
79
79
|
case 'begin':
|
80
80
|
if (!this.busyHandlers[handlerName]) {
|
81
|
-
this.busyHandlers[handlerName] =
|
81
|
+
this.busyHandlers[handlerName] = {
|
82
|
+
handler: this.busySignalService.reportBusy("(Yoda) " + (busyMessages[handlerName] || 'Processing')),
|
83
|
+
ids: new Set([]),
|
84
|
+
};
|
85
|
+
if (id) { this.busyHandlers[handlerName].ids.add(id); }
|
82
86
|
}
|
83
87
|
break;
|
84
88
|
case 'end':
|
85
89
|
if (this.busyHandlers[handlerName]) {
|
86
|
-
this.busyHandlers[handlerName].
|
87
|
-
this.busyHandlers[handlerName]
|
90
|
+
if (id) { this.busyHandlers[handlerName].ids.delete(id); }
|
91
|
+
if (!this.busyHandlers[handlerName].ids.size) {
|
92
|
+
this.busyHandlers[handlerName].handler.dispose();
|
93
|
+
this.busyHandlers[handlerName] = null;
|
94
|
+
}
|
88
95
|
}
|
89
96
|
break;
|
90
97
|
}
|
data/lib/yoda/commands.rb
CHANGED
@@ -11,6 +11,7 @@ module Yoda
|
|
11
11
|
|
12
12
|
class Top < Thor
|
13
13
|
class_option :log_level, type: :string, desc: 'Set log level (debug info warn error fatal)'
|
14
|
+
class_option :profile, type: :boolean, desc: 'Set log level (debug info warn error fatal)'
|
14
15
|
|
15
16
|
desc 'setup', 'Setup indexes for current Ruby version and project gems'
|
16
17
|
option :force_build, type: :boolean, desc: "If enabled, (re)build current project's index forcibly"
|
@@ -41,11 +42,28 @@ module Yoda
|
|
41
42
|
|
42
43
|
def process_class_options
|
43
44
|
set_log_level
|
45
|
+
use_profiler_if_enabled
|
44
46
|
end
|
45
47
|
|
46
48
|
def set_log_level
|
47
49
|
Yoda::Logger.log_level = options[:log_level].to_sym if options[:log_level]
|
48
50
|
end
|
51
|
+
|
52
|
+
def use_profiler_if_enabled
|
53
|
+
if options[:profile]
|
54
|
+
require 'stackprof'
|
55
|
+
require 'securerandom'
|
56
|
+
Logger.info('Enabled profiler')
|
57
|
+
StackProf.start(mode: :wall, raw: true)
|
58
|
+
|
59
|
+
at_exit do
|
60
|
+
StackProf.stop
|
61
|
+
tmpfile_path = File.expand_path(SecureRandom.hex(12), Dir.tmpdir)
|
62
|
+
StackProf.results(tmpfile_path)
|
63
|
+
Logger.fatal("Dumped file to: #{tmpfile_path}")
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
49
67
|
end
|
50
68
|
end
|
51
69
|
end
|
data/lib/yoda/instrument.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'concurrent'
|
2
|
+
|
1
3
|
module Yoda
|
2
4
|
class Instrument
|
3
5
|
class Subscription
|
@@ -57,9 +59,19 @@ module Yoda
|
|
57
59
|
# @return [Array<Subscription>]
|
58
60
|
attr_reader :subscriptions
|
59
61
|
|
60
|
-
|
61
|
-
|
62
|
-
@
|
62
|
+
class << self
|
63
|
+
# Returns Instrument instance (thread local).
|
64
|
+
# @return [Instrument]
|
65
|
+
def instance
|
66
|
+
local.value
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
# @return [Concurrent::ThreadLocalVar<Instrument>]
|
72
|
+
def local
|
73
|
+
@local ||= Concurrent::ThreadLocalVar.new { Instrument.new }
|
74
|
+
end
|
63
75
|
end
|
64
76
|
|
65
77
|
def initialize
|
data/lib/yoda/server.rb
CHANGED
@@ -3,67 +3,41 @@ require 'securerandom'
|
|
3
3
|
|
4
4
|
module Yoda
|
5
5
|
class Server
|
6
|
+
require 'yoda/server/session'
|
7
|
+
require 'yoda/server/file_store'
|
8
|
+
require 'yoda/server/concurrent_writer'
|
6
9
|
require 'yoda/server/notifier'
|
7
|
-
require 'yoda/server/
|
8
|
-
require 'yoda/server/
|
9
|
-
require 'yoda/server/
|
10
|
-
require 'yoda/server/definition_provider'
|
11
|
-
require 'yoda/server/initialization_provider'
|
10
|
+
require 'yoda/server/providers'
|
11
|
+
require 'yoda/server/root_handler'
|
12
|
+
require 'yoda/server/lifecycle_handler'
|
12
13
|
require 'yoda/server/deserializer'
|
13
|
-
require 'yoda/server/session'
|
14
|
-
|
15
|
-
LSP = ::LanguageServer::Protocol
|
16
14
|
|
17
|
-
|
18
|
-
Deserializer.new.deserialize(hash || {})
|
19
|
-
end
|
20
|
-
|
21
|
-
# @type ::LanguageServer::Protocol::Transport::Stdio::Reader
|
15
|
+
# @return [::LanguageServer::Protocol::Transport::Stdio::Reader]
|
22
16
|
attr_reader :reader
|
23
17
|
|
24
|
-
# @
|
18
|
+
# @return [ConcurrentWriter]
|
25
19
|
attr_reader :writer
|
26
20
|
|
27
|
-
# @return [
|
28
|
-
attr_reader :
|
29
|
-
|
30
|
-
# @type CompletionProvider
|
31
|
-
attr_reader :completion_provider
|
32
|
-
|
33
|
-
# @type HoverProvider
|
34
|
-
attr_reader :hover_provider
|
35
|
-
|
36
|
-
# @type SignatureProvider
|
37
|
-
attr_reader :signature_provider
|
21
|
+
# @return [RootHandler]
|
22
|
+
attr_reader :root_handler
|
38
23
|
|
39
|
-
#
|
40
|
-
|
24
|
+
# Use this value as return value for notification handling
|
25
|
+
NO_RESPONSE = :no_response
|
41
26
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
@
|
48
|
-
@
|
49
|
-
|
50
|
-
|
51
|
-
# @return [Notifier]
|
52
|
-
def notifier
|
53
|
-
@notifier ||= Notifier.new(self)
|
27
|
+
def initialize(
|
28
|
+
reader: LanguageServer::Protocol::Transport::Stdio::Reader.new,
|
29
|
+
writer: LanguageServer::Protocol::Transport::Stdio::Writer.new,
|
30
|
+
root_handler_class: RootHandler
|
31
|
+
)
|
32
|
+
@reader = reader
|
33
|
+
@writer = ConcurrentWriter.new(writer)
|
34
|
+
@root_handler = RootHandler.new(writer: @writer)
|
54
35
|
end
|
55
36
|
|
56
37
|
def run
|
57
38
|
reader.read do |request|
|
58
39
|
begin
|
59
|
-
|
60
|
-
if result.is_a?(LanguageServer::Protocol::Interface::ResponseError)
|
61
|
-
send_error(id: request[:id], error: result)
|
62
|
-
else
|
63
|
-
send_response(id: request[:id], result: result)
|
64
|
-
end
|
65
|
-
end
|
66
|
-
process_after_notifications if session&.client_initialized
|
40
|
+
root_handler.handle(id: request[:id], method: request[:method].to_sym, params: deserialize(request[:params]))
|
67
41
|
rescue StandardError => ex
|
68
42
|
Logger.warn ex
|
69
43
|
Logger.warn ex.backtrace
|
@@ -71,180 +45,8 @@ module Yoda
|
|
71
45
|
end
|
72
46
|
end
|
73
47
|
|
74
|
-
def
|
75
|
-
|
76
|
-
send_notification(**notification)
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
# @param method [String]
|
81
|
-
# @param params [Object]
|
82
|
-
def send_notification(method:, params:)
|
83
|
-
writer.write(method: method, params: params)
|
84
|
-
end
|
85
|
-
|
86
|
-
# @param id [String]
|
87
|
-
# @param result [Object]
|
88
|
-
def send_response(id:, result:)
|
89
|
-
writer.write(id: id, result: result)
|
90
|
-
end
|
91
|
-
|
92
|
-
# @param method [String]
|
93
|
-
# @param error [Object]
|
94
|
-
def send_error(id:, error:)
|
95
|
-
writer.write(id: id, error: error)
|
96
|
-
end
|
97
|
-
|
98
|
-
def callback(request)
|
99
|
-
if method_name = resolve(request_registrations, request[:method])
|
100
|
-
send(method_name, self.class.deserialize(request[:params] || {}))
|
101
|
-
elsif method_name = resolve(notification_registrations, request[:method])
|
102
|
-
send(method_name, self.class.deserialize(request[:params] || {}))
|
103
|
-
nil
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
# @param hash [Hash]
|
108
|
-
# @param key [String, Symbol]
|
109
|
-
# @return [Symbol, nil]
|
110
|
-
def resolve(hash, key)
|
111
|
-
resolved = key.to_s.split('/').reduce(hash) do |scope, key|
|
112
|
-
(scope || {})[key.to_sym]
|
113
|
-
end
|
114
|
-
resolved.is_a?(Symbol) && resolved
|
115
|
-
end
|
116
|
-
|
117
|
-
def request_registrations
|
118
|
-
{
|
119
|
-
initialize: :handle_initialize,
|
120
|
-
shutdown: :handle_shutdown,
|
121
|
-
textDocument: {
|
122
|
-
completion: :handle_text_document_completion,
|
123
|
-
hover: :handle_text_document_hover,
|
124
|
-
signatureHelp: :handle_text_document_signature_help,
|
125
|
-
definition: :handle_text_document_definition,
|
126
|
-
},
|
127
|
-
}
|
128
|
-
end
|
129
|
-
|
130
|
-
def notification_registrations
|
131
|
-
{
|
132
|
-
initialized: :handle_initialized,
|
133
|
-
exit: :handle_exit,
|
134
|
-
textDocument: {
|
135
|
-
didChange: :handle_text_document_did_change,
|
136
|
-
didOpen: :handle_text_document_did_open,
|
137
|
-
didSave: :handle_text_document_did_save,
|
138
|
-
},
|
139
|
-
}
|
140
|
-
end
|
141
|
-
|
142
|
-
def handle_initialize(params)
|
143
|
-
@session = Session.new(params[:root_uri])
|
144
|
-
@completion_provider = CompletionProvider.new(@session)
|
145
|
-
@hover_provider = HoverProvider.new(@session)
|
146
|
-
@signature_provider = SignatureProvider.new(@session)
|
147
|
-
@definition_provider = DefinitionProvider.new(@session)
|
148
|
-
|
149
|
-
(InitializationProvider.new(session: session, notifier: notifier).provide || []).each { |notification| after_notifications.push(notification) }
|
150
|
-
|
151
|
-
LSP::Interface::InitializeResult.new(
|
152
|
-
capabilities: LSP::Interface::ServerCapabilities.new(
|
153
|
-
text_document_sync: LSP::Interface::TextDocumentSyncOptions.new(
|
154
|
-
change: LSP::Constant::TextDocumentSyncKind::FULL,
|
155
|
-
save: LSP::Interface::SaveOptions.new(
|
156
|
-
include_text: true,
|
157
|
-
),
|
158
|
-
),
|
159
|
-
completion_provider: LSP::Interface::CompletionOptions.new(
|
160
|
-
resolve_provider: false,
|
161
|
-
trigger_characters: ['.', '@', '[', ':', '!', '<'],
|
162
|
-
),
|
163
|
-
hover_provider: true,
|
164
|
-
definition_provider: true,
|
165
|
-
signature_help_provider: LSP::Interface::SignatureHelpOptions.new(
|
166
|
-
trigger_characters: ['(', ','],
|
167
|
-
),
|
168
|
-
),
|
169
|
-
)
|
170
|
-
rescue => e
|
171
|
-
LanguageServer::Protocol::Interface::ResponseError.new(
|
172
|
-
message: "Failed to initialize yoda: #{e.class} #{e.message}",
|
173
|
-
code: LanguageServer::Protocol::Constant::InitializeError::UNKNOWN_PROTOCOL_VERSION,
|
174
|
-
data: LanguageServer::Protocol::Interface::InitializeError.new(retry: false),
|
175
|
-
)
|
176
|
-
end
|
177
|
-
|
178
|
-
def handle_initialized(_params)
|
179
|
-
session.client_initialized = true
|
180
|
-
end
|
181
|
-
|
182
|
-
def handle_shutdown(_params)
|
183
|
-
end
|
184
|
-
|
185
|
-
def handle_exit(_params)
|
186
|
-
end
|
187
|
-
|
188
|
-
module TextDocument
|
189
|
-
module CompletionTrigggerKind
|
190
|
-
INVOKED = 1
|
191
|
-
TRIGGER_CHARACTER = 2
|
192
|
-
end
|
193
|
-
|
194
|
-
def handle_text_document_did_open(params)
|
195
|
-
uri = params[:text_document][:uri]
|
196
|
-
text = params[:text_document][:text]
|
197
|
-
session.file_store.store(uri, text)
|
198
|
-
end
|
199
|
-
|
200
|
-
def handle_text_document_did_save(params)
|
201
|
-
uri = params[:text_document][:uri]
|
202
|
-
|
203
|
-
session.reparse_doc(uri)
|
204
|
-
end
|
205
|
-
|
206
|
-
def handle_text_document_did_change(params)
|
207
|
-
uri = params[:text_document][:uri]
|
208
|
-
text = params[:content_changes].first[:text]
|
209
|
-
session.file_store.store(uri, text)
|
210
|
-
end
|
211
|
-
|
212
|
-
def handle_text_document_completion(params)
|
213
|
-
uri = params[:text_document][:uri]
|
214
|
-
position = params[:position]
|
215
|
-
|
216
|
-
notifier.busy(type: :text_document_completion) do
|
217
|
-
completion_provider&.complete(uri, position)
|
218
|
-
end
|
219
|
-
end
|
220
|
-
|
221
|
-
def handle_text_document_hover(params)
|
222
|
-
uri = params[:text_document][:uri]
|
223
|
-
position = params[:position]
|
224
|
-
|
225
|
-
notifier.busy(type: :text_document_hover) do
|
226
|
-
hover_provider&.request_hover(uri, position)
|
227
|
-
end
|
228
|
-
end
|
229
|
-
|
230
|
-
def handle_text_document_signature_help(params)
|
231
|
-
uri = params[:text_document][:uri]
|
232
|
-
position = params[:position]
|
233
|
-
|
234
|
-
notifier.busy(type: :text_document_signature_help) do
|
235
|
-
signature_provider&.provide(uri, position)
|
236
|
-
end
|
237
|
-
end
|
238
|
-
|
239
|
-
def handle_text_document_definition(params)
|
240
|
-
uri = params[:text_document][:uri]
|
241
|
-
position = params[:position]
|
242
|
-
|
243
|
-
notifier.busy(type: :text_document_definition) do
|
244
|
-
definition_provider&.provide(uri, position)
|
245
|
-
end
|
246
|
-
end
|
48
|
+
def deserialize(hash)
|
49
|
+
Deserializer.new.deserialize(hash || {})
|
247
50
|
end
|
248
|
-
include TextDocument
|
249
51
|
end
|
250
52
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Yoda
|
2
|
+
class Server
|
3
|
+
# Wrapper class for writer to make thread safe
|
4
|
+
class ConcurrentWriter
|
5
|
+
# @param [::LanguageServer::Protocol::Transport::Stdio::Writer]
|
6
|
+
def initialize(channel)
|
7
|
+
@channel = channel
|
8
|
+
@mutex = Mutex.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def write(*args)
|
12
|
+
@mutex.synchronize { @channel.write(*args) }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'concurrent'
|
2
|
+
|
3
|
+
module Yoda
|
4
|
+
class Server
|
5
|
+
class FileStore
|
6
|
+
def initialize
|
7
|
+
@cache = Concurrent::Map.new
|
8
|
+
end
|
9
|
+
|
10
|
+
# @param uri_string [String]
|
11
|
+
# @return [String, nil]
|
12
|
+
def get(uri_string)
|
13
|
+
@cache[uri_string]
|
14
|
+
end
|
15
|
+
|
16
|
+
# @param uri_string [String]
|
17
|
+
# @param text [String]
|
18
|
+
def store(uri_string, text)
|
19
|
+
return unless program_file_uri?(uri_string)
|
20
|
+
@cache[uri_string] = text
|
21
|
+
end
|
22
|
+
|
23
|
+
# @param uri_string [String]
|
24
|
+
def load(uri_string)
|
25
|
+
store(uri_string, read(uri_string))
|
26
|
+
end
|
27
|
+
|
28
|
+
# @param uri_string [String]
|
29
|
+
def read(uri_string)
|
30
|
+
path = self.class.path_of_uri(uri_string)
|
31
|
+
fail ArgumentError unless path
|
32
|
+
File.read(path)
|
33
|
+
end
|
34
|
+
|
35
|
+
# @param path [String]
|
36
|
+
def self.uri_of_path(path)
|
37
|
+
"file://#{File.expand_path(path)}"
|
38
|
+
end
|
39
|
+
|
40
|
+
# @param uri_string [String]
|
41
|
+
def self.path_of_uri(uri_string)
|
42
|
+
uri = URI.parse(uri_string)
|
43
|
+
return nil unless uri.scheme == 'file'
|
44
|
+
uri.path
|
45
|
+
rescue URI::InvalidURIError
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
|
49
|
+
# @param uri_string [String]
|
50
|
+
def program_file_uri?(uri_string)
|
51
|
+
%w(.c .rb).include?(File.extname(URI.parse(uri_string).path))
|
52
|
+
rescue URI::InvalidURIError => _e
|
53
|
+
false
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|