yoda-language-server 0.6.2 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|